aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.htaccess25
-rw-r--r--Bugzilla.pm871
-rw-r--r--Bugzilla/Attachment.pm1030
-rw-r--r--Bugzilla/Attachment/PatchReader.pm295
-rw-r--r--Bugzilla/Auth.pm524
-rw-r--r--Bugzilla/Auth/Login.pm134
-rw-r--r--Bugzilla/Auth/Login/CGI.pm71
-rw-r--r--Bugzilla/Auth/Login/Cookie.pm88
-rw-r--r--Bugzilla/Auth/Login/Env.pm54
-rw-r--r--Bugzilla/Auth/Login/Stack.pm100
-rw-r--r--Bugzilla/Auth/Persist/Cookie.pm163
-rw-r--r--Bugzilla/Auth/Verify.pm235
-rw-r--r--Bugzilla/Auth/Verify/DB.pm108
-rw-r--r--Bugzilla/Auth/Verify/LDAP.pm199
-rw-r--r--Bugzilla/Auth/Verify/RADIUS.pm64
-rw-r--r--Bugzilla/Auth/Verify/Stack.pm89
-rw-r--r--Bugzilla/Bug.pm4093
-rw-r--r--Bugzilla/BugMail.pm587
-rw-r--r--Bugzilla/CGI.pm624
-rw-r--r--Bugzilla/Chart.pm445
-rw-r--r--Bugzilla/Classification.pm221
-rw-r--r--Bugzilla/Comment.pm316
-rw-r--r--Bugzilla/Component.pm657
-rw-r--r--Bugzilla/Config.pm408
-rw-r--r--Bugzilla/Config/Admin.pm63
-rw-r--r--Bugzilla/Config/Advanced.pm67
-rw-r--r--Bugzilla/Config/Attachment.pm91
-rw-r--r--Bugzilla/Config/Auth.pm128
-rw-r--r--Bugzilla/Config/BugChange.pm103
-rw-r--r--Bugzilla/Config/BugFields.pm120
-rw-r--r--Bugzilla/Config/Common.pm461
-rw-r--r--Bugzilla/Config/Core.pm68
-rw-r--r--Bugzilla/Config/DependencyGraph.pm52
-rw-r--r--Bugzilla/Config/General.pm83
-rw-r--r--Bugzilla/Config/GroupSecurity.pm102
-rw-r--r--Bugzilla/Config/LDAP.pm87
-rw-r--r--Bugzilla/Config/MTA.pm102
-rw-r--r--Bugzilla/Config/PatchViewer.pm75
-rw-r--r--Bugzilla/Config/Query.pm80
-rw-r--r--Bugzilla/Config/RADIUS.pm60
-rw-r--r--Bugzilla/Config/ShadowDB.pm73
-rw-r--r--Bugzilla/Config/UserMatch.pm64
-rw-r--r--Bugzilla/Constants.pm624
-rw-r--r--Bugzilla/DB.pm2553
-rw-r--r--Bugzilla/DB/Mysql.pm1106
-rw-r--r--Bugzilla/DB/Oracle.pm756
-rw-r--r--Bugzilla/DB/Pg.pm354
-rw-r--r--Bugzilla/DB/Schema.pm2854
-rw-r--r--Bugzilla/DB/Schema/Mysql.pm399
-rw-r--r--Bugzilla/DB/Schema/Oracle.pm439
-rw-r--r--Bugzilla/DB/Schema/Pg.pm173
-rw-r--r--Bugzilla/Error.pm270
-rw-r--r--Bugzilla/Extension.pm813
-rw-r--r--Bugzilla/Field.pm1225
-rw-r--r--Bugzilla/Field/Choice.pm347
-rw-r--r--Bugzilla/Field/ChoiceInterface.pm273
-rw-r--r--Bugzilla/Flag.pm1066
-rw-r--r--Bugzilla/FlagType.pm509
-rw-r--r--Bugzilla/Group.pm589
-rw-r--r--Bugzilla/Hook.pm1312
-rw-r--r--Bugzilla/Install.pm468
-rw-r--r--Bugzilla/Install/CPAN.pm314
-rw-r--r--Bugzilla/Install/DB.pm3456
-rw-r--r--Bugzilla/Install/Filesystem.pm837
-rw-r--r--Bugzilla/Install/Localconfig.pm515
-rw-r--r--Bugzilla/Install/Requirements.pm900
-rw-r--r--Bugzilla/Install/Util.pm886
-rw-r--r--Bugzilla/Job/Mailer.pm57
-rw-r--r--Bugzilla/JobQueue.pm110
-rw-r--r--Bugzilla/JobQueue/Runner.pm228
-rw-r--r--Bugzilla/Keyword.pm176
-rw-r--r--Bugzilla/Mailer.pm220
-rw-r--r--Bugzilla/Migrate.pm1173
-rw-r--r--Bugzilla/Migrate/Gnats.pm712
-rw-r--r--Bugzilla/Milestone.pm375
-rw-r--r--Bugzilla/Object.pm1197
-rw-r--r--Bugzilla/Product.pm1076
-rw-r--r--Bugzilla/Search.pm2516
-rw-r--r--Bugzilla/Search/Quicksearch.pm617
-rw-r--r--Bugzilla/Search/Recent.pm166
-rw-r--r--Bugzilla/Search/Saved.pm406
-rw-r--r--Bugzilla/Series.pm286
-rw-r--r--Bugzilla/Status.pm317
-rw-r--r--Bugzilla/Template.pm1120
-rw-r--r--Bugzilla/Template/Context.pm104
-rw-r--r--Bugzilla/Template/Plugin/Bugzilla.pm64
-rw-r--r--Bugzilla/Template/Plugin/Hook.pm165
-rw-r--r--Bugzilla/Template/Plugin/User.pm65
-rw-r--r--Bugzilla/Token.pm618
-rw-r--r--Bugzilla/Update.pm209
-rw-r--r--Bugzilla/User.pm2456
-rw-r--r--Bugzilla/User/Setting.pm433
-rw-r--r--Bugzilla/User/Setting/Lang.pm60
-rw-r--r--Bugzilla/User/Setting/Skin.pm79
-rw-r--r--Bugzilla/User/Setting/Timezone.pm71
-rw-r--r--Bugzilla/Util.pm1085
-rw-r--r--Bugzilla/Version.pm244
-rw-r--r--Bugzilla/WebService.pm339
-rw-r--r--Bugzilla/WebService/Bug.pm3149
-rw-r--r--Bugzilla/WebService/Bugzilla.pm270
-rw-r--r--Bugzilla/WebService/Constants.pm185
-rw-r--r--Bugzilla/WebService/Product.pm200
-rw-r--r--Bugzilla/WebService/README18
-rw-r--r--Bugzilla/WebService/Server.pm63
-rw-r--r--Bugzilla/WebService/Server/JSONRPC.pm559
-rw-r--r--Bugzilla/WebService/Server/XMLRPC.pm347
-rw-r--r--Bugzilla/WebService/User.pm631
-rw-r--r--Bugzilla/WebService/Util.pm149
-rw-r--r--Bugzilla/Whine.pm132
-rw-r--r--Bugzilla/Whine/Query.pm136
-rw-r--r--Bugzilla/Whine/Schedule.pm170
-rw-r--r--README92
-rwxr-xr-xadmin.cgi49
-rwxr-xr-xattachment.cgi761
-rwxr-xr-xbuglist.cgi1228
-rw-r--r--bugzilla.dtd88
-rwxr-xr-xchart.cgi371
-rwxr-xr-xchecksetup.pl516
-rwxr-xr-xcolchange.cgi202
-rwxr-xr-xcollectstats.pl530
-rwxr-xr-xconfig.cgi166
-rw-r--r--contrib/README73
-rwxr-xr-xcontrib/bugzilla-queue.rhel109
-rwxr-xr-xcontrib/bugzilla-queue.suse174
-rw-r--r--contrib/bugzilla-submit/README46
-rwxr-xr-xcontrib/bugzilla-submit/bugdata.txt13
-rwxr-xr-xcontrib/bugzilla-submit/bugzilla-submit301
-rwxr-xr-xcontrib/bugzilla-submit/bugzilla-submit.xml221
-rwxr-xr-xcontrib/bugzilla_ldapsync.rb183
-rwxr-xr-xcontrib/bz_webservice_demo.pl420
-rwxr-xr-xcontrib/bzdbcopy.pl254
-rwxr-xr-xcontrib/cmdline/bugcount7
-rwxr-xr-xcontrib/cmdline/bugids32
-rwxr-xr-xcontrib/cmdline/buglist31
-rwxr-xr-xcontrib/cmdline/bugs6
-rwxr-xr-xcontrib/cmdline/bugslink8
-rwxr-xr-xcontrib/cmdline/makequery108
-rwxr-xr-xcontrib/cmdline/query.conf49
-rwxr-xr-xcontrib/console.pl186
-rwxr-xr-xcontrib/convert-workflow.pl135
-rwxr-xr-xcontrib/cvs-update.pl51
-rwxr-xr-xcontrib/extension-convert.pl303
-rwxr-xr-xcontrib/fixperms.pl28
-rwxr-xr-xcontrib/jb2bz.py308
-rwxr-xr-xcontrib/merge-users.pl240
-rwxr-xr-xcontrib/mysqld-watcher.pl117
-rwxr-xr-xcontrib/new-yui.sh17
-rwxr-xr-xcontrib/recode.pl380
-rwxr-xr-xcontrib/sendbugmail.pl110
-rwxr-xr-xcontrib/sendunsentbugmail.pl66
-rwxr-xr-xcontrib/syncLDAP.pl298
-rwxr-xr-xcontrib/yp_nomail.sh77
-rwxr-xr-xcreateaccount.cgi82
-rwxr-xr-xdescribecomponents.cgi88
-rwxr-xr-xdescribekeywords.cgi46
-rw-r--r--docs/bugzilla.ent47
-rw-r--r--docs/en/README.docs155
-rw-r--r--docs/en/html/Bugzilla-Guide.html18111
-rw-r--r--docs/en/html/about.html174
-rw-r--r--docs/en/html/administration.html435
-rw-r--r--docs/en/html/api/Bugzilla.html262
-rw-r--r--docs/en/html/api/Bugzilla/Attachment.html270
-rw-r--r--docs/en/html/api/Bugzilla/Auth.html411
-rw-r--r--docs/en/html/api/Bugzilla/Auth/Login.html126
-rw-r--r--docs/en/html/api/Bugzilla/Auth/Verify.html142
-rw-r--r--docs/en/html/api/Bugzilla/CGI.html119
-rw-r--r--docs/en/html/api/Bugzilla/Classification.html87
-rw-r--r--docs/en/html/api/Bugzilla/Comment.html145
-rw-r--r--docs/en/html/api/Bugzilla/Component.html281
-rw-r--r--docs/en/html/api/Bugzilla/Config.html100
-rw-r--r--docs/en/html/api/Bugzilla/Config/Common.html66
-rw-r--r--docs/en/html/api/Bugzilla/DB.html1422
-rw-r--r--docs/en/html/api/Bugzilla/DB/Mysql.html40
-rw-r--r--docs/en/html/api/Bugzilla/DB/Oracle.html40
-rw-r--r--docs/en/html/api/Bugzilla/DB/Pg.html40
-rw-r--r--docs/en/html/api/Bugzilla/DB/Schema.html767
-rw-r--r--docs/en/html/api/Bugzilla/Error.html83
-rw-r--r--docs/en/html/api/Bugzilla/Extension.html546
-rw-r--r--docs/en/html/api/Bugzilla/Field.html466
-rw-r--r--docs/en/html/api/Bugzilla/Field/Choice.html93
-rw-r--r--docs/en/html/api/Bugzilla/Field/ChoiceInterface.html150
-rw-r--r--docs/en/html/api/Bugzilla/Flag.html207
-rw-r--r--docs/en/html/api/Bugzilla/FlagType.html223
-rw-r--r--docs/en/html/api/Bugzilla/Group.html160
-rw-r--r--docs/en/html/api/Bugzilla/Hook.html1322
-rw-r--r--docs/en/html/api/Bugzilla/Install.html94
-rw-r--r--docs/en/html/api/Bugzilla/Install/CPAN.html79
-rw-r--r--docs/en/html/api/Bugzilla/Install/DB.html76
-rw-r--r--docs/en/html/api/Bugzilla/Install/Filesystem.html88
-rw-r--r--docs/en/html/api/Bugzilla/Install/Localconfig.html124
-rw-r--r--docs/en/html/api/Bugzilla/Install/Requirements.html201
-rw-r--r--docs/en/html/api/Bugzilla/Install/Util.html259
-rw-r--r--docs/en/html/api/Bugzilla/JobQueue.html54
-rw-r--r--docs/en/html/api/Bugzilla/JobQueue/Runner.html45
-rw-r--r--docs/en/html/api/Bugzilla/Keyword.html72
-rw-r--r--docs/en/html/api/Bugzilla/Migrate.html378
-rw-r--r--docs/en/html/api/Bugzilla/Milestone.html191
-rw-r--r--docs/en/html/api/Bugzilla/Object.html575
-rw-r--r--docs/en/html/api/Bugzilla/Product.html279
-rw-r--r--docs/en/html/api/Bugzilla/Search/Recent.html44
-rw-r--r--docs/en/html/api/Bugzilla/Search/Saved.html133
-rw-r--r--docs/en/html/api/Bugzilla/Status.html123
-rw-r--r--docs/en/html/api/Bugzilla/Template.html95
-rw-r--r--docs/en/html/api/Bugzilla/Template/Plugin/Bugzilla.html45
-rw-r--r--docs/en/html/api/Bugzilla/Template/Plugin/Hook.html98
-rw-r--r--docs/en/html/api/Bugzilla/Template/Plugin/User.html45
-rw-r--r--docs/en/html/api/Bugzilla/Token.html233
-rw-r--r--docs/en/html/api/Bugzilla/Update.html66
-rw-r--r--docs/en/html/api/Bugzilla/User.html641
-rw-r--r--docs/en/html/api/Bugzilla/User/Setting.html176
-rw-r--r--docs/en/html/api/Bugzilla/User/Setting/Lang.html49
-rw-r--r--docs/en/html/api/Bugzilla/User/Setting/Skin.html49
-rw-r--r--docs/en/html/api/Bugzilla/User/Setting/Timezone.html53
-rw-r--r--docs/en/html/api/Bugzilla/Util.html428
-rw-r--r--docs/en/html/api/Bugzilla/Version.html78
-rw-r--r--docs/en/html/api/Bugzilla/WebService.html345
-rw-r--r--docs/en/html/api/Bugzilla/WebService/Bug.html2557
-rw-r--r--docs/en/html/api/Bugzilla/WebService/Bugzilla.html253
-rw-r--r--docs/en/html/api/Bugzilla/WebService/Product.html195
-rw-r--r--docs/en/html/api/Bugzilla/WebService/Server/JSONRPC.html171
-rw-r--r--docs/en/html/api/Bugzilla/WebService/Server/XMLRPC.html126
-rw-r--r--docs/en/html/api/Bugzilla/WebService/User.html532
-rw-r--r--docs/en/html/api/Bugzilla/WebService/Util.html77
-rw-r--r--docs/en/html/api/Bugzilla/Whine.html113
-rw-r--r--docs/en/html/api/Bugzilla/Whine/Query.html126
-rw-r--r--docs/en/html/api/Bugzilla/Whine/Schedule.html139
-rw-r--r--docs/en/html/api/checksetup.html267
-rw-r--r--docs/en/html/api/contrib/bz_webservice_demo.html301
-rw-r--r--docs/en/html/api/contrib/bzdbcopy.html54
-rw-r--r--docs/en/html/api/contrib/console.html64
-rw-r--r--docs/en/html/api/contrib/extension-convert.html38
-rw-r--r--docs/en/html/api/contrib/merge-users.html46
-rw-r--r--docs/en/html/api/contrib/recode.html102
-rw-r--r--docs/en/html/api/email_in.html158
-rw-r--r--docs/en/html/api/extensions/create.html38
-rw-r--r--docs/en/html/api/importxml.html103
-rw-r--r--docs/en/html/api/index.html349
-rw-r--r--docs/en/html/api/install-module.html98
-rw-r--r--docs/en/html/api/jobqueue.html77
-rw-r--r--docs/en/html/api/migrate.html78
-rw-r--r--docs/en/html/api/sanitycheck.html69
-rw-r--r--docs/en/html/attachments.html364
-rw-r--r--docs/en/html/bug_page.html510
-rw-r--r--docs/en/html/bug_status_workflow.html162
-rw-r--r--docs/en/html/bugreports.html353
-rw-r--r--docs/en/html/classifications.html165
-rw-r--r--docs/en/html/cmdline-bugmail.html188
-rw-r--r--docs/en/html/cmdline.html281
-rw-r--r--docs/en/html/components.html224
-rw-r--r--docs/en/html/configuration.html1867
-rw-r--r--docs/en/html/conventions.html416
-rw-r--r--docs/en/html/copyright.html171
-rw-r--r--docs/en/html/credits.html273
-rw-r--r--docs/en/html/cust-change-permissions.html360
-rw-r--r--docs/en/html/cust-skins.html188
-rw-r--r--docs/en/html/cust-templates.html916
-rw-r--r--docs/en/html/custom-fields.html379
-rw-r--r--docs/en/html/customization.html206
-rw-r--r--docs/en/html/disclaimer.html172
-rw-r--r--docs/en/html/edit-values.html212
-rw-r--r--docs/en/html/extensions.html161
-rw-r--r--docs/en/html/extraconfig.html487
-rw-r--r--docs/en/html/flags-overview.html981
-rw-r--r--docs/en/html/flags.html194
-rw-r--r--docs/en/html/general-advice.html202
-rw-r--r--docs/en/html/gfdl-0.html167
-rw-r--r--docs/en/html/gfdl-1.html202
-rw-r--r--docs/en/html/gfdl-10.html165
-rw-r--r--docs/en/html/gfdl-2.html159
-rw-r--r--docs/en/html/gfdl-3.html181
-rw-r--r--docs/en/html/gfdl-4.html275
-rw-r--r--docs/en/html/gfdl-5.html168
-rw-r--r--docs/en/html/gfdl-6.html158
-rw-r--r--docs/en/html/gfdl-7.html161
-rw-r--r--docs/en/html/gfdl-8.html157
-rw-r--r--docs/en/html/gfdl-9.html154
-rw-r--r--docs/en/html/gfdl-howto.html174
-rw-r--r--docs/en/html/gfdl.html220
-rw-r--r--docs/en/html/glossary.html1081
-rw-r--r--docs/en/html/groups.html587
-rw-r--r--docs/en/html/hintsandtips.html280
-rw-r--r--docs/en/html/index.html657
-rw-r--r--docs/en/html/install-perlmodules-manual.html162
-rw-r--r--docs/en/html/installation.html1136
-rw-r--r--docs/en/html/installing-bugzilla.html354
-rw-r--r--docs/en/html/integration.html157
-rw-r--r--docs/en/html/keywords.html168
-rw-r--r--docs/en/html/lifecycle.html186
-rw-r--r--docs/en/html/milestones.html203
-rw-r--r--docs/en/html/modules-manual-download.html366
-rw-r--r--docs/en/html/modules-manual-instructions.html239
-rw-r--r--docs/en/html/modules-manual-optional.html236
-rw-r--r--docs/en/html/multiple-bz-dbs.html227
-rw-r--r--docs/en/html/myaccount.html291
-rw-r--r--docs/en/html/newversions.html175
-rw-r--r--docs/en/html/nonroot.html745
-rw-r--r--docs/en/html/os-specific.html827
-rw-r--r--docs/en/html/parameters.html1343
-rw-r--r--docs/en/html/paranoid-security.html196
-rw-r--r--docs/en/html/patches.html169
-rw-r--r--docs/en/html/products.html817
-rw-r--r--docs/en/html/query.html625
-rw-r--r--docs/en/html/quips.html184
-rw-r--r--docs/en/html/reporting.html319
-rw-r--r--docs/en/html/sanitycheck.html206
-rw-r--r--docs/en/html/security-bugzilla.html184
-rw-r--r--docs/en/html/security-os.html292
-rw-r--r--docs/en/html/security-webserver.html414
-rw-r--r--docs/en/html/security.html201
-rw-r--r--docs/en/html/timetracking.html179
-rw-r--r--docs/en/html/trbl-dbdsponge.html226
-rw-r--r--docs/en/html/trbl-index.html172
-rw-r--r--docs/en/html/trbl-passwd-encryption.html177
-rw-r--r--docs/en/html/trbl-perlmodule.html177
-rw-r--r--docs/en/html/trbl-relogin-everyone.html268
-rw-r--r--docs/en/html/trbl-testserver.html186
-rw-r--r--docs/en/html/troubleshooting.html200
-rw-r--r--docs/en/html/upgrade.html941
-rw-r--r--docs/en/html/useradmin.html718
-rw-r--r--docs/en/html/userpreferences.html720
-rw-r--r--docs/en/html/using-intro.html167
-rw-r--r--docs/en/html/using.html346
-rw-r--r--docs/en/html/versions.html173
-rw-r--r--docs/en/html/voting.html199
-rw-r--r--docs/en/html/whining.html530
-rw-r--r--docs/en/images/bzLifecycle.pngbin0 -> 36761 bytes
-rw-r--r--docs/en/images/bzLifecycle.xml1724
-rw-r--r--docs/en/images/callouts/1.gifbin0 -> 890 bytes
-rw-r--r--docs/en/images/callouts/2.gifbin0 -> 907 bytes
-rw-r--r--docs/en/images/callouts/3.gifbin0 -> 914 bytes
-rw-r--r--docs/en/images/caution.gifbin0 -> 134 bytes
-rw-r--r--docs/en/images/note.gifbin0 -> 226 bytes
-rw-r--r--docs/en/images/tip.gifbin0 -> 1229 bytes
-rw-r--r--docs/en/images/warning.gifbin0 -> 140 bytes
-rw-r--r--docs/en/pdf/Bugzilla-Guide.pdf30483
-rw-r--r--docs/en/rel_notes.txt3028
-rw-r--r--docs/en/txt/Bugzilla-Guide.txt6041
-rw-r--r--docs/en/xml/Bugzilla-Guide.xml174
-rw-r--r--docs/en/xml/about.xml213
-rw-r--r--docs/en/xml/administration.xml3029
-rw-r--r--docs/en/xml/bugzilla.ent47
-rw-r--r--docs/en/xml/conventions.xml164
-rw-r--r--docs/en/xml/customization.xml609
-rw-r--r--docs/en/xml/gfdl.xml445
-rw-r--r--docs/en/xml/glossary.xml548
-rw-r--r--docs/en/xml/index.xml21
-rw-r--r--docs/en/xml/installation.xml2445
-rw-r--r--docs/en/xml/modules.xml177
-rw-r--r--docs/en/xml/patches.xml131
-rw-r--r--docs/en/xml/security.xml270
-rw-r--r--docs/en/xml/troubleshooting.xml277
-rw-r--r--docs/en/xml/using.xml1959
-rw-r--r--docs/lib/Pod/Simple/HTML/Bugzilla.pm78
-rw-r--r--docs/lib/Pod/Simple/HTMLBatch/Bugzilla.pm119
-rwxr-xr-xdocs/makedocs.pl228
-rw-r--r--docs/style.css107
-rwxr-xr-xduplicates.cgi263
-rwxr-xr-xeditclassifications.cgi236
-rwxr-xr-xeditcomponents.cgi256
-rwxr-xr-xeditfields.cgi175
-rwxr-xr-xeditflagtypes.cgi710
-rwxr-xr-xeditgroups.cgi472
-rwxr-xr-xeditkeywords.cgi187
-rwxr-xr-xeditmilestones.cgi225
-rwxr-xr-xeditparams.cgi161
-rwxr-xr-xeditproducts.cgi426
-rwxr-xr-xeditsettings.cgi73
-rwxr-xr-xeditusers.cgi793
-rwxr-xr-xeditvalues.cgi197
-rwxr-xr-xeditversions.cgi223
-rwxr-xr-xeditwhines.cgi415
-rwxr-xr-xeditworkflow.cgi151
-rwxr-xr-xemail_in.pl574
-rwxr-xr-xenter_bug.cgi591
-rw-r--r--extensions/BmpConvert/Config.pm33
-rw-r--r--extensions/BmpConvert/Extension.pm57
-rw-r--r--extensions/BmpConvert/disabled0
-rw-r--r--extensions/Example/Config.pm42
-rw-r--r--extensions/Example/Extension.pm777
-rw-r--r--extensions/Example/disabled0
-rw-r--r--extensions/Example/lib/Auth/Login.pm32
-rw-r--r--extensions/Example/lib/Auth/Verify.pm31
-rw-r--r--extensions/Example/lib/Config.pm41
-rw-r--r--extensions/Example/lib/Util.pm28
-rw-r--r--extensions/Example/lib/WebService.pm32
-rw-r--r--extensions/Example/template/en/default/account/prefs/my_tab.html.tmpl30
-rw-r--r--extensions/Example/template/en/default/admin/params/example.html.tmpl28
-rw-r--r--extensions/Example/template/en/default/hook/account/prefs/prefs-tabs.html.tmpl23
-rw-r--r--extensions/Example/template/en/default/hook/admin/sanitycheck/messages-statuses.html.tmpl36
-rw-r--r--extensions/Example/template/en/default/hook/global/setting-descs-settings.none.tmpl6
-rw-r--r--extensions/Example/template/en/default/hook/global/user-error-errors.html.tmpl15
-rw-r--r--extensions/Example/template/en/default/pages/example.html.tmpl32
-rw-r--r--extensions/Example/template/en/default/setup/strings.txt.pl24
-rw-r--r--extensions/OldBugMove/Config.pm25
-rw-r--r--extensions/OldBugMove/Extension.pm208
-rw-r--r--extensions/OldBugMove/disabled0
-rw-r--r--extensions/OldBugMove/lib/Params.pm60
-rw-r--r--extensions/OldBugMove/template/en/default/admin/params/oldbugmove.html.tmpl40
-rw-r--r--extensions/OldBugMove/template/en/default/hook/bug/edit-after_comment_textarea.html.tmpl27
-rw-r--r--extensions/OldBugMove/template/en/default/hook/bug/format_comment-type.txt.tmpl29
-rw-r--r--extensions/OldBugMove/template/en/default/hook/global/user-error-auth_failure_action.html.tmpl23
-rw-r--r--extensions/OldBugMove/template/en/default/hook/global/user-error-errors.html.tmpl29
-rw-r--r--extensions/OldBugMove/template/en/default/hook/list/edit-multiple-after_groups.html.tmpl28
-rw-r--r--extensions/Voting/Extension.pm870
-rw-r--r--extensions/Voting/disabled0
-rw-r--r--extensions/Voting/template/en/default/hook/account/prefs/email-relationships.html.tmpl22
-rw-r--r--extensions/Voting/template/en/default/hook/admin/products/edit-common-rows.html.tmpl60
-rw-r--r--extensions/Voting/template/en/default/hook/admin/products/updated-changes.html.tmpl102
-rw-r--r--extensions/Voting/template/en/default/hook/admin/sanitycheck/messages-statuses.html.tmpl41
-rw-r--r--extensions/Voting/template/en/default/hook/admin/users/confirm-delete-warn_safe.html.tmpl38
-rw-r--r--extensions/Voting/template/en/default/hook/bug/edit-after_importance.html.tmpl41
-rw-r--r--extensions/Voting/template/en/default/hook/bug/format_comment-type.txt.tmpl23
-rw-r--r--extensions/Voting/template/en/default/hook/bug/process/header-title.html.tmpl24
-rw-r--r--extensions/Voting/template/en/default/hook/bug/process/results-title.html.tmpl21
-rw-r--r--extensions/Voting/template/en/default/hook/global/code-error-errors.html.tmpl25
-rw-r--r--extensions/Voting/template/en/default/hook/global/field-descs-end.none.tmpl23
-rw-r--r--extensions/Voting/template/en/default/hook/global/reason-descs-end.none.tmpl23
-rw-r--r--extensions/Voting/template/en/default/hook/global/user-error-errors.html.tmpl55
-rw-r--r--extensions/Voting/template/en/default/hook/search/form-after_freetext_fields.html.tmpl28
-rw-r--r--extensions/Voting/template/en/default/hook/search/search-report-select-rep_fields.html.tmpl21
-rw-r--r--extensions/Voting/template/en/default/pages/voting.html.tmpl69
-rw-r--r--extensions/Voting/template/en/default/pages/voting/bug.html.tmpl61
-rw-r--r--extensions/Voting/template/en/default/pages/voting/user.html.tmpl186
-rw-r--r--extensions/Voting/template/en/default/voting/delete-all.html.tmpl52
-rw-r--r--extensions/Voting/template/en/default/voting/votes-removed.txt.tmpl55
-rw-r--r--extensions/Voting/web/style.css24
-rwxr-xr-xextensions/create.pl86
-rw-r--r--images/favicon.icobin0 -> 4150 bytes
-rw-r--r--images/padlock.pngbin0 -> 1066 bytes
-rwxr-xr-ximportxml.pl1383
-rwxr-xr-xindex.cgi75
-rwxr-xr-xinstall-module.pl174
-rwxr-xr-xjobqueue.pl79
-rw-r--r--js/TUI.js109
-rw-r--r--js/attachment.js350
-rw-r--r--js/bug.js131
-rw-r--r--js/change-columns.js142
-rw-r--r--js/comments.js99
-rw-r--r--js/expanding-tree.js157
-rw-r--r--js/field.js754
-rw-r--r--js/flag.js75
-rw-r--r--js/global.js131
-rw-r--r--js/params.js61
-rw-r--r--js/productform.js408
-rw-r--r--js/util.js273
-rw-r--r--js/yui/animation/animation-min.js23
-rw-r--r--js/yui/assets/skins/sam/ajax-loader.gifbin0 -> 3208 bytes
-rw-r--r--js/yui/assets/skins/sam/asc.gifbin0 -> 177 bytes
-rw-r--r--js/yui/assets/skins/sam/autocomplete.css7
-rw-r--r--js/yui/assets/skins/sam/back-h.pngbin0 -> 334 bytes
-rw-r--r--js/yui/assets/skins/sam/back-v.pngbin0 -> 338 bytes
-rw-r--r--js/yui/assets/skins/sam/bar-h.pngbin0 -> 365 bytes
-rw-r--r--js/yui/assets/skins/sam/bar-v.pngbin0 -> 387 bytes
-rw-r--r--js/yui/assets/skins/sam/bg-h.gifbin0 -> 212 bytes
-rw-r--r--js/yui/assets/skins/sam/bg-v.gifbin0 -> 481 bytes
-rw-r--r--js/yui/assets/skins/sam/blankimage.pngbin0 -> 2314 bytes
-rw-r--r--js/yui/assets/skins/sam/button.css7
-rw-r--r--js/yui/assets/skins/sam/calendar.css8
-rw-r--r--js/yui/assets/skins/sam/carousel.css7
-rw-r--r--js/yui/assets/skins/sam/check0.gifbin0 -> 608 bytes
-rw-r--r--js/yui/assets/skins/sam/check1.gifbin0 -> 622 bytes
-rw-r--r--js/yui/assets/skins/sam/check2.gifbin0 -> 609 bytes
-rw-r--r--js/yui/assets/skins/sam/colorpicker.css7
-rw-r--r--js/yui/assets/skins/sam/container.css7
-rw-r--r--js/yui/assets/skins/sam/datatable.css8
-rw-r--r--js/yui/assets/skins/sam/desc.gifbin0 -> 177 bytes
-rw-r--r--js/yui/assets/skins/sam/dt-arrow-dn.pngbin0 -> 116 bytes
-rw-r--r--js/yui/assets/skins/sam/dt-arrow-up.pngbin0 -> 116 bytes
-rw-r--r--js/yui/assets/skins/sam/editor-knob.gifbin0 -> 138 bytes
-rw-r--r--js/yui/assets/skins/sam/editor-sprite-active.gifbin0 -> 5614 bytes
-rw-r--r--js/yui/assets/skins/sam/editor-sprite.gifbin0 -> 5690 bytes
-rw-r--r--js/yui/assets/skins/sam/editor.css10
-rw-r--r--js/yui/assets/skins/sam/header_background.pngbin0 -> 158 bytes
-rw-r--r--js/yui/assets/skins/sam/hue_bg.pngbin0 -> 1120 bytes
-rw-r--r--js/yui/assets/skins/sam/imagecropper.css7
-rw-r--r--js/yui/assets/skins/sam/layout.css7
-rw-r--r--js/yui/assets/skins/sam/layout_sprite.pngbin0 -> 1409 bytes
-rw-r--r--js/yui/assets/skins/sam/loading.gifbin0 -> 2673 bytes
-rw-r--r--js/yui/assets/skins/sam/logger.css7
-rw-r--r--js/yui/assets/skins/sam/menu-button-arrow-disabled.pngbin0 -> 173 bytes
-rw-r--r--js/yui/assets/skins/sam/menu-button-arrow.pngbin0 -> 173 bytes
-rw-r--r--js/yui/assets/skins/sam/menu.css7
-rw-r--r--js/yui/assets/skins/sam/menubaritem_submenuindicator.pngbin0 -> 3618 bytes
-rw-r--r--js/yui/assets/skins/sam/menubaritem_submenuindicator_disabled.pngbin0 -> 3618 bytes
-rw-r--r--js/yui/assets/skins/sam/menuitem_checkbox.pngbin0 -> 3625 bytes
-rw-r--r--js/yui/assets/skins/sam/menuitem_checkbox_disabled.pngbin0 -> 3625 bytes
-rw-r--r--js/yui/assets/skins/sam/menuitem_submenuindicator.pngbin0 -> 3617 bytes
-rw-r--r--js/yui/assets/skins/sam/menuitem_submenuindicator_disabled.pngbin0 -> 3617 bytes
-rw-r--r--js/yui/assets/skins/sam/paginator.css7
-rw-r--r--js/yui/assets/skins/sam/picker_mask.pngbin0 -> 12174 bytes
-rw-r--r--js/yui/assets/skins/sam/profilerviewer.css7
-rw-r--r--js/yui/assets/skins/sam/progressbar.css7
-rw-r--r--js/yui/assets/skins/sam/resize.css7
-rw-r--r--js/yui/assets/skins/sam/simpleeditor.css10
-rw-r--r--js/yui/assets/skins/sam/slider.css7
-rw-r--r--js/yui/assets/skins/sam/split-button-arrow-active.pngbin0 -> 280 bytes
-rw-r--r--js/yui/assets/skins/sam/split-button-arrow-disabled.pngbin0 -> 185 bytes
-rw-r--r--js/yui/assets/skins/sam/split-button-arrow-focus.pngbin0 -> 185 bytes
-rw-r--r--js/yui/assets/skins/sam/split-button-arrow-hover.pngbin0 -> 185 bytes
-rw-r--r--js/yui/assets/skins/sam/split-button-arrow.pngbin0 -> 185 bytes
-rw-r--r--js/yui/assets/skins/sam/sprite.pngbin0 -> 3745 bytes
-rw-r--r--js/yui/assets/skins/sam/tabview.css8
-rw-r--r--js/yui/assets/skins/sam/treeview-loading.gifbin0 -> 2673 bytes
-rw-r--r--js/yui/assets/skins/sam/treeview-sprite.gifbin0 -> 4326 bytes
-rw-r--r--js/yui/assets/skins/sam/treeview.css7
-rw-r--r--js/yui/assets/skins/sam/wait.gifbin0 -> 1100 bytes
-rw-r--r--js/yui/assets/skins/sam/yuitest.css7
-rw-r--r--js/yui/autocomplete/autocomplete-min.js12
-rw-r--r--js/yui/base/base-min.css7
-rw-r--r--js/yui/base/base.css131
-rw-r--r--js/yui/button/button-min.js11
-rw-r--r--js/yui/calendar/calendar-min.js18
-rw-r--r--js/yui/carousel/carousel-min.js12
-rw-r--r--js/yui/charts/charts-min.js9
-rw-r--r--js/yui/colorpicker/colorpicker-min.js9
-rw-r--r--js/yui/connection/connection-min.js9
-rw-r--r--js/yui/connection/connection.swfbin0 -> 2423 bytes
-rw-r--r--js/yui/connection/connection_core-min.js8
-rw-r--r--js/yui/container/container-min.js19
-rw-r--r--js/yui/container/container_core-min.js14
-rw-r--r--js/yui/cookie/cookie-min.js7
-rw-r--r--js/yui/datasource/datasource-min.js12
-rw-r--r--js/yui/datatable/datatable-min.js29
-rw-r--r--js/yui/datemath/datemath-min.js7
-rw-r--r--js/yui/dom/dom-min.js9
-rw-r--r--js/yui/dragdrop/dragdrop-min.js10
-rw-r--r--js/yui/element-delegate/element-delegate-min.js7
-rw-r--r--js/yui/element/element-min.js8
-rw-r--r--js/yui/event-delegate/event-delegate-min.js7
-rw-r--r--js/yui/event-mouseenter/event-mouseenter-min.js7
-rw-r--r--js/yui/event-simulate/event-simulate-min.js7
-rw-r--r--js/yui/event/event-min.js11
-rw-r--r--js/yui/fonts/fonts-min.css7
-rw-r--r--js/yui/fonts/fonts.css56
-rw-r--r--js/yui/get/get-min.js7
-rw-r--r--js/yui/grids/grids-min.css7
-rw-r--r--js/yui/grids/grids.css467
-rw-r--r--js/yui/history/history-min.js7
-rw-r--r--js/yui/imagecropper/imagecropper-min.js8
-rw-r--r--js/yui/imageloader/imageloader-min.js7
-rw-r--r--js/yui/json/json-min.js7
-rw-r--r--js/yui/layout/layout-min.js11
-rw-r--r--js/yui/logger/logger-min.js9
-rw-r--r--js/yui/menu/menu-min.js16
-rw-r--r--js/yui/paginator/paginator-min.js10
-rw-r--r--js/yui/profiler/profiler-min.js7
-rw-r--r--js/yui/profilerviewer/profilerviewer-min.js9
-rw-r--r--js/yui/progressbar/progressbar-min.js8
-rw-r--r--js/yui/reset-fonts-grids/reset-fonts-grids.css7
-rw-r--r--js/yui/reset-fonts/reset-fonts.css7
-rw-r--r--js/yui/reset/reset-min.css7
-rw-r--r--js/yui/reset/reset.css142
-rw-r--r--js/yui/resize/resize-min.js10
-rw-r--r--js/yui/selector/selector-min.js8
-rw-r--r--js/yui/slider/slider-min.js9
-rw-r--r--js/yui/storage/storage-min.js8
-rw-r--r--js/yui/stylesheet/stylesheet-min.js7
-rw-r--r--js/yui/swf/swf-min.js7
-rw-r--r--js/yui/swfdetect/swfdetect-min.js7
-rw-r--r--js/yui/swfstore/swfstore-min.js7
-rw-r--r--js/yui/swfstore/swfstore.swfbin0 -> 4879 bytes
-rw-r--r--js/yui/tabview/tabview-min.js8
-rw-r--r--js/yui/treeview/treeview-min.js12
-rw-r--r--js/yui/uploader/uploader-min.js15
-rw-r--r--js/yui/yahoo-dom-event/yahoo-dom-event.js14
-rw-r--r--js/yui/yahoo/yahoo-min.js7
-rw-r--r--js/yui/yuiloader/yuiloader-min.js10
-rw-r--r--js/yui/yuitest/yuitest-min.js10
-rw-r--r--js/yui/yuitest/yuitest_core-min.js9
-rwxr-xr-xjsonrpc.cgi41
-rw-r--r--lib/CGI.pm8149
-rw-r--r--lib/CGI/Apache.pm27
-rw-r--r--lib/CGI/Carp.pm630
-rw-r--r--lib/CGI/Cookie.pm540
-rw-r--r--lib/CGI/Fast.pm224
-rw-r--r--lib/CGI/Pretty.pm312
-rw-r--r--lib/CGI/Push.pm325
-rw-r--r--lib/CGI/Switch.pm28
-rw-r--r--lib/CGI/Util.pm395
-rw-r--r--lib/README4
-rwxr-xr-xlong_list.cgi36
-rwxr-xr-xmigrate.pl110
-rw-r--r--mod_perl.pl159
-rwxr-xr-xpage.cgi98
-rwxr-xr-xpost_bug.cgi265
-rwxr-xr-xprocess_bug.cgi414
-rwxr-xr-xquery.cgi355
-rwxr-xr-xquips.cgi151
-rwxr-xr-xrelogin.cgi203
-rwxr-xr-xreport.cgi349
-rwxr-xr-xreports.cgi264
-rwxr-xr-xrequest.cgi360
-rw-r--r--robots.txt3
-rwxr-xr-xruntests.pl43
-rwxr-xr-xsanitycheck.cgi923
-rwxr-xr-xsanitycheck.pl114
-rwxr-xr-xsearch_plugin.cgi43
-rwxr-xr-xshow_activity.cgi66
-rwxr-xr-xshow_bug.cgi129
-rwxr-xr-xshowattachment.cgi40
-rwxr-xr-xshowdependencygraph.cgi342
-rwxr-xr-xshowdependencytree.cgi141
-rwxr-xr-xsidebar.cgi49
-rw-r--r--skins/README20
-rw-r--r--skins/contrib/Dusk/buglist.css24
-rw-r--r--skins/contrib/Dusk/global.css253
-rw-r--r--skins/contrib/Dusk/index.css9
-rw-r--r--skins/standard/IE-fixes.css56
-rw-r--r--skins/standard/admin.css127
-rw-r--r--skins/standard/attachment.css242
-rw-r--r--skins/standard/buglist.css117
-rw-r--r--skins/standard/dependency-tree.css94
-rw-r--r--skins/standard/dependency-tree/bug-item.pngbin0 -> 1279 bytes
-rw-r--r--skins/standard/dependency-tree/tree-closed.pngbin0 -> 1129 bytes
-rw-r--r--skins/standard/dependency-tree/tree-open.pngbin0 -> 1130 bytes
-rw-r--r--skins/standard/dependency-tree/tree.pngbin0 -> 961 bytes
-rw-r--r--skins/standard/duplicates.css49
-rw-r--r--skins/standard/editusers.css71
-rw-r--r--skins/standard/enter_bug.css70
-rw-r--r--skins/standard/global.css574
-rw-r--r--skins/standard/global/body-back.gifbin0 -> 526 bytes
-rw-r--r--skins/standard/global/calendar.pngbin0 -> 718 bytes
-rw-r--r--skins/standard/global/down.pngbin0 -> 335 bytes
-rw-r--r--skins/standard/global/header.pngbin0 -> 2042 bytes
-rw-r--r--skins/standard/global/left.pngbin0 -> 339 bytes
-rw-r--r--skins/standard/global/right.pngbin0 -> 339 bytes
-rw-r--r--skins/standard/global/up.pngbin0 -> 318 bytes
-rw-r--r--skins/standard/index.css136
-rw-r--r--skins/standard/index/file-a-bug.pngbin0 -> 3534 bytes
-rw-r--r--skins/standard/index/help.pngbin0 -> 4111 bytes
-rw-r--r--skins/standard/index/new-account.pngbin0 -> 4082 bytes
-rw-r--r--skins/standard/index/search.pngbin0 -> 4828 bytes
-rw-r--r--skins/standard/page.css103
-rw-r--r--skins/standard/panel.css37
-rw-r--r--skins/standard/params.css61
-rw-r--r--skins/standard/reports.css89
-rw-r--r--skins/standard/search_form.css202
-rw-r--r--skins/standard/show_bug.css118
-rw-r--r--skins/standard/show_multiple.css52
-rw-r--r--skins/standard/summarize-time.css46
-rwxr-xr-xsummarize_time.cgi374
-rw-r--r--t/001compile.t106
-rw-r--r--t/002goodperl.t129
-rw-r--r--t/003safesys.t65
-rw-r--r--t/004template.t137
-rw-r--r--t/005whitespace.t82
-rw-r--r--t/006spellcheck.t100
-rw-r--r--t/007util.t74
-rw-r--r--t/008filter.t236
-rw-r--r--t/009bugwords.t101
-rw-r--r--t/010dependencies.t118
-rw-r--r--t/011pod.t60
-rw-r--r--t/012throwables.t243
-rw-r--r--t/Support/Files.pm55
-rw-r--r--t/Support/Systemexec.pm14
-rw-r--r--t/Support/Templates.pm150
-rw-r--r--template/en/default/account/auth/login-small.html.tmpl121
-rw-r--r--template/en/default/account/auth/login.html.tmpl120
-rw-r--r--template/en/default/account/cancel-token.txt.tmpl106
-rw-r--r--template/en/default/account/create.html.tmpl79
-rw-r--r--template/en/default/account/created.html.tmpl40
-rw-r--r--template/en/default/account/email/change-new.txt.tmpl41
-rw-r--r--template/en/default/account/email/change-old.txt.tmpl46
-rw-r--r--template/en/default/account/email/confirm-new.html.tmpl79
-rw-r--r--template/en/default/account/email/confirm.html.tmpl47
-rw-r--r--template/en/default/account/email/request-new.txt.tmpl56
-rw-r--r--template/en/default/account/password/forgotten-password.txt.tmpl40
-rw-r--r--template/en/default/account/password/set-forgotten-password.html.tmpl56
-rw-r--r--template/en/default/account/prefs/account.html.tmpl99
-rw-r--r--template/en/default/account/prefs/email.html.tmpl289
-rw-r--r--template/en/default/account/prefs/permissions.html.tmpl93
-rw-r--r--template/en/default/account/prefs/prefs.html.tmpl114
-rw-r--r--template/en/default/account/prefs/saved-searches.html.tmpl209
-rw-r--r--template/en/default/account/prefs/settings.html.tmpl77
-rw-r--r--template/en/default/account/profile-activity.html.tmpl86
-rw-r--r--template/en/default/admin/admin.html.tmpl135
-rw-r--r--template/en/default/admin/classifications/add.html.tmpl39
-rw-r--r--template/en/default/admin/classifications/del.html.tmpl63
-rw-r--r--template/en/default/admin/classifications/edit-common.html.tmpl47
-rw-r--r--template/en/default/admin/classifications/edit.html.tmpl67
-rw-r--r--template/en/default/admin/classifications/footer.html.tmpl24
-rw-r--r--template/en/default/admin/classifications/reclassify.html.tmpl90
-rw-r--r--template/en/default/admin/classifications/select.html.tmpl66
-rw-r--r--template/en/default/admin/components/confirm-delete.html.tmpl160
-rw-r--r--template/en/default/admin/components/create.html.tmpl49
-rw-r--r--template/en/default/admin/components/edit-common.html.tmpl86
-rw-r--r--template/en/default/admin/components/edit.html.tmpl76
-rw-r--r--template/en/default/admin/components/footer.html.tmpl54
-rw-r--r--template/en/default/admin/components/list.html.tmpl128
-rw-r--r--template/en/default/admin/components/select-product.html.tmpl70
-rw-r--r--template/en/default/admin/confirm-action.html.tmpl98
-rw-r--r--template/en/default/admin/custom_fields/cf-js.js.tmpl77
-rw-r--r--template/en/default/admin/custom_fields/confirm-delete.html.tmpl66
-rw-r--r--template/en/default/admin/custom_fields/create.html.tmpl173
-rw-r--r--template/en/default/admin/custom_fields/edit.html.tmpl188
-rw-r--r--template/en/default/admin/custom_fields/list.html.tmpl106
-rw-r--r--template/en/default/admin/fieldvalues/confirm-delete.html.tmpl161
-rw-r--r--template/en/default/admin/fieldvalues/create.html.tmpl100
-rw-r--r--template/en/default/admin/fieldvalues/edit.html.tmpl114
-rw-r--r--template/en/default/admin/fieldvalues/footer.html.tmpl55
-rw-r--r--template/en/default/admin/fieldvalues/list.html.tmpl99
-rw-r--r--template/en/default/admin/fieldvalues/select-field.html.tmpl47
-rw-r--r--template/en/default/admin/flag-type/confirm-delete.html.tmpl63
-rw-r--r--template/en/default/admin/flag-type/edit.html.tmpl253
-rw-r--r--template/en/default/admin/flag-type/list.html.tmpl174
-rw-r--r--template/en/default/admin/groups/confirm-remove.html.tmpl66
-rw-r--r--template/en/default/admin/groups/create.html.tmpl102
-rw-r--r--template/en/default/admin/groups/delete.html.tmpl187
-rw-r--r--template/en/default/admin/groups/edit.html.tmpl249
-rw-r--r--template/en/default/admin/groups/list.html.tmpl168
-rw-r--r--template/en/default/admin/keywords/confirm-delete.html.tmpl53
-rw-r--r--template/en/default/admin/keywords/create.html.tmpl58
-rw-r--r--template/en/default/admin/keywords/edit.html.tmpl73
-rw-r--r--template/en/default/admin/keywords/list.html.tmpl70
-rw-r--r--template/en/default/admin/milestones/confirm-delete.html.tmpl98
-rw-r--r--template/en/default/admin/milestones/create.html.tmpl58
-rw-r--r--template/en/default/admin/milestones/edit.html.tmpl63
-rw-r--r--template/en/default/admin/milestones/footer.html.tmpl67
-rw-r--r--template/en/default/admin/milestones/list.html.tmpl108
-rw-r--r--template/en/default/admin/milestones/select-product.html.tmpl70
-rw-r--r--template/en/default/admin/params/admin.html.tmpl41
-rw-r--r--template/en/default/admin/params/advanced.html.tmpl81
-rw-r--r--template/en/default/admin/params/attachment.html.tmpl76
-rw-r--r--template/en/default/admin/params/auth.html.tmpl129
-rw-r--r--template/en/default/admin/params/bugchange.html.tmpl57
-rw-r--r--template/en/default/admin/params/bugfields.html.tmpl61
-rw-r--r--template/en/default/admin/params/common.html.tmpl150
-rw-r--r--template/en/default/admin/params/core.html.tmpl48
-rw-r--r--template/en/default/admin/params/dependencygraph.html.tmpl49
-rw-r--r--template/en/default/admin/params/editparams.html.tmpl121
-rw-r--r--template/en/default/admin/params/general.html.tmpl86
-rw-r--r--template/en/default/admin/params/groupsecurity.html.tmpl56
-rw-r--r--template/en/default/admin/params/index.html.tmpl51
-rw-r--r--template/en/default/admin/params/ldap.html.tmpl58
-rw-r--r--template/en/default/admin/params/mta.html.tmpl78
-rw-r--r--template/en/default/admin/params/patchviewer.html.tmpl64
-rw-r--r--template/en/default/admin/params/query.html.tmpl58
-rw-r--r--template/en/default/admin/params/radius.html.tmpl54
-rw-r--r--template/en/default/admin/params/shadowdb.html.tmpl49
-rw-r--r--template/en/default/admin/params/usermatch.html.tmpl39
-rw-r--r--template/en/default/admin/products/confirm-delete.html.tmpl266
-rw-r--r--template/en/default/admin/products/create.html.tmpl68
-rw-r--r--template/en/default/admin/products/edit-common.html.tmpl83
-rw-r--r--template/en/default/admin/products/edit.html.tmpl149
-rw-r--r--template/en/default/admin/products/footer.html.tmpl86
-rw-r--r--template/en/default/admin/products/groupcontrol/confirm-edit.html.tmpl58
-rw-r--r--template/en/default/admin/products/groupcontrol/edit.html.tmpl325
-rw-r--r--template/en/default/admin/products/groupcontrol/updated.html.tmpl50
-rw-r--r--template/en/default/admin/products/list-classifications.html.tmpl65
-rw-r--r--template/en/default/admin/products/list.html.tmpl95
-rw-r--r--template/en/default/admin/products/updated.html.tmpl105
-rw-r--r--template/en/default/admin/sanitycheck/list.html.tmpl37
-rw-r--r--template/en/default/admin/sanitycheck/messages.html.tmpl316
-rw-r--r--template/en/default/admin/settings/edit.html.tmpl101
-rw-r--r--template/en/default/admin/sudo.html.tmpl104
-rw-r--r--template/en/default/admin/table.html.tmpl194
-rw-r--r--template/en/default/admin/users/confirm-delete.html.tmpl469
-rw-r--r--template/en/default/admin/users/create.html.tmpl58
-rw-r--r--template/en/default/admin/users/edit.html.tmpl168
-rw-r--r--template/en/default/admin/users/list.html.tmpl115
-rw-r--r--template/en/default/admin/users/listselectvars.html.tmpl33
-rw-r--r--template/en/default/admin/users/responsibilities.html.tmpl65
-rw-r--r--template/en/default/admin/users/search.html.tmpl78
-rw-r--r--template/en/default/admin/users/userdata.html.tmpl97
-rw-r--r--template/en/default/admin/versions/confirm-delete.html.tmpl101
-rw-r--r--template/en/default/admin/versions/create.html.tmpl52
-rw-r--r--template/en/default/admin/versions/edit.html.tmpl57
-rw-r--r--template/en/default/admin/versions/footer.html.tmpl65
-rw-r--r--template/en/default/admin/versions/list.html.tmpl89
-rw-r--r--template/en/default/admin/versions/select-product.html.tmpl70
-rw-r--r--template/en/default/admin/workflow/comment.html.tmpl92
-rw-r--r--template/en/default/admin/workflow/edit.html.tmpl110
-rw-r--r--template/en/default/attachment/cancel-create-dupe.html.tmpl48
-rw-r--r--template/en/default/attachment/choose.html.tmpl43
-rw-r--r--template/en/default/attachment/confirm-delete.html.tmpl92
-rw-r--r--template/en/default/attachment/create.html.tmpl139
-rw-r--r--template/en/default/attachment/created.html.tmpl67
-rw-r--r--template/en/default/attachment/createformcontents.html.tmpl122
-rw-r--r--template/en/default/attachment/delete_reason.txt.tmpl32
-rw-r--r--template/en/default/attachment/diff-file.html.tmpl177
-rw-r--r--template/en/default/attachment/diff-footer.html.tmpl35
-rw-r--r--template/en/default/attachment/diff-header.html.tmpl155
-rw-r--r--template/en/default/attachment/edit.html.tmpl325
-rw-r--r--template/en/default/attachment/list.html.tmpl167
-rw-r--r--template/en/default/attachment/midair.html.tmpl78
-rw-r--r--template/en/default/attachment/show-multiple.html.tmpl102
-rw-r--r--template/en/default/attachment/updated.html.tmpl46
-rw-r--r--template/en/default/bug/activity/show.html.tmpl49
-rw-r--r--template/en/default/bug/activity/table.html.tmpl119
-rw-r--r--template/en/default/bug/choose.html.tmpl35
-rw-r--r--template/en/default/bug/comments.html.tmpl182
-rw-r--r--template/en/default/bug/create/comment-guided.txt.tmpl54
-rw-r--r--template/en/default/bug/create/comment.txt.tmpl32
-rw-r--r--template/en/default/bug/create/confirm-create-dupe.html.tmpl57
-rw-r--r--template/en/default/bug/create/create-guided.html.tmpl522
-rw-r--r--template/en/default/bug/create/create.html.tmpl762
-rw-r--r--template/en/default/bug/create/created.html.tmpl61
-rw-r--r--template/en/default/bug/create/make-template.html.tmpl46
-rw-r--r--template/en/default/bug/create/user-message.html.tmpl36
-rw-r--r--template/en/default/bug/dependency-graph.html.tmpl106
-rw-r--r--template/en/default/bug/dependency-tree.html.tmpl266
-rw-r--r--template/en/default/bug/edit.html.tmpl1179
-rw-r--r--template/en/default/bug/field-events.js.tmpl44
-rw-r--r--template/en/default/bug/field-help.none.tmpl239
-rw-r--r--template/en/default/bug/field-label.html.tmpl53
-rw-r--r--template/en/default/bug/field.html.tmpl230
-rw-r--r--template/en/default/bug/format_comment.txt.tmpl60
-rw-r--r--template/en/default/bug/knob.html.tmpl100
-rw-r--r--template/en/default/bug/navigate.html.tmpl87
-rw-r--r--template/en/default/bug/process/bugmail.html.tmpl60
-rw-r--r--template/en/default/bug/process/confirm-duplicate.html.tmpl75
-rw-r--r--template/en/default/bug/process/header.html.tmpl46
-rw-r--r--template/en/default/bug/process/midair.html.tmpl111
-rw-r--r--template/en/default/bug/process/results.html.tmpl59
-rw-r--r--template/en/default/bug/process/verify-new-product.html.tmpl205
-rw-r--r--template/en/default/bug/show-header.html.tmpl49
-rw-r--r--template/en/default/bug/show-multiple.html.tmpl373
-rw-r--r--template/en/default/bug/show.html.tmpl53
-rw-r--r--template/en/default/bug/show.xml.tmpl157
-rw-r--r--template/en/default/bug/summarize-time.html.tmpl351
-rw-r--r--template/en/default/bug/time.html.tmpl47
-rw-r--r--template/en/default/config.js.tmpl142
-rw-r--r--template/en/default/config.rdf.tmpl267
-rw-r--r--template/en/default/email/lockout.txt.tmpl39
-rw-r--r--template/en/default/email/newchangedmail.txt.tmpl70
-rw-r--r--template/en/default/email/sanitycheck.txt.tmpl36
-rw-r--r--template/en/default/email/sudo.txt.tmpl43
-rw-r--r--template/en/default/email/whine.txt.tmpl64
-rw-r--r--template/en/default/extensions/config.pm.tmpl41
-rw-r--r--template/en/default/extensions/extension.pm.tmpl46
-rw-r--r--template/en/default/extensions/hook-readme.txt.tmpl27
-rw-r--r--template/en/default/extensions/license.txt.tmpl47
-rw-r--r--template/en/default/extensions/name-readme.txt.tmpl38
-rw-r--r--template/en/default/extensions/util.pm.tmpl42
-rw-r--r--template/en/default/extensions/web-readme.txt.tmpl29
-rw-r--r--template/en/default/filterexceptions.pl511
-rw-r--r--template/en/default/flag/list.html.tmpl216
-rw-r--r--template/en/default/global/banner.html.tmpl26
-rw-r--r--template/en/default/global/choose-classification.html.tmpl63
-rw-r--r--template/en/default/global/choose-product.html.tmpl75
-rw-r--r--template/en/default/global/code-error.html.tmpl547
-rw-r--r--template/en/default/global/common-links.html.tmpl117
-rw-r--r--template/en/default/global/confirm-action.html.tmpl64
-rw-r--r--template/en/default/global/confirm-user-match.html.tmpl207
-rw-r--r--template/en/default/global/docslinks.html.tmpl52
-rw-r--r--template/en/default/global/field-descs.none.tmpl178
-rw-r--r--template/en/default/global/footer.html.tmpl51
-rw-r--r--template/en/default/global/header.html.tmpl346
-rw-r--r--template/en/default/global/help.html.tmpl33
-rw-r--r--template/en/default/global/hidden-fields.html.tmpl58
-rw-r--r--template/en/default/global/initialize.none.tmpl32
-rw-r--r--template/en/default/global/js-products.html.tmpl34
-rw-r--r--template/en/default/global/message.html.tmpl42
-rw-r--r--template/en/default/global/message.txt.tmpl25
-rw-r--r--template/en/default/global/messages.html.tmpl851
-rw-r--r--template/en/default/global/per-bug-queries.html.tmpl101
-rw-r--r--template/en/default/global/reason-descs.none.tmpl40
-rw-r--r--template/en/default/global/select-menu.html.tmpl64
-rw-r--r--template/en/default/global/setting-descs.none.tmpl54
-rw-r--r--template/en/default/global/site-navigation.html.tmpl90
-rw-r--r--template/en/default/global/tabs.html.tmpl56
-rw-r--r--template/en/default/global/textarea.html.tmpl56
-rw-r--r--template/en/default/global/useful-links.html.tmpl81
-rw-r--r--template/en/default/global/user-error.html.tmpl1798
-rw-r--r--template/en/default/global/user.html.tmpl39
-rw-r--r--template/en/default/global/userselect.html.tmpl109
-rw-r--r--template/en/default/global/value-descs.js.tmpl33
-rw-r--r--template/en/default/global/variables.none.tmpl44
-rw-r--r--template/en/default/index.html.tmpl183
-rw-r--r--template/en/default/list/change-columns.html.tmpl145
-rw-r--r--template/en/default/list/edit-multiple.html.tmpl441
-rw-r--r--template/en/default/list/list-simple.html.tmpl54
-rw-r--r--template/en/default/list/list.atom.tmpl101
-rw-r--r--template/en/default/list/list.csv.tmpl46
-rw-r--r--template/en/default/list/list.html.tmpl293
-rw-r--r--template/en/default/list/list.ics.tmpl103
-rw-r--r--template/en/default/list/list.js.tmpl37
-rw-r--r--template/en/default/list/list.rdf.tmpl56
-rw-r--r--template/en/default/list/quips.html.tmpl173
-rw-r--r--template/en/default/list/server-push.html.tmpl47
-rw-r--r--template/en/default/list/table.html.tmpl263
-rw-r--r--template/en/default/pages/bug-writing.html.tmpl181
-rw-r--r--template/en/default/pages/fields.html.tmpl234
-rw-r--r--template/en/default/pages/linked.html.tmpl55
-rw-r--r--template/en/default/pages/linkify.html.tmpl42
-rw-r--r--template/en/default/pages/quicksearch.html.tmpl274
-rw-r--r--template/en/default/pages/release-notes.html.tmpl4150
-rw-r--r--template/en/default/pages/sudo.html.tmpl68
-rw-r--r--template/en/default/reports/chart.csv.tmpl44
-rw-r--r--template/en/default/reports/chart.html.tmpl67
-rw-r--r--template/en/default/reports/chart.png.tmpl60
-rw-r--r--template/en/default/reports/components.html.tmpl104
-rw-r--r--template/en/default/reports/create-chart.html.tmpl271
-rw-r--r--template/en/default/reports/delete-series.html.tmpl59
-rw-r--r--template/en/default/reports/duplicates-simple.html.tmpl51
-rw-r--r--template/en/default/reports/duplicates-table.html.tmpl125
-rw-r--r--template/en/default/reports/duplicates.html.tmpl179
-rw-r--r--template/en/default/reports/edit-series.html.tmpl75
-rw-r--r--template/en/default/reports/keywords.html.tmpl86
-rw-r--r--template/en/default/reports/menu.html.tmpl85
-rw-r--r--template/en/default/reports/old-charts.html.tmpl71
-rw-r--r--template/en/default/reports/report-bar.png.tmpl64
-rw-r--r--template/en/default/reports/report-line.png.tmpl67
-rw-r--r--template/en/default/reports/report-pie.png.tmpl39
-rw-r--r--template/en/default/reports/report-simple.html.tmpl35
-rw-r--r--template/en/default/reports/report-table.csv.tmpl77
-rw-r--r--template/en/default/reports/report-table.html.tmpl164
-rw-r--r--template/en/default/reports/report.csv.tmpl25
-rw-r--r--template/en/default/reports/report.html.tmpl180
-rw-r--r--template/en/default/reports/series-common.html.tmpl119
-rw-r--r--template/en/default/reports/series.html.tmpl97
-rw-r--r--template/en/default/request/email.txt.tmpl87
-rw-r--r--template/en/default/request/queue.html.tmpl267
-rw-r--r--template/en/default/search/boolean-charts.html.tmpl143
-rw-r--r--template/en/default/search/field.html.tmpl142
-rw-r--r--template/en/default/search/form.html.tmpl424
-rw-r--r--template/en/default/search/knob.html.tmpl85
-rw-r--r--template/en/default/search/search-advanced.html.tmpl69
-rw-r--r--template/en/default/search/search-create-series.html.tmpl71
-rw-r--r--template/en/default/search/search-plugin.xml.tmpl28
-rw-r--r--template/en/default/search/search-report-graph.html.tmpl142
-rw-r--r--template/en/default/search/search-report-select.html.tmpl50
-rw-r--r--template/en/default/search/search-report-table.html.tmpl95
-rw-r--r--template/en/default/search/search-specific.html.tmpl129
-rw-r--r--template/en/default/search/tabs.html.tmpl36
-rw-r--r--template/en/default/search/type-select.html.tmpl29
-rw-r--r--template/en/default/setup/strings.txt.pl182
-rw-r--r--template/en/default/sidebar.xul.tmpl128
-rw-r--r--template/en/default/welcome-admin.html.tmpl86
-rw-r--r--template/en/default/whine/mail.html.tmpl95
-rw-r--r--template/en/default/whine/mail.txt.tmpl68
-rw-r--r--template/en/default/whine/multipart-mime.txt.tmpl53
-rw-r--r--template/en/default/whine/schedule.html.tmpl448
-rwxr-xr-xtestagent.cgi24
-rwxr-xr-xtestserver.pl289
-rwxr-xr-xtoken.cgi410
-rwxr-xr-xuserprefs.cgi564
-rwxr-xr-xvotes.cgi50
-rwxr-xr-xwhine.pl684
-rwxr-xr-xwhineatnews.pl98
-rwxr-xr-xxml.cgi41
-rwxr-xr-xxmlrpc.cgi48
-rw-r--r--xt/README18
-rw-r--r--xt/lib/Bugzilla/Test/Search.pm942
-rw-r--r--xt/lib/Bugzilla/Test/Search/AndTest.pm69
-rw-r--r--xt/lib/Bugzilla/Test/Search/Constants.pm1040
-rw-r--r--xt/lib/Bugzilla/Test/Search/FakeCGI.pm61
-rw-r--r--xt/lib/Bugzilla/Test/Search/FieldTest.pm582
-rw-r--r--xt/lib/Bugzilla/Test/Search/InjectionTest.pm77
-rw-r--r--xt/lib/Bugzilla/Test/Search/OperatorTest.pm110
-rw-r--r--xt/lib/Bugzilla/Test/Search/OrTest.pm186
-rw-r--r--xt/search.t96
952 files changed, 266964 insertions, 0 deletions
diff --git a/.htaccess b/.htaccess
new file mode 100644
index 000000000..4b06fe9a9
--- /dev/null
+++ b/.htaccess
@@ -0,0 +1,25 @@
+# Don't allow people to retrieve non-cgi executable files or our private data
+<FilesMatch ^(.*\.pm|.*\.pl|.*localconfig.*)$>
+ deny from all
+</FilesMatch>
+<IfModule mod_expires.c>
+<IfModule mod_headers.c>
+<IfModule mod_env.c>
+ <FilesMatch (\.js|\.css)$>
+ ExpiresActive On
+ # According to RFC 2616, "1 year in the future" means "never expire".
+ # We change the name of the file's URL whenever its modification date
+ # changes, so browsers can cache any individual JS or CSS URL forever.
+ # However, since all JS and CSS URLs involve a ? in them (for the changing
+ # name) we have to explicitly set an Expires header or browsers won't
+ # *ever* cache them.
+ ExpiresDefault "now plus 1 years"
+ Header append Cache-Control "public"
+ </FilesMatch>
+
+ # This lets Bugzilla know that we are properly sending Cache-Control
+ # and Expires headers for CSS and JS files.
+ SetEnv BZ_CACHE_CONTROL 1
+</IfModule>
+</IfModule>
+</IfModule>
diff --git a/Bugzilla.pm b/Bugzilla.pm
new file mode 100644
index 000000000..c7f1a47cd
--- /dev/null
+++ b/Bugzilla.pm
@@ -0,0 +1,871 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Bradley Baetz <bbaetz@student.usyd.edu.au>
+# Erik Stambaugh <erik@dasbistro.com>
+# A. Karl Kornel <karl@kornel.name>
+# Marc Schumann <wurblzap@gmail.com>
+
+package Bugzilla;
+
+use strict;
+
+# We want any compile errors to get to the browser, if possible.
+BEGIN {
+ # This makes sure we're in a CGI.
+ if ($ENV{SERVER_SOFTWARE} && !$ENV{MOD_PERL}) {
+ require CGI::Carp;
+ CGI::Carp->import('fatalsToBrowser');
+ }
+}
+
+use Bugzilla::Config;
+use Bugzilla::Constants;
+use Bugzilla::Auth;
+use Bugzilla::Auth::Persist::Cookie;
+use Bugzilla::CGI;
+use Bugzilla::Extension;
+use Bugzilla::DB;
+use Bugzilla::Install::Localconfig qw(read_localconfig);
+use Bugzilla::Install::Requirements qw(OPTIONAL_MODULES);
+use Bugzilla::Install::Util qw(init_console);
+use Bugzilla::Template;
+use Bugzilla::User;
+use Bugzilla::Error;
+use Bugzilla::Util;
+use Bugzilla::Field;
+use Bugzilla::Flag;
+use Bugzilla::Token;
+
+use File::Basename;
+use File::Spec::Functions;
+use DateTime::TimeZone;
+use Date::Parse;
+use Safe;
+
+#####################################################################
+# Constants
+#####################################################################
+
+# Scripts that are not stopped by shutdownhtml being in effect.
+use constant SHUTDOWNHTML_EXEMPT => qw(
+ editparams.cgi
+ checksetup.pl
+ migrate.pl
+ recode.pl
+);
+
+# Non-cgi scripts that should silently exit.
+use constant SHUTDOWNHTML_EXIT_SILENTLY => qw(
+ whine.pl
+);
+
+#####################################################################
+# Global Code
+#####################################################################
+
+# $::SIG{__DIE__} = i_am_cgi() ? \&CGI::Carp::confess : \&Carp::confess;
+
+# Note that this is a raw subroutine, not a method, so $class isn't available.
+sub init_page {
+ if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) {
+ init_console();
+ }
+ elsif (Bugzilla->params->{'utf8'}) {
+ binmode STDOUT, ':utf8';
+ }
+
+ if (${^TAINT}) {
+ # 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'} = '';
+ }
+
+ # 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);
+
+ # 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 (Bugzilla->params->{"shutdownhtml"}
+ && !grep { $_ eq $script } SHUTDOWNHTML_EXEMPT)
+ {
+ # 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';
+ }
+ print Bugzilla->cgi->header() if i_am_cgi();
+ my $t_output;
+ $template->process("global/message.$extension.tmpl", $vars, \$t_output)
+ || ThrowTemplateError($template->error);
+ print $t_output . "\n";
+ exit;
+ }
+}
+
+#####################################################################
+# Subroutines and Methods
+#####################################################################
+
+sub template {
+ my $class = shift;
+ $class->request_cache->{template} ||= Bugzilla::Template->create();
+ return $class->request_cache->{template};
+}
+
+sub template_inner {
+ my ($class, $lang) = @_;
+ my $cache = $class->request_cache;
+ my $current_lang = $cache->{template_current_lang}->[0];
+ $lang ||= $current_lang || '';
+ $class->request_cache->{"template_inner_$lang"}
+ ||= Bugzilla::Template->create(language => $lang);
+ return $class->request_cache->{"template_inner_$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;
+ }
+ 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->{module});
+ }
+ }
+ $cache->{feature_map} = $feature_map;
+ }
+
+ if (!$feature_map->{$feature}) {
+ ThrowCodeError('invalid_feature', { feature => $feature });
+ }
+
+ my $success = 1;
+ foreach my $module (@{ $feature_map->{$feature} }) {
+ # We can't use a string eval and "use" here (it kills Template-Toolkit,
+ # see https://rt.cpan.org/Public/Bug/Display.html?id=47929), so we have
+ # to do a block eval.
+ $module =~ s{::}{/}g;
+ $module .= ".pm";
+ eval { require $module; 1; } or $success = 0;
+ }
+ $cache->{feature}->{$feature} = $success;
+ return $success;
+}
+
+sub cgi {
+ my $class = shift;
+ $class->request_cache->{cgi} ||= new Bugzilla::CGI();
+ return $class->request_cache->{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};
+
+ # 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 {
+ my $class = shift;
+ $class->request_cache->{localconfig} ||= read_localconfig();
+ return $class->request_cache->{localconfig};
+}
+
+sub params {
+ my $class = shift;
+ $class->request_cache->{params} ||= Bugzilla::Config::read_param_file();
+ return $class->request_cache->{params};
+}
+
+sub user {
+ my $class = shift;
+ $class->request_cache->{user} ||= new Bugzilla::User;
+ return $class->request_cache->{user};
+}
+
+sub set_user {
+ my ($class, $user) = @_;
+ $class->request_cache->{user} = $user;
+}
+
+sub sudoer {
+ my $class = shift;
+ return $class->request_cache->{sudoer};
+}
+
+sub sudo_request {
+ my ($class, $new_user, $new_sudoer) = @_;
+ $class->request_cache->{user} = $new_user;
+ $class->request_cache->{sudoer} = $new_sudoer;
+ # NOTE: If you want to log the start of an sudo session, do it here.
+}
+
+sub page_requires_login {
+ return $_[0]->request_cache->{page_requires_login};
+}
+
+sub login {
+ my ($class, $type) = @_;
+
+ return $class->user if $class->user->id;
+
+ my $authorizer = new Bugzilla::Auth();
+ $type = LOGIN_REQUIRED if $class->cgi->param('GoAheadAndLogIn');
+
+ if (!defined $type || $type == LOGIN_NORMAL) {
+ $type = $class->params->{'requirelogin'} ? LOGIN_REQUIRED : LOGIN_NORMAL;
+ }
+
+ # 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 $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 });
+ }
+ }
+ else {
+ $class->set_user($authenticated_user);
+ }
+
+ return $class->user;
+}
+
+sub logout {
+ my ($class, $option) = @_;
+
+ # If we're not logged in, go away
+ return unless $class->user->id;
+
+ $option = LOGOUT_CURRENT unless defined $option;
+ Bugzilla::Auth::Persist::Cookie->logout({type => $option});
+ $class->logout_request() unless $option eq LOGOUT_KEEP_CURRENT;
+}
+
+sub logout_user {
+ my ($class, $user) = @_;
+ # When we're logging out another user we leave cookies alone, and
+ # therefore avoid calling Bugzilla->logout() directly.
+ Bugzilla::Auth::Persist::Cookie->logout({user => $user});
+}
+
+# just a compatibility front-end to logout_user that gets a user by id
+sub logout_user_by_id {
+ my ($class, $id) = @_;
+ my $user = new Bugzilla::User($id);
+ $class->logout_user($user);
+}
+
+# hack that invalidates credentials for a single request
+sub logout_request {
+ my $class = shift;
+ delete $class->request_cache->{user};
+ delete $class->request_cache->{sudoer};
+ # We can't delete from $cgi->cookie, so logincookie data will remain
+ # there. Don't rely on it: use Bugzilla->user->login instead!
+}
+
+sub job_queue {
+ my $class = shift;
+ require Bugzilla::JobQueue;
+ $class->request_cache->{job_queue} ||= Bugzilla::JobQueue->new();
+ return $class->request_cache->{job_queue};
+}
+
+sub dbh {
+ my $class = shift;
+ # If we're not connected, then we must want the main db
+ $class->request_cache->{dbh} ||= $class->dbh_main;
+
+ return $class->request_cache->{dbh};
+}
+
+sub dbh_main {
+ my $class = shift;
+ $class->request_cache->{dbh_main} ||= Bugzilla::DB::connect_main();
+ return $class->request_cache->{dbh_main};
+}
+
+sub languages {
+ my $class = shift;
+ return Bugzilla::Install::Util::supported_languages();
+}
+
+sub error_mode {
+ my ($class, $newval) = @_;
+ if (defined $newval) {
+ $class->request_cache->{error_mode} = $newval;
+ }
+ return $class->request_cache->{error_mode}
+ || (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};
+}
+
+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);
+ }
+ else {
+ ThrowCodeError('usage_mode_invalid',
+ {'invalid_usage_mode', $newval});
+ }
+ $class->request_cache->{usage_mode} = $newval;
+ }
+ return $class->request_cache->{usage_mode}
+ || (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;
+}
+
+sub installation_answers {
+ my ($class, $filename) = @_;
+ if ($filename) {
+ my $s = new Safe;
+ $s->rdo($filename);
+
+ die "Error reading $filename: $!" if $!;
+ die "Error evaluating $filename: $@" if $@;
+
+ # Now read the param back out from the sandbox
+ $class->request_cache->{installation_answers} = $s->varglob('answer');
+ }
+ return $class->request_cache->{installation_answers} || {};
+}
+
+sub switch_to_shadow_db {
+ my $class = shift;
+
+ if (!$class->request_cache->{dbh_shadow}) {
+ if ($class->params->{'shadowdb'}) {
+ $class->request_cache->{dbh_shadow} = Bugzilla::DB::connect_shadow();
+ } else {
+ $class->request_cache->{dbh_shadow} = $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;
+}
+
+sub switch_to_main_db {
+ my $class = shift;
+
+ $class->request_cache->{dbh} = $class->dbh_main;
+ return $class->dbh_main;
+}
+
+sub get_fields {
+ my $class = shift;
+ my $criteria = shift;
+ # This function may be called during installation, and Field::match
+ # may fail at that time. so we want to return an empty list in that
+ # case.
+ my $fields = eval { Bugzilla::Field->match($criteria) } || [];
+ return @$fields;
+}
+
+sub active_custom_fields {
+ my $class = shift;
+ if (!exists $class->request_cache->{active_custom_fields}) {
+ $class->request_cache->{active_custom_fields} =
+ Bugzilla::Field->match({ custom => 1, obsolete => 0 });
+ }
+ return @{$class->request_cache->{active_custom_fields}};
+}
+
+sub has_flags {
+ my $class = shift;
+
+ if (!defined $class->request_cache->{has_flags}) {
+ $class->request_cache->{has_flags} = Bugzilla::Flag->any_exist;
+ }
+ return $class->request_cache->{has_flags};
+}
+
+sub local_timezone {
+ my $class = shift;
+
+ if (!defined $class->request_cache->{local_timezone}) {
+ $class->request_cache->{local_timezone} =
+ DateTime::TimeZone->new(name => 'local');
+ }
+ return $class->request_cache->{local_timezone};
+}
+
+# This creates the request cache for non-mod_perl installations.
+# This is identical to Install::Util::_cache so that things loaded
+# into Install::Util::_cache during installation can be read out
+# of request_cache later in installation.
+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;
+}
+
+# Private methods
+
+# Per-process cleanup. Note that this is a plain subroutine, not a method,
+# so we don't have $class available.
+sub _cleanup {
+ my $main = Bugzilla->request_cache->{dbh_main};
+ my $shadow = Bugzilla->request_cache->{dbh_shadow};
+ foreach my $dbh ($main, $shadow) {
+ next if !$dbh;
+ $dbh->bz_rollback_transaction() if $dbh->bz_in_transaction;
+ $dbh->disconnect;
+ }
+ undef $_request_cache;
+}
+
+sub END {
+ # Bugzilla.pm cannot compile in mod_perl.pl if this runs.
+ _cleanup() unless $ENV{MOD_PERL};
+}
+
+init_page() if !$ENV{MOD_PERL};
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla - Semi-persistent collection of various objects used by scripts
+and modules
+
+=head1 SYNOPSIS
+
+ use Bugzilla;
+
+ sub someModulesSub {
+ Bugzilla->dbh->prepare(...);
+ Bugzilla->template->process(...);
+ }
+
+=head1 DESCRIPTION
+
+Several Bugzilla 'things' are used by a variety of modules and scripts. This
+includes database handles, template objects, and so on.
+
+This module is a singleton intended as a central place to store these objects.
+This approach has several advantages:
+
+=over 4
+
+=item *
+
+They're not global variables, so we don't have issues with them staying around
+with mod_perl
+
+=item *
+
+Everything is in one central place, so it's easy to access, modify, and maintain
+
+=item *
+
+Code in modules can get access to these objects without having to have them
+all passed from the caller, and the caller's caller, and....
+
+=item *
+
+We can reuse objects across requests using mod_perl where appropriate (eg
+templates), whilst destroying those which are only valid for a single request
+(such as the current user)
+
+=back
+
+Note that items accessible via this object are demand-loaded when requested.
+
+For something to be added to this object, it should either be able to benefit
+from persistence when run under mod_perl (such as the a C<template> object),
+or should be something which is globally required by a large ammount of code
+(such as the current C<user> object).
+
+=head1 METHODS
+
+Note that all C<Bugzilla> functionality is method based; use C<Bugzilla-E<gt>dbh>
+rather than C<Bugzilla::dbh>. Nothing cares about this now, but don't rely on
+that.
+
+=over 4
+
+=item C<template>
+
+The current C<Template> object, to be used for output
+
+=item C<template_inner>
+
+If you ever need a L<Bugzilla::Template> object while you're already
+processing a template, use this. Also use it if you want to specify
+the language to use. If no argument is passed, it uses the last
+language set. If the argument is "" (empty string), the language is
+reset to the current one (the one used by Bugzilla->template).
+
+=item C<cgi>
+
+The current C<cgi> object. Note that modules should B<not> be using this in
+general. Not all Bugzilla actions are cgi requests. Its useful as a convenience
+method for those scripts/templates which are only use via CGI, though.
+
+=item C<input_params>
+
+When running under the WebService, this is a hashref containing the arguments
+passed to the WebService method that was called. When running in a normal
+script, this is a hashref containing the contents of the CGI parameters.
+
+Modifying this hashref will modify the CGI parameters or the WebService
+arguments (depending on what C<input_params> currently represents).
+
+This should be used instead of L</cgi> in situations where your code
+could be being called by either a normal CGI script or a WebService method,
+such as during a code hook.
+
+B<Note:> When C<input_params> represents the CGI parameters, any
+parameter specified more than once (like C<foo=bar&foo=baz>) will appear
+as an arrayref in the hash, but any value specified only once will appear
+as a scalar. This means that even if a value I<can> appear multiple times,
+if it only I<does> appear once, then it will be a scalar in C<input_params>,
+not an arrayref.
+
+=item C<user>
+
+C<undef> if there is no currently logged in user or if the login code has not
+yet been run. If an sudo session is in progress, the C<Bugzilla::User>
+corresponding to the person who is being impersonated. If no session is in
+progress, the current C<Bugzilla::User>.
+
+=item C<set_user>
+
+Allows you to directly set what L</user> will return. You can use this
+if you want to bypass L</login> for some reason and directly "log in"
+a specific L<Bugzilla::User>. Be careful with it, though!
+
+=item C<sudoer>
+
+C<undef> if there is no currently logged in user, the currently logged in user
+is not in the I<sudoer> group, or there is no session in progress. If an sudo
+session is in progress, returns the C<Bugzilla::User> object corresponding to
+the person who logged in and initiated the session. If no session is in
+progress, returns the C<Bugzilla::User> object corresponding to the currently
+logged in user.
+
+=item C<sudo_request>
+This begins an sudo session for the current request. It is meant to be
+used when a session has just started. For normal use, sudo access should
+normally be set at login time.
+
+=item C<login>
+
+Logs in a user, returning a C<Bugzilla::User> object, or C<undef> if there is
+no logged in user. See L<Bugzilla::Auth|Bugzilla::Auth>, and
+L<Bugzilla::User|Bugzilla::User>.
+
+=item C<page_requires_login>
+
+If the current page always requires the user to log in (for example,
+C<enter_bug.cgi> or any page called with C<?GoAheadAndLogIn=1>) then
+this will return something true. Otherwise it will return false. (This is
+set when you call L</login>.)
+
+=item C<logout($option)>
+
+Logs out the current user, which involves invalidating user sessions and
+cookies. Three options are available from
+L<Bugzilla::Constants|Bugzilla::Constants>: LOGOUT_CURRENT (the
+default), LOGOUT_ALL or LOGOUT_KEEP_CURRENT.
+
+=item C<logout_user($user)>
+
+Logs out the specified user (invalidating all his sessions), taking a
+Bugzilla::User instance.
+
+=item C<logout_by_id($id)>
+
+Logs out the user with the id specified. This is a compatibility
+function to be used in callsites where there is only a userid and no
+Bugzilla::User instance.
+
+=item C<logout_request>
+
+Essentially, causes calls to C<Bugzilla-E<gt>user> to return C<undef>. This has the
+effect of logging out a user for the current request only; cookies and
+database sessions are left intact.
+
+=item C<error_mode>
+
+Call either C<Bugzilla->error_mode(Bugzilla::Constants::ERROR_MODE_DIE)>
+or C<Bugzilla->error_mode(Bugzilla::Constants::ERROR_MODE_DIE_SOAP_FAULT)> to
+change this flag's default of C<Bugzilla::Constants::ERROR_MODE_WEBPAGE> and to
+indicate that errors should be passed to error mode specific error handlers
+rather than being sent to a browser and finished with an exit().
+
+This is useful, for example, to keep C<eval> blocks from producing wild HTML
+on errors, making it easier for you to catch them.
+(Remember to reset the error mode to its previous value afterwards, though.)
+
+C<Bugzilla->error_mode> will return the current state of this flag.
+
+Note that C<Bugzilla->error_mode> is being called by C<Bugzilla->usage_mode> on
+usage mode changes.
+
+=item C<usage_mode>
+
+Call either C<Bugzilla->usage_mode(Bugzilla::Constants::USAGE_MODE_CMDLINE)>
+or C<Bugzilla->usage_mode(Bugzilla::Constants::USAGE_MODE_XMLRPC)> near the
+beginning of your script to change this flag's default of
+C<Bugzilla::Constants::USAGE_MODE_BROWSER> and to indicate that Bugzilla is
+being called in a non-interactive manner.
+
+This influences error handling because on usage mode changes, C<usage_mode>
+calls C<Bugzilla->error_mode> to set an error mode which makes sense for the
+usage mode.
+
+C<Bugzilla->usage_mode> will return the current state of this flag.
+
+=item C<installation_mode>
+
+Determines whether or not installation should be silent. See
+L<Bugzilla::Constants> for the C<INSTALLATION_MODE> constants.
+
+=item C<installation_answers>
+
+Returns a hashref representing any "answers" file passed to F<checksetup.pl>,
+used to automatically answer or skip prompts.
+
+=item C<dbh>
+
+The current database handle. See L<DBI>.
+
+=item C<dbh_main>
+
+The main database handle. See L<DBI>.
+
+=item C<languages>
+
+Currently installed languages.
+Returns a reference to a list of RFC 1766 language tags of installed languages.
+
+=item C<switch_to_shadow_db>
+
+Switch from using the main database to using the shadow database.
+
+=item C<switch_to_main_db>
+
+Change the database object to refer to the main database.
+
+=item C<params>
+
+The current Parameters of Bugzilla, as a hashref. If C<data/params>
+does not exist, then we return an empty hashref. If C<data/params>
+is unreadable or is not valid perl, we C<die>.
+
+=item C<local_timezone>
+
+Returns the local timezone of the Bugzilla installation,
+as a DateTime::TimeZone object. This detection is very time
+consuming, so we cache this information for future references.
+
+=item C<job_queue>
+
+Returns a L<Bugzilla::JobQueue> that you can use for queueing jobs.
+Will throw an error if job queueing is not correctly configured on
+this Bugzilla installation.
+
+=item C<feature>
+
+Tells you whether or not a specific feature is enabled. For names
+of features, see C<OPTIONAL_MODULES> in C<Bugzilla::Install::Requirements>.
+
+=back
diff --git a/Bugzilla/Attachment.pm b/Bugzilla/Attachment.pm
new file mode 100644
index 000000000..79f7b915e
--- /dev/null
+++ b/Bugzilla/Attachment.pm
@@ -0,0 +1,1030 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Myk Melez <myk@mozilla.org>
+# Marc Schumann <wurblzap@gmail.com>
+# Frédéric Buclin <LpSolit@gmail.com>
+
+use strict;
+
+package Bugzilla::Attachment;
+
+=head1 NAME
+
+Bugzilla::Attachment - Bugzilla attachment class.
+
+=head1 SYNOPSIS
+
+ use Bugzilla::Attachment;
+
+ # Get the attachment with the given ID.
+ my $attachment = new Bugzilla::Attachment($attach_id);
+
+ # Get the attachments with the given IDs.
+ my $attachments = Bugzilla::Attachment->new_from_list($attach_ids);
+
+=head1 DESCRIPTION
+
+Attachment.pm represents an attachment object. It is an implementation
+of L<Bugzilla::Object>, and thus provides all methods that
+L<Bugzilla::Object> provides.
+
+The methods that are specific to C<Bugzilla::Attachment> are listed
+below.
+
+=cut
+
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Flag;
+use Bugzilla::User;
+use Bugzilla::Util;
+use Bugzilla::Field;
+use Bugzilla::Hook;
+
+use base qw(Bugzilla::Object);
+
+###############################
+#### Initialization ####
+###############################
+
+use constant DB_TABLE => 'attachments';
+use constant ID_FIELD => 'attach_id';
+use constant LIST_ORDER => ID_FIELD;
+
+sub DB_COLUMNS {
+ my $dbh = Bugzilla->dbh;
+
+ return qw(
+ attach_id
+ bug_id
+ description
+ filename
+ isobsolete
+ ispatch
+ isprivate
+ isurl
+ mimetype
+ modification_time
+ submitter_id),
+ $dbh->sql_date_format('attachments.creation_ts', '%Y.%m.%d %H:%i') . ' AS creation_ts';
+}
+
+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
+);
+
+use constant VALIDATORS => {
+ bug => \&_check_bug,
+ description => \&_check_description,
+ ispatch => \&Bugzilla::Object::check_boolean,
+ isprivate => \&_check_is_private,
+ isurl => \&_check_is_url,
+ mimetype => \&_check_content_type,
+ store_in_file => \&_check_store_in_file,
+};
+
+use constant VALIDATOR_DEPENDENCIES => {
+ mimetype => ['ispatch', 'isurl'],
+};
+
+use constant UPDATE_VALIDATORS => {
+ filename => \&_check_filename,
+ isobsolete => \&Bugzilla::Object::check_boolean,
+};
+
+###############################
+#### Accessors ######
+###############################
+
+=pod
+
+=head2 Instance Properties
+
+=over
+
+=item C<bug_id>
+
+the ID of the bug to which the attachment is attached
+
+=back
+
+=cut
+
+sub bug_id {
+ my $self = shift;
+ return $self->{bug_id};
+}
+
+=over
+
+=item C<bug>
+
+the bug object to which the attachment is attached
+
+=back
+
+=cut
+
+sub bug {
+ my $self = shift;
+
+ require Bugzilla::Bug;
+ $self->{bug} ||= Bugzilla::Bug->new($self->bug_id);
+ return $self->{bug};
+}
+
+=over
+
+=item C<description>
+
+user-provided text describing the attachment
+
+=back
+
+=cut
+
+sub description {
+ my $self = shift;
+ return $self->{description};
+}
+
+=over
+
+=item C<contenttype>
+
+the attachment's MIME media type
+
+=back
+
+=cut
+
+sub contenttype {
+ my $self = shift;
+ return $self->{mimetype};
+}
+
+=over
+
+=item C<attacher>
+
+the user who attached the attachment
+
+=back
+
+=cut
+
+sub attacher {
+ my $self = shift;
+ return $self->{attacher} if exists $self->{attacher};
+ $self->{attacher} = new Bugzilla::User($self->{submitter_id});
+ return $self->{attacher};
+}
+
+=over
+
+=item C<attached>
+
+the date and time on which the attacher attached the attachment
+
+=back
+
+=cut
+
+sub attached {
+ my $self = shift;
+ return $self->{creation_ts};
+}
+
+=over
+
+=item C<modification_time>
+
+the date and time on which the attachment was last modified.
+
+=back
+
+=cut
+
+sub modification_time {
+ my $self = shift;
+ return $self->{modification_time};
+}
+
+=over
+
+=item C<filename>
+
+the name of the file the attacher attached
+
+=back
+
+=cut
+
+sub filename {
+ my $self = shift;
+ return $self->{filename};
+}
+
+=over
+
+=item C<ispatch>
+
+whether or not the attachment is a patch
+
+=back
+
+=cut
+
+sub ispatch {
+ my $self = shift;
+ return $self->{ispatch};
+}
+
+=over
+
+=item C<isurl>
+
+whether or not the attachment is a URL
+
+=back
+
+=cut
+
+sub isurl {
+ my $self = shift;
+ return $self->{isurl};
+}
+
+=over
+
+=item C<isobsolete>
+
+whether or not the attachment is obsolete
+
+=back
+
+=cut
+
+sub isobsolete {
+ my $self = shift;
+ return $self->{isobsolete};
+}
+
+=over
+
+=item C<isprivate>
+
+whether or not the attachment is private
+
+=back
+
+=cut
+
+sub isprivate {
+ my $self = shift;
+ return $self->{isprivate};
+}
+
+=over
+
+=item C<is_viewable>
+
+Returns 1 if the attachment has a content-type viewable in this browser.
+Note that we don't use $cgi->Accept()'s ability to check if a content-type
+matches, because this will return a value even if it's matched by the generic
+*/* which most browsers add to the end of their Accept: headers.
+
+=back
+
+=cut
+
+sub is_viewable {
+ my $self = shift;
+ my $contenttype = $self->contenttype;
+ my $cgi = Bugzilla->cgi;
+
+ # We assume we can view all text and image types.
+ return 1 if ($contenttype =~ /^(text|image)\//);
+
+ # Mozilla can view XUL. Note the trailing slash on the Gecko detection to
+ # avoid sending XUL to Safari.
+ return 1 if (($contenttype =~ /^application\/vnd\.mozilla\./)
+ && ($cgi->user_agent() =~ /Gecko\//));
+
+ # If it's not one of the above types, we check the Accept: header for any
+ # types mentioned explicitly.
+ my $accept = join(",", $cgi->Accept());
+ return 1 if ($accept =~ /^(.*,)?\Q$contenttype\E(,.*)?$/);
+
+ return 0;
+}
+
+=over
+
+=item C<data>
+
+the content of the attachment
+
+=back
+
+=cut
+
+sub data {
+ my $self = shift;
+ return $self->{data} if exists $self->{data};
+
+ # First try to get the attachment data from the database.
+ ($self->{data}) = Bugzilla->dbh->selectrow_array("SELECT thedata
+ FROM attach_data
+ WHERE id = ?",
+ undef,
+ $self->id);
+
+ # If there's no attachment data in the database, the attachment is stored
+ # in a local file, so retrieve it from there.
+ if (length($self->{data}) == 0) {
+ if (open(AH, $self->_get_local_filename())) {
+ local $/;
+ binmode AH;
+ $self->{data} = <AH>;
+ close(AH);
+ }
+ }
+
+ return $self->{data};
+}
+
+=over
+
+=item C<datasize>
+
+the length (in characters) of the attachment content
+
+=back
+
+=cut
+
+# datasize is a property of the data itself, and it's unclear whether we should
+# expose it at all, since you can easily derive it from the data itself: in TT,
+# attachment.data.size; in Perl, length($attachment->{data}). But perhaps
+# it makes sense for performance reasons, since accessing the data forces it
+# to get retrieved from the database/filesystem and loaded into memory,
+# while datasize avoids loading the attachment into memory, calling SQL's
+# LENGTH() function or stat()ing the file instead. I've left it in for now.
+
+sub datasize {
+ my $self = shift;
+ return $self->{datasize} if exists $self->{datasize};
+
+ # If we have already retrieved the data, return its size.
+ return length($self->{data}) if exists $self->{data};
+
+ $self->{datasize} =
+ Bugzilla->dbh->selectrow_array("SELECT LENGTH(thedata)
+ FROM attach_data
+ WHERE id = ?",
+ undef, $self->id) || 0;
+
+ # If there's no attachment data in the database, either the attachment
+ # is stored in a local file, and so retrieve its size from the file,
+ # or the attachment has been deleted.
+ unless ($self->{datasize}) {
+ if (open(AH, $self->_get_local_filename())) {
+ binmode AH;
+ $self->{datasize} = (stat(AH))[7];
+ close(AH);
+ }
+ }
+
+ return $self->{datasize};
+}
+
+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;
+}
+
+=over
+
+=item C<flags>
+
+flags that have been set on the attachment
+
+=back
+
+=cut
+
+sub flags {
+ 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};
+}
+
+=over
+
+=item C<flag_types>
+
+Return all flag types available for this attachment as well as flags
+already set, grouped by flag type.
+
+=back
+
+=cut
+
+sub 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 };
+
+ $self->{flag_types} = Bugzilla::Flag->_flag_types($vars);
+ return $self->{flag_types};
+}
+
+###############################
+#### Validators ######
+###############################
+
+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_flags {
+ my ($self, $flags, $new_flags) = @_;
+
+ Bugzilla::Flag->set_flag($self, $_) foreach (@$flags, @$new_flags);
+}
+
+sub _check_bug {
+ my ($invocant, $bug) = @_;
+ my $user = Bugzilla->user;
+
+ $bug = ref $invocant ? $invocant->bug : $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 });
+
+ return $bug;
+}
+
+sub _check_content_type {
+ my ($invocant, $content_type, undef, $params) = @_;
+
+ my ($is_url, $is_patch) = @$params{qw(isurl ispatch)};
+ if (ref $invocant) {
+ $is_url = $invocant->isurl;
+ $is_patch = $invocant->ispatch;
+ }
+
+ $content_type = 'text/plain' if ($is_url || $is_patch);
+ $content_type = trim($content_type);
+ my $legal_types = join('|', LEGAL_CONTENT_TYPES);
+ if (!$content_type or $content_type !~ /^($legal_types)\/.+$/) {
+ ThrowUserError("invalid_content_type", { contenttype => $content_type });
+ }
+ trick_taint($content_type);
+
+ return $content_type;
+}
+
+sub _check_data {
+ my ($invocant, $params) = @_;
+
+ my $data;
+ if ($params->{isurl}) {
+ $data = $params->{data};
+ ($data && $data =~ m#^(http|https|ftp)://\S+#)
+ || ThrowUserError('attachment_illegal_url', { url => $data });
+
+ $params->{mimetype} = 'text/plain';
+ $params->{ispatch} = 0;
+ $params->{store_in_file} = 0;
+ }
+ else {
+ if ($params->{store_in_file} || !ref $params->{data}) {
+ # If it's a filehandle, just store it, not the content of the file
+ # itself as the file may be quite large. If it's not a filehandle,
+ # it already contains the content of the file.
+ $data = $params->{data};
+ }
+ else {
+ # The file will be stored in the DB. We need the content of the file.
+ local $/;
+ my $fh = $params->{data};
+ $data = <$fh>;
+ }
+ }
+ Bugzilla::Hook::process('attachment_process_data', { data => \$data,
+ attributes => $params });
+
+ # Do not validate the size if we have a filehandle. It will be checked later.
+ return $data if ref $data;
+
+ $data || ThrowUserError('zero_length_file');
+ # Make sure the attachment does not exceed the maximum permitted size.
+ my $len = length($data);
+ my $max_size = $params->{store_in_file} ? Bugzilla->params->{'maxlocalattachment'} * 1048576
+ : Bugzilla->params->{'maxattachmentsize'} * 1024;
+ if ($len > $max_size) {
+ my $vars = { filesize => sprintf("%.0f", $len/1024) };
+ if ($params->{ispatch}) {
+ ThrowUserError('patch_too_large', $vars);
+ }
+ elsif ($params->{store_in_file}) {
+ ThrowUserError('local_file_too_large');
+ }
+ else {
+ ThrowUserError('file_too_large', $vars);
+ }
+ }
+ return $data;
+}
+
+sub _check_description {
+ my ($invocant, $description) = @_;
+
+ $description = trim($description);
+ $description || ThrowUserError('missing_attachment_description');
+ return $description;
+}
+
+sub _check_filename {
+ my ($invocant, $filename, $is_url) = @_;
+
+ $is_url = $invocant->isurl if ref $invocant;
+ # No file is attached, so it has no name.
+ return '' if $is_url;
+
+ $filename = trim($filename);
+ $filename || 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 100 characters, counting from the end of the
+ # string to make sure we keep the filename extension.
+ $filename = substr($filename, -100, 100);
+ 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;
+}
+
+sub _check_is_url {
+ my ($invocant, $is_url) = @_;
+
+ if ($is_url && !Bugzilla->params->{'allow_attach_url'}) {
+ ThrowCodeError('attachment_url_disabled');
+ }
+ return $is_url ? 1 : 0;
+}
+
+sub _check_store_in_file {
+ my ($invocant, $store_in_file) = @_;
+
+ if ($store_in_file && !Bugzilla->params->{'maxlocalattachment'}) {
+ ThrowCodeError('attachment_local_storage_disabled');
+ }
+ return $store_in_file ? 1 : 0;
+}
+
+=pod
+
+=head2 Class Methods
+
+=over
+
+=item C<get_attachments_by_bug($bug_id)>
+
+Description: retrieves and returns the attachments the currently logged in
+ user can view for the given bug.
+
+Params: C<$bug_id> - integer - the ID of the bug for which
+ to retrieve and return attachments.
+
+Returns: a reference to an array of attachment objects.
+
+=cut
+
+sub get_attachments_by_bug {
+ my ($class, $bug_id, $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);
+
+ # To avoid $attachment->flags to run SQL queries itself for each
+ # attachment listed here, we collect all the data at once and
+ # populate $attachment->{flags} ourselves.
+ if ($vars->{preload}) {
+ $_->{flags} = [] foreach @$attachments;
+ my %att = map { $_->id => $_ } @$attachments;
+
+ my $flags = Bugzilla::Flag->match({ bug_id => $bug_id,
+ target_type => 'attachment' });
+
+ # Exclude flags for private attachments you cannot see.
+ @$flags = grep {exists $att{$_->attach_id}} @$flags;
+
+ push(@{$att{$_->attach_id}->{flags}}, $_) foreach @$flags;
+ $attachments = [sort {$a->id <=> $b->id} values %att];
+ }
+ return $attachments;
+}
+
+=pod
+
+=item C<validate_can_edit($attachment, $product_id)>
+
+Description: validates if the user is allowed to view and edit the attachment.
+ Only the submitter or someone with editbugs privs can edit it.
+ Only the submitter and users in the insider group can view
+ private attachments.
+
+Params: $attachment - the attachment object being edited.
+ $product_id - the product ID the attachment belongs to.
+
+Returns: 1 on success, 0 otherwise.
+
+=cut
+
+sub validate_can_edit {
+ my ($attachment, $product_id) = @_;
+ 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', $product_id))) ? 1 : 0;
+}
+
+=item C<validate_obsolete($bug, $attach_ids)>
+
+Description: validates if attachments the user wants to mark as obsolete
+ really belong to the given bug and are not already obsolete.
+ Moreover, a user cannot mark an attachment as obsolete if
+ he cannot view it (due to restrictions on it).
+
+Params: $bug - The bug object obsolete attachments should belong to.
+ $attach_ids - The list of attachments to mark as obsolete.
+
+Returns: The list of attachment objects to mark as obsolete.
+ Else an error is thrown.
+
+=cut
+
+sub validate_obsolete {
+ 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;
+
+ detaint_natural($attachid)
+ || ThrowCodeError('invalid_attach_id_to_obsolete', $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($bug->product_id)
+ || ThrowUserError('illegal_attachment_edit', { attach_id => $attachment->id });
+
+ $vars->{'description'} = $attachment->description;
+
+ if ($attachment->bug_id != $bug->bug_id) {
+ $vars->{'my_bug_id'} = $bug->bug_id;
+ $vars->{'attach_bug_id'} = $attachment->bug_id;
+ ThrowCodeError('mismatched_bug_ids_on_obsolete', $vars);
+ }
+
+ next if $attachment->isobsolete;
+
+ push(@obsolete_attachments, $attachment);
+ }
+ return @obsolete_attachments;
+}
+
+###############################
+#### Constructors #####
+###############################
+
+=pod
+
+=item C<create>
+
+Description: inserts an attachment into the given bug.
+
+Params: takes a hashref with the following keys:
+ C<bug> - Bugzilla::Bug object - the bug for which to insert
+ the attachment.
+ C<data> - Either a filehandle pointing to the content of the
+ attachment, or the content of the attachment itself.
+ C<description> - string - describe what the attachment is about.
+ C<filename> - string - the name of the attachment (used by the
+ browser when downloading it). If the attachment is a URL, this
+ parameter has no effect.
+ C<mimetype> - string - a valid MIME type.
+ C<creation_ts> - string (optional) - timestamp of the insert
+ as returned by SELECT LOCALTIMESTAMP(0).
+ C<ispatch> - boolean (optional, default false) - true if the
+ attachment is a patch.
+ C<isprivate> - boolean (optional, default false) - true if
+ the attachment is private.
+ C<isurl> - boolean (optional, default false) - true if the
+ attachment is a URL pointing to some external ressource.
+ C<store_in_file> - boolean (optional, default false) - true
+ if the attachment must be stored in data/attachments/ instead
+ of in the DB.
+
+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 $fh = delete $params->{data};
+ my $store_in_file = delete $params->{store_in_file};
+
+ my $attachment = $class->insert_create_data($params);
+ my $attachid = $attachment->id;
+
+ # We only use $data here in this INSERT with a placeholder,
+ # so it's safe.
+ my $sth = $dbh->prepare("INSERT INTO attach_data
+ (id, thedata) VALUES ($attachid, ?)");
+
+ my $data = $store_in_file ? "" : $fh;
+ trick_taint($data);
+ $sth->bind_param(1, $data, $dbh->BLOB_TYPE);
+ $sth->execute();
+
+ # If the file is to be stored locally, stream the file from the web server
+ # to the local file without reading it into a local variable.
+ if ($store_in_file) {
+ my $attachdir = bz_locations()->{'attachdir'};
+ my $hash = ($attachid % 100) + 100;
+ $hash =~ s/.*(\d\d)$/group.$1/;
+ mkdir "$attachdir/$hash", 0770;
+ chmod 0770, "$attachdir/$hash";
+ open(AH, '>', "$attachdir/$hash/attachment.$attachid");
+ binmode AH;
+ if (ref $fh) {
+ my $limit = Bugzilla->params->{"maxlocalattachment"} * 1048576;
+ my $sizecount = 0;
+ while (<$fh>) {
+ print AH $_;
+ $sizecount += length($_);
+ if ($sizecount > $limit) {
+ close AH;
+ close $fh;
+ unlink "$attachdir/$hash/attachment.$attachid";
+ ThrowUserError("local_file_too_large");
+ }
+ }
+ close $fh;
+ }
+ else {
+ print AH $fh;
+ }
+ close AH;
+ }
+
+ $attachment->{bug} = $bug;
+
+ # Return the new attachment object.
+ return $attachment;
+}
+
+sub run_create_validators {
+ my ($class, $params) = @_;
+
+ # 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);
+
+ $params->{filename} = $class->_check_filename($params->{filename}, $params->{isurl});
+ $params->{creation_ts} ||= Bugzilla->dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
+ $params->{modification_time} = $params->{creation_ts};
+ $params->{submitter_id} = Bugzilla->user->id || ThrowCodeError('invalid_user');
+
+ return $params;
+}
+
+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.
+ my $sth = $dbh->prepare('INSERT INTO bugs_activity (bug_id, attach_id, who, bug_when,
+ fieldid, removed, added)
+ VALUES (?, ?, ?, ?, ?, ?, ?)');
+
+ foreach my $field (keys %$changes) {
+ my $change = $changes->{$field};
+ $field = "attachments.$field" unless $field eq "flagtypes.name";
+ my $fieldid = get_field_id($field);
+ $sth->execute($self->bug_id, $self->id, $user->id, $timestamp,
+ $fieldid, $change->[0], $change->[1]);
+ }
+
+ 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));
+ }
+
+ return $changes;
+}
+
+=pod
+
+=item C<remove_from_db()>
+
+Description: removes an attachment from the DB.
+
+Params: none
+
+Returns: nothing
+
+=back
+
+=cut
+
+sub remove_from_db {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ $dbh->bz_start_transaction();
+ $dbh->do('DELETE FROM flags WHERE attach_id = ?', undef, $self->id);
+ $dbh->do('DELETE FROM attach_data WHERE id = ?', undef, $self->id);
+ $dbh->do('UPDATE attachments SET mimetype = ?, ispatch = ?, isurl = ?, isobsolete = ?
+ WHERE attach_id = ?', undef, ('text/plain', 0, 0, 1, $self->id));
+ $dbh->bz_commit_transaction();
+}
+
+###############################
+#### Helpers #####
+###############################
+
+# Extract the content type from the attachment form.
+sub get_content_type {
+ my $cgi = Bugzilla->cgi;
+
+ return 'text/plain' if ($cgi->param('ispatch') || $cgi->param('attachurl'));
+
+ my $content_type;
+ if (!defined $cgi->param('contenttypemethod')) {
+ ThrowUserError("missing_content_type_method");
+ }
+ elsif ($cgi->param('contenttypemethod') eq 'autodetect') {
+ 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");
+
+ # Set the ispatch flag to 1 if the content type
+ # is text/x-diff or text/x-patch
+ if ($content_type =~ m{text/x-(?:diff|patch)}) {
+ $cgi->param('ispatch', 1);
+ $content_type = 'text/plain';
+ }
+
+ # 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';
+ }
+ }
+ elsif ($cgi->param('contenttypemethod') eq 'list') {
+ # The user selected a content type from the list, so use their
+ # selection.
+ $content_type = $cgi->param('contenttypeselection');
+ }
+ elsif ($cgi->param('contenttypemethod') eq 'manual') {
+ # The user entered a content type manually, so use their entry.
+ $content_type = $cgi->param('contenttypeentry');
+ }
+ else {
+ ThrowCodeError("illegal_content_type_method",
+ { contenttypemethod => $cgi->param('contenttypemethod') });
+ }
+ return $content_type;
+}
+
+
+1;
diff --git a/Bugzilla/Attachment/PatchReader.pm b/Bugzilla/Attachment/PatchReader.pm
new file mode 100644
index 000000000..cfc7610f4
--- /dev/null
+++ b/Bugzilla/Attachment/PatchReader.pm
@@ -0,0 +1,295 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s): John Keiser <john@johnkeiser.com>
+# Frédéric Buclin <LpSolit@gmail.com>
+
+use strict;
+
+package Bugzilla::Attachment::PatchReader;
+
+use Bugzilla::Error;
+use Bugzilla::Attachment;
+use Bugzilla::Util;
+
+sub process_diff {
+ my ($attachment, $format, $context) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $cgi = Bugzilla->cgi;
+ my $lc = Bugzilla->localconfig;
+ my $vars = {};
+
+ my ($reader, $last_reader) = setup_patch_readers(undef, $context);
+
+ if ($format eq 'raw') {
+ require PatchReader::DiffPrinter::raw;
+ $last_reader->sends_data_to(new PatchReader::DiffPrinter::raw());
+ # Actually print out the patch.
+ print $cgi->header(-type => 'text/plain',
+ -expires => '+3M');
+ disable_utf8();
+ $reader->iterate_string('Attachment ' . $attachment->id, $attachment->data);
+ }
+ else {
+ my @other_patches = ();
+ if ($lc->{interdiffbin} && $lc->{diffpath}) {
+ # Get the list of attachments that the user can view in this bug.
+ my @attachments =
+ @{Bugzilla::Attachment->get_attachments_by_bug($attachment->bug_id)};
+ # Extract patches only.
+ @attachments = grep {$_->ispatch == 1} @attachments;
+ # We want them sorted from newer to older.
+ @attachments = sort { $b->id <=> $a->id } @attachments;
+
+ # Ignore the current patch, but select the one right before it
+ # chronologically.
+ my $select_next_patch = 0;
+ foreach my $attach (@attachments) {
+ if ($attach->id == $attachment->id) {
+ $select_next_patch = 1;
+ }
+ else {
+ push(@other_patches, { 'id' => $attach->id,
+ 'desc' => $attach->description,
+ 'selected' => $select_next_patch });
+ $select_next_patch = 0;
+ }
+ }
+ }
+
+ $vars->{'bugid'} = $attachment->bug_id;
+ $vars->{'attachid'} = $attachment->id;
+ $vars->{'description'} = $attachment->description;
+ $vars->{'other_patches'} = \@other_patches;
+
+ setup_template_patch_reader($last_reader, $format, $context, $vars);
+ # The patch is going to be displayed in a HTML page and if the utf8
+ # param is enabled, we have to encode attachment data as utf8.
+ if (Bugzilla->params->{'utf8'}) {
+ $attachment->data; # Populate ->{data}
+ utf8::decode($attachment->{data});
+ }
+ $reader->iterate_string('Attachment ' . $attachment->id, $attachment->data);
+ }
+}
+
+sub process_interdiff {
+ my ($old_attachment, $new_attachment, $format, $context) = @_;
+ my $cgi = Bugzilla->cgi;
+ my $lc = Bugzilla->localconfig;
+ my $vars = {};
+
+ # Encode attachment data as utf8 if it's going to be displayed in a HTML
+ # page using the UTF-8 encoding.
+ if ($format ne 'raw' && Bugzilla->params->{'utf8'}) {
+ $old_attachment->data; # Populate ->{data}
+ utf8::decode($old_attachment->{data});
+ $new_attachment->data; # Populate ->{data}
+ utf8::decode($new_attachment->{data});
+ }
+
+ # Get old patch data.
+ my ($old_filename, $old_file_list) = get_unified_diff($old_attachment, $format);
+ # Get new patch data.
+ my ($new_filename, $new_file_list) = get_unified_diff($new_attachment, $format);
+
+ my $warning = warn_if_interdiff_might_fail($old_file_list, $new_file_list);
+
+ # Send through interdiff, send output directly to template.
+ # Must hack path so that interdiff will work.
+ $ENV{'PATH'} = $lc->{diffpath};
+ open my $interdiff_fh, "$lc->{interdiffbin} $old_filename $new_filename|";
+ binmode $interdiff_fh;
+ my ($reader, $last_reader) = setup_patch_readers("", $context);
+
+ if ($format eq 'raw') {
+ require PatchReader::DiffPrinter::raw;
+ $last_reader->sends_data_to(new PatchReader::DiffPrinter::raw());
+ # Actually print out the patch.
+ print $cgi->header(-type => 'text/plain',
+ -expires => '+3M');
+ disable_utf8();
+ }
+ else {
+ # In case the HTML page is displayed with the UTF-8 encoding.
+ binmode $interdiff_fh, ':utf8' if Bugzilla->params->{'utf8'};
+
+ $vars->{'warning'} = $warning if $warning;
+ $vars->{'bugid'} = $new_attachment->bug_id;
+ $vars->{'oldid'} = $old_attachment->id;
+ $vars->{'old_desc'} = $old_attachment->description;
+ $vars->{'newid'} = $new_attachment->id;
+ $vars->{'new_desc'} = $new_attachment->description;
+
+ setup_template_patch_reader($last_reader, $format, $context, $vars);
+ }
+ $reader->iterate_fh($interdiff_fh, 'interdiff #' . $old_attachment->id .
+ ' #' . $new_attachment->id);
+ close $interdiff_fh;
+ $ENV{'PATH'} = '';
+
+ # Delete temporary files.
+ unlink($old_filename) or warn "Could not unlink $old_filename: $!";
+ unlink($new_filename) or warn "Could not unlink $new_filename: $!";
+}
+
+######################
+# Internal routines
+######################
+
+sub get_unified_diff {
+ my ($attachment, $format) = @_;
+
+ # Bring in the modules we need.
+ require PatchReader::Raw;
+ require PatchReader::FixPatchRoot;
+ require PatchReader::DiffPrinter::raw;
+ require PatchReader::PatchInfoGrabber;
+ require File::Temp;
+
+ $attachment->ispatch
+ || ThrowCodeError('must_be_patch', { 'attach_id' => $attachment->id });
+
+ # Reads in the patch, converting to unified diff in a temp file.
+ my $reader = new PatchReader::Raw;
+ my $last_reader = $reader;
+
+ # Fixes patch root (makes canonical if possible).
+ if (Bugzilla->params->{'cvsroot'}) {
+ my $fix_patch_root =
+ new PatchReader::FixPatchRoot(Bugzilla->params->{'cvsroot'});
+ $last_reader->sends_data_to($fix_patch_root);
+ $last_reader = $fix_patch_root;
+ }
+
+ # Grabs the patch file info.
+ my $patch_info_grabber = new PatchReader::PatchInfoGrabber();
+ $last_reader->sends_data_to($patch_info_grabber);
+ $last_reader = $patch_info_grabber;
+
+ # Prints out to temporary file.
+ my ($fh, $filename) = File::Temp::tempfile();
+ if ($format ne 'raw' && Bugzilla->params->{'utf8'}) {
+ # The HTML page will be displayed with the UTF-8 encoding.
+ binmode $fh, ':utf8';
+ }
+ my $raw_printer = new PatchReader::DiffPrinter::raw($fh);
+ $last_reader->sends_data_to($raw_printer);
+ $last_reader = $raw_printer;
+
+ # Iterate!
+ $reader->iterate_string($attachment->id, $attachment->data);
+
+ return ($filename, $patch_info_grabber->patch_info()->{files});
+}
+
+sub warn_if_interdiff_might_fail {
+ my ($old_file_list, $new_file_list) = @_;
+
+ # Verify that the list of files diffed is the same.
+ my @old_files = sort keys %{$old_file_list};
+ my @new_files = sort keys %{$new_file_list};
+ if (@old_files != @new_files
+ || join(' ', @old_files) ne join(' ', @new_files))
+ {
+ return 'interdiff1';
+ }
+
+ # Verify that the revisions in the files are the same.
+ foreach my $file (keys %{$old_file_list}) {
+ if ($old_file_list->{$file}{old_revision} ne
+ $new_file_list->{$file}{old_revision})
+ {
+ return 'interdiff2';
+ }
+ }
+ return undef;
+}
+
+sub setup_patch_readers {
+ my ($diff_root, $context) = @_;
+
+ # Parameters:
+ # format=raw|html
+ # context=patch|file|0-n
+ # collapsed=0|1
+ # headers=0|1
+
+ # Define the patch readers.
+ # The reader that reads the patch in (whatever its format).
+ require PatchReader::Raw;
+ my $reader = new PatchReader::Raw;
+ my $last_reader = $reader;
+ # Fix the patch root if we have a cvs root.
+ if (Bugzilla->params->{'cvsroot'}) {
+ require PatchReader::FixPatchRoot;
+ $last_reader->sends_data_to(new PatchReader::FixPatchRoot(Bugzilla->params->{'cvsroot'}));
+ $last_reader->sends_data_to->diff_root($diff_root) if defined($diff_root);
+ $last_reader = $last_reader->sends_data_to;
+ }
+
+ # Add in cvs context if we have the necessary info to do it
+ if ($context ne 'patch' && Bugzilla->localconfig->{cvsbin}
+ && Bugzilla->params->{'cvsroot_get'})
+ {
+ require PatchReader::AddCVSContext;
+ # We need to set $cvsbin as global, because PatchReader::CVSClient
+ # needs it in order to find 'cvs'.
+ $main::cvsbin = Bugzilla->localconfig->{cvsbin};
+ $last_reader->sends_data_to(
+ new PatchReader::AddCVSContext($context, Bugzilla->params->{'cvsroot_get'}));
+ $last_reader = $last_reader->sends_data_to;
+ }
+
+ return ($reader, $last_reader);
+}
+
+sub setup_template_patch_reader {
+ my ($last_reader, $format, $context, $vars) = @_;
+ my $cgi = Bugzilla->cgi;
+ my $template = Bugzilla->template;
+
+ require PatchReader::DiffPrinter::template;
+
+ # Define the vars for templates.
+ if (defined $cgi->param('headers')) {
+ $vars->{'headers'} = $cgi->param('headers');
+ }
+ else {
+ $vars->{'headers'} = 1;
+ }
+
+ $vars->{'collapsed'} = $cgi->param('collapsed');
+ $vars->{'context'} = $context;
+ $vars->{'do_context'} = Bugzilla->localconfig->{cvsbin}
+ && Bugzilla->params->{'cvsroot_get'} && !$vars->{'newid'};
+
+ # Print everything out.
+ print $cgi->header(-type => 'text/html',
+ -expires => '+3M');
+
+ $last_reader->sends_data_to(new PatchReader::DiffPrinter::template($template,
+ "attachment/diff-header.$format.tmpl",
+ "attachment/diff-file.$format.tmpl",
+ "attachment/diff-footer.$format.tmpl",
+ { %{$vars},
+ bonsai_url => Bugzilla->params->{'bonsai_url'},
+ lxr_url => Bugzilla->params->{'lxr_url'},
+ lxr_root => Bugzilla->params->{'lxr_root'},
+ }));
+}
+
+1;
+
+__END__
diff --git a/Bugzilla/Auth.pm b/Bugzilla/Auth.pm
new file mode 100644
index 000000000..782953878
--- /dev/null
+++ b/Bugzilla/Auth.pm
@@ -0,0 +1,524 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Bradley Baetz <bbaetz@acm.org>
+# Erik Stambaugh <erik@dasbistro.com>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::Auth;
+
+use strict;
+use fields qw(
+ _info_getter
+ _verifier
+ _persister
+);
+
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Mailer;
+use Bugzilla::Util qw(datetime_from);
+use Bugzilla::User::Setting ();
+use Bugzilla::Auth::Login::Stack;
+use Bugzilla::Auth::Verify::Stack;
+use Bugzilla::Auth::Persist::Cookie;
+
+sub new {
+ my ($class, $params) = @_;
+ my $self = fields::new($class);
+
+ $params ||= {};
+ $params->{Login} ||= Bugzilla->params->{'user_info_class'} . ',Cookie';
+ $params->{Verify} ||= Bugzilla->params->{'user_verify_class'};
+
+ $self->{_info_getter} = new Bugzilla::Auth::Login::Stack($params->{Login});
+ $self->{_verifier} = new Bugzilla::Auth::Verify::Stack($params->{Verify});
+ # If we ever have any other login persistence methods besides cookies,
+ # this could become more configurable.
+ $self->{_persister} = new Bugzilla::Auth::Persist::Cookie();
+
+ return $self;
+}
+
+sub login {
+ my ($self, $type) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ # Get login info from the cookie, form, environment variables, etc.
+ my $login_info = $self->{_info_getter}->get_login_info();
+
+ if ($login_info->{failure}) {
+ return $self->_handle_login_result($login_info, $type);
+ }
+
+ # Now verify his username and password against the DB, LDAP, etc.
+ if ($self->{_info_getter}->{successful}->requires_verification) {
+ $login_info = $self->{_verifier}->check_credentials($login_info);
+ if ($login_info->{failure}) {
+ return $self->_handle_login_result($login_info, $type);
+ }
+ $login_info =
+ $self->{_verifier}->{successful}->create_or_update_user($login_info);
+ }
+ else {
+ $login_info = $self->{_verifier}->create_or_update_user($login_info);
+ }
+
+ if ($login_info->{failure}) {
+ return $self->_handle_login_result($login_info, $type);
+ }
+
+ # Make sure the user isn't disabled.
+ my $user = $login_info->{user};
+ if ($user->disabledtext) {
+ return $self->_handle_login_result({ failure => AUTH_DISABLED,
+ user => $user }, $type);
+ }
+ $user->set_authorizer($self);
+
+ return $self->_handle_login_result($login_info, $type);
+}
+
+sub can_change_password {
+ my ($self) = @_;
+ my $verifier = $self->{_verifier}->{successful};
+ $verifier ||= $self->{_verifier};
+ my $getter = $self->{_info_getter}->{successful};
+ $getter = $self->{_info_getter}
+ if (!$getter || $getter->isa('Bugzilla::Auth::Login::Cookie'));
+ return $verifier->can_change_password &&
+ $getter->user_can_create_account;
+}
+
+sub can_login {
+ my ($self) = @_;
+ my $getter = $self->{_info_getter}->{successful};
+ $getter = $self->{_info_getter}
+ if (!$getter || $getter->isa('Bugzilla::Auth::Login::Cookie'));
+ return $getter->can_login;
+}
+
+sub can_logout {
+ my ($self) = @_;
+ my $getter = $self->{_info_getter}->{successful};
+ # If there's no successful getter, we're not logged in, so of
+ # course we can't log out!
+ return 0 unless $getter;
+ return $getter->can_logout;
+}
+
+sub user_can_create_account {
+ my ($self) = @_;
+ my $verifier = $self->{_verifier}->{successful};
+ $verifier ||= $self->{_verifier};
+ my $getter = $self->{_info_getter}->{successful};
+ $getter = $self->{_info_getter}
+ if (!$getter || $getter->isa('Bugzilla::Auth::Login::Cookie'));
+ return $verifier->user_can_create_account
+ && $getter->user_can_create_account;
+}
+
+sub can_change_email {
+ return $_[0]->user_can_create_account;
+}
+
+sub _handle_login_result {
+ my ($self, $result, $login_type) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ my $user = $result->{user};
+ my $fail_code = $result->{failure};
+
+ if (!$fail_code) {
+ # 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})
+ {
+ $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;
+
+ # 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_username_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});
+ my $vars = {
+ locked_user => $user,
+ attempts => $attempts,
+ unlock_at => $unlock_at,
+ };
+ 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 });
+ }
+ # If we get here, then we've run out of options, which shouldn't happen.
+ else {
+ ThrowCodeError("authres_unhandled", { value => $fail_code });
+ }
+
+ return $user;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Auth - An object that authenticates the login credentials for
+ a user.
+
+=head1 DESCRIPTION
+
+Handles authentication for Bugzilla users.
+
+Authentication from Bugzilla involves two sets of modules. One set is
+used to obtain the username/password (from CGI, email, etc), and the
+other set uses this data to authenticate against the datasource
+(the Bugzilla DB, LDAP, PAM, etc.).
+
+Modules for obtaining the username/password are subclasses of
+L<Bugzilla::Auth::Login>, and modules for authenticating are subclasses
+of L<Bugzilla::Auth::Verify>.
+
+=head1 AUTHENTICATION ERROR CODES
+
+Whenever a method in the C<Bugzilla::Auth> family fails in some way,
+it will return a hashref containing at least a single key called C<failure>.
+C<failure> will point to an integer error code, and depending on the error
+code the hashref may contain more data.
+
+The error codes are explained here below.
+
+=head2 C<AUTH_NODATA>
+
+Insufficient login data was provided by the user. This may happen in several
+cases, such as cookie authentication when the cookie is not present.
+
+=head2 C<AUTH_ERROR>
+
+An error occurred when trying to use the login mechanism.
+
+The hashref will also contain an C<error> element, which is the name
+of an error from C<template/en/default/global/code-error.html> --
+the same type of error that would be thrown by
+L<Bugzilla::Error::ThrowCodeError>.
+
+The hashref *may* contain an element called C<details>, which is a hashref
+that should be passed to L<Bugzilla::Error::ThrowCodeError> as the
+various fields to be used in the error message.
+
+=head2 C<AUTH_LOGINFAILED>
+
+An incorrect username or password was given.
+
+The hashref may also contain a C<failure_count> element, which specifies
+how many times the account has failed to log in within the lockout
+period (see L</AUTH_LOCKOUT>). This is used to warn the user when
+he is getting close to being locked out.
+
+=head2 C<AUTH_NO_SUCH_USER>
+
+This is an optional more-specific version of C<AUTH_LOGINFAILED>.
+Modules should throw this error when they discover that the
+requested user account actually does not exist, according to them.
+
+That is, for example, L<Bugzilla::Auth::Verify::LDAP> would throw
+this if the user didn't exist in LDAP.
+
+The difference between C<AUTH_NO_SUCH_USER> and C<AUTH_LOGINFAILED>
+should never be communicated to the user, for security reasons.
+
+=head2 C<AUTH_DISABLED>
+
+The user successfully logged in, but their account has been disabled.
+Usually this is throw only by C<Bugzilla::Auth::login>.
+
+=head2 C<AUTH_LOCKOUT>
+
+The user's account is locked out after having failed to log in too many
+times within a certain period of time (as specified by
+L<Bugzilla::Constants/LOGIN_LOCKOUT_INTERVAL>).
+
+The hashref will also contain a C<user> element, representing the
+L<Bugzilla::User> whose account is locked out.
+
+=head1 LOGIN TYPES
+
+The C<login> function (below) can do different types of login, depending
+on what constant you pass into it:
+
+=head2 C<LOGIN_OPTIONAL>
+
+A login is never required to access this data. Attempting to login is
+still useful, because this allows the page to be personalised. Note that
+an incorrect login will still trigger an error, even though the lack of
+a login will be OK.
+
+=head2 C<LOGIN_NORMAL>
+
+A login may or may not be required, depending on the setting of the
+I<requirelogin> parameter. This is the default if you don't specify a
+type.
+
+=head2 C<LOGIN_REQUIRED>
+
+A login is always required to access this data.
+
+=head1 METHODS
+
+These are methods that can be called on a C<Bugzilla::Auth> object
+itself.
+
+=head2 Login
+
+=over 4
+
+=item C<login($type)>
+
+Description: Logs a user in. For more details on how this works
+ internally, see the section entitled "STRUCTURE."
+Params: $type - One of the Login Types from above.
+Returns: An authenticated C<Bugzilla::User>. Or, if the type was
+ not C<LOGIN_REQUIRED>, then we return an
+ empty C<Bugzilla::User> if no login data was passed in.
+
+=back
+
+=head2 Info Methods
+
+These are methods that give information about the Bugzilla::Auth object.
+
+=over 4
+
+=item C<can_change_password>
+
+Description: Tells you whether or not the current login system allows
+ changing passwords.
+Params: None
+Returns: C<true> if users and administrators should be allowed to
+ change passwords, C<false> otherwise.
+
+=item C<can_login>
+
+Description: Tells you whether or not the current login system allows
+ users to log in through the web interface.
+Params: None
+Returns: C<true> if users can log in through the web interface,
+ C<false> otherwise.
+
+=item C<can_logout>
+
+Description: Tells you whether or not the current login system allows
+ users to log themselves out.
+Params: None
+Returns: C<true> if users can log themselves out, C<false> otherwise.
+ If a user isn't logged in, we always return C<false>.
+
+=item C<user_can_create_account>
+
+Description: Tells you whether or not users are allowed to manually create
+ their own accounts, based on the current login system in use.
+ Note that this doesn't check the C<createemailregexp>
+ parameter--you have to do that by yourself in your code.
+Params: None
+Returns: C<true> if users are allowed to create new Bugzilla accounts,
+ C<false> otherwise.
+
+=item C<can_change_email>
+
+Description: Whether or not the current login system allows users to
+ change their own email address.
+Params: None
+Returns: C<true> if users can change their own email address,
+ C<false> otherwise.
+
+=back
+
+=head1 STRUCTURE
+
+This section is mostly interesting to developers who want to implement
+a new authentication type. It describes the general structure of the
+Bugzilla::Auth family, and how the C<login> function works.
+
+A C<Bugzilla::Auth> object is essentially a collection of a few other
+objects: the "Info Getter," the "Verifier," and the "Persistence
+Mechanism."
+
+They are used inside the C<login> function in the following order:
+
+=head2 The Info Getter
+
+This is a C<Bugzilla::Auth::Login> object. Basically, it gets the
+username and password from the user, somehow. Or, it just gets enough
+information to uniquely identify a user, and passes that on down the line.
+(For example, a C<user_id> is enough to uniquely identify a user,
+even without a username and password.)
+
+Some Info Getters don't require any verification. For example, if we got
+the C<user_id> from a Cookie, we don't need to check the username and
+password.
+
+If an Info Getter returns only a C<user_id> and no username/password,
+then it MUST NOT require verification. If an Info Getter requires
+verfication, then it MUST return at least a C<username>.
+
+=head2 The Verifier
+
+This verifies that the username and password are valid.
+
+It's possible that some methods of verification don't require a password.
+
+=head2 The Persistence Mechanism
+
+This makes it so that the user doesn't have to log in on every page.
+Normally this object just sends a cookie to the user's web browser,
+as that's the most common method of "login persistence."
+
+=head2 Other Things We Do
+
+After we verify the username and password, sometimes we automatically
+create an account in the Bugzilla database, for certain authentication
+types. We use the "Account Source" to get data about the user, and
+create them in the database. (Or, if their data has changed since the
+last time they logged in, their data gets updated.)
+
+=head2 The C<$login_data> Hash
+
+All of the C<Bugzilla::Auth::Login> and C<Bugzilla::Auth::Verify>
+methods take an argument called C<$login_data>. This is basically
+a hash that becomes more and more populated as we go through the
+C<login> function.
+
+All C<Bugzilla::Auth::Login> and C<Bugzilla::Auth::Verify> methods
+also *return* the C<$login_data> structure, when they succeed. They
+may have added new data to it.
+
+For all C<Bugzilla::Auth::Login> and C<Bugzilla::Auth::Verify> methods,
+the rule is "you must return the same hashref you were passed in." You can
+modify the hashref all you want, but you can't create a new one. The only
+time you can return a new one is if you're returning some error code
+instead of the C<$login_data> structure.
+
+Each C<Bugzilla::Auth::Login> or C<Bugzilla::Auth::Verify> method
+explains in its documentation which C<$login_data> elements are
+required by it, and which are set by it.
+
+Here are all of the elements that *may* be in C<$login_data>:
+
+=over 4
+
+=item C<user_id>
+
+A Bugzilla C<user_id> that uniquely identifies a user.
+
+=item C<username>
+
+The username that was provided by the user.
+
+=item C<bz_username>
+
+The username of this user inside of Bugzilla. Sometimes this differs from
+C<username>.
+
+=item C<password>
+
+The password provided by the user.
+
+=item C<realname>
+
+The real name of the user.
+
+=item C<extern_id>
+
+Some string that uniquely identifies the user in an external account
+source. If this C<extern_id> already exists in the database with
+a different username, the username will be *changed* to be the
+username specified in this C<$login_data>.
+
+That is, let's my extern_id is C<mkanat>. I already have an account
+in Bugzilla with the username of C<mkanat@foo.com>. But this time,
+when I log in, I have an extern_id of C<mkanat> and a C<username>
+of C<mkanat@bar.org>. So now, Bugzilla will automatically change my
+username to C<mkanat@bar.org> instead of C<mkanat@foo.com>.
+
+=item C<user>
+
+A L<Bugzilla::User> object representing the authenticated user.
+Note that C<Bugzilla::Auth::login> may modify this object at various points.
+
+=back
+
+
diff --git a/Bugzilla/Auth/Login.pm b/Bugzilla/Auth/Login.pm
new file mode 100644
index 000000000..b3add7365
--- /dev/null
+++ b/Bugzilla/Auth/Login.pm
@@ -0,0 +1,134 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::Auth::Login;
+
+use strict;
+use fields qw();
+
+# Determines whether or not a user can logout. It's really a subroutine,
+# but we implement it here as a constant. Override it in subclasses if
+# that particular type of login method cannot log out.
+use constant can_logout => 1;
+use constant can_login => 1;
+use constant requires_persistence => 1;
+use constant requires_verification => 1;
+use constant user_can_create_account => 0;
+use constant is_automatic => 0;
+
+sub new {
+ my ($class) = @_;
+ my $self = fields::new($class);
+ return $self;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Auth::Login - Gets username/password data from the user.
+
+=head1 DESCRIPTION
+
+Bugzilla::Auth::Login is used to get information that uniquely identifies
+a user and allows us to authorize their Bugzilla access.
+
+It is mostly an abstract class, requiring subclasses to implement
+most methods.
+
+Note that callers outside of the C<Bugzilla::Auth> package should never
+create this object directly. Just create a C<Bugzilla::Auth> object
+and call C<login> on it.
+
+=head1 LOGIN METHODS
+
+These are methods that have to do with getting the actual login data
+from the user or handling a login somehow.
+
+These methods are abstract -- they MUST be implemented by a subclass.
+
+=over 4
+
+=item C<get_login_info()>
+
+Description: Gets a username/password from the user, or some other
+ information that uniquely identifies them.
+Params: None
+Returns: A C<$login_data> hashref. (See L<Bugzilla::Auth> for details.)
+ The hashref MUST contain: C<user_id> *or* C<username>
+ If this is a login method that requires verification,
+ the hashref MUST contain C<password>.
+ The hashref MAY contain C<realname> and C<extern_id>.
+
+=item C<fail_nodata()>
+
+Description: This function is called when Bugzilla doesn't get
+ a username/password and the login type is C<LOGIN_REQUIRED>
+ (See L<Bugzilla::Auth> for a description of C<LOGIN_REQUIRED>).
+ That is, this handles C<AUTH_NODATA> in that situation.
+
+ This function MUST stop CGI execution when it is complete.
+ That is, it must call C<exit> or C<ThrowUserError> or some
+ such thing.
+Params: None
+Returns: Never Returns.
+
+=back
+
+=head1 INFO METHODS
+
+These are methods that describe the capabilities of this
+C<Bugzilla::Auth::Login> object. These are all no-parameter
+methods that return either C<true> or C<false>.
+
+=over 4
+
+=item C<can_logout>
+
+Whether or not users can log out if they logged in using this
+object. Defaults to C<true>.
+
+=item C<can_login>
+
+Whether or not users can log in through the web interface using
+this object. Defaults to C<true>.
+
+=item C<requires_persistence>
+
+Whether or not we should send the user a cookie if they logged in with
+this method. Defaults to C<true>.
+
+=item C<requires_verification>
+
+Whether or not we should check the username/password that we
+got from this login method. Defaults to C<true>.
+
+=item C<user_can_create_account>
+
+Whether or not users can create accounts, if this login method is
+currently being used by the system. Defaults to C<false>.
+
+=item C<is_automatic>
+
+True if this login method requires no interaction from the user within
+Bugzilla. (For example, C<Env> auth is "automatic" because the webserver
+just passes us an environment variable on most page requests, and does not
+ask the user for authentication information directly in Bugzilla.) Defaults
+to C<false>.
+
+=back
diff --git a/Bugzilla/Auth/Login/CGI.pm b/Bugzilla/Auth/Login/CGI.pm
new file mode 100644
index 000000000..8e877b951
--- /dev/null
+++ b/Bugzilla/Auth/Login/CGI.pm
@@ -0,0 +1,71 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Dan Mosedale <dmose@mozilla.org>
+# Joe Robins <jmrobins@tgix.com>
+# Dave Miller <justdave@syndicomm.com>
+# Christopher Aillon <christopher@aillon.com>
+# Gervase Markham <gerv@gerv.net>
+# Christian Reis <kiko@async.com.br>
+# Bradley Baetz <bbaetz@acm.org>
+# Erik Stambaugh <erik@dasbistro.com>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::Auth::Login::CGI;
+use strict;
+use base qw(Bugzilla::Auth::Login);
+use constant user_can_create_account => 1;
+
+use Bugzilla::Constants;
+use Bugzilla::WebService::Constants;
+use Bugzilla::Util;
+use Bugzilla::Error;
+
+sub get_login_info {
+ my ($self) = @_;
+ my $params = Bugzilla->input_params;
+
+ my $username = trim(delete $params->{"Bugzilla_login"});
+ my $password = delete $params->{"Bugzilla_password"};
+
+ if (!defined $username || !defined $password) {
+ return { failure => AUTH_NODATA };
+ }
+
+ return { username => $username, password => $password };
+}
+
+sub fail_nodata {
+ my ($self) = @_;
+ my $cgi = Bugzilla->cgi;
+ my $template = Bugzilla->template;
+
+ if (Bugzilla->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
new file mode 100644
index 000000000..91fb820fb
--- /dev/null
+++ b/Bugzilla/Auth/Login/Cookie.pm
@@ -0,0 +1,88 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s): Bradley Baetz <bbaetz@acm.org>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::Auth::Login::Cookie;
+use strict;
+use base qw(Bugzilla::Auth::Login);
+
+use Bugzilla::Constants;
+use Bugzilla::Util;
+
+use List::Util qw(first);
+
+use constant requires_persistence => 0;
+use constant requires_verification => 0;
+use constant can_login => 0;
+use constant is_automatic => 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 $ip_addr = remote_ip();
+ my $login_cookie = $cgi->cookie("Bugzilla_logincookie");
+ my $user_id = $cgi->cookie("Bugzilla_login");
+
+ # If cookies cannot be found, this could mean that they haven't
+ # been made available yet. In this case, look at Bugzilla_cookie_list.
+ unless ($login_cookie) {
+ my $cookie = first {$_->name eq 'Bugzilla_logincookie'}
+ @{$cgi->{'Bugzilla_cookie_list'}};
+ $login_cookie = $cookie->value if $cookie;
+ }
+ unless ($user_id) {
+ my $cookie = first {$_->name eq 'Bugzilla_login'}
+ @{$cgi->{'Bugzilla_cookie_list'}};
+ $user_id = $cookie->value if $cookie;
+ }
+
+ if ($login_cookie && $user_id) {
+ # Anything goes for these params - they're just strings which
+ # we're going to verify against the db
+ trick_taint($ip_addr);
+ trick_taint($login_cookie);
+ detaint_natural($user_id);
+
+ my $is_valid =
+ $dbh->selectrow_array('SELECT 1
+ FROM logincookies
+ WHERE cookie = ?
+ AND userid = ?
+ AND (ipaddr = ? OR ipaddr IS NULL)',
+ undef, ($login_cookie, $user_id, $ip_addr));
+
+ # If the cookie is valid, return a valid username.
+ if ($is_valid) {
+ # If we logged in successfully, then update the lastused
+ # time on the login cookie
+ $dbh->do("UPDATE logincookies SET lastused = NOW()
+ WHERE cookie = ?", undef, $login_cookie);
+ return { user_id => $user_id };
+ }
+ }
+
+ # Either the he cookie is invalid, or we got no cookie. We don't want
+ # to ever return AUTH_LOGINFAILED, because we don't want Bugzilla to
+ # actually throw an error when it gets a bad cookie. It should just
+ # look like there was no cookie to begin with.
+ return { failure => AUTH_NODATA };
+}
+
+1;
diff --git a/Bugzilla/Auth/Login/Env.pm b/Bugzilla/Auth/Login/Env.pm
new file mode 100644
index 000000000..f93034ef3
--- /dev/null
+++ b/Bugzilla/Auth/Login/Env.pm
@@ -0,0 +1,54 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Erik Stambaugh <erik@dasbistro.com>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::Auth::Login::Env;
+use strict;
+use base qw(Bugzilla::Auth::Login);
+
+use Bugzilla::Constants;
+use Bugzilla::Error;
+
+use constant can_logout => 0;
+use constant can_login => 0;
+use constant requires_persistence => 0;
+use constant requires_verification => 0;
+use constant is_automatic => 1;
+
+sub get_login_info {
+ my ($self) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ my $env_id = $ENV{Bugzilla->params->{"auth_env_id"}} || '';
+ my $env_email = $ENV{Bugzilla->params->{"auth_env_email"}} || '';
+ my $env_realname = $ENV{Bugzilla->params->{"auth_env_realname"}} || '';
+
+ return { failure => AUTH_NODATA } if !$env_email;
+
+ return { username => $env_email, extern_id => $env_id,
+ realname => $env_realname };
+}
+
+sub fail_nodata {
+ ThrowCodeError('env_no_email');
+}
+
+1;
diff --git a/Bugzilla/Auth/Login/Stack.pm b/Bugzilla/Auth/Login/Stack.pm
new file mode 100644
index 000000000..f490d243b
--- /dev/null
+++ b/Bugzilla/Auth/Login/Stack.pm
@@ -0,0 +1,100 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::Auth::Login::Stack;
+use strict;
+use base qw(Bugzilla::Auth::Login);
+use fields qw(
+ _stack
+ successful
+);
+use Hash::Util qw(lock_keys);
+use Bugzilla::Hook;
+
+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;
+}
+
+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;
+ last if !$result->{failure};
+ # So that if none of them succeed, it's undef.
+ $self->{successful} = undef;
+ }
+ return $result;
+}
+
+sub fail_nodata {
+ my $self = shift;
+ # We fail from the bottom of the stack.
+ my @reverse_stack = reverse @{$self->{_stack}};
+ foreach my $object (@reverse_stack) {
+ # We pick the first object that actually has the method
+ # implemented.
+ if ($object->can('fail_nodata')) {
+ $object->fail_nodata(@_);
+ }
+ }
+}
+
+sub can_login {
+ my ($self) = @_;
+ # We return true if any method can log in.
+ foreach my $object (@{$self->{_stack}}) {
+ return 1 if $object->can_login;
+ }
+ return 0;
+}
+
+sub user_can_create_account {
+ my ($self) = @_;
+ # We return true if any method allows users to create accounts.
+ foreach my $object (@{$self->{_stack}}) {
+ return 1 if $object->user_can_create_account;
+ }
+ return 0;
+}
+
+1;
diff --git a/Bugzilla/Auth/Persist/Cookie.pm b/Bugzilla/Auth/Persist/Cookie.pm
new file mode 100644
index 000000000..232212075
--- /dev/null
+++ b/Bugzilla/Auth/Persist/Cookie.pm
@@ -0,0 +1,163 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Dan Mosedale <dmose@mozilla.org>
+# Joe Robins <jmrobins@tgix.com>
+# Dave Miller <justdave@syndicomm.com>
+# Christopher Aillon <christopher@aillon.com>
+# Gervase Markham <gerv@gerv.net>
+# Christian Reis <kiko@async.com.br>
+# Bradley Baetz <bbaetz@acm.org>
+# Erik Stambaugh <erik@dasbistro.com>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::Auth::Persist::Cookie;
+use strict;
+use fields qw();
+
+use Bugzilla::Constants;
+use Bugzilla::Util;
+use Bugzilla::Token;
+
+use List::Util qw(first);
+
+sub new {
+ my ($class) = @_;
+ my $self = fields::new($class);
+ return $self;
+}
+
+sub persist_login {
+ my ($self, $user) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $cgi = Bugzilla->cgi;
+ my $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 < LOCALTIMESTAMP(0) - "
+ . $dbh->sql_interval(MAX_LOGINCOOKIE_AGE, 'DAY'));
+
+ $dbh->bz_commit_transaction();
+
+ # 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;
+ $param = {} unless $param;
+ my $user = $param->{user} || Bugzilla->user;
+ my $type = $param->{type} || LOGOUT_ALL;
+
+ if ($type == LOGOUT_ALL) {
+ $dbh->do("DELETE FROM logincookies WHERE userid = ?",
+ undef, $user->id);
+ return;
+ }
+
+ # The LOGOUT_*_CURRENT options require the current login cookie.
+ # If a new cookie has been issued during this run, that's the current one.
+ # If not, it's the one we've received.
+ my $cookie = first {$_->name eq 'Bugzilla_logincookie'}
+ @{$cgi->{'Bugzilla_cookie_list'}};
+ my $login_cookie;
+ if ($cookie) {
+ $login_cookie = $cookie->value;
+ }
+ else {
+ $login_cookie = $cgi->cookie("Bugzilla_logincookie");
+ }
+ trick_taint($login_cookie);
+
+ # These queries use both the cookie ID and the user ID as keys. Even
+ # though we know the userid must match, we still check it in the SQL
+ # as a sanity check, since there is no locking here, and if the user
+ # logged out from two machines simultaneously, while someone else
+ # logged in and got the same cookie, we could be logging the other
+ # user out here. Yes, this is very very very unlikely, but why take
+ # chances? - bbaetz
+ if ($type == LOGOUT_KEEP_CURRENT) {
+ $dbh->do("DELETE FROM logincookies WHERE cookie != ? AND userid = ?",
+ undef, $login_cookie, $user->id);
+ } elsif ($type == LOGOUT_CURRENT) {
+ $dbh->do("DELETE FROM logincookies WHERE cookie = ? AND userid = ?",
+ undef, $login_cookie, $user->id);
+ } else {
+ die("Invalid type $type supplied to logout()");
+ }
+
+ if ($type != LOGOUT_KEEP_CURRENT) {
+ clear_browser_cookies();
+ }
+
+}
+
+sub clear_browser_cookies {
+ my $cgi = Bugzilla->cgi;
+ $cgi->remove_cookie('Bugzilla_login');
+ $cgi->remove_cookie('Bugzilla_logincookie');
+ $cgi->remove_cookie('sudo');
+}
+
+1;
diff --git a/Bugzilla/Auth/Verify.pm b/Bugzilla/Auth/Verify.pm
new file mode 100644
index 000000000..b293e2583
--- /dev/null
+++ b/Bugzilla/Auth/Verify.pm
@@ -0,0 +1,235 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::Auth::Verify;
+
+use strict;
+use fields qw();
+
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::User;
+use Bugzilla::Util;
+
+use constant user_can_create_account => 1;
+
+sub new {
+ my ($class, $login_type) = @_;
+ my $self = fields::new($class);
+ return $self;
+}
+
+sub can_change_password {
+ return $_[0]->can('change_password');
+}
+
+sub create_or_update_user {
+ my ($self, $params) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ my $extern_id = $params->{extern_id};
+ my $username = $params->{bz_username} || $params->{username};
+ my $password = $params->{password} || '*';
+ my $real_name = $params->{realname} || '';
+ my $user_id = $params->{user_id};
+
+ # A passed-in user_id always overrides anything else, for determining
+ # what account we should return.
+ if (!$user_id) {
+ my $username_user_id = login_to_id($username || '');
+ my $extern_user_id;
+ if ($extern_id) {
+ trick_taint($extern_id);
+ $extern_user_id = $dbh->selectrow_array('SELECT userid
+ FROM profiles WHERE extern_id = ?', undef, $extern_id);
+ }
+
+ # If we have both a valid extern_id and a valid username, and they are
+ # not the same id, then we have a conflict.
+ if ($username_user_id && $extern_user_id
+ && $username_user_id ne $extern_user_id)
+ {
+ my $extern_name = Bugzilla::User->new($extern_user_id)->login;
+ return { failure => AUTH_ERROR, error => "extern_id_conflict",
+ details => {extern_id => $extern_id,
+ extern_user => $extern_name,
+ username => $username} };
+ }
+
+ # If we have a valid username, but no valid id,
+ # then we have to create the user. This happens when we're
+ # passed only a username, and that username doesn't exist already.
+ if ($username && !$username_user_id && !$extern_user_id) {
+ validate_email_syntax($username)
+ || return { failure => AUTH_ERROR,
+ error => 'auth_invalid_email',
+ details => {addr => $username} };
+ # Usually we'd call validate_password, but external authentication
+ # systems might follow different standards than ours. So in this
+ # place here, we call trick_taint without checks.
+ trick_taint($password);
+
+ # XXX Theoretically this could fail with an error, but the fix for
+ # that is too involved to be done right now.
+ my $user = Bugzilla::User->create({
+ login_name => $username,
+ cryptpassword => $password,
+ realname => $real_name});
+ $username_user_id = $user->id;
+ }
+
+ # If we have a valid username id and an extern_id, but no valid
+ # extern_user_id, then we have to set the user's extern_id.
+ if ($extern_id && $username_user_id && !$extern_user_id) {
+ $dbh->do('UPDATE profiles SET extern_id = ? WHERE userid = ?',
+ undef, $extern_id, $username_user_id);
+ }
+
+ # Finally, at this point, one of these will give us a valid user id.
+ $user_id = $extern_user_id || $username_user_id;
+ }
+
+ # If we still don't have a valid user_id, then we weren't passed
+ # enough information in $params, and we should die right here.
+ ThrowCodeError('bad_arg', {argument => 'params', function =>
+ 'Bugzilla::Auth::Verify::create_or_update_user'})
+ unless $user_id;
+
+ my $user = new Bugzilla::User($user_id);
+
+ # Now that we have a valid User, we need to see if any data has to be
+ # updated.
+ if ($username && lc($user->login) ne lc($username)) {
+ validate_email_syntax($username)
+ || return { failure => AUTH_ERROR, error => 'auth_invalid_email',
+ details => {addr => $username} };
+ $user->set_login($username);
+ }
+ if ($real_name && $user->name ne $real_name) {
+ # $real_name is more than likely tainted, but we only use it
+ # in a placeholder and we never use it after this.
+ trick_taint($real_name);
+ $user->set_name($real_name);
+ }
+ $user->update();
+
+ return { user => $user };
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Auth::Verify - An object that verifies usernames and passwords.
+
+=head1 DESCRIPTION
+
+Bugzilla::Auth::Verify provides the "Verifier" part of the Bugzilla
+login process. (For details, see the "STRUCTURE" section of
+L<Bugzilla::Auth>.)
+
+It is mostly an abstract class, requiring subclasses to implement
+most methods.
+
+Note that callers outside of the C<Bugzilla::Auth> package should never
+create this object directly. Just create a C<Bugzilla::Auth> object
+and call C<login> on it.
+
+=head1 VERIFICATION METHODS
+
+These are the methods that have to do with the actual verification.
+
+Subclasses MUST implement these methods.
+
+=over 4
+
+=item C<check_credentials($login_data)>
+
+Description: Checks whether or not a username is valid.
+Params: $login_data - A C<$login_data> hashref, as described in
+ L<Bugzilla::Auth>.
+ This C<$login_data> hashref MUST contain
+ C<username>, and SHOULD also contain
+ C<password>.
+Returns: A C<$login_data> hashref with C<bz_username> set. This
+ method may also set C<realname>. It must avoid changing
+ anything that is already set.
+
+=back
+
+=head1 MODIFICATION METHODS
+
+These are methods that change data in the actual authentication backend.
+
+These methods are optional, they do not have to be implemented by
+subclasses.
+
+=over 4
+
+=item C<create_or_update_user($login_data)>
+
+Description: Automatically creates a user account in the database
+ if it doesn't already exist, or updates the account
+ data if C<$login_data> contains newer information.
+
+Params: $login_data - A C<$login_data> hashref, as described in
+ L<Bugzilla::Auth>.
+ This C<$login_data> hashref MUST contain
+ either C<user_id>, C<bz_username>, or
+ C<username>. If both C<username> and C<bz_username>
+ are specified, C<bz_username> is used as the
+ login name of the user to create in the database.
+ It MAY also contain C<extern_id>, in which
+ case it still MUST contain C<bz_username> or
+ C<username>.
+ It MAY contain C<password> and C<realname>.
+
+Returns: A hashref with one element, C<user>, which is a
+ L<Bugzilla::User> object. May also return a login error
+ as described in L<Bugzilla::Auth>.
+
+Note: This method is not abstract, it is actually implemented
+ and creates accounts in the Bugzilla database. Subclasses
+ should probably all call the C<Bugzilla::Auth::Verify>
+ version of this function at the end of their own
+ C<create_or_update_user>.
+
+=item C<change_password($user, $password)>
+
+Description: Modifies the user's password in the authentication backend.
+Params: $user - A L<Bugzilla::User> object representing the user
+ whose password we want to change.
+ $password - The user's new password.
+Returns: Nothing.
+
+=back
+
+=head1 INFO METHODS
+
+These are methods that describe the capabilities of this object.
+These are all no-parameter methods that return either C<true> or
+C<false>.
+
+=over 4
+
+=item C<user_can_create_account>
+
+Whether or not users can manually create accounts in this type of
+account source. Defaults to C<true>.
+
+=back
diff --git a/Bugzilla/Auth/Verify/DB.pm b/Bugzilla/Auth/Verify/DB.pm
new file mode 100644
index 000000000..2fcfd4017
--- /dev/null
+++ b/Bugzilla/Auth/Verify/DB.pm
@@ -0,0 +1,108 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Dan Mosedale <dmose@mozilla.org>
+# Joe Robins <jmrobins@tgix.com>
+# Dave Miller <justdave@syndicomm.com>
+# Christopher Aillon <christopher@aillon.com>
+# Gervase Markham <gerv@gerv.net>
+# Christian Reis <kiko@async.com.br>
+# Bradley Baetz <bbaetz@acm.org>
+# Erik Stambaugh <erik@dasbistro.com>
+
+package Bugzilla::Auth::Verify::DB;
+use strict;
+use base qw(Bugzilla::Auth::Verify);
+
+use Bugzilla::Constants;
+use Bugzilla::Token;
+use Bugzilla::Util;
+use Bugzilla::User;
+
+sub check_credentials {
+ my ($self, $login_data) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ my $username = $login_data->{username};
+ my $user = new Bugzilla::User({ name => $username });
+
+ return { failure => AUTH_NO_SUCH_USER } unless $user;
+
+ $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,
+ just_locked_out => 1 };
+ }
+
+ return { failure => AUTH_LOGINFAILED,
+ failure_count => scalar(@{ $user->account_ip_login_failures }),
+ };
+ }
+
+ # Force the user to type a longer password if it's too short.
+ if (length($password) < USER_PASSWORD_MIN_LENGTH) {
+ return { failure => AUTH_ERROR, user_error => 'password_current_too_short',
+ 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();
+
+ # 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;
+ if ($real_password_crypted !~ /{\Q$current_algorithm\E}$/) {
+ $user->set_password($password);
+ $user->update();
+ }
+
+ return $login_data;
+}
+
+sub change_password {
+ my ($self, $user, $password) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $cryptpassword = bz_crypt($password);
+ $dbh->do("UPDATE profiles SET cryptpassword = ? WHERE userid = ?",
+ undef, $cryptpassword, $user->id);
+}
+
+1;
diff --git a/Bugzilla/Auth/Verify/LDAP.pm b/Bugzilla/Auth/Verify/LDAP.pm
new file mode 100644
index 000000000..cdc802ca0
--- /dev/null
+++ b/Bugzilla/Auth/Verify/LDAP.pm
@@ -0,0 +1,199 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Dan Mosedale <dmose@mozilla.org>
+# Joe Robins <jmrobins@tgix.com>
+# Dave Miller <justdave@syndicomm.com>
+# Christopher Aillon <christopher@aillon.com>
+# Gervase Markham <gerv@gerv.net>
+# Christian Reis <kiko@async.com.br>
+# Bradley Baetz <bbaetz@acm.org>
+# Erik Stambaugh <erik@dasbistro.com>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::Auth::Verify::LDAP;
+use strict;
+use base qw(Bugzilla::Auth::Verify);
+use fields qw(
+ ldap
+);
+
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::User;
+use Bugzilla::Util;
+
+use Net::LDAP;
+
+use constant admin_can_create_account => 0;
+use constant user_can_create_account => 0;
+
+sub check_credentials {
+ my ($self, $params) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ # We need to bind anonymously to the LDAP server. This is
+ # because we need to get the Distinguished Name of the user trying
+ # to log in. Some servers (such as iPlanet) allow you to have unique
+ # uids spread out over a subtree of an area (such as "People"), so
+ # just appending the Base DN to the uid isn't sufficient to get the
+ # user's DN. For servers which don't work this way, there will still
+ # be no harm done.
+ $self->_bind_ldap_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();
+
+ $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;
+
+
+ my $mail_attr = Bugzilla->params->{"LDAPmailattribute"};
+ if ($mail_attr) {
+ if (!$user_entry->exists($mail_attr)) {
+ return { failure => AUTH_ERROR,
+ error => "ldap_cannot_retreive_attr",
+ details => {attr => $mail_attr} };
+ }
+
+ my @emails = $user_entry->get_value($mail_attr);
+
+ # Default to the first email address returned.
+ $params->{bz_username} = $emails[0];
+
+ if (@emails > 1) {
+ # Cycle through the adresses and check if they're Bugzilla logins.
+ # Use the first one that returns a valid id.
+ foreach my $email (@emails) {
+ if ( login_to_id($email) ) {
+ $params->{bz_username} = $email;
+ last;
+ }
+ }
+ }
+
+ } else {
+ $params->{bz_username} = $username;
+ }
+
+ $params->{realname} ||= $user_entry->get_value("displayName");
+ $params->{realname} ||= $user_entry->get_value("cn");
+
+ $params->{extern_id} = $username;
+
+ return $params;
+}
+
+sub _bz_search_params {
+ my ($username) = @_;
+ return (base => Bugzilla->params->{"LDAPBaseDN"},
+ scope => "sub",
+ filter => '(&(' . Bugzilla->params->{"LDAPuidattribute"}
+ . "=$username)"
+ . Bugzilla->params->{"LDAPfilter"} . ')');
+}
+
+sub _bind_ldap_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;
+}
+
+# We can't just do this in new(), because we're not allowed to throw any
+# error from anywhere under Bugzilla::Auth::new -- otherwise we
+# could create a situation where the admin couldn't get to editparams
+# to fix his mistake. (Because Bugzilla->login always calls
+# Bugzilla::Auth->new, and almost every page calls Bugzilla->login.)
+sub ldap {
+ my ($self) = @_;
+ return $self->{ldap} if $self->{ldap};
+
+ my @servers = split(/[\s,]+/, Bugzilla->params->{"LDAPserver"});
+ ThrowCodeError("ldap_server_not_defined") unless @servers;
+
+ foreach (@servers) {
+ $self->{ldap} = new Net::LDAP(trim($_));
+ last if $self->{ldap};
+ }
+ ThrowCodeError("ldap_connect_failed", { server => join(", ", @servers) })
+ unless $self->{ldap};
+
+ # try to start TLS if needed
+ if (Bugzilla->params->{"LDAPstarttls"}) {
+ my $mesg = $self->{ldap}->start_tls();
+ ThrowCodeError("ldap_start_tls_failed", { error => $mesg->error() })
+ if $mesg->code();
+ }
+
+ return $self->{ldap};
+}
+
+1;
diff --git a/Bugzilla/Auth/Verify/RADIUS.pm b/Bugzilla/Auth/Verify/RADIUS.pm
new file mode 100644
index 000000000..da36c3bd1
--- /dev/null
+++ b/Bugzilla/Auth/Verify/RADIUS.pm
@@ -0,0 +1,64 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Marc Schumann.
+# Portions created by Marc Schumann are Copyright (c) 2007 Marc Schumann.
+# All rights reserved.
+#
+# Contributor(s): Marc Schumann <wurblzap@gmail.com>
+
+package Bugzilla::Auth::Verify::RADIUS;
+use strict;
+use base qw(Bugzilla::Auth::Verify);
+
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Util;
+
+use Authen::Radius;
+
+use constant admin_can_create_account => 0;
+use constant user_can_create_account => 0;
+
+sub check_credentials {
+ my ($self, $params) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $address_suffix = Bugzilla->params->{'RADIUS_email_suffix'};
+ my $username = $params->{username};
+
+ # If we're using RADIUS_email_suffix, we may need to cut it off from
+ # the login name.
+ if ($address_suffix) {
+ $username =~ s/\Q$address_suffix\E$//i;
+ }
+
+ # Create RADIUS object.
+ my $radius =
+ new Authen::Radius(Host => Bugzilla->params->{'RADIUS_server'},
+ Secret => Bugzilla->params->{'RADIUS_secret'})
+ || return { failure => AUTH_ERROR, error => 'radius_preparation_error',
+ details => {errstr => Authen::Radius::strerror() } };
+
+ # Check the password.
+ $radius->check_pwd($username, $params->{password},
+ Bugzilla->params->{'RADIUS_NAS_IP'} || undef)
+ || return { failure => AUTH_LOGINFAILED };
+
+ # Build the user account's e-mail address.
+ $params->{bz_username} = $username . $address_suffix;
+
+ return $params;
+}
+
+1;
diff --git a/Bugzilla/Auth/Verify/Stack.pm b/Bugzilla/Auth/Verify/Stack.pm
new file mode 100644
index 000000000..2df3fcd25
--- /dev/null
+++ b/Bugzilla/Auth/Verify/Stack.pm
@@ -0,0 +1,89 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::Auth::Verify::Stack;
+use strict;
+use base qw(Bugzilla::Auth::Verify);
+use fields qw(
+ _stack
+ successful
+);
+use Hash::Util qw(lock_keys);
+use Bugzilla::Hook;
+
+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;
+}
+
+sub can_change_password {
+ my ($self) = @_;
+ # We return true if any method can change passwords.
+ foreach my $object (@{$self->{_stack}}) {
+ return 1 if $object->can_change_password;
+ }
+ return 0;
+}
+
+sub check_credentials {
+ my $self = shift;
+ my $result;
+ foreach my $object (@{$self->{_stack}}) {
+ $result = $object->check_credentials(@_);
+ $self->{successful} = $object;
+ last if !$result->{failure};
+ # So that if none of them succeed, it's undef.
+ $self->{successful} = undef;
+ }
+ # Returns the result at the bottom of the stack if they all fail.
+ return $result;
+}
+
+sub create_or_update_user {
+ my $self = shift;
+ my $result;
+ foreach my $object (@{$self->{_stack}}) {
+ $result = $object->create_or_update_user(@_);
+ last if !$result->{failure};
+ }
+ # Returns the result at the bottom of the stack if they all fail.
+ return $result;
+}
+
+sub user_can_create_account {
+ my ($self) = @_;
+ # We return true if any method allows the user to create an account.
+ foreach my $object (@{$self->{_stack}}) {
+ return 1 if $object->user_can_create_account;
+ }
+ return 0;
+}
+
+1;
diff --git a/Bugzilla/Bug.pm b/Bugzilla/Bug.pm
new file mode 100644
index 000000000..ceb773aec
--- /dev/null
+++ b/Bugzilla/Bug.pm
@@ -0,0 +1,4093 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Dawn Endico <endico@mozilla.org>
+# Terry Weissman <terry@mozilla.org>
+# Chris Yeh <cyeh@bluemartini.com>
+# Bradley Baetz <bbaetz@acm.org>
+# Dave Miller <justdave@bugzilla.org>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+# Frédéric Buclin <LpSolit@gmail.com>
+# Lance Larsh <lance.larsh@oracle.com>
+# Elliotte Martin <elliotte_martin@yahoo.com>
+
+package Bugzilla::Bug;
+
+use strict;
+
+use Bugzilla::Attachment;
+use Bugzilla::Constants;
+use Bugzilla::Field;
+use Bugzilla::Flag;
+use Bugzilla::FlagType;
+use Bugzilla::Hook;
+use Bugzilla::Keyword;
+use Bugzilla::Milestone;
+use Bugzilla::User;
+use Bugzilla::Util;
+use Bugzilla::Version;
+use Bugzilla::Error;
+use Bugzilla::Product;
+use Bugzilla::Component;
+use Bugzilla::Group;
+use Bugzilla::Status;
+use Bugzilla::Comment;
+
+use List::MoreUtils qw(firstidx uniq);
+use List::Util qw(min max first);
+use Storable qw(dclone);
+use URI;
+use URI::QueryParam;
+use Scalar::Util qw(blessed);
+
+use base qw(Bugzilla::Object Exporter);
+@Bugzilla::Bug::EXPORT = qw(
+ bug_alias_to_id
+ LogActivityEntry
+ editable_bug_fields
+);
+
+#####################################################################
+# Constants
+#####################################################################
+
+use constant DB_TABLE => 'bugs';
+use constant ID_FIELD => 'bug_id';
+use constant NAME_FIELD => 'alias';
+use constant LIST_ORDER => ID_FIELD;
+
+# 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(
+ alias
+ assigned_to
+ bug_file_loc
+ bug_id
+ bug_severity
+ bug_status
+ cclist_accessible
+ component_id
+ 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('creation_ts', '%Y.%m.%d %H:%i') . ' AS creation_ts',
+ $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,
+ bug_file_loc => \&_check_bug_file_loc,
+ bug_severity => \&_check_select_field,
+ bug_status => \&_check_bug_status,
+ cc => \&_check_cc,
+ comment => \&_check_comment,
+ comment_is_private => \&_check_comment_is_private,
+ component => \&_check_component,
+ deadline => \&_check_deadline,
+ dup_id => \&_check_dup_id,
+ estimated_time => \&_check_estimated_time,
+ 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_remaining_time,
+ 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_FREETEXT) {
+ $validator = \&_check_freetext_field;
+ }
+ elsif ($field->type == FIELD_TYPE_BUG_ID) {
+ $validator = \&_check_bugid_field;
+ }
+ else {
+ $validator = \&_check_default_field;
+ }
+ $validators->{$field->name} = $validator;
+ }
+
+ 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'],
+ bug_status => ['product', 'comment', 'target_milestone'],
+ cc => ['component'],
+ component => ['product'],
+ dup_id => ['bug_status', 'resolution'],
+ groups => ['product'],
+ keywords => ['product'],
+ resolution => ['bug_status'],
+ qa_contact => ['component'],
+ target_milestone => ['product'],
+ version => ['product'],
+ );
+
+ my @custom_deps = Bugzilla->get_fields(
+ { visibility_field_id => NOT_NULL });
+ foreach my $field (@custom_deps) {
+ $deps{$field->name} = [$field->visibility_field->name];
+ }
+ $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(
+ alias
+ 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->get_fields({ type => FIELD_TYPE_DATETIME });
+ return map { $_->name } @fields;
+}
+
+# This is used by add_comment to know what we validate before putting in
+# the DB.
+use constant UPDATE_COMMENT_COLUMNS => qw(
+ thetext
+ work_time
+ type
+ extra_data
+ isprivate
+);
+
+# Used in LogActivityEntry(). Gives the max length of lines in the
+# activity table.
+use constant MAX_LINE_LENGTH => 254;
+
+# This maps the names of internal Bugzilla bug fields to things that would
+# make sense to somebody who's not intimately familiar with the inner workings
+# of Bugzilla. (These are the field names that the WebService and email_in.pl
+# use.)
+use constant FIELD_MAP => {
+ blocks => 'blocked',
+ cc_accessible => 'cclist_accessible',
+ 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',
+
+ # These are special values for the WebService Bug.search method.
+ limit => 'LIMIT',
+ offset => 'OFFSET',
+};
+
+use constant REQUIRED_FIELD_MAP => {
+ product_id => 'product',
+ component_id => 'component',
+};
+
+# Target Milestone is here because it has a default that the validator
+# creates (product.defaultmilestone) that is different from the database
+# default.
+#
+# CC is here because it is a separate table, and has a validator-created
+# default of the component initialcc.
+#
+# QA Contact is allowed to be NULL in the database, so it wouldn't normally
+# be caught by _required_create_fields. However, it always has to be validated,
+# because it has a default of the component.defaultqacontact.
+#
+# 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(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 =~ /^#(\d+)$/) {
+ $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 !~ /^\d+$/)) {
+ # But only if aliases are enabled.
+ if (Bugzilla->params->{'usebugaliases'} && $param) {
+ $param = { name => $param };
+ }
+ else {
+ # Aliases are off, and 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;
+}
+
+sub check {
+ my $class = shift;
+ my ($id, $field) = @_;
+
+ 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 $self = $class->new(trim($id));
+ # 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 });
+ }
+ }
+
+ unless ($field && $field =~ /^(dependson|blocked|dup_id)$/) {
+ $self->check_is_visible;
+ }
+ return $self;
+}
+
+sub check_is_visible {
+ my $self = shift;
+ 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 ($user->id) {
+ ThrowUserError("bug_access_denied", { bug_id => $self->id });
+ } else {
+ ThrowUserError("bug_access_query", { bug_id => $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 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(@_);
+}
+
+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 || '');
+ # Exclude punctuation from the array.
+ @words = map { /(\w+)/; $1 } @words;
+ # 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, 1);
+ $relevance_sql ||= $where_sql;
+ }
+ else {
+ my (@where, @relevance);
+ my $count = 0;
+ foreach my $word (@words) {
+ $count++;
+ my ($term, $rel_term) = $dbh->sql_fulltext_search(
+ 'bugs_fulltext.short_desc', $word, $count);
+ push(@where, $term);
+ push(@relevance, $rel_term || $term);
+ }
+
+ $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;
+
+ 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
+ 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);
+}
+
+# Docs for create() (there's no POD in this file yet, but we very
+# much need this documented right now):
+#
+# The same as Bugzilla::Object->create. Parameters are only required
+# if they say so below.
+#
+# Params:
+#
+# C<product> - B<Required> The name of the product this bug is being
+# filed against.
+# C<component> - B<Required> The name of the component this bug is being
+# filed against.
+#
+# C<bug_severity> - B<Required> The severity for the bug, a string.
+# C<creation_ts> - B<Required> A SQL timestamp for when the bug was created.
+# C<short_desc> - B<Required> A summary for the bug.
+# C<op_sys> - B<Required> The OS the bug was found against.
+# C<priority> - B<Required> The initial priority for the bug.
+# C<rep_platform> - B<Required> The platform the bug was found against.
+# C<version> - B<Required> The version of the product the bug was found in.
+#
+# C<alias> - An alias for this bug. Will be ignored if C<usebugaliases>
+# is off.
+# C<target_milestone> - When this bug is expected to be fixed.
+# C<status_whiteboard> - A string.
+# C<bug_status> - The initial status of the bug, a string.
+# C<bug_file_loc> - The URL field.
+#
+# 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.
+# Will be ignored if C<useqacontact> is off.
+#
+# 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 $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 ($comment, $privacy) = ($params->{comment}, $params->{comment_is_private});
+ delete $params->{comment};
+ delete $params->{comment_is_private};
+
+ # 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);
+ }
+ }
+
+ # And insert the comment. We always insert a comment on bug creation,
+ # but sometimes it's blank.
+ my @columns = qw(bug_id who bug_when thetext);
+ my @values = ($bug->bug_id, $bug->{reporter_id}, $timestamp, $comment);
+ # We don't include the "isprivate" column unless it was specified.
+ # This allows it to fall back to its database default.
+ if (defined $privacy) {
+ push(@columns, 'isprivate');
+ push(@values, $privacy);
+ }
+ my $qmarks = "?," x @columns;
+ chop($qmarks);
+ $dbh->do('INSERT INTO longdescs (' . join(',', @columns) . ")
+ VALUES ($qmarks)", undef, @values);
+
+ Bugzilla::Hook::process('bug_end_of_create', { bug => $bug,
+ timestamp => $timestamp,
+ });
+
+ $dbh->bz_commit_transaction();
+
+ # 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');
+
+ return $bug;
+}
+
+sub run_create_validators {
+ my $class = shift;
+ my $params = $class->SUPER::run_create_validators(@_);
+
+ 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->{creation_ts} =
+ Bugzilla->dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
+ $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);
+
+ ($params->{dependson}, $params->{blocked}) =
+ $class->_check_dependencies($params->{dependson}, $params->{blocked},
+ $product);
+
+ # You can't set these fields on bug creation (or sometimes ever).
+ delete $params->{resolution};
+ delete $params->{lastdiffed};
+ delete $params->{bug_id};
+
+ Bugzilla::Hook::process('bug_end_of_create_validators',
+ { params => $params });
+
+ my @mandatory_fields = Bugzilla->get_fields({ is_mandatory => 1,
+ enter_bug => 1,
+ obsolete => 0 });
+ foreach my $field (@mandatory_fields) {
+ $class->_check_field_is_mandatory($params->{$field->name}, $field,
+ $params);
+ }
+
+ return $params;
+}
+
+sub update {
+ my $self = shift;
+
+ my $dbh = Bugzilla->dbh;
+ # XXX This is just a temporary hack until all updating happens
+ # inside this function.
+ my $delta_ts = shift || $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
+
+ $dbh->bz_start_transaction();
+
+ my ($changes, $old_bug) = $self->SUPER::update(@_);
+
+ # 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];
+ }
+
+ # 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, '',
+ Bugzilla->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,
+ Bugzilla->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)];
+ }
+
+ # Flags
+ my ($removed, $added) = Bugzilla::Flag->update_flags($self, $old_bug, $delta_ts);
+ if ($removed || $added) {
+ $changes->{'flagtypes.name'} = [$removed, $added];
+ }
+
+ # Comments
+ foreach my $comment (@{$self->{added_comments} || []}) {
+ my $columns = join(',', keys %$comment);
+ my @values = values %$comment;
+ my $qmarks = join(',', ('?') x @values);
+ $dbh->do("INSERT INTO longdescs (bug_id, who, bug_when, $columns)
+ VALUES (?,?,?,$qmarks)", undef,
+ $self->bug_id, Bugzilla->user->id, $delta_ts, @values);
+ if ($comment->{work_time}) {
+ LogActivityEntry($self->id, "work_time", "", $comment->{work_time},
+ Bugzilla->user->id, $delta_ts);
+ }
+ }
+
+ # Comment Privacy
+ foreach my $comment_id (keys %{$self->{comment_isprivate} || {}}) {
+ $dbh->do("UPDATE longdescs SET isprivate = ? WHERE comment_id = ?",
+ undef, $self->{comment_isprivate}->{$comment_id}, $comment_id);
+ my ($from, $to)
+ = $self->{comment_isprivate}->{$comment_id} ? (0, 1) : (1, 0);
+ LogActivityEntry($self->id, "longdescs.isprivate", $from, $to,
+ Bugzilla->user->id, $delta_ts, $comment_id);
+ }
+
+ # 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);
+
+ if (scalar @$removed_see) {
+ $dbh->do('DELETE FROM bug_see_also WHERE bug_id = ? AND '
+ . $dbh->sql_in('value', [('?') x @$removed_see]),
+ undef, $self->id, @$removed_see);
+ }
+ foreach my $url (@$added_see) {
+ $dbh->do('INSERT INTO bug_see_also (bug_id, value) VALUES (?,?)',
+ undef, $self->id, $url);
+ }
+ # If any changes were found, record it in the activity log
+ if (scalar @$removed_see || scalar @$added_see) {
+ $changes->{see_also} = [join(', ', @$removed_see),
+ join(', ', @$added_see)];
+ }
+
+ # 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, Bugzilla->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;
+ }
+
+ $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()
+ if $self->{added_comments} || $changes->{short_desc}
+ || $self->{comment_isprivate};
+
+ # 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 Bugzilla->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;
+ }
+ }
+ return \%ms_values;
+}
+
+# Should be called any time you update short_desc or change a comment.
+sub _sync_fulltext {
+ my ($self, $new_bug) = @_;
+ my $dbh = Bugzilla->dbh;
+ if ($new_bug) {
+ $dbh->do('INSERT INTO bugs_fulltext (bug_id, short_desc)
+ SELECT bug_id, short_desc FROM bugs WHERE bug_id = ?',
+ undef, $self->id);
+ }
+ else {
+ $dbh->do('UPDATE bugs_fulltext SET short_desc = ? WHERE bug_id = ?',
+ undef, $self->short_desc, $self->id);
+ }
+ my $comments = $dbh->selectall_arrayref(
+ 'SELECT thetext, isprivate FROM longdescs WHERE bug_id = ?',
+ undef, $self->id);
+ my $all = join("\n", map { $_->[0] } @$comments);
+ my @no_private = grep { !$_->[1] } @$comments;
+ my $nopriv_string = join("\n", map { $_->[0] } @no_private);
+ $dbh->do('UPDATE bugs_fulltext SET comments = ?, comments_noprivate = ?
+ WHERE bug_id = ?', undef, $all, $nopriv_string, $self->id);
+}
+
+
+# This is the correct way to delete bugs from the DB.
+# No bug should be deleted from anywhere else except from here.
+#
+sub remove_from_db {
+ my ($self) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ if ($self->{'error'}) {
+ ThrowCodeError("bug_error", { bug => $self });
+ }
+
+ my $bug_id = $self->{'bug_id'};
+
+ # tables having 'bugs.bug_id' as a foreign key:
+ # - attachments
+ # - bug_group_map
+ # - bugs
+ # - bugs_activity
+ # - bugs_fulltext
+ # - cc
+ # - dependencies
+ # - duplicates
+ # - flags
+ # - keywords
+ # - longdescs
+
+ # Also, the attach_data table uses attachments.attach_id as a foreign
+ # key, and so indirectly depends on a bug deletion too.
+
+ $dbh->bz_start_transaction();
+
+ $dbh->do("DELETE FROM bug_group_map WHERE bug_id = ?", undef, $bug_id);
+ $dbh->do("DELETE FROM bugs_activity WHERE bug_id = ?", undef, $bug_id);
+ $dbh->do("DELETE FROM cc WHERE bug_id = ?", undef, $bug_id);
+ $dbh->do("DELETE FROM dependencies WHERE blocked = ? OR dependson = ?",
+ undef, ($bug_id, $bug_id));
+ $dbh->do("DELETE FROM duplicates WHERE dupe = ? OR dupe_of = ?",
+ undef, ($bug_id, $bug_id));
+ $dbh->do("DELETE FROM flags WHERE bug_id = ?", undef, $bug_id);
+ $dbh->do("DELETE FROM keywords WHERE bug_id = ?", undef, $bug_id);
+
+ # The attach_data table doesn't depend on bugs.bug_id directly.
+ my $attach_ids =
+ $dbh->selectcol_arrayref("SELECT attach_id FROM attachments
+ WHERE bug_id = ?", undef, $bug_id);
+
+ if (scalar(@$attach_ids)) {
+ $dbh->do("DELETE FROM attach_data WHERE "
+ . $dbh->sql_in('id', $attach_ids));
+ }
+
+ # Several of the previous tables also depend on attach_id.
+ $dbh->do("DELETE FROM attachments WHERE bug_id = ?", undef, $bug_id);
+ $dbh->do("DELETE FROM bugs WHERE bug_id = ?", undef, $bug_id);
+ $dbh->do("DELETE FROM longdescs WHERE bug_id = ?", undef, $bug_id);
+
+ $dbh->bz_commit_transaction();
+
+ # The bugs_fulltext table doesn't support transactions.
+ $dbh->do("DELETE FROM bugs_fulltext WHERE bug_id = ?", undef, $bug_id);
+
+ # Now this bug no longer exists
+ $self->DESTROY;
+ return $self;
+}
+
+#####################################################################
+# Sending Email After Bug Update
+#####################################################################
+
+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.
+ my %notify_deps;
+ 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)) {
+ $notify_deps{$_} = 1 foreach (@{ $self->blocked });
+ }
+ }
+
+ # 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{''};
+
+ my %all_dep_changes = (%notify_deps, %changed_deps);
+ foreach my $id (sort { $a <=> $b } (keys %all_dep_changes)) {
+ _send_bugmail({ forced => { changer => $user }, type => "dep",
+ id => $id }, $vars);
+ }
+}
+
+sub _send_bugmail {
+ my ($params, $vars) = @_;
+
+ my $results =
+ Bugzilla::BugMail::Send($params->{'id'}, $params->{'forced'});
+
+ 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;
+ }
+}
+
+#####################################################################
+# Validators
+#####################################################################
+
+sub _check_alias {
+ my ($invocant, $alias) = @_;
+ $alias = trim($alias);
+ return undef if (!Bugzilla->params->{'usebugaliases'} || !$alias);
+
+ # Make sure the alias isn't too long.
+ if (length($alias) > 20) {
+ 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 });
+ }
+
+ return $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))
+ {
+ $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_file_loc {
+ my ($invocant, $url) = @_;
+ $url = '' if !defined($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_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::Status->can_change_to()};
+ if (!$product->allows_unconfirmed) {
+ @valid_statuses = grep {$_->name ne 'UNCONFIRMED'} @valid_statuses;
+ }
+ }
+
+ # 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];
+ }
+ }
+ }
+ 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)
+ {
+ ThrowUserError('comment_required', { old => $old_status,
+ new => $new_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 });
+ }
+
+ if (!blessed $invocant) {
+ $params->{everconfirmed} = $new_status->name eq 'UNCONFIRMED' ? 0 : 1;
+ }
+
+ 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;
+ }
+
+ # Enforce Default CC
+ $cc_ids{$_->id} = 1 foreach (@{$component->initial_cc});
+
+ return [keys %cc_ids];
+}
+
+sub _check_comment {
+ my ($invocant, $comment) = @_;
+
+ $comment = '' unless defined $comment;
+
+ # Remove any trailing whitespace. Leading whitespace could be
+ # a valid part of the comment.
+ $comment =~ s/\s*$//s;
+ $comment =~ s/\r\n?/\n/g; # Get rid of \r.
+
+ ThrowUserError('comment_too_long') if length($comment) > MAX_COMMENT_LENGTH;
+ return $comment;
+}
+
+sub _check_comment_is_private {
+ my ($invocant, $comment_privacy) = @_;
+ if ($comment_privacy && !Bugzilla->user->is_insider) {
+ ThrowUserError('user_not_insider');
+ }
+ return $comment_privacy ? 1 : 0;
+}
+
+sub _check_comment_type {
+ my ($invocant, $type) = @_;
+ detaint_natural($type)
+ || ThrowCodeError('bad_arg', { argument => 'type',
+ function => caller });
+ return $type;
+}
+
+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 $obj = Bugzilla::Component->check({ product => $product, name => $name });
+ return $obj;
+}
+
+sub _check_deadline {
+ 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;
+ }
+
+ # 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, $depends_on, $blocks, $product) = @_;
+
+ if (!ref $invocant) {
+ # Only editbugs users can set dependencies on bug entry.
+ return ([], []) unless Bugzilla->user->in_group('editbugs',
+ $product->id);
+ }
+
+ my %deps_in = (dependson => $depends_on || '', blocked => $blocks || '');
+
+ 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;
+ # We do this up here to make sure all aliases are converted to IDs.
+ @bug_ids = map { $invocant->check($_, $type)->id } @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});
+ }
+ }
+ }
+
+ $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);
+
+ return ($deps{'dependson'}, $deps{'blocked'});
+}
+
+sub _check_dup_id {
+ my ($self, $dupe_of) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ $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;
+
+ # 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 he can see the bug...
+ if ($self->reporter->can_see_bug($dupe_of)) {
+ # We only add him if he's 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 he user is already the reporter
+ # of the dupe_of bug, since we already checked if he 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 he's 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;
+ }
+ }
+
+ return $dupe_of;
+}
+
+sub _check_estimated_time {
+ return $_[0]->_check_time($_[1], 'estimated_time');
+}
+
+sub _check_groups {
+ my ($invocant, $group_names, undef, $params) = @_;
+ 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.
+ foreach my $name (@$group_names) {
+ # We don't want to expose the existence or non-existence of groups,
+ # so instead of doing check(), we just do "next" on an invalid
+ # group.
+ my $group = new Bugzilla::Group({ name => $name }) or next;
+ next if !$product->group_is_settable($group);
+ $add_groups{$group->id} = $group;
+ }
+ }
+
+ # Now enforce mandatory groups.
+ $add_groups{$_->id} = $_ foreach @{ $product->groups_mandatory };
+
+ my @add_groups = values %add_groups;
+ return \@add_groups;
+}
+
+sub _check_keywords {
+ my ($invocant, $keywords_in, undef, $params) = @_;
+
+ 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)];
+ }
+
+ # On creation, only editbugs users can set keywords.
+ if (!ref $invocant) {
+ my $product = $params->{product};
+ return [] if !Bugzilla->user->in_group('editbugs', $product->id);
+ }
+
+ 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;
+}
+
+sub _check_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};
+ my $id;
+ 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)
+ {
+ $id = $component->default_qa_contact->id;
+ }
+ }
+
+ # If a QA Contact was specified or if we're updating, check
+ # the QA Contact for validity.
+ if (!defined $id && $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;
+}
+
+sub _check_remaining_time {
+ return $_[0]->_check_time($_[1], 'remaining_time');
+}
+
+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 he must be logged in first!).
+ Bugzilla->login(LOGIN_REQUIRED);
+ $reporter = Bugzilla->user->id;
+ }
+ return $reporter;
+}
+
+sub _check_resolution {
+ my ($self, $resolution) = @_;
+ $resolution = trim($resolution);
+
+ # 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 => $self->status->name })
+ if !$resolution && !$self->status->is_open;
+
+ # Make sure this is a valid resolution.
+ $resolution = $self->_check_select_field($resolution, 'resolution');
+
+ # Don't allow open bugs to have resolutions.
+ ThrowUserError('resolution_not_allowed') if $self->status->is_open;
+
+ # Check noresolveonopenblockers.
+ if (Bugzilla->params->{"noresolveonopenblockers"}
+ && $resolution eq 'FIXED'
+ && (!$self->resolution || $resolution ne $self->resolution)
+ && scalar @{$self->dependson})
+ {
+ my $dep_bugs = Bugzilla::Bug->new_from_list($self->dependson);
+ my $count_open = grep { $_->isopened } @$dep_bugs;
+ if ($count_open) {
+ ThrowUserError("still_unresolved_bugs",
+ { bug_id => $self->id, dep_count => $count_open });
+ }
+ }
+
+ # Check if they're changing the resolution and need to comment.
+ if (Bugzilla->params->{'commentonchange_resolution'}
+ && $self->resolution && $resolution ne $self->resolution
+ && !$self->{added_comments})
+ {
+ ThrowUserError('comment_required');
+ }
+
+ 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;
+
+ 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 @related_users = @$ccs;
+ push(@related_users, $assignee) if $assignee;
+
+ 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);
+ }
+ }
+ 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 });
+ }
+}
+
+sub _check_target_milestone {
+ my ($invocant, $target, undef, $params) = @_;
+ my $product = blessed($invocant) ? $invocant->product_obj
+ : $params->{product};
+ $target = trim($target);
+ $target = $product->default_milestone if !defined $target;
+ my $object = Bugzilla::Milestone->check(
+ { product => $product, name => $target });
+ return $object->name;
+}
+
+sub _check_time {
+ my ($invocant, $time, $field) = @_;
+
+ # 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;
+ }
+
+ $time = trim($time) || 0;
+ ValidateTime($time, $field);
+ return $time;
+}
+
+sub _check_version {
+ my ($invocant, $version, undef, $params) = @_;
+ $version = trim($version);
+ my $product = blessed($invocant) ? $invocant->product_obj
+ : $params->{product};
+ my $object =
+ Bugzilla::Version->check({ product => $product, name => $version });
+ return $object->name;
+}
+
+sub _check_work_time {
+ my ($self, $input) = @_;
+ my $time = $self->_check_time($input, 'work_time');
+ # Because work_time isn't set by a set_ function, we need to
+ # do check_can_change_field here manually.
+ my $privs;
+ $self->check_can_change_field('work_time', 0, $time, \$privs)
+ || ThrowUserError('illegal_change',
+ { field => 'work_time', privs => $privs });
+ return $time;
+}
+
+# Custom Field Validators
+
+sub _check_field_is_mandatory {
+ my ($invocant, $value, $field, $params) = @_;
+
+ if (!blessed($field)) {
+ $field = Bugzilla::Field->new({ name => $field });
+ return if !$field;
+ }
+
+ return if !$field->is_mandatory;
+
+ return if !$field->is_visible_on_bug($params || $invocant);
+
+ 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 });
+ }
+}
+
+sub _check_datetime_field {
+ my ($invocant, $date_time) = @_;
+
+ # 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 && !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) = @_;
+
+ $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)];
+ }
+
+ 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;
+}
+
+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);
+ }
+
+ return $checked_id;
+}
+
+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);
+}
+
+#####################################################################
+# Class Accessors
+#####################################################################
+
+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;
+}
+
+#####################################################################
+# 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 = user_id_to_login($value);
+ $current = user_id_to_login($current);
+ }
+ ThrowUserError('illegal_change', { field => $field,
+ oldvalue => $current,
+ newvalue => $value,
+ privs => $privs });
+ }
+ $self->_check_field_is_mandatory($value, $field);
+}
+
+
+#################
+# "Set" Methods #
+#################
+
+# 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 ($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'} });
+ }
+
+ 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};
+ }
+ }
+ $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->_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});
+ }
+ }
+
+ $self->_add_remove($params, 'cc');
+
+ # 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$//;
+ my $add_method = "add_$name";
+ my $remove_method = "remove_$name";
+ $self->$add_method($_) foreach @add;
+ $self->$remove_method($_) foreach @remove;
+}
+
+sub set_alias { $_[0]->set('alias', $_[1]); }
+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'};
+}
+sub reset_assigned_to {
+ my $self = shift;
+ my $comp = $self->component_obj;
+ $self->set_assigned_to($comp->default_assignee);
+}
+sub set_cclist_accessible { $_[0]->set('cclist_accessible', $_[1]); }
+sub set_comment_is_private {
+ my ($self, $comment_id, $isprivate) = @_;
+
+ # We also allow people to pass in a hash of comment ids to update.
+ if (ref $comment_id) {
+ while (my ($id, $is) = each %$comment_id) {
+ $self->set_comment_is_private($id, $is);
+ }
+ return;
+ }
+
+ my ($comment) = grep($comment_id == $_->id, @{ $self->comments });
+ ThrowUserError('comment_invalid_isprivate', { id => $comment_id })
+ if !$comment;
+
+ $isprivate = $isprivate ? 1 : 0;
+ if ($isprivate != $comment->is_private) {
+ ThrowUserError('user_not_insider') if !Bugzilla->user->is_insider;
+ $self->{comment_isprivate} ||= {};
+ $self->{comment_isprivate}->{$comment_id} = $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) = @_;
+
+ 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) = @_;
+ ($dependson, $blocked) = $self->_check_dependencies($dependson, $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;
+}
+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 });
+ }
+}
+sub set_estimated_time { $_[0]->set('estimated_time', $_[1]); }
+sub _set_everconfirmed { $_[0]->set('everconfirmed', $_[1]); }
+sub set_flags {
+ my ($self, $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]); }
+# 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;
+ }
+
+ $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 } @{$product->components}];
+ $vars{milestones} = [map { $_->name } @{$product->milestones}];
+ $vars{versions} = [map { $_->name } @{$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};
+ # 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
+ (SELECT gcm.group_id
+ FROM group_control_map AS gcm
+ 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);
+ }
+
+ 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 {
+ $self->set_target_milestone($tm_name);
+ }
+ }
+
+ if ($product_changed) {
+ # Remove groups that can't be set in the new product, or that aren't
+ # active.
+ #
+ # 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 (!$group->is_active or !$product->group_is_valid($group)) {
+ $self->remove_group($group);
+ }
+ }
+
+ # 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'};
+}
+sub reset_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;
+
+ 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();
+ }
+ }
+
+ # 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;
+}
+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};
+ }
+ 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);
+
+ # 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]); }
+sub set_target_milestone { $_[0]->set('target_milestone', $_[1]); }
+sub set_url { $_[0]->set('bug_file_loc', $_[1]); }
+sub set_version { $_[0]->set('version', $_[1]); }
+
+########################
+# "Add/Remove" Methods #
+########################
+
+# These are in alphabetical order by field name.
+
+# 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);
+}
+
+# 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 $cc_users = $self->cc_users;
+ @$cc_users = grep { $_->id != $user->id } @$cc_users;
+}
+
+# $bug->add_comment("comment", {isprivate => 1, work_time => 10.5,
+# type => CMT_NORMAL, extra_data => $data});
+sub add_comment {
+ my ($self, $comment, $params) = @_;
+
+ $comment = $self->_check_comment($comment);
+
+ $params ||= {};
+ $params->{work_time} = $self->_check_work_time($params->{work_time});
+ if (exists $params->{type}) {
+ $params->{type} = $self->_check_comment_type($params->{type});
+ }
+ if (exists $params->{isprivate}) {
+ $params->{isprivate} =
+ $self->_check_comment_is_private($params->{isprivate});
+ }
+ # XXX We really should check extra_data, too.
+
+ if ($comment eq '' && !($params->{type} || abs($params->{work_time}))) {
+ 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));
+ }
+
+ # So we really want to comment. Make sure we are allowed to do so.
+ my $privs;
+ $self->check_can_change_field('longdesc', 0, 1, \$privs)
+ || ThrowUserError('illegal_change', { field => 'longdesc', privs => $privs });
+
+ $self->{added_comments} ||= [];
+ my $add_comment = dclone($params);
+ $add_comment->{thetext} = $comment;
+
+ # We only want to trick_taint fields that we know about--we don't
+ # want to accidentally let somebody set some field that's not OK
+ # to set!
+ foreach my $field (UPDATE_COMMENT_COLUMNS) {
+ trick_taint($add_comment->{$field}) if defined $add_comment->{$field};
+ }
+
+ push(@{$self->{added_comments}}, $add_comment);
+}
+
+# There was a lot of duplicate code when I wrote this as three separate
+# functions, so I just combined them all into one. This is also easier for
+# process_bug to use.
+sub modify_keywords {
+ my ($self, $keywords, $action) = @_;
+
+ $action ||= 'set';
+ if (!grep($action eq $_, qw(add remove set))) {
+ $action = 'set';
+ }
+
+ $keywords = $self->_check_keywords($keywords);
+
+ my (@result, $any_changes);
+ if ($action eq 'set') {
+ @result = @$keywords;
+ # Check if anything was added or removed.
+ my @old_ids = map { $_->id } @{$self->keyword_objects};
+ my @new_ids = map { $_->id } @result;
+ my ($removed, $added) = diff_arrays(\@old_ids, \@new_ids);
+ $any_changes = scalar @$removed || scalar @$added;
+ }
+ else {
+ # We're adding or deleting specific keywords.
+ my %keys = map {$_->id => $_} @{$self->keyword_objects};
+ if ($action eq 'add') {
+ $keys{$_->id} = $_ foreach @$keywords;
+ }
+ else {
+ delete $keys{$_->id} foreach @$keywords;
+ }
+ @result = values %keys;
+ $any_changes = scalar @$keywords;
+ }
+ # 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;
+}
+
+sub add_group {
+ my ($self, $group) = @_;
+ # Invalid ids are silently ignored. (We can't tell people whether
+ # or not a group exists.)
+ $group = Bugzilla::Group->check($group) if !blessed $group;
+
+ return if !$group->is_active or !$group->is_bug_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_invalid_restriction',
+ { product => $self->product, group => $group,
+ bug => $self });
+
+ # 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_change_denied',
+ { bug => $self, group => $group });
+ }
+ }
+
+ my $current_groups = $self->groups_in;
+ if (!grep($group->id == $_->id, @$current_groups)) {
+ push(@$current_groups, $group);
+ }
+}
+
+sub remove_group {
+ my ($self, $group) = @_;
+ $group = Bugzilla::Group->check($group) if !blessed $group;
+
+ # First, check if this is a valid group for this product.
+ # You can *always* remove a group that is not valid for this product
+ # or that is not active, so we don't do any other checks if either of
+ # those are the case. (Users might remove inactive groups, and set_product
+ # removes groups that aren't valid for this product.)
+ #
+ # This particularly happens when isbuggroup is no longer 1, and we're
+ # moving a bug to a new product.
+ if ($group->is_active and $self->product_obj->group_is_valid($group)) {
+ my $controls = $self->product_obj->group_controls->{$group->id};
+
+ # Nobody can ever remove a Mandatory group.
+ if ($controls->{membercontrol} == CONTROLMAPMANDATORY) {
+ ThrowUserError('group_invalid_removal',
+ { product => $self->product, group => $group,
+ bug => $self });
+ }
+
+ # 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_change_denied',
+ { bug => $self, group => $group });
+ }
+ }
+ }
+
+ my $current_groups = $self->groups_in;
+ @$current_groups = grep { $_->id != $group->id } @$current_groups;
+}
+
+sub add_see_also {
+ my ($self, $input) = @_;
+ $input = trim($input);
+
+ if (!$input) {
+ ThrowCodeError('param_required',
+ { function => 'add_see_also', param => '$input' });
+ }
+
+ # We assume that the URL is an HTTP URL if there is no (something)://
+ # in front.
+ my $uri = new URI($input);
+ 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://$input");
+ }
+ elsif ($uri->scheme ne 'http' && $uri->scheme ne 'https') {
+ ThrowUserError('bug_url_invalid', { url => $input, 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 => $input, reason => 'path_only' });
+ }
+
+ my $result;
+ # Launchpad URLs
+ if ($uri->authority =~ /launchpad.net$/) {
+ # 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"
+ if ($uri->path =~ m|bugs?/(\d+)$|) {
+ # This is the shortest standard URL form for Launchpad bugs,
+ # and so we reduce all URLs to this.
+ $result = "https://launchpad.net/bugs/$1";
+ }
+ else {
+ ThrowUserError('bug_url_invalid',
+ { url => $input, reason => 'id' });
+ }
+ }
+ # Google Code URLs
+ elsif ($uri->authority =~ /^code.google.com$/i) {
+ # Google Code URLs only have one form:
+ # http(s)://code.google.com/p/PROJECT_NAME/issues/detail?id=1234
+ my $project_name;
+ if ($uri->path =~ m|^/p/([^/]+)/issues/detail$|) {
+ $project_name = $1;
+ } else {
+ ThrowUserError('bug_url_invalid',
+ { url => $input });
+ }
+ my $bug_id = $uri->query_param('id');
+ detaint_natural($bug_id);
+ if (!$bug_id) {
+ ThrowUserError('bug_url_invalid',
+ { url => $input, reason => 'id' });
+ }
+ # While Google Code URLs can be either HTTP or HTTPS,
+ # always go with the HTTP scheme, as that's the default.
+ $result = "http://code.google.com/p/" . $project_name .
+ "/issues/detail?id=" . $bug_id;
+ }
+ # Debian BTS URLs
+ elsif ($uri->authority =~ /^bugs.debian.org$/i) {
+ # Debian BTS URLs can look like various things:
+ # http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1234
+ # http://bugs.debian.org/1234
+ my $bug_id;
+ if ($uri->path =~ m|^/(\d+)$|) {
+ $bug_id = $1;
+ }
+ elsif ($uri->path =~ /bugreport\.cgi$/) {
+ $bug_id = $uri->query_param('bug');
+ detaint_natural($bug_id);
+ }
+ if (!$bug_id) {
+ ThrowUserError('bug_url_invalid',
+ { url => $input, reason => 'id' });
+ }
+ # This is the shortest standard URL form for Debian BTS URLs,
+ # and so we reduce all URLs to this.
+ $result = "http://bugs.debian.org/" . $bug_id;
+ }
+ # Bugzilla URLs
+ else {
+ if ($uri->path !~ /show_bug\.cgi$/) {
+ ThrowUserError('bug_url_invalid',
+ { url => $input, reason => 'show_bug' });
+ }
+
+ 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) {
+ ThrowUserError('bug_url_invalid',
+ { url => $input, 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);
+ $result = $uri->canonical->as_string;
+ }
+
+ if (length($result) > MAX_BUG_URL_LENGTH) {
+ ThrowUserError('bug_url_too_long', { url => $result });
+ }
+
+ # 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($_) eq lc($result) } @{ $self->see_also }) {
+ my $privs;
+ my $can = $self->check_can_change_field('see_also', '', $result, \$privs);
+ if (!$can) {
+ ThrowUserError('illegal_change', { field => 'see_also',
+ newvalue => $result,
+ privs => $privs });
+ }
+
+ push(@{ $self->see_also }, $result);
+ }
+}
+
+sub remove_see_also {
+ my ($self, $url) = @_;
+ my $see_also = $self->see_also;
+ my @new_see_also = grep { lc($_) 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 });
+ }
+ $self->{see_also} = \@new_see_also;
+}
+
+#####################################################################
+# Instance Accessors
+#####################################################################
+
+# These subs are in alphabetical order, as much as possible.
+# If you add a new sub, please try to keep it in alphabetical order
+# with the other ones.
+
+# Note: If you add a new method, remember that you must check the error
+# state of the bug before returning any data. If $self->{error} is
+# defined, then return something empty. Otherwise you risk potential
+# security holes.
+
+sub dup_id {
+ my ($self) = @_;
+ return $self->{'dup_id'} if exists $self->{'dup_id'};
+
+ $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
+ FROM duplicates
+ 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 him 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'};
+
+ if ( $self->{'error'} || !Bugzilla->user->is_timetracker ) {
+ $self->{'actual_time'} = undef;
+ return $self->{'actual_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'};
+}
+
+sub any_flags_requesteeble {
+ 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;
+
+ return $self->{'any_flags_requesteeble'};
+}
+
+sub attachments {
+ my ($self) = @_;
+ return $self->{'attachments'} if exists $self->{'attachments'};
+ return [] if $self->{'error'};
+
+ $self->{'attachments'} =
+ Bugzilla::Attachment->get_attachments_by_bug($self->bug_id, {preload => 1});
+ 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($self->{'assigned_to'});
+ 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'};
+}
+
+# Even bugs in an error state always have a bug_id.
+sub bug_id { $_[0]->{'bug_id'}; }
+
+sub bug_group {
+ my ($self) = @_;
+ return join(', ', (map { $_->name } @{$self->groups_in}));
+}
+
+sub related_bugs {
+ 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};
+}
+
+sub cc {
+ 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
+ WHERE bug_id = ?
+ AND cc.who = profiles.userid
+ ORDER BY profiles.login_name},
+ undef, $self->bug_id);
+
+ $self->{'cc'} = undef if !scalar(@{$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'};
+}
+
+sub component {
+ my ($self) = @_;
+ return $self->{component} if exists $self->{component};
+ return '' if $self->{error};
+ ($self->{component}) = Bugzilla->dbh->selectrow_array(
+ 'SELECT name FROM components WHERE id = ?',
+ undef, $self->{component_id});
+ 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($self->{component_id});
+ return $self->{component_obj};
+}
+
+sub classification_id {
+ my ($self) = @_;
+ return $self->{classification_id} if exists $self->{classification_id};
+ return 0 if $self->{error};
+ ($self->{classification_id}) = Bugzilla->dbh->selectrow_array(
+ 'SELECT classification_id FROM products WHERE id = ?',
+ undef, $self->{product_id});
+ return $self->{classification_id};
+}
+
+sub classification {
+ my ($self) = @_;
+ return $self->{classification} if exists $self->{classification};
+ return '' if $self->{error};
+ ($self->{classification}) = Bugzilla->dbh->selectrow_array(
+ 'SELECT name FROM classifications WHERE id = ?',
+ undef, $self->classification_id);
+ return $self->{classification};
+}
+
+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'};
+}
+
+sub flag_types {
+ 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 };
+
+ $self->{'flag_types'} = Bugzilla::Flag->_flag_types($vars);
+ return $self->{'flag_types'};
+}
+
+sub flags {
+ 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};
+}
+
+sub isopened {
+ my $self = shift;
+ return is_open_state($self->{bug_status}) ? 1 : 0;
+}
+
+sub keywords {
+ 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 $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;
+ foreach my $comment (@{ $self->{'comments'} }) {
+ $comment->{count} = $count++;
+ $comment->{bug} = $self;
+ }
+ Bugzilla::Comment->preload($self->{'comments'});
+ }
+ my @comments = @{ $self->{'comments'} };
+
+ my $order = $params->{order}
+ || Bugzilla->user->settings->{'comment_sort_order'}->{'value'};
+ 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;
+}
+
+# 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;
+ return 100 * ($actual / $total);
+}
+
+sub product {
+ my ($self) = @_;
+ return $self->{product} if exists $self->{product};
+ return '' if $self->{error};
+ ($self->{product}) = Bugzilla->dbh->selectrow_array(
+ 'SELECT name FROM products WHERE id = ?',
+ undef, $self->{product_id});
+ 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($self->{product_id});
+ 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'};
+
+ if (Bugzilla->params->{'useqacontact'} && $self->{'qa_contact'}) {
+ $self->{'qa_contact_obj'} = new Bugzilla::User($self->{'qa_contact'});
+ } else {
+ # XXX - This is somewhat inconsistent with the assignee/reporter
+ # methods, which will return an empty User if they get a 0.
+ # However, we're keeping it this way now, for backwards-compatibility.
+ $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($self->{'reporter_id'});
+ return $self->{'reporter'};
+}
+
+sub see_also {
+ my ($self) = @_;
+ return [] if $self->{'error'};
+ $self->{'see_also'} ||= Bugzilla->dbh->selectcol_arrayref(
+ 'SELECT value FROM bug_see_also WHERE bug_id = ?', undef, $self->id);
+ return $self->{'see_also'};
+}
+
+sub status {
+ my $self = shift;
+ return undef if $self->{'error'};
+
+ $self->{'status'} ||= new Bugzilla::Status({name => $self->{'bug_status'}});
+ return $self->{'status'};
+}
+
+sub statuses_available {
+ my $self = shift;
+ return [] if $self->{'error'};
+ return $self->{'statuses_available'}
+ if defined $self->{'statuses_available'};
+
+ my @statuses = @{ $self->status->can_change_to };
+
+ # UNCONFIRMED is only a valid status if it is enabled in this product.
+ if (!$self->product_obj->allows_unconfirmed) {
+ @statuses = grep { $_->name ne 'UNCONFIRMED' } @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);
+ }
+
+ # 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);
+ }
+
+ $self->{'statuses_available'} = \@available;
+ return $self->{'statuses_available'};
+}
+
+sub show_attachment_flags {
+ 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 });
+
+ $self->{'show_attachment_flags'} =
+ ($num_attachment_flag_types || $num_attachment_flags);
+
+ return $self->{'show_attachment_flags'};
+}
+
+sub groups {
+ 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);
+
+ push (@groups, { "bit" => $groupid,
+ "name" => $name,
+ "ison" => $ison,
+ "ingroup" => $ingroup,
+ "mandatory" => $ismandatory,
+ "description" => $description });
+ }
+ }
+
+ $self->{'groups'} = \@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'};
+}
+
+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 $unknown_privileges = $user->in_group('editbugs', $prod_id);
+ my $canedit = $unknown_privileges
+ || $user->id == $self->{'assigned_to'}
+ || (Bugzilla->params->{'useqacontact'}
+ && $self->{'qa_contact'}
+ && $user->id == $self->{'qa_contact'});
+ my $canconfirm = $unknown_privileges
+ || $user->in_group('canconfirm', $prod_id);
+ my $isreporter = $user->id
+ && $user->id == $self->{reporter_id};
+
+ $self->{'user'} = {canconfirm => $canconfirm,
+ canedit => $canedit,
+ isreporter => $isreporter};
+ 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;
+
+ $self->{'choices'} = \%choices;
+ return $self->{'choices'};
+}
+
+# Convenience Function. If you need speed, use this. If you need
+# other Bug fields in addition to this, just create a new Bug with
+# the alias.
+# 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) = @_;
+ return undef unless Bugzilla->params->{"usebugaliases"};
+ my $dbh = Bugzilla->dbh;
+ trick_taint($alias);
+ return $dbh->selectrow_array(
+ "SELECT bug_id FROM bugs WHERE alias = ?", undef, $alias);
+}
+
+#####################################################################
+# Subroutines
+#####################################################################
+
+# Represents which fields from the bugs table are handled by process_bug.cgi.
+sub editable_bug_fields {
+ my @fields = Bugzilla->dbh->bz_table_columns('bugs');
+ # Obsolete custom fields are not editable.
+ my @obsolete_fields = Bugzilla->get_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;
+ # Custom multi-select fields are not stored in the bugs table.
+ splice(@fields, $location, 1) if ($location > -1);
+ }
+ # Sorted because the old @::log_columns variable, which this replaces,
+ # was sorted.
+ return sort(@fields);
+}
+
+# XXX - When Bug::update() will be implemented, we should make this routine
+# a private method.
+# Join with bug_status and bugs tables to show bugs with open statuses first,
+# and then the others
+sub EmitDependList {
+ my ($myfield, $targetfield, $bug_id) = (@_);
+ my $dbh = Bugzilla->dbh;
+ my $list_ref = $dbh->selectcol_arrayref(
+ "SELECT $targetfield
+ FROM dependencies
+ INNER JOIN bugs ON dependencies.$targetfield = bugs.bug_id
+ INNER JOIN bug_status ON bugs.bug_status = bug_status.value
+ WHERE $myfield = ?
+ ORDER BY is_open DESC, $targetfield",
+ undef, $bug_id);
+ return $list_ref;
+}
+
+sub ValidateTime {
+ my ($time, $field) = @_;
+
+ # 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"});
+ }
+
+ # Only the "work_time" field is allowed to contain a negative value.
+ if ( ($time < 0) && ($field ne "work_time") ) {
+ 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"});
+ }
+}
+
+# Get the activity of a bug, starting from $starttime (if given).
+# This routine assumes Bugzilla::Bug->check has been previously called.
+sub GetBugActivity {
+ my ($bug_id, $attach_id, $starttime) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ # Arguments passed to the SQL query.
+ my @args = ($bug_id);
+
+ # Only consider changes since $starttime, if given.
+ my $datepart = "";
+ if (defined $starttime) {
+ trick_taint($starttime);
+ push (@args, $starttime);
+ $datepart = "AND bugs_activity.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 (!Bugzilla->user->is_insider)
+ {
+ $suppjoins = "LEFT JOIN attachments
+ ON attachments.attach_id = bugs_activity.attach_id";
+ $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') .
+ ", bugs_activity.removed, bugs_activity.added, profiles.login_name,
+ bugs_activity.comment_id
+ FROM bugs_activity
+ $suppjoins
+ LEFT JOIN fielddefs
+ ON bugs_activity.fieldid = fielddefs.id
+ INNER JOIN profiles
+ ON profiles.userid = bugs_activity.who
+ WHERE bugs_activity.bug_id = ?
+ $datepart
+ $attachpart
+ $suppwhere
+ ORDER BY bugs_activity.bug_when";
+
+ my $list = $dbh->selectall_arrayref($query, undef, @args);
+
+ my @operations;
+ my $operation = {};
+ my $changes = [];
+ my $incomplete_data = 0;
+
+ foreach my $entry (@$list) {
+ my ($fieldname, $attachid, $when, $removed, $added, $who, $comment_id) = @$entry;
+ my %change;
+ my $activity_visible = 1;
+
+ # check if the user should see this field's activity
+ if ($fieldname eq 'remaining_time'
+ || $fieldname eq 'estimated_time'
+ || $fieldname eq 'work_time'
+ || $fieldname eq 'deadline')
+ {
+ $activity_visible = Bugzilla->user->is_timetracker;
+ }
+ elsif ($fieldname eq 'longdescs.isprivate'
+ && !Bugzilla->user->is_insider
+ && $added)
+ {
+ $activity_visible = 0;
+ }
+ else {
+ $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 = [];
+ }
+
+ $operation->{'who'} = $who;
+ $operation->{'when'} = $when;
+
+ $change{'fieldname'} = $fieldname;
+ $change{'attachid'} = $attachid;
+ $change{'removed'} = $removed;
+ $change{'added'} = $added;
+
+ if ($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 ($i, $col, $removed, $added, $whoid, $timestamp, $comment_id) = @_;
+ my $dbh = Bugzilla->dbh;
+ # 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);
+ $removed =~ s/^[,\s]+//; # remove any comma or space
+ } 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);
+ $added =~ s/^[,\s]+//; # remove any comma or space
+ } else {
+ $added = ""; # no more entries
+ }
+ trick_taint($addstr);
+ trick_taint($removestr);
+ my $fieldid = get_field_id($col);
+ $dbh->do("INSERT INTO bugs_activity
+ (bug_id, who, bug_when, fieldid, removed, added, comment_id)
+ VALUES (?, ?, ?, ?, ?, ?, ?)",
+ undef, ($i, $whoid, $timestamp, $fieldid, $removestr, $addstr, $comment_id));
+ }
+}
+
+# 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) {
+ my $field_name;
+ if ($except->{$field}) {
+ $field_name = $field;
+ }
+ else {
+ $field_name = FIELD_MAP->{$field} || $field;
+ }
+ $field_values{$field_name} = $params->{$field};
+ }
+ return \%field_values;
+}
+
+################################################################################
+# check_can_change_field() defines what users are allowed to change. You
+# can add code here for site-specific policy changes, according to the
+# instructions given in the Bugzilla Guide and below. Note that you may also
+# have to update the Bugzilla::Bug::user() function to give people access to the
+# options that they are permitted to change.
+#
+# check_can_change_field() returns true if the user is allowed to change this
+# field, and false if they are not.
+#
+# The parameters to this method are as follows:
+# $field - name of the field in the bugs table the user is trying to change
+# $oldvalue - what they are changing it from
+# $newvalue - what they are changing it to
+# $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;
+
+ $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)
+ {
+ 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 anyone to change comments.
+ if ($field =~ /^longdesc/) {
+ return 1;
+ }
+
+ # If the user isn't allowed to change a field, we must tell him 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.
+ if ( grep($_ eq $field, TIMETRACKING_FIELDS) ) {
+ 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;
+ }
+
+ # 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;
+ }
+ }
+
+ # 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 him;
+ # 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 he 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;
+ }
+
+ # 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;
+}
+
+# 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');
+ }
+ }
+ return 0;
+}
+
+#
+# Field Validation
+#
+
+# 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;
+ 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("SELECT $target
+ FROM dependencies
+ WHERE $me = ?", 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;
+}
+
+
+#####################################################################
+# Autoloaded Accessors
+#####################################################################
+
+# Determines whether an attribute access trapped by the AUTOLOAD function
+# is for a valid bug attribute. Bug attributes are properties and methods
+# predefined by this module as well as bug fields for which an accessor
+# can be defined by AUTOLOAD at runtime when the accessor is first accessed.
+#
+# XXX Strangely, some predefined attributes are on the list, but others aren't,
+# and the original code didn't specify why that is. Presumably the only
+# attributes that need to be on this list are those that aren't predefined;
+# we should verify that and update the list accordingly.
+#
+sub _validate_attribute {
+ my ($attribute) = @_;
+
+ my @valid_attributes = (
+ # Miscellaneous properties and methods.
+ qw(error groups product_id component_id
+ comments milestoneurl attachments isopened
+ flag_types num_attachment_flag_types
+ show_attachment_flags any_flags_requesteeble
+ lastdiffed),
+
+ # Bug fields.
+ Bugzilla::Bug->fields
+ );
+
+ return grep($attribute eq $_, @valid_attributes) ? 1 : 0;
+}
+
+sub AUTOLOAD {
+ use vars qw($AUTOLOAD);
+ my $attr = $AUTOLOAD;
+
+ $attr =~ s/.*:://;
+ return unless $attr=~ /[^A-Z]/;
+ if (!_validate_attribute($attr)) {
+ require Carp;
+ Carp::confess("invalid bug attribute $attr");
+ }
+
+ no strict 'refs';
+ *$AUTOLOAD = sub {
+ my $self = shift;
+
+ return $self->{$attr} if defined $self->{$attr};
+
+ $self->{_multi_selects} ||= [Bugzilla->get_fields(
+ {custom => 1, type => FIELD_TYPE_MULTI_SELECT })];
+ if ( grep($_->name eq $attr, @{$self->{_multi_selects}}) ) {
+ # There is a bug in Perl 5.10.0, which is fixed in 5.10.1,
+ # which taints $attr at this point. trick_taint() can go
+ # away once we require 5.10.1 or newer.
+ trick_taint($attr);
+
+ $self->{$attr} ||= Bugzilla->dbh->selectcol_arrayref(
+ "SELECT value FROM bug_$attr WHERE bug_id = ? ORDER BY value",
+ undef, $self->id);
+ return $self->{$attr};
+ }
+
+ return '';
+ };
+
+ goto &$AUTOLOAD;
+}
+
+1;
diff --git a/Bugzilla/BugMail.pm b/Bugzilla/BugMail.pm
new file mode 100644
index 000000000..d12836273
--- /dev/null
+++ b/Bugzilla/BugMail.pm
@@ -0,0 +1,587 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>,
+# Bryce Nesbitt <bryce-mozilla@nextbus.com>
+# Dan Mosedale <dmose@mozilla.org>
+# Alan Raetz <al_raetz@yahoo.com>
+# Jacob Steenhagen <jake@actex.net>
+# Matthew Tuck <matty@chariot.net.au>
+# Bradley Baetz <bbaetz@student.usyd.edu.au>
+# J. Paul Reed <preed@sigkill.com>
+# Gervase Markham <gerv@gerv.net>
+# Byron Jones <bugzilla@glob.com.au>
+# Reed Loden <reed@reedloden.com>
+
+use strict;
+
+package Bugzilla::BugMail;
+
+use Bugzilla::Error;
+use Bugzilla::User;
+use Bugzilla::Constants;
+use Bugzilla::Util;
+use Bugzilla::Bug;
+use Bugzilla::Classification;
+use Bugzilla::Product;
+use Bugzilla::Component;
+use Bugzilla::Status;
+use Bugzilla::Mailer;
+use Bugzilla::Hook;
+
+use Date::Parse;
+use Date::Format;
+
+use constant FORMAT_TRIPLE => "%19s|%-28s|%-28s";
+use constant FORMAT_3_SIZE => [19,28,28];
+use constant FORMAT_DOUBLE => "%19s %-55s";
+use constant FORMAT_2_SIZE => [19,55];
+
+use constant BIT_DIRECT => 1;
+use constant BIT_WATCHING => 2;
+
+# We use this instead of format because format doesn't deal well with
+# multi-byte languages.
+sub multiline_sprintf {
+ my ($format, $args, $sizes) = @_;
+ my @parts;
+ my @my_sizes = @$sizes; # Copy this so we don't modify the input array.
+ foreach my $string (@$args) {
+ my $size = shift @my_sizes;
+ my @pieces = split("\n", wrap_hard($string, $size));
+ push(@parts, \@pieces);
+ }
+
+ my $formatted;
+ while (1) {
+ # Get the first item of each part.
+ my @line = map { shift @$_ } @parts;
+ # If they're all undef, we're done.
+ last if !grep { defined $_ } @line;
+ # Make any single undef item into ''
+ @line = map { defined $_ ? $_ : '' } @line;
+ # And append a formatted line
+ $formatted .= sprintf($format, @line);
+ # Remove trailing spaces, or they become lots of =20's in
+ # quoted-printable emails.
+ $formatted =~ s/\s+$//;
+ $formatted .= "\n";
+ }
+ return $formatted;
+}
+
+sub three_columns {
+ return multiline_sprintf(FORMAT_TRIPLE, \@_, FORMAT_3_SIZE);
+}
+
+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;
+}
+
+# This is a bit of a hack, basically keeping the old system()
+# cmd line interface. Should clean this up at some point.
+#
+# args: bug_id, and an optional hash ref which may have keys for:
+# changer, owner, qa, reporter, cc
+# Optional hash contains values of people which will be forced to those
+# roles when the email is sent.
+# All the names are email addresses, not userids
+# values are scalars, except for cc, which is a list
+sub Send {
+ my ($id, $forced) = (@_);
+
+ my $dbh = Bugzilla->dbh;
+ my $bug = new Bugzilla::Bug($id);
+
+ # Only used for headers in bugmail for new bugs
+ my @fields = Bugzilla->get_fields({obsolete => 0, mailhead => 1});
+
+ 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 @args = ($bug->id);
+
+ # If lastdiffed is NULL, then we don't limit the search on time.
+ my $when_restriction = '';
+ if ($start) {
+ $when_restriction = ' AND bug_when > ? AND bug_when <= ?';
+ push @args, ($start, $end);
+ }
+
+ my $diffs = $dbh->selectall_arrayref(
+ "SELECT profiles.login_name, profiles.realname, fielddefs.description,
+ bugs_activity.bug_when, bugs_activity.removed,
+ bugs_activity.added, bugs_activity.attach_id, fielddefs.name,
+ bugs_activity.comment_id
+ FROM bugs_activity
+ INNER JOIN fielddefs
+ ON fielddefs.id = bugs_activity.fieldid
+ INNER JOIN profiles
+ ON profiles.userid = bugs_activity.who
+ WHERE bugs_activity.bug_id = ?
+ $when_restriction
+ ORDER BY bugs_activity.bug_when", undef, @args);
+
+ my @new_depbugs;
+ my $difftext = "";
+ my $diffheader = "";
+ my @diffparts;
+ my $lastwho = "";
+ my $fullwho;
+ my @changedfields;
+ foreach my $ref (@$diffs) {
+ my ($who, $whoname, $what, $when, $old, $new, $attachid, $fieldname, $comment_id) = (@$ref);
+ my $diffpart = {};
+ if ($who ne $lastwho) {
+ $lastwho = $who;
+ $fullwho = $whoname ? "$whoname <$who>" : $who;
+ $diffheader = "\n$fullwho changed:\n\n";
+ $diffheader .= three_columns("What ", "Removed", "Added");
+ $diffheader .= ('-' x 76) . "\n";
+ }
+ $what =~ s/^(Attachment )?/Attachment #$attachid / if $attachid;
+ if( $fieldname eq 'estimated_time' ||
+ $fieldname eq 'remaining_time' ) {
+ $old = format_time_decimal($old);
+ $new = format_time_decimal($new);
+ }
+ if ($fieldname eq 'dependson') {
+ push(@new_depbugs, grep {$_ =~ /^\d+$/} split(/[\s,]+/, $new));
+ }
+ if ($attachid) {
+ ($diffpart->{'isprivate'}) = $dbh->selectrow_array(
+ 'SELECT isprivate FROM attachments WHERE attach_id = ?',
+ undef, ($attachid));
+ }
+ if ($fieldname eq 'longdescs.isprivate') {
+ my $comment = Bugzilla::Comment->new($comment_id);
+ my $comment_num = $comment->count;
+ $what =~ s/^(Comment )?/Comment #$comment_num /;
+ $diffpart->{'isprivate'} = $new;
+ }
+ $difftext = three_columns($what, $old, $new);
+ $diffpart->{'header'} = $diffheader;
+ $diffpart->{'fieldname'} = $fieldname;
+ $diffpart->{'text'} = $difftext;
+ push(@diffparts, $diffpart);
+ push(@changedfields, $what);
+ }
+
+ my @depbugs;
+ my $deptext = "";
+ # Do not include data about dependent bugs when they have just been added.
+ # Completely skip checking for dependent bugs on bug creation as all
+ # dependencies bugs will just have been added.
+ if ($start) {
+ my $dep_restriction = "";
+ if (scalar @new_depbugs) {
+ $dep_restriction = "AND bugs_activity.bug_id NOT IN (" .
+ join(", ", @new_depbugs) . ")";
+ }
+
+ my $dependency_diffs = $dbh->selectall_arrayref(
+ "SELECT bugs_activity.bug_id, bugs.short_desc, fielddefs.name,
+ fielddefs.description, bugs_activity.removed,
+ bugs_activity.added
+ FROM bugs_activity
+ INNER JOIN bugs
+ ON bugs.bug_id = bugs_activity.bug_id
+ INNER JOIN dependencies
+ ON bugs_activity.bug_id = dependencies.dependson
+ INNER JOIN fielddefs
+ ON fielddefs.id = bugs_activity.fieldid
+ WHERE dependencies.blocked = ?
+ AND (fielddefs.name = 'bug_status'
+ OR fielddefs.name = 'resolution')
+ $when_restriction
+ $dep_restriction
+ ORDER BY bugs_activity.bug_when, bugs.bug_id", undef, @args);
+
+ my $thisdiff = "";
+ my $lastbug = "";
+ my $interestingchange = 0;
+ foreach my $dependency_diff (@$dependency_diffs) {
+ my ($depbug, $summary, $fieldname, $what, $old, $new) = @$dependency_diff;
+
+ if ($depbug ne $lastbug) {
+ if ($interestingchange) {
+ $deptext .= $thisdiff;
+ }
+ $lastbug = $depbug;
+ $thisdiff =
+ "\nBug $id depends on bug $depbug, which changed state.\n\n" .
+ "Bug $depbug Summary: $summary\n" .
+ correct_urlbase() . "show_bug.cgi?id=$depbug\n\n";
+ $thisdiff .= three_columns("What ", "Old Value", "New Value");
+ $thisdiff .= ('-' x 76) . "\n";
+ $interestingchange = 0;
+ }
+ $thisdiff .= three_columns($what, $old, $new);
+ if ($fieldname eq 'bug_status'
+ && is_open_state($old) ne is_open_state($new))
+ {
+ $interestingchange = 1;
+ }
+ push(@depbugs, $depbug);
+ }
+
+ if ($interestingchange) {
+ $deptext .= $thisdiff;
+ }
+ $deptext = trim($deptext);
+
+ if ($deptext) {
+ my $diffpart = {};
+ $diffpart->{'text'} = "\n" . trim($deptext);
+ push(@diffparts, $diffpart);
+ }
+ }
+
+ my $comments = $bug->comments({ after => $start, to => $end });
+ # Skip empty comments.
+ @$comments = grep { $_->type || $_->body =~ /\S/ } @$comments;
+
+ ###########################################################################
+ # Start of email filtering code
+ ###########################################################################
+
+ # A user_id => roles hash to keep track of people.
+ my %recipients;
+ my %watching;
+
+ # Now we work out all the people involved with this bug, and note all of
+ # the relationships in a hash. The keys are userids, the values are an
+ # array of role constants.
+
+ # 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 $_;
+ }
+ }
+
+ # 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 $ref (@$diffs) {
+ my ($who, $whoname, $what, $when, $old, $new) = (@$ref);
+ if ($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 ($what eq "CC") {
+ foreach my $cc_user (split(/[\s,]+/, $old)) {
+ my $uid = login_to_id($cc_user);
+ $recipients{$uid}->{+REL_CC} = BIT_DIRECT if $uid;
+ }
+ }
+ elsif ($what eq "QAContact") {
+ my $uid = login_to_id($old);
+ $recipients{$uid}->{+REL_QA} = BIT_DIRECT if $uid;
+ }
+ elsif ($what eq "AssignedTo") {
+ my $uid = login_to_id($old);
+ $recipients{$uid}->{+REL_ASSIGNEE} = BIT_DIRECT if $uid;
+ }
+ }
+ }
+
+ Bugzilla::Hook::process('bugmail_recipients',
+ { bug => $bug, recipients => \%recipients,
+ diffs => $diffs });
+
+ # Find all those user-watching anyone on the current list, who is not
+ # on it already themselves.
+ my $involved = join(",", keys %recipients);
+
+ my $userwatchers =
+ $dbh->selectall_arrayref("SELECT watcher, watched FROM watch
+ WHERE watched IN ($involved)");
+
+ # Mark these people as having the role of the person they are watching
+ foreach my $watch (@$userwatchers) {
+ while (my ($role, $bits) = each %{$recipients{$watch->[1]}}) {
+ $recipients{$watch->[0]}->{$role} |= BIT_WATCHING
+ if $bits & BIT_DIRECT;
+ }
+ push(@{$watching{$watch->[0]}}, $watch->[1]);
+ }
+
+ # Global watcher
+ my @watchers = split(/[,\s]+/, Bugzilla->params->{'globalwatchers'});
+ foreach (@watchers) {
+ my $watcher_id = login_to_id($_);
+ next unless $watcher_id;
+ $recipients{$watcher_id}->{+REL_GLOBAL_WATCHER} = BIT_DIRECT;
+ }
+
+ # We now have a complete set of all the users, and their relationships to
+ # the bug in question. However, we are not necessarily going to mail them
+ # all - there are preferences, permissions checks and all sorts to do yet.
+ my @sent;
+ my @excluded;
+
+ foreach my $user_id (keys %recipients) {
+ my %rels_which_want;
+ my $sent_mail = 0;
+ my $user = new Bugzilla::User($user_id);
+ # Deleted users must be excluded.
+ next unless $user;
+
+ 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($id,
+ $relationship,
+ $diffs,
+ $comments,
+ $deptext,
+ $changer,
+ !$start))
+ {
+ $rels_which_want{$relationship} =
+ $recipients{$user_id}->{$relationship};
+ }
+ }
+ }
+
+ if (scalar(%rels_which_want)) {
+ # So the user exists, can see the bug, and wants mail in at least
+ # one role. But do we want to send it to them?
+
+ # We shouldn't send mail if this is a dependency mail (i.e. there
+ # is something in @depbugs), and any of the depending bugs are not
+ # visible to the user. This is to avoid leaking the summaries of
+ # confidential bugs.
+ my $dep_ok = 1;
+ foreach my $dep_id (@depbugs) {
+ if (!$user->can_see_bug($dep_id)) {
+ $dep_ok = 0;
+ last;
+ }
+ }
+
+ # Make sure the user isn't in the nomail list, and the insider and
+ # dep checks passed.
+ if ($user->email_enabled && $dep_ok) {
+ # OK, OK, if we must. Email the user.
+ $sent_mail = sendMail(
+ { to => $user,
+ fields => \@fields,
+ bug => $bug,
+ comments => $comments,
+ is_new => !$start,
+ changer => $changer,
+ watchers => exists $watching{$user_id} ?
+ $watching{$user_id} : undef,
+ diff_parts => \@diffparts,
+ rels_which_want => \%rels_which_want,
+ changed_fields => \@changedfields,
+ });
+ }
+ }
+
+ if ($sent_mail) {
+ push(@sent, $user->login);
+ }
+ else {
+ push(@excluded, $user->login);
+ }
+ }
+
+ $dbh->do('UPDATE bugs SET lastdiffed = ? WHERE bug_id = ?',
+ undef, ($end, $id));
+ $bug->{lastdiffed} = $end;
+
+ return {'sent' => \@sent, 'excluded' => \@excluded};
+}
+
+sub sendMail {
+ my $params = shift;
+
+ my $user = $params->{to};
+ my @fields = @{ $params->{fields} };
+ my $bug = $params->{bug};
+ my @send_comments = @{ $params->{comments} };
+ my $isnew = $params->{is_new};
+ my $changer = $params->{changer};
+ my $watchingRef = $params->{watchers};
+ my @diffparts = @{ $params->{diff_parts} };
+ my $relRef = $params->{rels_which_want};
+ my @changed_fields = @{ $params->{changed_fields} };
+
+ # Build difftext (the actions) by verifying the user should see them
+ my $difftext = "";
+ my $diffheader = "";
+ my $add_diff;
+
+ foreach my $diff (@diffparts) {
+ $add_diff = 0;
+
+ if (exists($diff->{'fieldname'}) &&
+ ($diff->{'fieldname'} eq 'estimated_time' ||
+ $diff->{'fieldname'} eq 'remaining_time' ||
+ $diff->{'fieldname'} eq 'work_time' ||
+ $diff->{'fieldname'} eq 'deadline'))
+ {
+ $add_diff = 1 if $user->is_timetracker;
+ } elsif ($diff->{'isprivate'}
+ && !$user->is_insider)
+ {
+ $add_diff = 0;
+ } else {
+ $add_diff = 1;
+ }
+
+ if ($add_diff) {
+ if (exists($diff->{'header'}) &&
+ ($diffheader ne $diff->{'header'})) {
+ $diffheader = $diff->{'header'};
+ $difftext .= $diffheader;
+ }
+ $difftext .= $diff->{'text'};
+ }
+ }
+
+ if (!$user->is_insider) {
+ @send_comments = grep { !$_->is_private } @send_comments;
+ }
+
+ if ($difftext eq "" && !scalar(@send_comments) && !$isnew) {
+ # Whoops, no differences!
+ return 0;
+ }
+
+ my $diffs = $difftext;
+ # Remove extra newlines.
+ $diffs =~ s/^\n+//s; $diffs =~ s/\n+$//s;
+ if ($isnew) {
+ my $head = "";
+ foreach my $field (@fields) {
+ my $name = $field->name;
+ my $value = $bug->$name;
+
+ if (ref $value eq 'ARRAY') {
+ $value = join(', ', @$value);
+ }
+ elsif (ref $value && $value->isa('Bugzilla::User')) {
+ $value = $value->login;
+ }
+ elsif (ref $value && $value->isa('Bugzilla::Object')) {
+ $value = $value->name;
+ }
+ elsif ($name eq 'estimated_time') {
+ $value = ($value == 0) ? 0 : format_time_decimal($value);
+ }
+ 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;
+ # Only send estimated_time if it is enabled and the user is in the group.
+ if (($name ne 'estimated_time' && $name ne 'deadline') || $user->is_timetracker) {
+ my $desc = $field->description;
+ $head .= multiline_sprintf(FORMAT_DOUBLE, ["$desc:", $value],
+ FORMAT_2_SIZE);
+ }
+ }
+ $diffs = $head . ($difftext ? "\n\n" : "") . $diffs;
+ }
+
+ my (@reasons, @reasons_watch);
+ while (my ($relationship, $bits) = each %{$relRef}) {
+ push(@reasons, $relationship) if ($bits & BIT_DIRECT);
+ push(@reasons_watch, $relationship) if ($bits & BIT_WATCHING);
+ }
+
+ my %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 { user_id_to_login($_) } @$watchingRef;
+
+ my $vars = {
+ isnew => $isnew,
+ to_user => $user,
+ bug => $bug,
+ changedfields => \@changed_fields,
+ reasons => \@reasons,
+ reasons_watch => \@reasons_watch,
+ reasonsheader => join(" ", @headerrel),
+ reasonswatchheader => join(" ", @watchingrel),
+ changer => $changer,
+ diffs => $diffs,
+ new_comments => \@send_comments,
+ threadingmarker => build_thread_marker($bug->id, $user->id, $isnew),
+ };
+
+ my $msg;
+ my $template = Bugzilla->template_inner($user->settings->{'lang'}->{'value'});
+ $template->process("email/newchangedmail.txt.tmpl", $vars, \$msg)
+ || ThrowTemplateError($template->error());
+
+ MessageToMTA($msg);
+
+ return 1;
+}
+
+1;
diff --git a/Bugzilla/CGI.pm b/Bugzilla/CGI.pm
new file mode 100644
index 000000000..106944d74
--- /dev/null
+++ b/Bugzilla/CGI.pm
@@ -0,0 +1,624 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Bradley Baetz <bbaetz@student.usyd.edu.au>
+# Byron Jones <bugzilla@glob.com.au>
+# Marc Schumann <wurblzap@gmail.com>
+
+package Bugzilla::CGI;
+use strict;
+
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Util;
+use Bugzilla::Search::Recent;
+
+use File::Basename;
+
+BEGIN {
+ if (ON_WINDOWS) {
+ # Help CGI find the correct temp directory as the default list
+ # isn't Windows friendly (Bug 248988)
+ $ENV{'TMPDIR'} = $ENV{'TEMP'} || $ENV{'TMP'} || "$ENV{'WINDIR'}\\TEMP";
+ }
+}
+
+use CGI qw(-no_xhtml -oldstyle_urls :private_tempfiles
+ :unique_headers SERVER_PUSH);
+use base qw(CGI);
+
+# We need to disable output buffering - see bug 179174
+$| = 1;
+
+# Ignore SIGTERM and SIGPIPE - this prevents DB corruption. If the user closes
+# their browser window while a script is running, the web server sends these
+# signals, and we don't want to die half way through a write.
+$::SIG{TERM} = 'IGNORE';
+$::SIG{PIPE} = 'IGNORE';
+
+# CGI.pm uses AUTOLOAD, but explicitly defines a DESTROY sub.
+# We need to do so, too, otherwise perl dies when the object is destroyed
+# and we don't have a DESTROY method (because CGI.pm's AUTOLOAD will |die|
+# on getting an unknown sub to try to call)
+sub DESTROY {
+ my $self = shift;
+ $self->SUPER::DESTROY(@_);
+};
+
+sub new {
+ my ($invocant, @args) = @_;
+ my $class = ref($invocant) || $invocant;
+
+ my $self = $class->SUPER::new(@args);
+
+ # Make sure our outgoing cookie list is empty on each invocation
+ $self->{Bugzilla_cookie_list} = [];
+
+ # Send appropriate charset
+ $self->charset(Bugzilla->params->{'utf8'} ? 'UTF-8' : '');
+
+ # Redirect to urlbase/sslbase if we are not viewing an attachment.
+ my $script = basename($0);
+ 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) = @_;
+
+ $self->convert_old_params();
+ # 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;
+
+ # 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}$/;
+
+ my $esc_key = url_quote($key);
+
+ foreach my $value ($self->param($key)) {
+ if (defined($value)) {
+ my $esc_value = url_quote($value);
+
+ push(@parameters, "$esc_key=$esc_value");
+ }
+ }
+ }
+
+ return join("&", @parameters);
+}
+
+sub convert_old_params {
+ my $self = shift;
+
+ # bugidtype is now bug_id_type.
+ if ($self->param('bugidtype')) {
+ my $value = $self->param('bugidtype') eq 'exclude' ? 'nowords' : 'anyexact';
+ $self->param('bug_id_type', $value);
+ $self->delete('bugidtype');
+ }
+}
+
+sub clean_search_url {
+ my $self = shift;
+ # Delete any empty URL parameter.
+ my @cgi_params = $self->param;
+
+ foreach my $param (@cgi_params) {
+ if (defined $self->param($param) && $self->param($param) eq '') {
+ $self->delete($param);
+ $self->delete("${param}_type");
+ }
+
+ # Boolean Chart stuff is empty if it's "noop"
+ if ($param =~ /\d-\d-\d/ && defined $self->param($param)
+ && $self->param($param) eq 'noop')
+ {
+ $self->delete($param);
+ }
+ }
+
+ # Delete leftovers from the login form
+ $self->delete('Bugzilla_remember', 'GoAheadAndLogIn');
+
+ 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');
+
+ # 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');
+ }
+}
+
+# Overwrite to ensure nph doesn't get set, and unset HEADERS_ONCE
+sub multipart_init {
+ my $self = shift;
+
+ # Keys are case-insensitive, map to lowercase
+ my %args = @_;
+ my %param;
+ foreach my $key (keys %args) {
+ $param{lc $key} = $args{$key};
+ }
+
+ # Set the MIME boundary and content-type
+ my $boundary = $param{'-boundary'}
+ || '------- =_' . generate_random_password(16);
+ delete $param{'-boundary'};
+ $self->{'separator'} = "\r\n--$boundary\r\n";
+ $self->{'final_separator'} = "\r\n--$boundary--\r\n";
+ $param{'-type'} = SERVER_PUSH($boundary);
+
+ # Note: CGI.pm::multipart_init up to v3.04 explicitly set nph to 0
+ # CGI.pm::multipart_init v3.05 explicitly sets nph to 1
+ # CGI.pm's header() sets nph according to a param or $CGI::NPH, which
+ # is the desired behaviour.
+
+ return $self->header(
+ %param,
+ ) . "WARNING: YOUR BROWSER DOESN'T SUPPORT THIS SERVER-PUSH TECHNOLOGY." . $self->multipart_end;
+}
+
+# Have to add the cookies in.
+sub multipart_start {
+ my $self = shift;
+
+ my %args = @_;
+
+ # CGI.pm::multipart_start doesn't honour its own charset information, so
+ # we do it ourselves here
+ if (defined $self->charset() && defined $args{-type}) {
+ # Remove any existing charset specifier
+ $args{-type} =~ s/;.*$//;
+ # and add the specified one
+ $args{-type} .= '; charset=' . $self->charset();
+ }
+
+ my $headers = $self->SUPER::multipart_start(%args);
+ # Eliminate the one extra CRLF at the end.
+ $headers =~ s/$CGI::CRLF$//;
+ # Add the cookies. We have to do it this way instead of
+ # passing them to multpart_start, because CGI.pm's multipart_start
+ # doesn't understand a '-cookie' argument pointing to an arrayref.
+ foreach my $cookie (@{$self->{Bugzilla_cookie_list}}) {
+ $headers .= "Set-Cookie: ${cookie}${CGI::CRLF}";
+ }
+ $headers .= $CGI::CRLF;
+ return $headers;
+}
+
+# Override header so we can add the cookies in
+sub header {
+ my $self = shift;
+
+ # If there's only one parameter, then it's a Content-Type.
+ if (scalar(@_) == 1) {
+ # Since we're adding parameters below, we have to name it.
+ unshift(@_, '-type' => shift(@_));
+ }
+
+ # Add the cookies in if we have any
+ if (scalar(@{$self->{Bugzilla_cookie_list}})) {
+ unshift(@_, '-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';
+ }
+ unshift(@_, '-strict_transport_security' => $sts_opts);
+ }
+
+ # Add X-Frame-Options header to prevent framing and subsequent
+ # possible clickjacking problems.
+ unless ($self->url_is_attachment_base) {
+ unshift(@_, '-x_frame_options' => 'SAMEORIGIN');
+ }
+
+ return $self->SUPER::header(@_) || "";
+}
+
+sub param {
+ my $self = shift;
+
+ # 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')
+ {
+ # Some servers fail to set the QUERY_STRING parameter, which
+ # causes undef issues
+ $ENV{'QUERY_STRING'} = '' unless exists $ENV{'QUERY_STRING'};
+ @result = $self->SUPER::url_param(@_);
+ }
+
+ # 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(@_);
+}
+
+sub _fix_utf8 {
+ my $input = shift;
+ # The is_utf8 is here in case CGI gets smart about utf8 someday.
+ utf8::decode($input) if defined $input && !utf8::is_utf8($input);
+ return $input;
+}
+
+sub should_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));
+}
+
+# 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');
+}
+
+# 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 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');
+ my $last_search;
+ 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;
+ }
+
+ $self->clean_search_url();
+
+ if ($user->id) {
+ # 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);
+ }
+
+ # 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 $uri_length = length($self->self_url());
+ if ($self->request_method() ne 'POST' or $uri_length < CGI_URI_LIMIT) {
+ print $self->redirect(-url => $self->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;
+}
+
+# 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;
+}
+
+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->self_url =~ $regex) ? 1 : 0;
+}
+
+##########################
+# Vars TIEHASH Interface #
+##########################
+
+# 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(@_);
+}
+
+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;
+}
+
+# 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;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::CGI - CGI handling for Bugzilla
+
+=head1 SYNOPSIS
+
+ use Bugzilla::CGI;
+
+ my $cgi = new Bugzilla::CGI();
+
+=head1 DESCRIPTION
+
+This package inherits from the standard CGI module, to provide additional
+Bugzilla-specific functionality. In general, see L<the CGI.pm docs|CGI> for
+documention.
+
+=head1 CHANGES FROM L<CGI.PM|CGI>
+
+Bugzilla::CGI has some differences from L<CGI.pm|CGI>.
+
+=over 4
+
+=item C<cgi_error> is automatically checked
+
+After creating the CGI object, C<Bugzilla::CGI> automatically checks
+I<cgi_error>, and throws a CodeError if a problem is detected.
+
+=back
+
+=head1 ADDITIONAL FUNCTIONS
+
+I<Bugzilla::CGI> also includes additional functions.
+
+=over 4
+
+=item C<canonicalise_query(@exclude)>
+
+This returns a sorted string of the parameters, suitable for use in a url.
+Values in C<@exclude> are not included in the result.
+
+=item C<send_cookie>
+
+This routine is identical to the cookie generation part of CGI.pm's C<cookie>
+routine, except that it knows about Bugzilla's cookie_path and cookie_domain
+parameters and takes them into account if necessary.
+This should be used by all Bugzilla code (instead of C<cookie> or the C<-cookie>
+argument to C<header>), so that under mod_perl the headers can be sent
+correctly, using C<print> or the mod_perl APIs as appropriate.
+
+To remove (expire) a cookie, use C<remove_cookie>.
+
+=item C<remove_cookie>
+
+This is a wrapper around send_cookie, setting an expiry date in the past,
+effectively removing the cookie.
+
+As its only argument, it takes the name of the cookie to expire.
+
+=item C<redirect_to_https>
+
+This routine redirects the client to the https version of the page that
+they're looking at, using the C<sslbase> parameter for the redirection.
+
+Generally you should use L<Bugzilla::Util/do_ssl_redirect_if_required>
+instead of calling this directly.
+
+=item C<redirect_to_urlbase>
+
+Redirects from the current URL to one prefixed by the urlbase parameter.
+
+=back
+
+=head1 SEE ALSO
+
+L<CGI|CGI>, L<CGI::Cookie|CGI::Cookie>
diff --git a/Bugzilla/Chart.pm b/Bugzilla/Chart.pm
new file mode 100644
index 000000000..760db135d
--- /dev/null
+++ b/Bugzilla/Chart.pm
@@ -0,0 +1,445 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Gervase Markham <gerv@gerv.net>
+# Albert Ting <altlst@sonic.net>
+# A. Karl Kornel <karl@kornel.name>
+
+use strict;
+
+# This module represents a chart.
+#
+# Note that it is perfectly legal for the 'lines' member variable of this
+# class (which is an array of Bugzilla::Series objects) to have empty members
+# in it. If this is true, the 'labels' array will also have empty members at
+# the same points.
+package Bugzilla::Chart;
+
+use Bugzilla::Error;
+use Bugzilla::Util;
+use Bugzilla::Series;
+
+use Date::Format;
+use Date::Parse;
+use List::Util qw(max);
+
+sub new {
+ my $invocant = shift;
+ my $class = ref($invocant) || $invocant;
+
+ # Create a ref to an empty hash and bless it
+ my $self = {};
+ bless($self, $class);
+
+ if ($#_ == 0) {
+ # Construct from a CGI object.
+ $self->init($_[0]);
+ }
+ else {
+ die("CGI object not passed in - invalid number of args \($#_\)($_)");
+ }
+
+ return $self;
+}
+
+sub init {
+ my $self = shift;
+ my $cgi = shift;
+
+ # The data structure is a list of lists (lines) of Series objects.
+ # There is a separate list for the labels.
+ #
+ # The URL encoding is:
+ # line0=67&line0=73&line1=81&line2=67...
+ # &label0=B+/+R+/+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}});
+ }
+ }
+
+ # datefrom can't be after dateto
+ if ($self->{'datefrom'} && $self->{'dateto'} &&
+ $self->{'datefrom'} > $self->{'dateto'})
+ {
+ ThrowUserError("misarranged_dates",
+ {'datefrom' => $cgi->param('datefrom'),
+ 'dateto' => $cgi->param('dateto')});
+ }
+}
+
+# Alter Chart so that the selected series are added to it.
+sub add {
+ my $self = shift;
+ my @series_ids = @_;
+
+ # Get the current size of the series; required for adding Grand Total later
+ my $current_size = scalar($self->getSeriesIDs());
+
+ # Count the number of added series
+ my $added = 0;
+ # Create new Series and push them on to the list of lines.
+ # Note that new lines have no label; the display template is responsible
+ # for inventing something sensible.
+ foreach my $series_id (@series_ids) {
+ my $series = new Bugzilla::Series($series_id);
+ if ($series) {
+ push(@{$self->{'lines'}}, [$series]);
+ push(@{$self->{'labels'}}, "");
+ $added++;
+ }
+ }
+
+ # If we are going from < 2 to >= 2 series, add the Grand Total line.
+ if (!$self->{'gt'}) {
+ if ($current_size < 2 &&
+ $current_size + $added >= 2)
+ {
+ $self->{'gt'} = 1;
+ }
+ }
+}
+
+# Alter Chart so that the selections are removed from it.
+sub remove {
+ my $self = shift;
+ my @line_ids = @_;
+
+ foreach my $line_id (@line_ids) {
+ if ($line_id == 65536) {
+ # Magic value - delete Grand Total.
+ $self->{'gt'} = 0;
+ }
+ else {
+ delete($self->{'lines'}->[$line_id]);
+ delete($self->{'labels'}->[$line_id]);
+ }
+ }
+}
+
+# Alter Chart so that the selections are summed.
+sub sum {
+ my $self = shift;
+ my @line_ids = @_;
+
+ # We can't add the Grand Total to things.
+ @line_ids = grep(!/^65536$/, @line_ids);
+
+ # We can't add less than two things.
+ return if scalar(@line_ids) < 2;
+
+ my @series;
+ my $label = "";
+ my $biggestlength = 0;
+
+ # We rescue the Series objects of all the series involved in the sum.
+ foreach my $line_id (@line_ids) {
+ my @line = @{$self->{'lines'}->[$line_id]};
+
+ foreach my $series (@line) {
+ push(@series, $series);
+ }
+
+ # We keep the label that labels the line with the most series.
+ if (scalar(@line) > $biggestlength) {
+ $biggestlength = scalar(@line);
+ $label = $self->{'labels'}->[$line_id];
+ }
+ }
+
+ $self->remove(@line_ids);
+
+ push(@{$self->{'lines'}}, \@series);
+ push(@{$self->{'labels'}}, $label);
+}
+
+sub data {
+ my $self = shift;
+ $self->{'_data'} ||= $self->readData();
+ return $self->{'_data'};
+}
+
+# Convert the Chart's data into a plottable form in $self->{'_data'}.
+sub readData {
+ my $self = shift;
+ my @data;
+ my @maxvals;
+
+ # Note: you get a bad image if getSeriesIDs returns nothing
+ # We need to handle errors better.
+ my $series_ids = join(",", $self->getSeriesIDs());
+
+ return [] unless $series_ids;
+
+ # Work out the date boundaries for our data.
+ my $dbh = Bugzilla->dbh;
+
+ # The date used is the one given if it's in a sensible range; otherwise,
+ # it's the earliest or latest date in the database as appropriate.
+ my $datefrom = $dbh->selectrow_array("SELECT MIN(series_date) " .
+ "FROM series_data " .
+ "WHERE series_id IN ($series_ids)");
+ $datefrom = str2time($datefrom);
+
+ if ($self->{'datefrom'} && $self->{'datefrom'} > $datefrom) {
+ $datefrom = $self->{'datefrom'};
+ }
+
+ my $dateto = $dbh->selectrow_array("SELECT MAX(series_date) " .
+ "FROM series_data " .
+ "WHERE series_id IN ($series_ids)");
+ $dateto = str2time($dateto);
+
+ if ($self->{'dateto'} && $self->{'dateto'} < $dateto) {
+ $dateto = $self->{'dateto'};
+ }
+
+ # Convert UNIX times back to a date format usable for SQL queries.
+ my $sql_from = time2str('%Y-%m-%d', $datefrom);
+ my $sql_to = time2str('%Y-%m-%d', $dateto);
+
+ # Prepare the query which retrieves the data for each series
+ my $query = "SELECT " . $dbh->sql_to_days('series_date') . " - " .
+ $dbh->sql_to_days('?') . ", series_value " .
+ "FROM series_data " .
+ "WHERE series_id = ? " .
+ "AND series_date >= ?";
+ if ($dateto) {
+ $query .= " AND series_date <= ?";
+ }
+
+ my $sth = $dbh->prepare($query);
+
+ my $gt_index = $self->{'gt'} ? scalar(@{$self->{'lines'}}) : undef;
+ my $line_index = 0;
+
+ $maxvals[$gt_index] = 0 if $gt_index;
+
+ my @datediff_total;
+
+ foreach my $line (@{$self->{'lines'}}) {
+ # Even if we end up with no data, we need an empty arrayref to prevent
+ # errors in the PNG-generating code
+ $data[$line_index] = [];
+ $maxvals[$line_index] = 0;
+
+ foreach my $series (@$line) {
+
+ # Get the data for this series and add it on
+ if ($dateto) {
+ $sth->execute($sql_from, $series->{'series_id'}, $sql_from, $sql_to);
+ }
+ else {
+ $sth->execute($sql_from, $series->{'series_id'}, $sql_from);
+ }
+ my $points = $sth->fetchall_arrayref();
+
+ foreach my $point (@$points) {
+ my ($datediff, $value) = @$point;
+ $data[$line_index][$datediff] ||= 0;
+ $data[$line_index][$datediff] += $value;
+ if ($data[$line_index][$datediff] > $maxvals[$line_index]) {
+ $maxvals[$line_index] = $data[$line_index][$datediff];
+ }
+
+ $datediff_total[$datediff] += $value;
+
+ # Add to the grand total, if we are doing that
+ if ($gt_index) {
+ $data[$gt_index][$datediff] ||= 0;
+ $data[$gt_index][$datediff] += $value;
+ if ($data[$gt_index][$datediff] > $maxvals[$gt_index]) {
+ $maxvals[$gt_index] = $data[$gt_index][$datediff];
+ }
+ }
+ }
+ }
+
+ # We are done with the series making up this line, go to the next one
+ $line_index++;
+ }
+
+ # calculate maximum y value
+ if ($self->{'cumulate'}) {
+ # Make sure we do not try to take the max of an array with undef values
+ my @processed_datediff;
+ while (@datediff_total) {
+ my $datediff = shift @datediff_total;
+ push @processed_datediff, $datediff if defined($datediff);
+ }
+ $self->{'y_max_value'} = max(@processed_datediff);
+ }
+ else {
+ $self->{'y_max_value'} = max(@maxvals);
+ }
+ $self->{'y_max_value'} |= 1; # For log()
+
+ # Align the max y value:
+ # For one- or two-digit numbers, increase y_max_value until divisible by 8
+ # For larger numbers, see the comments below to figure out what's going on
+ if ($self->{'y_max_value'} < 100) {
+ do {
+ ++$self->{'y_max_value'};
+ } while ($self->{'y_max_value'} % 8 != 0);
+ }
+ else {
+ # First, get the # of digits in the y_max_value
+ my $num_digits = 1+int(log($self->{'y_max_value'})/log(10));
+
+ # We want to zero out all but the top 2 digits
+ my $mask_length = $num_digits - 2;
+ $self->{'y_max_value'} /= 10**$mask_length;
+ $self->{'y_max_value'} = int($self->{'y_max_value'});
+ $self->{'y_max_value'} *= 10**$mask_length;
+
+ # Add 10^$mask_length to the max value
+ # Continue to increase until it's divisible by 8 * 10^($mask_length-1)
+ # (Throwing in the -1 keeps at least the smallest digit at zero)
+ do {
+ $self->{'y_max_value'} += 10**$mask_length;
+ } while ($self->{'y_max_value'} % (8*(10**($mask_length-1))) != 0);
+ }
+
+
+ # Add the x-axis labels into the data structure
+ my $date_progression = generateDateProgression($datefrom, $dateto);
+ unshift(@data, $date_progression);
+
+ if ($self->{'gt'}) {
+ # Add Grand Total to label list
+ push(@{$self->{'labels'}}, $self->{'labelgt'});
+
+ $data[$gt_index] ||= [];
+ }
+
+ return \@data;
+}
+
+# Flatten the data structure into a list of series_ids
+sub getSeriesIDs {
+ my $self = shift;
+ my @series_ids;
+
+ foreach my $line (@{$self->{'lines'}}) {
+ foreach my $series (@$line) {
+ push(@series_ids, $series->{'series_id'});
+ }
+ }
+
+ return @series_ids;
+}
+
+# Class method to get the data necessary to populate the "select series"
+# widgets on various pages.
+sub getVisibleSeries {
+ my %cats;
+
+ 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;
+ }
+
+ return \@progression;
+}
+
+sub dump {
+ my $self = shift;
+
+ # Make sure we've read in our data
+ my $data = $self->data;
+
+ require Data::Dumper;
+ print "<pre>Bugzilla::Chart object:\n";
+ print Data::Dumper::Dumper($self);
+ print "</pre>";
+}
+
+1;
diff --git a/Bugzilla/Classification.pm b/Bugzilla/Classification.pm
new file mode 100644
index 000000000..88ec4eb3b
--- /dev/null
+++ b/Bugzilla/Classification.pm
@@ -0,0 +1,221 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s): Tiago R. Mello <timello@async.com.br>
+# Frédéric Buclin <LpSolit@gmail.com>
+
+use strict;
+
+package Bugzilla::Classification;
+
+use Bugzilla::Constants;
+use Bugzilla::Field;
+use Bugzilla::Util;
+use Bugzilla::Error;
+use Bugzilla::Product;
+
+use base qw(Bugzilla::Field::ChoiceInterface Bugzilla::Object);
+
+###############################
+#### Initialization ####
+###############################
+
+use constant DB_TABLE => 'classifications';
+use constant LIST_ORDER => 'sortkey, name';
+
+use constant DB_COLUMNS => qw(
+ id
+ name
+ description
+ sortkey
+);
+
+use constant UPDATE_COLUMNS => qw(
+ name
+ description
+ sortkey
+);
+
+use constant VALIDATORS => {
+ name => \&_check_name,
+ description => \&_check_description,
+ sortkey => \&_check_sortkey,
+};
+
+###############################
+#### Constructors #####
+###############################
+sub remove_from_db {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ ThrowUserError("classification_not_deletable") if ($self->id == 1);
+
+ $dbh->bz_start_transaction();
+ # Reclassify products to the default classification, if needed.
+ $dbh->do("UPDATE products SET classification_id = 1
+ WHERE classification_id = ?", undef, $self->id);
+
+ $self->SUPER::remove_from_db();
+
+ $dbh->bz_commit_transaction();
+
+}
+
+###############################
+#### Validators ####
+###############################
+
+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;
+}
+
+sub _check_description {
+ my ($invocant, $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;
+}
+
+#####################################
+# Implement Bugzilla::Field::Choice #
+#####################################
+
+use constant FIELD_NAME => 'classification';
+use constant is_default => 0;
+use constant is_active => 1;
+
+###############################
+#### Methods ####
+###############################
+
+sub set_name { $_[0]->set('name', $_[1]); }
+sub set_description { $_[0]->set('description', $_[1]); }
+sub set_sortkey { $_[0]->set('sortkey', $_[1]); }
+
+sub product_count {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ if (!defined $self->{'product_count'}) {
+ $self->{'product_count'} = $dbh->selectrow_array(q{
+ SELECT COUNT(*) FROM products
+ WHERE classification_id = ?}, undef, $self->id) || 0;
+ }
+ return $self->{'product_count'};
+}
+
+sub products {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ if (!$self->{'products'}) {
+ my $product_ids = $dbh->selectcol_arrayref(q{
+ SELECT id FROM products
+ WHERE classification_id = ?
+ ORDER BY name}, undef, $self->id);
+
+ $self->{'products'} = Bugzilla::Product->new_from_list($product_ids);
+ }
+ return $self->{'products'};
+}
+
+###############################
+#### Accessors ####
+###############################
+
+sub description { return $_[0]->{'description'}; }
+sub sortkey { return $_[0]->{'sortkey'}; }
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Classification - Bugzilla classification class.
+
+=head1 SYNOPSIS
+
+ use Bugzilla::Classification;
+
+ my $classification = new Bugzilla::Classification(1);
+ my $classification = new Bugzilla::Classification({name => 'Acme'});
+
+ my $id = $classification->id;
+ my $name = $classification->name;
+ my $description = $classification->description;
+ my $sortkey = $classification->sortkey;
+ my $product_count = $classification->product_count;
+ my $products = $classification->products;
+
+=head1 DESCRIPTION
+
+Classification.pm represents a classification object. It is an
+implementation of L<Bugzilla::Object>, and thus provides all methods
+that L<Bugzilla::Object> provides.
+
+The methods that are specific to C<Bugzilla::Classification> are listed
+below.
+
+A Classification is a higher-level grouping of Products.
+
+=head1 METHODS
+
+=over
+
+=item C<product_count()>
+
+ Description: Returns the total number of products that belong to
+ the classification.
+
+ Params: none.
+
+ Returns: Integer - The total of products inside the classification.
+
+=item C<products>
+
+ Description: Returns all products of the classification.
+
+ Params: none.
+
+ Returns: A reference to an array of Bugzilla::Product objects.
+
+=back
+
+=cut
diff --git a/Bugzilla/Comment.pm b/Bugzilla/Comment.pm
new file mode 100644
index 000000000..074f28dd6
--- /dev/null
+++ b/Bugzilla/Comment.pm
@@ -0,0 +1,316 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is James Robson.
+# Portions created by James Robson are Copyright (c) 2009 James Robson.
+# All rights reserved.
+#
+# Contributor(s): James Robson <arbingersys@gmail.com>
+
+use strict;
+
+package Bugzilla::Comment;
+
+use base qw(Bugzilla::Object);
+
+use Bugzilla::Attachment;
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::User;
+use Bugzilla::Util;
+
+use Scalar::Util qw(blessed);
+
+###############################
+#### Initialization ####
+###############################
+
+use constant DB_COLUMNS => qw(
+ comment_id
+ bug_id
+ who
+ bug_when
+ work_time
+ thetext
+ isprivate
+ already_wrapped
+ type
+ extra_data
+);
+
+use constant UPDATE_COLUMNS => qw(
+ type
+ extra_data
+);
+
+use constant DB_TABLE => 'longdescs';
+use constant ID_FIELD => 'comment_id';
+use constant LIST_ORDER => 'bug_when';
+
+use constant VALIDATORS => {
+ extra_data => \&_check_extra_data,
+ type => \&_check_type,
+};
+
+use constant VALIDATOR_DEPENDENCIES => {
+ extra_data => ['type'],
+};
+
+#########################
+# Database Manipulation #
+#########################
+
+sub update {
+ my $self = shift;
+ my $changes = $self->SUPER::update(@_);
+ $self->bug->_sync_fulltext();
+ return $changes;
+}
+
+# Speeds up displays of comment lists by loading all ->author objects
+# at once for a whole list.
+sub preload {
+ my ($class, $comments) = @_;
+ 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}};
+ }
+}
+
+###############################
+#### Accessors ######
+###############################
+
+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 { return $_[0]->{'work_time'}; }
+sub type { return $_[0]->{'type'}; }
+sub extra_data { return $_[0]->{'extra_data'} }
+
+sub 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;
+}
+
+sub attachment {
+ my ($self) = @_;
+ return undef if not $self->is_about_attachment;
+ $self->{attachment} ||= new Bugzilla::Attachment($self->extra_data);
+ return $self->{attachment};
+}
+
+sub author {
+ my $self = shift;
+ $self->{'author'} ||= new Bugzilla::User($self->{'who'});
+ 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;
+}
+
+############
+# Mutators #
+############
+
+sub set_extra_data { $_[0]->set('extra_data', $_[1]); }
+
+sub set_type {
+ my ($self, $type) = @_;
+ $self->set('type', $type);
+}
+
+##############
+# Validators #
+##############
+
+sub _check_extra_data {
+ 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 });
+ }
+ }
+ 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 });
+ }
+ }
+
+ 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;
+}
+
+sub count {
+ my ($self) = @_;
+
+ return $self->{'count'} if defined $self->{'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);
+
+ return --$self->{'count'};
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Comment - A Comment for a given bug
+
+=head1 SYNOPSIS
+
+ use Bugzilla::Comment;
+
+ my $comment = Bugzilla::Comment->new($comment_id);
+ my $comments = Bugzilla::Comment->new_from_list($comment_ids);
+
+=head1 DESCRIPTION
+
+Bugzilla::Comment represents a comment attached to a bug.
+
+This implements all standard C<Bugzilla::Object> methods. See
+L<Bugzilla::Object> for more details.
+
+=head2 Accessors
+
+=over
+
+=item C<bug_id>
+
+C<int> The ID of the bug to which the comment belongs.
+
+=item C<creation_ts>
+
+C<string> The comment creation timestamp.
+
+=item C<body>
+
+C<string> The body without any special additional text.
+
+=item C<work_time>
+
+C<string> Time spent as related to this comment.
+
+=item C<is_private>
+
+C<boolean> Comment is marked as private
+
+=item C<already_wrapped>
+
+If this comment is stored in the database word-wrapped, this will be C<1>.
+C<0> otherwise.
+
+=item C<author>
+
+L<Bugzilla::User> who created the comment.
+
+=item C<count>
+
+C<int> The position this comment is located in the full list of comments for a bug starting from 0.
+
+=item C<body_full>
+
+=over
+
+=item B<Description>
+
+C<string> Body of the comment, including any special text (such as
+"this bug was marked as a duplicate of...").
+
+=item B<Params>
+
+=over
+
+=item C<is_bugmail>
+
+C<boolean>. C<1> if this comment should be formatted specifically for
+bugmail.
+
+=item C<wrap>
+
+C<boolean>. C<1> if the comment should be returned word-wrapped.
+
+=back
+
+=item B<Returns>
+
+A string, the full text of the comment as it would be displayed to an end-user.
+
+=back
+
+=back
+
+=cut
diff --git a/Bugzilla/Component.pm b/Bugzilla/Component.pm
new file mode 100644
index 000000000..c2b36ebeb
--- /dev/null
+++ b/Bugzilla/Component.pm
@@ -0,0 +1,657 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s): Tiago R. Mello <timello@async.com.br>
+# Frédéric Buclin <LpSolit@gmail.com>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+# Akamai Technologies <bugzilla-dev@akamai.com>
+
+package Bugzilla::Component;
+use strict;
+use base qw(Bugzilla::Field::ChoiceInterface Bugzilla::Object);
+
+use Bugzilla::Constants;
+use Bugzilla::Util;
+use Bugzilla::Error;
+use Bugzilla::User;
+use Bugzilla::FlagType;
+use Bugzilla::Series;
+
+use Scalar::Util qw(blessed);
+
+###############################
+#### Initialization ####
+###############################
+
+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
+);
+
+use constant UPDATE_COLUMNS => qw(
+ name
+ initialowner
+ initialqacontact
+ description
+);
+
+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,
+};
+
+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 };
+ }
+
+ unshift @_, $param;
+ my $component = $class->SUPER::new(@_);
+ # Add the product object as attribute only if the component exists.
+ $component->{product} = $product if ($component && $product);
+ return $component;
+}
+
+sub create {
+ my $class = shift;
+ my $dbh = Bugzilla->dbh;
+
+ $dbh->bz_start_transaction();
+
+ $class->check_required_create_fields(@_);
+ my $params = $class->run_create_validators(@_);
+ my $cc_list = delete $params->{initial_cc};
+ my $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;
+
+ # 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;
+
+ $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;
+}
+
+sub remove_from_db {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ $self->_check_if_controller(); # From ChoiceInterface
+
+ $dbh->bz_start_transaction();
+
+ if ($self->bug_count) {
+ if (Bugzilla->params->{'allowbugdeletion'}) {
+ require Bugzilla::Bug;
+ foreach my $bug_id (@{$self->bug_ids}) {
+ # Note: We allow admins to delete bugs even if they can't
+ # see them, as long as they can see the product.
+ my $bug = new Bugzilla::Bug($bug_id);
+ $bug->remove_from_db();
+ }
+ } else {
+ ThrowUserError('component_has_bugs', {nb => $self->bug_count});
+ }
+ }
+
+ $dbh->do('DELETE FROM flaginclusions WHERE component_id = ?',
+ undef, $self->id);
+ $dbh->do('DELETE FROM flagexclusions WHERE component_id = ?',
+ undef, $self->id);
+ $dbh->do('DELETE FROM component_cc WHERE component_id = ?',
+ undef, $self->id);
+ $dbh->do('DELETE FROM components WHERE id = ?', undef, $self->id);
+
+ $dbh->bz_commit_transaction();
+}
+
+################################
+# Validators
+################################
+
+sub _check_name {
+ my ($invocant, $name, 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) = @_;
+
+ $description = trim($description);
+ $description || ThrowUserError('component_blank_description');
+ return $description;
+}
+
+sub _check_initialowner {
+ my ($invocant, $owner) = @_;
+
+ $owner || ThrowUserError('component_need_initialowner');
+ my $owner_id = Bugzilla::User->check($owner)->id;
+ return $owner_id;
+}
+
+sub _check_initialqacontact {
+ my ($invocant, $qa_contact) = @_;
+
+ my $qa_contact_id;
+ if (Bugzilla->params->{'useqacontact'}) {
+ $qa_contact_id = Bugzilla::User->check($qa_contact)->id if $qa_contact;
+ }
+ elsif (ref $invocant) {
+ $qa_contact_id = $invocant->{initialqacontact};
+ }
+ return $qa_contact_id;
+}
+
+sub _check_product {
+ my ($invocant, $product) = @_;
+ $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];
+}
+
+###############################
+#### Methods ####
+###############################
+
+sub _update_cc_list {
+ my ($self, $cc_list) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ my $old_cc_list =
+ $dbh->selectcol_arrayref('SELECT user_id FROM component_cc
+ WHERE component_id = ?', undef, $self->id);
+
+ my ($removed, $added) = diff_arrays($old_cc_list, $cc_list);
+ my $diff;
+ if (scalar @$removed || scalar @$added) {
+ $diff = [join(', ', @$removed), join(', ', @$added)];
+ }
+
+ $dbh->do('DELETE FROM component_cc WHERE component_id = ?', undef, $self->id);
+
+ my $sth = $dbh->prepare('INSERT INTO component_cc
+ (user_id, component_id) VALUES (?, ?)');
+ $sth->execute($_, $self->id) foreach (@$cc_list);
+
+ return $diff;
+}
+
+sub _create_series {
+ my $self = shift;
+
+ # Insert default charting queries for this product.
+ # If they aren't using charting, this won't do any harm.
+ my $prodcomp = "&product=" . url_quote($self->product->name) .
+ "&component=" . url_quote($self->name);
+
+ my $open_query = 'field0-0-0=resolution&type0-0-0=notregexp&value0-0-0=.' .
+ $prodcomp;
+ my $nonopen_query = 'field0-0-0=resolution&type0-0-0=regexp&value0-0-0=.' .
+ $prodcomp;
+
+ my @series = ([get_text('series_all_open'), $open_query],
+ [get_text('series_all_closed'), $nonopen_query]);
+
+ foreach my $sdata (@series) {
+ my $series = new Bugzilla::Series(undef, $self->product->name,
+ $self->name, $sdata->[0],
+ Bugzilla->user->id, 1, $sdata->[1], 1);
+ $series->writeToDatabase();
+ }
+}
+
+sub set_name { $_[0]->set('name', $_[1]); }
+sub set_description { $_[0]->set('description', $_[1]); }
+sub set_default_assignee {
+ my ($self, $owner) = @_;
+
+ $self->set('initialowner', $owner);
+ # Reset the default owner object.
+ delete $self->{default_assignee};
+}
+sub set_default_qa_contact {
+ my ($self, $qa_contact) = @_;
+
+ $self->set('initialqacontact', $qa_contact);
+ # Reset the default QA contact object.
+ delete $self->{default_qa_contact};
+}
+sub set_cc_list {
+ my ($self, $cc_list) = @_;
+
+ $self->{cc_ids} = $self->_check_cc_list($cc_list);
+ # Reset the list of CC user objects.
+ delete $self->{initial_cc};
+}
+
+sub bug_count {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ if (!defined $self->{'bug_count'}) {
+ $self->{'bug_count'} = $dbh->selectrow_array(q{
+ SELECT COUNT(*) FROM bugs
+ WHERE component_id = ?}, undef, $self->id) || 0;
+ }
+ return $self->{'bug_count'};
+}
+
+sub bug_ids {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ if (!defined $self->{'bugs_ids'}) {
+ $self->{'bugs_ids'} = $dbh->selectcol_arrayref(q{
+ SELECT bug_id FROM bugs
+ WHERE component_id = ?}, undef, $self->id);
+ }
+ return $self->{'bugs_ids'};
+}
+
+sub default_assignee {
+ my $self = shift;
+
+ if (!defined $self->{'default_assignee'}) {
+ $self->{'default_assignee'} =
+ new Bugzilla::User($self->{'initialowner'});
+ }
+ return $self->{'default_assignee'};
+}
+
+sub default_qa_contact {
+ my $self = shift;
+
+ if (!defined $self->{'default_qa_contact'}) {
+ $self->{'default_qa_contact'} =
+ new Bugzilla::User($self->{'initialqacontact'});
+ }
+ return $self->{'default_qa_contact'};
+}
+
+sub flag_types {
+ my $self = shift;
+
+ if (!defined $self->{'flag_types'}) {
+ 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'};
+}
+
+sub product {
+ my $self = shift;
+ if (!defined $self->{'product'}) {
+ require Bugzilla::Product; # We cannot |use| it.
+ $self->{'product'} = new Bugzilla::Product($self->product_id);
+ }
+ return $self->{'product'};
+}
+
+###############################
+#### Accessors ####
+###############################
+
+sub description { return $_[0]->{'description'}; }
+sub product_id { return $_[0]->{'product_id'}; }
+
+##############################################
+# Implement Bugzilla::Field::ChoiceInterface #
+##############################################
+
+use constant FIELD_NAME => 'component';
+use constant is_default => 0;
+use constant is_active => 1;
+
+sub is_set_on_bug {
+ my ($self, $bug) = @_;
+ # We treat it like a hash always, so that we don't have to check if it's
+ # a hash or an object.
+ return 0 if !defined $bug->{component_id};
+ $bug->{component_id} == $self->id ? 1 : 0;
+}
+
+###############################
+#### Subroutines ####
+###############################
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Component - Bugzilla product component class.
+
+=head1 SYNOPSIS
+
+ use Bugzilla::Component;
+
+ my $component = new Bugzilla::Component($comp_id);
+ my $component = new Bugzilla::Component({ product => $product, name => $name });
+
+ my $bug_count = $component->bug_count();
+ my $bug_ids = $component->bug_ids();
+ my $id = $component->id;
+ my $name = $component->name;
+ my $description = $component->description;
+ my $product_id = $component->product_id;
+ my $default_assignee = $component->default_assignee;
+ my $default_qa_contact = $component->default_qa_contact;
+ my $initial_cc = $component->initial_cc;
+ my $product = $component->product;
+ my $bug_flag_types = $component->flag_types->{'bug'};
+ my $attach_flag_types = $component->flag_types->{'attachment'};
+
+ my $component = Bugzilla::Component->check({ product => $product, name => $name });
+
+ my $component =
+ Bugzilla::Component->create({ name => $name,
+ product => $product,
+ initialowner => $user_login1,
+ initialqacontact => $user_login2,
+ description => $description});
+
+ $component->set_name($new_name);
+ $component->set_description($new_description);
+ $component->set_default_assignee($new_login_name);
+ $component->set_default_qa_contact($new_login_name);
+ $component->set_cc_list(\@new_login_names);
+ $component->update();
+
+ $component->remove_from_db;
+
+=head1 DESCRIPTION
+
+Component.pm represents a Product Component object.
+
+=head1 METHODS
+
+=over
+
+=item C<new($param)>
+
+ Description: The constructor is used to load an existing component
+ by passing a component ID or a hash with the product
+ object the component belongs to and the component name.
+
+ Params: $param - If you pass an integer, the integer is the
+ component ID from the database that we want to
+ read in. If you pass in a hash with the 'name'
+ and 'product' keys, then the value of the name
+ key is the name of a component being in the given
+ product.
+
+ Returns: A Bugzilla::Component object.
+
+=item C<bug_count()>
+
+ Description: Returns the total of bugs that belong to the component.
+
+ Params: none.
+
+ Returns: Integer with the number of bugs.
+
+=item C<bugs_ids()>
+
+ Description: Returns all bug IDs that belong to the component.
+
+ Params: none.
+
+ Returns: A reference to an array of bug IDs.
+
+=item C<default_assignee()>
+
+ Description: Returns a user object that represents the default assignee for
+ the component.
+
+ Params: none.
+
+ Returns: A Bugzilla::User object.
+
+=item C<default_qa_contact()>
+
+ Description: Returns a user object that represents the default QA contact for
+ the component.
+
+ Params: none.
+
+ Returns: A Bugzilla::User object.
+
+=item C<initial_cc>
+
+ Description: Returns a list of user objects representing users being
+ in the initial CC list.
+
+ Params: none.
+
+ Returns: An arrayref of L<Bugzilla::User> objects.
+
+=item C<flag_types()>
+
+ Description: Returns all bug and attachment flagtypes available for
+ the component.
+
+ Params: none.
+
+ Returns: Two references to an array of flagtype objects.
+
+=item C<product()>
+
+ Description: Returns the product the component belongs to.
+
+ Params: none.
+
+ Returns: A Bugzilla::Product object.
+
+=item C<set_name($new_name)>
+
+ Description: Changes the name of the component.
+
+ Params: $new_name - new name of the component (string). This name
+ must be unique within the product.
+
+ Returns: Nothing.
+
+=item C<set_description($new_desc)>
+
+ Description: Changes the description of the component.
+
+ Params: $new_desc - new description of the component (string).
+
+ Returns: Nothing.
+
+=item C<set_default_assignee($new_assignee)>
+
+ Description: Changes the default assignee of the component.
+
+ Params: $new_owner - login name of the new default assignee of
+ the component (string). This user account
+ must already exist.
+
+ Returns: Nothing.
+
+=item C<set_default_qa_contact($new_qa_contact)>
+
+ Description: Changes the default QA contact of the component.
+
+ Params: $new_qa_contact - login name of the new QA contact of
+ the component (string). This user
+ account must already exist.
+
+ Returns: Nothing.
+
+=item C<set_cc_list(\@cc_list)>
+
+ Description: Changes the list of users being in the CC list by default.
+
+ Params: \@cc_list - list of login names (string). All the user
+ accounts must already exist.
+
+ Returns: Nothing.
+
+=item C<update()>
+
+ Description: Write changes made to the component into the DB.
+
+ Params: none.
+
+ Returns: A hashref with changes made to the component object.
+
+=item C<remove_from_db()>
+
+ Description: Deletes the current component from the DB. The object itself
+ is not destroyed.
+
+ Params: none.
+
+ Returns: Nothing.
+
+=back
+
+=head1 CLASS METHODS
+
+=over
+
+=item C<create(\%params)>
+
+ Description: Create a new component for the given product.
+
+ Params: The hashref must have the following keys:
+ name - name of the new component (string). This name
+ must be unique within the product.
+ product - a Bugzilla::Product object to which
+ the Component is being added.
+ description - description of the new component (string).
+ initialowner - login name of the default assignee (string).
+ The following keys are optional:
+ initiaqacontact - login name of the default QA contact (string),
+ or an empty string to clear it.
+ initial_cc - an arrayref of login names to add to the
+ CC list by default.
+
+ Returns: A Bugzilla::Component object.
+
+=back
+
+=cut
diff --git a/Bugzilla/Config.pm b/Bugzilla/Config.pm
new file mode 100644
index 000000000..a7184866f
--- /dev/null
+++ b/Bugzilla/Config.pm
@@ -0,0 +1,408 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Dawn Endico <endico@mozilla.org>
+# Dan Mosedale <dmose@mozilla.org>
+# Joe Robins <jmrobins@tgix.com>
+# Jake <jake@bugzilla.org>
+# J. Paul Reed <preed@sigkill.com>
+# Bradley Baetz <bbaetz@student.usyd.edu.au>
+# Christopher Aillon <christopher@aillon.com>
+# Erik Stambaugh <erik@dasbistro.com>
+# Frédéric Buclin <LpSolit@gmail.com>
+
+package Bugzilla::Config;
+
+use strict;
+
+use base qw(Exporter);
+use Bugzilla::Constants;
+use Bugzilla::Hook;
+use Bugzilla::Install::Filesystem qw(fix_file_permissions);
+use Data::Dumper;
+use File::Temp;
+
+# Don't export localvars by default - people should have to explicitly
+# ask for it, as a (probably futile) attempt to stop code using it
+# when it shouldn't
+%Bugzilla::Config::EXPORT_TAGS =
+ (
+ admin => [qw(update_params SetParam write_params)],
+ );
+Exporter::export_ok_tags('admin');
+
+use vars qw(@param_list);
+
+# INITIALISATION CODE
+# Perl throws a warning if we use bz_locations() directly after do.
+our %params;
+# Load in the param definitions
+sub _load_params {
+ my $panels = param_panels();
+ 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 };
+ foreach my $item (@new_param_list) {
+ $params{$item->{'name'}} = $item;
+ }
+ push(@param_list, @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 });
+}
+# 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;
+}
+
+sub SetParam {
+ my ($name, $value) = @_;
+
+ _load_params unless %params;
+ die "Unknown param $name" unless (exists $params{$name});
+
+ my $entry = $params{$name};
+
+ # sanity check the value
+
+ # XXX - This runs the checks. Which would be good, except that
+ # check_shadowdb creates the database as a side effect, and so the
+ # checker fails the second time around...
+ if ($name ne 'shadowdb' && exists $entry->{'checker'}) {
+ my $err = $entry->{'checker'}->($value, $entry);
+ die "Param $name is not valid: $err" unless $err eq '';
+ }
+
+ Bugzilla->params->{$name} = $value;
+}
+
+sub update_params {
+ my ($params) = @_;
+ my $answer = Bugzilla->installation_answers;
+
+ my $param = read_param_file();
+
+ # If we didn't return any param values, then this is a new installation.
+ my $new_install = !(keys %$param);
+
+ # --- UPDATE OLD PARAMS ---
+
+ # Old Bugzilla versions stored the version number in the params file
+ # We don't want it, so get rid of it
+ delete $param->{'version'};
+
+ # Change from usebrowserinfo to defaultplatform/defaultopsys combo
+ if (exists $param->{'usebrowserinfo'}) {
+ if (!$param->{'usebrowserinfo'}) {
+ if (!exists $param->{'defaultplatform'}) {
+ $param->{'defaultplatform'} = 'Other';
+ }
+ if (!exists $param->{'defaultopsys'}) {
+ $param->{'defaultopsys'} = 'Other';
+ }
+ }
+ delete $param->{'usebrowserinfo'};
+ }
+
+ # Change from a boolean for quips to multi-state
+ if (exists $param->{'usequip'} && !exists $param->{'enablequips'}) {
+ $param->{'enablequips'} = $param->{'usequip'} ? 'on' : 'off';
+ delete $param->{'usequip'};
+ }
+
+ # Change from old product groups to controls for group_control_map
+ # 2002-10-14 bug 147275 bugreport@peshkin.net
+ if (exists $param->{'usebuggroups'} &&
+ !exists $param->{'makeproductgroups'})
+ {
+ $param->{'makeproductgroups'} = $param->{'usebuggroups'};
+ }
+
+ # Modularise auth code
+ if (exists $param->{'useLDAP'} && !exists $param->{'loginmethod'}) {
+ $param->{'loginmethod'} = $param->{'useLDAP'} ? "LDAP" : "DB";
+ }
+
+ # set verify method to whatever loginmethod was
+ if (exists $param->{'loginmethod'}
+ && !exists $param->{'user_verify_class'})
+ {
+ $param->{'user_verify_class'} = $param->{'loginmethod'};
+ delete $param->{'loginmethod'};
+ }
+
+ # Remove quip-display control from parameters
+ # and give it to users via User Settings (Bug 41972)
+ if ( exists $param->{'enablequips'}
+ && !exists $param->{'quip_list_entry_control'})
+ {
+ my $new_value;
+ ($param->{'enablequips'} eq 'on') && do {$new_value = 'open';};
+ ($param->{'enablequips'} eq 'approved') && do {$new_value = 'moderated';};
+ ($param->{'enablequips'} eq 'frozen') && do {$new_value = 'closed';};
+ ($param->{'enablequips'} eq 'off') && do {$new_value = 'closed';};
+ $param->{'quip_list_entry_control'} = $new_value;
+ delete $param->{'enablequips'};
+ }
+
+ # Old mail_delivery_method choices contained no uppercase characters
+ if (exists $param->{'mail_delivery_method'}
+ && $param->{'mail_delivery_method'} !~ /[A-Z]/) {
+ my $method = $param->{'mail_delivery_method'};
+ my %translation = (
+ 'sendmail' => 'Sendmail',
+ 'smtp' => 'SMTP',
+ 'qmail' => 'Qmail',
+ 'testfile' => 'Test',
+ 'none' => 'None');
+ $param->{'mail_delivery_method'} = $translation{$method};
+ }
+
+ # 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') {
+ $param->{'ssl_redirect'} = 1;
+ }
+
+ # --- DEFAULTS FOR NEW PARAMS ---
+
+ _load_params unless %params;
+ foreach my $item (@param_list) {
+ my $name = $item->{'name'};
+ unless (exists $param->{$name}) {
+ print "New parameter: $name\n" unless $new_install;
+ if (exists $answer->{$name}) {
+ $param->{$name} = $answer->{$name};
+ }
+ else {
+ $param->{$name} = $item->{'default'};
+ }
+ }
+ }
+
+ $param->{'utf8'} = 1 if $new_install;
+
+ # --- REMOVE OLD PARAMS ---
+
+ my %oldparams;
+ # Remove any old params
+ foreach my $item (keys %$param) {
+ if (!grep($_ eq $item, map ($_->{'name'}, @param_list))) {
+ $oldparams{$item} = $param->{$item};
+ delete $param->{$item};
+ }
+ }
+
+ # Write any old parameters to old-params.txt
+ my $datadir = bz_locations()->{'datadir'};
+ 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";
+
+ local $Data::Dumper::Terse = 1;
+ local $Data::Dumper::Indent = 0;
+
+ my $comma = "";
+ foreach my $item (keys %oldparams) {
+ print $op_file "\n\n$item:\n" . Data::Dumper->Dump([$oldparams{$item}]) . "\n";
+ print "${comma}$item";
+ $comma = ", ";
+ }
+ print "\n";
+ $op_file->close;
+ }
+
+ if (ON_WINDOWS && !-e SENDMAIL_EXE
+ && $param->{'mail_delivery_method'} eq 'Sendmail')
+ {
+ my $smtp = $answer->{'SMTP_SERVER'};
+ if (!$smtp) {
+ print "\nBugzilla requires an SMTP server to function on",
+ " Windows.\nPlease enter your SMTP server's hostname: ";
+ $smtp = <STDIN>;
+ chomp $smtp;
+ if ($smtp) {
+ $param->{'smtpserver'} = $smtp;
+ }
+ else {
+ print "\nWarning: No SMTP Server provided, defaulting to",
+ " localhost\n";
+ }
+ }
+
+ $param->{'mail_delivery_method'} = 'SMTP';
+ }
+
+ write_params($param);
+
+ # Return deleted params and values so that checksetup.pl has a chance
+ # to convert old params to new data.
+ return %oldparams;
+}
+
+sub write_params {
+ my ($param_data) = @_;
+ $param_data ||= Bugzilla->params;
+
+ my $datadir = bz_locations()->{'datadir'};
+ my $param_file = "$datadir/params";
+
+ local $Data::Dumper::Sortkeys = 1;
+
+ my ($fh, $tmpname) = File::Temp::tempfile('params.XXXXX',
+ DIR => $datadir );
+
+ print $fh (Data::Dumper->Dump([$param_data], ['*param']))
+ || die "Can't write param file: $!";
+
+ close $fh;
+
+ rename $tmpname, $param_file
+ or die "Can't rename $tmpname to $param_file: $!";
+
+ 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};
+}
+
+sub read_param_file {
+ my %params;
+ my $datadir = bz_locations()->{'datadir'};
+ if (-e "$datadir/params") {
+ # Note that checksetup.pl sets file permissions on '$datadir/params'
+
+ # Using Safe mode is _not_ a guarantee of safety if someone does
+ # manage to write to the file. However, it won't hurt...
+ # See bug 165144 for not needing to eval this at all
+ my $s = new Safe;
+
+ $s->rdo("$datadir/params");
+ die "Error reading $datadir/params: $!" if $!;
+ die "Error evaluating $datadir/params: $@" if $@;
+
+ # Now read the param back out from the sandbox
+ %params = %{$s->varglob('param')};
+ }
+ elsif ($ENV{'SERVER_SOFTWARE'}) {
+ # We're in a CGI, but the params file doesn't exist. We can't
+ # Template Toolkit, or even install_string, since checksetup
+ # might not have thrown an error. Bugzilla::CGI->new
+ # hasn't even been called yet, so we manually use CGI::Carp here
+ # so that the user sees the error.
+ require CGI::Carp;
+ CGI::Carp->import('fatalsToBrowser');
+ die "The $datadir/params file does not exist."
+ . ' You probably need to run checksetup.pl.',
+ }
+ return \%params;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Config - Configuration parameters for Bugzilla
+
+=head1 SYNOPSIS
+
+ # Administration functions
+ use Bugzilla::Config qw(:admin);
+
+ update_params();
+ SetParam($param, $value);
+ write_params();
+
+=head1 DESCRIPTION
+
+This package contains ways to access Bugzilla configuration parameters.
+
+=head1 FUNCTIONS
+
+=head2 Parameters
+
+Parameters can be set, retrieved, and updated.
+
+=over 4
+
+=item C<SetParam($name, $value)>
+
+Sets the param named $name to $value. Values are checked using the checker
+function for the given param if one exists.
+
+=item C<update_params()>
+
+Updates the parameters, by transitioning old params to new formats, setting
+defaults for new params, and removing obsolete ones. Used by F<checksetup.pl>
+in the process of an installation or upgrade.
+
+Prints out information about what it's doing, if it makes any changes.
+
+May prompt the user for input, if certain required parameters are not
+specified.
+
+=item C<write_params($params)>
+
+Description: Writes the parameters to disk.
+
+Params: C<$params> (optional) - A hashref to write to the disk
+ instead of C<Bugzilla->params>. Used only by
+ C<update_params>.
+
+Returns: nothing
+
+=item C<read_param_file()>
+
+Description: Most callers should never need this. This is used
+ by C<Bugzilla->params> to directly read C<$datadir/params>
+ and load it into memory. Use C<Bugzilla->params> instead.
+
+Params: none
+
+Returns: A hashref containing the current params in C<$datadir/params>.
+
+=back
diff --git a/Bugzilla/Config/Admin.pm b/Bugzilla/Config/Admin.pm
new file mode 100644
index 000000000..e6141cf9e
--- /dev/null
+++ b/Bugzilla/Config/Admin.pm
@@ -0,0 +1,63 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Dawn Endico <endico@mozilla.org>
+# Dan Mosedale <dmose@mozilla.org>
+# Joe Robins <jmrobins@tgix.com>
+# Jacob Steenhagen <jake@bugzilla.org>
+# J. Paul Reed <preed@sigkill.com>
+# Bradley Baetz <bbaetz@student.usyd.edu.au>
+# Joseph Heenan <joseph@heenan.me.uk>
+# Erik Stambaugh <erik@dasbistro.com>
+# Frédéric Buclin <LpSolit@gmail.com>
+#
+
+package Bugzilla::Config::Admin;
+
+use strict;
+
+use Bugzilla::Config::Common;
+
+our $sortkey = 200;
+
+sub get_param_list {
+ my $class = shift;
+ my @param_list = (
+ {
+ name => 'allowbugdeletion',
+ type => 'b',
+ default => 0
+ },
+
+ {
+ name => 'allowemailchange',
+ type => 'b',
+ default => 1
+ },
+
+ {
+ name => 'allowuserdeletion',
+ type => 'b',
+ default => 0
+ });
+ return @param_list;
+}
+
+1;
diff --git a/Bugzilla/Config/Advanced.pm b/Bugzilla/Config/Advanced.pm
new file mode 100644
index 000000000..faab6bbbd
--- /dev/null
+++ b/Bugzilla/Config/Advanced.pm
@@ -0,0 +1,67 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Dawn Endico <endico@mozilla.org>
+# Dan Mosedale <dmose@mozilla.org>
+# Joe Robins <jmrobins@tgix.com>
+# Jacob Steenhagen <jake@bugzilla.org>
+# J. Paul Reed <preed@sigkill.com>
+# Bradley Baetz <bbaetz@student.usyd.edu.au>
+# Joseph Heenan <joseph@heenan.me.uk>
+# Erik Stambaugh <erik@dasbistro.com>
+# Frédéric Buclin <LpSolit@gmail.com>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::Config::Advanced;
+use strict;
+
+use Bugzilla::Config::Common;
+
+our $sortkey = 1700;
+
+use constant get_param_list => (
+ {
+ name => 'cookiedomain',
+ type => 't',
+ default => ''
+ },
+
+ {
+ name => 'inbound_proxies',
+ 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
+ },
+);
+
+1;
diff --git a/Bugzilla/Config/Attachment.pm b/Bugzilla/Config/Attachment.pm
new file mode 100644
index 000000000..ed4c4e459
--- /dev/null
+++ b/Bugzilla/Config/Attachment.pm
@@ -0,0 +1,91 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Dawn Endico <endico@mozilla.org>
+# Dan Mosedale <dmose@mozilla.org>
+# Joe Robins <jmrobins@tgix.com>
+# Jacob Steenhagen <jake@bugzilla.org>
+# J. Paul Reed <preed@sigkill.com>
+# Bradley Baetz <bbaetz@student.usyd.edu.au>
+# Joseph Heenan <joseph@heenan.me.uk>
+# Erik Stambaugh <erik@dasbistro.com>
+# Frédéric Buclin <LpSolit@gmail.com>
+#
+
+package Bugzilla::Config::Attachment;
+
+use strict;
+
+use Bugzilla::Config::Common;
+
+our $sortkey = 400;
+
+sub get_param_list {
+ 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 => 'allow_attach_url',
+ 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;
+}
+
+1;
diff --git a/Bugzilla/Config/Auth.pm b/Bugzilla/Config/Auth.pm
new file mode 100644
index 000000000..c7d921ed5
--- /dev/null
+++ b/Bugzilla/Config/Auth.pm
@@ -0,0 +1,128 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Dawn Endico <endico@mozilla.org>
+# Dan Mosedale <dmose@mozilla.org>
+# Joe Robins <jmrobins@tgix.com>
+# Jacob Steenhagen <jake@bugzilla.org>
+# J. Paul Reed <preed@sigkill.com>
+# Bradley Baetz <bbaetz@student.usyd.edu.au>
+# Joseph Heenan <joseph@heenan.me.uk>
+# Erik Stambaugh <erik@dasbistro.com>
+# Frédéric Buclin <LpSolit@gmail.com>
+#
+
+package Bugzilla::Config::Auth;
+
+use strict;
+
+use Bugzilla::Config::Common;
+
+our $sortkey = 300;
+
+sub get_param_list {
+ my $class = shift;
+ my @param_list = (
+ {
+ name => 'auth_env_id',
+ type => 't',
+ default => '',
+ },
+
+ {
+ name => 'auth_env_email',
+ type => 't',
+ default => '',
+ },
+
+ {
+ name => 'auth_env_realname',
+ type => 't',
+ default => '',
+ },
+
+ # XXX in the future:
+ #
+ # user_verify_class and user_info_class should have choices gathered from
+ # whatever sits in their respective directories
+ #
+ # rather than comma-separated lists, these two should eventually become
+ # arrays, but that requires alterations to editparams first
+
+ {
+ name => 'user_info_class',
+ type => 's',
+ choices => [ 'CGI', 'Env', 'Env,CGI' ],
+ default => 'CGI',
+ checker => \&check_multi
+ },
+
+ {
+ name => 'user_verify_class',
+ type => 'o',
+ choices => [ 'DB', 'RADIUS', 'LDAP' ],
+ default => 'DB',
+ checker => \&check_user_verify_class
+ },
+
+ {
+ name => 'rememberlogin',
+ type => 's',
+ choices => ['on', 'defaulton', 'defaultoff', 'off'],
+ default => 'on',
+ checker => \&check_multi
+ },
+
+ {
+ name => 'requirelogin',
+ type => 'b',
+ default => '0'
+ },
+
+ {
+ name => 'emailregexp',
+ type => 't',
+ default => q:^[\\w\\.\\+\\-=]+@[\\w\\.\\-]+\\.[\\w\\-]+$:,
+ checker => \&check_regexp
+ },
+
+ {
+ name => 'emailregexpdesc',
+ type => 'l',
+ default => 'A legal address must contain exactly one \'@\', and at least ' .
+ 'one \'.\' after the @.'
+ },
+
+ {
+ name => 'emailsuffix',
+ type => 't',
+ default => ''
+ },
+
+ {
+ name => 'createemailregexp',
+ type => 't',
+ default => q:.*:,
+ checker => \&check_regexp
+ } );
+ return @param_list;
+}
+
+1;
diff --git a/Bugzilla/Config/BugChange.pm b/Bugzilla/Config/BugChange.pm
new file mode 100644
index 000000000..4e197c5e9
--- /dev/null
+++ b/Bugzilla/Config/BugChange.pm
@@ -0,0 +1,103 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Dawn Endico <endico@mozilla.org>
+# Dan Mosedale <dmose@mozilla.org>
+# Joe Robins <jmrobins@tgix.com>
+# Jacob Steenhagen <jake@bugzilla.org>
+# J. Paul Reed <preed@sigkill.com>
+# Bradley Baetz <bbaetz@student.usyd.edu.au>
+# Joseph Heenan <joseph@heenan.me.uk>
+# Erik Stambaugh <erik@dasbistro.com>
+# Frédéric Buclin <LpSolit@gmail.com>
+#
+
+package Bugzilla::Config::BugChange;
+
+use strict;
+
+use Bugzilla::Config::Common;
+use Bugzilla::Status;
+
+our $sortkey = 500;
+
+sub get_param_list {
+ my $class = shift;
+
+ # Hardcoded bug statuses which existed before Bugzilla 3.1.
+ my @closed_bug_statuses = ('RESOLVED', 'VERIFIED', 'CLOSED');
+
+ # If we are upgrading from 3.0 or older, bug statuses are not customisable
+ # and bug_status.is_open is not yet defined (hence the eval), so we use
+ # the bug statuses above as they are still hardcoded.
+ eval {
+ my @current_closed_states = map {$_->name} closed_bug_statuses();
+ # If no closed state was found, use the default list above.
+ @closed_bug_statuses = @current_closed_states if scalar(@current_closed_states);
+ };
+
+ my @param_list = (
+ {
+ name => 'duplicate_or_move_bug_status',
+ type => 's',
+ choices => \@closed_bug_statuses,
+ default => $closed_bug_statuses[0],
+ checker => \&check_bug_status
+ },
+
+ {
+ name => 'letsubmitterchoosepriority',
+ type => 'b',
+ default => 1
+ },
+
+ {
+ name => 'letsubmitterchoosemilestone',
+ type => 'b',
+ default => 1
+ },
+
+ {
+ name => 'musthavemilestoneonaccept',
+ type => 'b',
+ default => 0
+ },
+
+ {
+ name => 'commentonchange_resolution',
+ type => 'b',
+ default => 0
+ },
+
+ {
+ name => 'commentonduplicate',
+ type => 'b',
+ default => 0
+ },
+
+ {
+ name => 'noresolveonopenblockers',
+ type => 'b',
+ default => 0,
+ } );
+ return @param_list;
+}
+
+1;
diff --git a/Bugzilla/Config/BugFields.pm b/Bugzilla/Config/BugFields.pm
new file mode 100644
index 000000000..d0de9dac6
--- /dev/null
+++ b/Bugzilla/Config/BugFields.pm
@@ -0,0 +1,120 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Dawn Endico <endico@mozilla.org>
+# Dan Mosedale <dmose@mozilla.org>
+# Joe Robins <jmrobins@tgix.com>
+# Jacob Steenhagen <jake@bugzilla.org>
+# J. Paul Reed <preed@sigkill.com>
+# Bradley Baetz <bbaetz@student.usyd.edu.au>
+# Joseph Heenan <joseph@heenan.me.uk>
+# Erik Stambaugh <erik@dasbistro.com>
+# Frédéric Buclin <LpSolit@gmail.com>
+#
+
+package Bugzilla::Config::BugFields;
+
+use strict;
+
+use Bugzilla::Config::Common;
+use Bugzilla::Field;
+
+our $sortkey = 600;
+
+sub get_param_list {
+ my $class = shift;
+
+ my @legal_priorities = @{get_legal_field_values('priority')};
+ my @legal_severities = @{get_legal_field_values('bug_severity')};
+ my @legal_platforms = @{get_legal_field_values('rep_platform')};
+ my @legal_OS = @{get_legal_field_values('op_sys')};
+
+ my @param_list = (
+ {
+ name => 'useclassification',
+ type => 'b',
+ default => 0
+ },
+
+ {
+ name => 'usetargetmilestone',
+ type => 'b',
+ default => 0
+ },
+
+ {
+ name => 'useqacontact',
+ type => 'b',
+ default => 0
+ },
+
+ {
+ name => 'usestatuswhiteboard',
+ type => 'b',
+ default => 0
+ },
+
+ {
+ name => 'usebugaliases',
+ 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
+ } );
+ return @param_list;
+}
+
+1;
diff --git a/Bugzilla/Config/Common.pm b/Bugzilla/Config/Common.pm
new file mode 100644
index 000000000..9fffe02ee
--- /dev/null
+++ b/Bugzilla/Config/Common.pm
@@ -0,0 +1,461 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Dawn Endico <endico@mozilla.org>
+# Dan Mosedale <dmose@mozilla.org>
+# Joe Robins <jmrobins@tgix.com>
+# Jacob Steenhagen <jake@bugzilla.org>
+# J. Paul Reed <preed@sigkill.com>
+# Bradley Baetz <bbaetz@student.usyd.edu.au>
+# Joseph Heenan <joseph@heenan.me.uk>
+# Erik Stambaugh <erik@dasbistro.com>
+# Frédéric Buclin <LpSolit@gmail.com>
+# Marc Schumann <wurblzap@gmail.com>
+#
+
+package Bugzilla::Config::Common;
+
+use strict;
+
+use Email::Address;
+use Socket;
+
+use Bugzilla::Util;
+use Bugzilla::Constants;
+use Bugzilla::Field;
+use Bugzilla::Group;
+use Bugzilla::Status;
+
+use base qw(Exporter);
+@Bugzilla::Config::Common::EXPORT =
+ qw(check_multi check_numeric check_regexp check_url check_group
+ check_sslbase check_priority check_severity check_platform
+ check_opsys check_shadowdb check_urlbase check_webdotbase
+ check_user_verify_class
+ check_mail_delivery_method check_notification check_utf8
+ check_bug_status check_smtp_auth check_theschwartz_available
+ check_maxattachmentsize check_email
+);
+
+# Checking functions for the various values
+
+sub check_multi {
+ my ($value, $param) = (@_);
+
+ if ($param->{'type'} eq "s") {
+ unless (scalar(grep {$_ eq $value} (@{$param->{'choices'}}))) {
+ return "Invalid choice '$value' for single-select list param '$param->{'name'}'";
+ }
+
+ return "";
+ }
+ elsif ($param->{'type'} eq 'm' || $param->{'type'} eq 'o') {
+ foreach my $chkParam (split(',', $value)) {
+ unless (scalar(grep {$_ eq $chkParam} (@{$param->{'choices'}}))) {
+ return "Invalid choice '$chkParam' for multi-select list param '$param->{'name'}'";
+ }
+ }
+
+ return "";
+ }
+ else {
+ return "Invalid param type '$param->{'type'}' for check_multi(); " .
+ "contact your Bugzilla administrator";
+ }
+}
+
+sub check_numeric {
+ my ($value) = (@_);
+ if ($value !~ /^[0-9]+$/) {
+ return "must be a numeric value";
+ }
+ return "";
+}
+
+sub check_regexp {
+ my ($value) = (@_);
+ eval { qr/$value/ };
+ return $@;
+}
+
+sub check_email {
+ 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 "";
+}
+
+sub check_utf8 {
+ my $utf8 = shift;
+ # You cannot turn off the UTF-8 parameter if you've already converted
+ # your tables to utf-8.
+ my $dbh = Bugzilla->dbh;
+ if ($dbh->isa('Bugzilla::DB::Mysql') && $dbh->bz_db_is_utf8 && !$utf8) {
+ return "You cannot disable UTF-8 support, because your MySQL database"
+ . " is encoded in UTF-8";
+ }
+ return "";
+}
+
+sub check_priority {
+ my ($value) = (@_);
+ my $legal_priorities = get_legal_field_values('priority');
+ if (!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 "";
+}
+
+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 "";
+}
+
+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 "";
+}
+
+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 "";
+}
+
+sub check_group {
+ my $group_name = shift;
+ return "" unless $group_name;
+ my $group = new Bugzilla::Group({'name' => $group_name});
+ unless (defined $group) {
+ return "Must be an existing group name";
+ }
+ return "";
+}
+
+sub check_shadowdb {
+ my ($value) = (@_);
+ $value = trim($value);
+ if ($value eq "") {
+ return "";
+ }
+
+ if (!Bugzilla->params->{'shadowdbhost'}) {
+ return "You need to specify a host when using a shadow database";
+ }
+
+ # Can't test existence of this because ConnectToDatabase uses the param,
+ # but we can't set this before testing....
+ # This can really only be fixed after we can use the DBI more openly
+ return "";
+}
+
+sub check_urlbase {
+ my ($url) = (@_);
+ if ($url && $url !~ m:^http.*/$:) {
+ return "must be a legal URL, that starts with http and ends with a slash.";
+ }
+ return "";
+}
+
+sub check_url {
+ my ($url) = (@_);
+ return '' if $url eq ''; # Allow empty URLs
+ if ($url !~ m:/$:) {
+ return 'must be a legal URL, absolute or relative, ending with a slash.';
+ }
+ return '';
+}
+
+sub check_webdotbase {
+ my ($value) = (@_);
+ $value = trim($value);
+ if ($value eq "") {
+ return "";
+ }
+ if($value !~ /^https?:/) {
+ if(! -x $value) {
+ return "The file path \"$value\" is not a valid executable. Please specify the complete file path to 'dot' if you intend to generate graphs locally.";
+ }
+ # Check .htaccess allows access to generated images
+ my $webdotdir = bz_locations()->{'webdotdir'};
+ if(-e "$webdotdir/.htaccess") {
+ open HTACCESS, "$webdotdir/.htaccess";
+ if(! grep(/ \\\.png\$/,<HTACCESS>)) {
+ return "Dependency graph images are not accessible.\nAssuming that you have not modified the file, delete $webdotdir/.htaccess and re-run checksetup.pl to rectify.\n";
+ }
+ close HTACCESS;
+ }
+ }
+ return "";
+}
+
+sub check_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 "";
+}
+
+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 "";
+}
+
+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 "";
+}
+
+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 "";
+}
+
+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 "";
+}
+
+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 "";
+}
+
+# OK, here are the parameter definitions themselves.
+#
+# Each definition is a hash with keys:
+#
+# name - name of the param
+# desc - description of the param (for editparams.cgi)
+# type - see below
+# choices - (optional) see below
+# default - default value for the param
+# checker - (optional) checking function for validating parameter entry
+# It is called with the value of the param as the first arg and a
+# reference to the param's hash as the second argument
+#
+# The type value can be one of the following:
+#
+# t -- A short text entry field (suitable for a single line)
+# p -- A short text entry field (as with type = 't'), but the string is
+# replaced by asterisks (appropriate for passwords)
+# l -- A long text field (suitable for many lines)
+# b -- A boolean value (either 1 or 0)
+# m -- A list of values, with many selectable (shows up as a select box)
+# To specify the list of values, make the 'choices' key be an array
+# reference of the valid choices. The 'default' key should be a string
+# with a list of selected values (as a comma-separated list), i.e.:
+# {
+# name => 'multiselect',
+# desc => 'A list of options, choose many',
+# type => 'm',
+# choices => [ 'a', 'b', 'c', 'd' ],
+# default => [ 'a', 'd' ],
+# checker => \&check_multi
+# }
+#
+# Here, 'a' and 'd' are the default options, and the user may pick any
+# combination of a, b, c, and d as valid options.
+#
+# &check_multi should always be used as the param verification function
+# for list (single and multiple) parameter types.
+#
+# o -- A list of values, orderable, and with many selectable (shows up as a
+# JavaScript-enhanced select box if JavaScript is enabled, and a text
+# entry field if not)
+# Set up in the same way as type m.
+#
+# s -- A list of values, with one selectable (shows up as a select box)
+# To specify the list of values, make the 'choices' key be an array
+# reference of the valid choices. The 'default' key should be one of
+# those values, i.e.:
+# {
+# name => 'singleselect',
+# desc => 'A list of options, choose one',
+# type => 's',
+# choices => [ 'a', 'b', 'c' ],
+# default => 'b',
+# checker => \&check_multi
+# }
+#
+# Here, 'b' is the default option, and 'a' and 'c' are other possible
+# options, but only one at a time!
+#
+# &check_multi should always be used as the param verification function
+# for list (single and multiple) parameter types.
+
+sub get_param_list {
+ return;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Config::Common - Parameter checking functions
+
+=head1 DESCRIPTION
+
+All parameter checking functions are called with two parameters: the value to
+check, and a hash with the details of the param (type, default etc.) as defined
+in the relevant F<Bugzilla::Config::*> package.
+
+=head2 Functions
+
+=over
+
+=item C<check_multi>
+
+Checks that a multi-valued parameter (ie types C<s>, C<o> or C<m>) satisfies
+its contraints.
+
+=item C<check_numeric>
+
+Checks that the value is a valid number
+
+=item C<check_regexp>
+
+Checks that the value is a valid regexp
+
+=back
diff --git a/Bugzilla/Config/Core.pm b/Bugzilla/Config/Core.pm
new file mode 100644
index 000000000..1548dcd9c
--- /dev/null
+++ b/Bugzilla/Config/Core.pm
@@ -0,0 +1,68 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Dawn Endico <endico@mozilla.org>
+# Dan Mosedale <dmose@mozilla.org>
+# Joe Robins <jmrobins@tgix.com>
+# Jacob Steenhagen <jake@bugzilla.org>
+# J. Paul Reed <preed@sigkill.com>
+# Bradley Baetz <bbaetz@student.usyd.edu.au>
+# Joseph Heenan <joseph@heenan.me.uk>
+# Erik Stambaugh <erik@dasbistro.com>
+# Frédéric Buclin <LpSolit@gmail.com>
+#
+
+package Bugzilla::Config::Core;
+
+use strict;
+
+use Bugzilla::Config::Common;
+
+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 => '/'
+ },
+);
+
+1;
diff --git a/Bugzilla/Config/DependencyGraph.pm b/Bugzilla/Config/DependencyGraph.pm
new file mode 100644
index 000000000..b217bfb06
--- /dev/null
+++ b/Bugzilla/Config/DependencyGraph.pm
@@ -0,0 +1,52 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Dawn Endico <endico@mozilla.org>
+# Dan Mosedale <dmose@mozilla.org>
+# Joe Robins <jmrobins@tgix.com>
+# Jacob Steenhagen <jake@bugzilla.org>
+# J. Paul Reed <preed@sigkill.com>
+# Bradley Baetz <bbaetz@student.usyd.edu.au>
+# Joseph Heenan <joseph@heenan.me.uk>
+# Erik Stambaugh <erik@dasbistro.com>
+# Frédéric Buclin <LpSolit@gmail.com>
+#
+
+package Bugzilla::Config::DependencyGraph;
+
+use strict;
+
+use Bugzilla::Config::Common;
+
+our $sortkey = 800;
+
+sub get_param_list {
+ my $class = shift;
+ my @param_list = (
+ {
+ name => 'webdotbase',
+ type => 't',
+ default => 'http://www.research.att.com/~north/cgi-bin/webdot.cgi/%urlbase%',
+ checker => \&check_webdotbase
+ } );
+ return @param_list;
+}
+
+1;
diff --git a/Bugzilla/Config/General.pm b/Bugzilla/Config/General.pm
new file mode 100644
index 000000000..0f043548b
--- /dev/null
+++ b/Bugzilla/Config/General.pm
@@ -0,0 +1,83 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Dawn Endico <endico@mozilla.org>
+# Dan Mosedale <dmose@mozilla.org>
+# Joe Robins <jmrobins@tgix.com>
+# Jacob Steenhagen <jake@bugzilla.org>
+# J. Paul Reed <preed@sigkill.com>
+# Bradley Baetz <bbaetz@student.usyd.edu.au>
+# Joseph Heenan <joseph@heenan.me.uk>
+# Erik Stambaugh <erik@dasbistro.com>
+# Frédéric Buclin <LpSolit@gmail.com>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::Config::General;
+use strict;
+use Bugzilla::Config::Common;
+
+our $sortkey = 150;
+
+use constant get_param_list => (
+ {
+ name => 'maintainer',
+ type => 't',
+ no_reset => '1',
+ default => '',
+ checker => \&check_email
+ },
+
+ {
+ name => 'docs_urlbase',
+ type => 't',
+ default => 'docs/%lang%/html/',
+ checker => \&check_url
+ },
+
+ {
+ name => 'utf8',
+ type => 'b',
+ default => '0',
+ checker => \&check_utf8
+ },
+
+ {
+ name => 'shutdownhtml',
+ 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
+ },
+);
+
+1;
diff --git a/Bugzilla/Config/GroupSecurity.pm b/Bugzilla/Config/GroupSecurity.pm
new file mode 100644
index 000000000..f7f717379
--- /dev/null
+++ b/Bugzilla/Config/GroupSecurity.pm
@@ -0,0 +1,102 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Dawn Endico <endico@mozilla.org>
+# Dan Mosedale <dmose@mozilla.org>
+# Joe Robins <jmrobins@tgix.com>
+# Jacob Steenhagen <jake@bugzilla.org>
+# J. Paul Reed <preed@sigkill.com>
+# Bradley Baetz <bbaetz@student.usyd.edu.au>
+# Joseph Heenan <joseph@heenan.me.uk>
+# Erik Stambaugh <erik@dasbistro.com>
+# Frédéric Buclin <LpSolit@gmail.com>
+#
+
+package Bugzilla::Config::GroupSecurity;
+
+use strict;
+
+use Bugzilla::Config::Common;
+use Bugzilla::Group;
+
+our $sortkey = 900;
+
+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 => 'usevisibilitygroups',
+ type => 'b',
+ default => 0
+ },
+
+ {
+ name => 'strict_isolation',
+ type => 'b',
+ default => 0
+ } );
+ return @param_list;
+}
+
+sub _get_all_group_names {
+ my @group_names = map {$_->name} Bugzilla::Group->get_all;
+ unshift(@group_names, '');
+ return \@group_names;
+}
+1;
diff --git a/Bugzilla/Config/LDAP.pm b/Bugzilla/Config/LDAP.pm
new file mode 100644
index 000000000..e47f92308
--- /dev/null
+++ b/Bugzilla/Config/LDAP.pm
@@ -0,0 +1,87 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Dawn Endico <endico@mozilla.org>
+# Dan Mosedale <dmose@mozilla.org>
+# Joe Robins <jmrobins@tgix.com>
+# Jacob Steenhagen <jake@bugzilla.org>
+# J. Paul Reed <preed@sigkill.com>
+# Bradley Baetz <bbaetz@student.usyd.edu.au>
+# Joseph Heenan <joseph@heenan.me.uk>
+# Erik Stambaugh <erik@dasbistro.com>
+# Frédéric Buclin <LpSolit@gmail.com>
+#
+
+package Bugzilla::Config::LDAP;
+
+use strict;
+
+use Bugzilla::Config::Common;
+
+our $sortkey = 1000;
+
+sub get_param_list {
+ my $class = shift;
+ my @param_list = (
+ {
+ name => 'LDAPserver',
+ type => 't',
+ default => ''
+ },
+
+ {
+ name => 'LDAPstarttls',
+ type => 'b',
+ default => 0
+ },
+
+ {
+ name => 'LDAPbinddn',
+ type => 't',
+ default => ''
+ },
+
+ {
+ name => 'LDAPBaseDN',
+ type => 't',
+ default => ''
+ },
+
+ {
+ name => 'LDAPuidattribute',
+ type => 't',
+ default => 'uid'
+ },
+
+ {
+ name => 'LDAPmailattribute',
+ type => 't',
+ default => 'mail'
+ },
+
+ {
+ name => 'LDAPfilter',
+ type => 't',
+ default => '',
+ } );
+ return @param_list;
+}
+
+1;
diff --git a/Bugzilla/Config/MTA.pm b/Bugzilla/Config/MTA.pm
new file mode 100644
index 000000000..c90e5dc76
--- /dev/null
+++ b/Bugzilla/Config/MTA.pm
@@ -0,0 +1,102 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Dawn Endico <endico@mozilla.org>
+# Dan Mosedale <dmose@mozilla.org>
+# Joe Robins <jmrobins@tgix.com>
+# Jacob Steenhagen <jake@bugzilla.org>
+# J. Paul Reed <preed@sigkill.com>
+# Bradley Baetz <bbaetz@student.usyd.edu.au>
+# Joseph Heenan <joseph@heenan.me.uk>
+# Erik Stambaugh <erik@dasbistro.com>
+# Frédéric Buclin <LpSolit@gmail.com>
+#
+
+package Bugzilla::Config::MTA;
+
+use strict;
+
+use Bugzilla::Config::Common;
+use Email::Send;
+
+our $sortkey = 1200;
+
+sub get_param_list {
+ my $class = shift;
+ my @param_list = (
+ {
+ name => 'mail_delivery_method',
+ type => 's',
+ # Bugzilla is not ready yet to send mails to newsgroups, and 'IO'
+ # is of no use for now as we already have our own 'Test' mode.
+ choices => [grep {$_ ne 'NNTP' && $_ ne 'IO'} Email::Send->new()->all_mailers(), 'None'],
+ default => 'Sendmail',
+ checker => \&check_mail_delivery_method
+ },
+
+ {
+ name => 'mailfrom',
+ type => 't',
+ default => 'bugzilla-daemon'
+ },
+
+ {
+ name => 'use_mailer_queue',
+ type => 'b',
+ default => 0,
+ checker => \&check_theschwartz_available,
+ },
+
+ {
+ name => 'smtpserver',
+ type => 't',
+ default => 'localhost'
+ },
+ {
+ name => 'smtp_username',
+ type => 't',
+ default => '',
+ checker => \&check_smtp_auth
+ },
+ {
+ name => 'smtp_password',
+ type => 'p',
+ default => ''
+ },
+ {
+ name => 'smtp_debug',
+ type => 'b',
+ default => 0
+ },
+ {
+ name => 'whinedays',
+ type => 't',
+ default => 7,
+ checker => \&check_numeric
+ },
+ {
+ name => 'globalwatchers',
+ type => 't',
+ default => '',
+ }, );
+ return @param_list;
+}
+
+1;
diff --git a/Bugzilla/Config/PatchViewer.pm b/Bugzilla/Config/PatchViewer.pm
new file mode 100644
index 000000000..6bd9557a9
--- /dev/null
+++ b/Bugzilla/Config/PatchViewer.pm
@@ -0,0 +1,75 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Dawn Endico <endico@mozilla.org>
+# Dan Mosedale <dmose@mozilla.org>
+# Joe Robins <jmrobins@tgix.com>
+# Jacob Steenhagen <jake@bugzilla.org>
+# J. Paul Reed <preed@sigkill.com>
+# Bradley Baetz <bbaetz@student.usyd.edu.au>
+# Joseph Heenan <joseph@heenan.me.uk>
+# Erik Stambaugh <erik@dasbistro.com>
+# Frédéric Buclin <LpSolit@gmail.com>
+#
+
+package Bugzilla::Config::PatchViewer;
+
+use strict;
+
+use Bugzilla::Config::Common;
+
+our $sortkey = 1300;
+
+sub get_param_list {
+ my $class = shift;
+ my @param_list = (
+ {
+ name => 'cvsroot',
+ type => 't',
+ default => '',
+ },
+
+ {
+ name => 'cvsroot_get',
+ type => 't',
+ default => '',
+ },
+
+ {
+ name => 'bonsai_url',
+ type => 't',
+ default => ''
+ },
+
+ {
+ name => 'lxr_url',
+ type => 't',
+ default => ''
+ },
+
+ {
+ name => 'lxr_root',
+ type => 't',
+ default => '',
+ } );
+ return @param_list;
+}
+
+1;
diff --git a/Bugzilla/Config/Query.pm b/Bugzilla/Config/Query.pm
new file mode 100644
index 000000000..821f09fc6
--- /dev/null
+++ b/Bugzilla/Config/Query.pm
@@ -0,0 +1,80 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Dawn Endico <endico@mozilla.org>
+# Dan Mosedale <dmose@mozilla.org>
+# Joe Robins <jmrobins@tgix.com>
+# Jacob Steenhagen <jake@bugzilla.org>
+# J. Paul Reed <preed@sigkill.com>
+# Bradley Baetz <bbaetz@student.usyd.edu.au>
+# Joseph Heenan <joseph@heenan.me.uk>
+# Erik Stambaugh <erik@dasbistro.com>
+# Frédéric Buclin <LpSolit@gmail.com>
+#
+
+package Bugzilla::Config::Query;
+
+use strict;
+
+use Bugzilla::Config::Common;
+
+our $sortkey = 1400;
+
+sub get_param_list {
+ my $class = shift;
+ my @param_list = (
+ {
+ name => 'quip_list_entry_control',
+ type => 's',
+ choices => ['open', 'moderated', 'closed'],
+ default => 'open',
+ checker => \&check_multi
+ },
+
+ {
+ name => 'mostfreqthreshold',
+ type => 't',
+ default => '2',
+ checker => \&check_numeric
+ },
+
+ {
+ name => 'mybugstemplate',
+ type => 't',
+ default => 'buglist.cgi?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 => 'specific_search_allow_empty_words',
+ type => 'b',
+ default => 1
+ }
+
+ );
+ return @param_list;
+}
+
+1;
diff --git a/Bugzilla/Config/RADIUS.pm b/Bugzilla/Config/RADIUS.pm
new file mode 100644
index 000000000..bc072a9c4
--- /dev/null
+++ b/Bugzilla/Config/RADIUS.pm
@@ -0,0 +1,60 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Marc Schumann.
+# Portions created by Marc Schumann are Copyright (c) 2007 Marc Schumann.
+# All rights reserved.
+#
+# Contributor(s): Marc Schumann <wurblzap@gmail.com>
+#
+
+package Bugzilla::Config::RADIUS;
+
+use strict;
+
+use Bugzilla::Config::Common;
+
+our $sortkey = 1100;
+
+sub get_param_list {
+ my $class = shift;
+ my @param_list = (
+ {
+ name => 'RADIUS_server',
+ type => 't',
+ default => ''
+ },
+
+ {
+ name => 'RADIUS_secret',
+ type => 't',
+ default => ''
+ },
+
+ {
+ name => 'RADIUS_NAS_IP',
+ type => 't',
+ default => ''
+ },
+
+ {
+ name => 'RADIUS_email_suffix',
+ type => 't',
+ default => ''
+ },
+ );
+ return @param_list;
+}
+
+1;
diff --git a/Bugzilla/Config/ShadowDB.pm b/Bugzilla/Config/ShadowDB.pm
new file mode 100644
index 000000000..a605b2363
--- /dev/null
+++ b/Bugzilla/Config/ShadowDB.pm
@@ -0,0 +1,73 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Dawn Endico <endico@mozilla.org>
+# Dan Mosedale <dmose@mozilla.org>
+# Joe Robins <jmrobins@tgix.com>
+# Jacob Steenhagen <jake@bugzilla.org>
+# J. Paul Reed <preed@sigkill.com>
+# Bradley Baetz <bbaetz@student.usyd.edu.au>
+# Joseph Heenan <joseph@heenan.me.uk>
+# Erik Stambaugh <erik@dasbistro.com>
+# Frédéric Buclin <LpSolit@gmail.com>
+#
+
+package Bugzilla::Config::ShadowDB;
+
+use strict;
+
+use Bugzilla::Config::Common;
+
+our $sortkey = 1500;
+
+sub get_param_list {
+ my $class = shift;
+ my @param_list = (
+ {
+ name => 'shadowdbhost',
+ type => 't',
+ default => '',
+ },
+
+ {
+ name => 'shadowdbport',
+ type => 't',
+ default => '3306',
+ checker => \&check_numeric,
+ },
+
+ {
+ name => 'shadowdbsock',
+ type => 't',
+ default => '',
+ },
+
+ # This entry must be _after_ the shadowdb{host,port,sock} settings so that
+ # they can be used in the validation here
+ {
+ name => 'shadowdb',
+ type => 't',
+ default => '',
+ checker => \&check_shadowdb
+ } );
+ return @param_list;
+}
+
+1;
diff --git a/Bugzilla/Config/UserMatch.pm b/Bugzilla/Config/UserMatch.pm
new file mode 100644
index 000000000..f97cfeab2
--- /dev/null
+++ b/Bugzilla/Config/UserMatch.pm
@@ -0,0 +1,64 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Dawn Endico <endico@mozilla.org>
+# Dan Mosedale <dmose@mozilla.org>
+# Joe Robins <jmrobins@tgix.com>
+# Jacob Steenhagen <jake@bugzilla.org>
+# J. Paul Reed <preed@sigkill.com>
+# Bradley Baetz <bbaetz@student.usyd.edu.au>
+# Joseph Heenan <joseph@heenan.me.uk>
+# Erik Stambaugh <erik@dasbistro.com>
+# Frédéric Buclin <LpSolit@gmail.com>
+#
+
+package Bugzilla::Config::UserMatch;
+
+use strict;
+
+use Bugzilla::Config::Common;
+
+our $sortkey = 1600;
+
+sub get_param_list {
+ my $class = shift;
+ my @param_list = (
+ {
+ name => 'usemenuforusers',
+ type => 'b',
+ default => '0'
+ },
+
+ {
+ name => 'maxusermatches',
+ type => 't',
+ default => '1000',
+ checker => \&check_numeric
+ },
+
+ {
+ name => 'confirmuniqueusermatch',
+ type => 'b',
+ default => 1,
+ } );
+ return @param_list;
+}
+
+1;
diff --git a/Bugzilla/Constants.pm b/Bugzilla/Constants.pm
new file mode 100644
index 000000000..011d02b0a
--- /dev/null
+++ b/Bugzilla/Constants.pm
@@ -0,0 +1,624 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Dawn Endico <endico@mozilla.org>
+# Dan Mosedale <dmose@mozilla.org>
+# Joe Robins <jmrobins@tgix.com>
+# Jake <jake@bugzilla.org>
+# J. Paul Reed <preed@sigkill.com>
+# Bradley Baetz <bbaetz@student.usyd.edu.au>
+# Christopher Aillon <christopher@aillon.com>
+# Shane H. W. Travis <travis@sedsystems.ca>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+# Marc Schumann <wurblzap@gmail.com>
+
+package Bugzilla::Constants;
+use strict;
+use base qw(Exporter);
+
+# For bz_locations
+use File::Basename;
+
+@Bugzilla::Constants::EXPORT = qw(
+ BUGZILLA_VERSION
+
+ bz_locations
+
+ IS_NULL
+ NOT_NULL
+
+ CONTROLMAPNA
+ CONTROLMAPSHOWN
+ CONTROLMAPDEFAULT
+ CONTROLMAPMANDATORY
+
+ AUTH_OK
+ AUTH_NODATA
+ AUTH_ERROR
+ AUTH_LOGINFAILED
+ AUTH_DISABLED
+ AUTH_NO_SUCH_USER
+ 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
+
+ QUERY_LIST
+ LIST_OF_BUGS
+
+ SAVE_NUM_SEARCHES
+
+ COMMENT_COLS
+ MAX_COMMENT_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
+
+ 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_BUG_ID
+ FIELD_TYPE_BUG_URLS
+ FIELD_TYPE_KEYWORDS
+
+ 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
+
+ ERROR_MODE_WEBPAGE
+ ERROR_MODE_DIE
+ ERROR_MODE_DIE_SOAP_FAULT
+ ERROR_MODE_JSON_RPC
+ ERROR_MODE_TEST
+
+ COLOR_ERROR
+
+ 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
+ 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_FREETEXT_LENGTH
+ MAX_BUG_URL_LENGTH
+ MAX_POSSIBLE_DUPLICATES
+
+ PASSWORD_DIGEST_ALGORITHM
+ PASSWORD_SALT_LENGTH
+
+ CGI_URI_LIMIT
+
+ PRIVILEGES_REQUIRED_NONE
+ PRIVILEGES_REQUIRED_REPORTER
+ PRIVILEGES_REQUIRED_ASSIGNEE
+ PRIVILEGES_REQUIRED_EMPOWERED
+);
+
+@Bugzilla::Constants::EXPORT_OK = qw(contenttypes);
+
+# CONSTANTS
+#
+# Bugzilla version
+use constant BUGZILLA_VERSION => "4.0";
+
+# These are unique values that are unlikely to match a string or a number,
+# to be used in criteria for match() functions and other things. They start
+# and end with spaces because most Bugzilla stuff has trim() called on it,
+# so this is unlikely to match anything we get out of the DB.
+#
+# We can't use a reference, because Template Toolkit doesn't work with
+# them properly (constants.IS_NULL => {} just returns an empty string instead
+# of the reference).
+use constant IS_NULL => ' __IS_NULL__ ';
+use constant NOT_NULL => ' __NOT_NULL__ ';
+
+#
+# ControlMap constants for group_control_map.
+# membercontol:othercontrol => meaning
+# Na:Na => Bugs in this product may not be restricted to this
+# group.
+# Shown:Na => Members of the group may restrict bugs
+# in this product to this group.
+# Shown:Shown => Members of the group may restrict bugs
+# in this product to this group.
+# Anyone who can enter bugs in this product may initially
+# restrict bugs in this product to this group.
+# Shown:Mandatory => Members of the group may restrict bugs
+# in this product to this group.
+# Non-members who can enter bug in this product
+# will be forced to restrict it.
+# Default:Na => Members of the group may restrict bugs in this
+# product to this group and do so by default.
+# Default:Default => Members of the group may restrict bugs in this
+# product to this group and do so by default and
+# nonmembers have this option on entry.
+# Default:Mandatory => Members of the group may restrict bugs in this
+# product to this group and do so by default.
+# Non-members who can enter bug in this product
+# will be forced to restrict it.
+# Mandatory:Mandatory => Bug will be forced into this group regardless.
+# All other combinations are illegal.
+
+use constant CONTROLMAPNA => 0;
+use constant CONTROLMAPSHOWN => 1;
+use constant CONTROLMAPDEFAULT => 2;
+use constant CONTROLMAPMANDATORY => 3;
+
+# See Bugzilla::Auth for docs on AUTH_*, LOGIN_* and LOGOUT_*
+
+use constant AUTH_OK => 0;
+use constant AUTH_NODATA => 1;
+use constant AUTH_ERROR => 2;
+use constant AUTH_LOGINFAILED => 3;
+use constant AUTH_DISABLED => 4;
+use constant AUTH_NO_SUCH_USER => 5;
+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_REQUIRED => 2;
+
+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 MAILTO_USER => 0;
+use constant MAILTO_GROUP => 1;
+
+# The default list of columns for buglist.cgi
+use constant DEFAULT_COLUMN_LIST => (
+ "bug_severity", "priority", "op_sys","assigned_to",
+ "bug_status", "resolution", "short_desc"
+);
+
+# Used by query.cgi and buglist.cgi as the named-query name
+# for the default settings.
+use constant DEFAULT_QUERY_NAME => '(Default query)';
+
+# The default "defaultmilestone" created for products.
+use constant DEFAULT_MILESTONE => '---';
+
+# The possible types for saved searches.
+use constant QUERY_LIST => 0;
+use constant LIST_OF_BUGS => 1;
+
+# How many of the user's most recent searches to save.
+use constant SAVE_NUM_SEARCHES => 10;
+
+# The column length for displayed (and wrapped) bug comments.
+use constant COMMENT_COLS => 80;
+# Used in _check_comment(). Gives the max length allowed for a comment.
+use constant MAX_COMMENT_LENGTH => 65535;
+
+# The type of bug comments.
+use constant CMT_NORMAL => 0;
+use constant CMT_DUPE_OF => 1;
+use constant CMT_HAS_DUPE => 2;
+# 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;
+use constant CMT_ATTACHMENT_UPDATED => 6;
+
+# Determine whether a validation routine should return 0 or throw
+# an error when the validation fails.
+use constant THROW_ERROR => 1;
+
+use constant REL_ASSIGNEE => 0;
+use constant REL_QA => 1;
+use constant REL_REPORTER => 2;
+use constant REL_CC => 3;
+# REL 4 was REL_VOTER, before it was moved ino an extension.
+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"
+};
+
+# Used for global events like EVT_FLAG_REQUESTED
+use constant REL_ANY => 100;
+
+# There are two sorts of event - positive and negative. Positive events are
+# those for which the user says "I want mail if this happens." Negative events
+# are those for which the user says "I don't want mail if this happens."
+#
+# Exactly when each event fires is defined in wants_bug_mail() in User.pm; I'm
+# not commenting them here in case the comments and the code get out of sync.
+use constant EVT_OTHER => 0;
+use constant EVT_ADDED_REMOVED => 1;
+use constant EVT_COMMENT => 2;
+use constant EVT_ATTACHMENT => 3;
+use constant EVT_ATTACHMENT_DATA => 4;
+use constant EVT_PROJ_MANAGEMENT => 5;
+use constant EVT_OPENED_CLOSED => 6;
+use constant EVT_KEYWORD => 7;
+use constant EVT_CC => 8;
+use constant EVT_DEPEND_BLOCK => 9;
+use constant EVT_BUG_CREATED => 10;
+
+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;
+
+use constant EVT_UNCONFIRMED => 50;
+use constant EVT_CHANGED_BY_ME => 51;
+
+use constant NEG_EVENTS => EVT_UNCONFIRMED, EVT_CHANGED_BY_ME;
+
+# These are the "global" flags, which aren't tied to a particular relationship.
+# and so use REL_ANY.
+use constant EVT_FLAG_REQUESTED => 100; # Flag has been requested of me
+use constant EVT_REQUESTED_FLAG => 101; # I have requested a flag
+
+use constant GLOBAL_EVENTS => EVT_FLAG_REQUESTED, EVT_REQUESTED_FLAG;
+
+# Default administration group name.
+use constant ADMIN_GROUP_NAME => 'admin';
+
+# Privileges which can be per-product.
+use constant PER_PRODUCT_PRIVILEGES => ('editcomponents', 'editbugs', 'canconfirm');
+
+# Path to sendmail.exe (Windows only)
+use constant SENDMAIL_EXE => '/usr/lib/sendmail.exe';
+# Paths to search for the sendmail binary (non-Windows)
+use constant SENDMAIL_PATH => '/usr/lib:/usr/sbin:/usr/ucblib';
+
+# Field types. Match values in fielddefs.type column. These are purposely
+# not named after database column types, since Bugzilla fields comprise not
+# only storage but also logic. For example, we might add a "user" field type
+# whose values are stored in an integer column in the database but for which
+# we do more than we would do for a standard integer type (f.e. we might
+# display a user picker).
+
+use constant FIELD_TYPE_UNKNOWN => 0;
+use constant FIELD_TYPE_FREETEXT => 1;
+use constant FIELD_TYPE_SINGLE_SELECT => 2;
+use constant FIELD_TYPE_MULTI_SELECT => 3;
+use constant FIELD_TYPE_TEXTAREA => 4;
+use constant FIELD_TYPE_DATETIME => 5;
+use constant FIELD_TYPE_BUG_ID => 6;
+use constant FIELD_TYPE_BUG_URLS => 7;
+use constant FIELD_TYPE_KEYWORDS => 8;
+
+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 => qw(
+ classification
+ product
+ component
+);
+
+# 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 deadline);
+
+# 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;
+
+# The maximum number of seconds the Strict-Transport-Security header
+# will remain valid. Default is one week.
+use constant MAX_STS_AGE => 604800;
+
+# Protocols which are considered as safe.
+use constant SAFE_PROTOCOLS => ('afs', 'cid', 'ftp', 'gopher', 'http', 'https',
+ 'irc', 'mid', 'news', 'nntp', 'prospero', 'telnet',
+ 'view-source', 'wais');
+
+# 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" ,
+ "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;
+
+# Error modes. Default set by Bugzilla->usage_mode (so ERROR_MODE_WEBPAGE
+# usually). Use with Bugzilla->error_mode.
+use constant ERROR_MODE_WEBPAGE => 0;
+use constant ERROR_MODE_DIE => 1;
+use constant ERROR_MODE_DIE_SOAP_FAULT => 2;
+use constant ERROR_MODE_JSON_RPC => 3;
+use constant ERROR_MODE_TEST => 4;
+
+# The ANSI colors of messages that command-line scripts use
+use constant COLOR_ERROR => 'red';
+
+# The various modes that checksetup.pl can run in.
+use constant INSTALLATION_MODE_INTERACTIVE => 0;
+use constant INSTALLATION_MODE_NON_INTERACTIVE => 1;
+
+# Data about what we require for different databases.
+use constant DB_MODULE => {
+ 'mysql' => {db => 'Bugzilla::DB::Mysql', db_version => '4.1.2',
+ dbd => {
+ package => 'DBD-mysql',
+ module => 'DBD::mysql',
+ # Disallow development versions
+ blacklist => ['_'],
+ # For UTF-8 support
+ version => '4.00',
+ },
+ name => 'MySQL'},
+ # 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.00.0000',
+ dbd => {
+ package => 'DBD-Pg',
+ module => 'DBD::Pg',
+ version => '1.45',
+ },
+ name => 'PostgreSQL'},
+ 'oracle'=> {db => 'Bugzilla::DB::Oracle', db_version => '10.02.0',
+ dbd => {
+ package => 'DBD-Oracle',
+ module => 'DBD::Oracle',
+ version => '1.19',
+ },
+ name => 'Oracle'},
+};
+
+# True if we're on Win32.
+use constant ON_WINDOWS => ($^O =~ /MSWin32/i);
+# True if we're using ActiveState Perl (as opposed to Strawberry) on Windows.
+use constant ON_ACTIVESTATE => eval { &Win32::BuildNumber };
+
+# The user who should be considered "root" when we're giving
+# instructions to Bugzilla administrators.
+use constant ROOT_USER => ON_WINDOWS ? 'Administrator' : 'root';
+
+use constant MIN_SMALLINT => -32768;
+use constant MAX_SMALLINT => 32767;
+use constant MAX_INT_32 => 2147483647;
+
+# The longest that a saved search name can be.
+use constant MAX_LEN_QUERY_NAME => 64;
+
+# The longest classification name allowed.
+use constant MAX_CLASSIFICATION_SIZE => 64;
+
+# The longest product name allowed.
+use constant MAX_PRODUCT_SIZE => 64;
+
+# The longest milestone name allowed.
+use constant MAX_MILESTONE_SIZE => 20;
+
+# The longest component name allowed.
+use constant MAX_COMPONENT_SIZE => 64;
+
+# The maximum length for values of <select> fields.
+use constant MAX_FIELD_VALUE_SIZE => 64;
+
+# Maximum length allowed for free text fields.
+use constant MAX_FREETEXT_LENGTH => 255;
+
+# The longest a bug URL in a BUG_URLS field can be.
+use constant MAX_BUG_URL_LENGTH => 255;
+
+# The largest number of possible duplicates that Bug::possible_duplicates
+# will return.
+use constant MAX_POSSIBLE_DUPLICATES => 25;
+
+# This is the name of the algorithm used to hash passwords before storing
+# them in the database. This can be any string that is valid to pass to
+# Perl's "Digest" module. Note that if you change this, it won't take
+# effect until a user changes his password.
+use constant PASSWORD_DIGEST_ALGORITHM => 'SHA-256';
+# How long of a salt should we use? Note that if you change this, none
+# of your users will be able to log in until they reset their passwords.
+use constant PASSWORD_SALT_LENGTH => 8;
+
+# Certain scripts redirect to GET even if the form was submitted originally
+# 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.
+use constant CGI_URI_LIMIT => 8000;
+
+# If the user isn't allowed to change a field, we must tell him who can.
+# We store the required permission set into the $PrivilegesRequired
+# variable which gets passed to the error template.
+
+use constant PRIVILEGES_REQUIRED_NONE => 0;
+use constant PRIVILEGES_REQUIRED_REPORTER => 1;
+use constant PRIVILEGES_REQUIRED_ASSIGNEE => 2;
+use constant PRIVILEGES_REQUIRED_EMPOWERED => 3;
+
+sub bz_locations {
+ # We know that Bugzilla/Constants.pm must be in %INC at this point.
+ # So the only question is, what's the name of the directory
+ # above it? This is the most reliable way to get our current working
+ # directory under both mod_cgi and mod_perl. We call dirname twice
+ # to get the name of the directory above the "Bugzilla/" directory.
+ #
+ # Calling dirname twice like that won't work on VMS or AmigaOS
+ # but I doubt anybody runs Bugzilla on those.
+ #
+ # On mod_cgi this will be a relative path. On mod_perl it will be an
+ # absolute path.
+ my $libpath = dirname(dirname($INC{'Bugzilla/Constants.pm'}));
+ # We have to detaint $libpath, but we can't use Bugzilla::Util here.
+ $libpath =~ /(.*)/;
+ $libpath = $1;
+
+ my ($project, $localconfig, $datadir);
+ if ($ENV{'PROJECT'} && $ENV{'PROJECT'} =~ /^(\w+)$/) {
+ $project = $1;
+ $localconfig = "localconfig.$project";
+ $datadir = "data/$project";
+ } else {
+ $localconfig = "localconfig";
+ $datadir = "data";
+ }
+
+ # We have to return absolute paths for mod_perl.
+ # That means that if you modify these paths, they must be absolute paths.
+ return {
+ 'libpath' => $libpath,
+ 'ext_libpath' => "$libpath/lib",
+ # If you put the libraries in a different location than the CGIs,
+ # make sure this still points to the CGIs.
+ 'cgi_path' => $libpath,
+ 'templatedir' => "$libpath/template",
+ 'project' => $project,
+ 'localconfig' => "$libpath/$localconfig",
+ 'datadir' => "$libpath/$datadir",
+ 'attachdir' => "$libpath/$datadir/attachments",
+ 'skinsdir' => "$libpath/skins",
+ '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' => "$libpath/$datadir/webdot",
+ 'extensionsdir' => "$libpath/extensions",
+ };
+}
+
+1;
diff --git a/Bugzilla/DB.pm b/Bugzilla/DB.pm
new file mode 100644
index 000000000..cda668b91
--- /dev/null
+++ b/Bugzilla/DB.pm
@@ -0,0 +1,2553 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Dan Mosedale <dmose@mozilla.org>
+# Jacob Steenhagen <jake@bugzilla.org>
+# Bradley Baetz <bbaetz@student.usyd.edu.au>
+# Christopher Aillon <christopher@aillon.com>
+# Tomas Kopal <Tomas.Kopal@altap.cz>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+# Lance Larsh <lance.larsh@oracle.com>
+
+package Bugzilla::DB;
+
+use strict;
+
+use DBI;
+
+# Inherit the DB class from DBI::db.
+use base qw(DBI::db);
+
+use Bugzilla::Constants;
+use Bugzilla::Install::Requirements;
+use Bugzilla::Install::Util qw(vers_cmp);
+use Bugzilla::Install::Localconfig;
+use Bugzilla::Util;
+use Bugzilla::Error;
+use Bugzilla::DB::Schema;
+
+use List::Util qw(max);
+use Storable qw(dclone);
+
+#####################################################################
+# Constants
+#####################################################################
+
+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
+# are no longer stored in localconfig. If we are upgrading from a
+# Bugzilla with enums to a Bugzilla without enums, we use the
+# enum values.
+#
+# The values that you see here are ONLY DEFAULTS. They are only used
+# the FIRST time you run checksetup.pl, IF you are NOT upgrading from a
+# 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"],
+};
+
+# The character that means "OR" in a boolean fulltext search. If empty,
+# the database doesn't support OR searches in fulltext searches.
+# Used by Bugzilla::Bug::possible_duplicates.
+use constant FULLTEXT_OR => '';
+
+#####################################################################
+# Connection Methods
+#####################################################################
+
+sub connect_shadow {
+ 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'};
+
+ return _connect(\%connect_params);
+}
+
+sub connect_main {
+ my $lc = Bugzilla->localconfig;
+ return _connect(Bugzilla->localconfig);
+}
+
+sub _connect {
+ my ($params) = @_;
+
+ 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: " . $@);
+
+ # instantiate the correct DB specific module
+ my $dbh = $pkg_module->new($params);
+
+ return $dbh;
+}
+
+sub _handle_error {
+ 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
+}
+
+sub bz_check_requirements {
+ my ($output) = @_;
+
+ 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'};
+ }
+
+ # 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;
+ }
+
+ # 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;
+}
+
+sub _bz_check_dbd {
+ 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;
+
+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):
+
+ $command
+
+EOT
+ }
+}
+
+sub bz_check_server_version {
+ my ($self, $db, $output) = @_;
+
+ 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_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;
+
+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;
+}
+
+# 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();
+ print "Creating database $db_name...\n";
+
+ # 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;
+}
+
+# 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;
+}
+
+# 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};
+
+return <<EOT;
+This might have several reasons:
+
+* $server is not running.
+* $server is running, but there is a problem either in the
+ server configuration or the database access rights. Read the Bugzilla
+ Guide in the doc directory. The section about database configuration
+ should help.
+* Your password for the '$lc->{db_user}' user, specified in \$db_pass, is
+ incorrect, in '$lc_file'.
+* There is a subtle problem with Perl, DBI, or $server. Make
+ sure all settings in '$lc_file' are correct. If all else fails, set
+ '\$db_check' to 0.
+
+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_interval 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");
+ }
+ }
+
+ # 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 ||= "=";
+
+ return $self->sql_istring($left) . " $op " . $self->sql_istring($right);
+}
+
+sub sql_istring {
+ my ($self, $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);
+}
+
+sub sql_position {
+ my ($self, $fragment, $text) = @_;
+
+ return "POSITION($fragment IN $text)";
+}
+
+sub sql_group_by {
+ my ($self, $needed_columns, $optional_columns) = @_;
+
+ my $expression = "GROUP BY $needed_columns";
+ $expression .= ", " . $optional_columns if $optional_columns;
+
+ return $expression;
+}
+
+sub sql_string_concat {
+ my ($self, @params) = @_;
+
+ return '(' . join(' || ', @params) . ')';
+}
+
+sub sql_string_until {
+ my ($self, $string, $substring) = @_;
+ return "SUBSTRING($string FROM 1 FOR " .
+ $self->sql_position($substring, $string) . " - 1)";
+}
+
+sub sql_in {
+ my ($self, $column_name, $in_list_ref) = @_;
+ return " $column_name 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 "CASE WHEN (" . join(" AND ", @words) . ") THEN 1 ELSE 0 END";
+}
+
+#####################################################################
+# General Info Methods
+#####################################################################
+
+# XXX - Needs to be documented.
+sub bz_server_version {
+ my ($self) = @_;
+ return $self->get_info(18); # SQL_DBMS_VER
+}
+
+sub bz_last_key {
+ my ($self, $table, $column) = @_;
+
+ return $self->last_insert_id(Bugzilla->localconfig->{db_name}, undef,
+ $table, $column);
+}
+
+sub bz_check_regexp {
+ my ($self, $pattern) = @_;
+
+ eval { $self->do("SELECT " . $self->sql_regexp($self->quote("a"), $pattern, 1)) };
+
+ $@ && ThrowUserError('illegal_regexp',
+ { value => $pattern, dberror => $self->errstr });
+}
+
+#####################################################################
+# Database Setup
+#####################################################################
+
+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();
+
+ my @desired_tables = $self->_bz_schema->get_table_list();
+
+ foreach my $table_name (@desired_tables) {
+ $self->bz_add_table($table_name);
+ }
+}
+
+# This really just exists to get overridden in Bugzilla::DB::Mysql.
+sub bz_enum_initial_values {
+ return ENUM_DEFAULTS;
+}
+
+sub bz_populate_enum_tables {
+ my ($self) = @_;
+
+ my $enum_values = $self->bz_enum_initial_values();
+ while (my ($table, $values) = each %$enum_values) {
+ $self->_bz_populate_enum_table($table, $values);
+ }
+}
+
+sub bz_setup_foreign_keys {
+ my ($self) = @_;
+
+ # We use _bz_schema because bz_add_table has removed all REFERENCES
+ # items from _bz_real_schema.
+ my @tables = $self->_bz_schema->get_table_list();
+ foreach my $table (@tables) {
+ my @columns = $self->_bz_schema->get_table_columns($table);
+ my %add_fks;
+ foreach my $column (@columns) {
+ my $def = $self->_bz_schema->get_column_abstract($table, $column);
+ if ($def->{REFERENCES}) {
+ $add_fks{$column} = $def->{REFERENCES};
+ }
+ }
+ $self->bz_add_fks($table, \%add_fks);
+ }
+}
+
+# This is used by contrib/bzdbcopy.pl, mostly.
+sub bz_drop_foreign_keys {
+ my ($self) = @_;
+
+ my @tables = $self->_bz_real_schema->get_table_list();
+ foreach my $table (@tables) {
+ my @columns = $self->_bz_real_schema->get_table_columns($table);
+ foreach my $column (@columns) {
+ $self->bz_drop_fk($table, $column);
+ }
+ }
+}
+
+#####################################################################
+# Schema Modification Methods
+#####################################################################
+
+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 $current_def = $self->bz_column_info($table, $name);
+
+ if (!$current_def) {
+ my @statements = $self->_bz_real_schema->get_add_column_ddl(
+ $table, $name, $new_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);
+ }
+ $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 });
+}
+
+sub bz_add_fks {
+ my ($self, $table, $column_fks) = @_;
+
+ my %add_these;
+ foreach my $column (keys %$column_fks) {
+ my $col_def = $self->bz_column_info($table, $column);
+ next if $col_def->{REFERENCES};
+ my $fk = $column_fks->{$column};
+ $self->_check_references($table, $column, $fk);
+ $add_these{$column} = $fk;
+ print get_text('install_fk_add',
+ { table => $table, column => $column, fk => $fk })
+ . "\n" if Bugzilla->usage_mode == USAGE_MODE_CMDLINE;
+ }
+
+ return if !scalar(keys %add_these);
+
+ my @sql = $self->_bz_real_schema->get_add_fks_sql($table, \%add_these);
+ $self->do($_) foreach @sql;
+
+ foreach my $column (keys %add_these) {
+ my $col_def = $self->bz_column_info($table, $column);
+ $col_def->{REFERENCES} = $add_these{$column};
+ $self->_bz_real_schema->set_column($table, $column, $col_def);
+ }
+
+ $self->_bz_store_real_schema();
+}
+
+sub bz_alter_column {
+ my ($self, $table, $name, $new_def, $set_nulls_to) = @_;
+
+ 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 (defined $current_def->{REFERENCES}) {
+ # Make sure we don't modify the caller's $new_def.
+ $new_def = dclone($new_def);
+ $new_def->{REFERENCES} = $current_def->{REFERENCES};
+ }
+ $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;
+ }
+}
+
+
+# bz_alter_column_raw($table, $name, $new_def, $current_def)
+#
+# Description: A helper function for bz_alter_column.
+# Alters a column in the database
+# without updating any Schema object. Generally
+# should only be called by bz_alter_column.
+# Used when either: (1) You don't yet have a Schema
+# object but you need to alter a column, for some reason.
+# (2) You need to alter a column for some database-specific
+# reason.
+# Params: $table - The name of the table the column is on.
+# $name - The name of the column you're changing.
+# $new_def - The abstract definition that you are changing
+# this column to.
+# $current_def - (optional) The current definition of the
+# column. Will be used in the output message,
+# if given.
+# $set_nulls_to - The same as the param of the same name
+# from 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);
+ print "Updating column $name in table $table ...\n";
+ if (defined $current_def) {
+ my $old_ddl = $self->_bz_schema->get_type_ddl($current_def);
+ print "Old: $old_ddl\n";
+ }
+ print "New: $new_ddl\n";
+ $self->do($_) foreach (@statements);
+}
+
+sub bz_add_index {
+ my ($self, $table, $name, $definition) = @_;
+
+ 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;
+ }
+}
+
+# bz_add_index_raw($table, $name, $silent)
+#
+# Description: A helper function for bz_add_index.
+# Adds an index to the database
+# without updating any Schema object. Generally
+# should only be called by bz_add_index.
+# Used when you don't yet have a Schema
+# object but you need to add an index, for some reason.
+# Params: $table - The name of the table the index is on.
+# $name - The name of the index you're adding.
+# $definition - The abstract index definition, in hashref
+# or arrayref format.
+# $silent - (optional) If specified and true, don't output
+# any message about this change.
+# 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);
+}
+
+sub bz_add_table {
+ my ($self, $name) = @_;
+
+ my $table_exists = $self->bz_table_info($name);
+
+ if (!$table_exists) {
+ $self->_bz_add_table_raw($name);
+ 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.
+ delete $fields{$col}->{REFERENCES};
+ }
+ $self->_bz_real_schema->add_table($name, $table_def);
+ $self->_bz_store_real_schema;
+ }
+}
+
+# _bz_add_table_raw($name) - Private
+#
+# Description: A helper function for bz_add_table.
+# Creates a table in the database without
+# updating any Schema object. Generally
+# should only be called by bz_add_table and by
+# _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
+# _bz_schema.
+# Returns: nothing
+#
+sub _bz_add_table_raw {
+ my ($self, $name) = @_;
+ my @statements = $self->_bz_schema->get_table_ddl($name);
+ print "Adding new table $name ...\n"
+ if Bugzilla->usage_mode == USAGE_MODE_CMDLINE;
+ $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);
+}
+
+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'} });
+ }
+}
+
+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);
+}
+
+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;
+ }
+}
+
+sub bz_drop_fk {
+ my ($self, $table, $column) = @_;
+
+ my $col_def = $self->bz_column_info($table, $column);
+ if ($col_def && exists $col_def->{REFERENCES}) {
+ my $def = $col_def->{REFERENCES};
+ print get_text('install_fk_drop',
+ { table => $table, column => $column, fk => $def })
+ . "\n" if Bugzilla->usage_mode == USAGE_MODE_CMDLINE;
+ my @statements =
+ $self->_bz_real_schema->get_drop_fk_sql($table,$column,$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: $@";
+ }
+ delete $col_def->{REFERENCES};
+ $self->_bz_real_schema->set_column($table, $column, $col_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 $def = $self->bz_column_info($check_table, $check_column);
+ my $fk = $def->{REFERENCES};
+ 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;
+}
+
+sub bz_drop_index {
+ my ($self, $table, $name) = @_;
+
+ my $index_exists = $self->bz_index_info($table, $name);
+
+ if ($index_exists) {
+ $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)
+#
+# Description: A helper function for 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
+# 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.
+# Params: $table - The name of the table the index is on.
+# $name - The name of the index you're dropping.
+# $silent - (optional) If specified and true, don't output
+# any message about this change.
+# 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: $@";
+ }
+}
+
+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;
+ }
+}
+
+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;
+}
+
+sub bz_rename_column {
+ my ($self, $table, $old_name, $new_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);
+
+ 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;
+ }
+}
+
+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;
+ 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;
+}
+
+#####################################################################
+# Schema Information Methods
+#####################################################################
+
+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};
+}
+
+# _bz_get_initial_schema()
+#
+# Description: A protected method, intended for use only by Bugzilla::DB
+# and subclasses. Used to get the initial Schema that will
+# be written to disk for _bz_init_schema_storage. You probably
+# want to use _bz_schema or _bz_real_schema instead of this
+# method.
+# Params: none
+# 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();
+}
+
+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;
+}
+
+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;
+}
+
+sub bz_table_info {
+ 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);
+}
+
+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;
+}
+
+#####################################################################
+# Protected "Real Database" Schema Information Methods
+#####################################################################
+
+# Only Bugzilla::DB and subclasses should use these methods.
+# If you need a method that does the same thing as one of these
+# methods, use the version without _real on the end.
+
+# bz_table_columns_real($table)
+#
+# Description: Returns a list of columns on a given table
+# as the table actually is, on the disk.
+# Params: $table - Name of the table.
+# 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]}) };
+}
+
+# bz_table_list_real()
+#
+# Description: Gets a list of tables in the current
+# database, directly from the disk.
+# 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] })};
+}
+
+#####################################################################
+# Transaction Methods
+#####################################################################
+
+sub bz_in_transaction {
+ 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.
+ $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;
+ } 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;
+ }
+}
+
+#####################################################################
+# Subclass Helpers
+#####################################################################
+
+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,
+ FetchHashKeyName => 'NAME',
+ # Note: NAME_lc causes crash on ActiveState Perl
+ # 5.8.4 (see Bug 253696)
+ # XXX - This will likely cause problems in DB
+ # back ends that twiddle column case (Oracle?)
+ };
+
+ 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";
+
+ # RaiseError was only set to 0 so that we could catch the
+ # above "die" condition.
+ $self->{RaiseError} = 1;
+
+ bless ($self, $class);
+
+ return $self;
+}
+
+#####################################################################
+# Private Methods
+#####################################################################
+
+=begin private
+
+=head1 PRIVATE METHODS
+
+These methods really are private. Do not override them in subclasses.
+
+=over 4
+
+=item C<_init_bz_schema_storage>
+
+ Description: Initializes the bz_schema table if it contains nothing.
+ Params: none
+ Returns: nothing
+
+=cut
+
+sub _bz_init_schema_storage {
+ 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');
+ }
+
+ print "Initializing the new Schema storage...\n";
+ 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).";
+ }
+}
+
+=item C<_bz_real_schema()>
+
+ Description: Returns a Schema object representing the database
+ that is being used in the current installation.
+ Params: none
+ Returns: A C<Bugzilla::DB::Schema> object representing the database
+ as it exists on the disk.
+
+=cut
+
+sub _bz_real_schema {
+ my ($self) = @_;
+ return $self->{private_real_schema} if exists $self->{private_real_schema};
+
+ my ($data, $version) = $self->selectrow_array(
+ "SELECT schema_data, version FROM bz_schema");
+
+ (die "_bz_real_schema tried to read the bz_schema table but it's empty!")
+ if !$data;
+
+ $self->{private_real_schema} =
+ $self->_bz_schema->deserialize_abstract($data, $version);
+
+ return $self->{private_real_schema};
+}
+
+=item C<_bz_store_real_schema()>
+
+ Description: Stores the _bz_real_schema structures in the database
+ for later recovery. Call this function whenever you make
+ a change to the _bz_real_schema.
+ Params: none
+ Returns: nothing
+
+ Precondition: $self->{_bz_real_schema} must exist.
+
+=back
+
+=end private
+
+=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();
+}
+
+# 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) {
+ my $insert = $self->prepare(
+ "INSERT INTO $sql_table (value,sortkey) VALUES (?,?)");
+ print "Inserting values into the '$table' table:\n";
+ my $sortorder = 0;
+ my $maxlen = max(map(length($_), @$valuelist)) + 2;
+ foreach my $value (@$valuelist) {
+ $sortorder += 100;
+ printf "%-${maxlen}s sortkey: $sortorder\n", "'$value'";
+ $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
+ 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
+ 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";
+ }
+ }
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::DB - Database access routines, using L<DBI>
+
+=head1 SYNOPSIS
+
+ # Obtain db handle
+ use Bugzilla::DB;
+ my $dbh = Bugzilla->dbh;
+
+ # prepare a query using DB methods
+ my $sth = $dbh->prepare("SELECT " .
+ $dbh->sql_date_format("creation_ts", "%Y%m%d") .
+ " FROM bugs WHERE bug_status != 'RESOLVED' " .
+ $dbh->sql_limit(1));
+
+ # Execute the query
+ $sth->execute;
+
+ # Get the results
+ my @result = $sth->fetchrow_array;
+
+ # Schema Modification
+ $dbh->bz_add_column($table, $name, \%definition, $init_value);
+ $dbh->bz_add_index($table, $name, $definition);
+ $dbh->bz_add_table($name);
+ $dbh->bz_drop_index($table, $name);
+ $dbh->bz_drop_table($name);
+ $dbh->bz_alter_column($table, $name, \%new_def, $set_nulls_to);
+ $dbh->bz_drop_column($table, $column);
+ $dbh->bz_rename_column($table, $old_name, $new_name);
+
+ # Schema Information
+ my $column = $dbh->bz_column_info($table, $column);
+ my $index = $dbh->bz_index_info($table, $index);
+
+=head1 DESCRIPTION
+
+Functions in this module allows creation of a database handle to connect
+to the Bugzilla database. This should never be done directly; all users
+should use the L<Bugzilla> module to access the current C<dbh> instead.
+
+This module also contains methods extending the returned handle with
+functionality which is different between databases allowing for easy
+customization for particular database via inheritance. These methods
+should be always preffered over hard-coding SQL commands.
+
+=head1 CONSTANTS
+
+Subclasses of Bugzilla::DB are required to define certain constants. These
+constants are required to be subroutines or "use constant" variables.
+
+=over
+
+=item C<BLOB_TYPE>
+
+The C<\%attr> argument that must be passed to bind_param in order to
+correctly escape a C<LONGBLOB> type.
+
+=item C<ISOLATION_LEVEL>
+
+The argument that this database should send to
+C<SET TRANSACTION ISOLATION LEVEL> when starting a transaction. If you
+override this in a subclass, the isolation level you choose should
+be as strict as or more strict than the default isolation level defined in
+L<Bugzilla::DB>.
+
+=back
+
+
+=head1 CONNECTION
+
+A new database handle to the required database can be created using this
+module. This is normally done by the L<Bugzilla> module, and so these routines
+should not be called from anywhere else.
+
+=head2 Functions
+
+=over
+
+=item C<connect_main>
+
+=over
+
+=item B<Description>
+
+Function to connect to the main database, returning a new database handle.
+
+=item B<Params>
+
+=over
+
+=item C<$no_db_name> (optional) - If true, connect to the database
+server, but don't connect to a specific database. This is only used
+when creating a database. After you create the database, you should
+re-create a new Bugzilla::DB object without using this parameter.
+
+=back
+
+=item B<Returns>
+
+New instance of the DB class
+
+=back
+
+=item C<connect_shadow>
+
+=over
+
+=item B<Description>
+
+Function to connect to the shadow database, returning a new database handle.
+This routine C<die>s if no shadow database is configured.
+
+=item B<Params> (none)
+
+=item B<Returns>
+
+A new instance of the DB class
+
+=back
+
+=item C<bz_check_requirements>
+
+=over
+
+=item B<Description>
+
+Checks to make sure that you have the correct DBD and database version
+installed for the database that Bugzilla will be using. Prints a message
+and exits if you don't pass the requirements.
+
+If C<$db_check> is false (from F<localconfig>), we won't check the
+database version.
+
+=item B<Params>
+
+=over
+
+=item C<$output> - C<true> if the function should display informational
+output about what it's doing, such as versions found.
+
+=back
+
+=item B<Returns> (nothing)
+
+=back
+
+
+=item C<bz_create_database>
+
+=over
+
+=item B<Description>
+
+Creates an empty database with the name C<$db_name>, if that database
+doesn't already exist. Prints an error message and exits if we can't
+create the database.
+
+=item B<Params> (none)
+
+=item B<Returns> (nothing)
+
+=back
+
+=item C<_connect>
+
+=over
+
+=item B<Description>
+
+Internal function, creates and returns a new, connected instance of the
+correct DB class. This routine C<die>s if no driver is specified.
+
+=item B<Params>
+
+=over
+
+=item C<$driver> - name of the database driver to use
+
+=item C<$host> - host running the database we are connecting to
+
+=item C<$dbname> - name of the database to connect to
+
+=item C<$port> - port the database is listening on
+
+=item C<$sock> - socket the database is listening on
+
+=item C<$user> - username used to log in to the database
+
+=item C<$pass> - password used to log in to the database
+
+=back
+
+=item B<Returns>
+
+A new instance of the DB class
+
+=back
+
+=item C<_handle_error>
+
+Function passed to the DBI::connect call for error handling. It shortens the
+error for printing.
+
+=item C<import>
+
+Overrides the standard import method to check that derived class
+implements all required abstract methods. Also calls original implementation
+in its super class.
+
+=back
+
+=head1 ABSTRACT METHODS
+
+Note: Methods which can be implemented generically for all DBs are implemented in
+this module. If needed, they can be overridden with DB specific code.
+Methods which do not have standard implementation are abstract and must
+be implemented for all supported databases separately.
+To avoid confusion with standard DBI methods, all methods returning string with
+formatted SQL command have prefix C<sql_>. All other methods have prefix C<bz_>.
+
+=head2 Constructor
+
+=over
+
+=item C<new>
+
+=over
+
+=item B<Description>
+
+Constructor. Abstract method, should be overridden by database specific
+code.
+
+=item B<Params>
+
+=over
+
+=item C<$user> - username used to log in to the database
+
+=item C<$pass> - password used to log in to the database
+
+=item C<$host> - host running the database we are connecting to
+
+=item C<$dbname> - name of the database to connect to
+
+=item C<$port> - port the database is listening on
+
+=item C<$sock> - socket the database is listening on
+
+=back
+
+=item B<Returns>
+
+A new instance of the DB class
+
+=item B<Note>
+
+The constructor should create a DSN from the parameters provided and
+then call C<db_new()> method of its super class to create a new
+class instance. See L<db_new> description in this module. As per
+DBI documentation, all class variables must be prefixed with
+"private_". See L<DBI>.
+
+=back
+
+=back
+
+=head2 SQL Generation
+
+=over
+
+=item C<sql_regexp>
+
+=over
+
+=item B<Description>
+
+Outputs SQL regular expression operator for POSIX regex
+searches (case insensitive) in format suitable for a given
+database.
+
+Abstract method, should be overridden by database specific code.
+
+=item B<Params>
+
+=over
+
+=item C<$expr> - SQL expression for the text to be searched (scalar)
+
+=item C<$pattern> - the regular expression to search for (scalar)
+
+=item C<$nocheck> - true if the pattern should not be tested; false otherwise (boolean)
+
+=item C<$real_pattern> - the real regular expression to search for.
+This argument is used when C<$pattern> is a placeholder ('?').
+
+=back
+
+=item B<Returns>
+
+Formatted SQL for regular expression search (e.g. REGEXP) (scalar)
+
+=back
+
+=item C<sql_not_regexp>
+
+=over
+
+=item B<Description>
+
+Outputs SQL regular expression operator for negative POSIX
+regex searches (case insensitive) in format suitable for a given
+database.
+
+Abstract method, should be overridden by database specific code.
+
+=item B<Params>
+
+Same as L</sql_regexp>.
+
+=item B<Returns>
+
+Formatted SQL for negative regular expression search (e.g. NOT REGEXP)
+(scalar)
+
+=back
+
+=item C<sql_limit>
+
+=over
+
+=item B<Description>
+
+Returns SQL syntax for limiting results to some number of rows
+with optional offset if not starting from the begining.
+
+Abstract method, should be overridden by database specific code.
+
+=item B<Params>
+
+=over
+
+=item C<$limit> - number of rows to return from query (scalar)
+
+=item C<$offset> - number of rows to skip before counting (scalar)
+
+=back
+
+=item B<Returns>
+
+Formatted SQL for limiting number of rows returned from query
+with optional offset (e.g. LIMIT 1, 1) (scalar)
+
+=back
+
+=item C<sql_from_days>
+
+=over
+
+=item B<Description>
+
+Outputs SQL syntax for converting Julian days to date.
+
+Abstract method, should be overridden by database specific code.
+
+=item B<Params>
+
+=over
+
+=item C<$days> - days to convert to date
+
+=back
+
+=item B<Returns>
+
+Formatted SQL for returning Julian days in dates. (scalar)
+
+=back
+
+=item C<sql_to_days>
+
+=over
+
+=item B<Description>
+
+Outputs SQL syntax for converting date to Julian days.
+
+Abstract method, should be overridden by database specific code.
+
+=item B<Params>
+
+=over
+
+=item C<$date> - date to convert to days
+
+=back
+
+=item B<Returns>
+
+Formatted SQL for returning date fields in Julian days. (scalar)
+
+=back
+
+=item C<sql_date_format>
+
+=over
+
+=item B<Description>
+
+Outputs SQL syntax for formatting dates.
+
+Abstract method, should be overridden by database specific code.
+
+=item B<Params>
+
+=over
+
+=item C<$date> - date or name of date type column (scalar)
+
+=item C<$format> - format string for date output (scalar)
+(C<%Y> = year, four digits, C<%y> = year, two digits, C<%m> = month,
+C<%d> = day, C<%a> = weekday name, 3 letters, C<%H> = hour 00-23,
+C<%i> = minute, C<%s> = second)
+
+=back
+
+=item B<Returns>
+
+Formatted SQL for date formatting (scalar)
+
+=back
+
+=item C<sql_interval>
+
+=over
+
+=item B<Description>
+
+Outputs proper SQL syntax for a time interval function.
+
+Abstract method, should be overridden by database specific code.
+
+=item B<Params>
+
+=over
+
+=item C<$interval> - the time interval requested (e.g. '30') (integer)
+
+=item C<$units> - the units the interval is in (e.g. 'MINUTE') (string)
+
+=back
+
+=item B<Returns>
+
+Formatted SQL for interval function (scalar)
+
+=back
+
+=item C<sql_position>
+
+=over
+
+=item B<Description>
+
+Outputs proper SQL syntax determining position of a substring
+(fragment) withing a string (text). Note: if the substring or
+text are string constants, they must be properly quoted (e.g. "'pattern'").
+
+It searches for the string in a case-sensitive manner. If you want to do
+a case-insensitive search, use L</sql_iposition>.
+
+=item B<Params>
+
+=over
+
+=item C<$fragment> - the string fragment we are searching for (scalar)
+
+=item C<$text> - the text to search (scalar)
+
+=back
+
+=item B<Returns>
+
+Formatted SQL for substring search (scalar)
+
+=back
+
+=item C<sql_iposition>
+
+Just like L</sql_position>, but case-insensitive.
+
+=item C<sql_group_by>
+
+=over
+
+=item B<Description>
+
+Outputs proper SQL syntax for grouping the result of a query.
+
+For ANSI SQL databases, we need to group by all columns we are
+querying for (except for columns used in aggregate functions).
+Some databases require (or even allow) to specify only one
+or few columns if the result is uniquely defined. For those
+databases, the default implementation needs to be overloaded.
+
+=item B<Params>
+
+=over
+
+=item C<$needed_columns> - string with comma separated list of columns
+we need to group by to get expected result (scalar)
+
+=item C<$optional_columns> - string with comma separated list of all
+other columns we are querying for, but which are not in the required list.
+
+=back
+
+=item B<Returns>
+
+Formatted SQL for row grouping (scalar)
+
+=back
+
+=item C<sql_string_concat>
+
+=over
+
+=item B<Description>
+
+Returns SQL syntax for concatenating multiple strings (constants
+or values from table columns) together.
+
+=item B<Params>
+
+=over
+
+=item C<@params> - array of column names or strings to concatenate
+
+=back
+
+=item B<Returns>
+
+Formatted SQL for concatenating specified strings
+
+=back
+
+=item C<sql_string_until>
+
+=over
+
+=item B<Description>
+
+Returns SQL for truncating a string at the first occurrence of a certain
+substring.
+
+=item B<Params>
+
+Note that both parameters need to be sql-quoted.
+
+=item C<$string> The string we're truncating
+
+=item C<$substring> The substring we're truncating at.
+
+=back
+
+=item C<sql_fulltext_search>
+
+=over
+
+=item B<Description>
+
+Returns SQL syntax for performing a full text search for specified text
+on a given column.
+
+There is a ANSI SQL version of this method implemented using LIKE operator,
+but it's not a real full text search. DB specific modules should override
+this, as this generic implementation will be always much slower. This
+generic implementation returns 'relevance' as 0 for no match, or 1 for a
+match.
+
+=item B<Params>
+
+=over
+
+=item C<$column> - name of column to search (scalar)
+
+=item C<$text> - text to search for (scalar)
+
+=back
+
+=item B<Returns>
+
+Formatted SQL for full text search
+
+=back
+
+=item C<sql_istrcmp>
+
+=over
+
+=item B<Description>
+
+Returns SQL for a case-insensitive string comparison.
+
+=item B<Params>
+
+=over
+
+=item C<$left> - What should be on the left-hand-side of the operation.
+
+=item C<$right> - What should be on the right-hand-side of the operation.
+
+=item C<$op> (optional) - What the operation is. Should be a valid ANSI
+SQL comparison operator, such as C<=>, C<E<lt>>, C<LIKE>, etc. Defaults
+to C<=> if not specified.
+
+=back
+
+=item B<Returns>
+
+A SQL statement that will run the comparison in a case-insensitive fashion.
+
+=item B<Note>
+
+Uses L</sql_istring>, so it has the same performance concerns.
+Try to avoid using this function unless absolutely necessary.
+
+Subclass Implementors: Override sql_istring instead of this
+function, most of the time (this function uses sql_istring).
+
+=back
+
+=item C<sql_istring>
+
+=over
+
+=item B<Description>
+
+Returns SQL syntax "preparing" a string or text column for case-insensitive
+comparison.
+
+=item B<Params>
+
+=over
+
+=item C<$string> - string to convert (scalar)
+
+=back
+
+=item B<Returns>
+
+Formatted SQL making the string case insensitive.
+
+=item B<Note>
+
+The default implementation simply calls LOWER on the parameter.
+If this is used to search on a text column with index, the index
+will not be usually used unless it was created as LOWER(column).
+
+=back
+
+=item C<sql_in>
+
+=over
+
+=item B<Description>
+
+Returns SQL syntax for the C<IN ()> operator.
+
+Only necessary where an C<IN> clause can have more than 1000 items.
+
+=item B<Params>
+
+=over
+
+=item C<$column_name> - Column name (e.g. C<bug_id>)
+
+=item C<$in_list_ref> - an arrayref containing values for C<IN ()>
+
+=back
+
+=item B<Returns>
+
+Formatted SQL for the C<IN> operator.
+
+=back
+
+=back
+
+
+=head1 IMPLEMENTED METHODS
+
+These methods are implemented in Bugzilla::DB, and only need
+to be implemented in subclasses if you need to override them for
+database-compatibility reasons.
+
+=head2 General Information Methods
+
+These methods return information about data in the database.
+
+=over
+
+=item C<bz_last_key>
+
+=over
+
+=item B<Description>
+
+Returns the last serial number, usually from a previous INSERT.
+
+Must be executed directly following the relevant INSERT.
+This base implementation uses L<DBI/last_insert_id>. If the
+DBD supports it, it is the preffered way to obtain the last
+serial index. If it is not supported, the DB-specific code
+needs to override this function.
+
+=item B<Params>
+
+=over
+
+=item C<$table> - name of table containing serial column (scalar)
+
+=item C<$column> - name of column containing serial data type (scalar)
+
+=back
+
+=item B<Returns>
+
+Last inserted ID (scalar)
+
+=back
+
+=back
+
+=head2 Database Setup Methods
+
+These methods are used by the Bugzilla installation programs to set up
+the database.
+
+=over
+
+=item C<bz_populate_enum_tables>
+
+=over
+
+=item B<Description>
+
+For an upgrade or an initial installation, populates the tables that hold
+the legal values for the old "enum" fields: C<bug_severity>,
+C<resolution>, etc. Prints out information if it inserts anything into the
+DB.
+
+=item B<Params> (none)
+
+=item B<Returns> (nothing)
+
+=back
+
+=back
+
+
+=head2 Schema Modification Methods
+
+These methods modify the current Bugzilla Schema.
+
+Where a parameter says "Abstract index/column definition", it returns/takes
+information in the formats defined for indexes and columns in
+C<Bugzilla::DB::Schema::ABSTRACT_SCHEMA>.
+
+=over
+
+=item C<bz_add_column>
+
+=over
+
+=item B<Description>
+
+Adds a new column to a table in the database. Prints out a brief statement
+that it did so, to stdout. Note that you cannot add a NOT NULL column that
+has no default -- the database won't know what to set all the NULL
+values to.
+
+=item B<Params>
+
+=over
+
+=item C<$table> - the table where the column is being added
+
+=item C<$name> - the name of the new column
+
+=item C<\%definition> - Abstract column definition for the new column
+
+=item C<$init_value> (optional) - An initial value to set the column
+to. Required if your column is NOT NULL and has no DEFAULT set.
+
+=back
+
+=item B<Returns> (nothing)
+
+=back
+
+=item C<bz_add_index>
+
+=over
+
+=item B<Description>
+
+Adds a new index to a table in the database. Prints out a brief statement
+that it did so, to stdout. If the index already exists, we will do nothing.
+
+=item B<Params>
+
+=over
+
+=item C<$table> - The table the new index is on.
+
+=item C<$name> - A name for the new index.
+
+=item C<$definition> - An abstract index definition. Either a hashref
+or an arrayref.
+
+=back
+
+=item B<Returns> (nothing)
+
+=back
+
+=item C<bz_add_table>
+
+=over
+
+=item B<Description>
+
+Creates a new table in the database, based on the definition for that
+table in the abstract schema.
+
+Note that unlike the other 'add' functions, this does not take a
+definition, but always creates the table as it exists in
+L<Bugzilla::DB::Schema/ABSTRACT_SCHEMA>.
+
+If a table with that name already exists, then this function returns
+silently.
+
+=item B<Params>
+
+=over
+
+=item C<$name> - The name of the table you want to create.
+
+=back
+
+=item B<Returns> (nothing)
+
+=back
+
+=item C<bz_drop_index>
+
+=over
+
+=item B<Description>
+
+Removes an index from the database. Prints out a brief statement that it
+did so, to stdout. If the index doesn't exist, we do nothing.
+
+=item B<Params>
+
+=over
+
+=item C<$table> - The table that the index is on.
+
+=item C<$name> - The name of the index that you want to drop.
+
+=back
+
+=item B<Returns> (nothing)
+
+=back
+
+=item C<bz_drop_table>
+
+=over
+
+=item B<Description>
+
+Drops a table from the database. If the table doesn't exist, we just
+return silently.
+
+=item B<Params>
+
+=over
+
+=item C<$name> - The name of the table to drop.
+
+=back
+
+=item B<Returns> (nothing)
+
+=back
+
+=item C<bz_alter_column>
+
+=over
+
+=item B<Description>
+
+Changes the data type of a column in a table. Prints out the changes
+being made to stdout. If the new type is the same as the old type,
+the function returns without changing anything.
+
+=item B<Params>
+
+=over
+
+=item C<$table> - the table where the column is
+
+=item C<$name> - the name of the column you want to change
+
+=item C<\%new_def> - An abstract column definition for the new
+data type of the columm
+
+=item C<$set_nulls_to> (Optional) - If you are changing the column
+to be NOT NULL, you probably also want to set any existing NULL columns
+to a particular value. Specify that value here. B<NOTE>: The value should
+not already be SQL-quoted.
+
+=back
+
+=item B<Returns> (nothing)
+
+=back
+
+=item C<bz_drop_column>
+
+=over
+
+=item B<Description>
+
+Removes a column from a database table. If the column doesn't exist, we
+return without doing anything. If we do anything, we print a short
+message to C<stdout> about the change.
+
+=item B<Params>
+
+=over
+
+=item C<$table> - The table where the column is
+
+=item C<$column> - The name of the column you want to drop
+
+=back
+
+=item B<Returns> (nothing)
+
+=back
+
+=item C<bz_rename_column>
+
+=over
+
+=item B<Description>
+
+Renames a column in a database table. If the C<$old_name> column
+doesn't exist, we return without doing anything. If C<$old_name>
+and C<$new_name> both already exist in the table specified, we fail.
+
+=item B<Params>
+
+=over
+
+=item C<$table> - The name of the table containing the column
+that you want to rename
+
+=item C<$old_name> - The current name of the column that you want to rename
+
+=item C<$new_name> - The new name of the column
+
+=back
+
+=item B<Returns> (nothing)
+
+=back
+
+=item C<bz_rename_table>
+
+=over
+
+=item B<Description>
+
+Renames a table in the database. Does nothing if the table doesn't exist.
+
+Throws an error if the old table exists and there is already a table
+with the new name.
+
+=item B<Params>
+
+=over
+
+=item C<$old_name> - The current name of the table.
+
+=item C<$new_name> - What you're renaming the table to.
+
+=back
+
+=item B<Returns> (nothing)
+
+=back
+
+=back
+
+=head2 Schema Information Methods
+
+These methods return information about the current Bugzilla database
+schema, as it currently exists on the disk.
+
+Where a parameter says "Abstract index/column definition", it returns/takes
+information in the formats defined for indexes and columns for
+L<Bugzilla::DB::Schema/ABSTRACT_SCHEMA>.
+
+=over
+
+=item C<bz_column_info>
+
+=over
+
+=item B<Description>
+
+Get abstract column definition.
+
+=item B<Params>
+
+=over
+
+=item C<$table> - The name of the table the column is in.
+
+=item C<$column> - The name of the column.
+
+=back
+
+=item B<Returns>
+
+An abstract column definition for that column. If the table or column
+does not exist, we return C<undef>.
+
+=back
+
+=item C<bz_index_info>
+
+=over
+
+=item B<Description>
+
+Get abstract index definition.
+
+=item B<Params>
+
+=over
+
+=item C<$table> - The table the index is on.
+
+=item C<$index> - The name of the index.
+
+=back
+
+=item B<Returns>
+
+An abstract index definition for that index, always in hashref format.
+The hashref will always contain the C<TYPE> element, but it will
+be an empty string if it's just a normal index.
+
+If the index does not exist, we return C<undef>.
+
+=back
+
+=back
+
+
+=head2 Transaction Methods
+
+These methods deal with the starting and stopping of transactions
+in the database.
+
+=over
+
+=item C<bz_in_transaction>
+
+Returns C<1> if we are currently in the middle of an uncommitted transaction,
+C<0> otherwise.
+
+=item C<bz_start_transaction>
+
+Starts a transaction.
+
+It is OK to call C<bz_start_transaction> when you are already inside of
+a transaction. However, you must call L</bz_commit_transaction> as many
+times as you called C<bz_start_transaction>, in order for your transaction
+to actually commit.
+
+Bugzilla uses C<REPEATABLE READ> transactions.
+
+Returns nothing and takes no parameters.
+
+=item C<bz_commit_transaction>
+
+Ends a transaction, commiting all changes. Returns nothing and takes
+no parameters.
+
+=item C<bz_rollback_transaction>
+
+Ends a transaction, rolling back all changes. Returns nothing and takes
+no parameters.
+
+=back
+
+
+=head1 SUBCLASS HELPERS
+
+Methods in this class are intended to be used by subclasses to help them
+with their functions.
+
+=over
+
+=item C<db_new>
+
+=over
+
+=item B<Description>
+
+Constructor
+
+=item B<Params>
+
+=over
+
+=item C<$dsn> - database connection string
+
+=item C<$user> - username used to log in to the database
+
+=item C<$pass> - password used to log in to the database
+
+=item C<\%override_attrs> - set of attributes for DB connection (optional).
+You only have to set attributes that you want to be different from
+the default attributes set inside of C<db_new>.
+
+=back
+
+=item B<Returns>
+
+A new instance of the DB class
+
+=item B<Note>
+
+The name of this constructor is not C<new>, as that would make
+our check for implementation of C<new> by derived class useless.
+
+=back
+
+=back
+
+
+=head1 SEE ALSO
+
+L<DBI>
+
+L<Bugzilla::Constants/DB_MODULE>
diff --git a/Bugzilla/DB/Mysql.pm b/Bugzilla/DB/Mysql.pm
new file mode 100644
index 000000000..ffdaf32c0
--- /dev/null
+++ b/Bugzilla/DB/Mysql.pm
@@ -0,0 +1,1106 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Dave Miller <davem00@aol.com>
+# Gayathri Swaminath <gayathrik00@aol.com>
+# Jeroen Ruigrok van der Werven <asmodai@wxs.nl>
+# Dave Lawrence <dkl@redhat.com>
+# Tomas Kopal <Tomas.Kopal@altap.cz>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+# Lance Larsh <lance.larsh@oracle.com>
+
+=head1 NAME
+
+Bugzilla::DB::Mysql - Bugzilla database compatibility layer for MySQL
+
+=head1 DESCRIPTION
+
+This module overrides methods of the Bugzilla::DB module with MySQL specific
+implementation. It is instantiated by the Bugzilla::DB module and should never
+be used directly.
+
+For interface details see L<Bugzilla::DB> and L<DBI>.
+
+=cut
+
+package Bugzilla::DB::Mysql;
+use strict;
+use base qw(Bugzilla::DB);
+
+use Bugzilla::Constants;
+use Bugzilla::Install::Util qw(install_string);
+use Bugzilla::Util;
+use Bugzilla::Error;
+use Bugzilla::DB::Schema::Mysql;
+
+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
+# 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,
+ );
+
+ 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);
+
+ # Bug 321645 - disable MySQL strict mode, if set
+ my ($var, $sql_mode) = $self->selectrow_array(
+ "SHOW VARIABLES LIKE 'sql\\_mode'");
+
+ if ($sql_mode) {
+ # STRICT_TRANS_TABLE or STRICT_ALL_TABLES enable MySQL strict mode,
+ # causing bug 321645. TRADITIONAL sets these modes (among others) as
+ # well, so it has to be stipped as well
+ my $new_sql_mode =
+ join(",", grep {$_ !~ /^STRICT_(?:TRANS|ALL)_TABLES|TRADITIONAL$/}
+ split(/,/, $sql_mode));
+
+ if ($sql_mode ne $new_sql_mode) {
+ $self->do("SET SESSION sql_mode = ?", undef, $new_sql_mode);
+ }
+ }
+
+ # Allow large GROUP_CONCATs (largely for inserting comments
+ # into bugs_fulltext).
+ $self->do('SET SESSION group_concat_max_len = 128000000');
+
+ return $self;
+}
+
+# when last_insert_id() is supported on MySQL by lowest DBI/DBD version
+# required by Bugzilla, this implementation can be removed.
+sub bz_last_key {
+ my ($self) = @_;
+
+ my ($last_insert_id) = $self->selectrow_array('SELECT LAST_INSERT_ID()');
+
+ return $last_insert_id;
+}
+
+sub sql_group_concat {
+ my ($self, $column, $separator, $sort) = @_;
+ $separator = $self->quote(', ') if !defined $separator;
+ $sort = 1 if !defined $sort;
+ if ($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;
+
+ $self->bz_check_regexp($real_pattern) if !$nocheck;
+
+ return "$expr REGEXP $pattern";
+}
+
+sub sql_not_regexp {
+ my ($self, $expr, $pattern, $nocheck, $real_pattern) = @_;
+ $real_pattern ||= $pattern;
+
+ $self->bz_check_regexp($real_pattern) if !$nocheck;
+
+ return "$expr NOT REGEXP $pattern";
+}
+
+sub sql_limit {
+ my ($self, $limit, $offset) = @_;
+
+ if (defined($offset)) {
+ return "LIMIT $offset, $limit";
+ } else {
+ return "LIMIT $limit";
+ }
+}
+
+sub sql_string_concat {
+ my ($self, @params) = @_;
+
+ return 'CONCAT(' . join(', ', @params) . ')';
+}
+
+sub sql_fulltext_search {
+ my ($self, $column, $text) = @_;
+
+ # Add the boolean mode modifier if the search string contains
+ # boolean operators at the start or end of a word.
+ my $mode = '';
+ if ($text =~ /(?:^|\W)[+\-<>~"()]/ || $text =~ /[()"*](?:$|\W)/) {
+ $mode = 'IN BOOLEAN MODE';
+
+ # quote un-quoted compound words
+ my @words = quotewords('[\s()]+', 'delimiters', $text);
+ 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('', @words);
+ }
+
+ # quote the text for use in the MATCH AGAINST expression
+ $text = $self->quote($text);
+
+ # untaint the text, since it's safe to use now that we've quoted it
+ trick_taint($text);
+
+ return "MATCH($column) AGAINST($text $mode)";
+}
+
+sub sql_istring {
+ my ($self, $string) = @_;
+
+ return $string;
+}
+
+sub sql_from_days {
+ my ($self, $days) = @_;
+
+ return "FROM_DAYS($days)";
+}
+
+sub sql_to_days {
+ my ($self, $date) = @_;
+
+ return "TO_DAYS($date)";
+}
+
+sub sql_date_format {
+ my ($self, $date, $format) = @_;
+
+ $format = "%Y.%m.%d %H:%i:%s" if !$format;
+
+ return "DATE_FORMAT($date, " . $self->quote($format) . ")";
+}
+
+sub sql_interval {
+ my ($self, $interval, $units) = @_;
+
+ return "INTERVAL $interval $units";
+}
+
+sub sql_iposition {
+ my ($self, $fragment, $text) = @_;
+ return "INSTR($text, $fragment)";
+}
+
+sub sql_position {
+ my ($self, $fragment, $text) = @_;
+
+ return "INSTR(CAST($text AS BINARY), CAST($fragment AS BINARY))";
+}
+
+sub sql_group_by {
+ my ($self, $needed_columns, $optional_columns) = @_;
+
+ # MySQL allows you to specify the minimal subset of columns to get
+ # a unique result. While it does allow specifying all columns as
+ # ANSI SQL requires, according to MySQL documentation, the fewer
+ # columns you specify, the faster the query runs.
+ return "GROUP BY $needed_columns";
+}
+
+sub bz_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);
+}
+
+sub _bz_get_initial_schema {
+ my ($self) = @_;
+ return $self->_bz_build_schema_from_disk();
+}
+
+#####################################################################
+# Database Setup
+#####################################################################
+
+sub bz_check_server_version {
+ 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";
+ }
+
+ $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";
+ }
+
+ # Make sure the installation has InnoDB turned on, or we're going to be
+ # doing silly things like making foreign keys on MyISAM tables, which is
+ # hard to fix later. We do this up here because none of the code below
+ # works if InnoDB is off. (Particularly if we've already converted the
+ # tables to InnoDB.)
+ my ($innodb_on) = @{$self->selectcol_arrayref(
+ q{SHOW VARIABLES LIKE '%have_innodb%'}, {Columns=>[2]})};
+ if ($innodb_on ne 'YES') {
+ die <<EOT;
+InnoDB is disabled in your MySQL installation.
+Bugzilla requires InnoDB to be enabled.
+Please enable it and then re-run checksetup.pl.
+
+EOT
+ }
+
+
+ my %table_status = @{ $self->selectcol_arrayref("SHOW TABLE STATUS",
+ {Columns=>[1,2]}) };
+ my @isam_tables;
+ foreach my $name (keys %table_status) {
+ push(@isam_tables, $name) if (defined($table_status{$name}) && $table_status{$name} eq "ISAM");
+ }
+
+ if(scalar(@isam_tables)) {
+ print "One or more of the tables in your existing MySQL database are\n"
+ . "of type ISAM. ISAM tables are deprecated in MySQL 3.23 and\n"
+ . "don't support more than 16 indexes per table, which \n"
+ . "Bugzilla needs.\n Converting your ISAM tables to type"
+ . " MyISAM:\n\n";
+ foreach my $table (@isam_tables) {
+ print "Converting table $table... ";
+ $self->do("ALTER TABLE $table TYPE = MYISAM");
+ print "done.\n";
+ }
+ print "\nISAM->MyISAM table conversion done.\n\n";
+ }
+
+ my ($sd_index_deleted, $longdescs_index_deleted);
+ my @tables = $self->bz_table_list_real();
+ # We want to convert tables to InnoDB, but it's possible that they have
+ # fulltext indexes on them, and conversion will fail unless we remove
+ # the indexes.
+ if (grep($_ eq 'bugs', @tables)
+ 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 ($self->bz_index_info_real('longdescs', 'longdescs_thetext_idx')) {
+ $self->bz_drop_index_raw('longdescs', 'longdescs_thetext_idx');
+ $longdescs_index_deleted = 1; # For later schema cleanup.
+ }
+ }
+
+ # Upgrade tables from MyISAM to InnoDB
+ my @myisam_tables;
+ foreach my $name (keys %table_status) {
+ if (defined($table_status{$name})
+ && $table_status{$name} =~ /^MYISAM$/i
+ && !grep($_ eq $name, Bugzilla::DB::Schema::Mysql::MYISAM_TABLES))
+ {
+ push(@myisam_tables, $name) ;
+ }
+ }
+ if (scalar @myisam_tables) {
+ print "Bugzilla now uses the InnoDB storage engine in MySQL for",
+ " most tables.\nConverting tables to InnoDB:\n";
+ foreach my $table (@myisam_tables) {
+ print "Converting table $table... ";
+ $self->do("ALTER TABLE $table ENGINE = InnoDB");
+ print "done.\n";
+ }
+ }
+
+ $self->_after_table_status(\@tables);
+
+ # Versions of Bugzilla before the existence of Bugzilla::DB::Schema did
+ # not provide explicit names for the table indexes. This means
+ # that our upgrades will not be reliable, because we look for the name
+ # of the index, not what fields it is on, when doing upgrades.
+ # (using the name is much better for cross-database compatibility
+ # and general reliability). It's also very important that our
+ # Schema object be consistent with what is on the disk.
+ #
+ # While we're at it, we also fix some inconsistent index naming
+ # from the original checkin of Bugzilla::DB::Schema.
+
+ # We check for the existence of a particular "short name" index that
+ # has existed at least since Bugzilla 2.8, and probably earlier.
+ # For fixing the inconsistent naming of Schema indexes,
+ # we also check for one of those inconsistently-named indexes.
+ if (grep($_ eq 'bugs', @tables)
+ && ($self->bz_index_info_real('bugs', 'assigned_to')
+ || $self->bz_index_info_real('flags', 'flags_bidattid_idx')) )
+ {
+
+ # This is a check unrelated to the indexes, to see if people are
+ # upgrading from 2.18 or below, but somehow have a bz_schema table
+ # already. This only happens if they have done a mysqldump into
+ # a database without doing a DROP DATABASE first.
+ # We just do the check here since this check is a reliable way
+ # of telling that we are upgrading from a version pre-2.20.
+ if (grep($_ eq 'bz_schema', $self->bz_table_list_real())) {
+ die("\nYou are upgrading from a version before 2.20, but the"
+ . " bz_schema\ntable already exists. This means that you"
+ . " restored a mysqldump into\nthe Bugzilla database without"
+ . " first dropping the already-existing\nBugzilla database,"
+ . " at some point. Whenever you restore a Bugzilla\ndatabase"
+ . " backup, you must always drop the entire database first.\n\n"
+ . "Please drop your Bugzilla database and restore it from a"
+ . " backup that\ndoes not contain the bz_schema table. If for"
+ . " some reason you cannot\ndo this, you can connect to your"
+ . " MySQL database and drop the bz_schema\ntable, as a last"
+ . " resort.\n");
+ }
+
+ my $bug_count = $self->selectrow_array("SELECT COUNT(*) FROM bugs");
+ # We estimate one minute for each 3000 bugs, plus 3 minutes just
+ # to handle basic MySQL stuff.
+ my $rename_time = int($bug_count / 3000) + 3;
+ # And 45 minutes for every 15,000 attachments, per some experiments.
+ my ($attachment_count) =
+ $self->selectrow_array("SELECT COUNT(*) FROM attachments");
+ $rename_time += int(($attachment_count * 45) / 15000);
+ # If we're going to take longer than 5 minutes, we let the user know
+ # and allow them to abort.
+ if ($rename_time > 5) {
+ print "\nWe are about to rename old indexes.\n"
+ . "The estimated time to complete renaming is "
+ . "$rename_time minutes.\n"
+ . "You cannot interrupt this action once it has begun.\n"
+ . "If you would like to cancel, press Ctrl-C now..."
+ . " (Waiting 45 seconds...)\n\n";
+ # Wait 45 seconds for them to respond.
+ sleep(45) unless Bugzilla->installation_answers->{NO_PAUSE};
+ }
+ print "Renaming indexes...\n";
+
+ # We can't be interrupted, because of how the "if"
+ # works above.
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ # Certain indexes had names in Schema that did not easily conform
+ # to a standard. We store those names here, so that they
+ # can be properly renamed.
+ # Also, sometimes an old mysqldump would incorrectly rename
+ # unique indexes to "PRIMARY", so we address that here, also.
+ my $bad_names = {
+ # 'when' is a possible leftover from Bugzillas before 2.8
+ bugs_activity => ['when', 'bugs_activity_bugid_idx',
+ 'bugs_activity_bugwhen_idx'],
+ cc => ['PRIMARY'],
+ longdescs => ['longdescs_bugid_idx',
+ 'longdescs_bugwhen_idx'],
+ flags => ['flags_bidattid_idx'],
+ flaginclusions => ['flaginclusions_tpcid_idx'],
+ flagexclusions => ['flagexclusions_tpc_id_idx'],
+ keywords => ['PRIMARY'],
+ milestones => ['PRIMARY'],
+ profiles_activity => ['profiles_activity_when_idx'],
+ group_control_map => ['group_control_map_gid_idx', 'PRIMARY'],
+ user_group_map => ['PRIMARY'],
+ group_group_map => ['PRIMARY'],
+ email_setting => ['PRIMARY'],
+ bug_group_map => ['PRIMARY'],
+ category_group_map => ['PRIMARY'],
+ watch => ['PRIMARY'],
+ namedqueries => ['PRIMARY'],
+ series_data => ['PRIMARY'],
+ # series_categories is dealt with below, not here.
+ };
+
+ # The series table is broken and needs to have one index
+ # dropped before we begin the renaming, because it had a
+ # useless index on it that would cause a naming conflict here.
+ if (grep($_ eq 'series', @tables)) {
+ my $dropname;
+ # This is what the bad index was called before Schema.
+ if ($self->bz_index_info_real('series', 'creator_2')) {
+ $dropname = 'creator_2';
+ }
+ # This is what the bad index is called in Schema.
+ elsif ($self->bz_index_info_real('series', 'series_creator_idx')) {
+ $dropname = 'series_creator_idx';
+ }
+ $self->bz_drop_index_raw('series', $dropname) if $dropname;
+ }
+
+ # The email_setting table also had the same problem.
+ if( grep($_ eq 'email_setting', @tables)
+ && $self->bz_index_info_real('email_setting',
+ 'email_settings_user_id_idx') )
+ {
+ $self->bz_drop_index_raw('email_setting',
+ 'email_settings_user_id_idx');
+ }
+
+ # Go through all the tables.
+ foreach my $table (@tables) {
+ # Will contain the names of old indexes as keys, and the
+ # definition of the new indexes as a value. The values
+ # include an extra hash key, NAME, with the new name of
+ # the index.
+ my %rename_indexes;
+ # And go through all the columns on each table.
+ my @columns = $self->bz_table_columns_real($table);
+
+ # We also want to fix the silly naming of unique indexes
+ # that happened when we first checked-in Bugzilla::DB::Schema.
+ if ($table eq 'series_categories') {
+ # The series_categories index had a nonstandard name.
+ push(@columns, 'series_cats_unique_idx');
+ }
+ elsif ($table eq 'email_setting') {
+ # The email_setting table had a similar problem.
+ push(@columns, 'email_settings_unique_idx');
+ }
+ else {
+ push(@columns, "${table}_unique_idx");
+ }
+ # And this is how we fix the other inconsistent Schema naming.
+ push(@columns, @{$bad_names->{$table}})
+ if (exists $bad_names->{$table});
+ foreach my $column (@columns) {
+ # If we have an index named after this column, it's an
+ # old-style-name index.
+ if (my $index = $self->bz_index_info_real($table, $column)) {
+ # Fix the name to fit in with the new naming scheme.
+ $index->{NAME} = $table . "_" .
+ $index->{FIELDS}->[0] . "_idx";
+ print "Renaming index $column to "
+ . $index->{NAME} . "...\n";
+ $rename_indexes{$column} = $index;
+ } # if
+ } # foreach column
+
+ my @rename_sql = $self->_bz_schema->get_rename_indexes_ddl(
+ $table, %rename_indexes);
+ $self->do($_) foreach (@rename_sql);
+
+ } # foreach table
+ } # if old-name indexes
+
+ # If there are no tables, but the DB isn't utf8 and it should be,
+ # then we should alter the database to be utf8. We know it should be
+ # if the utf8 parameter is true or there are no params at all.
+ # This kind of situation happens when people create the database
+ # themselves, and if we don't do this they will get the big
+ # scary WARNING statement about conversion to UTF8.
+ if ( !$self->bz_db_is_utf8 && !@tables
+ && (Bugzilla->params->{'utf8'} || !scalar keys %{Bugzilla->params}) )
+ {
+ $self->_alter_db_charset_to_utf8();
+ }
+
+ # And now we create the tables and the Schema object.
+ $self->SUPER::bz_setup_database();
+
+ if ($sd_index_deleted) {
+ $self->_bz_real_schema->delete_index('bugs', 'bugs_short_desc_idx');
+ $self->_bz_store_real_schema;
+ }
+ if ($longdescs_index_deleted) {
+ $self->_bz_real_schema->delete_index('longdescs',
+ 'longdescs_thetext_idx');
+ $self->_bz_store_real_schema;
+ }
+
+ # The old timestamp fields need to be adjusted here instead of in
+ # checksetup. Otherwise the UPDATE statements inside of bz_add_column
+ # will cause accidental timestamp updates.
+ # The code that does this was moved here from checksetup.
+
+ # 2002-08-14 - bbaetz@student.usyd.edu.au - bug 153578
+ # attachments creation time needs to be a datetime, not a timestamp
+ my $attach_creation =
+ $self->bz_column_info("attachments", "creation_ts");
+ if ($attach_creation && $attach_creation->{TYPE} =~ /^TIMESTAMP/i) {
+ print "Fixing creation time on attachments...\n";
+
+ my $sth = $self->prepare("SELECT COUNT(attach_id) FROM attachments");
+ $sth->execute();
+ my ($attach_count) = $sth->fetchrow_array();
+
+ if ($attach_count > 1000) {
+ print "This may take a while...\n";
+ }
+ my $i = 0;
+
+ # This isn't just as simple as changing the field type, because
+ # the creation_ts was previously updated when an attachment was made
+ # obsolete from the attachment creation screen. So we have to go
+ # and recreate these times from the comments..
+ $sth = $self->prepare("SELECT bug_id, attach_id, submitter_id " .
+ "FROM attachments");
+ $sth->execute();
+
+ # Restrict this as much as possible in order to avoid false
+ # positives, and keep the db search time down
+ my $sth2 = $self->prepare("SELECT bug_when FROM longdescs
+ WHERE bug_id=? AND who=?
+ AND thetext LIKE ?
+ ORDER BY bug_when " . $self->sql_limit(1));
+ while (my ($bug_id, $attach_id, $submitter_id)
+ = $sth->fetchrow_array())
+ {
+ $sth2->execute($bug_id, $submitter_id,
+ "Created an attachment (id=$attach_id)%");
+ my ($when) = $sth2->fetchrow_array();
+ if ($when) {
+ $self->do("UPDATE attachments " .
+ "SET creation_ts='$when' " .
+ "WHERE attach_id=$attach_id");
+ } else {
+ print "Warning - could not determine correct creation"
+ . " time for attachment $attach_id on bug $bug_id\n";
+ }
+ ++$i;
+ print "Converted $i of $attach_count attachments\n" if !($i % 1000);
+ }
+ print "Done - converted $i attachments\n";
+
+ $self->bz_alter_column("attachments", "creation_ts",
+ {TYPE => 'DATETIME', NOTNULL => 1});
+ }
+
+ # 2004-08-29 - Tomas.Kopal@altap.cz, bug 257303
+ # Change logincookies.lastused type from timestamp to datetime
+ my $login_lastused = $self->bz_column_info("logincookies", "lastused");
+ if ($login_lastused && $login_lastused->{TYPE} =~ /^TIMESTAMP/i) {
+ $self->bz_alter_column('logincookies', 'lastused',
+ { TYPE => 'DATETIME', NOTNULL => 1});
+ }
+
+ # 2005-01-17 - Tomas.Kopal@altap.cz, bug 257315
+ # Change bugs.delta_ts type from timestamp to datetime
+ my $bugs_deltats = $self->bz_column_info("bugs", "delta_ts");
+ if ($bugs_deltats && $bugs_deltats->{TYPE} =~ /^TIMESTAMP/i) {
+ $self->bz_alter_column('bugs', 'delta_ts',
+ {TYPE => 'DATETIME', NOTNULL => 1});
+ }
+
+ # 2005-09-24 - bugreport@peshkin.net, bug 307602
+ # Make sure that default 4G table limit is overridden
+ my $row = $self->selectrow_hashref("SHOW TABLE STATUS LIKE 'attach_data'");
+ if ($$row{'Create_options'} !~ /MAX_ROWS/i) {
+ print "Converting attach_data maximum size to 100G...\n";
+ $self->do("ALTER TABLE attach_data
+ AVG_ROW_LENGTH=1000000,
+ MAX_ROWS=100000");
+ }
+
+ # Convert the database to UTF-8 if the utf8 parameter is on.
+ # We check if any table isn't utf8, because lots of crazy
+ # partial-conversion situations can happen, and this handles anything
+ # that could come up (including having the DB charset be utf8 but not
+ # the table charsets.
+ my $utf_table_status =
+ $self->selectall_arrayref("SHOW TABLE STATUS", {Slice=>{}});
+ $self->_after_table_status([map($_->{Name}, @$utf_table_status)]);
+ my @non_utf8_tables = grep(defined($_->{Collation}) && $_->{Collation} !~ /^utf8/, @$utf_table_status);
+
+ if (Bugzilla->params->{'utf8'} && scalar @non_utf8_tables) {
+ print <<EOT;
+
+WARNING: We are about to convert your table storage format to UTF8. This
+ allows Bugzilla to correctly store and sort international characters.
+ However, if you have any non-UTF-8 data in your database,
+ it ***WILL BE DELETED*** by this process. So, before
+ you continue with checksetup.pl, if you have any non-UTF-8
+ data (or even if you're not sure) you should press Ctrl-C now
+ to interrupt checksetup.pl, and run contrib/recode.pl to make all
+ the data in your database into UTF-8. You should also back up your
+ database before continuing. This will affect every single table
+ in the database, even non-Bugzilla tables.
+
+ If you ever used a version of Bugzilla before 2.22, we STRONGLY
+ recommend that you stop checksetup.pl NOW and run contrib/recode.pl.
+
+EOT
+
+ if (!Bugzilla->installation_answers->{NO_PAUSE}) {
+ if (Bugzilla->installation_mode ==
+ INSTALLATION_MODE_NON_INTERACTIVE)
+ {
+ print <<EOT;
+ Re-run checksetup.pl in interactive mode (without an 'answers' file)
+ to continue.
+EOT
+ exit;
+ }
+ else {
+ print " Press Enter to continue or Ctrl-C to exit...";
+ getc;
+ }
+ }
+
+ print "Converting table storage format to UTF-8. This may take a",
+ " while.\n";
+ my @dropped_fks;
+ 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";
+
+ my $dropped = $self->bz_drop_related_fks($table, $name);
+ push(@dropped_fks, @$dropped);
+
+ 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)
+
+ foreach my $fk_args (@dropped_fks) {
+ $self->bz_add_fk(@$fk_args);
+ }
+ }
+
+ # 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();
+}
+
+# When you import a MySQL 3/4 mysqldump into MySQL 5, columns that
+# aren't supposed to have defaults will have defaults. This is only
+# a minor issue, but it makes our tests fail, and it's good to keep
+# the DB actually consistent with what DB::Schema thinks the 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);
+ }
+}
+
+# There is a bug in MySQL 4.1.0 - 4.1.15 that makes certain SELECT
+# statements fail after a SHOW TABLE STATUS:
+# http://bugs.mysql.com/bug.php?id=13535
+# This is a workaround, a dummy SELECT to reset the LAST_INSERT_ID.
+sub _after_table_status {
+ my ($self, $tables) = @_;
+ if (grep($_ eq 'bugs', @$tables)
+ && $self->bz_column_info_real("bugs", "bug_id"))
+ {
+ $self->do('SELECT 1 FROM bugs WHERE bug_id IS NULL');
+ }
+}
+
+sub _alter_db_charset_to_utf8 {
+ my $self = shift;
+ my $db_name = Bugzilla->localconfig->{db_name};
+ $self->do("ALTER DATABASE $db_name CHARACTER SET utf8");
+}
+
+sub bz_db_is_utf8 {
+ my $self = shift;
+ my $db_collation = $self->selectrow_arrayref(
+ "SHOW VARIABLES LIKE 'character_set_database'");
+ # First column holds the variable name, second column holds the value.
+ return $db_collation->[1] =~ /utf8/ ? 1 : 0;
+}
+
+
+sub bz_enum_initial_values {
+ my ($self) = @_;
+ my %enum_values = %{$self->ENUM_DEFAULTS};
+ # Get a complete description of the 'bugs' table; with DBD::MySQL
+ # there isn't a column-by-column way of doing this. Could use
+ # $dbh->column_info, but it would go slower and we would have to
+ # use the undocumented mysql_type_name accessor to get the type
+ # of each row.
+ my $sth = $self->prepare("DESCRIBE bugs");
+ $sth->execute();
+ # Look for the particular columns we are interested in.
+ while (my ($thiscol, $thistype) = $sth->fetchrow_array()) {
+ if (defined $enum_values{$thiscol}) {
+ # this is a column of interest.
+ my @value_list;
+ if ($thistype and ($thistype =~ /^enum\(/)) {
+ # it has an enum type; get the set of values.
+ while ($thistype =~ /'([^']*)'(.*)/) {
+ push(@value_list, $1);
+ $thistype = $2;
+ }
+ }
+ if (@value_list) {
+ # record the enum values found.
+ $enum_values{$thiscol} = \@value_list;
+ }
+ }
+ }
+
+ return \%enum_values;
+}
+
+#####################################################################
+# MySQL-specific Database-Reading Methods
+#####################################################################
+
+=begin private
+
+=head1 MYSQL-SPECIFIC DATABASE-READING METHODS
+
+These methods read information about the database from the disk,
+instead of from a Schema object. They are only reliable for MySQL
+(see bug 285111 for the reasons why not all DBs use/have functions
+like this), but that's OK because we only need them for
+backwards-compatibility anyway, for versions of Bugzilla before 2.20.
+
+=over 4
+
+=item C<bz_column_info_real($table, $column)>
+
+ Description: Returns an abstract column definition for a column
+ as it actually exists on disk in the database.
+ Params: $table - The name of the table the column is on.
+ $column - The name of the column you want info about.
+ Returns: An abstract column definition.
+ If the column does not exist, returns undef.
+
+=cut
+
+sub bz_column_info_real {
+ my ($self, $table, $column) = @_;
+ 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;
+}
+
+=item C<bz_index_info_real($table, $index)>
+
+ Description: Returns information about an index on a table in the database.
+ Params: $table = name of table containing the index
+ $index = name of an index
+ Returns: An abstract index definition, always in hashref format.
+ If the index does not exist, the function returns undef.
+=cut
+sub bz_index_info_real {
+ my ($self, $table, $index) = @_;
+
+ my $sth = $self->prepare("SHOW INDEX FROM $table");
+ $sth->execute;
+
+ my @fields;
+ my $index_type;
+ # $raw_def will be an arrayref containing the following information:
+ # 0 = name of the table that the index is on
+ # 1 = 0 if unique, 1 if not unique
+ # 2 = name of the index
+ # 3 = seq_in_index (The order of the current field in the index).
+ # 4 = Name of ONE column that the index is on
+ # 5 = 'Collation' of the index. Usually 'A'.
+ # 6 = Cardinality. Either a number or undef.
+ # 7 = sub_part. Usually undef. Sometimes 1.
+ # 8 = "packed". Usually undef.
+ # 9 = Null. Sometimes undef, sometimes 'YES'.
+ # 10 = Index_type. The type of the index. Usually either 'BTREE' or 'FULLTEXT'
+ # 11 = 'Comment.' Usually undef.
+ while (my $raw_def = $sth->fetchrow_arrayref) {
+ if ($raw_def->[2] eq $index) {
+ push(@fields, $raw_def->[4]);
+ # No index can be both UNIQUE and FULLTEXT, that's why
+ # this is written this way.
+ $index_type = $raw_def->[1] ? '' : 'UNIQUE';
+ $index_type = $raw_def->[10] eq 'FULLTEXT'
+ ? 'FULLTEXT' : $index_type;
+ }
+ }
+
+ my $retval;
+ if (scalar(@fields)) {
+ $retval = {FIELDS => \@fields, TYPE => $index_type};
+ }
+ return $retval;
+}
+
+=item C<bz_index_list_real($table)>
+
+ Description: Returns a list of index names on a table in
+ the database, as it actually exists on disk.
+ Params: $table - The name of the table you want info about.
+ Returns: An array of index names.
+
+=cut
+
+sub bz_index_list_real {
+ my ($self, $table) = @_;
+ my $sth = $self->prepare("SHOW INDEX FROM $table");
+ # Column 3 of a SHOW INDEX statement contains the name of the index.
+ return @{ $self->selectcol_arrayref($sth, {Columns => [3]}) };
+}
+
+#####################################################################
+# MySQL-Specific "Schema Builder"
+#####################################################################
+
+=back
+
+=head1 MYSQL-SPECIFIC "SCHEMA BUILDER"
+
+MySQL needs to be able to read in a legacy database (from before
+Schema existed) and create a Schema object out of it. That's what
+this code does.
+
+=end private
+
+=cut
+
+# This sub itself is actually written generically, but the subroutines
+# that it depends on are database-specific. In particular, the
+# bz_column_info_real function would be very difficult to create
+# properly for any other DB besides MySQL.
+sub _bz_build_schema_from_disk {
+ my ($self) = @_;
+
+ print "Building Schema object from database...\n";
+
+ my $schema = $self->_bz_schema->get_empty_schema();
+
+ my @tables = $self->bz_table_list_real();
+ foreach my $table (@tables) {
+ $schema->add_table($table);
+ my @columns = $self->bz_table_columns_real($table);
+ foreach my $column (@columns) {
+ my $type_info = $self->bz_column_info_real($table, $column);
+ $schema->set_column($table, $column, $type_info);
+ }
+
+ my @indexes = $self->bz_index_list_real($table);
+ foreach my $index (@indexes) {
+ unless ($index eq 'PRIMARY') {
+ my $index_info = $self->bz_index_info_real($table, $index);
+ ($index_info = $index_info->{FIELDS})
+ if (!$index_info->{TYPE});
+ $schema->set_index($table, $index, $index_info);
+ }
+ }
+ }
+
+ return $schema;
+}
+1;
diff --git a/Bugzilla/DB/Oracle.pm b/Bugzilla/DB/Oracle.pm
new file mode 100644
index 000000000..0819bd19a
--- /dev/null
+++ b/Bugzilla/DB/Oracle.pm
@@ -0,0 +1,756 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Oracle Corporation.
+# Portions created by Oracle are Copyright (C) 2007 Oracle Corporation.
+# All Rights Reserved.
+#
+# Contributor(s): Lance Larsh <lance.larsh@oracle.com>
+# Xiaoou Wu <xiaoou.wu@oracle.com>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+=head1 NAME
+
+Bugzilla::DB::Oracle - Bugzilla database compatibility layer for Oracle
+
+=head1 DESCRIPTION
+
+This module overrides methods of the Bugzilla::DB module with Oracle
+specific implementation. It is instantiated by the Bugzilla::DB module
+and should never be used directly.
+
+For interface details see L<Bugzilla::DB> and L<DBI>.
+
+=cut
+
+package Bugzilla::DB::Oracle;
+use strict;
+use base qw(Bugzilla::DB);
+
+use DBD::Oracle;
+use DBD::Oracle qw(:ora_types);
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Util;
+
+#####################################################################
+# Constants
+#####################################################################
+use constant EMPTY_STRING => '__BZ_EMPTY_STR__';
+use constant ISOLATION_LEVEL => 'READ COMMITTED';
+use constant BLOB_TYPE => { ora_type => ORA_BLOB };
+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 => ( Bugzilla->params->{'maxattachmentsize'}
+ || 1000 ) * 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 $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) = @_;
+
+ eval { $self->do("SELECT 1 FROM DUAL WHERE "
+ . $self->sql_regexp($self->quote("a"), $pattern, 1)) };
+
+ $@ && 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 sql_group_concat {
+ my ($self, $text, $separator) = @_;
+ $separator = $self->quote(', ') if !defined $separator;
+ return "group_concat(T_CLOB_DELIM($text, $separator))";
+}
+
+sub sql_regexp {
+ my ($self, $expr, $pattern, $nocheck, $real_pattern) = @_;
+ $real_pattern ||= $pattern;
+
+ $self->bz_check_regexp($real_pattern) if !$nocheck;
+
+ return "REGEXP_LIKE($expr, $pattern)";
+}
+
+sub sql_not_regexp {
+ my ($self, $expr, $pattern, $nocheck, $real_pattern) = @_;
+ $real_pattern ||= $pattern;
+
+ $self->bz_check_regexp($real_pattern) if !$nocheck;
+
+ return "NOT REGEXP_LIKE($expr, $pattern)"
+}
+
+sub sql_limit {
+ my ($self, $limit, $offset) = @_;
+
+ if(defined $offset) {
+ return "/* LIMIT $limit $offset */";
+ }
+ return "/* LIMIT $limit */";
+}
+
+sub sql_string_concat {
+ my ($self, @params) = @_;
+
+ return 'CONCAT(' . join(', ', @params) . ')';
+}
+
+sub sql_string_until {
+ my ($self, $string, $substring) = @_;
+ return "SUBSTR($string, 1, "
+ . $self->sql_position($substring, $string)
+ . " - 1)";
+}
+
+sub sql_to_days {
+ my ($self, $date) = @_;
+
+ return " TO_CHAR(TO_DATE($date),'J') ";
+}
+sub sql_from_days{
+ my ($self, $date) = @_;
+
+ return " TO_DATE($date,'J') ";
+}
+sub sql_fulltext_search {
+ my ($self, $column, $text, $label) = @_;
+ $text = $self->quote($text);
+ trick_taint($text);
+ return "CONTAINS($column,$text,$label)", "SCORE($label)";
+}
+
+sub sql_date_format {
+ my ($self, $date, $format) = @_;
+
+ $format = "%Y.%m.%d %H:%i:%s" if !$format;
+
+ $format =~ s/\%Y/YYYY/g;
+ $format =~ s/\%y/YY/g;
+ $format =~ s/\%m/MM/g;
+ $format =~ s/\%d/DD/g;
+ $format =~ s/\%a/Dy/g;
+ $format =~ s/\%H/HH24/g;
+ $format =~ s/\%i/MI/g;
+ $format =~ s/\%s/SS/g;
+
+ return "TO_CHAR($date, " . $self->quote($format) . ")";
+}
+
+sub sql_interval {
+ my ($self, $interval, $units) = @_;
+ if ($units =~ /YEAR|MONTH/i) {
+ return "NUMTOYMINTERVAL($interval,'$units')";
+ } else{
+ return "NUMTODSINTERVAL($interval,'$units')";
+ }
+}
+
+sub sql_position {
+ my ($self, $fragment, $text) = @_;
+ return "INSTR($text, $fragment)";
+}
+
+sub sql_in {
+ my ($self, $column_name, $in_list_ref) = @_;
+ my @in_list = @$in_list_ref;
+ return $self->SUPER::sql_in($column_name, $in_list_ref) if $#in_list < 1000;
+ my @in_str;
+ while (@in_list) {
+ my $length = $#in_list + 1;
+ my $splice = $length > 1000 ? 1000 : $length;
+ my @sub_in_list = splice(@in_list, 0, $splice);
+ push(@in_str,
+ $self->SUPER::sql_in($column_name, \@sub_in_list));
+ }
+ return "( " . join(" OR ", @in_str) . " )";
+}
+
+sub _bz_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)");
+ }
+}
+
+sub bz_drop_table {
+ my ($self, $name) = @_;
+ my $table_exists = $self->bz_table_info($name);
+ if ($table_exists) {
+ $self->_bz_drop_fks($name);
+ $self->SUPER::bz_drop_table($name);
+ }
+}
+
+# Dropping all FKs for a specified table.
+sub _bz_drop_fks {
+ my ($self, $table) = @_;
+ my @columns = $self->_bz_real_schema->get_table_columns($table);
+ foreach my $column (@columns) {
+ $self->bz_drop_fk($table, $column);
+ }
+}
+
+sub _fix_empty {
+ my ($string) = @_;
+ $string = '' if $string eq EMPTY_STRING;
+ return $string;
+}
+
+sub _fix_arrayref {
+ my ($row) = @_;
+ return undef if !defined $row;
+ foreach my $field (@$row) {
+ $field = _fix_empty($field) if defined $field;
+ }
+ return $row;
+}
+
+sub _fix_hashref {
+ my ($row) = @_;
+ return undef if !defined $row;
+ foreach my $value (values %$row) {
+ $value = _fix_empty($value) if defined $value;
+ }
+ return $row;
+}
+
+sub adjust_statement {
+ my ($sql) = @_;
+
+ 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 recognizes CURRENT_DATE, but not CURRENT_DATE()
+ $part =~ s/\bCURRENT_DATE\b\(\)/CURRENT_DATE/io;
+
+ # Oracle use SUBSTR instead of SUBSTRING
+ $part =~ s/\bSUBSTRING\b/SUBSTR/io;
+
+ # Oracle need no 'AS'
+ $part =~ s/\bAS\b//ig;
+
+ # Oracle doesn't have LIMIT, so if we find the LIMIT comment, wrap the
+ # query with "SELECT * FROM (...) WHERE rownum < $limit"
+ my ($limit,$offset) = ($part =~ m{/\* LIMIT (\d*) (\d*) \*/}o);
+
+ push @result, $part;
+ while( @parts ) {
+ my $string = shift @parts;
+ my $nonstring = shift @parts;
+
+ # if the non-string part is zero-length and there are more parts left,
+ # then this is an escaped quote inside a string literal
+ while( !(length $nonstring) && @parts ) {
+ # we know it's safe to remove two parts at a time, since we
+ # entered the loop with an even number of parts
+ $string .= "''" . shift @parts;
+ $nonstring = shift @parts;
+ }
+
+ # Look for a FROM if this is a SELECT and we haven't found one yet
+ $has_from = ($nonstring =~ m/\bFROM\b/io)
+ if ($is_select and !$has_from);
+
+ # Oracle recognizes CURRENT_DATE, but not CURRENT_DATE()
+ $nonstring =~ s/\bCURRENT_DATE\b\(\)/CURRENT_DATE/io;
+
+ # Oracle use SUBSTR instead of SUBSTRING
+ $nonstring =~ s/\bSUBSTRING\b/SUBSTR/io;
+
+ # Oracle need no 'AS'
+ $nonstring =~ s/\bAS\b//ig;
+
+ # Take the first 4000 chars for comparison
+ $nonstring =~ s/\(\s*(longdescs_\d+\.thetext|attachdata_\d+\.thedata)/
+ \(DBMS_LOB.SUBSTR\($1, 4000, 1\)/ig;
+
+ # Look for a LIMIT clause
+ ($limit) = ($nonstring =~ m(/\* LIMIT (\d*) \*/)o);
+
+ if(!length($string)){
+ push @result, EMPTY_STRING;
+ push @result, $nonstring;
+ } else {
+ push @result, $string;
+ push @result, $nonstring;
+ }
+ }
+
+ my $new_sql = join "'", @result;
+
+ # Append "FROM dual" if this is a SELECT without a FROM clause
+ $new_sql .= " FROM DUAL" if ($is_select and !$has_from);
+
+ # Wrap the query with a "WHERE rownum <= ..." if we found LIMIT
+
+ if (defined($limit)) {
+ if ($new_sql !~ /\bWHERE\b/) {
+ $new_sql = $new_sql." WHERE 1=1";
+ }
+ my ($before_where, $after_where) = split /\bWHERE\b/i,$new_sql;
+ if (defined($offset)) {
+ if ($new_sql =~ /(.*\s+)FROM(\s+.*)/i) {
+ my ($before_from,$after_from) = ($1,$2);
+ $before_where = "$before_from FROM ($before_from,"
+ . " ROW_NUMBER() OVER (ORDER BY 1) R "
+ . " FROM $after_from ) ";
+ $after_where = " R BETWEEN $offset+1 AND $limit+$offset";
+ }
+ } else {
+ $after_where = " rownum <=$limit AND ".$after_where;
+ }
+
+ $new_sql = $before_where." WHERE ".$after_where;
+ }
+ return $new_sql;
+}
+
+sub do {
+ my $self = shift;
+ my $sql = shift;
+ $sql = adjust_statement($sql);
+ unshift @_, $sql;
+ return $self->SUPER::do(@_);
+}
+
+sub selectrow_array {
+ my $self = shift;
+ my $stmt = shift;
+ my $new_stmt = (ref $stmt) ? $stmt : adjust_statement($stmt);
+ unshift @_, $new_stmt;
+ if ( wantarray ) {
+ my @row = $self->SUPER::selectrow_array(@_);
+ _fix_arrayref(\@row);
+ return @row;
+ } else {
+ my $row = $self->SUPER::selectrow_array(@_);
+ $row = _fix_empty($row) if defined $row;
+ return $row;
+ }
+}
+
+sub selectrow_arrayref {
+ my $self = shift;
+ my $stmt = shift;
+ my $new_stmt = (ref $stmt) ? $stmt : adjust_statement($stmt);
+ unshift @_, $new_stmt;
+ my $ref = $self->SUPER::selectrow_arrayref(@_);
+ return undef if !defined $ref;
+
+ _fix_arrayref($ref);
+ return $ref;
+}
+
+sub selectrow_hashref {
+ my $self = shift;
+ my $stmt = shift;
+ my $new_stmt = (ref $stmt) ? $stmt : adjust_statement($stmt);
+ unshift @_, $new_stmt;
+ my $ref = $self->SUPER::selectrow_hashref(@_);
+ return undef if !defined $ref;
+
+ _fix_hashref($ref);
+ return $ref;
+}
+
+sub selectall_arrayref {
+ my $self = shift;
+ my $stmt = shift;
+ my $new_stmt = (ref $stmt) ? $stmt : adjust_statement($stmt);
+ unshift @_, $new_stmt;
+ my $ref = $self->SUPER::selectall_arrayref(@_);
+ return undef if !defined $ref;
+
+ foreach my $row (@$ref) {
+ if (ref($row) eq 'ARRAY') {
+ _fix_arrayref($row);
+ }
+ elsif (ref($row) eq 'HASH') {
+ _fix_hashref($row);
+ }
+ }
+
+ return $ref;
+}
+
+sub selectall_hashref {
+ my $self = shift;
+ my $stmt = shift;
+ my $new_stmt = (ref $stmt) ? $stmt : adjust_statement($stmt);
+ unshift @_, $new_stmt;
+ my $rows = $self->SUPER::selectall_hashref(@_);
+ return undef if !defined $rows;
+ foreach my $row (values %$rows) {
+ _fix_hashref($row);
+ }
+ return $rows;
+}
+
+sub selectcol_arrayref {
+ my $self = shift;
+ my $stmt = shift;
+ my $new_stmt = (ref $stmt) ? $stmt : adjust_statement($stmt);
+ unshift @_, $new_stmt;
+ my $ref = $self->SUPER::selectcol_arrayref(@_);
+ return undef if !defined $ref;
+ _fix_arrayref($ref);
+ return $ref;
+}
+
+sub prepare {
+ my $self = shift;
+ my $sql = shift;
+ my $new_sql = adjust_statement($sql);
+ unshift @_, $new_sql;
+ return bless $self->SUPER::prepare(@_),
+ 'Bugzilla::DB::Oracle::st';
+}
+
+sub prepare_cached {
+ my $self = shift;
+ my $sql = shift;
+ my $new_sql = adjust_statement($sql);
+ unshift @_, $new_sql;
+ return bless $self->SUPER::prepare_cached(@_),
+ 'Bugzilla::DB::Oracle::st';
+}
+
+sub quote_identifier {
+ my ($self,$id) = @_;
+ return $id;
+}
+
+#####################################################################
+# Protected "Real Database" Schema Information Methods
+#####################################################################
+
+sub bz_table_columns_real {
+ my ($self, $table) = @_;
+ $table = uc($table);
+ my $cols = $self->selectcol_arrayref(
+ "SELECT LOWER(COLUMN_NAME) FROM USER_TAB_COLUMNS WHERE
+ TABLE_NAME = ? ORDER BY COLUMN_NAME", undef, $table);
+ return @$cols;
+}
+
+sub bz_table_list_real {
+ my ($self) = @_;
+ my $tables = $self->selectcol_arrayref(
+ "SELECT LOWER(TABLE_NAME) FROM USER_TABLES WHERE
+ TABLE_NAME NOT LIKE ? ORDER BY TABLE_NAME", undef, 'DR$%');
+ return @$tables;
+}
+
+#####################################################################
+# Custom Database Setup
+#####################################################################
+
+sub bz_setup_database {
+ my $self = shift;
+
+ # Create a function that returns SYSDATE to emulate MySQL's "NOW()".
+ # Function NOW() is used widely in Bugzilla SQLs, but Oracle does not
+ # have that function, So we have to create one ourself.
+ $self->do("CREATE OR REPLACE FUNCTION NOW "
+ . " RETURN DATE IS BEGIN RETURN SYSDATE; END;");
+ $self->do("CREATE OR REPLACE FUNCTION CHAR_LENGTH(COLUMN_NAME VARCHAR2)"
+ . " RETURN NUMBER IS BEGIN RETURN LENGTH(COLUMN_NAME); END;");
+
+ # Create types for group_concat
+ my $t_clob_delim = $self->selectcol_arrayref("
+ SELECT TYPE_NAME FROM USER_TYPES WHERE TYPE_NAME=?",
+ undef, 'T_CLOB_DELIM');
+
+ if ( !@$t_clob_delim ) {
+ $self->do("CREATE OR REPLACE TYPE T_CLOB_DELIM AS OBJECT "
+ . "( p_CONTENT CLOB, p_DELIMITER VARCHAR2(256));");
+ }
+
+ $self->do("CREATE OR REPLACE TYPE T_GROUP_CONCAT AS OBJECT
+ ( CLOB_CONTENT CLOB,
+ DELIMITER VARCHAR2(256),
+ STATIC FUNCTION ODCIAGGREGATEINITIALIZE(
+ SCTX IN OUT NOCOPY T_GROUP_CONCAT)
+ RETURN NUMBER,
+ MEMBER FUNCTION ODCIAGGREGATEITERATE(
+ SELF IN OUT NOCOPY T_GROUP_CONCAT,
+ VALUE IN T_CLOB_DELIM)
+ RETURN NUMBER,
+ MEMBER FUNCTION ODCIAGGREGATETERMINATE(
+ SELF IN T_GROUP_CONCAT,
+ RETURNVALUE OUT NOCOPY CLOB,
+ FLAGS IN NUMBER)
+ RETURN NUMBER,
+ MEMBER FUNCTION ODCIAGGREGATEMERGE(
+ SELF IN OUT NOCOPY T_GROUP_CONCAT,
+ CTX2 IN T_GROUP_CONCAT)
+ RETURN NUMBER);");
+
+ $self->do("CREATE OR REPLACE TYPE BODY T_GROUP_CONCAT IS
+ STATIC FUNCTION ODCIAGGREGATEINITIALIZE(
+ SCTX IN OUT NOCOPY T_GROUP_CONCAT)
+ RETURN NUMBER IS
+ BEGIN
+ SCTX := T_GROUP_CONCAT(EMPTY_CLOB(), NULL);
+ DBMS_LOB.CREATETEMPORARY(SCTX.CLOB_CONTENT, TRUE);
+ RETURN ODCICONST.SUCCESS;
+ END;
+ MEMBER FUNCTION ODCIAGGREGATEITERATE(
+ SELF IN OUT NOCOPY T_GROUP_CONCAT,
+ VALUE IN T_CLOB_DELIM)
+ RETURN NUMBER IS
+ BEGIN
+ SELF.DELIMITER := VALUE.P_DELIMITER;
+ DBMS_LOB.WRITEAPPEND(SELF.CLOB_CONTENT,
+ LENGTH(SELF.DELIMITER),
+ SELF.DELIMITER);
+ DBMS_LOB.APPEND(SELF.CLOB_CONTENT, VALUE.P_CONTENT);
+
+ RETURN ODCICONST.SUCCESS;
+ END;
+ MEMBER FUNCTION ODCIAGGREGATETERMINATE(
+ SELF IN T_GROUP_CONCAT,
+ RETURNVALUE OUT NOCOPY CLOB,
+ FLAGS IN NUMBER)
+ RETURN NUMBER IS
+ BEGIN
+ RETURNVALUE := RTRIM(LTRIM(SELF.CLOB_CONTENT,
+ SELF.DELIMITER),
+ SELF.DELIMITER);
+ RETURN ODCICONST.SUCCESS;
+ END;
+ MEMBER FUNCTION ODCIAGGREGATEMERGE(
+ SELF IN OUT NOCOPY T_GROUP_CONCAT,
+ CTX2 IN T_GROUP_CONCAT)
+ RETURN NUMBER IS
+ BEGIN
+ DBMS_LOB.WRITEAPPEND(SELF.CLOB_CONTENT,
+ LENGTH(SELF.DELIMITER),
+ SELF.DELIMITER);
+ DBMS_LOB.APPEND(SELF.CLOB_CONTENT, CTX2.CLOB_CONTENT);
+ RETURN ODCICONST.SUCCESS;
+ END;
+ END;");
+
+ # 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 @tables = $self->bz_table_list_real();
+ foreach my $table (@tables) {
+ my @columns = $self->bz_table_columns_real($table);
+ foreach my $column (@columns) {
+ my $def = $self->bz_column_info($table, $column);
+ if ($def->{REFERENCES}) {
+ my $references = $def->{REFERENCES};
+ my $update = $references->{UPDATE} || 'CASCADE';
+ my $to_table = $references->{TABLE};
+ my $to_column = $references->{COLUMN};
+ my $fk_name = $self->_bz_schema->_get_fk_name($table,
+ $column,
+ $references);
+ if ( $update =~ /CASCADE/i ){
+ my $trigger_name = uc($fk_name . "_UC");
+ my $exist_trigger = $self->selectcol_arrayref(
+ "SELECT OBJECT_NAME FROM USER_OBJECTS
+ WHERE OBJECT_NAME = ?", undef, $trigger_name);
+ if(@$exist_trigger) {
+ $self->do("DROP TRIGGER $trigger_name");
+ }
+
+ my $tr_str = "CREATE OR REPLACE TRIGGER $trigger_name"
+ . " AFTER UPDATE 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(
+ "SELECT OBJECT_NAME FROM USER_OBJECTS
+ WHERE OBJECT_NAME = ?", undef, $trigger_name);
+ if(@$exist_trigger) {
+ $self->do("DROP TRIGGER $trigger_name");
+ }
+}
+
+package Bugzilla::DB::Oracle::st;
+use base qw(DBI::st);
+
+sub fetchrow_arrayref {
+ my $self = shift;
+ my $ref = $self->SUPER::fetchrow_arrayref(@_);
+ return undef if !defined $ref;
+ Bugzilla::DB::Oracle::_fix_arrayref($ref);
+ return $ref;
+}
+
+sub fetchrow_array {
+ my $self = shift;
+ if ( wantarray ) {
+ my @row = $self->SUPER::fetchrow_array(@_);
+ Bugzilla::DB::Oracle::_fix_arrayref(\@row);
+ return @row;
+ } else {
+ my $row = $self->SUPER::fetchrow_array(@_);
+ $row = Bugzilla::DB::Oracle::_fix_empty($row) if defined $row;
+ return $row;
+ }
+}
+
+sub fetchrow_hashref {
+ my $self = shift;
+ my $ref = $self->SUPER::fetchrow_hashref(@_);
+ return undef if !defined $ref;
+ Bugzilla::DB::Oracle::_fix_hashref($ref);
+ return $ref;
+}
+
+sub fetchall_arrayref {
+ my $self = shift;
+ my $ref = $self->SUPER::fetchall_arrayref(@_);
+ return undef if !defined $ref;
+ foreach my $row (@$ref) {
+ if (ref($row) eq 'ARRAY') {
+ Bugzilla::DB::Oracle::_fix_arrayref($row);
+ }
+ elsif (ref($row) eq 'HASH') {
+ Bugzilla::DB::Oracle::_fix_hashref($row);
+ }
+ }
+ return $ref;
+}
+
+sub fetchall_hashref {
+ my $self = shift;
+ my $ref = $self->SUPER::fetchall_hashref(@_);
+ return undef if !defined $ref;
+ foreach my $row (values %$ref) {
+ Bugzilla::DB::Oracle::_fix_hashref($row);
+ }
+ return $ref;
+}
+
+sub fetch {
+ my $self = shift;
+ my $row = $self->SUPER::fetch(@_);
+ if ($row) {
+ Bugzilla::DB::Oracle::_fix_arrayref($row);
+ }
+ return $row;
+}
+1;
diff --git a/Bugzilla/DB/Pg.pm b/Bugzilla/DB/Pg.pm
new file mode 100644
index 000000000..0a21a5c10
--- /dev/null
+++ b/Bugzilla/DB/Pg.pm
@@ -0,0 +1,354 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Dave Miller <davem00@aol.com>
+# Gayathri Swaminath <gayathrik00@aol.com>
+# Jeroen Ruigrok van der Werven <asmodai@wxs.nl>
+# Dave Lawrence <dkl@redhat.com>
+# Tomas Kopal <Tomas.Kopal@altap.cz>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+# Lance Larsh <lance.larsh@oracle.com>
+
+=head1 NAME
+
+Bugzilla::DB::Pg - Bugzilla database compatibility layer for PostgreSQL
+
+=head1 DESCRIPTION
+
+This module overrides methods of the Bugzilla::DB module with PostgreSQL
+specific implementation. It is instantiated by the Bugzilla::DB module
+and should never be used directly.
+
+For interface details see L<Bugzilla::DB> and L<DBI>.
+
+=cut
+
+package Bugzilla::DB::Pg;
+
+use strict;
+
+use Bugzilla::Error;
+use DBD::Pg;
+
+# This module extends the DB interface via inheritance
+use base qw(Bugzilla::DB);
+
+use constant BLOB_TYPE => { pg_type => DBD::Pg::PG_BYTEA };
+
+sub new {
+ my ($class, $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';
+
+ # construct the DSN from the parameters we got
+ my $dsn = "dbi:Pg:dbname=$dbname";
+ $dsn .= ";host=$host" if $host;
+ $dsn .= ";port=$port" if $port;
+
+ # This stops Pg from printing out lots of "NOTICE" messages when
+ # creating tables.
+ $dsn .= ";options='-c client_min_messages=warning'";
+
+ my $attrs = { pg_enable_utf8 => Bugzilla->params->{'utf8'} };
+
+ my $self = $class->db_new({ dsn => $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;
+
+ bless ($self, $class);
+
+ return $self;
+}
+
+# if last_insert_id is supported on PostgreSQL by lowest DBI/DBD version
+# supported by Bugzilla, this implementation can be removed.
+sub bz_last_key {
+ my ($self, $table, $column) = @_;
+
+ my $seq = $table . "_" . $column . "_seq";
+ my ($last_insert_id) = $self->selectrow_array("SELECT CURRVAL('$seq')");
+
+ return $last_insert_id;
+}
+
+sub sql_group_concat {
+ my ($self, $text, $separator, $sort) = @_;
+ $sort = 1 if !defined $sort;
+ $separator = $self->quote(', ') if !defined $separator;
+ my $sql = "array_accum($text)";
+ if ($sort) {
+ $sql = "array_sort($sql)";
+ }
+ return "array_to_string($sql, $separator)";
+}
+
+sub sql_istring {
+ my ($self, $string) = @_;
+
+ return "LOWER(${string}::text)";
+}
+
+sub sql_position {
+ my ($self, $fragment, $text) = @_;
+
+ return "POSITION($fragment IN ${text}::text)";
+}
+
+sub sql_regexp {
+ my ($self, $expr, $pattern, $nocheck, $real_pattern) = @_;
+ $real_pattern ||= $pattern;
+
+ $self->bz_check_regexp($real_pattern) if !$nocheck;
+
+ return "${expr}::text ~* $pattern";
+}
+
+sub sql_not_regexp {
+ my ($self, $expr, $pattern, $nocheck, $real_pattern) = @_;
+ $real_pattern ||= $pattern;
+
+ $self->bz_check_regexp($real_pattern) if !$nocheck;
+
+ return "${expr}::text !~* $pattern"
+}
+
+sub sql_limit {
+ my ($self, $limit, $offset) = @_;
+
+ if (defined($offset)) {
+ return "LIMIT $limit OFFSET $offset";
+ } else {
+ return "LIMIT $limit";
+ }
+}
+
+sub sql_from_days {
+ my ($self, $days) = @_;
+
+ return "TO_TIMESTAMP('$days', 'J')::date";
+}
+
+sub sql_to_days {
+ my ($self, $date) = @_;
+
+ return "TO_CHAR(${date}::date, 'J')::int";
+}
+
+sub sql_date_format {
+ my ($self, $date, $format) = @_;
+
+ $format = "%Y.%m.%d %H:%i:%s" if !$format;
+
+ $format =~ s/\%Y/YYYY/g;
+ $format =~ s/\%y/YY/g;
+ $format =~ s/\%m/MM/g;
+ $format =~ s/\%d/DD/g;
+ $format =~ s/\%a/Dy/g;
+ $format =~ s/\%H/HH24/g;
+ $format =~ s/\%i/MI/g;
+ $format =~ s/\%s/SS/g;
+
+ return "TO_CHAR($date, " . $self->quote($format) . ")";
+}
+
+sub sql_interval {
+ my ($self, $interval, $units) = @_;
+
+ return "$interval * INTERVAL '1 $units'";
+}
+
+sub sql_string_concat {
+ my ($self, @params) = @_;
+
+ # Postgres 7.3 does not support concatenating of different types, so we
+ # need to cast both parameters to text. Version 7.4 seems to handle this
+ # properly, so when we stop support 7.3, this can be removed.
+ return '(CAST(' . join(' AS text) || CAST(', @params) . ' AS text))';
+}
+
+sub sql_string_until {
+ my ($self, $string, $substring) = @_;
+
+ # PostgreSQL does not permit a negative substring length; therefore we
+ # use CASE to only perform the SUBSTRING operation when $substring can
+ # be found withing $string.
+ my $position = $self->sql_position($substring, $string);
+ return "CASE WHEN $position != 0"
+ . " THEN SUBSTRING($string FROM 1 FOR $position - 1)"
+ . " ELSE $string END";
+}
+
+# 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;
+}
+
+sub bz_explain {
+ my ($self, $sql) = @_;
+ my $explain = $self->selectcol_arrayref("EXPLAIN ANALYZE $sql");
+ return join("\n", @$explain);
+}
+
+#####################################################################
+# Custom Database Setup
+#####################################################################
+
+sub bz_check_server_version {
+ my $self = shift;
+ my ($db) = @_;
+ my $server_version = $self->SUPER::bz_check_server_version(@_);
+ my ($major_version) = $server_version =~ /^(\d+)/;
+ # Pg 9 requires DBD::Pg 2.17.2 in order to properly read bytea values.
+ if ($major_version >= 9) {
+ local $db->{dbd}->{version} = '2.17.2';
+ local $db->{name} = $db->{name} . ' 9+';
+ Bugzilla::DB::_bz_check_dbd(@_);
+ }
+}
+
+sub bz_setup_database {
+ my $self = shift;
+ $self->SUPER::bz_setup_database(@_);
+
+ # Custom Functions
+ 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');
+CREATE OR REPLACE FUNCTION array_sort(ANYARRAY)
+RETURNS ANYARRAY LANGUAGE SQL
+IMMUTABLE STRICT
+AS $$
+SELECT ARRAY(
+ SELECT $1[s.i] AS each_item
+ FROM
+ generate_series(array_lower($1,1), array_upper($1,1)) AS s(i)
+ ORDER BY each_item
+);
+$$;
+END
+
+ # PostgreSQL doesn't like having *any* index on the thetext
+ # field, because it can't have index data longer than 2770
+ # characters on that field.
+ $self->bz_drop_index('longdescs', 'longdescs_thetext_idx');
+ # Same for all the comments fields in the fulltext table.
+ $self->bz_drop_index('bugs_fulltext', 'bugs_fulltext_comments_idx');
+ $self->bz_drop_index('bugs_fulltext',
+ 'bugs_fulltext_comments_noprivate_idx');
+
+ # PostgreSQL also wants an index for calling LOWER on
+ # login_name, which we do with sql_istrcmp all over the place.
+ $self->bz_add_index('profiles', 'profiles_login_name_lower_idx',
+ {FIELDS => ['LOWER(login_name)'], TYPE => 'UNIQUE'});
+
+ # Now that Bugzilla::Object uses sql_istrcmp, other tables
+ # also need a LOWER() index.
+ _fix_case_differences('fielddefs', 'name');
+ $self->bz_add_index('fielddefs', 'fielddefs_name_lower_idx',
+ {FIELDS => ['LOWER(name)'], TYPE => 'UNIQUE'});
+ _fix_case_differences('keyworddefs', 'name');
+ $self->bz_add_index('keyworddefs', 'keyworddefs_name_lower_idx',
+ {FIELDS => ['LOWER(name)'], TYPE => 'UNIQUE'});
+ _fix_case_differences('products', 'name');
+ $self->bz_add_index('products', 'products_name_lower_idx',
+ {FIELDS => ['LOWER(name)'], TYPE => 'UNIQUE'});
+
+ # bz_rename_column didn't correctly rename the sequence.
+ if ($self->bz_column_info('fielddefs', 'id')
+ && $self->bz_sequence_exists('fielddefs_fieldid_seq'))
+ {
+ print "Fixing fielddefs_fieldid_seq sequence...\n";
+ $self->do("ALTER TABLE fielddefs_fieldid_seq RENAME TO fielddefs_id_seq");
+ $self->do("ALTER TABLE fielddefs ALTER COLUMN id
+ SET DEFAULT NEXTVAL('fielddefs_id_seq')");
+ }
+}
+
+# Renames things that differ only in case.
+sub _fix_case_differences {
+ my ($table, $field) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ my $duplicates = $dbh->selectcol_arrayref(
+ "SELECT DISTINCT LOWER($field) FROM $table
+ GROUP BY LOWER($field) HAVING COUNT(LOWER($field)) > 1");
+
+ foreach my $name (@$duplicates) {
+ my $dups = $dbh->selectcol_arrayref(
+ "SELECT $field FROM $table WHERE LOWER($field) = ?",
+ undef, $name);
+ my $primary = shift @$dups;
+ foreach my $dup (@$dups) {
+ my $new_name = "${dup}_";
+ # Make sure the new name isn't *also* a duplicate.
+ while (1) {
+ last if (!$dbh->selectrow_array(
+ "SELECT 1 FROM $table WHERE LOWER($field) = ?",
+ undef, lc($new_name)));
+ $new_name .= "_";
+ }
+ print "$table '$primary' and '$dup' have names that differ",
+ " only in case.\nRenaming '$dup' to '$new_name'...\n";
+ $dbh->do("UPDATE $table SET $field = ? WHERE $field = ?",
+ undef, $new_name, $dup);
+ }
+ }
+}
+
+#####################################################################
+# Custom Schema Information Functions
+#####################################################################
+
+# Pg includes the PostgreSQL system tables in table_list_real, so
+# we need to remove those.
+sub bz_table_list_real {
+ my $self = shift;
+
+ my @full_table_list = $self->SUPER::bz_table_list_real(@_);
+ # All PostgreSQL system tables start with "pg_" or "sql_"
+ my @table_list = grep(!/(^pg_)|(^sql_)/, @full_table_list);
+ return @table_list;
+}
+
+1;
diff --git a/Bugzilla/DB/Schema.pm b/Bugzilla/DB/Schema.pm
new file mode 100644
index 000000000..6245dbbc5
--- /dev/null
+++ b/Bugzilla/DB/Schema.pm
@@ -0,0 +1,2854 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Andrew Dunstan <andrew@dunslane.net>,
+# Edward J. Sabol <edwardjsabol@iname.com>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+# Lance Larsh <lance.larsh@oracle.com>
+# Dennis Melentyev <dennis.melentyev@infopulse.com.ua>
+# Akamai Technologies <bugzilla-dev@akamai.com>
+# Elliotte Martin <emartin@everythingsolved.com>
+
+package Bugzilla::DB::Schema;
+
+###########################################################################
+#
+# Purpose: Object-oriented, DBMS-independent database schema for Bugzilla
+#
+# This is the base class implementing common methods and abstract schema.
+#
+###########################################################################
+
+use strict;
+use Bugzilla::Error;
+use Bugzilla::Hook;
+use Bugzilla::Util;
+use Bugzilla::Constants;
+
+use Carp qw(confess);
+use Digest::MD5 qw(md5_hex);
+use Hash::Util qw(lock_value unlock_hash lock_keys unlock_keys);
+use List::MoreUtils qw(firstidx);
+use Safe;
+# Historical, needed for SCHEMA_VERSION = '1.00'
+use Storable qw(dclone freeze thaw);
+
+# New SCHEMA_VERSION (2.00) use this
+use Data::Dumper;
+
+=head1 NAME
+
+Bugzilla::DB::Schema - Abstract database schema for Bugzilla
+
+=head1 SYNOPSIS
+
+ # Obtain MySQL database schema.
+ # Do not do this. Use Bugzilla::DB instead.
+ use Bugzilla::DB::Schema;
+ my $mysql_schema = new Bugzilla::DB::Schema('Mysql');
+
+ # Recommended way to obtain database schema.
+ use Bugzilla::DB;
+ my $dbh = Bugzilla->dbh;
+ my $schema = $dbh->_bz_schema();
+
+ # Get the list of tables in the Bugzilla database.
+ my @tables = $schema->get_table_list();
+
+ # Get the SQL statements need to create the bugs table.
+ my @statements = $schema->get_table_ddl('bugs');
+
+ # Get the database-specific SQL data type used to implement
+ # the abstract data type INT1.
+ my $db_specific_type = $schema->sql_type('INT1');
+
+=head1 DESCRIPTION
+
+This module implements an object-oriented, abstract database schema.
+It should be considered package-private to the Bugzilla::DB module.
+That means that CGI scripts should never call any function in this
+module directly, but should instead rely on methods provided by
+Bugzilla::DB.
+
+=head1 NEW TO SCHEMA.PM?
+
+If this is your first time looking at Schema.pm, especially if
+you are making changes to the database, please take a look at
+L<http://www.bugzilla.org/docs/developer.html#sql-schema> to learn
+more about how this integrates into the rest of Bugzilla.
+
+=cut
+
+#--------------------------------------------------------------------------
+# Define the Bugzilla abstract database schema and version as constants.
+
+=head1 CONSTANTS
+
+=over
+
+=item C<SCHEMA_VERSION>
+
+The 'version' of the internal schema structure. This version number
+is incremented every time the the fundamental structure of Schema
+internals changes.
+
+This is NOT changed every time a table or a column is added. This
+number is incremented only if the internal structures of this
+Schema would be incompatible with the internal structures of a
+previous Schema version.
+
+In general, unless you are messing around with serialization
+and deserialization of the schema, you don't need to worry about
+this constant.
+
+=begin private
+
+An example of the use of the version number:
+
+Today, we store all individual columns like this:
+
+column_name => { TYPE => 'SOMETYPE', NOTNULL => 1 }
+
+Imagine that someday we decide that NOTNULL => 1 is bad, and we want
+to change it so that the schema instead uses NULL => 0.
+
+But we have a bunch of Bugzilla installations around the world with a
+serialized schema that has NOTNULL in it! When we deserialize that
+structure, it just WILL NOT WORK properly inside of our new Schema object.
+So, immediately after deserializing, we need to go through the hash
+and change all NOTNULLs to NULLs and so on.
+
+We know that we need to do that on deserializing because we know that
+version 1.00 used NOTNULL. Having made the change to NULL, we would now
+be version 1.01.
+
+=end private
+
+=item C<ABSTRACT_SCHEMA>
+
+The abstract database schema structure consists of a hash reference
+in which each key is the name of a table in the Bugzilla database.
+
+The value for each key is a hash reference containing the keys
+C<FIELDS> and C<INDEXES> which in turn point to array references
+containing information on the table's fields and indexes.
+
+A field hash reference should must contain the key C<TYPE>. Optional field
+keys include C<PRIMARYKEY>, C<NOTNULL>, and C<DEFAULT>.
+
+The C<INDEXES> array reference contains index names and information
+regarding the index. If the index name points to an array reference,
+then the index is a regular index and the array contains the indexed
+columns. If the index name points to a hash reference, then the hash
+must contain the key C<FIELDS>. It may also contain the key C<TYPE>,
+which can be used to specify the type of index such as UNIQUE or FULLTEXT.
+
+=back
+
+=head2 Referential Integrity
+
+Bugzilla::DB::Schema supports "foreign keys", a way of saying
+that "Column X may only contain values from Column Y in Table Z".
+For example, in Bugzilla, bugs.resolution should only contain
+values from the resolution.values field.
+
+It does this by adding an additional item to a column, called C<REFERENCES>.
+This is a hash with the following members:
+
+=over
+
+=item C<TABLE>
+
+The table the foreign key points at
+
+=item C<COLUMN>
+
+The column pointed at in that table.
+
+=item C<DELETE>
+
+What to do if the row in the parent table is deleted. Choices are
+C<RESTRICT>, C<CASCADE>, or C<SET NULL>.
+
+C<RESTRICT> means the deletion of the row in the parent table will
+be forbidden by the database if there is a row in I<this> table that
+still refers to it. This is the default, if you don't specify
+C<DELETE>.
+
+C<CASCADE> means that this row will be deleted along with that row.
+
+C<SET NULL> means that the column will be set to C<NULL> when the parent
+row is deleted. Note that this is only valid if the column can actually
+be set to C<NULL>. (That is, the column isn't C<NOT NULL>.)
+
+=item C<UPDATE>
+
+What to do if the value in the parent table is updated. You can set this
+to C<CASCADE> or C<RESTRICT>, which mean the same thing as they do for
+L</DELETE>. This variable defaults to C<CASCADE>, which means "also
+update this column in this table."
+
+=back
+
+=cut
+
+use constant SCHEMA_VERSION => '2.00';
+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 => [
+ bug_id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
+ PRIMARYKEY => 1},
+ assigned_to => {TYPE => 'INT3', NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles',
+ COLUMN => 'userid'}},
+ bug_file_loc => {TYPE => 'MEDIUMTEXT'},
+ 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 => 'INT2', NOTNULL => 1,
+ REFERENCES => {TABLE => 'components',
+ COLUMN => 'id'}},
+ resolution => {TYPE => 'varchar(64)',
+ NOTNULL => 1, DEFAULT => "''"},
+ target_milestone => {TYPE => 'varchar(20)',
+ 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'},
+ alias => {TYPE => 'varchar(20)'},
+ ],
+ INDEXES => [
+ bugs_alias_idx => {FIELDS => ['alias'],
+ TYPE => 'UNIQUE'},
+ 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 => [
+ 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 => 'TINYTEXT'},
+ comment_id => {TYPE => 'INT3',
+ 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'],
+ ],
+ },
+
+ 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 => 'MEDIUMSERIAL', 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 => ['bug_id'],
+ longdescs_who_idx => [qw(who bug_id)],
+ longdescs_bug_when_idx => ['bug_when'],
+ ],
+ },
+
+ 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 => ['blocked'],
+ 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(100)', 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'},
+ isurl => {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 => [
+ bug_id => {TYPE => 'INT3', NOTNULL => 1,
+ REFERENCES => {TABLE => 'bugs',
+ COLUMN => 'bug_id',
+ DELETE => 'CASCADE'}},
+ value => {TYPE => 'varchar(255)', NOTNULL => 1},
+ ],
+ INDEXES => [
+ bug_see_also_bug_id_idx => {FIELDS => [qw(bug_id value)],
+ TYPE => 'UNIQUE'},
+ ],
+ },
+
+ # 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 => 'INT2',
+ REFERENCES => {TABLE => 'components',
+ COLUMN => 'id',
+ DELETE => 'CASCADE'}},
+ ],
+ INDEXES => [
+ flaginclusions_type_id_idx =>
+ [qw(type_id product_id component_id)],
+ ],
+ },
+
+ 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 => 'INT2',
+ REFERENCES => {TABLE => 'components',
+ COLUMN => 'id',
+ DELETE => 'CASCADE'}},
+ ],
+ INDEXES => [
+ flagexclusions_type_id_idx =>
+ [qw(type_id product_id component_id)],
+ ],
+ },
+
+ # 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},
+ 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'}},
+ visibility_value_id => {TYPE => 'INT2'},
+ value_field_id => {TYPE => 'INT3',
+ REFERENCES => {TABLE => 'fielddefs',
+ COLUMN => 'id'}},
+ reverse_desc => {TYPE => 'TINYTEXT'},
+ is_mandatory => {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'],
+ ],
+ },
+
+ # 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'}},
+ ],
+ 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(20)', NOTNULL => 1},
+ sortkey => {TYPE => 'INT2', NOTNULL => 1,
+ DEFAULT => 0},
+ ],
+ 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)'},
+ ],
+ 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},
+ bug_list => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
+ list_order => {TYPE => 'MEDIUMTEXT'},
+ ],
+ INDEXES => [
+ profile_search_user_id => [qw(user_id)],
+ ],
+ },
+
+ profiles_activity => {
+ FIELDS => [
+ 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'},
+ ],
+ },
+
+ 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},
+ query_type => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 0},
+ ],
+ 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'],
+ ],
+ },
+
+ component_cc => {
+
+ FIELDS => [
+ user_id => {TYPE => 'INT3', NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles',
+ COLUMN => 'userid',
+ DELETE => 'CASCADE'}},
+ component_id => {TYPE => 'INT2', 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(8)', 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(20)',
+ NOTNULL => 1, DEFAULT => "'---'"},
+ allows_unconfirmed => {TYPE => 'BOOLEAN', NOTNULL => 1,
+ DEFAULT => 'TRUE'},
+ ],
+ INDEXES => [
+ products_name_idx => {FIELDS => ['name'],
+ TYPE => 'UNIQUE'},
+ ],
+ },
+
+ components => {
+ FIELDS => [
+ id => {TYPE => 'SMALLSERIAL', 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},
+ ],
+ 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 =>
+ {FIELDS => [qw(creator 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 => 'MEDIUMTEXT', 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'},
+ ],
+ },
+
+ # 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},
+ ],
+ },
+
+};
+
+# 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'},
+ ],
+};
+
+#--------------------------------------------------------------------------
+
+=head1 METHODS
+
+Note: Methods which can be implemented generically for all DBs are
+implemented in this module. If needed, they can be overridden with
+DB-specific code in a subclass. Methods which are prefixed with C<_>
+are considered protected. Subclasses may override these methods, but
+other modules should not invoke these methods directly.
+
+=cut
+
+#--------------------------------------------------------------------------
+sub new {
+
+=over
+
+=item C<new>
+
+ Description: Public constructor method used to instantiate objects of this
+ class. However, it also can be used as a factory method to
+ instantiate database-specific subclasses when an optional
+ driver argument is supplied.
+ Parameters: $driver (optional) - Used to specify the type of database.
+ This routine C<die>s if no subclass is found for the specified
+ driver.
+ $schema (optional) - A reference to a hash. Callers external
+ to this package should never use this parameter.
+ Returns: new instance of the Schema class or a database-specific subclass
+
+=cut
+
+ 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__);
+
+ my $self = {};
+ bless $self, $class;
+ $self = $self->_initialize(@_);
+
+ return($self);
+
+} #eosub--new
+#--------------------------------------------------------------------------
+sub _initialize {
+
+=item C<_initialize>
+
+ Description: Protected method that initializes an object after
+ instantiation with the abstract schema. All subclasses should
+ override this method. The typical subclass implementation
+ should first call the C<_initialize> method of the superclass,
+ then do any database-specific initialization (especially
+ define the database-specific implementation of the all
+ abstract data types), and then call the C<_adjust_schema>
+ method.
+ Parameters: $abstract_schema (optional) - A reference to a hash. If
+ provided, this hash will be used as the internal
+ representation of the abstract schema instead of our
+ default abstract schema. This is intended for internal
+ use only by deserialize_abstract.
+ Returns: the instance of the Schema class
+
+=cut
+
+ 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);
+
+ # 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;
+
+ return $self;
+
+} #eosub--_initialize
+#--------------------------------------------------------------------------
+sub _adjust_schema {
+
+=item C<_adjust_schema>
+
+ Description: Protected method that alters the abstract schema at
+ instantiation-time to be database-specific. It is a generic
+ enough routine that it can be defined here in the base class.
+ It takes the abstract schema and replaces the abstract data
+ types with database-specific data types.
+ Parameters: none
+ Returns: the instance of the Schema class
+
+=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}};
+ }
+ }
+ }
+
+ return $self;
+
+} #eosub--_adjust_schema
+#--------------------------------------------------------------------------
+sub get_type_ddl {
+
+=item C<get_type_ddl>
+
+=over
+
+=item B<Description>
+
+Public method to convert abstract (database-generic) field specifiers to
+database-specific data types suitable for use in a C<CREATE TABLE> or
+C<ALTER TABLE> SQL statment. If no database-specific field type has been
+defined for the given field type, then it will just return the same field type.
+
+=item B<Parameters>
+
+=over
+
+=item C<$def> - A reference to a hash of a field containing the following keys:
+C<TYPE> (required), C<NOTNULL> (optional), C<DEFAULT> (optional),
+C<PRIMARYKEY> (optional), C<REFERENCES> (optional)
+
+=back
+
+=item B<Returns>
+
+A DDL string suitable for describing a field in a C<CREATE TABLE> or
+C<ALTER TABLE> SQL statement
+
+=back
+
+=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 $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));
+ $type_ddl .= " NOT NULL" if ($finfo->{NOTNULL});
+ $type_ddl .= " PRIMARY KEY" if ($finfo->{PRIMARYKEY});
+
+ return($type_ddl);
+
+} #eosub--get_type_ddl
+
+
+sub get_fk_ddl {
+=item C<_get_fk_ddl>
+
+=over
+
+=item B<Description>
+
+Protected method. Translates the C<REFERENCES> item of a column into SQL.
+
+=item B<Params>
+
+=over
+
+=item C<$table> - The name of the table the reference is from.
+=item C<$column> - The name of the column the reference is from
+=item C<$references> - The C<REFERENCES> hashref from a column.
+
+=back
+
+=item B<Returns>
+
+SQL for to define the foreign key, or an empty string if C<$references>
+is undefined.
+
+=back
+
+=cut
+
+ 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);
+
+ 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}";
+
+ if (length($name) > $self->MAX_IDENTIFIER_LEN) {
+ $name = 'fk_' . $self->_hash_identifier($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);
+}
+
+
+sub get_add_fks_sql {
+ my ($self, $table, $column_fks) = @_;
+
+ my @add;
+ foreach my $column (keys %$column_fks) {
+ my $def = $column_fks->{$column};
+ my $fk_string = $self->get_fk_ddl($table, $column, $def);
+ push(@add, $fk_string);
+ }
+ 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");
+ }
+ }
+ return @sql;
+}
+
+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");
+}
+
+sub convert_type {
+
+=item C<convert_type>
+
+Converts a TYPE from the L</ABSTRACT_SCHEMA> format into the real SQL type.
+
+=cut
+
+ my ($self, $type) = @_;
+ return $self->{db_specific}->{$type} || $type;
+}
+
+sub get_column {
+=item C<get_column($table, $column)>
+
+ Description: Public method to get the abstract definition of a column.
+ Parameters: $table - the table name
+ $column - a column in the table
+ Returns: a hashref containing information about the column, including its
+ type (C<TYPE>), whether or not it can be null (C<NOTNULL>),
+ its default value if it has one (C<DEFAULT), etc.
+ Returns undef if the table or column does not exist.
+
+=cut
+
+ 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
+
+sub get_table_list {
+
+=item C<get_table_list>
+
+ Description: Public method for discovering what tables should exist in the
+ Bugzilla database.
+
+ Parameters: none
+
+ Returns: An array of table names, in alphabetical order.
+
+=cut
+
+ my $self = shift;
+ return sort keys %{$self->{schema}};
+}
+
+sub get_table_columns {
+
+=item C<get_table_columns>
+
+ Description: Public method for discovering what columns are in a given
+ table in the Bugzilla database.
+ Parameters: $table - the table name
+ Returns: array of column names
+
+=cut
+
+ my($self, $table) = @_;
+ my @ddl = ();
+
+ 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);
+ }
+
+ return @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;
+}
+
+sub get_create_database_sql {
+ my ($self, $name) = @_;
+ return ("CREATE DATABASE $name");
+}
+
+sub get_table_ddl {
+
+=item C<get_table_ddl>
+
+ Description: Public method to generate the SQL statements needed to create
+ the a given table and its indexes in the Bugzilla database.
+ Subclasses may override or extend this method, if needed, but
+ subclasses probably should override C<_get_create_table_ddl>
+ or C<_get_create_index_ddl> instead.
+ Parameters: $table - the table name
+ Returns: an array of strings containing SQL statements
+
+=cut
+
+ my($self, $table) = @_;
+ my @ddl = ();
+
+ 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 @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}));
+
+ return @ddl;
+
+} #eosub--get_table_ddl
+#--------------------------------------------------------------------------
+sub _get_create_table_ddl {
+
+=item C<_get_create_table_ddl>
+
+ Description: Protected method to generate the "create table" SQL statement
+ for a given table.
+ Parameters: $table - the table name
+ Returns: a string containing the DDL statement for the specified table
+
+=cut
+
+ my($self, $table) = @_;
+
+ my $thash = $self->{schema}{$table};
+ die "Table $table does not exist in the database schema."
+ unless (ref($thash));
+
+ my $create_table = "CREATE TABLE $table \(\n";
+
+ my @fields = @{ $thash->{FIELDS} };
+ while (@fields) {
+ my $field = shift(@fields);
+ my $finfo = shift(@fields);
+ $create_table .= "\t$field\t" . $self->get_type_ddl($finfo);
+ $create_table .= "," if (@fields);
+ $create_table .= "\n";
+ }
+
+ $create_table .= "\)";
+
+ return($create_table)
+
+} #eosub--_get_create_table_ddl
+#--------------------------------------------------------------------------
+sub _get_create_index_ddl {
+
+=item C<_get_create_index_ddl>
+
+ Description: Protected method to generate a "create index" SQL statement
+ for a given table and index.
+ Parameters: $table_name - the name of the table
+ $index_name - the name of the index
+ $index_fields - a reference to an array of field names
+ $index_type (optional) - specify type of index (e.g., UNIQUE)
+ Returns: a string containing the DDL statement
+
+=cut
+
+ 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) . "\)";
+
+ return($sql);
+
+} #eosub--_get_create_index_ddl
+#--------------------------------------------------------------------------
+
+sub get_add_column_ddl {
+
+=item C<get_add_column_ddl($table, $column, \%definition, $init_value)>
+
+ Description: Generate SQL to add a column to a table.
+ Params: $table - The table containing the column.
+ $column - The name of the column being added.
+ \%definition - The new definition for the column,
+ in standard C<ABSTRACT_SCHEMA> format.
+ $init_value - (optional) An initial value to set
+ the column to. Should already be SQL-quoted
+ if necessary.
+ Returns: An array of SQL statements.
+
+=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_fk_sql($table, $column,
+ $definition->{REFERENCES}));
+ }
+
+ return (@statements);
+}
+
+sub get_add_index_ddl {
+
+=item C<get_add_index_ddl>
+
+ Description: Gets SQL for creating an index.
+ NOTE: Subclasses should not override this function. Instead,
+ if they need to specify a custom CREATE INDEX statement,
+ they should override C<_get_create_index_ddl>
+ Params: $table - The name of the table the index will be on.
+ $name - The name of the new index.
+ $definition - An index definition. Either a hashref
+ with FIELDS and TYPE or an arrayref
+ containing a list of columns.
+ Returns: An array of SQL statements that will create the
+ requested index.
+
+=cut
+
+ 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);
+}
+
+sub get_alter_column_ddl {
+
+=item C<get_alter_column_ddl($table, $column, \%definition)>
+
+ Description: Generate SQL to alter a column in a table.
+ The column that you are altering must exist,
+ and the table that it lives in must exist.
+ Params: $table - The table containing the column.
+ $column - The name of the column being changed.
+ \%definition - The new definition for the column,
+ in standard C<ABSTRACT_SCHEMA> format.
+ $set_nulls_to - A value to set NULL values to, if
+ your new definition is NOT NULL and contains
+ no DEFAULT, and when there is a possibility
+ that the column could contain NULLs. $set_nulls_to
+ should be already SQL-quoted if necessary.
+ Returns: An array of SQL statements.
+
+=cut
+
+ 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 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}) {
+ 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 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;
+}
+
+sub get_drop_index_ddl {
+
+=item C<get_drop_index_ddl($table, $name)>
+
+ Description: Generates SQL statements to drop an index.
+ Params: $table - The table the index is on.
+ $name - The name of the index being dropped.
+ Returns: An array of SQL statements.
+
+=cut
+
+ 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");
+}
+
+sub get_drop_column_ddl {
+
+=item C<get_drop_column_ddl($table, $column)>
+
+ Description: Generate SQL to drop a column from a table.
+ Params: $table - The table containing the column.
+ $column - The name of the column being dropped.
+ Returns: An array of SQL statements.
+
+=cut
+
+ my ($self, $table, $column) = @_;
+ return ("ALTER TABLE $table DROP COLUMN $column");
+}
+
+=item C<get_drop_table_ddl($table)>
+
+ Description: Generate SQL to drop a table from the database.
+ Params: $table - The name of the table to drop.
+ Returns: An array of SQL statements.
+
+=cut
+
+sub get_drop_table_ddl {
+ my ($self, $table) = @_;
+ return ("DROP TABLE $table");
+}
+
+sub get_rename_column_ddl {
+
+=item C<get_rename_column_ddl($table, $old_name, $new_name)>
+
+ Description: Generate SQL to change the name of a column in a table.
+ NOTE: ANSI SQL contains no simple way to rename a column,
+ so this function is ABSTRACT and must be implemented
+ by subclasses.
+ Params: $table - The table containing the column to be renamed.
+ $old_name - The name of the column being renamed.
+ $new_name - The name the column is changing to.
+ Returns: An array of SQL statements.
+
+=cut
+
+ die "ANSI SQL has no way to rename a column, and your database driver\n"
+ . " has not implemented a method.";
+}
+
+
+sub get_rename_table_sql {
+
+=item C<get_rename_table_sql>
+
+=over
+
+=item B<Description>
+
+Gets SQL to rename a table in the database.
+
+=item B<Params>
+
+=over
+
+=item C<$old_name> - The current name of the table.
+
+=item C<$new_name> - The new name of the table.
+
+=back
+
+=item B<Returns>: An array of SQL statements to rename a table.
+
+=back
+
+=cut
+
+ my ($self, $old_name, $new_name) = @_;
+ return ("ALTER TABLE $old_name RENAME TO $new_name");
+}
+
+=item C<delete_table($name)>
+
+ Description: Deletes a table from this Schema object.
+ Dies if you try to delete a table that doesn't exist.
+ Params: $name - The name of the table to delete.
+ Returns: nothing
+
+=cut
+
+sub delete_table {
+ my ($self, $name) = @_;
+
+ die "Attempted to delete nonexistent table '$name'." unless
+ $self->get_table_abstract($name);
+
+ delete $self->{abstract_schema}->{$name};
+ delete $self->{schema}->{$name};
+}
+
+sub get_column_abstract {
+
+=item C<get_column_abstract($table, $column)>
+
+ Description: A column definition from the abstract internal schema.
+ cross-database format.
+ Params: $table - The name of the table
+ $column - The name of the column that you want
+ Returns: A hash reference. For the format, see the docs for
+ C<ABSTRACT_SCHEMA>.
+ Returns undef if the column or table does not exist.
+
+=cut
+
+ 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;
+}
+
+=item C<get_indexes_on_column_abstract($table, $column)>
+
+ Description: Gets a list of indexes that are on a given column.
+ Params: $table - The table the column is on.
+ $column - The name of the column.
+ Returns: Indexes in the standard format of an INDEX
+ entry on a table. That is, key-value pairs
+ where the key is the index name and the value
+ is the index definition.
+ If there are no indexes on that column, we return
+ undef.
+
+=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});
+ }
+ }
+ }
+
+ return %ret_hash;
+}
+
+sub get_index_abstract {
+
+=item C<get_index_abstract($table, $index)>
+
+ Description: Returns an index definition from the internal abstract schema.
+ Params: $table - The table the index is on.
+ $index - The name of the index.
+ Returns: A hash reference representing an index definition.
+ See the C<ABSTRACT_SCHEMA> docs for details.
+ Returns undef if the index does not exist.
+
+=cut
+
+ 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;
+}
+
+=item C<get_table_abstract($table)>
+
+ Description: Gets the abstract definition for a table in this Schema
+ object.
+ Params: $table - The name of the table you want a definition for.
+ Returns: An abstract table definition, or undef if the table doesn't
+ exist.
+
+=cut
+
+sub get_table_abstract {
+ my ($self, $table) = @_;
+ return $self->{abstract_schema}->{$table};
+}
+
+=item C<add_table($name, \%definition)>
+
+ Description: Creates a new table in this Schema object.
+ If you do not specify a definition, we will
+ simply create an empty table.
+ Params: $name - The name for the new table.
+ \%definition (optional) - An abstract definition for
+ the new table.
+ Returns: nothing
+
+=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 => []};
+ }
+}
+
+
+
+sub rename_table {
+
+=item C<rename_table>
+
+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);
+}
+
+sub delete_column {
+
+=item C<delete_column($table, $column)>
+
+ Description: Deletes a column from this Schema object.
+ Params: $table - Name of the table that the column is in.
+ The table must exist, or we will fail.
+ $column - Name of the column to delete.
+ Returns: nothing
+
+=cut
+
+ 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);
+
+ $self->{schema} = dclone($self->{abstract_schema});
+ $self->_adjust_schema();
+}
+
+sub rename_column {
+
+=item C<rename_column($table, $old_name, $new_name)>
+
+ Description: Renames a column on a table in the Schema object.
+ The column that you are renaming must exist.
+ Params: $table - The table the column is on.
+ $old_name - The current name of the column.
+ $new_name - The new name of hte column.
+ Returns: nothing
+
+=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);
+}
+
+sub set_column {
+
+=item C<set_column($table, $column, \%new_def)>
+
+ Description: Changes the definition of a column in this Schema object.
+ If the column doesn't exist, it will be added.
+ The table that you specify must already exist in the Schema.
+ NOTE: This does not affect the database on the disk.
+ Use the C<Bugzilla::DB> "Schema Modification Methods"
+ if you want to do that.
+ Params: $table - The name of the table that the column is on.
+ $column - The name of the column.
+ \%new_def - The new definition for the column, in
+ C<ABSTRACT_SCHEMA> format.
+ Returns: nothing
+
+=cut
+
+ my ($self, $table, $column, $new_def) = @_;
+
+ my $fields = $self->{abstract_schema}{$table}{FIELDS};
+ $self->_set_object($table, $column, $new_def, $fields);
+}
+
+sub set_index {
+
+=item C<set_index($table, $name, $definition)>
+
+ Description: Changes the definition of an index in this Schema object.
+ If the index doesn't exist, it will be added.
+ The table that you specify must already exist in the Schema.
+ NOTE: This does not affect the database on the disk.
+ Use the C<Bugzilla::DB> "Schema Modification Methods"
+ if you want to do that.
+ Params: $table - The table the index is on.
+ $name - The name of the index.
+ $definition - A hashref or an arrayref. An index
+ definition in C<ABSTRACT_SCHEMA> format.
+ Returns: nothing
+
+=cut
+
+ my ($self, $table, $name, $definition) = @_;
+
+ 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);
+}
+
+# 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 $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);
+ }
+
+ $self->{schema} = dclone($self->{abstract_schema});
+ $self->_adjust_schema();
+}
+
+=item C<delete_index($table, $name)>
+
+ Description: Removes an index definition from this Schema object.
+ If the index doesn't exist, we will fail.
+ The table that you specify must exist in the Schema.
+ NOTE: This does not affect the database on the disk.
+ Use the C<Bugzilla::DB> "Schema Modification Methods"
+ if you want to do that.
+ Params: $table - The table the index is on.
+ $name - The name of the index that we're removing.
+ Returns: nothing
+
+=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();
+}
+
+sub columns_equal {
+
+=item C<columns_equal($col_one, $col_two)>
+
+ Description: Tells you if two columns have entirely identical definitions.
+ The TYPE field's value will be compared case-insensitive.
+ However, all other fields will be case-sensitive.
+ Params: $col_one, $col_two - The columns to compare. Hash
+ references, in C<ABSTRACT_SCHEMA> format.
+ Returns: C<1> if the columns are identical, C<0> if they are not.
+
+=back
+
+=cut
+
+ 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});
+
+ # 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 ($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;
+}
+
+
+=head1 SERIALIZATION/DESERIALIZATION
+
+=over 4
+
+=item C<serialize_abstract()>
+
+ Description: Serializes the "abstract" schema into a format
+ that deserialize_abstract() can read in. This is
+ a method, called on a Schema instance.
+ Parameters: none
+ Returns: A scalar containing the serialized, abstract schema.
+ Do not attempt to manipulate this data directly,
+ as the format may change at any time in the future.
+ The only thing you should do with the returned value
+ is either store it somewhere (coupled with appropriate
+ SCHEMA_VERSION) or deserialize it.
+
+=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});
+}
+
+=item C<deserialize_abstract($serialized, $version)>
+
+ Description: Used for when you've read a serialized Schema off the disk,
+ and you want a Schema object that represents that data.
+ Params: $serialized - scalar. The serialized data.
+ $version - A number in the format X.YZ. The "version"
+ of the Schema that did the serialization.
+ See the docs for C<SCHEMA_VERSION> for more details.
+ Returns: A Schema object. It will have the methods of (and work
+ in the same fashion as) the current version of Schema.
+ However, it will represent the serialized data instead of
+ ABSTRACT_SCHEMA.
+=cut
+
+sub deserialize_abstract {
+ my ($class, $serialized, $version) = @_;
+
+ my $thawed_hash;
+ if (int($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')};
+ }
+
+ return $class->new(undef, $thawed_hash);
+}
+
+#####################################################################
+# Class Methods
+#####################################################################
+
+=back
+
+=head1 CLASS METHODS
+
+These methods are generally called on the class instead of on a specific
+object.
+
+=over
+
+=item C<get_empty_schema()>
+
+ Description: Returns a Schema that has no tables. In effect, this
+ Schema is totally "empty."
+ Params: none
+ Returns: A "empty" Schema object.
+
+=back
+
+=cut
+
+sub get_empty_schema {
+ my ($class) = @_;
+ return $class->deserialize_abstract(Dumper({}), SCHEMA_VERSION);
+}
+
+1;
+
+__END__
+
+=head1 ABSTRACT DATA TYPES
+
+The size and range data provided here is only
+intended as a guide. See your database's Bugzilla
+module (in this directory) for the most up-to-date
+values for these data types. The following
+abstract data types are used:
+
+=over 4
+
+=item C<BOOLEAN>
+
+Logical value 0 or 1 where 1 is true, 0 is false.
+
+=item C<INT1>
+
+Integer values (-128 - 127 or 0 - 255 unsigned).
+
+=item C<INT2>
+
+Integer values (-32,768 - 32767 or 0 - 65,535 unsigned).
+
+=item C<INT3>
+
+Integer values (-8,388,608 - 8,388,607 or 0 - 16,777,215 unsigned)
+
+=item C<INT4>
+
+Integer values (-2,147,483,648 - 2,147,483,647 or 0 - 4,294,967,295
+unsigned)
+
+=item C<SMALLSERIAL>
+
+An auto-increment L</INT1>
+
+=item C<MEDIUMSERIAL>
+
+An auto-increment L</INT3>
+
+=item C<INTSERIAL>
+
+An auto-increment L</INT4>
+
+=item C<TINYTEXT>
+
+Variable length string of characters up to 255 (2^8 - 1) characters wide.
+
+=item C<MEDIUMTEXT>
+
+Variable length string of characters up to 4000 characters wide.
+May be longer on some databases.
+
+=item C<LONGTEXT>
+
+Variable length string of characters up to 16M (2^24 - 1) characters wide.
+
+=item C<LONGBLOB>
+
+Variable length string of binary data up to 4M (2^32 - 1) bytes wide
+
+=item C<DATETIME>
+
+DATETIME support varies from database to database, however, it's generally
+safe to say that DATETIME entries support all date/time combinations greater
+than 1900-01-01 00:00:00. Note that the format used is C<YYYY-MM-DD hh:mm:ss>
+to be safe, though it's possible that your database may not require
+leading zeros. For greatest compatibility, however, please make sure dates
+are formatted as above for queries to guarantee consistent results.
+
+=back
+
+Database-specific subclasses should define the implementation for these data
+types as a hash reference stored internally in the schema object as
+C<db_specific>. This is typically done in overridden L<_initialize> method.
+
+The following abstract boolean values should also be defined on a
+database-specific basis:
+
+=over 4
+
+=item C<TRUE>
+
+=item C<FALSE>
+
+=back
+
+=head1 SEE ALSO
+
+L<Bugzilla::DB>
+
+L<http://www.bugzilla.org/docs/developer.html#sql-schema>
+
+=cut
diff --git a/Bugzilla/DB/Schema/Mysql.pm b/Bugzilla/DB/Schema/Mysql.pm
new file mode 100644
index 000000000..8c9ea2dda
--- /dev/null
+++ b/Bugzilla/DB/Schema/Mysql.pm
@@ -0,0 +1,399 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Andrew Dunstan <andrew@dunslane.net>,
+# Edward J. Sabol <edwardjsabol@iname.com>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::DB::Schema::Mysql;
+
+###############################################################################
+#
+# DB::Schema implementation for MySQL
+#
+###############################################################################
+
+use strict;
+use Bugzilla::Error;
+
+use base qw(Bugzilla::DB::Schema);
+
+# This is for column_info_to_column, to know when a tinyint is a
+# boolean and when it's really a tinyint. This only has to be accurate
+# up to and through 2.19.3, because that's the only time we need
+# column_info_to_column.
+#
+# This is basically a hash of tables/columns, with one entry for each column
+# that should be interpreted as a BOOLEAN instead of as an INT1 when
+# reading in the Schema from the disk. The values are discarded; I just
+# used "1" for simplicity.
+#
+# 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}
+};
+
+# Maps the db_specific hash backwards, for use in column_info_to_column.
+use constant REVERSE_MAPPING => {
+ # Boolean and the SERIAL fields are handled in column_info_to_column,
+ # and so don't have an entry here.
+ TINYINT => 'INT1',
+ SMALLINT => 'INT2',
+ MEDIUMINT => 'INT3',
+ INTEGER => 'INT4',
+
+ # All the other types have the same name in their abstract version
+ # as in their db-specific version, so no reverse mapping is needed.
+};
+
+use constant MYISAM_TABLES => qw(bugs_fulltext);
+
+#------------------------------------------------------------------------------
+sub _initialize {
+
+ my $self = shift;
+
+ $self = $self->SUPER::_initialize(@_);
+
+ $self->{db_specific} = {
+
+ BOOLEAN => 'tinyint',
+ FALSE => '0',
+ TRUE => '1',
+
+ INT1 => 'tinyint',
+ INT2 => 'smallint',
+ INT3 => 'mediumint',
+ INT4 => 'integer',
+
+ SMALLSERIAL => 'smallint auto_increment',
+ MEDIUMSERIAL => 'mediumint auto_increment',
+ INTSERIAL => 'integer auto_increment',
+
+ TINYTEXT => 'tinytext',
+ MEDIUMTEXT => 'mediumtext',
+ LONGTEXT => 'mediumtext',
+
+ LONGBLOB => 'longblob',
+
+ DATETIME => 'datetime',
+
+ };
+
+ $self->_adjust_schema;
+
+ return $self;
+
+} #eosub--_initialize
+#------------------------------------------------------------------------------
+sub _get_create_table_ddl {
+ # Extend superclass method to specify the MYISAM storage engine.
+ # Returns a "create table" SQL statement.
+
+ my($self, $table) = @_;
+
+ my $charset = Bugzilla->dbh->bz_db_is_utf8 ? "CHARACTER SET utf8" : '';
+ my $type = grep($_ eq $table, MYISAM_TABLES) ? 'MYISAM' : 'InnoDB';
+ return($self->SUPER::_get_create_table_ddl($table)
+ . " ENGINE = $type $charset");
+
+} #eosub--_get_create_table_ddl
+#------------------------------------------------------------------------------
+sub _get_create_index_ddl {
+ # Extend superclass method to create FULLTEXT indexes on text fields.
+ # Returns a "create index" SQL statement.
+
+ my($self, $table_name, $index_name, $index_fields, $index_type) = @_;
+
+ my $sql = "CREATE ";
+ $sql .= "$index_type " if ($index_type eq 'UNIQUE'
+ || $index_type eq 'FULLTEXT');
+ $sql .= "INDEX \`$index_name\` ON $table_name \(" .
+ join(", ", @$index_fields) . "\)";
+
+ return($sql);
+
+} #eosub--_get_create_index_ddl
+#--------------------------------------------------------------------
+
+sub get_create_database_sql {
+ my ($self, $name) = @_;
+ # We only create as utf8 if we have no params (meaning we're doing
+ # a new installation) or if the utf8 param is on.
+ my $create_utf8 = Bugzilla->params->{'utf8'}
+ || !defined Bugzilla->params->{'utf8'};
+ my $charset = $create_utf8 ? "CHARACTER SET utf8" : '';
+ return ("CREATE DATABASE $name $charset");
+}
+
+# MySQL has a simpler ALTER TABLE syntax than ANSI.
+sub get_alter_column_ddl {
+ my ($self, $table, $column, $new_def, $set_nulls_to) = @_;
+ my $old_def = $self->get_column($table, $column);
+ my %new_def_copy = %$new_def;
+ if ($old_def->{PRIMARYKEY} && $new_def->{PRIMARYKEY}) {
+ # If a column stays a primary key do NOT specify PRIMARY KEY in the
+ # ALTER TABLE statement. This avoids a MySQL error that two primary
+ # keys are not allowed.
+ delete $new_def_copy{PRIMARYKEY};
+ }
+
+ my @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});
+ }
+ }
+ 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;
+}
+
+sub get_drop_index_ddl {
+ my ($self, $table, $name) = @_;
+ return ("DROP INDEX \`$name\` ON $table");
+}
+
+# A special function for MySQL, for renaming a lot of indexes.
+# Index renames is a hash, where the key is a string - the
+# old names of the index, and the value is a hash - the index
+# definition that we're renaming to, with an extra key of "NAME"
+# that contains the new index name.
+# The indexes in %indexes must be in hashref format.
+sub get_rename_indexes_ddl {
+ my ($self, $table, %indexes) = @_;
+ my @keys = keys %indexes or return ();
+
+ my $sql = "ALTER TABLE $table ";
+
+ foreach my $old_name (@keys) {
+ my $name = $indexes{$old_name}->{NAME};
+ my $type = $indexes{$old_name}->{TYPE};
+ $type ||= 'INDEX';
+ my $fields = join(',', @{$indexes{$old_name}->{FIELDS}});
+ # $old_name needs to be escaped, sometimes, because it was
+ # a reserved word.
+ $old_name = '`' . $old_name . '`';
+ $sql .= " ADD $type $name ($fields), DROP INDEX $old_name,";
+ }
+ # Remove the last comma.
+ chop($sql);
+ return ($sql);
+}
+
+sub get_set_serial_sql {
+ my ($self, $table, $column, $value) = @_;
+ return ("ALTER TABLE $table AUTO_INCREMENT = $value");
+}
+
+# Converts a DBI column_info output to an abstract column definition.
+# Expects to only be called by Bugzila::DB::Mysql::_bz_build_schema_from_disk,
+# although there's a chance that it will also work properly if called
+# elsewhere.
+sub column_info_to_column {
+ my ($self, $column_info) = @_;
+
+ # Unfortunately, we have to break Schema's normal "no database"
+ # barrier a few times in this function.
+ my $dbh = Bugzilla->dbh;
+
+ my $table = $column_info->{TABLE_NAME};
+ my $col_name = $column_info->{COLUMN_NAME};
+
+ my $column = {};
+
+ ($column->{NOTNULL} = 1) if $column_info->{NULLABLE} == 0;
+
+ if ($column_info->{mysql_is_pri_key}) {
+ # In MySQL, if a table has no PK, but it has a UNIQUE index,
+ # that index will show up as the PK. So we have to eliminate
+ # that possibility.
+ # Unfortunately, the only way to definitely solve this is
+ # to break Schema's standard of not touching the live database
+ # and check if the index called PRIMARY is on that field.
+ my $pri_index = $dbh->bz_index_info_real($table, 'PRIMARY');
+ if ( $pri_index && grep($_ eq $col_name, @{$pri_index->{FIELDS}}) ) {
+ $column->{PRIMARYKEY} = 1;
+ }
+ }
+
+ # MySQL frequently defines a default for a field even when we
+ # didn't explicitly set one. So we have to have some special
+ # hacks to determine whether or not we should actually put
+ # a default in the abstract schema for this field.
+ if (defined $column_info->{COLUMN_DEF}) {
+ # The defaults that MySQL inputs automatically are usually
+ # something that would be considered "false" by perl, either
+ # a 0 or an empty string. (Except for datetime and decimal
+ # fields, which have their own special auto-defaults.)
+ #
+ # Here's how we handle this: If it exists in the schema
+ # without a default, then we don't use the default. If it
+ # doesn't exist in the schema, then we're either going to
+ # be dropping it soon, or it's a custom end-user column, in which
+ # case having a bogus default won't harm anything.
+ my $schema_column = $self->get_column($table, $col_name);
+ unless ( (!$column_info->{COLUMN_DEF}
+ || $column_info->{COLUMN_DEF} eq '0000-00-00 00:00:00'
+ || $column_info->{COLUMN_DEF} eq '0.00')
+ && $schema_column
+ && !exists $schema_column->{DEFAULT}) {
+
+ my $default = $column_info->{COLUMN_DEF};
+ # Schema uses '0' for the defaults for decimal fields.
+ $default = 0 if $default =~ /^0\.0+$/;
+ # If we're not a number, we're a string and need to be
+ # quoted.
+ $default = $dbh->quote($default) if !($default =~ /^(-)?(\d+)(.\d+)?$/);
+ $column->{DEFAULT} = $default;
+ }
+ }
+
+ my $type = $column_info->{TYPE_NAME};
+
+ # Certain types of columns need the size/precision appended.
+ if ($type =~ /CHAR$/ || $type eq 'DECIMAL') {
+ # This is nicely lowercase and has the size/precision appended.
+ $type = $column_info->{mysql_type_name};
+ }
+
+ # If we're a tinyint, we could be either a BOOLEAN or an INT1.
+ # Only the BOOLEAN_MAP knows the difference.
+ elsif ($type eq 'TINYINT' && exists BOOLEAN_MAP->{$table}
+ && exists BOOLEAN_MAP->{$table}->{$col_name}) {
+ $type = 'BOOLEAN';
+ if (exists $column->{DEFAULT}) {
+ $column->{DEFAULT} = $column->{DEFAULT} ? 'TRUE' : 'FALSE';
+ }
+ }
+
+ # We also need to check if we're an auto_increment field.
+ elsif ($type =~ /INT/) {
+ # Unfortunately, the only way to do this in DBI is to query the
+ # database, so we have to break the rule here that Schema normally
+ # doesn't touch the live DB.
+ my $ref_sth = $dbh->prepare(
+ "SELECT $col_name FROM $table LIMIT 1");
+ $ref_sth->execute;
+ if ($ref_sth->{mysql_is_auto_increment}->[0]) {
+ if ($type eq 'MEDIUMINT') {
+ $type = 'MEDIUMSERIAL';
+ }
+ elsif ($type eq 'SMALLINT') {
+ $type = 'SMALLSERIAL';
+ }
+ else {
+ $type = 'INTSERIAL';
+ }
+ }
+ $ref_sth->finish;
+
+ }
+
+ # For all other db-specific types, check if they exist in
+ # REVERSE_MAPPING and use the type found there.
+ if (exists REVERSE_MAPPING->{$type}) {
+ $type = REVERSE_MAPPING->{$type};
+ }
+
+ $column->{TYPE} = $type;
+
+ #print "$table.$col_name: " . Data::Dumper->Dump([$column]) . "\n";
+
+ return $column;
+}
+
+sub get_rename_column_ddl {
+ my ($self, $table, $old_name, $new_name) = @_;
+ my $def = $self->get_type_ddl($self->get_column($table, $old_name));
+ # MySQL doesn't like having the PRIMARY KEY statement in a rename.
+ $def =~ s/PRIMARY KEY//i;
+ return ("ALTER TABLE $table CHANGE COLUMN $old_name $new_name $def");
+}
+
+1;
diff --git a/Bugzilla/DB/Schema/Oracle.pm b/Bugzilla/DB/Schema/Oracle.pm
new file mode 100644
index 000000000..6011cecfc
--- /dev/null
+++ b/Bugzilla/DB/Schema/Oracle.pm
@@ -0,0 +1,439 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Oracle Corporation.
+# Portions created by Oracle are Copyright (C) 2007 Oracle Corporation.
+# All Rights Reserved.
+#
+# Contributor(s): Lance Larsh <lance.larsh@oracle.com>
+# Xiaoou Wu <xiaoou.wu@oracle.com>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::DB::Schema::Oracle;
+
+###############################################################################
+#
+# DB::Schema implementation for Oracle
+#
+###############################################################################
+
+use strict;
+
+use base qw(Bugzilla::DB::Schema);
+use Carp qw(confess);
+use Bugzilla::Util;
+
+use constant ADD_COLUMN => 'ADD';
+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.
+use constant MAX_IDENTIFIER_LEN => 27;
+
+#------------------------------------------------------------------------------
+sub _initialize {
+
+ my $self = shift;
+
+ $self = $self->SUPER::_initialize(@_);
+
+ $self->{db_specific} = {
+
+ BOOLEAN => 'integer',
+ FALSE => '0',
+ TRUE => '1',
+
+ INT1 => 'integer',
+ INT2 => 'integer',
+ INT3 => 'integer',
+ INT4 => 'integer',
+
+ SMALLSERIAL => 'integer',
+ MEDIUMSERIAL => 'integer',
+ INTSERIAL => 'integer',
+
+ TINYTEXT => 'varchar(255)',
+ MEDIUMTEXT => 'varchar(4000)',
+ LONGTEXT => 'clob',
+
+ LONGBLOB => 'blob',
+
+ DATETIME => 'date',
+
+ };
+
+ $self->_adjust_schema;
+
+ return $self;
+
+} #eosub--_initialize
+#--------------------------------------------------------------------
+
+sub get_table_ddl {
+ my $self = shift;
+ my $table = shift;
+ unshift @_, $table;
+ my @ddl = $self->SUPER::get_table_ddl(@_);
+
+ my @fields = @{ $self->{abstract_schema}{$table}{FIELDS} || [] };
+ while (@fields) {
+ my $field_name = shift @fields;
+ my $field_info = shift @fields;
+ # Create triggers to deal with empty string.
+ if ( $field_info->{TYPE} =~ /varchar|TEXT/i
+ && $field_info->{NOTNULL} ) {
+ push (@ddl, _get_notnull_trigger_ddl($table, $field_name));
+ }
+ # Create sequences and triggers to emulate SERIAL datatypes.
+ if ( $field_info->{TYPE} =~ /SERIAL/i ) {
+ push (@ddl, $self->_get_create_seq_ddl($table, $field_name));
+ }
+ }
+ return @ddl;
+
+} #eosub--get_table_ddl
+
+# Extend superclass method to create Oracle Text indexes if index type
+# is FULLTEXT from schema. Returns a "create index" SQL statement.
+sub _get_create_index_ddl {
+
+ my ($self, $table_name, $index_name, $index_fields, $index_type) = @_;
+ $index_name = "idx_" . $self->_hash_identifier($index_name);
+ if ($index_type eq 'FULLTEXT') {
+ my $sql = "CREATE INDEX $index_name ON $table_name ("
+ . join(',',@$index_fields)
+ . ") INDEXTYPE IS CTXSYS.CONTEXT "
+ . " PARAMETERS('LEXER BZ_LEX SYNC(ON COMMIT)')" ;
+ return $sql;
+ }
+
+ return($self->SUPER::_get_create_index_ddl($table_name, $index_name,
+ $index_fields, $index_type));
+
+}
+
+sub get_drop_index_ddl {
+ my $self = shift;
+ my ($table, $name) = @_;
+
+ $name = 'idx_' . $self->_hash_identifier($name);
+ return $self->SUPER::get_drop_index_ddl($table, $name);
+}
+
+# Oracle supports the use of FOREIGN KEY integrity constraints
+# to define the referential integrity actions, including:
+# - Update and delete No Action (default)
+# - Delete CASCADE
+# - Delete SET NULL
+sub get_fk_ddl {
+ my $self = 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;
+
+ 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;
+CREATE OR REPLACE TRIGGER ${fk_name}_UC
+ 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 ${fk_name}_UC;
+END
+ push(@sql, $trigger);
+ }
+
+ 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;
+}
+
+sub _get_fk_name {
+ my ($self, $table, $column, $references) = @_;
+ my $to_table = $references->{TABLE};
+ my $to_column = $references->{COLUMN};
+ my $fk_name = "${table}_${column}_${to_table}_${to_column}";
+ $fk_name = "fk_" . $self->_hash_identifier($fk_name);
+
+ return $fk_name;
+}
+
+sub get_alter_column_ddl {
+ my ($self, $table, $column, $new_def, $set_nulls_to) = @_;
+
+ my @statements;
+ my $old_def = $self->get_column_abstract($table, $column);
+ my $specific = $self->{db_specific};
+
+ # If the types have changed, we have to deal with that.
+ if (uc(trim($old_def->{TYPE})) ne uc(trim($new_def->{TYPE}))) {
+ push(@statements, $self->_get_alter_type_sql($table, $column,
+ $new_def, $old_def));
+ }
+
+ my $default = $new_def->{DEFAULT};
+ my $default_old = $old_def->{DEFAULT};
+
+ 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)");
+ }
+ # If we went from being a PK to not being a PK
+ elsif ( $old_def->{PRIMARYKEY} && !$new_def->{PRIMARYKEY} ) {
+ push(@statements, "ALTER TABLE $table DROP PRIMARY KEY");
+ }
+
+ return @statements;
+}
+
+sub _get_alter_type_sql {
+ my ($self, $table, $column, $new_def, $old_def) = @_;
+ my @statements;
+
+ my $type = $new_def->{TYPE};
+ $type = $self->{db_specific}->{$type}
+ if exists $self->{db_specific}->{$type};
+
+ if ($type =~ /serial/i && $old_def->{TYPE} !~ /serial/i) {
+ die("You cannot specify a DEFAULT on a SERIAL-type column.")
+ if $new_def->{DEFAULT};
+ }
+
+ if ( ($old_def->{TYPE} =~ /LONGTEXT/i && $new_def->{TYPE} !~ /LONGTEXT/i)
+ || ($old_def->{TYPE} !~ /LONGTEXT/i && $new_def->{TYPE} =~ /LONGTEXT/i)
+ ) {
+ # LONG to VARCHAR or VARCHAR to LONG is not allowed in Oracle,
+ # just a way to work around.
+ # Determine whether column_temp is already exist.
+ my $dbh=Bugzilla->dbh;
+ my $column_exist = $dbh->selectcol_arrayref(
+ "SELECT CNAME FROM COL WHERE TNAME = UPPER(?) AND
+ CNAME = UPPER(?)", undef,$table,$column . "_temp");
+ if(!@$column_exist) {
+ push(@statements,
+ "ALTER TABLE $table ADD ${column}_temp $type");
+ }
+ push(@statements, "UPDATE $table SET ${column}_temp = $column");
+ push(@statements, "COMMIT");
+ push(@statements, "ALTER TABLE $table DROP COLUMN $column");
+ push(@statements,
+ "ALTER TABLE $table RENAME COLUMN ${column}_temp TO $column");
+ } else {
+ push(@statements, "ALTER TABLE $table MODIFY $column $type");
+ }
+
+ if ($new_def->{TYPE} =~ /serial/i && $old_def->{TYPE} !~ /serial/i) {
+ push(@statements, _get_create_seq_ddl($table, $column));
+ }
+
+ # If this column is no longer SERIAL, we need to drop the sequence
+ # that went along with it.
+ if ($old_def->{TYPE} =~ /serial/i && $new_def->{TYPE} !~ /serial/i) {
+ push(@statements, "DROP SEQUENCE ${table}_${column}_SEQ");
+ push(@statements, "DROP TRIGGER ${table}_${column}_TR");
+ }
+
+ # If this column is changed to type TEXT/VARCHAR, we need to deal with
+ # empty string.
+ if ( $old_def->{TYPE} !~ /varchar|text/i
+ && $new_def->{TYPE} =~ /varchar|text/i
+ && $new_def->{NOTNULL} )
+ {
+ push (@statements, _get_notnull_trigger_ddl($table, $column));
+ }
+ # If this column is no longer TEXT/VARCHAR, we need to drop the trigger
+ # that went along with it.
+ if ( $old_def->{TYPE} =~ /varchar|text/i
+ && $old_def->{NOTNULL}
+ && $new_def->{TYPE} !~ /varchar|text/i )
+ {
+ push(@statements, "DROP TRIGGER ${table}_${column}");
+ }
+ return @statements;
+}
+
+sub get_rename_column_ddl {
+ my ($self, $table, $old_name, $new_name) = @_;
+ if (lc($old_name) eq lc($new_name)) {
+ # if the only change is a case change, return an empty list.
+ return ();
+ }
+ my @sql = ("ALTER TABLE $table RENAME COLUMN $old_name TO $new_name");
+ my $def = $self->get_column_abstract($table, $old_name);
+ if ($def->{TYPE} =~ /SERIAL/i) {
+ # We have to rename the series also, and fix the default of the series.
+ push(@sql, "RENAME ${table}_${old_name}_SEQ TO
+ ${table}_${new_name}_seq");
+ my $serial_sql =
+ "CREATE OR REPLACE TRIGGER ${table}_${new_name}_TR "
+ . " BEFORE INSERT ON ${table} "
+ . " FOR EACH ROW "
+ . " BEGIN "
+ . " SELECT ${table}_${new_name}_SEQ.NEXTVAL "
+ . " INTO :NEW.${new_name} FROM DUAL; "
+ . " END;";
+ push(@sql, $serial_sql);
+ push(@sql, "DROP TRIGGER ${table}_${old_name}_TR");
+ }
+ if ($def->{TYPE} =~ /varchar|text/i && $def->{NOTNULL} ) {
+ push(@sql, _get_notnull_trigger_ddl($table,$new_name));
+ push(@sql, "DROP TRIGGER ${table}_${old_name}");
+ }
+ return @sql;
+}
+
+sub _get_notnull_trigger_ddl {
+ my ($table, $column) = @_;
+
+ my $notnull_sql = "CREATE OR REPLACE TRIGGER "
+ . " ${table}_${column}"
+ . " BEFORE INSERT OR UPDATE ON ". $table
+ . " FOR EACH ROW"
+ . " BEGIN "
+ . " IF :NEW.". $column ." IS NULL THEN "
+ . " SELECT '" . Bugzilla::DB::Oracle->EMPTY_STRING
+ . "' INTO :NEW.". $column ." FROM DUAL; "
+ . " END IF; "
+ . " END ".$table.";";
+ return $notnull_sql;
+}
+
+sub _get_create_seq_ddl {
+ my ($self, $table, $column, $start_with) = @_;
+ $start_with ||= 1;
+ my @ddl;
+ my $seq_name = "${table}_${column}_SEQ";
+ my $seq_sql = "CREATE SEQUENCE $seq_name "
+ . " INCREMENT BY 1 "
+ . " START WITH $start_with "
+ . " NOMAXVALUE "
+ . " NOCYCLE "
+ . " NOCACHE";
+ my $serial_sql = "CREATE OR REPLACE TRIGGER ${table}_${column}_TR "
+ . " BEFORE INSERT ON ${table} "
+ . " FOR EACH ROW "
+ . " BEGIN "
+ . " SELECT ${seq_name}.NEXTVAL "
+ . " INTO :NEW.${column} FROM DUAL; "
+ . " END;";
+ push (@ddl, $seq_sql);
+ push (@ddl, $serial_sql);
+
+ return @ddl;
+}
+
+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_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");
+ }
+ return @sql;
+}
+
+1;
diff --git a/Bugzilla/DB/Schema/Pg.pm b/Bugzilla/DB/Schema/Pg.pm
new file mode 100644
index 000000000..0b10acbb0
--- /dev/null
+++ b/Bugzilla/DB/Schema/Pg.pm
@@ -0,0 +1,173 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Andrew Dunstan <andrew@dunslane.net>,
+# Edward J. Sabol <edwardjsabol@iname.com>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::DB::Schema::Pg;
+
+###############################################################################
+#
+# DB::Schema implementation for PostgreSQL
+#
+###############################################################################
+
+use strict;
+use base qw(Bugzilla::DB::Schema);
+use Storable qw(dclone);
+
+#------------------------------------------------------------------------------
+sub _initialize {
+
+ my $self = shift;
+
+ $self = $self->SUPER::_initialize(@_);
+
+ # Remove FULLTEXT index types from the schemas.
+ foreach my $table (keys %{ $self->{schema} }) {
+ if ($self->{schema}{$table}{INDEXES}) {
+ foreach my $index (@{ $self->{schema}{$table}{INDEXES} }) {
+ if (ref($index) eq 'HASH') {
+ delete($index->{TYPE}) if (exists $index->{TYPE}
+ && $index->{TYPE} eq 'FULLTEXT');
+ }
+ }
+ foreach my $index (@{ $self->{abstract_schema}{$table}{INDEXES} }) {
+ if (ref($index) eq 'HASH') {
+ delete($index->{TYPE}) if (exists $index->{TYPE}
+ && $index->{TYPE} eq 'FULLTEXT');
+ }
+ }
+ }
+ }
+
+ $self->{db_specific} = {
+
+ BOOLEAN => 'smallint',
+ FALSE => '0',
+ TRUE => '1',
+
+ INT1 => 'integer',
+ INT2 => 'integer',
+ INT3 => 'integer',
+ INT4 => 'integer',
+
+ SMALLSERIAL => 'serial unique',
+ MEDIUMSERIAL => 'serial unique',
+ INTSERIAL => 'serial unique',
+
+ TINYTEXT => 'varchar(255)',
+ MEDIUMTEXT => 'text',
+ LONGTEXT => 'text',
+
+ LONGBLOB => 'bytea',
+
+ DATETIME => 'timestamp(0) without time zone',
+
+ };
+
+ $self->_adjust_schema;
+
+ return $self;
+
+} #eosub--_initialize
+#--------------------------------------------------------------------
+
+sub get_rename_column_ddl {
+ my ($self, $table, $old_name, $new_name) = @_;
+ if (lc($old_name) eq lc($new_name)) {
+ # if the only change is a case change, return an empty list, since Pg
+ # is case-insensitive and will return an error about a duplicate name
+ return ();
+ }
+ my @sql = ("ALTER TABLE $table RENAME COLUMN $old_name TO $new_name");
+ my $def = $self->get_column_abstract($table, $old_name);
+ if ($def->{TYPE} =~ /SERIAL/i) {
+ # We have to rename the series also, and fix the default of the series.
+ push(@sql, "ALTER TABLE ${table}_${old_name}_seq
+ RENAME TO ${table}_${new_name}_seq");
+ push(@sql, "ALTER TABLE $table ALTER COLUMN $new_name
+ SET DEFAULT NEXTVAL('${table}_${new_name}_seq')");
+ }
+ return @sql;
+}
+
+sub get_rename_table_sql {
+ my ($self, $old_name, $new_name) = @_;
+ if (lc($old_name) eq lc($new_name)) {
+ # if the only change is a case change, return an empty list, since Pg
+ # is case-insensitive and will return an error about a duplicate name
+ return ();
+ }
+ return ("ALTER TABLE $old_name RENAME TO $new_name");
+}
+
+sub get_set_serial_sql {
+ 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");
+ push(@statements, "SELECT setval('${table}_${column}_seq',
+ MAX($table.$column))
+ FROM $table");
+ push(@statements, "ALTER TABLE $table ALTER COLUMN $column
+ SET DEFAULT nextval('${table}_${column}_seq')");
+ }
+
+ # If this column is no longer SERIAL, we need to drop the sequence
+ # that went along with it.
+ if ($old_def->{TYPE} =~ /serial/i && $new_def->{TYPE} !~ /serial/i) {
+ push(@statements, "ALTER TABLE $table ALTER COLUMN $column
+ DROP DEFAULT");
+ # XXX Pg actually won't let us drop the sequence, even though it's
+ # no longer in use. So we harmlessly leave behind a sequence
+ # that does nothing.
+ #push(@statements, "DROP SEQUENCE ${table}_${column}_seq");
+ }
+
+ return @statements;
+}
+
+1;
diff --git a/Bugzilla/Error.pm b/Bugzilla/Error.pm
new file mode 100644
index 000000000..649fdd486
--- /dev/null
+++ b/Bugzilla/Error.pm
@@ -0,0 +1,270 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Bradley Baetz <bbaetz@acm.org>
+# Marc Schumann <wurblzap@gmail.com>
+# Frédéric Buclin <LpSolit@gmail.com>
+
+package Bugzilla::Error;
+
+use strict;
+use base qw(Exporter);
+
+@Bugzilla::Error::EXPORT = qw(ThrowCodeError ThrowTemplateError ThrowUserError);
+
+use Bugzilla::Constants;
+use Bugzilla::WebService::Constants;
+use Bugzilla::Util;
+
+use Carp;
+use Data::Dumper;
+use Date::Format;
+
+# We cannot use $^S to detect if we are in an eval(), because mod_perl
+# already eval'uates everything, so $^S = 1 in all cases under mod_perl!
+sub _in_eval {
+ my $in_eval = 0;
+ for (my $stack = 1; my $sub = (caller($stack))[3]; $stack++) {
+ last if $sub =~ /^ModPerl/;
+ $in_eval = 1 if $sub =~ /^\(eval\)/;
+ }
+ return $in_eval;
+}
+
+sub _throw_error {
+ my ($name, $error, $vars) = @_;
+ my $dbh = Bugzilla->dbh;
+ $vars ||= {};
+
+ $vars->{error} = $error;
+ # 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();
+
+ # Make sure any transaction is rolled back (if supported).
+ # If we are within an eval(), do not roll back transactions as we are
+ # eval'uating some test on purpose.
+ $dbh->bz_rollback_transaction() if ($dbh->bz_in_transaction() && !_in_eval());
+
+ my $datadir = bz_locations()->{'datadir'};
+ # If a writable $datadir/errorlog exists, log error details there.
+ if (-w "$datadir/errorlog") {
+ require Data::Dumper;
+ my $mesg = "";
+ for (1..75) { $mesg .= "-"; };
+ $mesg .= "\n[$$] " . time2str("%D %H:%M:%S ", time());
+ $mesg .= "$name $error ";
+ $mesg .= 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;
+ if (Bugzilla->error_mode == ERROR_MODE_WEBPAGE) {
+ print Bugzilla->cgi->header();
+ $template->process($name, $vars)
+ || ThrowTemplateError($template->error());
+ }
+ # 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.
+ elsif (Bugzilla->error_mode == ERROR_MODE_TEST) {
+ die Dumper($vars);
+ }
+ else {
+ my $message;
+ $template->process($name, $vars, \$message)
+ || ThrowTemplateError($template->error());
+ if (Bugzilla->error_mode == ERROR_MODE_DIE) {
+ die("$message\n");
+ }
+ elsif (Bugzilla->error_mode == ERROR_MODE_DIE_SOAP_FAULT
+ || Bugzilla->error_mode == ERROR_MODE_JSON_RPC)
+ {
+ # Clone the hash so we aren't modifying the constant.
+ my %error_map = %{ WS_ERROR_CODE() };
+ require Bugzilla::Hook;
+ Bugzilla::Hook::process('webservice_error_codes',
+ { error_map => \%error_map });
+ my $code = $error_map{$error};
+ if (!$code) {
+ $code = ERROR_UNKNOWN_FATAL if $name =~ /code/i;
+ $code = ERROR_UNKNOWN_TRANSIENT if $name =~ /user/i;
+ }
+
+ if (Bugzilla->error_mode == ERROR_MODE_DIE_SOAP_FAULT) {
+ die SOAP::Fault->faultcode($code)->faultstring($message);
+ }
+ else {
+ my $server = Bugzilla->_json_server;
+ # 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,
+ 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;
+}
+
+sub ThrowUserError {
+ _throw_error("global/user-error.html.tmpl", @_);
+}
+
+sub ThrowCodeError {
+ _throw_error("global/code-error.html.tmpl", @_);
+}
+
+sub ThrowTemplateError {
+ my ($template_err) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ # Make sure the transaction is rolled back (if supported).
+ $dbh->bz_rollback_transaction() if $dbh->bz_in_transaction();
+
+ my $vars = {};
+ if (Bugzilla->error_mode == ERROR_MODE_DIE) {
+ die("error: template error: $template_err");
+ }
+
+ $vars->{'template_error_msg'} = $template_err;
+ $vars->{'error'} = "template_error";
+
+ my $template = Bugzilla->template;
+
+ # Try a template first; but if this one fails too, fall back
+ # on plain old print statements.
+ if (!$template->process("global/code-error.html.tmpl", $vars)) {
+ my $maintainer = Bugzilla->params->{'maintainer'};
+ my $error = html_quote($vars->{'template_error_msg'});
+ my $error2 = html_quote($template->error());
+ print <<END;
+ <tt>
+ <p>
+ Bugzilla has suffered an internal error. Please save this page and
+ send it to $maintainer with details of what you were doing at the
+ time this message appeared.
+ </p>
+ <script type="text/javascript"> <!--
+ document.write("<p>URL: " +
+ document.location.href.replace(/&/g,"&amp;")
+ .replace(/</g,"&lt;")
+ .replace(/>/g,"&gt;") + "</p>");
+ // -->
+ </script>
+ <p>Template->process() failed twice.<br>
+ First error: $error<br>
+ Second error: $error2</p>
+ </tt>
+END
+ }
+ exit;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Error - Error handling utilities for Bugzilla
+
+=head1 SYNOPSIS
+
+ use Bugzilla::Error;
+
+ ThrowUserError("error_tag",
+ { foo => 'bar' });
+
+=head1 DESCRIPTION
+
+Various places throughout the Bugzilla codebase need to report errors to the
+user. The C<Throw*Error> family of functions allow this to be done in a
+generic and localizable manner.
+
+These functions automatically unlock the database tables, if there were any
+locked. They will also roll back the transaction, if it is supported by
+the underlying DB.
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item C<ThrowUserError>
+
+This function takes an error tag as the first argument, and an optional hashref
+of variables as a second argument. These are used by the
+I<global/user-error.html.tmpl> template to format the error, using the passed
+in variables as required.
+
+=item C<ThrowCodeError>
+
+This function is used when an internal check detects an error of some sort.
+This usually indicates a bug in Bugzilla, although it can occur if the user
+manually constructs urls without correct parameters.
+
+This function's behaviour is similar to C<ThrowUserError>, except that the
+template used to display errors is I<global/code-error.html.tmpl>. In addition
+if the hashref used as the optional second argument contains a key I<variables>
+then the contents of the hashref (which is expected to be another hashref) will
+be displayed after the error message, as a debugging aid.
+
+=item C<ThrowTemplateError>
+
+This function should only be called if a C<template-<gt>process()> fails.
+It tries another template first, because often one template being
+broken or missing doesn't mean that they all are. But it falls back to
+a print statement as a last-ditch error.
+
+=back
+
+=head1 SEE ALSO
+
+L<Bugzilla|Bugzilla>
diff --git a/Bugzilla/Extension.pm b/Bugzilla/Extension.pm
new file mode 100644
index 000000000..857d0d75b
--- /dev/null
+++ b/Bugzilla/Extension.pm
@@ -0,0 +1,813 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by the Initial Developers are Copyright (C) 2009 the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::Extension;
+use strict;
+
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Install::Util qw(
+ extension_code_files extension_template_directory
+ extension_package_directory);
+
+use File::Basename;
+use File::Spec;
+
+####################
+# Subclass Methods #
+####################
+
+sub new {
+ my ($class, $params) = @_;
+ $params ||= {};
+ bless $params, $class;
+ return $params;
+}
+
+#######################################
+# Class (Bugzilla::Extension) Methods #
+#######################################
+
+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;
+ }
+ 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;
+ }
+
+ $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';
+ }
+
+ 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;
+}
+
+# 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', @_) });
+}
+
+# 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);
+ }
+ # 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;
+}
+
+####################
+# Instance Methods #
+####################
+
+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');
+}
+
+sub template_dir { return extension_template_directory(@_); }
+sub package_dir { return extension_package_directory(@_); }
+
+######################
+# Helper Subroutines #
+######################
+
+# In order to not conflict with extensions' private subroutines, any helpers
+# here should start with a double underscore.
+
+# This is for methods that can optionally be overridden in Config.pm.
+# It falls back to the local implementation if $class cannot do
+# 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);
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Extension - Base class for Bugzilla Extensions.
+
+=head1 SYNOPSIS
+
+The following would be in F<extensions/Foo/Extension.pm> or
+F<extensions/Foo.pm>:
+
+ package Bugzilla::Extension::Foo
+ use strict;
+ use base qw(Bugzilla::Extension);
+
+ our $VERSION = '0.02';
+ use constant NAME => 'Foo';
+
+ sub some_hook_name { ... }
+
+ __PACKAGE__->NAME;
+
+Custom templates would go into F<extensions/Foo/template/en/default/>.
+L<Template hooks|/Template Hooks> would go into
+F<extensions/Foo/template/en/default/hook/>.
+
+=head1 DESCRIPTION
+
+This is the base class for all Bugzilla extensions.
+
+=head1 WRITING EXTENSIONS
+
+The L</SYNOPSIS> above gives a pretty good overview of what's basically
+required to write an extension. This section gives more information
+on exactly how extensions work and how you write them. There is also a
+L<wiki page|https://wiki.mozilla.org/Bugzilla:Extension_Notes> with additional HOWTOs, tips and tricks.
+
+=head2 Using F<extensions/create.pl>
+
+There is a script, L<extensions::create>, that will set up the framework
+of a new extension for you. To use it, pick a name for your extension
+and, in the base bugzilla directory, do:
+
+C<extensions/create.pl NAME>
+
+But replace C<NAME> with the name you picked for your extension. That
+will create a new directory in the F<extensions/> directory with the name
+of your extension. The directory will contain a full framework for
+a new extension, with helpful comments in each file describing things
+about them.
+
+=head2 Example Extension
+
+There is a sample extension in F<extensions/Example/> that demonstrates
+most of the things described in this document, so if you find the
+documentation confusing, try just reading the code instead.
+
+=head2 Where Extension Code Goes
+
+Extension code lives under the F<extensions/> directory in Bugzilla.
+
+There are two ways to write extensions:
+
+=over
+
+=item 1
+
+If your extension will have only code and no templates or other files,
+you can create a simple C<.pm> file in the F<extensions/> directory.
+
+For example, if you wanted to create an extension called "Foo" using this
+method, you would put your code into a file called F<extensions/Foo.pm>.
+
+=item 2
+
+If you plan for your extension to have templates and other files, you
+can create a whole directory for your extension, and the main extension
+code would go into a file called F<Extension.pm> in that directory.
+
+For example, if you wanted to create an extension called "Foo" using this
+method, you would put your code into a file called
+F<extensions/Foo/Extension.pm>.
+
+=back
+
+=head2 The Extension C<NAME>.
+
+The "name" of an extension shows up in several places:
+
+=over
+
+=item 1
+
+The name of the package:
+
+C<package Bugzilla::Extension::Foo;>
+
+=item 2
+
+In a C<NAME> constant that B<must> be defined for every extension:
+
+C<< use constant NAME => 'Foo'; >>
+
+=item 3
+
+At the very end of the file:
+
+C<< __PACKAGE__->NAME; >>
+
+You'll notice that though most Perl packages end with C<1;>, Bugzilla
+Extensions must B<always> end with C<< __PACKAGE__->NAME; >>.
+
+=back
+
+The name must be identical in all of those locations.
+
+=head2 Hooks
+
+In L<Bugzilla::Hook>, there is a L<list of hooks|Bugzilla::Hook/HOOKS>.
+These are the various areas of Bugzilla that an extension can "hook" into,
+which allow your extension to perform code during that point in Bugzilla's
+execution.
+
+If your extension wants to implement a hook, all you have to do is
+write a subroutine in your hook package that has the same name as
+the hook. The subroutine will be called as a method on your extension,
+and it will get the arguments specified in the hook's documentation as
+named parameters in a hashref.
+
+For example, here's an implementation of a hook named C<foo_start>
+that gets an argument named C<bar>:
+
+ sub foo_start {
+ my ($self, $args) = @_;
+ my $bar = $args->{bar};
+ print "I got $bar!\n";
+ }
+
+And that would go into your extension's code file--the file that was
+described in the L</Where Extension Code Goes> section above.
+
+During your subroutine, you may want to know what values were passed
+as CGI arguments to the current script, or what arguments were passed to
+the current WebService method. You can get that data via
+L<Bugzilla/input_params>.
+
+=head3 Adding New Hooks To Bugzilla
+
+If you need a new hook for your extension and you want that hook to be
+added to Bugzilla itself, see our development process at
+L<http://wiki.mozilla.org/Bugzilla:Developers>.
+
+In order for a new hook to be accepted into Bugzilla, it has to work,
+it must have documentation in L<Bugzilla::Hook>, and it must have example
+code in F<extensions/Example/Extension.pm>.
+
+One question that is often asked about new hooks is, "Is this the most
+flexible way to implement this hook?" That is, the more power extension
+authors get from a hook, the more likely it is to be accepted into Bugzilla.
+Hooks that only hook a very specific part of Bugzilla will not be accepted
+if their functionality can be accomplished equally well with a more generic
+hook.
+
+=head2 If Your Extension Requires Certain Perl Modules
+
+If there are certain Perl modules that your extension requires in order
+to run, there is a way you can tell Bugzilla this, and then L<checksetup>
+will make sure that those modules are installed, when you run L<checksetup>.
+
+To do this, you need to specify a constant called C<REQUIRED_MODULES>
+in your extension. This constant has the same format as
+L<Bugzilla::Install::Requirements/REQUIRED_MODULES>.
+
+If there are optional modules that add additional functionality to your
+application, you can specify them in a constant called OPTIONAL_MODULES,
+which has the same format as
+L<Bugzilla::Install::Requirements/OPTIONAL_MODULES>.
+
+=head3 If Your Extension Needs Certain Modules In Order To Compile
+
+If your extension needs a particular Perl module in order to
+I<compile>, then you have a "chicken and egg" problem--in order to
+read C<REQUIRED_MODULES>, we have to compile your extension. In order
+to compile your extension, we need to already have the modules in
+C<REQUIRED_MODULES>!
+
+To get around this problem, Bugzilla allows you to have an additional
+file, besides F<Extension.pm>, called F<Config.pm>, that contains
+just C<REQUIRED_MODULES>. If you have a F<Config.pm>, it must also
+contain the C<NAME> constant, instead of your main F<Extension.pm>
+containing the C<NAME> constant.
+
+The contents of the file would look something like this for an extension
+named C<Foo>:
+
+ package Bugzilla::Extension::Foo;
+ use strict;
+ use constant NAME => 'Foo';
+ use constant REQUIRED_MODULES => [
+ {
+ package => 'Some-Package',
+ module => 'Some::Module',
+ version => 0,
+ }
+ ];
+ __PACKAGE__->NAME;
+
+Note that it is I<not> a subclass of C<Bugzilla::Extension>, because
+at the time that module requirements are being checked in L<checksetup>,
+C<Bugzilla::Extension> cannot be loaded. Also, just like F<Extension.pm>,
+it ends with C<< __PACKAGE__->NAME; >>. Note also that it has the
+B<exact same> C<package> name as F<Extension.pm>.
+
+This file may not use any Perl modules other than L<Bugzilla::Constants>,
+L<Bugzilla::Install::Util>, L<Bugzilla::Install::Requirements>, and
+modules that ship with Perl itself.
+
+If you want to define both C<REQUIRED_MODULES> and C<OPTIONAL_MODULES>,
+they must both be in F<Config.pm> or both in F<Extension.pm>.
+
+Every time your extension is loaded by Bugzilla, F<Config.pm> will be
+read and then F<Extension.pm> will be read, so your methods in F<Extension.pm>
+will have access to everything in F<Config.pm>. Don't define anything
+with an identical name in both files, or Perl may throw a warning that
+you are redefining things.
+
+This method of setting C<REQUIRED_MODULES> is of course not available if
+your extension is a single file named C<Foo.pm>.
+
+If any of this is confusing, just look at the code of the Example extension.
+It uses this method to specify requirements.
+
+=head2 Libraries
+
+Extensions often want to have their own Perl modules. Your extension
+can load any Perl module in its F<lib/> directory. (So, if your extension is
+F<extensions/Foo/>, then your Perl modules go into F<extensions/Foo/lib/>.)
+
+However, the C<package> name of your libraries will not work quite
+like normal Perl modules do. F<extensions/Foo/lib/Bar.pm> is
+loaded as C<Bugzilla::Extension::Foo::Bar>. Or, to say it another way,
+C<use Bugzilla::Extension::Foo::Bar;> loads F<extensions/Foo/lib/Bar.pm>,
+which should have C<package Bugzilla::Extension::Foo::Bar;> as its package
+name.
+
+This allows any place in Bugzilla to load your modules, which is important
+for some hooks. It even allows other extensions to load your modules, and
+allows you to install your modules into the global Perl install
+as F<Bugzilla/Extension/Foo/Bar.pm>, if you'd like, which helps allow CPAN
+distribution of Bugzilla extensions.
+
+B<Note:> If you want to C<use> or C<require> a module that's in
+F<extensions/Foo/lib/> at the top level of your F<Extension.pm>,
+you must have a F<Config.pm> (see above) with at least the C<NAME>
+constant defined in it.
+
+=head2 Templates
+
+Extensions store templates in a C<template> subdirectory of the extension.
+(Obviously, this isn't available for extensions that aren't a directory.)
+
+The format of this directory is exactly like the normal layout of Bugzilla's
+C<template> directory--in fact, your extension's C<template> directory
+becomes part of Bugzilla's template "search path" as described in
+L<Bugzilla::Install::Util/template_include_path>.
+
+You can actually include templates in your extension without having any
+C<.pm> files in your extension at all, if you want. (That is, it's entirely
+valid to have an extension that's just template files and no code files.)
+
+Bugzilla's templates are written in a language called Template Toolkit.
+You can find out more about Template Toolkit at L<http://template-toolkit.org>.
+
+There are two ways to extend or modify Bugzilla's templates: you can use
+template hooks (described below) or you can override existing templates
+entirely (described further down).
+
+=head2 Template Hooks
+
+Templates can be extended using a system of "hooks" that add new UI elements
+to a particular area of Bugzilla without modifying the code of the existing
+templates. This is the recommended way for extensions to modify the user
+interface of Bugzilla.
+
+=head3 Which Templates Can Be Hooked
+
+There is no list of template hooks like there is for standard code hooks.
+To find what places in the user interface can be hooked, search for the
+string C<Hook.process> in Bugzilla's templates (in the
+F<template/en/default/> directory). That will also give you the name of
+the hooks--the first argument to C<Hook.process> is the name of the hook.
+(A later section in this document explains how to use that name).
+
+For example, if you see C<Hook.process("additional_header")>, that means
+the name of the hook is C<additional_header>.
+
+=head3 Where Template Hooks Go
+
+To extend templates in your extension using template hooks, you put files into
+the F<template/en/default/hook> directory of your extension. So, if you had an
+extension called "Foo", your template extensions would go into
+F<extensions/Foo/template/en/default/hook/>.
+
+(Note that the base F<template/en/default/hook> directory in Bugzilla itself
+also works, although you would never use that for an extension that you
+intended to distribute.)
+
+The files that go into this directory have a certain name, based on the
+name of the template that is being hooked, and the name of the hook.
+For example, let's imagine that you have an extension named "Foo",
+and you want to use the C<additional_header> hook in
+F<template/en/default/global/header.html.tmpl>. Your code would go into
+F<extensions/Foo/template/en/default/hook/global/header-additional_header.html.tmpl>. Any code you put into that file will happen at the point that
+C<Hook.process("additional_header")> is called in
+F<template/en/default/global/header.html.tmpl>.
+
+As you can see, template extension file names follow a pattern. The
+pattern looks like:
+
+ <templates>/hook/<template path>/<template name>-<hook name>.<template type>.tmpl
+
+=over
+
+=item <templates>
+
+This is the full path to the template directory, like
+F<extensions/Foo/template/en/default>. This works much like normal templates
+do, in the sense that template extensions in C<custom> override template
+extensions in C<default> for your extension, templates for different languages
+can be supplied, etc. Template extensions are searched for and run in the
+order described in L<Bugzilla::Install::Util/template_include_path>.
+
+The difference between normal templates and template hooks is that hooks
+will be run for I<every> extension, whereas for normal templates, Bugzilla
+just takes the first one it finds and stops searching. So while a template
+extension in the C<custom> directory may override the same-named template
+extension in the C<default> directory I<within your Bugzilla extension>,
+it will not override the same-named template extension in the C<default>
+directory of another Bugzilla extension.
+
+=item <template path>
+
+This is the part of the path (excluding the filename) that comes after
+F<template/en/default/> in a template's path. So, for
+F<template/en/default/global/header.html.tmpl>, this would simply be
+C<global>.
+
+=item <template name>
+
+This is the file name of the template, before the C<.html.tmpl> part.
+So, for F<template/en/default/global/header.html.tmpl>, this would be
+C<header>.
+
+=item <hook name>
+
+This is the name of the hook--what you saw in C<Hook.process> inside
+of the template you want to hook. In our example, this is
+C<additional_header>.
+
+=item <template type>
+
+This is what comes after the template name but before C<.tmpl> in the
+template's path. In most cases this is C<html>, but sometimes it's
+C<none>, C<txt>, C<js>, or various other formats, indicating what
+type of output the template has.
+
+=back
+
+=head3 Adding New Template Hooks to Bugzilla
+
+Adding new template hooks is just like adding code hooks (see
+L</Adding New Hooks To Bugzilla>) except that you don't have to
+document them, and including example code is optional.
+
+=head2 Overriding Existing Templates
+
+Sometimes you don't want to extend a template, you just want to replace
+it entirely with your extension's template, or you want to add an entirely
+new template to Bugzilla for your extension to use.
+
+To replace the F<template/en/default/global/banner.html.tmpl> template
+in an extension named "Foo", create a file called
+F<extensions/Foo/template/en/default/global/banner.html.tmpl>. Note that this
+is very similar to the path for a template hook, except that it excludes
+F<hook/>, and the template is named I<exactly> like the standard Bugzilla
+template.
+
+You can also use this method to add entirely new templates. If you have
+an extension named "Foo", and you add a file named
+F<extensions/Foo/template/en/default/foo/bar.html.tmpl>, you can load
+that in your code using C<< $template->process('foo/bar.html.tmpl') >>.
+
+=head3 A Warning About Extensions That You Want To Distribute
+
+You should never override an existing Bugzilla template in an
+extension that you plan to distribute to others, because only one extension
+can override any given template, and which extension will "win" that war
+if there are multiple extensions installed is totally undefined.
+
+However, adding new templates in an extension that you want to distribute
+is fine, though you have to be careful about how you name them, because
+any templates with an identical path and name (say, both called
+F<global/stuff.html.tmpl>) will conflict. The usual way to work around
+this is to put all your custom templates into a template path that's
+named after your extension (since the name of your extension has to be
+unique anyway). So if your extension was named Foo, your custom templates
+would go into F<extensions/Foo/template/en/default/foo/>. The only
+time that doesn't work is with the C<page_before_template> extension, in which
+case your templates should probably be in a directory like
+F<extensions/Foo/template/en/default/page/foo/> so as not to conflict with
+other pages that other extensions might add.
+
+=head2 CSS, JavaScript, and Images
+
+If you include CSS, JavaScript, and images in your extension that are
+served directly to the user (that is, they're not read by a script and
+then printed--they're just linked directly in your HTML), they should go
+into the F<web/> subdirectory of your extension.
+
+So, for example, if you had a CSS file called F<style.css> and your
+extension was called F<Foo>, your file would go into
+F<extensions/Foo/web/style.css>.
+
+=head2 Disabling Your Extension
+
+If you want your extension to be totally ignored by Bugzilla (it will
+not be compiled or seen to exist at all), then create a file called
+C<disabled> in your extension's directory. (If your extension is just
+a file, like F<extensions/Foo.pm>, you cannot use this method to disable
+your extension, and will just have to remove it from the directory if you
+want to totally disable it.) Note that if you are running under mod_perl,
+you may have to restart your web server for this to take effect.
+
+If you want your extension to be compiled and have L<checksetup> check
+for its module pre-requisites, but you don't want the module to be used
+by Bugzilla, then you should make your extension's L</enabled> method
+return C<0> or some false value.
+
+=head1 DISTRIBUTING EXTENSIONS
+
+If you've made an extension and you want to publish it, the first
+thing you'll want to do is package up your extension's code and
+then put a link to it in the appropriate section of
+L<http://wiki.mozilla.org/Bugzilla:Addons>.
+
+=head2 Distributing on CPAN
+
+If you want a centralized distribution point that makes it easy
+for Bugzilla users to install your extension, it is possible to
+distribute your Bugzilla Extension through CPAN.
+
+The details of making a standard CPAN module are too much to
+go into here, but a lot of it is covered in L<perlmodlib>
+and on L<http://www.cpan.org/> among other places.
+
+When you distribute your extension via CPAN, your F<Extension.pm>
+should simply install itself as F<Bugzilla/Extension/Foo.pm>,
+where C<Foo> is the name of your module. You do not need a separate
+F<Config.pm> file, because CPAN itself will handle installing
+the prerequisites of your module, so Bugzilla doesn't have to
+worry about it.
+
+=head3 Templates in extensions distributed on CPAN
+
+If your extension is F</usr/lib/perl5/Bugzilla/Extension/Foo.pm>,
+then Bugzilla will look for templates in the directory
+F</usr/lib/perl5/Bugzilla/Extension/Foo/template/>.
+
+You can change this behavior by overriding the L</template_dir>
+or L</package_dir> methods described lower down in this document.
+
+=head3 Using an extension distributed on CPAN
+
+There is a file named F<data/extensions/additional> in Bugzilla.
+This is a plain-text file. Each line is the name of a module,
+like C<Bugzilla::Extension::Foo>. In addition to the extensions
+in the F<extensions/> directory, each module listed in this file
+will be loaded as a Bugzilla Extension whenever Bugzilla loads or
+uses extensions.
+
+=head1 GETTING HELP WITH WRITING EXTENSIONS
+
+If you are an extension author and you'd like some assistance from other
+extension authors or the Bugzilla development team, you can use the
+normal support channels described at L<http://www.bugzilla.org/support/>.
+
+=head1 ADDITIONAL CONSTANTS
+
+In addition to C<NAME>, there are some other constants you might
+want to define:
+
+=head2 C<$VERSION>
+
+This should be a string that describes what version of your extension
+this is. Something like C<1.0>, C<1.3.4> or a similar string.
+
+There are no particular restrictions on the format of version numbers,
+but you should probably keep them to just numbers and periods, in the
+interest of other software that parses version numbers.
+
+By default, this will be C<undef> if you don't define it.
+
+=head1 SUBCLASS METHODS
+
+In addition to hooks, there are a few methods that your extension can
+define to modify its behavior, if you want:
+
+=head2 Class Methods
+
+These methods are called on your extension's class. (Like
+C<< Bugzilla::Extension::Foo->some_method >>).
+
+=head3 C<new>
+
+Once every request, this method is called on your extension in order
+to create an "instance" of it. (Extensions are treated like objects--they
+are instantiated once per request in Bugzilla, and then methods are
+called on the object.)
+
+=head2 Instance Methods
+
+These are called on an instantiated Extension object.
+
+=head3 C<enabled>
+
+This should return C<1> if this extension's hook code should be run
+by Bugzilla, and C<0> otherwise.
+
+=head3 C<package_dir>
+
+This returns the directory that your extension is located in.
+
+If this is an extension that was installed via CPAN, the directory will
+be the path to F<Bugzilla/Extension/Foo/>, if C<Foo.pm> is the name of your
+extension.
+
+If you want to override this method, and you have a F<Config.pm>, you must
+override this method in F<Config.pm>.
+
+=head3 C<template_dir>
+
+The directory that your package's templates are in.
+
+This defaults to the C<template> subdirectory of the L</package_dir>.
+
+If you want to override this method, and you have a F<Config.pm>, you must
+override this method in F<Config.pm>.
+
+=head3 C<lib_dir>
+
+The directory where your extension's libraries are.
+
+This defaults to the C<lib> subdirectory of the L</package_dir>.
+
+If you want to override this method, and you have a F<Config.pm>, you must
+override this method in F<Config.pm>.
+
+=head1 BUGZILLA::EXTENSION CLASS METHODS
+
+These are used internally by Bugzilla to load and set up extensions.
+If you are an extension author, you don't need to care about these.
+
+=head2 C<load>
+
+Takes two arguments, the path to F<Extension.pm> and the path to F<Config.pm>,
+for an extension. Loads the extension's code packages into memory using
+C<require>, does some sanity-checking on the extension, and returns the
+package name of the loaded extension.
+
+=head2 C<load_all>
+
+Calls L</load> for every enabled extension installed into Bugzilla,
+and returns an arrayref of all the package names that were loaded.
diff --git a/Bugzilla/Field.pm b/Bugzilla/Field.pm
new file mode 100644
index 000000000..65c1cec58
--- /dev/null
+++ b/Bugzilla/Field.pm
@@ -0,0 +1,1225 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s): Dan Mosedale <dmose@mozilla.org>
+# Frédéric Buclin <LpSolit@gmail.com>
+# Myk Melez <myk@mozilla.org>
+# Greg Hendricks <ghendricks@novell.com>
+
+=head1 NAME
+
+Bugzilla::Field - a particular piece of information about bugs
+ and useful routines for form field manipulation
+
+=head1 SYNOPSIS
+
+ use Bugzilla;
+ use Data::Dumper;
+
+ # Display information about all fields.
+ print Dumper(Bugzilla->get_fields());
+
+ # Display information about non-obsolete custom fields.
+ print Dumper(Bugzilla->active_custom_fields);
+
+ use Bugzilla::Field;
+
+ # Display information about non-obsolete custom fields.
+ # Bugzilla->get_fields() is a wrapper around Bugzilla::Field->match(),
+ # so both methods take the same arguments.
+ print Dumper(Bugzilla::Field->match({ obsolete => 0, custom => 1 }));
+
+ # Create or update a custom field or field definition.
+ my $field = Bugzilla::Field->create(
+ {name => 'cf_silly', description => 'Silly', custom => 1});
+
+ # Instantiate a Field object for an existing field.
+ my $field = new Bugzilla::Field({name => 'qacontact_accessible'});
+ if ($field->obsolete) {
+ print $field->description . " is obsolete\n";
+ }
+
+ # Validation Routines
+ check_field($name, $value, \@legal_values, $no_warn);
+ $fieldid = get_field_id($fieldname);
+
+=head1 DESCRIPTION
+
+Field.pm defines field objects, which represent the particular pieces
+of information that Bugzilla stores about bugs.
+
+This package also provides functions for dealing with CGI form fields.
+
+C<Bugzilla::Field> is an implementation of L<Bugzilla::Object>, and
+so provides all of the methods available in L<Bugzilla::Object>,
+in addition to what is documented here.
+
+=cut
+
+package Bugzilla::Field;
+
+use strict;
+
+use base qw(Exporter Bugzilla::Object);
+@Bugzilla::Field::EXPORT = qw(check_field get_field_id get_legal_field_values);
+
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Util;
+
+use Scalar::Util qw(blessed);
+
+###############################
+#### Initialization ####
+###############################
+
+use constant DB_TABLE => 'fielddefs';
+use constant LIST_ORDER => 'sortkey, name';
+
+use constant DB_COLUMNS => qw(
+ id
+ name
+ description
+ type
+ custom
+ mailhead
+ sortkey
+ obsolete
+ enter_bug
+ buglist
+ visibility_field_id
+ visibility_value_id
+ value_field_id
+ reverse_desc
+ is_mandatory
+);
+
+use constant VALIDATORS => {
+ custom => \&_check_custom,
+ description => \&_check_description,
+ 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_value_id => \&_check_control_value,
+ is_mandatory => \&Bugzilla::Object::check_boolean,
+};
+
+use constant VALIDATOR_DEPENDENCIES => {
+ name => ['custom'],
+ type => ['custom'],
+ reverse_desc => ['type'],
+ value_field_id => ['type'],
+ visibility_value_id => ['visibility_field_id'],
+};
+
+use constant UPDATE_COLUMNS => qw(
+ description
+ mailhead
+ sortkey
+ obsolete
+ enter_bug
+ buglist
+ visibility_field_id
+ visibility_value_id
+ value_field_id
+ reverse_desc
+ is_mandatory
+ 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)' },
+ FIELD_TYPE_SINGLE_SELECT, { TYPE => 'varchar(64)', NOTNULL => 1,
+ DEFAULT => "'---'" },
+ FIELD_TYPE_TEXTAREA, { TYPE => 'MEDIUMTEXT' },
+ FIELD_TYPE_DATETIME, { TYPE => 'DATETIME' },
+ FIELD_TYPE_BUG_ID, { TYPE => 'INT3' },
+};
+
+# Field definitions for the fields that ship with Bugzilla.
+# These are used by populate_field_definitions to populate
+# the fielddefs table.
+use constant DEFAULT_FIELDS => (
+ {name => 'bug_id', desc => 'Bug #', in_new_bugmail => 1,
+ buglist => 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 => 'cc', desc => 'CC', in_new_bugmail => 1},
+ {name => 'dependson', desc => 'Depends on', in_new_bugmail => 1},
+ {name => 'blocked', desc => 'Blocks', in_new_bugmail => 1},
+
+ {name => 'attachments.description', desc => 'Attachment description'},
+ {name => 'attachments.filename', desc => 'Attachment filename'},
+ {name => 'attachments.mimetype', desc => 'Attachment mime type'},
+ {name => 'attachments.ispatch', desc => 'Attachment is patch'},
+ {name => 'attachments.isobsolete', desc => 'Attachment is obsolete'},
+ {name => 'attachments.isprivate', desc => 'Attachment is private'},
+ {name => 'attachments.submitter', desc => 'Attachment creator'},
+
+ {name => 'target_milestone', desc => 'Target Milestone',
+ 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'},
+ {name => 'alias', desc => 'Alias', buglist => 1},
+ {name => 'everconfirmed', desc => 'Ever Confirmed'},
+ {name => 'reporter_accessible', desc => 'Reporter Accessible'},
+ {name => 'cclist_accessible', desc => 'CC Accessible'},
+ {name => 'bug_group', desc => 'Group', in_new_bugmail => 1},
+ {name => 'estimated_time', desc => 'Estimated Hours',
+ in_new_bugmail => 1, buglist => 1},
+ {name => 'remaining_time', desc => 'Remaining Hours', buglist => 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},
+ {name => 'percentage_complete', desc => 'Percentage Complete',
+ buglist => 1},
+ {name => 'content', desc => 'Content'},
+ {name => 'attach_data.thedata', desc => 'Attachment data'},
+ {name => 'attachments.isurl', desc => 'Attachment is a URL'},
+ {name => "owner_idle_time", desc => "Time Since Assignee Touched"},
+ {name => 'see_also', desc => "See Also",
+ type => FIELD_TYPE_BUG_URLS},
+);
+
+################
+# Constructors #
+################
+
+# 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(@_);
+}
+
+##############
+# Validators #
+##############
+
+sub _check_custom { return $_[1] ? 1 : 0; }
+
+sub _check_description {
+ my ($invocant, $desc) = @_;
+ $desc = clean_text($desc);
+ $desc || ThrowUserError('field_missing_description');
+ return $desc;
+}
+
+sub _check_enter_bug { return $_[1] ? 1 : 0; }
+
+sub _check_mailhead { return $_[1] ? 1 : 0; }
+
+sub _check_name {
+ my ($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;
+
+ return $name;
+}
+
+sub _check_obsolete { return $_[1] ? 1 : 0; }
+
+sub _check_sortkey {
+ my ($invocant, $sortkey) = @_;
+ my $skey = $sortkey;
+ if (!defined $skey || $skey eq '') {
+ ($sortkey) = Bugzilla->dbh->selectrow_array(
+ 'SELECT MAX(sortkey) + 100 FROM fielddefs') || 100;
+ }
+ detaint_natural($sortkey)
+ || ThrowUserError('field_invalid_sortkey', { sortkey => $skey });
+ return $sortkey;
+}
+
+sub _check_type {
+ my ($invocant, $type, undef, $params) = @_;
+ my $saved_type = $type;
+ # The constant here should be updated every time a new,
+ # higher field type is added.
+ (detaint_natural($type) && $type <= FIELD_TYPE_KEYWORDS)
+ || 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;
+}
+
+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);
+}
+
+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;
+}
+
+sub _check_control_value {
+ my ($invocant, $value_id, 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 value is set.
+ return undef if !$field;
+ my $value_obj = Bugzilla::Field::Choice->type($field)
+ ->check({ id => $value_id });
+ return $value_obj->id;
+}
+
+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;
+}
+
+sub _check_is_mandatory { return $_[1] ? 1 : 0; }
+
+=pod
+
+=head2 Instance Properties
+
+=over
+
+=item C<name>
+
+the name of the field in the database; begins with "cf_" if field
+is a custom field, but test the value of the boolean "custom" property
+to determine if a given field is a custom field;
+
+=item C<description>
+
+a short string describing the field; displayed to Bugzilla users
+in several places within Bugzilla's UI, f.e. as the form field label
+on the "show bug" page;
+
+=back
+
+=cut
+
+sub description { return $_[0]->{description} }
+
+=over
+
+=item C<type>
+
+an integer specifying the kind of field this is; values correspond to
+the FIELD_TYPE_* constants in Constants.pm
+
+=back
+
+=cut
+
+sub type { return $_[0]->{type} }
+
+=over
+
+=item C<custom>
+
+a boolean specifying whether or not the field is a custom field;
+if true, field name should start "cf_", but use this property to determine
+which fields are custom fields;
+
+=back
+
+=cut
+
+sub custom { return $_[0]->{custom} }
+
+=over
+
+=item C<in_new_bugmail>
+
+a boolean specifying whether or not the field is displayed in bugmail
+for newly-created bugs;
+
+=back
+
+=cut
+
+sub in_new_bugmail { return $_[0]->{mailhead} }
+
+=over
+
+=item C<sortkey>
+
+an integer specifying the sortkey of the field.
+
+=back
+
+=cut
+
+sub sortkey { return $_[0]->{sortkey} }
+
+=over
+
+=item C<obsolete>
+
+a boolean specifying whether or not the field is obsolete;
+
+=back
+
+=cut
+
+sub obsolete { return $_[0]->{obsolete} }
+
+=over
+
+=item C<enter_bug>
+
+A boolean specifying whether or not this field should appear on
+enter_bug.cgi
+
+=back
+
+=cut
+
+sub enter_bug { return $_[0]->{enter_bug} }
+
+=over
+
+=item C<buglist>
+
+A boolean specifying whether or not this field is selectable
+as a display or order column in buglist.cgi
+
+=back
+
+=cut
+
+sub buglist { return $_[0]->{buglist} }
+
+=over
+
+=item C<is_select>
+
+True if this is a C<FIELD_TYPE_SINGLE_SELECT> or C<FIELD_TYPE_MULTI_SELECT>
+field. It is only safe to call L</legal_values> if this is true.
+
+=item C<legal_values>
+
+Valid values for this field, as an array of L<Bugzilla::Field::Choice>
+objects.
+
+=back
+
+=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
+}
+
+=over
+
+=item C<is_abnormal>
+
+Most fields that have a C<SELECT> L</type> have a certain schema for
+the table that stores their values, the table has the same name as the field,
+and the field's legal values can be edited via F<editvalues.cgi>.
+
+However, some fields do not follow that pattern. Those fields are
+considered "abnormal".
+
+This method returns C<1> if the field is "abnormal", C<0> otherwise.
+
+=back
+
+=cut
+
+sub is_abnormal {
+ my $self = shift;
+ return grep($_ eq $self->name, ABNORMAL_SELECTS) ? 1 : 0;
+}
+
+sub legal_values {
+ 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'};
+}
+
+=pod
+
+=over
+
+=item C<is_timetracking>
+
+True if this is a time-tracking field that should only be shown to users
+in the C<timetrackinggroup>.
+
+=back
+
+=cut
+
+sub is_timetracking {
+ my ($self) = @_;
+ return grep($_ eq $self->name, TIMETRACKING_FIELDS) ? 1 : 0;
+}
+
+=pod
+
+=over
+
+=item C<visibility_field>
+
+What field controls this field's visibility? Returns a C<Bugzilla::Field>
+object representing the field that controls this field's visibility.
+
+Returns undef if there is no field that controls this field's visibility.
+
+=back
+
+=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};
+}
+
+=pod
+
+=over
+
+=item C<visibility_value>
+
+If we have a L</visibility_field>, then what value does that field have to
+be set to in order to show this field? Returns a L<Bugzilla::Field::Choice>
+or undef if there is no C<visibility_field> set.
+
+=back
+
+=cut
+
+
+sub visibility_value {
+ my $self = shift;
+ if ($self->{visibility_field_id}) {
+ require Bugzilla::Field::Choice;
+ $self->{visibility_value} ||=
+ Bugzilla::Field::Choice->type($self->visibility_field)->new(
+ $self->{visibility_value_id});
+ }
+ return $self->{visibility_value};
+}
+
+=pod
+
+=over
+
+=item C<controls_visibility_of>
+
+An arrayref of C<Bugzilla::Field> objects, representing fields that this
+field controls the visibility of.
+
+=back
+
+=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};
+}
+
+=pod
+
+=over
+
+=item C<value_field>
+
+The Bugzilla::Field that controls the list of values for this field.
+
+Returns undef if there is no field that controls this field's visibility.
+
+=back
+
+=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};
+}
+
+=pod
+
+=over
+
+=item C<controls_values_of>
+
+An arrayref of C<Bugzilla::Field> objects, representing fields that this
+field controls the values of.
+
+=back
+
+=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};
+}
+
+=over
+
+=item C<is_visible_on_bug>
+
+See L<Bugzilla::Field::ChoiceInterface>.
+
+=back
+
+=cut
+
+sub is_visible_on_bug {
+ my ($self, $bug) = @_;
+
+ my $visibility_value = $self->visibility_value;
+ return 1 if !$visibility_value;
+
+ return $visibility_value->is_set_on_bug($bug);
+}
+
+=over
+
+=item C<is_relationship>
+
+Applies only to fields of type FIELD_TYPE_BUG_ID.
+Checks to see if a reverse relationship description has been set.
+This is the canonical condition to enable reverse link display,
+dependency tree display, and similar functionality.
+
+=back
+
+=cut
+
+sub is_relationship {
+ my $self = shift;
+ my $desc = $self->reverse_desc;
+ if (defined $desc && $desc ne "") {
+ return 1;
+ }
+ return 0;
+}
+
+=over
+
+=item C<reverse_desc>
+
+Applies only to fields of type FIELD_TYPE_BUG_ID.
+Describes the reverse relationship of this field.
+For example, if a BUG_ID field is called "Is a duplicate of",
+the reverse description would be "Duplicates of this bug".
+
+=back
+
+=cut
+
+sub reverse_desc { return $_[0]->{reverse_desc} }
+
+=over
+
+=item C<is_mandatory>
+
+a boolean specifying whether or not the field is mandatory;
+
+=back
+
+=cut
+
+sub is_mandatory { return $_[0]->{is_mandatory} }
+
+
+=pod
+
+=head2 Instance Mutators
+
+These set the particular field that they are named after.
+
+They take a single value--the new value for that field.
+
+They will throw an error if you try to set the values to something invalid.
+
+=over
+
+=item C<set_description>
+
+=item C<set_enter_bug>
+
+=item C<set_obsolete>
+
+=item C<set_sortkey>
+
+=item C<set_in_new_bugmail>
+
+=item C<set_buglist>
+
+=item C<set_reverse_desc>
+
+=item C<set_visibility_field>
+
+=item C<set_visibility_value>
+
+=item C<set_value_field>
+
+=item C<set_is_mandatory>
+
+
+=back
+
+=cut
+
+sub set_description { $_[0]->set('description', $_[1]); }
+sub set_enter_bug { $_[0]->set('enter_bug', $_[1]); }
+sub set_obsolete { $_[0]->set('obsolete', $_[1]); }
+sub set_sortkey { $_[0]->set('sortkey', $_[1]); }
+sub set_in_new_bugmail { $_[0]->set('mailhead', $_[1]); }
+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_value};
+}
+sub set_visibility_value {
+ my ($self, $value) = @_;
+ $self->set('visibility_value_id', $value);
+ delete $self->{visibility_value};
+}
+sub set_value_field {
+ my ($self, $value) = @_;
+ $self->set('value_field_id', $value);
+ delete $self->{value_field};
+}
+sub set_is_mandatory { $_[0]->set('is_mandatory', $_[1]); }
+
+# This is only used internally by upgrade code in Bugzilla::Field.
+sub _set_type { $_[0]->set('type', $_[1]); }
+
+=pod
+
+=head2 Instance Method
+
+=over
+
+=item C<remove_from_db>
+
+Attempts to remove the passed in field from the database.
+Deleting a field is only successful if the field is obsolete and
+there are no values specified (or EVER specified) for the field.
+
+=back
+
+=cut
+
+sub remove_from_db {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ my $name = $self->name;
+
+ if (!$self->custom) {
+ ThrowCodeError('field_not_custom', {'name' => $name });
+ }
+
+ if (!$self->obsolete) {
+ ThrowUserError('customfield_not_obsolete', {'name' => $self->name });
+ }
+
+ $dbh->bz_start_transaction();
+
+ # Check to see if bug activity table has records (should be fast with index)
+ my $has_activity = $dbh->selectrow_array("SELECT COUNT(*) FROM bugs_activity
+ WHERE fieldid = ?", undef, $self->id);
+ if ($has_activity) {
+ ThrowUserError('customfield_has_activity', {'name' => $name });
+ }
+
+ # Check to see if bugs table has records (slow)
+ my $bugs_query = "";
+
+ if ($self->type == FIELD_TYPE_MULTI_SELECT) {
+ $bugs_query = "SELECT COUNT(*) FROM bug_$name";
+ }
+ else {
+ $bugs_query = "SELECT COUNT(*) FROM bugs WHERE $name IS NOT NULL";
+ if ($self->type != FIELD_TYPE_BUG_ID && $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 != '---'";
+ }
+ }
+
+ my $has_bugs = $dbh->selectrow_array($bugs_query);
+ if ($has_bugs) {
+ ThrowUserError('customfield_has_contents', {'name' => $name });
+ }
+
+ # Once we reach here, we should be OK to delete.
+ $dbh->do('DELETE FROM fielddefs WHERE id = ?', undef, $self->id);
+
+ my $type = $self->type;
+
+ # the values for multi-select are stored in a seperate table
+ if ($type != FIELD_TYPE_MULTI_SELECT) {
+ $dbh->bz_drop_column('bugs', $name);
+ }
+
+ if ($self->is_select) {
+ # Delete the table that holds the legal values for this field.
+ $dbh->bz_drop_field_tables($self);
+ }
+
+ $dbh->bz_commit_transaction()
+}
+
+=pod
+
+=head2 Class Methods
+
+=over
+
+=item C<create>
+
+Just like L<Bugzilla::Object/create>. Takes the following parameters:
+
+=over
+
+=item C<name> B<Required> - The name of the field.
+
+=item C<description> B<Required> - The field label to display in the UI.
+
+=item C<mailhead> - boolean - Whether this field appears at the
+top of the bugmail for a newly-filed bug. Defaults to 0.
+
+=item C<custom> - boolean - True if this is a Custom Field. The field
+will be added to the C<bugs> table if it does not exist. Defaults to 0.
+
+=item C<sortkey> - integer - The sortkey of the field. Defaults to 0.
+
+=item C<enter_bug> - boolean - Whether this field is
+editable on the bug creation form. Defaults to 0.
+
+=item C<buglist> - boolean - Whether this field is
+selectable as a display or order column in bug lists. Defaults to 0.
+
+C<obsolete> - boolean - Whether this field is obsolete. Defaults to 0.
+
+C<is_mandatory> - boolean - Whether this field is mandatory. Defaults to 0.
+
+=back
+
+=back
+
+=cut
+
+sub create {
+ my $class = shift;
+ my ($params) = @_;
+ # 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;
+ my $field = $class->SUPER::create(@_);
+
+ my $dbh = Bugzilla->dbh;
+ if ($field->custom) {
+ my $name = $field->name;
+ my $type = $field->type;
+ if (SQL_DEFINITIONS->{$type}) {
+ # Create the database column that stores the data for this field.
+ $dbh->bz_add_column('bugs', $name, SQL_DEFINITIONS->{$type});
+ }
+
+ if ($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 ('---')");
+ }
+ }
+
+ 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");
+ }
+ return $changes;
+}
+
+
+=pod
+
+=over
+
+=item C<get_legal_field_values($field)>
+
+Description: returns all the legal values for a field that has a
+ list of legal values, like rep_platform or resolution.
+ The table where these values are stored must at least have
+ the following columns: value, isactive, sortkey.
+
+Params: C<$field> - Name of the table where valid values are.
+
+Returns: a reference to a list of valid values.
+
+=back
+
+=cut
+
+sub get_legal_field_values {
+ my ($field) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $result_ref = $dbh->selectcol_arrayref(
+ "SELECT value FROM $field
+ WHERE isactive = ?
+ ORDER BY sortkey, value", undef, (1));
+ return $result_ref;
+}
+
+=over
+
+=item C<populate_field_definitions()>
+
+Description: Populates the fielddefs table during an installation
+ or upgrade.
+
+Params: none
+
+Returns: nothing
+
+=back
+
+=cut
+
+sub populate_field_definitions {
+ my $dbh = Bugzilla->dbh;
+
+ # ADD and UPDATE field definitions
+ foreach my $def (DEFAULT_FIELDS) {
+ my $field = new Bugzilla::Field({ name => $def->{name} });
+ if ($field) {
+ $field->set_description($def->{desc});
+ $field->set_in_new_bugmail($def->{in_new_bugmail});
+ $field->set_buglist($def->{buglist});
+ $field->_set_type($def->{type}) if $def->{type};
+ $field->set_is_mandatory($def->{is_mandatory});
+ $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)) {
+ print "SQL fragment found in the 'fielddefs' table...\n";
+ print "Old field name: " . $old_field_name . "\n";
+ # We have to fix saved searches first. Queries have been escaped
+ # before being saved. We have to do the same here to find them.
+ $old_field_name = url_quote($old_field_name);
+ my $broken_named_queries =
+ $dbh->selectall_arrayref('SELECT userid, name, query
+ FROM namedqueries WHERE ' .
+ $dbh->sql_istrcmp('query', '?', 'LIKE'),
+ undef, "%=$old_field_name%");
+
+ my $sth_UpdateQueries = $dbh->prepare('UPDATE namedqueries SET query = ?
+ WHERE userid = ? AND name = ?');
+
+ print "Fixing saved searches...\n" if scalar(@$broken_named_queries);
+ foreach my $named_query (@$broken_named_queries) {
+ my ($userid, $name, $query) = @$named_query;
+ $query =~ s/=\Q$old_field_name\E(&|$)/=$new_field_name$1/gi;
+ $sth_UpdateQueries->execute($query, $userid, $name);
+ }
+
+ # We now do the same with saved chart series.
+ my $broken_series =
+ $dbh->selectall_arrayref('SELECT series_id, query
+ FROM series WHERE ' .
+ $dbh->sql_istrcmp('query', '?', 'LIKE'),
+ undef, "%=$old_field_name%");
+
+ my $sth_UpdateSeries = $dbh->prepare('UPDATE series SET query = ?
+ WHERE series_id = ?');
+
+ print "Fixing saved chart series...\n" if scalar(@$broken_series);
+ foreach my $series (@$broken_series) {
+ my ($series_id, $query) = @$series;
+ $query =~ s/=\Q$old_field_name\E(&|$)/=$new_field_name$1/gi;
+ $sth_UpdateSeries->execute($query, $series_id);
+ }
+ # Now that saved searches have been fixed, we can fix the field name.
+ print "Fixing the 'fielddefs' table...\n";
+ print "New field name: " . $new_field_name . "\n";
+ $dbh->do('UPDATE fielddefs SET name = ? WHERE id = ?',
+ undef, ($new_field_name, $old_field_id));
+ }
+
+ # This field has to be created separately, or the above upgrade code
+ # might not run properly.
+ Bugzilla::Field->create({ name => $new_field_name,
+ description => $field_description })
+ unless new Bugzilla::Field({ name => $new_field_name });
+
+}
+
+
+
+=head2 Data Validation
+
+=over
+
+=item C<check_field($name, $value, \@legal_values, $no_warn)>
+
+Description: Makes sure the field $name is defined and its $value
+ is non empty. If @legal_values is defined, this routine
+ checks whether its value is one of the legal values
+ associated with this field, else it checks against
+ the default valid values for this field obtained by
+ C<get_legal_field_values($name)>. If the test is successful,
+ the function returns 1. If the test fails, an error
+ is thrown (by default), unless $no_warn is true, in which
+ case the function returns 0.
+
+Params: $name - the field name
+ $value - the field value
+ @legal_values - (optional) list of legal values
+ $no_warn - (optional) do not throw an error if true
+
+Returns: 1 on success; 0 on failure if $no_warn is true (else an
+ error is thrown).
+
+=back
+
+=cut
+
+sub check_field {
+ my ($name, $value, $legalsRef, $no_warn) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ # If $legalsRef is undefined, we use the default valid values.
+ # 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);
+
+ my $field = new Bugzilla::Field({ name => $name });
+ my $field_desc = $field ? $field->description : $name;
+ ThrowCodeError('illegal_field', { field => $field_desc });
+ }
+ return 1;
+}
+
+=pod
+
+=over
+
+=item C<get_field_id($fieldname)>
+
+Description: Returns the ID of the specified field name and throws
+ an error if this field does not exist.
+
+Params: $name - a field name
+
+Returns: the corresponding field ID or an error if the field name
+ does not exist.
+
+=back
+
+=cut
+
+sub get_field_id {
+ my ($name) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ trick_taint($name);
+ my $id = $dbh->selectrow_array('SELECT id FROM fielddefs
+ WHERE name = ?', undef, $name);
+
+ ThrowCodeError('invalid_field_name', {field => $name}) unless $id;
+ return $id
+}
+
+1;
+
+__END__
diff --git a/Bugzilla/Field/Choice.pm b/Bugzilla/Field/Choice.pm
new file mode 100644
index 000000000..773dbd4ce
--- /dev/null
+++ b/Bugzilla/Field/Choice.pm
@@ -0,0 +1,347 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Initial Developer of the Original Code is NASA.
+# Portions created by NASA are Copyright (C) 2006 San Jose State
+# University Foundation. All Rights Reserved.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+# Greg Hendricks <ghendricks@novell.com>
+
+use strict;
+
+package Bugzilla::Field::Choice;
+
+use base qw(Bugzilla::Field::ChoiceInterface Bugzilla::Object);
+
+use Bugzilla::Config qw(SetParam write_params);
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Field;
+use Bugzilla::Util qw(trim detaint_natural);
+
+use Scalar::Util qw(blessed);
+
+##################
+# Initialization #
+##################
+
+use constant DB_COLUMNS => qw(
+ id
+ value
+ sortkey
+ isactive
+ visibility_value_id
+);
+
+use constant UPDATE_COLUMNS => qw(
+ 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,
+};
+
+use constant CLASS_MAP => {
+ 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',
+};
+
+#################
+# Class Factory #
+#################
+
+# Bugzilla::Field::Choice is actually an abstract base class. Every field
+# type has its own dynamically-generated class for its values. This allows
+# certain fields to have special types, like how bug_status's values
+# 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 ($class->CLASS_MAP->{$field_name}) {
+ return $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;
+ package $package;
+ use base qw(Bugzilla::Field::Choice);
+ use constant DB_TABLE => '$field_name';
+EOC
+ }
+
+ return $package;
+}
+
+################
+# Constructors #
+################
+
+# 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(@_);
+}
+
+#########################
+# Database Manipulation #
+#########################
+
+# Our subclasses can take more arguments than we normally accept.
+# So, we override create() to remove arguments that aren't valid
+# 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};
+ }
+ }
+ 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);
+ }
+
+ 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;
+}
+
+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();
+}
+
+############
+# Mutators #
+############
+
+sub set_is_active { $_[0]->set('isactive', $_[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};
+}
+
+##############
+# Validators #
+##############
+
+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 });
+ }
+ }
+ return $value;
+}
+
+sub _check_value {
+ my ($invocant, $value) = @_;
+
+ my $field = $invocant->field;
+
+ $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 });
+ }
+
+ 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 });
+ }
+
+ 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)
+ || 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;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Field::Choice - A legal value for a <select>-type field.
+
+=head1 SYNOPSIS
+
+ my $field = new Bugzilla::Field({name => 'bug_status'});
+
+ my $choice = new Bugzilla::Field::Choice->type($field)->new(1);
+
+ my $choices = Bugzilla::Field::Choice->type($field)->new_from_list([1,2,3]);
+ my $choices = Bugzilla::Field::Choice->type($field)->get_all();
+ my $choices = Bugzilla::Field::Choice->type($field->match({ sortkey => 10 });
+
+=head1 DESCRIPTION
+
+This is an implementation of L<Bugzilla::Object>, but with a twist.
+You can't call any class methods (such as C<new>, C<create>, etc.)
+directly on C<Bugzilla::Field::Choice> itself. Instead, you have to
+call C<Bugzilla::Field::Choice-E<gt>type($field)> to get the class
+you're going to instantiate, and then you call the methods on that.
+
+We do that because each field has its own database table for its values, so
+each value type needs its own class.
+
+See the L</SYNOPSIS> for examples of how this works.
+
+This class implements L<Bugzilla::Field::ChoiceInterface>, and so all
+methods of that class are also available here.
+
+=head1 METHODS
+
+=head2 Class Factory
+
+In object-oriented design, a "class factory" is a method that picks
+and returns the right class for you, based on an argument that you pass.
+
+=over
+
+=item C<type>
+
+Takes a single argument, which is either the name of a field from the
+C<fielddefs> table, or a L<Bugzilla::Field> object representing a field.
+
+Returns an appropriate subclass of C<Bugzilla::Field::Choice> that you
+can now call class methods on (like C<new>, C<create>, C<match>, etc.)
+
+B<NOTE>: YOU CANNOT CALL CLASS METHODS ON C<Bugzilla::Field::Choice>. You
+must call C<type> to get a class you can call methods on.
+
+=back
+
+=head2 Mutators
+
+This class implements mutators for all of the settable accessors in
+L<Bugzilla::Field::ChoiceInterface>.
diff --git a/Bugzilla/Field/ChoiceInterface.pm b/Bugzilla/Field/ChoiceInterface.pm
new file mode 100644
index 000000000..894ce00d3
--- /dev/null
+++ b/Bugzilla/Field/ChoiceInterface.pm
@@ -0,0 +1,273 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Initial Developer of the Original Code is NASA.
+# Portions created by NASA are Copyright (C) 2006 San Jose State
+# University Foundation. All Rights Reserved.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+# Greg Hendricks <ghendricks@novell.com>
+
+package Bugzilla::Field::ChoiceInterface;
+use strict;
+
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Field;
+
+use Scalar::Util qw(blessed);
+
+# Helps implement the "field" accessor without subclasses having to
+# write code.
+sub FIELD_NAME { return $_[0]->DB_TABLE; }
+
+####################
+# Subclass Helpers #
+####################
+
+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 });
+ }
+}
+
+
+#############
+# Accessors #
+#############
+
+sub is_active { return $_[0]->{'isactive'}; }
+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;
+}
+
+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"};
+}
+
+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;
+}
+
+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;
+}
+
+sub controls_visibility_of_fields {
+ my $self = shift;
+ $self->{controls_visibility_of_fields} ||= Bugzilla::Field->match(
+ { visibility_field_id => $self->field->id,
+ visibility_value_id => $self->id });
+ 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};
+}
+
+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};
+}
+
+sub controlled_values_array {
+ my ($self) = @_;
+ my $values = $self->controlled_values;
+ return [map { @{ $values->{$_} } } keys %$values];
+}
+
+sub is_visible_on_bug {
+ my ($self, $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;
+
+ # 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);
+}
+
+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};
+ 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;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Field::ChoiceInterface - Makes an object act like a
+Bugzilla::Field::Choice.
+
+=head1 DESCRIPTION
+
+This is an "interface", in the Java sense (sometimes called a "Role"
+or a "Mixin" in other languages). L<Bugzilla::Field::Choice> is the
+primary implementor of this interface, but other classes also implement
+it if they want to "act like" L<Bugzilla::Field::Choice>.
+
+=head1 METHODS
+
+=head2 Accessors
+
+These are in addition to the standard L<Bugzilla::Object> accessors.
+
+=over
+
+=item C<sortkey>
+
+The key that determines the sort order of this item.
+
+=item C<field>
+
+The L<Bugzilla::Field> object that this field value belongs to.
+
+=item C<is_active>
+
+Whether or not this value should appear as an option on bugs that do
+not already have it set as the current value.
+
+=item C<is_static>
+
+C<0> if this field value can be renamed or deleted, C<1> otherwise.
+
+=item C<is_default>
+
+C<1> if this is the default value for this field, C<0> otherwise.
+
+=item C<bug_count>
+
+An integer count of the number of bugs that have this value set.
+
+=item C<controls_visibility_of_fields>
+
+Returns an arrayref of L<Bugzilla::Field> objects, representing any
+fields whose visibility are controlled by this field value.
+
+=item C<controlled_values>
+
+Tells you which values in B<other> fields appear (become visible) when this
+value is set in its field.
+
+Returns a hashref of arrayrefs. The hash keys are the names of fields,
+and the values are arrays of objects that implement
+C<Bugzilla::Field::ChoiceInterface>, representing values that this value
+controls the visibility of, for that field.
+
+=item C<visibility_value>
+
+Returns an object that implements C<Bugzilla::Field::ChoiceInterface>,
+which represents the value that needs to be set in order for this
+value to appear in the UI.
+
+=item C<is_visible_on_bug>
+
+Returns C<1> if, according to the settings of C<is_active> and
+C<visibility_value>, this value should be displayed as an option
+when viewing a bug. Returns C<0> otherwise.
+
+Takes a single argument, a L<Bugzilla::Bug> object or a hash with
+similar fields to a L<Bugzilla::Bug> object.
+
+=item C<is_set_on_bug>
+
+Returns C<1> if this value is the current value set for its field on
+the passed-in L<Bugzilla::Bug> object (or a hash that looks like a
+L<Bugzilla::Bug>). For multi-valued fields, we return C<1> if
+I<any> of the currently selected values are this value.
+
+Returns C<0> otherwise.
+
+=back
diff --git a/Bugzilla/Flag.pm b/Bugzilla/Flag.pm
new file mode 100644
index 000000000..13dfe6ad9
--- /dev/null
+++ b/Bugzilla/Flag.pm
@@ -0,0 +1,1066 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Myk Melez <myk@mozilla.org>
+# Jouni Heikniemi <jouni@heikniemi.net>
+# Frédéric Buclin <LpSolit@gmail.com>
+
+use strict;
+
+package Bugzilla::Flag;
+
+=head1 NAME
+
+Bugzilla::Flag - A module to deal with Bugzilla flag values.
+
+=head1 SYNOPSIS
+
+Flag.pm provides an interface to flags as stored in Bugzilla.
+See below for more information.
+
+=head1 NOTES
+
+=over
+
+=item *
+
+Import relevant functions from that script.
+
+=item *
+
+Use of private functions / variables outside this module may lead to
+unexpected results after an upgrade. Please avoid using private
+functions in other files/modules. Private functions are functions
+whose names start with _ or a re specifically noted as being private.
+
+=back
+
+=cut
+
+use Scalar::Util qw(blessed);
+use Storable qw(dclone);
+
+use Bugzilla::FlagType;
+use Bugzilla::Hook;
+use Bugzilla::User;
+use Bugzilla::Util;
+use Bugzilla::Error;
+use Bugzilla::Mailer;
+use Bugzilla::Constants;
+use Bugzilla::Field;
+
+use base qw(Bugzilla::Object Exporter);
+@Bugzilla::Flag::EXPORT = qw(SKIP_REQUESTEE_ON_ERROR);
+
+###############################
+#### Initialization ####
+###############################
+
+use constant DB_TABLE => 'flags';
+use constant LIST_ORDER => 'id';
+
+use constant SKIP_REQUESTEE_ON_ERROR => 1;
+
+use constant DB_COLUMNS => qw(
+ id
+ type_id
+ bug_id
+ attach_id
+ requestee_id
+ setter_id
+ status
+);
+
+use constant UPDATE_COLUMNS => qw(
+ requestee_id
+ setter_id
+ status
+ type_id
+);
+
+use constant VALIDATORS => {
+};
+
+use constant UPDATE_VALIDATORS => {
+ setter => \&_check_setter,
+ status => \&_check_status,
+};
+
+###############################
+#### Accessors ######
+###############################
+
+=head2 METHODS
+
+=over
+
+=item C<id>
+
+Returns the ID of the flag.
+
+=item C<name>
+
+Returns the name of the flagtype the flag belongs to.
+
+=item C<bug_id>
+
+Returns the ID of the bug this flag belongs to.
+
+=item C<attach_id>
+
+Returns the ID of the attachment this flag belongs to, if any.
+
+=item C<status>
+
+Returns the status '+', '-', '?' of the flag.
+
+=back
+
+=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'}; }
+
+###############################
+#### Methods ####
+###############################
+
+=pod
+
+=over
+
+=item C<type>
+
+Returns the type of the flag, as a Bugzilla::FlagType object.
+
+=item C<setter>
+
+Returns the user who set the flag, as a Bugzilla::User object.
+
+=item C<requestee>
+
+Returns the user who has been requested to set the flag, as a
+Bugzilla::User object.
+
+=item C<attachment>
+
+Returns the attachment object the flag belongs to if the flag
+is an attachment flag, else undefined.
+
+=back
+
+=cut
+
+sub type {
+ my $self = shift;
+
+ $self->{'type'} ||= new Bugzilla::FlagType($self->{'type_id'});
+ return $self->{'type'};
+}
+
+sub setter {
+ my $self = shift;
+
+ $self->{'setter'} ||= new Bugzilla::User($self->{'setter_id'});
+ return $self->{'setter'};
+}
+
+sub requestee {
+ my $self = shift;
+
+ if (!defined $self->{'requestee'} && $self->{'requestee_id'}) {
+ $self->{'requestee'} = new Bugzilla::User($self->{'requestee_id'});
+ }
+ return $self->{'requestee'};
+}
+
+sub attachment {
+ my $self = shift;
+ return undef unless $self->attach_id;
+
+ require Bugzilla::Attachment;
+ $self->{'attachment'} ||= new Bugzilla::Attachment($self->attach_id);
+ return $self->{'attachment'};
+}
+
+sub bug {
+ my $self = shift;
+
+ require Bugzilla::Bug;
+ $self->{'bug'} ||= new Bugzilla::Bug($self->bug_id);
+ return $self->{'bug'};
+}
+
+################################
+## Searching/Retrieving Flags ##
+################################
+
+=pod
+
+=over
+
+=item C<match($criteria)>
+
+Queries the database for flags matching the given criteria
+(specified as a hash of field names and their matching values)
+and returns an array of matching records.
+
+=back
+
+=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;
+ }
+ }
+ # 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(@_);
+}
+
+=pod
+
+=over
+
+=item C<count($criteria)>
+
+Queries the database for flags matching the given criteria
+(specified as a hash of field names and their matching values)
+and returns an array of matching records.
+
+=back
+
+=cut
+
+sub count {
+ my $class = shift;
+ return scalar @{$class->match(@_)};
+}
+
+######################################################################
+# Creating and Modifying
+######################################################################
+
+sub set_flag {
+ my ($class, $obj, $params) = @_;
+
+ 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 {
+ ThrowCodeError('flag_unexpected_object', { 'caller' => ref $obj });
+ }
+
+ # 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);
+ }
+ my ($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;
+
+ $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 });
+ }
+ }
+
+ $class->_validate(undef, $obj_flagtype, $params, $bug, $attachment);
+ }
+ else {
+ ThrowCodeError('param_required', { function => $class . '->set_flag',
+ param => 'id/type_id' });
+ }
+}
+
+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;
+
+ $obj_flag->_set_status($params->{status});
+ $obj_flag->_set_requestee($params->{requestee}, $attachment, $params->{skip_roe});
+
+ # The setter field MUST NOT be updated if neither the status
+ # nor the requestee fields changed.
+ if (($obj_flag->status ne $old_status)
+ # The requestee ID can be undefined.
+ || (($obj_flag->requestee_id || 0) != ($old_requestee_id || 0)))
+ {
+ $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}};
+ }
+ # Add the newly created flag to the list.
+ elsif (!$obj_flag->id) {
+ push(@{$flag_type->{flags}}, $obj_flag);
+ }
+}
+
+=pod
+
+=over
+
+=item C<create($flag, $timestamp)>
+
+Creates a flag record in the database.
+
+=back
+
+=cut
+
+sub create {
+ my ($class, $flag, $timestamp) = @_;
+ $timestamp ||= Bugzilla->dbh->selectrow_array('SELECT NOW()');
+
+ my $params = {};
+ my @columns = grep { $_ ne 'id' } $class->_get_db_columns;
+ $params->{$_} = $flag->{$_} foreach @columns;
+
+ $params->{creation_date} = $params->{modification_date} = $timestamp;
+
+ $flag = $class->SUPER::create($params);
+ return $flag;
+}
+
+sub update {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+ my $timestamp = shift || $dbh->selectrow_array('SELECT NOW()');
+
+ my $changes = $self->SUPER::update(@_);
+
+ if (scalar(keys %$changes)) {
+ $dbh->do('UPDATE flags SET modification_date = ? WHERE id = ?',
+ undef, ($timestamp, $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;
+}
+
+sub update_activity {
+ 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/^[^:]+:// }
+
+ $removed = join(", ", @$removed);
+ $added = join(", ", @$added);
+ return ($removed, $added);
+ }
+ return ();
+}
+
+sub update_flags {
+ my ($class, $self, $old_self, $timestamp) = @_;
+
+ 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);
+ }
+ else {
+ my $changes = $new_flag->update($timestamp);
+ if (scalar(keys %$changes)) {
+ $class->notify($new_flag, $old_flags{$new_flag->id}, $self);
+ }
+ delete $old_flags{$new_flag->id};
+ }
+ }
+ # These flags have been deleted.
+ foreach my $old_flag (values %old_flags) {
+ $class->notify(undef, $old_flag, $self);
+ $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', { object => $self,
+ timestamp => $timestamp,
+ old_flags => \@old_summaries,
+ new_flags => \@new_summaries,
+ });
+ 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;
+}
+
+# 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 $flag_ids = $dbh->selectcol_arrayref(
+ 'SELECT DISTINCT flags.id
+ FROM flags
+ INNER JOIN bugs
+ ON flags.bug_id = bugs.bug_id
+ LEFT JOIN flaginclusions AS i
+ 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);
+
+ my @removed = $class->force_retarget($flag_ids, $bug);
+
+ $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);
+
+ 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));
+ }
+ 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;
+}
+
+###############################
+#### Validators ######
+###############################
+
+sub _set_requestee {
+ my ($self, $requestee, $attachment, $skip_requestee_on_error) = @_;
+
+ # Used internally to check if the requestee is retargetting the request.
+ $self->{_old_requestee_id} = $self->requestee ? $self->requestee->id : 0;
+ $self->{requestee} =
+ $self->_check_requestee($requestee, $attachment, $skip_requestee_on_error);
+
+ $self->{requestee_id} =
+ $self->{requestee} ? $self->{requestee}->id : undef;
+}
+
+sub _set_setter {
+ my ($self, $setter) = @_;
+
+ $self->set('setter', $setter);
+ $self->{setter_id} = $self->setter->id;
+}
+
+sub _set_status {
+ my ($self, $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, $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 him or leave him alone.
+ ThrowCodeError('flag_requestee_disabled', { type => $self->type })
+ if !$self->type->is_requesteeble;
+
+ # 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 restrictions matters.
+ if (!$requestee->can_see_bug($self->bug_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});
+ }
+ }
+ }
+ 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)
+ || ThrowCodeError('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 requester is retargetting the request, we don't
+ # update the setter, so that the setter gets the notification.
+ if ($status eq '?' && $self->{_old_requestee_id} == $setter->id) {
+ 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;
+}
+
+######################################################################
+# Utility Functions
+######################################################################
+
+=pod
+
+=over
+
+=item C<extract_flags_from_cgi($bug, $attachment, $hr_vars)>
+
+Checks whether or not there are new flags to create and returns an
+array of hashes. This array is then passed to Flag::create().
+
+=back
+
+=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';
+ }
+
+ # 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 () if (!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 });
+ }
+
+ # 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 });
+ }
+ }
+
+ # Return the list of flags to update and/or to create.
+ return (\@flags, \@new_flags);
+}
+
+=pod
+
+=over
+
+=item C<notify($flag, $bug, $attachment)>
+
+Sends an email notification about a flag being created, fulfilled
+or deleted.
+
+=back
+
+=cut
+
+sub notify {
+ my ($class, $flag, $old_flag, $obj) = @_;
+
+ 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 $cc_list = $flag ? $flag->type->cc_list : $old_flag->type->cc_list;
+ # Is there someone to notify?
+ return unless ($addressee || $cc_list);
+
+ # 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;
+ my $default_timezone;
+ if (grep { !$_ } values %recipients) {
+ $default_lang = Bugzilla::User->new()->settings->{'lang'}->{'value'};
+ $default_timezone = Bugzilla::User->new()->settings->{'timezone'}->{'value'};
+ }
+
+ 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;
+
+ my $timezone = $recipients{$to} ?
+ $recipients{$to}->settings->{'timezone'}->{'value'} : $default_timezone;
+
+ my $vars = { 'flag' => $flag,
+ 'old_flag' => $old_flag,
+ 'to' => $to,
+ 'timezone' => $timezone,
+ 'bug' => $bug,
+ 'attachment' => $attachment,
+ 'threadingmarker' => build_thread_marker($bug->id, $thread_user_id) };
+
+ my $lang = $recipients{$to} ?
+ $recipients{$to}->settings->{'lang'}->{'value'} : $default_lang;
+
+ my $template = Bugzilla->template_inner($lang);
+ my $message;
+ $template->process("request/email.txt.tmpl", $vars, \$message)
+ || ThrowTemplateError($template->error());
+
+ MessageToMTA($message);
+ }
+}
+
+# This is an internal function used by $bug->flag_types
+# and $attachment->flag_types to collect data about available
+# 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;
+}
+
+=head1 SEE ALSO
+
+=over
+
+=item B<Bugzilla::FlagType>
+
+=back
+
+
+=head1 CONTRIBUTORS
+
+=over
+
+=item Myk Melez <myk@mozilla.org>
+
+=item Jouni Heikniemi <jouni@heikniemi.net>
+
+=item Kevin Benton <kevin.benton@amd.com>
+
+=item Frédéric Buclin <LpSolit@gmail.com>
+
+=back
+
+=cut
+
+1;
diff --git a/Bugzilla/FlagType.pm b/Bugzilla/FlagType.pm
new file mode 100644
index 000000000..0cc392ed2
--- /dev/null
+++ b/Bugzilla/FlagType.pm
@@ -0,0 +1,509 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Myk Melez <myk@mozilla.org>
+# Frédéric Buclin <LpSolit@gmail.com>
+
+use strict;
+
+package Bugzilla::FlagType;
+
+=head1 NAME
+
+Bugzilla::FlagType - A module to deal with Bugzilla flag types.
+
+=head1 SYNOPSIS
+
+FlagType.pm provides an interface to flag types as stored in Bugzilla.
+See below for more information.
+
+=head1 NOTES
+
+=over
+
+=item *
+
+Use of private functions/variables outside this module may lead to
+unexpected results after an upgrade. Please avoid using private
+functions in other files/modules. Private functions are functions
+whose names start with _ or are specifically noted as being private.
+
+=back
+
+=cut
+
+use Bugzilla::Error;
+use Bugzilla::Util;
+use Bugzilla::Group;
+
+use base qw(Bugzilla::Object);
+
+###############################
+#### Initialization ####
+###############################
+
+=begin private
+
+=head1 PRIVATE VARIABLES/CONSTANTS
+
+=over
+
+=item C<DB_COLUMNS>
+
+basic sets of columns and tables for getting flag types from the
+database.
+
+=back
+
+=cut
+
+use constant DB_COLUMNS => qw(
+ flagtypes.id
+ flagtypes.name
+ flagtypes.description
+ flagtypes.cc_list
+ flagtypes.target_type
+ flagtypes.sortkey
+ flagtypes.is_active
+ flagtypes.is_requestable
+ flagtypes.is_requesteeble
+ flagtypes.is_multiplicable
+ flagtypes.grant_group_id
+ flagtypes.request_group_id
+);
+
+=pod
+
+=over
+
+=item C<DB_TABLE>
+
+Which database(s) is the data coming from?
+
+Note: when adding tables to DB_TABLE, make sure to include the separator
+(i.e. words like "LEFT OUTER JOIN") before the table name, since tables take
+multiple separators based on the join type, and therefore it is not possible
+to join them later using a single known separator.
+
+=back
+
+=end private
+
+=cut
+
+use constant DB_TABLE => 'flagtypes';
+use constant LIST_ORDER => 'flagtypes.sortkey, flagtypes.name';
+
+###############################
+#### Accessors ######
+###############################
+
+=head2 METHODS
+
+=over
+
+=item C<id>
+
+Returns the ID of the flagtype.
+
+=item C<name>
+
+Returns the name of the flagtype.
+
+=item C<description>
+
+Returns the description of the flagtype.
+
+=item C<cc_list>
+
+Returns the concatenated CC list for the flagtype, as a single string.
+
+=item C<target_type>
+
+Returns whether the flagtype applies to bugs or attachments.
+
+=item C<is_active>
+
+Returns whether the flagtype is active or disabled. Flags being
+in a disabled flagtype are not deleted. It only prevents you from
+adding new flags to it.
+
+=item C<is_requestable>
+
+Returns whether you can request for the given flagtype
+(i.e. whether the '?' flag is available or not).
+
+=item C<is_requesteeble>
+
+Returns whether you can ask someone specifically or not.
+
+=item C<is_multiplicable>
+
+Returns whether you can have more than one flag for the given
+flagtype in a given bug/attachment.
+
+=item C<sortkey>
+
+Returns the sortkey of the flagtype.
+
+=back
+
+=cut
+
+sub id { return $_[0]->{'id'}; }
+sub name { return $_[0]->{'name'}; }
+sub description { return $_[0]->{'description'}; }
+sub cc_list { return $_[0]->{'cc_list'}; }
+sub target_type { return $_[0]->{'target_type'} eq 'b' ? 'bug' : 'attachment'; }
+sub is_active { return $_[0]->{'is_active'}; }
+sub is_requestable { return $_[0]->{'is_requestable'}; }
+sub is_requesteeble { return $_[0]->{'is_requesteeble'}; }
+sub is_multiplicable { return $_[0]->{'is_multiplicable'}; }
+sub sortkey { return $_[0]->{'sortkey'}; }
+sub request_group_id { return $_[0]->{'request_group_id'}; }
+sub grant_group_id { return $_[0]->{'grant_group_id'}; }
+
+###############################
+#### Methods ####
+###############################
+
+=pod
+
+=over
+
+=item C<grant_list>
+
+Returns a reference to an array of users who have permission to grant this flag type.
+The arrays are populated with hashrefs containing the login, identity and visibility of users.
+
+=item C<grant_group>
+
+Returns the group (as a Bugzilla::Group object) in which a user
+must be in order to grant or deny a request.
+
+=item C<request_group>
+
+Returns the group (as a Bugzilla::Group object) in which a user
+must be in order to request or clear a flag.
+
+=item C<flag_count>
+
+Returns the number of flags belonging to the flagtype.
+
+=item C<inclusions>
+
+Return a hash of product/component IDs and names
+explicitly associated with the flagtype.
+
+=item C<exclusions>
+
+Return a hash of product/component IDs and names
+explicitly excluded from the flagtype.
+
+=back
+
+=cut
+
+sub grant_list {
+ my $self = shift;
+ 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;
+
+ if (!defined $self->{'grant_group'} && $self->{'grant_group_id'}) {
+ $self->{'grant_group'} = new Bugzilla::Group($self->{'grant_group_id'});
+ }
+ return $self->{'grant_group'};
+}
+
+sub request_group {
+ my $self = shift;
+
+ if (!defined $self->{'request_group'} && $self->{'request_group_id'}) {
+ $self->{'request_group'} = new Bugzilla::Group($self->{'request_group_id'});
+ }
+ return $self->{'request_group'};
+}
+
+sub flag_count {
+ my $self = shift;
+
+ if (!defined $self->{'flag_count'}) {
+ $self->{'flag_count'} =
+ Bugzilla->dbh->selectrow_array('SELECT COUNT(*) FROM flags
+ WHERE type_id = ?', undef, $self->{'id'});
+ }
+ return $self->{'flag_count'};
+}
+
+sub inclusions {
+ my $self = shift;
+
+ 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;
+
+ $self->inclusions unless defined $self->{inclusions_as_hash};
+ return $self->{inclusions_as_hash};
+}
+
+sub exclusions {
+ my $self = shift;
+
+ 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;
+
+ $self->exclusions unless defined $self->{exclusions_as_hash};
+ return $self->{exclusions_as_hash};
+}
+
+######################################################################
+# Public Functions
+######################################################################
+
+=pod
+
+=head1 PUBLIC FUNCTIONS/METHODS
+
+=over
+
+=item C<get_clusions($id, $type)>
+
+Return a hash of product/component IDs and names
+associated with the flagtype:
+$clusions{'product_name:component_name'} = "product_ID:component_ID"
+
+=back
+
+=cut
+
+sub get_clusions {
+ my ($id, $type) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ my $list =
+ $dbh->selectall_arrayref("SELECT products.id, products.name, " .
+ " components.id, components.name " .
+ "FROM flagtypes, flag${type}clusions " .
+ "LEFT OUTER JOIN products " .
+ " ON flag${type}clusions.product_id = products.id " .
+ "LEFT OUTER JOIN components " .
+ " ON flag${type}clusions.component_id = components.id " .
+ "WHERE flagtypes.id = ? " .
+ " AND flag${type}clusions.type_id = flagtypes.id",
+ undef, $id);
+ my (%clusions, %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
+
+=over
+
+=item C<match($criteria)>
+
+Queries the database for flag types matching the given criteria
+and returns a list of matching flagtype objects.
+
+=back
+
+=cut
+
+sub match {
+ my ($criteria) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ # Depending on the criteria, we may have to append additional tables.
+ my $tables = [DB_TABLE];
+ my @criteria = sqlify_criteria($criteria, $tables);
+ $tables = join(' ', @$tables);
+ $criteria = join(' AND ', @criteria);
+
+ my $flagtype_ids = $dbh->selectcol_arrayref("SELECT id FROM $tables WHERE $criteria");
+
+ return Bugzilla::FlagType->new_from_list($flagtype_ids);
+}
+
+=pod
+
+=over
+
+=item C<count($criteria)>
+
+Returns the total number of flag types matching the given criteria.
+
+=back
+
+=cut
+
+sub count {
+ my ($criteria) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ # Depending on the criteria, we may have to append additional tables.
+ my $tables = [DB_TABLE];
+ my @criteria = sqlify_criteria($criteria, $tables);
+ $tables = join(' ', @$tables);
+ $criteria = join(' AND ', @criteria);
+
+ my $count = $dbh->selectrow_array("SELECT COUNT(flagtypes.id)
+ FROM $tables WHERE $criteria");
+ return $count;
+}
+
+######################################################################
+# Private Functions
+######################################################################
+
+=begin private
+
+=head1 PRIVATE FUNCTIONS
+
+=over
+
+=item C<sqlify_criteria($criteria, $tables)>
+
+Converts a hash of criteria into a list of SQL criteria.
+$criteria is a reference to the criteria (field => value),
+$tables is a reference to an array of tables being accessed
+by the query.
+
+=back
+
+=cut
+
+sub sqlify_criteria {
+ my ($criteria, $tables) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ # the generated list of SQL criteria; "1=1" is a clever way of making sure
+ # there's something in the list so calling code doesn't have to check list
+ # size before building a WHERE clause out of it
+ my @criteria = ("1=1");
+
+ if ($criteria->{name}) {
+ my $name = $dbh->quote($criteria->{name});
+ trick_taint($name); # Detaint data as we have quoted it.
+ push(@criteria, "flagtypes.name = $name");
+ }
+ if ($criteria->{target_type}) {
+ # The target type is stored in the database as a one-character string
+ # ("a" for attachment and "b" for bug), but this function takes complete
+ # names ("attachment" and "bug") for clarity, so we must convert them.
+ my $target_type = $criteria->{target_type} eq 'bug'? 'b' : 'a';
+ push(@criteria, "flagtypes.target_type = '$target_type'");
+ }
+ if (exists($criteria->{is_active})) {
+ my $is_active = $criteria->{is_active} ? "1" : "0";
+ push(@criteria, "flagtypes.is_active = $is_active");
+ }
+ if ($criteria->{product_id}) {
+ my $product_id = $criteria->{product_id};
+
+ # Add inclusions to the query, which simply involves joining the table
+ # by flag type ID and target product/component.
+ push(@$tables, "INNER JOIN flaginclusions AS i ON flagtypes.id = i.type_id");
+ push(@criteria, "(i.product_id = $product_id OR i.product_id IS NULL)");
+
+ # 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};
+ 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->{group}) {
+ my $gid = $criteria->{group};
+ detaint_natural($gid);
+ push(@criteria, "(flagtypes.grant_group_id = $gid " .
+ " OR flagtypes.request_group_id = $gid)");
+ }
+
+ return @criteria;
+}
+
+1;
+
+=end private
+
+=head1 SEE ALSO
+
+=over
+
+=item B<Bugzilla::Flags>
+
+=back
+
+=head1 CONTRIBUTORS
+
+=over
+
+=item Myk Melez <myk@mozilla.org>
+
+=item Kevin Benton <kevin.benton@amd.com>
+
+=item Frédéric Buclin <LpSolit@gmail.com>
+
+=back
+
+=cut
diff --git a/Bugzilla/Group.pm b/Bugzilla/Group.pm
new file mode 100644
index 000000000..f047ef365
--- /dev/null
+++ b/Bugzilla/Group.pm
@@ -0,0 +1,589 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Joel Peshkin <bugreport@peshkin.net>
+# Erik Stambaugh <erik@dasbistro.com>
+# Tiago R. Mello <timello@async.com.br>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+use strict;
+
+package Bugzilla::Group;
+
+use base qw(Bugzilla::Object);
+
+use Bugzilla::Constants;
+use Bugzilla::Util;
+use Bugzilla::Error;
+use Bugzilla::Config qw(:admin);
+
+###############################
+##### Module Initialization ###
+###############################
+
+use constant DB_COLUMNS => qw(
+ groups.id
+ groups.name
+ groups.description
+ groups.isbuggroup
+ groups.userregexp
+ groups.isactive
+ groups.icon_url
+);
+
+use constant DB_TABLE => 'groups';
+
+use constant LIST_ORDER => 'isbuggroup, name';
+
+use constant VALIDATORS => {
+ name => \&_check_name,
+ description => \&_check_description,
+ userregexp => \&_check_user_regexp,
+ isactive => \&_check_is_active,
+ isbuggroup => \&_check_is_bug_group,
+ icon_url => \&_check_icon_url,
+};
+
+use constant UPDATE_COLUMNS => qw(
+ name
+ description
+ userregexp
+ isactive
+ icon_url
+);
+
+# Parameters that are lists of groups.
+use constant GROUP_PARAMS => qw(chartgroup insidergroup timetrackinggroup
+ querysharegroup);
+
+###############################
+#### Accessors ######
+###############################
+
+sub description { return $_[0]->{'description'}; }
+sub is_bug_group { return $_[0]->{'isbuggroup'}; }
+sub user_regexp { return $_[0]->{'userregexp'}; }
+sub is_active { return $_[0]->{'isactive'}; }
+sub icon_url { return $_[0]->{'icon_url'}; }
+
+sub 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};
+}
+
+sub 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 = $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);
+}
+
+sub 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};
+}
+
+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};
+}
+
+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,
+ 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};
+}
+
+###############################
+#### Methods ####
+###############################
+
+sub set_description { $_[0]->set('description', $_[1]); }
+sub set_is_active { $_[0]->set('isactive', $_[1]); }
+sub set_name { $_[0]->set('name', $_[1]); }
+sub set_user_regexp { $_[0]->set('userregexp', $_[1]); }
+sub set_icon_url { $_[0]->set('icon_url', $_[1]); }
+
+sub update {
+ my $self = shift;
+ my $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]);
+
+ $self->_rederive_regexp() if exists $changes->{userregexp};
+
+ Bugzilla::Hook::process('group_end_of_update',
+ { group => $self, changes => $changes });
+ $dbh->bz_commit_transaction();
+ 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 });
+ }
+
+ # 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'};
+
+ my $cantdelete = 0;
+
+ 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 $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;
+ }
+
+ 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();
+}
+
+# Add missing entries in bug_group_map for bugs created while
+# a mandatory group was disabled and which is now enabled again.
+sub _enforce_mandatory {
+ my ($self) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $gid = $self->id;
+
+ my $bug_ids =
+ $dbh->selectcol_arrayref('SELECT bugs.bug_id
+ FROM bugs
+ INNER JOIN group_control_map
+ ON group_control_map.product_id = bugs.product_id
+ LEFT JOIN bug_group_map
+ ON bug_group_map.bug_id = bugs.bug_id
+ AND bug_group_map.group_id = group_control_map.group_id
+ WHERE group_control_map.group_id = ?
+ AND group_control_map.membercontrol = ?
+ AND bug_group_map.group_id IS NULL',
+ undef, ($gid, CONTROLMAPMANDATORY));
+
+ my $sth = $dbh->prepare('INSERT INTO bug_group_map (bug_id, group_id) VALUES (?, ?)');
+ foreach my $bug_id (@$bug_ids) {
+ $sth->execute($bug_id, $gid);
+ }
+}
+
+sub is_active_bug_group {
+ my $self = shift;
+ return $self->is_active && $self->is_bug_group;
+}
+
+sub _rederive_regexp {
+ my ($self) = @_;
+
+ my $dbh = Bugzilla->dbh;
+ my $sth = $dbh->prepare("SELECT userid, login_name, group_id
+ FROM profiles
+ LEFT JOIN user_group_map
+ ON user_group_map.user_id = profiles.userid
+ AND group_id = ?
+ AND grant_type = ?
+ AND isbless = 0");
+ my $sthadd = $dbh->prepare("INSERT INTO user_group_map
+ (user_id, group_id, grant_type, isbless)
+ VALUES (?, ?, ?, 0)");
+ my $sthdel = $dbh->prepare("DELETE FROM user_group_map
+ WHERE user_id = ? AND group_id = ?
+ AND grant_type = ? and isbless = 0");
+ $sth->execute($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
+ 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;
+ }
+ }
+ }
+ return \@groups;
+}
+
+
+
+
+################################
+##### Module Subroutines ###
+################################
+
+sub create {
+ my $class = shift;
+ my ($params) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ print get_text('install_group_create', { name => $params->{name} }) . "\n"
+ if Bugzilla->usage_mode == USAGE_MODE_CMDLINE;
+
+ $dbh->bz_start_transaction();
+
+ my $group = $class->SUPER::create(@_);
+
+ # Since we created a new group, give the "admin" group all privileges
+ # initially.
+ my $admin = new Bugzilla::Group({name => 'admin'});
+ # This function is also used to create the "admin" group itself,
+ # so there's a chance it won't exist yet.
+ if ($admin) {
+ my $sth = $dbh->prepare('INSERT INTO group_group_map
+ (member_id, grantor_id, grant_type)
+ VALUES (?, ?, ?)');
+ $sth->execute($admin->id, $group->id, GROUP_MEMBERSHIP);
+ $sth->execute($admin->id, $group->id, GROUP_BLESS);
+ $sth->execute($admin->id, $group->id, GROUP_VISIBLE);
+ }
+
+ $group->_rederive_regexp() if $group->user_regexp;
+
+ 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 $sth = $dbh->prepare($query);
+ $sth->execute($name);
+ my ($ret) = $sth->fetchrow_array();
+ return $ret;
+}
+
+###############################
+### Validators ###
+###############################
+
+sub _check_name {
+ my ($invocant, $name) = @_;
+ $name = trim($name);
+ $name || ThrowUserError("empty_group_name");
+ # If we're creating a Group or changing the name...
+ if (!ref($invocant) || 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;
+}
+
+sub _check_user_regexp {
+ my ($invocant, $regex) = @_;
+ $regex = trim($regex) || '';
+ ThrowUserError("invalid_regexp") unless (eval {qr/$regex/});
+ return $regex;
+}
+
+sub _check_is_active { return $_[1] ? 1 : 0; }
+sub _check_is_bug_group {
+ return $_[1] ? 1 : 0;
+}
+
+sub _check_icon_url { return $_[1] ? clean_text($_[1]) : undef; }
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Group - Bugzilla group class.
+
+=head1 SYNOPSIS
+
+ use Bugzilla::Group;
+
+ my $group = new Bugzilla::Group(1);
+ my $group = new Bugzilla::Group({name => 'AcmeGroup'});
+
+ my $id = $group->id;
+ my $name = $group->name;
+ my $description = $group->description;
+ my $user_reg_exp = $group->user_reg_exp;
+ my $is_active = $group->is_active;
+ my $icon_url = $group->icon_url;
+ my $is_active_bug_group = $group->is_active_bug_group;
+
+ my $group_id = Bugzilla::Group::ValidateGroupName('admin', @users);
+ my @groups = Bugzilla::Group->get_all;
+
+=head1 DESCRIPTION
+
+Group.pm represents a Bugzilla Group object. It is an implementation
+of L<Bugzilla::Object>, and thus has all the methods that L<Bugzilla::Object>
+provides, in addition to any methods documented below.
+
+=head1 SUBROUTINES
+
+=over
+
+=item C<create>
+
+Note that in addition to what L<Bugzilla::Object/create($params)>
+normally does, this function also makes the new group be inherited
+by the C<admin> group. That is, the C<admin> group will automatically
+be a member of this group.
+
+=item C<ValidateGroupName($name, @users)>
+
+ Description: ValidateGroupName checks to see if ANY of the users
+ in the provided list of user objects can see the
+ named group.
+
+ Params: $name - String with the group name.
+ @users - An array with Bugzilla::User objects.
+
+ Returns: It returns the group id if successful
+ and undef otherwise.
+
+=back
+
+=head1 METHODS
+
+=over
+
+=item C<check_remove>
+
+=over
+
+=item B<Description>
+
+Determines whether it's OK to remove this group from the database, and
+throws an error if it's not OK.
+
+=item B<Params>
+
+=over
+
+=item C<test_only>
+
+C<boolean> If you want to only check if the group can be deleted I<at all>,
+under any circumstances, specify C<test_only> to just do the most basic tests
+(the other parameters will be ignored in this situation, as those tests won't
+be run).
+
+=item C<remove_from_users>
+
+C<boolean> True if it would be OK to remove all users who are in this group
+from this group.
+
+=item C<remove_from_bugs>
+
+C<boolean> True if it would be OK to remove all bugs that are in this group
+from this group.
+
+=item C<remove_from_flags>
+
+C<boolean> True if it would be OK to stop all flagtypes that reference
+this group from referencing this group (e.g., as their grantgroup or
+requestgroup).
+
+=item C<remove_from_products>
+
+C<boolean> True if it would be OK to remove this group from all group controls
+on products.
+
+=back
+
+=item B<Returns> (nothing)
+
+=back
+
+=item C<members_non_inherited>
+
+Returns an arrayref of L<Bugzilla::User> objects representing people who are
+"directly" in this group, meaning that they're in it because they match
+the group regular expression, or they have been actually added to the
+group manually.
+
+=item C<flatten_group_membership>
+
+Accepts a list of groups and returns a list of all the groups whose members
+inherit membership in any group on the list. So, we can determine if a user
+is in any of the groups input to flatten_group_membership by querying the
+user_group_map for any user with DIRECT or REGEXP membership IN() the list
+of groups returned.
+
+=back
diff --git a/Bugzilla/Hook.pm b/Bugzilla/Hook.pm
new file mode 100644
index 000000000..14b27b48d
--- /dev/null
+++ b/Bugzilla/Hook.pm
@@ -0,0 +1,1312 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Zach Lipton <zach@zachlipton.com>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::Hook;
+use strict;
+
+sub process {
+ my ($name, $args) = @_;
+
+ _entering($name);
+
+ foreach my $extension (@{ Bugzilla->extensions }) {
+ if ($extension->can($name)) {
+ $extension->$name($args);
+ }
+ }
+
+ _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;
+}
+
+sub _entering {
+ my ($hook_name) = @_;
+ my $hook_stack = Bugzilla->request_cache->{hook_stack} ||= [];
+ push(@$hook_stack, $hook_name);
+}
+
+sub _leaving {
+ pop @{ Bugzilla->request_cache->{hook_stack} };
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Hook - Extendable extension hooks for Bugzilla code
+
+=head1 SYNOPSIS
+
+ use Bugzilla::Hook;
+
+ Bugzilla::Hook::process("hookname", { arg => $value, arg2 => $value2 });
+
+=head1 DESCRIPTION
+
+Bugzilla allows extension modules to drop in and add routines at
+arbitrary points in Bugzilla code. These points are referred to as
+hooks. When a piece of standard Bugzilla code wants to allow an extension
+to perform additional functions, it uses Bugzilla::Hook's L</process>
+subroutine to invoke any extension code if installed.
+
+The implementation of extensions is described in L<Bugzilla::Extension>.
+
+There is sample code for every hook in the Example extension, located in
+F<extensions/Example/Extension.pm>.
+
+=head2 How Hooks Work
+
+When a hook named C<HOOK_NAME> is run, Bugzilla looks through all
+enabled L<extensions|Bugzilla::Extension> for extensions that implement
+a subroutine named C<HOOK_NAME>.
+
+See L<Bugzilla::Extension> for more details about how an extension
+can run code during a hook.
+
+=head1 SUBROUTINES
+
+=over
+
+=item C<process>
+
+=over
+
+=item B<Description>
+
+Invoke any code hooks with a matching name from any installed extensions.
+
+See L<Bugzilla::Extension> for more information on Bugzilla's extension
+mechanism.
+
+=item B<Params>
+
+=over
+
+=item C<$name> - The name of the hook to invoke.
+
+=item C<$args> - A hashref. The named args to pass to the hook.
+They will be passed as arguments to the hook method in the extension.
+
+=back
+
+=item B<Returns> (nothing)
+
+=back
+
+=back
+
+=head1 HOOKS
+
+This describes what hooks exist in Bugzilla currently. They are mostly
+in alphabetical order, but some related hooks are near each other instead
+of being alphabetical.
+
+=head2 attachment_process_data
+
+This happens at the very beginning process of the attachment creation.
+You can edit the attachment content itself as well as all attributes
+of the attachment, before they are validated and inserted into the DB.
+
+Params:
+
+=over
+
+=item C<data> - A reference pointing either to the content of the file
+being uploaded or pointing to the filehandle associated with the file.
+
+=item C<attributes> - A hashref whose keys are the same as the input to
+L<Bugzilla::Attachment/create>. The data in this hashref hasn't been validated
+yet.
+
+=back
+
+=head2 auth_login_methods
+
+This allows you to add new login types to Bugzilla.
+(See L<Bugzilla::Auth::Login>.)
+
+Params:
+
+=over
+
+=item C<modules>
+
+This is a hash--a mapping from login-type "names" to the actual module on
+disk. The keys will be all the values that were passed to
+L<Bugzilla::Auth/login> for the C<Login> parameter. The values are the
+actual path to the module on disk. (For example, if the key is C<DB>, the
+value is F<Bugzilla/Auth/Login/DB.pm>.)
+
+For your extension, the path will start with
+F<Bugzilla/Extension/Foo/>, where "Foo" is the name of your Extension.
+(See the code in the example extension.)
+
+If your login type is in the hash as a key, you should set that key to the
+right path to your module. That module's C<new> method will be called,
+probably with empty parameters. If your login type is I<not> in the hash,
+you should not set it.
+
+You will be prevented from adding new keys to the hash, so make sure your
+key is in there before you modify it. (In other words, you can't add in
+login methods that weren't passed to L<Bugzilla::Auth/login>.)
+
+=back
+
+=head2 auth_verify_methods
+
+This works just like L</auth_login_methods> except it's for
+login verification methods (See L<Bugzilla::Auth::Verify>.) It also
+takes a C<modules> parameter, just like L</auth_login_methods>.
+
+=head2 bug_columns
+
+B<DEPRECATED> Use L</object_columns> instead.
+
+This allows you to add new fields that will show up in every L<Bugzilla::Bug>
+object. Note that you will also need to use the L</bug_fields> hook in
+conjunction with this hook to make this work.
+
+Params:
+
+=over
+
+=item C<columns> - An arrayref containing an array of column names. Push
+your column name(s) onto the array.
+
+=back
+
+=head2 bug_end_of_create
+
+This happens at the end of L<Bugzilla::Bug/create>, after all other changes are
+made to the database. This occurs inside a database transaction.
+
+Params:
+
+=over
+
+=item C<bug> - The created bug object.
+
+=item C<timestamp> - The timestamp used for all updates in this transaction,
+as a SQL date string.
+
+=back
+
+=head2 bug_end_of_create_validators
+
+This happens during L<Bugzilla::Bug/create>, after all parameters have
+been validated, but before anything has been inserted into the database.
+
+Params:
+
+=over
+
+=item C<params>
+
+A hashref. The validated parameters passed to C<create>.
+
+=back
+
+=head2 bug_end_of_update
+
+This happens at the end of L<Bugzilla::Bug/update>, after all other changes are
+made to the database. This generally occurs inside a database transaction.
+
+Params:
+
+=over
+
+=item C<bug>
+
+The changed bug object, with all fields set to their updated values.
+
+=item C<old_bug>
+
+A bug object pulled from the database before the fields were set to
+their updated values (so it has the old values available for each field).
+
+=item C<timestamp>
+
+The timestamp used for all updates in this transaction, as a SQL date
+string.
+
+=item C<changes>
+
+The hash of changed fields. C<< $changes->{field} = [old, new] >>
+
+=back
+
+=head2 bug_check_can_change_field
+
+This hook controls what fields users are allowed to change. You can add code
+here for site-specific policy changes and other customizations.
+
+This hook is only executed if the field's new and old values differ.
+
+Any denies take priority over any allows. So, if another extension denies
+a change but yours allows the change, the other extension's deny will
+override your extension's allow.
+
+Params:
+
+=over
+
+=item C<bug>
+
+L<Bugzilla::Bug> - The current bug object that this field is changing on.
+
+=item C<field>
+
+The name (from the C<fielddefs> table) of the field that we are checking.
+
+=item C<new_value>
+
+The new value that the field is being changed to.
+
+=item C<old_value>
+
+The old value that the field is being changed from.
+
+=item C<priv_results>
+
+C<array> - This is how you explicitly allow or deny a change. You should only
+push something into this array if you want to explicitly allow or explicitly
+deny the change, and thus skip all other permission checks that would otherwise
+happen after this hook is called. If you don't care about the field change,
+then don't push anything into the array.
+
+The pushed value should be a choice from the following constants:
+
+=over
+
+=item C<PRIVILEGES_REQUIRED_NONE>
+
+No privileges required. This explicitly B<allows> a change.
+
+=item C<PRIVILEGES_REQUIRED_REPORTER>
+
+User is not the reporter, assignee or an empowered user, so B<deny>.
+
+=item C<PRIVILEGES_REQUIRED_ASSIGNEE>
+
+User is not the assignee or an empowered user, so B<deny>.
+
+=item C<PRIVILEGES_REQUIRED_EMPOWERED>
+
+User is not a sufficiently empowered user, so B<deny>.
+
+=back
+
+=back
+
+=head2 bug_fields
+
+Allows the addition of database fields from the bugs table to the standard
+list of allowable fields in a L<Bugzilla::Bug> object, so that
+you can call the field as a method.
+
+Note: You should add here the names of any fields you added in L</bug_columns>.
+
+Params:
+
+=over
+
+=item C<columns> - A arrayref containing an array of column names. Push
+your column name(s) onto the array.
+
+=back
+
+=head2 bug_format_comment
+
+Allows you to do custom parsing on comments before they are displayed. You do
+this by returning two regular expressions: one that matches the section you
+want to replace, and then another that says what you want to replace that
+match with.
+
+The matching and replacement will be run with the C</g> switch on the regex.
+
+Params:
+
+=over
+
+=item C<regexes>
+
+An arrayref of hashrefs.
+
+You should push a hashref containing two keys (C<match> and C<replace>)
+in to this array. C<match> is the regular expression that matches the
+text you want to replace, C<replace> is what you want to replace that
+text with. (This gets passed into a regular expression like
+C<s/$match/$replace/>.)
+
+Instead of specifying a regular expression for C<replace> you can also
+return a coderef (a reference to a subroutine). If you want to use
+backreferences (using C<$1>, C<$2>, etc. in your C<replace>), you have to use
+this method--it won't work if you specify C<$1>, C<$2> in a regular expression
+for C<replace>. Your subroutine will get a hashref as its only argument. This
+hashref contains a single key, C<matches>. C<matches> is an arrayref that
+contains C<$1>, C<$2>, C<$3>, etc. in order, up to C<$10>. Your subroutine
+should return what you want to replace the full C<match> with. (See the code
+example for this hook if you want to see how this actually all works in code.
+It's simpler than it sounds.)
+
+B<You are responsible for HTML-escaping your returned data.> Failing to
+do so could open a security hole in Bugzilla.
+
+=item C<text>
+
+A B<reference> to the exact text that you are parsing.
+
+Generally you should not modify this yourself. Instead you should be
+returning regular expressions using the C<regexes> array.
+
+The text has not been parsed in any way. (So, for example, it is not
+HTML-escaped. You get "&", not "&amp;".)
+
+=item C<bug>
+
+The L<Bugzilla::Bug> object that this comment is on. Sometimes this is
+C<undef>, meaning that we are parsing text that is not on a bug.
+
+=item C<comment>
+
+A L<Bugzilla::Comment> object representing the comment you are about to
+parse.
+
+Sometimes this is C<undef>, meaning that we are parsing text that is
+not a bug comment (but could still be some other part of a bug, like
+the summary line).
+
+=back
+
+=head2 buglist_columns
+
+This happens in L<Bugzilla::Search/COLUMNS>, which determines legal bug
+list columns for F<buglist.cgi> and F<colchange.cgi>. It gives you the
+opportunity to add additional display columns.
+
+Params:
+
+=over
+
+=item C<columns> - A hashref, where the keys are unique string identifiers
+for the column being defined and the values are hashrefs with the
+following fields:
+
+=over
+
+=item C<name> - The name of the column in the database.
+
+=item C<title> - The title of the column as displayed to users.
+
+=back
+
+The definition is structured as:
+
+ $columns->{$id} = { name => $name, title => $title };
+
+=back
+
+=head2 search_operator_field_override
+
+This allows you to modify L<Bugzilla::Search/OPERATOR_FIELD_OVERRIDE>,
+which determines the search functions for fields. It allows you to specify
+custom search functionality for certain fields.
+
+See L<Bugzilla::Search/OPERATOR_FIELD_OVERRIDE> for reference and see
+the code in the example extension.
+
+Note that the interface to this hook is B<UNSTABLE> and it may change in the
+future.
+
+Params:
+
+=over
+
+=item C<operators> - See L<Bugzilla::Search/OPERATOR_FIELD_OVERRIDE> to get
+an idea of the structure.
+
+=item C<search> - The L<Bugzilla::Search> object.
+
+=back
+
+=head2 bugmail_recipients
+
+This allows you to modify the list of users who are going to be receiving
+a particular bugmail. It also allows you to specify why they are receiving
+the bugmail.
+
+Users' bugmail preferences will be applied to any users that you add
+to the list. (So, for example, if you add somebody as though they were
+a CC on the bug, and their preferences state that they don't get email
+when they are a CC, they won't get email.)
+
+This hook is called before watchers or globalwatchers are added to the
+recipient list.
+
+Params:
+
+=over
+
+=item C<bug>
+
+The L<Bugzilla::Bug> that bugmail is being sent about.
+
+=item C<recipients>
+
+This is a hashref. The keys are numeric user ids from the C<profiles>
+table in the database, for each user who should be receiving this bugmail.
+The values are hashrefs. The keys in I<these> hashrefs correspond to
+the "relationship" that the user has to the bug they're being emailed
+about, and the value should always be C<1>. The "relationships"
+are described by the various C<REL_> constants in L<Bugzilla::Constants>.
+
+Here's an example of adding userid C<123> to the recipient list
+as though he were on the CC list:
+
+ $recipients->{123}->{+REL_CC} = 1
+
+(We use C<+> in front of C<REL_CC> so that Perl interprets it as a constant
+instead of as a string.)
+
+=item C<diffs>
+
+This is a list of hashes, each hash representing a change to the bug. Each
+hash has the following members: C<field_name>, C<bug_when>, C<old>, C<new>
+and C<who> (a L<Bugzilla::User>). If appropriate, there will also be
+C<attach_id> or C<comment_id>; if either is present, there will be
+C<isprivate>. See C<_get_diffs> in F<Bugzilla/BugMail.pm> to see exactly how
+it is populated. Warning: the format and existence of the "diffs" parameter
+is subject to change in future releases of Bugzilla.
+
+=back
+
+
+=head2 bugmail_relationships
+
+There are various sorts of "relationships" that a user can have to a bug,
+such as Assignee, CC, etc. If you want to add a new type of relationship,
+you should use this hook.
+
+Params:
+
+=over
+
+=item C<relationships>
+
+A hashref, where the keys are numbers and the values are strings.
+
+The keys represent a numeric identifier for the relationship. The
+numeric identifier should be a negative number between -1 and -127.
+The number must be unique across all extensions. (Negative numbers
+are used so as not to conflict with relationship identifiers in Bugzilla
+itself.)
+
+The value is the "name" of this relationship that will show up in email
+headers in bugmails. The "name" should be short and should contain no
+spaces.
+
+=back
+
+
+=head2 config_add_panels
+
+If you want to add new panels to the Parameters administrative interface,
+this is where you do it.
+
+Params:
+
+=over
+
+=item C<panel_modules>
+
+A hashref, where the keys are the "name" of the panel and the value
+is the Perl module representing that panel. For example, if
+the name is C<Auth>, the value would be C<Bugzilla::Config::Auth>.
+
+For your extension, the Perl module would start with
+C<Bugzilla::Extension::Foo>, where "Foo" is the name of your Extension.
+(See the code in the example extension.)
+
+=back
+
+=head2 config_modify_panels
+
+This is how you modify already-existing panels in the Parameters
+administrative interface. For example, if you wanted to add a new
+Auth method (modifying Bugzilla::Config::Auth) this is how you'd
+do it.
+
+Params:
+
+=over
+
+=item C<panels>
+
+A hashref, where the keys are lower-case panel "names" (like C<auth>,
+C<admin>, etc.) and the values are hashrefs. The hashref contains a
+single key, C<params>. C<params> is an arrayref--the return value from
+C<get_param_list> for that module. You can modify C<params> and
+your changes will be reflected in the interface.
+
+Adding new keys to C<panels> will have no effect. You should use
+L</config_add_panels> if you want to add new panels.
+
+=back
+
+=head2 email_in_before_parse
+
+This happens right after an inbound email is converted into an Email::MIME
+object, but before we start parsing the email to extract field data. This
+means the email has already been decoded for you. It gives you a chance
+to interact with the email itself before L<email_in> starts parsing its content.
+
+=over
+
+=item C<mail> - An Email::MIME object. The decoded incoming email.
+
+=item C<fields> - A hashref. The hash which will contain extracted data.
+
+=back
+
+=head2 email_in_after_parse
+
+This happens after all the data has been extracted from the email, but before
+the reporter is validated, during L<email_in>. This lets you do things after
+the normal parsing of the email, such as sanitizing field data, changing the
+user account being used to file a bug, etc.
+
+=over
+
+=item C<fields> - A hashref. The hash containing the extracted field data.
+
+=back
+
+=head2 enter_bug_entrydefaultvars
+
+B<DEPRECATED> - Use L</template_before_process> instead.
+
+This happens right before the template is loaded on enter_bug.cgi.
+
+Params:
+
+=over
+
+=item C<vars> - A hashref. The variables that will be passed into the template.
+
+=back
+
+=head2 flag_end_of_update
+
+This happens at the end of L<Bugzilla::Flag/update_flags>, after all other
+changes are made to the database and after emails are sent. It gives you a
+before/after snapshot of flags so you can react to specific flag changes.
+This generally occurs inside a database transaction.
+
+Note that the interface to this hook is B<UNSTABLE> and it may change in the
+future.
+
+Params:
+
+=over
+
+=item C<object> - The changed bug or attachment object.
+
+=item C<timestamp> - The timestamp used for all updates in this transaction,
+as a SQL date string.
+
+=item C<old_flags> - The snapshot of flag summaries from before the change.
+
+=item C<new_flags> - The snapshot of flag summaries after the change. Call
+C<my ($removed, $added) = diff_arrays(old_flags, new_flags)> to get the list of
+changed flags, and search for a specific condition like C<added eq 'review-'>.
+
+=back
+
+=head2 group_before_delete
+
+This happens in L<Bugzilla::Group/remove_from_db>, after we've confirmed
+that the group can be deleted, but before any rows have actually
+been removed from the database. This occurs inside a database
+transaction.
+
+Params:
+
+=over
+
+=item C<group> - The L<Bugzilla::Group> being deleted.
+
+=back
+
+=head2 group_end_of_create
+
+This happens at the end of L<Bugzilla::Group/create>, after all other
+changes are made to the database. This occurs inside a database transaction.
+
+Params:
+
+=over
+
+=item C<group>
+
+The new L<Bugzilla::Group> object that was just created.
+
+=back
+
+=head2 group_end_of_update
+
+This happens at the end of L<Bugzilla::Group/update>, after all other
+changes are made to the database. This occurs inside a database transaction.
+
+Params:
+
+=over
+
+=item C<group> - The changed L<Bugzilla::Group> object, with all fields set
+to their updated values.
+
+=item C<changes> - The hash of changed fields.
+C<< $changes->{$field} = [$old, $new] >>
+
+=back
+
+=head2 install_before_final_checks
+
+Allows execution of custom code before the final checks are done in
+checksetup.pl.
+
+Params:
+
+=over
+
+=item C<silent>
+
+A flag that indicates whether or not checksetup is running in silent mode.
+If this is true, messages that are I<always> printed by checksetup.pl should be
+suppressed, but messages about any changes that are just being done this one
+time should be printed.
+
+=back
+
+=head2 install_update_db
+
+This happens at the very end of all the tables being updated
+during an installation or upgrade. If you need to modify your custom
+schema or add new columns to existing tables, do it here. No params are
+passed.
+
+=head2 db_schema_abstract_schema
+
+This allows you to add tables to Bugzilla. Note that we recommend that you
+prefix the names of your tables with some word (preferably the name of
+your Extension), so that they don't conflict with any future Bugzilla tables.
+
+If you wish to add new I<columns> to existing Bugzilla tables, do that
+in L</install_update_db>.
+
+Params:
+
+=over
+
+=item C<schema> - A hashref, in the format of
+L<Bugzilla::DB::Schema/ABSTRACT_SCHEMA>. Add new hash keys to make new table
+definitions. F<checksetup.pl> will automatically add these tables to the
+database when run.
+
+=back
+
+=head2 mailer_before_send
+
+Called right before L<Bugzilla::Mailer> sends a message to the MTA.
+
+Params:
+
+=over
+
+=item C<email> - The C<Email::MIME> object that's about to be sent.
+
+=item C<mailer_args> - An arrayref that's passed as C<mailer_args> to
+L<Email::Send/new>.
+
+=back
+
+=head2 object_before_create
+
+This happens at the beginning of L<Bugzilla::Object/create>.
+
+Params:
+
+=over
+
+=item C<class>
+
+The name of the class that C<create> was called on. You can check this
+like C<< if ($class->isa('Some::Class')) >> in your code, to perform specific
+tasks before C<create> for only certain classes.
+
+=item C<params>
+
+A hashref. The set of named parameters passed to C<create>.
+
+=back
+
+
+=head2 object_before_delete
+
+This happens in L<Bugzilla::Object/remove_from_db>, after we've confirmed
+that the object can be deleted, but before any rows have actually
+been removed from the database. This sometimes occurs inside a database
+transaction.
+
+Params:
+
+=over
+
+=item C<object> - The L<Bugzilla::Object> being deleted. You will probably
+want to check its type like C<< $object->isa('Some::Class') >> before doing
+anything with it.
+
+=back
+
+
+=head2 object_before_set
+
+Called during L<Bugzilla::Object/set>, before any actual work is done.
+You can use this to perform actions before a value is changed for
+specific fields on certain types of objects.
+
+Params:
+
+=over
+
+=item C<object>
+
+The object that C<set> was called on. You will probably want to
+do something like C<< if ($object->isa('Some::Class')) >> in your code to
+limit your changes to only certain subclasses of Bugzilla::Object.
+
+=item C<field>
+
+The name of the field being updated in the object.
+
+=item C<value>
+
+The value being set on the object.
+
+=back
+
+=head2 object_columns
+
+This hook allows you to add new "fields" to existing Bugzilla objects,
+that correspond to columns in their tables.
+
+For example, if you added an C<example> column to the "bugs" table, you
+would have to also add an C<example> field to the C<Bugzilla::Bug> object
+in order to access that data via Bug objects.
+
+Don't do anything slow inside this hook--it's called several times on
+every page of Bugzilla.
+
+Params:
+
+=over
+
+=item C<class>
+
+The name of the class that this hook is being called on. You can check this
+like C<< if ($class->isa('Some::Class')) >> in your code, to add new
+fields only for certain classes.
+
+=item C<columns>
+
+An arrayref. Add the string names of columns to this array to add new
+values to objects.
+
+For example, if you add an C<example> column to a particular table
+(using L</install_update_db>), and then push the string C<example> into
+this array for the object that uses that table, then you can access the
+information in that column via C<< $object->{example} >> on all objects
+of that type.
+
+This arrayref does not contain the standard column names--you cannot modify
+or remove standard object columns using this hook.
+
+=back
+
+=head2 object_end_of_create
+
+Called at the end of L<Bugzilla::Object/create>, after all other changes are
+made to the database. This occurs inside a database transaction.
+
+Params:
+
+=over
+
+=item C<class>
+
+The name of the class that C<create> was called on. You can check this
+like C<< if ($class->isa('Some::Class')) >> in your code, to perform specific
+tasks for only certain classes.
+
+=item C<object>
+
+The created object.
+
+=back
+
+=head2 object_end_of_create_validators
+
+Called at the end of L<Bugzilla::Object/run_create_validators>. You can
+use this to run additional validation when creating an object.
+
+If a subclass has overridden C<run_create_validators>, then this usually
+happens I<before> the subclass does its custom validation.
+
+Params:
+
+=over
+
+=item C<class>
+
+The name of the class that C<create> was called on. You can check this
+like C<< if ($class->isa('Some::Class')) >> in your code, to perform specific
+tasks for only certain classes.
+
+=item C<params>
+
+A hashref. The set of named parameters passed to C<create>, modified and
+validated by the C<VALIDATORS> specified for the object.
+
+=back
+
+
+=head2 object_end_of_set
+
+Called during L<Bugzilla::Object/set>, after all the code of the function
+has completed (so the value has been validated and the field has been set
+to the new value). You can use this to perform actions after a value is
+changed for specific fields on certain types of objects.
+
+The new value is not specifically passed to this hook because you can
+get it as C<< $object->{$field} >>.
+
+Params:
+
+=over
+
+=item C<object>
+
+The object that C<set> was called on. You will probably want to
+do something like C<< if ($object->isa('Some::Class')) >> in your code to
+limit your changes to only certain subclasses of Bugzilla::Object.
+
+=item C<field>
+
+The name of the field that was updated in the object.
+
+=back
+
+
+=head2 object_end_of_set_all
+
+This happens at the end of L<Bugzilla::Object/set_all>. This is a
+good place to call custom set_ functions on objects, or to make changes
+to an object before C<update()> is called.
+
+Params:
+
+=over
+
+=item C<object>
+
+The L<Bugzilla::Object> which is being updated. You will probably want to
+do something like C<< if ($object->isa('Some::Class')) >> in your code to
+limit your changes to only certain subclasses of Bugzilla::Object.
+
+=item C<params>
+
+A hashref. The set of named parameters passed to C<set_all>.
+
+=back
+
+=head2 object_end_of_update
+
+Called during L<Bugzilla::Object/update>, after changes are made
+to the database, but while still inside a transaction.
+
+Params:
+
+=over
+
+=item C<object>
+
+The object that C<update> was called on. You will probably want to
+do something like C<< if ($object->isa('Some::Class')) >> in your code to
+limit your changes to only certain subclasses of Bugzilla::Object.
+
+=item C<old_object>
+
+The object as it was before it was updated.
+
+=item C<changes>
+
+The fields that have been changed, in the same format that
+L<Bugzilla::Object/update> returns.
+
+=back
+
+=head2 object_update_columns
+
+If you've added fields to bugs via L</object_columns>, then this
+hook allows you to say which of those columns should be updated in the
+database when L<Bugzilla::Object/update> is called on the object.
+
+If you don't use this hook, then your custom columns won't be modified in
+the database by Bugzilla.
+
+Params:
+
+=over
+
+=item C<object>
+
+The object that is about to be updated. You should check this
+like C<< if ($object->isa('Some::Class')) >> in your code, to modify
+the "update columns" only for certain classes.
+
+=item C<columns>
+
+An arrayref. Add the string names of columns to this array to allow
+that column to be updated when C<update()> is called on the object.
+
+This arrayref does not contain the standard column names--you cannot stop
+standard columns from being updated by using this hook.
+
+=back
+
+=head2 object_validators
+
+Allows you to add new items to L<Bugzilla::Object/VALIDATORS> for
+particular classes.
+
+Params:
+
+=over
+
+=item C<class>
+
+The name of the class that C<VALIDATORS> was called on. You can check this
+like C<< if ($class->isa('Some::Class')) >> in your code, to add
+validators only for certain classes.
+
+=item C<validators>
+
+A hashref, where the keys are database columns and the values are subroutine
+references. You can add new validators or modify existing ones. If you modify
+an existing one, you should remember to call the original validator
+inside of your modified validator. (This way, several extensions can all
+modify the same validator.)
+
+=back
+
+
+=head2 page_before_template
+
+This is a simple way to add your own pages to Bugzilla. This hooks C<page.cgi>,
+which loads templates from F<template/en/default/pages>. For example,
+C<page.cgi?id=fields.html> loads F<template/en/default/pages/fields.html.tmpl>.
+
+This hook is called right before the template is loaded, so that you can
+pass your own variables to your own pages.
+
+You can also use this to implement complex custom pages, by doing your own
+output and then calling C<exit> at the end of the hook, thus preventing
+the normal C<page.cgi> behavior from occurring.
+
+Params:
+
+=over
+
+=item C<page_id>
+
+This is the name of the page being loaded, like C<fields.html>.
+
+Note that if two extensions use the same name, it is uncertain which will
+override the others, so you should be careful with how you name your pages.
+Usually extensions prefix their pages with a directory named after their
+extension, so for an extension named "Foo", page ids usually look like
+C<foo/mypage.html>.
+
+=item C<vars>
+
+This is a hashref--put variables into here if you want them passed to
+your template.
+
+=back
+
+=head2 product_confirm_delete
+
+B<DEPRECATED> - Use L</template_before_process> instead.
+
+Called before displaying the confirmation message when deleting a product.
+
+Params:
+
+=over
+
+=item C<vars> - The template vars hashref.
+
+=back
+
+=head2 product_end_of_create
+
+Called right after a new product has been created, allowing additional
+changes to be made to the new product's attributes. This occurs inside of
+a database transaction, so if the hook throws an error, all previous
+changes will be rolled back, including the creation of the new product.
+(However, note that such rollbacks should not normally be used, as
+some databases that Bugzilla supports have very bad rollback performance.
+If you want to validate input and throw errors before the Product is created,
+use L</object_end_of_create_validators> instead, or add a validator
+using L</object_validators>.)
+
+Params:
+
+=over
+
+=item C<product> - The new L<Bugzilla::Product> object that was just created.
+
+=back
+
+=head2 quicksearch_map
+
+This hook allows you to alter the Quicksearch syntax to include e.g. special
+searches for custom fields you have.
+
+Params:
+
+=over
+
+=item C<map> - a hash where the key is the name you want to use in
+Quicksearch, and the value is the name from the C<fielddefs> table that you
+want it to map to. You can modify existing mappings or add new ones.
+
+=back
+
+=head2 sanitycheck_check
+
+This hook allows for extra sanity checks to be added, for use by
+F<sanitycheck.cgi>.
+
+Params:
+
+=over
+
+=item C<status> - a CODEREF that allows status messages to be displayed
+to the user. (F<sanitycheck.cgi>'s C<Status>)
+
+=back
+
+=head2 sanitycheck_repair
+
+This hook allows for extra sanity check repairs to be made, for use by
+F<sanitycheck.cgi>.
+
+Params:
+
+=over
+
+=item C<status> - a CODEREF that allows status messages to be displayed
+to the user. (F<sanitycheck.cgi>'s C<Status>)
+
+=back
+
+=head2 template_before_create
+
+This hook allows you to modify the configuration of L<Bugzilla::Template>
+objects before they are created. For example, you could add a new
+global template variable this way.
+
+Params:
+
+=over
+
+=item C<config>
+
+A hashref--the configuration that will be passed to L<Template/new>.
+See L<http://template-toolkit.org/docs/modules/Template.html#section_CONFIGURATION_SUMMARY>
+for information on how this configuration variable is structured (or just
+look at the code for C<create> in L<Bugzilla::Template>.)
+
+=back
+
+=head2 template_before_process
+
+This hook is called any time Bugzilla processes a template file, including
+calls to C<< $template->process >>, C<PROCESS> statements in templates,
+and C<INCLUDE> statements in templates. It is not called when templates
+process a C<BLOCK>, only when they process a file.
+
+This hook allows you to define additional variables that will be available to
+the template being processed, or to modify the variables that are currently
+in the template. It works exactly as though you inserted code to modify
+template variables at the top of a template.
+
+You probably want to restrict this hook to operating only if a certain
+file is being processed (which is why you get a C<file> argument
+below). Otherwise, modifying the C<vars> argument will affect every single
+template in Bugzilla.
+
+B<Note:> This hook is not called if you are already in this hook.
+(That is, it won't call itself recursively.) This prevents infinite
+recursion in situations where this hook needs to process a template
+(such as if this hook throws an error).
+
+Params:
+
+=over
+
+=item C<vars>
+
+This is the entire set of variables that the current template can see.
+Technically, this is a L<Template::Stash> object, but you can just
+use it like a hashref if you want.
+
+=item C<file>
+
+The name of the template file being processed. This is relative to the
+main template directory for the language (i.e. for
+F<template/en/default/bug/show.html.tmpl>, this variable will contain
+C<bug/show.html.tmpl>).
+
+=item C<context>
+
+A L<Template::Context> object. Usually you will not have to use this, but
+if you need information about the template itself (other than just its
+name), you can get it from here.
+
+=back
+
+=head2 user_preferences
+
+This hook allows you to add additional panels to the User Preferences page,
+and validate data displayed and returned from these panels. It works in
+combination with the C<tabs> hook available in the
+F<template/en/default/account/prefs/prefs.html.tmpl> template. To make it
+work, you must define two templates in your extension:
+F<extensions/Foo/template/en/default/hook/account/prefs/prefs-tabs.html.tmpl>
+contains a list of additional panels to include.
+F<extensions/Foo/template/en/default/account/prefs/bar.html.tmpl> contains
+the content of the panel itself. See the C<Example> extension to see how
+things work.
+
+Params:
+
+=over
+
+=item C<current_tab>
+
+The name of the current panel being viewed by the user. You should always
+make sure that the name of the panel matches what you expect it to be.
+Else you could be interacting with the panel of another extension.
+
+=item C<save_changes>
+
+A boolean which is true when data should be validated and the DB updated
+accordingly. This means the user clicked the "Submit Changes" button.
+
+=item C<handled>
+
+This is a B<reference> to a scalar, not a scalar. (So you would set it like
+C<$$handled = 1>, not like C<$handled = 1>.) Set this to a true value to let
+Bugzilla know that the passed-in panel is valid and that you have handled it.
+(Otherwise, Bugzilla will throw an error that the panel is invalid.) Don't set
+this to true if you didn't handle the panel listed in C<current_tab>.
+
+=item C<vars>
+
+You can add as many new key/value pairs as you want to this hashref.
+It will be passed to the template.
+
+=back
+
+=head2 webservice
+
+This hook allows you to add your own modules to the WebService. (See
+L<Bugzilla::WebService>.)
+
+Params:
+
+=over
+
+=item C<dispatch>
+
+A hashref where you can specify the names of your modules and which Perl
+module handles the functions for that module. (This is actually sent to
+L<SOAP::Lite/dispatch_with>. You can see how that's used in F<xmlrpc.cgi>.)
+
+The Perl module name will most likely start with C<Bugzilla::Extension::Foo::>
+(where "Foo" is the name of your extension).
+
+Example:
+
+ $dispatch->{'Example.Blah'} = "Bugzilla::Extension::Example::Webservice::Blah";
+
+And then you'd have a module F<extensions/Example/lib/Webservice/Blah.pm>,
+and could call methods from that module like C<Example.Blah.Foo()> using
+the WebServices interface.
+
+It's recommended that all the keys you put in C<dispatch> start with the
+name of your extension, so that you don't conflict with the standard Bugzilla
+WebService functions (and so that you also don't conflict with other
+plugins).
+
+=back
+
+=head2 webservice_error_codes
+
+If your webservice extension throws custom errors, you can set numeric
+codes for those errors here.
+
+Extensions should use error codes above 10000, unless they are re-using
+an already-existing error code.
+
+Params:
+
+=over
+
+=item C<error_map>
+
+A hash that maps the names of errors (like C<invalid_param>) to numbers.
+See L<Bugzilla::WebService::Constants/WS_ERROR_CODE> for an example.
+
+=back
+
+=head1 SEE ALSO
+
+L<Bugzilla::Extension>
diff --git a/Bugzilla/Install.pm b/Bugzilla/Install.pm
new file mode 100644
index 000000000..bcc73bb28
--- /dev/null
+++ b/Bugzilla/Install.pm
@@ -0,0 +1,468 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::Install;
+
+# Functions in this this package can assume that the database
+# has been set up, params are available, localconfig is
+# available, and any module can be used.
+#
+# If you want to write an installation function that can't
+# make those assumptions, then it should go into one of the
+# packages under the Bugzilla::Install namespace.
+
+use strict;
+
+use Bugzilla::Component;
+use Bugzilla::Config qw(:admin);
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Group;
+use Bugzilla::Product;
+use Bugzilla::User;
+use Bugzilla::User::Setting;
+use Bugzilla::Util qw(get_text);
+use Bugzilla::Version;
+
+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'],
+);
+
+sub SETTINGS {
+ return {
+ # 2005-03-03 travis@sedsystems.ca -- Bug 41972
+ display_quips => { options => ["on", "off"], default => "on" },
+ # 2005-03-10 travis@sedsystems.ca -- Bug 199048
+ comment_sort_order => { options => ["oldest_to_newest", "newest_to_oldest",
+ "newest_to_oldest_desc_first"],
+ default => "oldest_to_newest" },
+ # 2005-05-12 bugzilla@glob.com.au -- Bug 63536
+ post_bug_submit_action => { options => ["next_bug", "same_bug", "nothing"],
+ default => "next_bug" },
+ # 2005-06-29 wurblzap@gmail.com -- Bug 257767
+ csv_colsepchar => { options => [',',';'], default => ',' },
+ # 2005-10-26 wurblzap@gmail.com -- Bug 291459
+ zoom_textareas => { options => ["on", "off"], default => "on" },
+ # 2005-10-21 LpSolit@gmail.com -- Bug 313020
+ per_bug_queries => { options => ['on', 'off'], default => 'off' },
+ # 2006-05-01 olav@bkor.dhs.org -- Bug 7710
+ state_addselfcc => { options => ['always', 'never', 'cc_unless_role'],
+ default => 'cc_unless_role' },
+ # 2006-08-04 wurblzap@gmail.com -- Bug 322693
+ skin => { subclass => 'Skin', default => 'Dusk' },
+ # 2006-12-10 LpSolit@gmail.com -- Bug 297186
+ lang => { subclass => 'Lang',
+ default => ${Bugzilla->languages}[0] },
+ # 2007-07-02 altlist@gmail.com -- Bug 225731
+ quote_replies => { options => ['quoted_reply', 'simple_reply', 'off'],
+ default => "quoted_reply" },
+ # 2009-02-01 mozilla@matt.mchenryfamily.org -- Bug 398473
+ comment_box_position => { options => ['before_comments', 'after_comments'],
+ default => 'before_comments' },
+ # 2008-08-27 LpSolit@gmail.com -- Bug 182238
+ timezone => { subclass => 'Timezone', default => 'local' },
+ }
+};
+
+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'],
+ },
+);
+
+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,
+};
+
+use constant DEFAULT_COMPONENT => {
+ name => 'TestComponent',
+ description => 'This is a test component in the test product database.'
+ . ' This ought to be blown away and replaced with real stuff in'
+ . ' a finished installation of Bugzilla.'
+};
+
+sub update_settings {
+ my %settings = %{SETTINGS()};
+ foreach my $setting (keys %settings) {
+ add_setting($setting,
+ $settings{$setting}->{options},
+ $settings{$setting}->{default},
+ $settings{$setting}->{subclass});
+ }
+}
+
+sub update_system_groups {
+ my $dbh = Bugzilla->dbh;
+
+ $dbh->bz_start_transaction();
+
+ # Create most of the system groups
+ foreach my $definition (SYSTEM_GROUPS) {
+ my $exists = new Bugzilla::Group({ name => $definition->{name} });
+ if (!$exists) {
+ $definition->{isbuggroup} = 0;
+ 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();
+}
+
+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);
+ }
+}
+
+# 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 });
+ }
+
+}
+
+sub init_workflow {
+ my $dbh = Bugzilla->dbh;
+ my $has_workflow = $dbh->selectrow_array('SELECT 1 FROM status_workflow');
+ return if $has_workflow;
+
+ print get_text('install_workflow_init'), "\n";
+
+ 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);
+ }
+}
+
+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) {
+ print "\n" . get_text('install_admin_setup') . "\n\n";
+ }
+
+ while (!$login) {
+ print get_text('install_admin_get_email') . ' ';
+ $login = <STDIN>;
+ chomp $login;
+ eval { Bugzilla::User->check_login_name_for_creation($login); };
+ if ($@) {
+ print $@ . "\n";
+ undef $login;
+ }
+ }
+
+ while (!defined $full_name) {
+ print get_text('install_admin_get_name') . ' ';
+ $full_name = <STDIN>;
+ chomp($full_name);
+ }
+
+ if (!$password) {
+ $password = _prompt_for_password(
+ get_text('install_admin_get_password'));
+ }
+
+ my $admin = Bugzilla::User->create({ login_name => $login,
+ realname => $full_name,
+ cryptpassword => $password });
+ make_admin($admin);
+}
+
+sub make_admin {
+ my ($user) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ $user = ref($user) ? $user
+ : new Bugzilla::User(login_to_id($user, THROW_ERROR));
+
+ my $admin_group = new Bugzilla::Group({ name => 'admin' });
+
+ # Admins get explicit membership and bless capability for the admin group
+ $dbh->selectrow_array("SELECT id FROM groups WHERE name = 'admin'");
+
+ my $group_insert = $dbh->prepare(
+ 'INSERT INTO user_group_map (user_id, group_id, isbless, grant_type)
+ VALUES (?, ?, ?, ?)');
+ # These are run in an eval so that we can ignore the error of somebody
+ # already being granted these things.
+ eval {
+ $group_insert->execute($user->id, $admin_group->id, 0, GRANT_DIRECT);
+ };
+ eval {
+ $group_insert->execute($user->id, $admin_group->id, 1, GRANT_DIRECT);
+ };
+
+ # Admins should also have editusers directly, even though they'll usually
+ # inherit it. People could have changed their inheritance structure.
+ my $editusers = new Bugzilla::Group({ name => 'editusers' });
+ eval {
+ $group_insert->execute($user->id, $editusers->id, 0, GRANT_DIRECT);
+ };
+
+ # If there is no maintainer set, make this user the maintainer.
+ if (!Bugzilla->params->{'maintainer'}) {
+ SetParam('maintainer', $user->email);
+ write_params();
+ }
+
+ if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) {
+ print "\n", get_text('install_admin_created', { user => $user }), "\n";
+ }
+}
+
+sub _prompt_for_password {
+ my $prompt = shift;
+
+ my $password;
+ while (!$password) {
+ # trap a few interrupts so we can fix the echo if we get aborted.
+ local $SIG{HUP} = \&_password_prompt_exit;
+ local $SIG{INT} = \&_password_prompt_exit;
+ local $SIG{QUIT} = \&_password_prompt_exit;
+ local $SIG{TERM} = \&_password_prompt_exit;
+
+ system("stty","-echo") unless ON_WINDOWS; # disable input echoing
+
+ print $prompt, ' ';
+ $password = <STDIN>;
+ chomp $password;
+ print "\n", get_text('install_confirm_password'), ' ';
+ my $pass2 = <STDIN>;
+ chomp $pass2;
+ eval { validate_password($password, $pass2); };
+ if ($@) {
+ print "\n$@\n";
+ undef $password;
+ }
+ system("stty","echo") unless ON_WINDOWS;
+ }
+ return $password;
+}
+
+# This is just in case we get interrupted while getting a password.
+sub _password_prompt_exit {
+ # re-enable input echoing
+ system("stty","echo") unless ON_WINDOWS;
+ exit 1;
+}
+
+sub reset_password {
+ my $login = shift;
+ my $user = Bugzilla::User->check($login);
+ my $prompt = "\n" . get_text('install_reset_password', { user => $user });
+ my $password = _prompt_for_password($prompt);
+ $user->set_password($password);
+ $user->update();
+ print "\n", get_text('install_reset_password_done'), "\n";
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Install - Functions and variables having to do with
+ installation.
+
+=head1 SYNOPSIS
+
+ use Bugzilla::Install;
+ Bugzilla::Install::update_settings();
+
+=head1 DESCRIPTION
+
+This module is used primarily by L<checksetup.pl> during installation.
+This module contains functions that deal with general installation
+issues after the database is completely set up and configured.
+
+=head1 CONSTANTS
+
+=over
+
+=item C<SETTINGS>
+
+Contains information about Settings, used by L</update_settings()>.
+
+=back
+
+=head1 SUBROUTINES
+
+=over
+
+=item C<update_settings()>
+
+Description: Adds and updates Settings for users.
+
+Params: none
+
+Returns: nothing.
+
+=item C<create_default_classification>
+
+Creates the default "Unclassified" L<Classification|Bugzilla::Classification>
+if it doesn't already exist
+
+=item C<create_default_product()>
+
+Description: Creates the default product and component if
+ they don't exist.
+
+Params: none
+
+Returns: nothing
+
+=back
diff --git a/Bugzilla/Install/CPAN.pm b/Bugzilla/Install/CPAN.pm
new file mode 100644
index 000000000..ba3ebc0fe
--- /dev/null
+++ b/Bugzilla/Install/CPAN.pm
@@ -0,0 +1,314 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by Everything Solved are Copyright (C) 2007
+# Everything Solved, Inc. All Rights Reserved.
+#
+# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::Install::CPAN;
+use strict;
+use base qw(Exporter);
+our @EXPORT = qw(
+ BZ_LIB
+
+ check_cpan_requirements
+ set_cpan_config
+ install_module
+);
+
+use Bugzilla::Constants;
+use Bugzilla::Install::Requirements qw(have_vers);
+use Bugzilla::Install::Util qw(bin_loc install_string);
+
+use CPAN;
+use Cwd qw(abs_path);
+use File::Path qw(rmtree);
+use List::Util qw(shuffle);
+
+# 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,
+ },
+);
+
+# We need the absolute path of ext_libpath, because CPAN chdirs around
+# and so we can't use a relative directory.
+#
+# We need it often enough (and at compile time, in install-module.pl) so
+# we make it a constant.
+use constant BZ_LIB => abs_path(bz_locations()->{ext_libpath});
+
+# CPAN requires nearly all of its parameters to be set, or it will start
+# asking questions to the user. We want to avoid that, so we have
+# defaults here for most of the required parameters we know about, in case
+# any of them aren't set. The rest are handled by set_cpan_defaults().
+use constant CPAN_DEFAULTS => {
+ auto_commit => 0,
+ # We always force builds, so there's no reason to cache them.
+ build_cache => 0,
+ 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 => [shuffle qw(
+ http://cpan.pair.com/
+ http://mirror.hiwaay.net/CPAN/
+ ftp://ftp.dc.aleron.net/pub/CPAN/
+ http://mirrors.kernel.org/cpan/
+ http://mirrors2.kernel.org/cpan/)],
+};
+
+sub check_cpan_requirements {
+ my ($original_dir, $original_args) = @_;
+
+ my @install;
+ foreach my $module (REQUIREMENTS) {
+ my $installed = have_vers($module, 1);
+ push(@install, $module) if !$installed;
+ }
+
+ return if !@install;
+
+ 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);
+ }
+}
+
+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;
+}
+
+sub set_cpan_config {
+ my $do_global = shift;
+ my $bzlib = BZ_LIB;
+
+ # We set defaults before we do anything, otherwise CPAN will
+ # start asking us questions as soon as we load its configuration.
+ eval { require CPAN::Config; };
+ _set_cpan_defaults();
+
+ # Calling a senseless autoload that does nothing makes us
+ # automatically load any existing configuration.
+ # We want to avoid the "invalid command" message.
+ open(my $saveout, ">&STDOUT");
+ open(STDOUT, '>/dev/null');
+ eval { CPAN->ignore_this_error_message_from_bugzilla; };
+ undef $@;
+ close(STDOUT);
+ open(STDOUT, '>&', $saveout);
+
+ my $dir = $CPAN::Config->{cpan_home};
+ if (!defined $dir || !-w $dir) {
+ # If we can't use the standard CPAN build dir, we try to make one.
+ $dir = "$ENV{HOME}/.cpan";
+ mkdir $dir;
+
+ # If we can't make one, we finally try to use the Bugzilla directory.
+ if (!-w $dir) {
+ print "WARNING: Using the Bugzilla directory as the CPAN home.\n";
+ $dir = "$bzlib/.cpan";
+ }
+ }
+ $CPAN::Config->{cpan_home} = $dir;
+ $CPAN::Config->{build_dir} = "$dir/build";
+ # We always force builds, so there's no reason to cache them.
+ $CPAN::Config->{keep_source_where} = "$dir/source";
+ # This is set both here and in defaults so that it's always true.
+ $CPAN::Config->{inhibit_startup_message} = 1;
+ # Automatically install dependencies.
+ $CPAN::Config->{prerequisites_policy} = 'follow';
+
+ # Unless specified, we install the modules into the Bugzilla directory.
+ if (!$do_global) {
+ 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} = '';
+ }
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Install::CPAN - Routines to install Perl modules from CPAN.
+
+=head1 SYNOPSIS
+
+ use Bugzilla::Install::CPAN;
+
+ set_cpan_config();
+ install_module('Module::Name');
+
+=head1 DESCRIPTION
+
+This is primarily used by L<install-module> to do the "hard work" of
+installing CPAN modules.
+
+=head1 SUBROUTINES
+
+=over
+
+=item C<set_cpan_config>
+
+Sets up the configuration of CPAN for this session. Must be called
+before L</install_module>. Takes one boolean parameter. If true,
+L</install_module> will install modules globally instead of to the
+local F<lib/> directory. On most systems, you have to be root to do that.
+
+=item C<install_module>
+
+Installs a module from CPAN. Takes two arguments:
+
+=over
+
+=item C<$name> - The name of the module, just like you'd pass to the
+C<install> command in the CPAN shell.
+
+=item C<$test> - If true, we run tests on this module before installing,
+but we still force the install if the tests fail. This is only used
+when we internally install a newer CPAN module.
+
+=back
+
+Note that calling this function prints a B<lot> of information to
+STDOUT and STDERR.
+
+=back
diff --git a/Bugzilla/Install/DB.pm b/Bugzilla/Install/DB.pm
new file mode 100644
index 000000000..d3dcc175b
--- /dev/null
+++ b/Bugzilla/Install/DB.pm
@@ -0,0 +1,3456 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+# Noel Cragg <noel@red-bean.com>
+
+package Bugzilla::Install::DB;
+
+# NOTE: This package may "use" any modules that it likes,
+# localconfig is available, and params are up to date.
+
+use strict;
+
+use Bugzilla::Constants;
+use Bugzilla::Hook;
+use Bugzilla::Install ();
+use Bugzilla::Install::Util qw(indicate_progress install_string);
+use Bugzilla::Util;
+use Bugzilla::Series;
+
+use Date::Parse;
+use Date::Format;
+use IO::File;
+
+# 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
+ 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;
+ }
+ }
+
+ $dbh->bz_add_column('fielddefs', 'visibility_field_id', {TYPE => 'INT3'});
+ $dbh->bz_add_column('fielddefs', 'visibility_value_id', {TYPE => 'INT2'});
+ $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);
+ }
+
+ #2008-08-26 elliotte_martin@yahoo.com - Bug 251556
+ $dbh->bz_add_column('fielddefs', 'reverse_desc', {TYPE => 'TINYTEXT'});
+
+ $dbh->do('UPDATE fielddefs SET buglist = 1
+ WHERE custom = 1 AND type = ' . FIELD_TYPE_MULTI_SELECT);
+
+ $dbh->bz_add_column('fielddefs', 'is_mandatory',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
+ $dbh->bz_add_index('fielddefs', 'fielddefs_is_mandatory_idx',
+ ['is_mandatory']);
+
+ # 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.
+# However, larger changes (more than three or four lines) should
+# go into their own private subroutine, and you should call that
+# subroutine from this function. That keeps this function readable.
+#
+# 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
+# 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
+# docs at:
+# http://www.ravenbrook.com/project/p4dti/tool/cgi/bugzilla-schema/
+#
+# When you add a change, you should only add a comment if you want
+# to describe why the change was made. You don't need to describe
+# 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);
+
+ _populate_milestones_table();
+
+ # 2000-03-22 Changed the default value for target_milestone to be "---"
+ # (which is still not quite correct, but much better than what it was
+ # doing), and made the size of the value field in the milestones table match
+ # the size of the target_milestone field in the bugs table.
+ $dbh->bz_alter_column('bugs', 'target_milestone',
+ {TYPE => 'varchar(20)', NOTNULL => 1, DEFAULT => "'---'"});
+ $dbh->bz_alter_column('milestones', 'value',
+ {TYPE => 'varchar(20)', NOTNULL => 1});
+
+ _add_products_defaultmilestone();
+
+ # 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)]});
+ }
+
+ _copy_from_comments_to_longdescs();
+ _populate_duplicates_table();
+
+ 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'});
+
+ $dbh->bz_add_column('attachments', 'isobsolete',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
+
+ $dbh->bz_drop_column("profiles", "emailnotification");
+ $dbh->bz_drop_column("profiles", "newemailtech");
+
+ # 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});
+
+ _update_bugs_activity_to_only_record_changes();
+
+ # bug 90933: Make disabledtext NOT NULL
+ if (!$dbh->bz_column_info('profiles', 'disabledtext')->{NOTNULL}) {
+ $dbh->bz_alter_column("profiles", "disabledtext",
+ {TYPE => 'MEDIUMTEXT', NOTNULL => 1}, '');
+ }
+
+ $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'});
+
+ $dbh->bz_add_column("bugs_activity", "attach_id", {TYPE => 'INT3'});
+
+ _delete_logincookies_cryptpassword_and_handle_invalid_cookies();
+
+ # 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");
+
+ # 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'});
+
+ _use_ip_instead_of_hostname_in_logincookies();
+
+ $dbh->bz_add_column('longdescs', 'isprivate',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
+ $dbh->bz_add_column('attachments', 'isprivate',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
+
+ $dbh->bz_add_column("bugs", "alias", {TYPE => "varchar(20)"});
+ $dbh->bz_add_index('bugs', 'bugs_alias_idx',
+ {TYPE => 'UNIQUE', FIELDS => [qw(alias)]});
+
+ _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();
+
+ # 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();
+
+ # 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_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_dependency_tree_changes_email_pref();
+ _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-09-28 bugreport@peshkin.net Bug 149504
+ $dbh->bz_add_column('attachments', 'isurl',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 0});
+
+ # 2005-10-21 LpSolit@gmail.com - Bug 313020
+ $dbh->bz_add_column('namedqueries', 'query_type',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 0});
+
+ # 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 => 'MEDIUMSERIAL', 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});
+ }
+ }
+
+ # 2006-08-04 LpSolit@gmail.com - Bug 305941
+ $dbh->bz_drop_column('profiles', 'refreshed_when');
+ $dbh->bz_drop_column('groups', 'last_changed');
+
+ # 2006-08-06 LpSolit@gmail.com - Bug 347521
+ $dbh->bz_alter_column('flagtypes', 'id',
+ {TYPE => 'SMALLSERIAL', NOTNULL => 1, PRIMARYKEY => 1});
+
+ $dbh->bz_alter_column('keyworddefs', 'id',
+ {TYPE => 'SMALLSERIAL', NOTNULL => 1, PRIMARYKEY => 1});
+
+ # 2006-08-19 LpSolit@gmail.com - Bug 87795
+ $dbh->bz_alter_column('tokens', 'userid', {TYPE => 'INT3'});
+
+ $dbh->bz_drop_index('bugs', 'bugs_short_desc_idx');
+
+ # 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 => "''"});
+
+ _update_longdescs_who_index();
+
+ $dbh->bz_add_column('setting', 'subclass', {TYPE => 'varchar(32)'});
+
+ $dbh->bz_alter_column('longdescs', 'thetext',
+ {TYPE => 'LONGTEXT', NOTNULL => 1}, '');
+
+ # 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'});
+
+ # 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)'});
+
+ $dbh->bz_add_column('versions', 'id',
+ {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1});
+ $dbh->bz_add_column('milestones', 'id',
+ {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1});
+
+ _fix_uppercase_custom_field_names();
+ _fix_uppercase_index_names();
+
+ # 2007-05-17 LpSolit@gmail.com - Bug 344965
+ _initialize_workflow_for_upgrade($old_params);
+
+ # 2007-08-08 LpSolit@gmail.com - Bug 332149
+ $dbh->bz_add_column('groups', 'icon_url', {TYPE => 'TINYTEXT'});
+
+ # 2007-08-21 wurblzap@gmail.com - Bug 365378
+ _make_lang_setting_dynamic();
+
+ # 2007-11-29 xiaoou.wu@oracle.com - Bug 153129
+ _change_text_types();
+
+ # 2007-09-09 LpSolit@gmail.com - Bug 99215
+ _fix_attachment_modification_date();
+
+ # This had the wrong definition in DB::Schema.
+ $dbh->bz_alter_column('namedqueries', 'query_type',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 0});
+
+ $dbh->bz_drop_index('longdescs', 'longdescs_thetext_idx');
+ _populate_bugs_fulltext();
+
+ # 2008-01-18 xiaoou.wu@oracle.com - Bug 414292
+ $dbh->bz_alter_column('series', 'query',
+ { TYPE => 'MEDIUMTEXT', NOTNULL => 1 });
+
+ # Add FK to multi select field tables
+ _add_foreign_keys_to_multiselects();
+
+ # 2008-07-28 tfu@redhat.com - Bug 431669
+ $dbh->bz_alter_column('group_control_map', 'product_id',
+ { TYPE => 'INT2', NOTNULL => 1 });
+
+ # 2008-09-07 LpSolit@gmail.com - Bug 452893
+ _fix_illegal_flag_modification_dates();
+
+ _add_visiblity_value_to_value_tables();
+
+ # 2009-03-02 arbingersys@gmail.com - Bug 423613
+ _add_extern_id_index();
+
+ # 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'});
+
+ # 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();
+
+ $dbh->bz_alter_column('bugs_activity', 'added',
+ { TYPE => 'varchar(255)' });
+ $dbh->bz_add_index('bugs_activity', 'bugs_activity_added_idx', ['added']);
+
+ # 2009-09-28 LpSolit@gmail.com - Bug 519032
+ $dbh->bz_drop_column('series', 'last_viewed');
+
+ # 2009-09-28 LpSolit@gmail.com - Bug 399073
+ _fix_logincookies_ipaddr();
+
+ # 2009-11-01 LpSolit@gmail.com - Bug 525025
+ _fix_invalid_custom_field_names();
+
+ _set_attachment_comment_types();
+
+ $dbh->bz_drop_column('products', 'milestoneurl');
+
+ _add_allows_unconfirmed_to_product_table();
+ _convert_flagtypes_fks_to_set_null();
+ _fix_decimal_types();
+ _fix_series_creator_fk();
+
+ # 2009-11-14 dkl@redhat.com - Bug 310450
+ $dbh->bz_add_column('bugs_activity', 'comment_id', {TYPE => 'INT3'});
+
+ # 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});
+
+ # 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 }, '');
+
+ # 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' });
+
+ # 2010-10-09 LpSolit@gmail.com - Bug 505165
+ $dbh->bz_alter_column('flags', 'setter_id', {TYPE => 'INT3', NOTNULL => 1});
+
+ ################################################################
+ # 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();
+}
+
+# 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}, '');
+}
+
+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);
+ }
+}
+
+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});
+ }
+}
+
+# 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);
+}
+
+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())
+ {
+ $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 him or something. Invent a new profile
+ # entry disabled, just to represent him.
+ $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";
+ }
+ _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
+}
+
+sub _update_bugs_activity_field_to_fieldid {
+ 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);
+
+ $dbh->bz_add_index('bugs_activity', 'bugs_activity_fieldid_idx',
+ [qw(fieldid)]);
+ print "Populating new bugs_activity.fieldid field...\n";
+
+ $dbh->bz_start_transaction();
+
+ 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();
+
+ $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
+ 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");
+ }
+ 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;
+ }
+
+ $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);
+ }
+
+ $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
+ 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}, '');
+ }
+
+ # Populate the milestone table with all existing values in the database
+ my $sth = $dbh->prepare("SELECT DISTINCT target_milestone, product
+ FROM bugs");
+ $sth->execute();
+
+ print "Populating milestones table...\n";
+
+ 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);
+ }
+ }
+ }
+}
+
+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)
+ SELECT $quoted_when, bug_id, who, comment
+ 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
+ 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}'")
+ . ")
+ 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;
+ }
+
+ 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')) {
+
+ 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
+re-crypted with the Perl function, and plaintext passwords will be
+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;
+
+ 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);
+ }
+
+ # 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
+ 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");
+ }
+}
+
+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");
+ }
+}
+
+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)'});
+ }
+}
+
+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 <<EOT;
+
+Quips are now stored in the database, rather than in an external file.
+The quips previously stored in $datadir/comments have been copied into
+the database, and that file has been renamed to $datadir/comments.bak
+You may delete the renamed file once you have confirmed that all your
+quips were moved successfully.
+
+EOT
+ $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";
+
+ $dbh->bz_add_column("components", "id",
+ {TYPE => 'SMALLSERIAL', NOTNULL => 1, PRIMARYKEY => 1});
+ $dbh->bz_add_column("bugs", "component_id",
+ {TYPE => 'INT2', 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;
+ }
+ } 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.
+#
+# _list_bits(arg) returns a list of UNKNOWN<n> if the group
+# has been deleted for all bits set in arg. When the activity
+# records are converted from groupset numbers to lists of
+# 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,
+ ($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;
+}
+
+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");
+ }
+
+ $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
+ (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
+ (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)");
+ }
+ }
+ # 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
+ 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;
+ }
+ 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;
+
+ # 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,
+ 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)");
+ }
+
+ # 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");
+
+ # 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()";
+
+
+ $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)");
+ }
+
+ # 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,
+ 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");
+ }
+
+ # Remove the attachment status field from the field definitions.
+ $dbh->do("DELETE FROM fielddefs WHERE name='attachstatusdefs.name'");
+
+ 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);
+ }
+ }
+ 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",
+ "Rename the flag by hand and run checksetup.pl again.\n";
+ die("Bad flag type name $flagname");
+ }
+ $tryflagname = $lastchanceflagname;
+ }
+ }
+ $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,
+ 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));
+ }
+ }
+ }
+ }
+}
+
+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");
+ }
+
+ $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) . "&";
+ }
+
+ # 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) {
+ # 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)]});
+ }
+}
+
+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)]});
+ }
+}
+
+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
+ 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 '% %'});
+ }
+}
+
+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)') {
+ 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";
+
+ $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');
+}
+
+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";
+ }
+}
+
+# A helper for the emailprefs subs below
+sub _clone_email_event {
+ my ($source, $target) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ $dbh->do("INSERT INTO email_setting (user_id, relationship, event)
+ SELECT user_id, relationship, $target FROM email_setting
+ 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 . ")");
+ }
+ }
+
+ 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_dependency_tree_changes_email_pref {
+ 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);
+
+ 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'});
+ }
+ }
+}
+
+# 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);
+}
+
+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)]});
+ }
+}
+
+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 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)]);
+}
+
+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");
+ }
+}
+
+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;
+
+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;
+
+WARNING - Series $broken_series_id was meant to collect non-open bug
+counts, but it has counted all bugs instead. It cannot be repaired
+automatically because no series that collected open bug counts was found.
+You'll probably want to delete or repair collected data for
+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)
+}
+
+# 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 $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;
+
+ 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,
+ groups.userregexp, user_group_map.group_id
+ FROM (profiles CROSS JOIN groups)
+ LEFT JOIN user_group_map
+ ON user_group_map.user_id = profiles.userid
+ AND user_group_map.group_id = groups.id
+ AND user_group_map.grant_type = ?
+ WHERE userregexp != '' OR user_group_map.group_id IS NOT NULL");
+
+ my $sth_add = $dbh->prepare(
+ "INSERT INTO user_group_map (user_id, group_id, isbless, grant_type)
+ VALUES (?, ?, 0, " . GRANT_REGEXP . ")");
+
+ my $sth_del = $dbh->prepare(
+ "DELETE FROM user_group_map
+ WHERE user_id = ? AND group_id = ? AND isbless = 0
+ AND grant_type = " . GRANT_REGEXP);
+
+ $sth->execute(GRANT_REGEXP);
+ while (my ($uid, $login, $gid, $rexp, $present) =
+ $sth->fetchrow_array())
+ {
+ if ($login =~ m/$rexp/i) {
+ $sth_add->execute($uid, $gid) unless $present;
+ } else {
+ $sth_del->execute($uid, $gid) if $present;
+ }
+ }
+}
+
+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;
+ }
+}
+
+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');
+ }
+
+ # 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 <<EOT;
+
+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:
+EOT
+ 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 = "The original summary for this bug"
+ . " was longer than 255 characters, and so it was truncated"
+ . " when Bugzilla was upgraded. The original summary was:"
+ . "\n\n$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
+ 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");
+ }
+}
+
+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;
+ }
+ }
+}
+
+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;
+
+ # 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 <<EOT;
+
+WARNING: The following users were listed in data/nomail, but do not
+have an account here. The unmatched entries have been moved
+to $datadir/nomail.bad:
+EOT
+ 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)]);
+ }
+}
+
+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);
+ }
+ }
+}
+
+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);
+ }
+ }
+}
+
+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);
+ }
+ }
+
+ # 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
+ 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'});
+
+ print "Setting the modification time for attachments...\n";
+ $dbh->do('UPDATE attachments SET modification_time = creation_ts');
+
+ # Now force values to be always defined.
+ $dbh->bz_alter_column('attachments', 'modification_time',
+ {TYPE => 'DATETIME', NOTNULL => 1});
+
+ # 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('quips', 'quip',
+ { 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;
+ }
+}
+
+sub _add_foreign_keys_to_multiselects {
+ my $dbh = Bugzilla->dbh;
+
+ my $names = $dbh->selectcol_arrayref(
+ 'SELECT name
+ FROM fielddefs
+ 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',});
+ }
+}
+
+# This subroutine is used in multiple places (for times when we update
+# the text of comments), so it takes an argument, $bug_ids, which causes
+# 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,
+ 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
+ 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'));
+ }
+}
+
+sub _fix_illegal_flag_modification_dates {
+ 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+$/);
+}
+
+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']);
+ }
+}
+
+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)]});
+ }
+}
+
+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');
+ }
+}
+
+sub _fix_logincookies_ipaddr {
+ 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');
+}
+
+sub _fix_invalid_custom_field_names {
+ my @fields = Bugzilla->get_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 "Removing custom field '" . $field->name . "' (illegal name)... ";
+ eval { $field->remove_from_db(); };
+ print $@ ? "failed:\n$@\n" : "succeeded\n";
+ }
+}
+
+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";
+ 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) {
+ # 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 });
+ }
+ 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);
+}
+
+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');
+ }
+ }
+}
+
+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}) {
+ # checksetup will re-create the FK with the appropriate definition
+ # at the end of its table upgrades, so we just drop it here.
+ $dbh->bz_drop_fk('flagtypes', $column);
+ }
+ }
+}
+
+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);
+}
+
+sub _fix_series_creator_fk {
+ my $dbh = Bugzilla->dbh;
+ my $fk = $dbh->bz_fk_info('series', 'creator');
+ # Change the FK from SET NULL to CASCADE. (It will be re-created
+ # automatically at the end of all DB changes.)
+ if ($fk and $fk->{DELETE} eq 'SET NULL') {
+ $dbh->bz_drop_fk('series', 'creator');
+ }
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Install::DB - Fix up the database during installation.
+
+=head1 SYNOPSIS
+
+ use Bugzilla::Install::DB qw(indicate_progress);
+ Bugzilla::Install::DB::update_table_definitions();
+
+ indicate_progress({ total => $total, current => $count, every => 10 });
+
+=head1 DESCRIPTION
+
+This module is used primarily by L<checksetup.pl> to modify the
+database during upgrades.
+
+=head1 SUBROUTINES
+
+=over
+
+=item C<update_table_definitions()>
+
+Description: This is the primary code that updates table definitions
+ during upgrades. If you modify the schema in some
+ way, you should add code to the end of this function to
+ make sure that your modifications happen over all installations.
+
+Params: none
+
+Returns: nothing
+
+=item C<update_fielddefs_definition()>
+
+Description: L<checksetup.pl> depends on the fielddefs table having
+ its schema adjusted before the rest of the tables. So
+ these schema updates happen in a separate function from
+ L</update_table_definitions()>.
+
+Params: none
+
+Returns: nothing
+
+=back
diff --git a/Bugzilla/Install/Filesystem.pm b/Bugzilla/Install/Filesystem.pm
new file mode 100644
index 000000000..3a4473ae3
--- /dev/null
+++ b/Bugzilla/Install/Filesystem.pm
@@ -0,0 +1,837 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+# Bill Barry <after.fallout@gmail.com>
+
+package Bugzilla::Install::Filesystem;
+
+# NOTE: This package may "use" any modules that it likes,
+# and localconfig is available. However, all functions in this
+# package should assume that:
+#
+# * Templates are not available.
+# * Files do not have the correct permissions.
+# * The database does not exist.
+
+use strict;
+
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Install::Localconfig;
+use Bugzilla::Install::Util qw(install_string);
+use Bugzilla::Util;
+
+use File::Find;
+use File::Path;
+use File::Basename;
+use File::Copy qw(move);
+use IO::File;
+use POSIX ();
+
+use base qw(Exporter);
+our @EXPORT = qw(
+ update_filesystem
+ create_htaccess
+ fix_all_file_permissions
+ fix_file_permissions
+);
+
+use constant HT_DEFAULT_DENY => <<EOT;
+# nothing in this directory is retrievable unless overridden by an .htaccess
+# in a subdirectory
+deny from all
+EOT
+
+###############
+# Permissions #
+###############
+
+# Used by the permissions "constants" below.
+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 };
+# A file that is read by cgi scripts, but is not ever read
+# directly by the webserver.
+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 };
+# A file that is served directly by the web server.
+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 };
+# A directory that is read by cgi scripts, but is never accessed
+# directly by the webserver
+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 };
+# 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 };
+
+# 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 };
+
+# This looks like a constant because it effectively is, but
+# it has to call other subroutines and read the current filesystem,
+# so it's defined as a sub. This is not exported, so it doesn't have
+# a perldoc. However, look at the various hashes defined inside this
+# function to understand what it returns. (There are comments throughout.)
+#
+# The rationale for the file permissions is that there is a group the
+# web server executes the scripts as, so the cgi scripts should not be writable
+# 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 $graphsdir = bz_locations()->{'graphsdir'};
+
+ # 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 => OWNER_EXECUTE },
+ 'migrate.pl' => { perms => OWNER_EXECUTE },
+ 'install-module.pl' => { perms => OWNER_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 },
+
+ 'contrib/README' => { perms => OWNER_WRITE },
+ 'contrib/*/README' => { 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" => { 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
+ "$datadir/template" => { 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 },
+
+ # 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.
+ $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.
+ $attachdir => DIR_CGI_WRITE,
+ $graphsdir => DIR_CGI_WRITE | DIR_ALSO_WS_SERVE,
+ $webdotdir => 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>
+ <meta http-equiv="Refresh" content="0; URL=index.cgi">
+</head>
+<body>
+ <h1>I think you are looking for <a href="index.cgi">index.cgi</a></h1>
+</body>
+</html>
+EOT
+ }
+ );
+
+ # Because checksetup controls the .htaccess creation separately
+ # by a localconfig variable, these go in a separate variable from
+ # %create_files.
+ #
+ # 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)\$>
+ Allow from all
+</FilesMatch>
+
+# And no directory listings, either.
+Deny from all
+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
+<FilesMatch \\.dot\$>
+ Allow from 192.20.225.0/24
+ Deny from all
+</FilesMatch>
+
+# Allow access to .png files created by a local copy of 'dot'
+<FilesMatch \\.png\$>
+ Allow from all
+</FilesMatch>
+
+# And no directory listings, either.
+Deny from all
+EOT
+ },
+ );
+
+ 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'};
+ # 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);
+ }
+
+ # 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: $!";
+ }
+ }
+
+ # 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");
+ }
+
+ _create_files(%files);
+ if ($params->{index_html}) {
+ _create_files(%{$fs->{index_html}});
+ }
+ elsif (-e 'index.html') {
+ my $templatedir = bz_locations()->{'templatedir'};
+ print <<EOT;
+
+*** It appears that you still have an old index.html hanging around.
+ Either the contents of this file should be moved into a template and
+ placed in the '$templatedir/en/custom' directory, or you should delete
+ the file.
+
+EOT
+ }
+
+ # Delete old files that no longer need to exist
+
+ # 2001-04-29 jake@bugzilla.org - Remove oldemailtech
+ # http://bugzilla.mozilla.org/show_bugs.cgi?id=71552
+ if (-d 'shadow') {
+ print "Removing shadow directory...\n";
+ rmtree("shadow");
+ }
+
+ if (-e "$datadir/versioncache") {
+ print "Removing versioncache...\n";
+ unlink "$datadir/versioncache";
+ }
+
+ 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();
+}
+
+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);
+ }
+}
+
+# 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;
+/*
+ * 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: $!";
+ }
+ };
+}
+
+# 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");
+ }
+}
+
+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>; }
+ $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 $!;
+ }
+}
+
+# A helper for the above functions.
+sub _create_files {
+ my (%files) = @_;
+
+ # It's not necessary to sort these, but it does make the
+ # output of checksetup.pl look a bit nicer.
+ foreach my $file (sort keys %files) {
+ unless (-e $file) {
+ print "Creating $file...\n";
+ my $info = $files{$file};
+ my $fh = new IO::File($file, O_WRONLY | O_CREAT, $info->{perms})
+ || die $!;
+ print $fh $info->{contents} if $info->{contents};
+ $fh->close;
+ }
+ }
+}
+
+# If you ran a REALLY old version of Bugzilla, your chart files are in the
+# wrong format. This code is a little messy, because it's very old, and
+# when moving it into this module, I couldn't test it so I left it almost
+# completely alone.
+sub _update_old_charts {
+ my ($datadir) = @_;
+ print "Updating old chart storage format...\n";
+ foreach my $in_file (glob("$datadir/mining/*")) {
+ # Don't try and upgrade image or db files!
+ next if (($in_file =~ /\.gif$/i) ||
+ ($in_file =~ /\.png$/i) ||
+ ($in_file =~ /\.db$/i) ||
+ ($in_file =~ /\.orig$/i));
+
+ rename("$in_file", "$in_file.orig") or next;
+ open(IN, "$in_file.orig") or next;
+ open(OUT, '>', $in_file) or next;
+
+ # Fields in the header
+ my @declared_fields;
+
+ # Fields we changed to half way through by mistake
+ # This list comes from an old version of collectstats.pl
+ # This part is only for people who ran later versions of 2.11 (devel)
+ my @intermediate_fields = qw(DATE UNCONFIRMED NEW ASSIGNED REOPENED
+ RESOLVED VERIFIED CLOSED);
+
+ # Fields we actually want (matches the current collectstats.pl)
+ my @out_fields = qw(DATE NEW ASSIGNED REOPENED UNCONFIRMED RESOLVED
+ VERIFIED CLOSED FIXED INVALID WONTFIX LATER REMIND
+ DUPLICATE WORKSFORME MOVED);
+
+ while (<IN>) {
+ if (/^# fields?: (.*)\s$/) {
+ @declared_fields = map uc, (split /\||\r/, $1);
+ print OUT "# fields: ", join('|', @out_fields), "\n";
+ }
+ elsif (/^(\d+\|.*)/) {
+ my @data = split(/\||\r/, $1);
+ my %data;
+ if (@data == @declared_fields) {
+ # old format
+ for my $i (0 .. $#declared_fields) {
+ $data{$declared_fields[$i]} = $data[$i];
+ }
+ }
+ elsif (@data == @intermediate_fields) {
+ # Must have changed over at this point
+ for my $i (0 .. $#intermediate_fields) {
+ $data{$intermediate_fields[$i]} = $data[$i];
+ }
+ }
+ elsif (@data == @out_fields) {
+ # This line's fine - it has the right number of entries
+ for my $i (0 .. $#out_fields) {
+ $data{$out_fields[$i]} = $data[$i];
+ }
+ }
+ else {
+ print "Oh dear, input line $. of $in_file had " .
+ scalar(@data) . " fields\nThis was unexpected.",
+ " You may want to check your data files.\n";
+ }
+
+ print OUT join('|',
+ map { defined ($data{$_}) ? ($data{$_}) : "" } @out_fields),
+ "\n";
+ }
+ else {
+ print OUT;
+ }
+ }
+
+ close(IN);
+ close(OUT);
+ }
+}
+
+sub fix_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);
+}
+
+sub fix_all_file_permissions {
+ my ($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;
+
+ 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;
+
+ 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 $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, '.');
+}
+
+sub _get_owner_and_group {
+ 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);
+}
+
+# A helper for fix_all_file_permissions
+sub _fix_cvs_dirs {
+ my ($owner_id, $dir) = @_;
+ my $owner_gid = POSIX::getgid();
+ find({ no_chdir => 1, wanted => sub {
+ my $name = $File::Find::name;
+ if ($File::Find::dir =~ /\/CVS/ || $_ eq '.cvsignore'
+ || (-d $name && $_ =~ /CVS$/))
+ {
+ my $perms = 0600;
+ if (-d $name) {
+ $perms = 0700;
+ }
+ _fix_perms($name, $owner_id, $owner_gid, $perms);
+ }
+ }}, $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";
+}
+
+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 $name = $File::Find::name;
+ if (-d $name) {
+ _fix_perms($name, $owner_id, $group_id, $perms->{dirs});
+ }
+ else {
+ _fix_perms($name, $owner_id, $group_id, $perms->{files});
+ }
+ }}, $dir);
+}
+
+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";
+ }
+ }
+
+ return $group_id;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Install::Filesystem - Fix up the filesystem during
+ installation.
+
+=head1 DESCRIPTION
+
+This module is used primarily by L<checksetup.pl> to modify the
+filesystem during installation, including creating the data/ directory.
+
+=head1 SUBROUTINES
+
+=over
+
+=item C<update_filesystem({ index_html => 0 })>
+
+Description: Creates all the directories and files that Bugzilla
+ needs to function but doesn't ship with. Also does
+ any updates to these files as necessary during an
+ upgrade.
+
+Params: C<index_html> - Whether or not we should create
+ the F<index.html> file.
+
+Returns: nothing
+
+=item C<create_htaccess()>
+
+Description: Creates all of the .htaccess files for Apache,
+ in the various Bugzilla directories. Also updates
+ the .htaccess files if they need updating.
+
+Params: none
+
+Returns: nothing
+
+=item C<fix_all_file_permissions($output)>
+
+Description: Sets all the file permissions on all of Bugzilla's files
+ to what they should be. Note that permissions are different
+ depending on whether or not C<$webservergroup> is set
+ in F<localconfig>.
+
+Params: C<$output> - C<true> if you want this function to print
+ out information about what it's doing.
+
+Returns: nothing
+
+=item C<fix_file_permissions>
+
+Given the name of a file, its permissions will be fixed according to
+how they are supposed to be set in Bugzilla's current configuration.
+If it fails to set the permissions, a warning will be printed to STDERR.
+
+=back
diff --git a/Bugzilla/Install/Localconfig.pm b/Bugzilla/Install/Localconfig.pm
new file mode 100644
index 000000000..e15e23507
--- /dev/null
+++ b/Bugzilla/Install/Localconfig.pm
@@ -0,0 +1,515 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Initial Developer of the Original Code is Everything Solved.
+# Portions created by Everything Solved are Copyright (C) 2006
+# Everything Solved. All Rights Reserved.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::Install::Localconfig;
+
+# NOTE: This package may "use" any modules that it likes. However,
+# all functions in this package should assume that:
+#
+# * The data/ directory does not exist.
+# * Templates are not available.
+# * Files do not have the correct permissions
+# * The database is not up to date
+
+use strict;
+
+use Bugzilla::Constants;
+use Bugzilla::Install::Util qw(bin_loc);
+use Bugzilla::Util qw(generate_random_password);
+
+use Data::Dumper;
+use File::Basename qw(dirname);
+use IO::File;
+use Safe;
+
+use base qw(Exporter);
+
+our @EXPORT_OK = qw(
+ read_localconfig
+ update_localconfig
+);
+
+use constant LOCALCONFIG_VARS => (
+ {
+ name => 'create_htaccess',
+ default => 1,
+ desc => <<EOT
+# If you are using Apache as your web server, Bugzilla can create .htaccess
+# files for you that will instruct Apache not to serve files that shouldn't
+# be accessed from the web browser (like your local configuration data and non-cgi
+# executable files). For this to work, the directory your Bugzilla
+# installation is in must be within the jurisdiction of a <Directory> block
+# in the httpd.conf file that has 'AllowOverride Limit' in it. If it has
+# 'AllowOverride All' or other options with Limit, that's fine.
+# (Older Apache installations may use an access.conf file to store these
+# <Directory> blocks.)
+# If this is set to 1, Bugzilla will create these files if they don't exist.
+# If this is set to 0, Bugzilla will not create these files.
+EOT
+ },
+ {
+ name => 'webservergroup',
+ default => ON_WINDOWS ? '' : 'apache',
+ desc => q{# Usually, this is the group your web server runs as.
+# If you have a Windows box, ignore this setting.
+# If you have use_suexec switched on below, this is the group Apache switches
+# to in order to run Bugzilla scripts.
+# If you do not have access to the group your scripts will run under,
+# set this to "". If you do set this to "", then your Bugzilla installation
+# will be _VERY_ insecure, because some files will be world readable/writable,
+# and so anyone who can get local access to your machine can do whatever they
+# want. You should only have this set to "" if this is a testing installation
+# and you cannot set this up any other way. YOU HAVE BEEN WARNED!
+# If you set this to anything other than "", you will need to run checksetup.pl
+# as} . ROOT_USER . qq{, or as a user who is a member of the specified group.\n}
+ },
+ {
+ name => 'use_suexec',
+ default => 0,
+ desc => <<EOT
+# Set this if Bugzilla runs in an Apache SuexecUserGroup environment.
+# (If your web server runs control panel software (cPanel, Plesk or similar),
+# or if your Bugzilla is to run in a shared hosting environment, then you are
+# almost certainly in an Apache SuexecUserGroup environment.)
+# If you have a Windows box, ignore this setting.
+# If set to 0, Bugzilla will set file permissions as tightly as possible.
+# If set to 1, Bugzilla will set file permissions so that it may work in an
+# SuexecUserGroup environment. The difference is that static files (CSS,
+# JavaScript and so on) will receive world read permissions.
+EOT
+ },
+ {
+ name => 'db_driver',
+ default => 'mysql',
+ desc => <<EOT
+# What SQL database to use. Default is mysql. List of supported databases
+# can be obtained by listing Bugzilla/DB directory - every module corresponds
+# to one supported database and the name corresponds to a driver name.
+EOT
+ },
+ {
+ name => 'db_host',
+ default => 'localhost',
+ desc =>
+ "# The DNS name of the host that the database server runs on.\n"
+ },
+ {
+ name => 'db_name',
+ default => 'bugs',
+ desc => "# The name of the database\n"
+ },
+ {
+ name => 'db_user',
+ default => 'bugs',
+ desc => "# Who we connect to the database as.\n"
+ },
+ {
+ name => 'db_pass',
+ default => '',
+ desc => <<EOT
+# Enter your database password here. It's normally advisable to specify
+# a password for your bugzilla database user.
+# If you use apostrophe (') or a backslash (\\) in your password, you'll
+# need to escape it by preceding it with a '\\' character. (\\') or (\\)
+# (Far simpler just not to use those characters.)
+EOT
+ },
+ {
+ name => 'db_port',
+ default => 0,
+ desc => <<EOT
+# Sometimes the database server is running on a non-standard port. If that's
+# the case for your database server, set this to the port number that your
+# database server is running on. Setting this to 0 means "use the default
+# port for my database server."
+EOT
+ },
+ {
+ name => 'db_sock',
+ default => '',
+ desc => <<EOT
+# MySQL Only: Enter a path to the unix socket for MySQL. If this is
+# blank, then MySQL's compiled-in default will be used. You probably
+# want that.
+EOT
+ },
+ {
+ name => 'db_check',
+ default => 1,
+ desc => <<EOT
+# Should checksetup.pl try to verify that your database setup is correct?
+# (with some combinations of database servers/Perl modules/moonphase this
+# doesn't work)
+EOT
+ },
+ {
+ name => 'index_html',
+ default => 0,
+ desc => <<EOT
+# With the introduction of a configurable index page using the
+# template toolkit, Bugzilla's main index page is now index.cgi.
+# Most web servers will allow you to use index.cgi as a directory
+# index, and many come preconfigured that way, but if yours doesn't
+# then you'll need an index.html file that provides redirection
+# to index.cgi. Setting \$index_html to 1 below will allow
+# checksetup.pl to create one for you if it doesn't exist.
+# NOTE: checksetup.pl will not replace an existing file, so if you
+# wish to have checksetup.pl create one for you, you must
+# make sure that index.html doesn't already exist
+EOT
+ },
+ {
+ name => 'cvsbin',
+ default => \&_get_default_cvsbin,
+ desc => <<EOT
+# For some optional functions of Bugzilla (such as the pretty-print patch
+# viewer), we need the cvs binary to access files and revisions.
+# Because it's possible that this program is not in your path, you can specify
+# its location here. Please specify the full path to the executable.
+EOT
+ },
+ {
+ name => 'interdiffbin',
+ default => \&_get_default_interdiffbin,
+ desc => <<EOT
+# For some optional functions of Bugzilla (such as the pretty-print patch
+# viewer), we need the interdiff binary to make diffs between two patches.
+# Because it's possible that this program is not in your path, you can specify
+# its location here. Please specify the full path to the executable.
+EOT
+ },
+ {
+ name => 'diffpath',
+ default => \&_get_default_diffpath,
+ desc => <<EOT
+# The interdiff feature needs diff, so we have to have that path.
+# Please specify the directory name only; do not use trailing slash.
+EOT
+ },
+ {
+ 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) },
+ desc => <<EOT
+# This secret key is used by your installation for the creation and
+# validation of encrypted tokens to prevent unsolicited changes,
+# such as bug changes. A random string is generated by default.
+# It's very important that this key is kept secret. It also must be
+# very long.
+EOT
+ },
+);
+
+sub read_localconfig {
+ my ($include_deprecated) = @_;
+ my $filename = bz_locations()->{'localconfig'};
+
+ my %localconfig;
+ if (-e $filename) {
+ my $s = new Safe;
+ # Some people like to store their database password in another file.
+ $s->permit('dofile');
+
+ $s->rdo($filename);
+ if ($@ || $!) {
+ my $err_msg = $@ ? $@ : $!;
+ die <<EOT;
+An error has occurred while reading your 'localconfig' file. The text of
+the error message is:
+
+$err_msg
+
+Please fix the error in your 'localconfig' file. Alternately, rename your
+'localconfig' file, rerun checksetup.pl, and re-enter your answers.
+
+ \$ mv -f localconfig localconfig.old
+ \$ ./checksetup.pl
+EOT
+ }
+
+ my @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;
+}
+
+
+#
+# This is quite tricky. But fun!
+#
+# First we read the file 'localconfig'. Then we check if the variables we
+# need are defined. If not, we will append the new settings to
+# localconfig, instruct the user to check them, and stop.
+#
+# Why do it this way?
+#
+# Assume we will enhance Bugzilla and eventually more local configuration
+# stuff arises on the horizon.
+#
+# But the file 'localconfig' is not in the Bugzilla CVS or tarfile. You
+# know, we never want to overwrite your own version of 'localconfig', so
+# we can't put it into the CVS/tarfile, can we?
+#
+# Now, when we need a new variable, we simply add the necessary stuff to
+# LOCALCONFIG_VARS. When the user gets the new version of Bugzilla from CVS and
+# runs checksetup, it finds out "Oh, there is something new". Then it adds
+# some default value to the user's local setup and informs the user to
+# check that to see if it is what the user wants.
+#
+# Cute, ey?
+#
+sub update_localconfig {
+ my ($params) = @_;
+
+ my $output = $params->{output} || 0;
+ my $answer = Bugzilla->installation_answers;
+ my $localconfig = read_localconfig('include deprecated');
+
+ my @new_vars;
+ foreach my $var (LOCALCONFIG_VARS) {
+ my $name = $var->{name};
+ 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) {
+ push(@new_vars, $name);
+ $var->{default} = &{$var->{default}} if ref($var->{default}) eq 'CODE';
+ if (exists $answer->{$name}) {
+ $localconfig->{$name} = $answer->{$name};
+ }
+ else {
+ $localconfig->{$name} = $var->{default};
+ }
+ }
+ }
+
+ if (!$localconfig->{'interdiffbin'} && $output) {
+ print <<EOT
+
+OPTIONAL NOTE: If you want to be able to use the 'difference between two
+patches' feature of Bugzilla (which requires the PatchReader Perl module
+as well), you should install patchutils from:
+
+ http://cyberelk.net/tim/patchutils/
+
+EOT
+ }
+
+ my @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, ">>$filename_old") || 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 <<EOT
+
+The following variables are no longer used in $filename, and
+have been moved to $filename_old: $oldstuff
+
+EOT
+ }
+
+ # Re-write localconfig
+ open(my $fh, ">$filename") || die "$filename: $!";
+ foreach my $var (LOCALCONFIG_VARS) {
+ print $fh "\n", $var->{desc},
+ Data::Dumper->Dump([$localconfig->{$var->{name}}],
+ ["*$var->{name}"]);
+ }
+
+ if (@new_vars) {
+ my $newstuff = join(', ', @new_vars);
+ print <<EOT;
+
+This version of Bugzilla contains some variables that you may want to
+change and adapt to your local settings. Please edit the file
+$filename and rerun checksetup.pl.
+
+The following variables are new to $filename since you last ran
+checksetup.pl: $newstuff
+
+EOT
+ exit;
+ }
+
+ # Reset the cache for Bugzilla->localconfig so that it will be re-read
+ delete Bugzilla->request_cache->{localconfig};
+
+ return { old_vars => \@old_vars, new_vars => \@new_vars };
+}
+
+sub _get_default_cvsbin { return bin_loc('cvs') }
+sub _get_default_interdiffbin { return bin_loc('interdiff') }
+sub _get_default_diffpath {
+ my $diff_bin = bin_loc('diff');
+ return dirname($diff_bin);
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Install::Localconfig - Functions and variables dealing
+ with the manipulation and creation of the F<localconfig> file.
+
+=head1 SYNOPSIS
+
+ use Bugzilla::Install::Requirements qw(update_localconfig);
+ update_localconfig({ output => 1 });
+
+=head1 DESCRIPTION
+
+This module is used primarily by L<checksetup.pl> to create and
+modify the localconfig file. Most scripts should use L<Bugzilla/localconfig>
+to access localconfig variables.
+
+=head1 CONSTANTS
+
+=over
+
+=item C<LOCALCONFIG_VARS>
+
+An array of hashrefs. These hashrefs contain three keys:
+
+ name - The name of the variable.
+ default - The default value for the variable. Should always be
+ something that can fit in a scalar.
+ desc - Additional text to put in localconfig before the variable
+ definition. Must end in a newline. Each line should start
+ with "#" unless you have some REALLY good reason not
+ to do that.
+
+=item C<OLD_LOCALCONFIG_VARS>
+
+An array of names of variables. If C<update_localconfig> finds these
+variables defined in localconfig, it will print out a warning.
+
+=back
+
+=head1 SUBROUTINES
+
+=over
+
+=item C<read_localconfig>
+
+=over
+
+=item B<Description>
+
+Reads the localconfig file and returns all valid values in a hashref.
+
+=item B<Params>
+
+=over
+
+=item C<$include_deprecated>
+
+C<true> if you want the returned hashref to include *any* variable
+currently defined in localconfig, even if it doesn't exist in
+C<LOCALCONFIG_VARS>. Generally this is is only for use
+by L</update_localconfig>.
+
+=back
+
+=item B<Returns>
+
+A hashref of the localconfig variables. If an array is defined in
+localconfig, it will be an arrayref in the returned hash. If a
+hash is defined, it will be a hashref in the returned hash.
+Only includes variables specified in C<LOCALCONFIG_VARS>, unless
+C<$include_deprecated> is true.
+
+=back
+
+
+=item C<update_localconfig>
+
+Description: Adds any new variables to localconfig that aren't
+ currently defined there. Also optionally prints out
+ a message about vars that *should* be there and aren't.
+ Exits the program if it adds any new vars.
+
+Params: C<$output> - C<true> if the function should display informational
+ output and warnings. It will always display errors or
+ any message which would cause program execution to halt.
+
+Returns: A hashref, with C<old_vals> being an array of names of variables
+ that were removed, and C<new_vals> being an array of names
+ of variables that were added to localconfig.
+
+=back
diff --git a/Bugzilla/Install/Requirements.pm b/Bugzilla/Install/Requirements.pm
new file mode 100644
index 000000000..64a56410a
--- /dev/null
+++ b/Bugzilla/Install/Requirements.pm
@@ -0,0 +1,900 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+# Marc Schumann <wurblzap@gmail.com>
+
+package Bugzilla::Install::Requirements;
+
+# NOTE: This package MUST NOT "use" any Bugzilla modules other than
+# Bugzilla::Constants, anywhere. We may "use" standard perl modules.
+#
+# Subroutines may "require" and "import" from modules, but they
+# MUST NOT "use."
+
+use strict;
+
+use Bugzilla::Constants;
+use Bugzilla::Install::Util qw(vers_cmp install_string bin_loc
+ extension_requirement_packages);
+use List::Util qw(max);
+use Safe;
+use Term::ANSIColor;
+
+use base qw(Exporter);
+our @EXPORT = qw(
+ 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
+# by checksetup.pl.
+use constant TABLE_WIDTH => 71;
+
+# Optional Apache modules that have no Perl component to them.
+# If these are installed, Bugzilla has additional functionality.
+#
+# 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',
+};
+
+# These are all of the binaries that we could possibly use that can
+# give us info about which Apache modules are installed.
+# If we can't use "apachectl", the "httpd" binary itself takes the same
+# parameters. Note that on Debian and Gentoo, there is an "apache2ctl",
+# but it takes different parameters on each of those two distros, so we
+# don't use apache2ctl.
+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/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.
+
+# "package" is the perl package we're checking for. "module" is the name
+# of the actual module we load with "require" to see if the package is
+# installed or not. "version" is the version we need, or 0 if we'll accept
+# any version.
+#
+# "blacklist" is an arrayref of regular expressions that describe versions that
+# are 'blacklisted'--that is, even if the version is high enough, Bugzilla
+# will refuse to say that it's OK to run with that version.
+sub REQUIRED_MODULES {
+ my $perl_ver = sprintf('%vd', $^V);
+ my @modules = (
+ {
+ package => 'CGI.pm',
+ module => 'CGI',
+ # 3.51 fixes a security problem that affects Bugzilla.
+ # (bug 591165)
+ version => '3.51',
+ },
+ {
+ package => 'Digest-SHA',
+ module => 'Digest::SHA',
+ version => 0
+ },
+ {
+ package => 'TimeDate',
+ module => 'Date::Format',
+ version => '2.21'
+ },
+ # 0.28 fixed some important bugs in DateTime.
+ {
+ package => 'DateTime',
+ module => 'DateTime',
+ version => '0.28'
+ },
+ # 0.79 is required to work on Windows Vista and Windows Server 2008.
+ # As correctly detecting the flavor of Windows is not easy,
+ # we require this version for all Windows installations.
+ # 0.71 fixes a major bug affecting all platforms.
+ {
+ package => 'DateTime-TimeZone',
+ module => 'DateTime::TimeZone',
+ version => ON_WINDOWS ? '0.79' : '0.71'
+ },
+ {
+ package => 'DBI',
+ module => 'DBI',
+ version => (vers_cmp($perl_ver, '5.13.3') > -1) ? '1.614' : '1.41'
+ },
+ # 2.22 fixes various problems related to UTF8 strings in hash keys,
+ # as well as line endings on Windows.
+ {
+ package => 'Template-Toolkit',
+ module => 'Template',
+ version => '2.22'
+ },
+ {
+ package => 'Email-Send',
+ module => 'Email::Send',
+ version => ON_WINDOWS ? '2.16' : '2.00',
+ blacklist => ['^2\.196$']
+ },
+ {
+ 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',
+ version => 0
+ },
+ {
+ package => 'List-MoreUtils',
+ module => 'List::MoreUtils',
+ version => 0.22,
+ },
+ );
+
+ my $extra_modules = _get_extension_requirements('REQUIRED_MODULES');
+ push(@modules, @$extra_modules);
+ return \@modules;
+};
+
+sub OPTIONAL_MODULES {
+ my $perl_ver = sprintf('%vd', $^V);
+ my @modules = (
+ {
+ package => 'GD',
+ module => 'GD',
+ version => '1.20',
+ feature => [qw(graphical_reports new_charts old_charts)],
+ },
+ {
+ package => 'Chart',
+ module => 'Chart::Lines',
+ # Versions below 2.1 cannot be detected accurately.
+ version => '2.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 => 'GDTextUtil',
+ module => 'GD::Text',
+ version => 0,
+ feature => ['graphical_reports'],
+ },
+ {
+ package => 'GDGraph',
+ module => 'GD::Graph',
+ version => 0,
+ feature => ['graphical_reports'],
+ },
+ {
+ package => 'XML-Twig',
+ module => 'XML::Twig',
+ version => 0,
+ feature => ['moving', 'updates'],
+ },
+ {
+ 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 => 'PatchReader',
+ module => 'PatchReader',
+ version => '0.9.4',
+ feature => ['patch_viewer'],
+ },
+ {
+ package => 'perl-ldap',
+ module => 'Net::LDAP',
+ version => 0,
+ feature => ['auth_ldap'],
+ },
+ {
+ package => 'Authen-SASL',
+ module => 'Authen::SASL',
+ version => 0,
+ feature => ['smtp_auth'],
+ },
+ {
+ package => 'RadiusPerl',
+ module => 'Authen::Radius',
+ version => 0,
+ feature => ['auth_radius'],
+ },
+ {
+ package => 'SOAP-Lite',
+ module => 'SOAP::Lite',
+ # Fixes various bugs, including 542931 and 552353 + stops
+ # throwing warnings with Perl 5.12.
+ version => '0.712',
+ feature => ['xmlrpc'],
+ },
+ {
+ package => 'JSON-RPC',
+ module => 'JSON::RPC',
+ version => 0,
+ feature => ['jsonrpc'],
+ },
+ {
+ package => 'JSON-XS',
+ module => 'JSON::XS',
+ # 2.0 is the first version that will work with JSON::RPC.
+ version => '2.0',
+ feature => ['jsonrpc_faster'],
+ },
+ {
+ package => 'Test-Taint',
+ module => 'Test::Taint',
+ version => 0,
+ feature => ['jsonrpc', 'xmlrpc'],
+ },
+ {
+ # We need the 'utf8_mode' method of HTML::Parser, for HTML::Scrubber.
+ package => 'HTML-Parser',
+ module => 'HTML::Parser',
+ version => (vers_cmp($perl_ver, '5.13.3') > -1) ? '3.67' : '3.40',
+ feature => ['html_desc'],
+ },
+ {
+ package => 'HTML-Scrubber',
+ module => 'HTML::Scrubber',
+ version => 0,
+ feature => ['html_desc'],
+ },
+
+ # Inbound Email
+ {
+ package => 'Email-MIME-Attachment-Stripper',
+ module => 'Email::MIME::Attachment::Stripper',
+ version => 0,
+ feature => ['inbound_email'],
+ },
+ {
+ package => 'Email-Reply',
+ module => 'Email::Reply',
+ version => 0,
+ feature => ['inbound_email'],
+ },
+
+ # Mail Queueing
+ {
+ package => 'TheSchwartz',
+ module => 'TheSchwartz',
+ 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 => 'Apache-SizeLimit',
+ module => 'Apache2::SizeLimit',
+ # 0.93 fixes problems on Linux and Windows, and changes the
+ # syntax used by SizeLimit.
+ version => '0.93',
+ feature => ['mod_perl'],
+ },
+ {
+ package => 'Math-Random-Secure',
+ module => 'Math::Random::Secure',
+ # This is the first version that installs properly on Windows.
+ version => '0.05',
+ feature => ['rand_security'],
+ },
+ );
+
+ if (ON_WINDOWS) {
+ # SizeLimit needs Win32::API to work on Windows.
+ push(@modules, {
+ package => 'Win32-API',
+ module => 'Win32::API',
+ version => 0,
+ feature => ['mod_perl'],
+ });
+ }
+
+ 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'],
+ moving => ['importxml.pl'],
+ auth_ldap => ['Bugzilla/Auth/Verify/LDAP.pm'],
+ auth_radius => ['Bugzilla/Auth/Verify/RADIUS.pm'],
+ 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'],
+);
+
+# 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;
+};
+
+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 @missing;
+ foreach my $module (@$modules) {
+ unless (have_vers($module, $output)) {
+ push(@missing, $module);
+ }
+ }
+
+ 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 (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;
+}
+
+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;
+}
+
+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;
+
+ # We only print the PPM repository note if we have to.
+ if ($need_module_instructions and ON_ACTIVESTATE) {
+ my $perl_ver = sprintf('%vd', $^V);
+
+ # URL when running Perl 5.8.x.
+ my $url_to_theory58S = 'http://theoryx5.uwinnipeg.ca/ppms';
+ # Packages for Perl 5.10 are not compatible with Perl 5.8.
+ if (vers_cmp($perl_ver, '5.10') > -1) {
+ $url_to_theory58S = 'http://cpan.uwinnipeg.ca/PPMPackages/10xx/';
+ }
+ print colored(
+ install_string('ppm_repo_add',
+ { theory_url => $url_to_theory58S }),
+ COLOR_ERROR);
+
+ # ActivePerls older than revision 819 require an additional command.
+ if (ON_ACTIVESTATE < 819) {
+ print install_string('ppm_repo_up');
+ }
+ }
+
+ 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);
+}
+
+sub check_graphviz {
+ my ($output) = @_;
+
+ return 1 if (Bugzilla->params->{'webdotbase'} =~ /^https?:/);
+
+ my $return;
+ $return = 1 if -x Bugzilla->params->{'webdotbase'};
+
+ if ($output) {
+ _checking_for({ package => 'GraphViz', ok => $return });
+ }
+
+ if (!$return) {
+ print "not a valid executable: " . Bugzilla->params->{'webdotbase'}
+ . "\n";
+ }
+
+ my $webdotdir = bz_locations()->{'webdotdir'};
+ # Check .htaccess allows access to generated images
+ if (-e "$webdotdir/.htaccess") {
+ my $htaccess = new IO::File("$webdotdir/.htaccess", 'r')
+ || die "$webdotdir/.htaccess: " . $!;
+ if (!grep(/png/, $htaccess->getlines)) {
+ print "Dependency graph images are not accessible.\n";
+ print "delete $webdotdir/.htaccess and re-run checksetup.pl to fix.\n";
+ }
+ $htaccess->close;
+ }
+
+ return $return;
+}
+
+# This was originally clipped from the libnet Makefile.PL, adapted here to
+# use the below vers_cmp routine for accurate version checking.
+sub have_vers {
+ my ($params, $output) = @_;
+ my $module = $params->{module};
+ my $package = $params->{package};
+ if (!$package) {
+ $package = $module;
+ $package =~ s/::/-/g;
+ }
+ my $wanted = $params->{version};
+
+ eval "require $module;";
+ # 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.
+ my $vnum = $module->VERSION || -1;
+
+ # CGI's versioning scheme went 2.75, 2.751, 2.752, 2.753, 2.76
+ # That breaks the standard version tests, so we need to manually correct
+ # the version
+ if ($module eq 'CGI' && $vnum =~ /(2\.7\d)(\d+)/) {
+ $vnum = $1 . "." . $2;
+ }
+ # CPAN did a similar thing, where it has versions like 1.9304.
+ if ($module eq 'CPAN' and $vnum =~ /^(\d\.\d{2})\d{2}$/) {
+ $vnum = $1;
+ }
+
+ my $vok = (vers_cmp($vnum,$wanted) > -1);
+ 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.
+ if ($found and $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;
+ }
+ 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 $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;
+}
+
+# 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;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Install::Requirements - Functions and variables dealing
+ with Bugzilla's perl-module requirements.
+
+=head1 DESCRIPTION
+
+This module is used primarily by C<checksetup.pl> to determine whether
+or not all of Bugzilla's prerequisites are installed. (That is, all the
+perl modules it requires.)
+
+=head1 CONSTANTS
+
+=over
+
+=item C<REQUIRED_MODULES>
+
+An arrayref of hashrefs that describes the perl modules required by
+Bugzilla. The hashes have three keys:
+
+=over
+
+=item C<package> - The name of the Perl package that you'd find on
+CPAN for this requirement.
+
+=item C<module> - The name of a module that can be passed to the
+C<install> command in C<CPAN.pm> to install this module.
+
+=item C<version> - The version of this module that we require, or C<0>
+if any version is acceptable.
+
+=back
+
+=item C<OPTIONAL_MODULES>
+
+An arrayref of hashrefs that describes the perl modules that add
+additional features to Bugzilla if installed. Its hashes have all
+the fields of L</REQUIRED_MODULES>, plus a C<feature> item--an arrayref
+of strings that describe what features require this module.
+
+=item C<FEATURE_FILES>
+
+A hashref that describes what files should only be compiled if a certain
+feature is enabled. The feature is the key, and the values are arrayrefs
+of file names (which are passed to C<glob>, so shell patterns work).
+
+=back
+
+
+=head1 SUBROUTINES
+
+=over 4
+
+=item C<check_requirements>
+
+=over
+
+=item B<Description>
+
+This checks what optional or required perl modules are installed, like
+C<checksetup.pl> does.
+
+=item B<Params>
+
+=over
+
+=item C<$output> - C<true> if you want the function to print out information
+about what it's doing, and the versions of everything installed.
+
+=back
+
+=item B<Returns>
+
+A hashref containing these values:
+
+=over
+
+=item C<pass> - Whether or not we have all the mandatory requirements.
+
+=item C<missing> - An arrayref containing any required modules that
+are not installed or that are not up-to-date. Each item in the array is
+a hashref in the format of items from L</REQUIRED_MODULES>.
+
+=item C<optional> - The same as C<missing>, but for optional modules.
+
+=item C<apache> - The name of each optional Apache module that is missing.
+
+=item C<have_one_dbd> - True if at least one C<DBD::> module is installed.
+
+=item C<any_missing> - True if there are any missing Perl modules, even
+optional modules.
+
+=back
+
+=back
+
+=item C<check_graphviz($output)>
+
+Description: Checks if the graphviz binary specified in the
+ C<webdotbase> parameter is a valid binary, or a valid URL.
+
+Params: C<$output> - C<$true> if you want the function to
+ print out information about what it's doing.
+
+Returns: C<1> if the check was successful, C<0> otherwise.
+
+=item C<have_vers($module, $output)>
+
+ Description: Tells you whether or not you have the appropriate
+ version of the module requested. It also prints
+ out a message to the user explaining the check
+ and the result.
+
+ Params: C<$module> - A hashref, in the format of an item from
+ L</REQUIRED_MODULES>.
+ C<$output> - Set to true if you want this function to
+ print information to STDOUT about what it's
+ doing.
+
+ Returns: C<1> if you have the module installed and you have the
+ appropriate version. C<0> otherwise.
+
+=item C<install_command($module)>
+
+ Description: Prints out the appropriate command to install the
+ module specified, depending on whether you're
+ on Windows or Linux.
+
+ Params: C<$module> - A hashref, in the format of an item from
+ L</REQUIRED_MODULES>.
+
+ Returns: nothing
+
+=item C<map_files_to_features>
+
+Returns a hashref where file names are the keys and the value is the feature
+that must be enabled in order to compile that file.
+
+=back
diff --git a/Bugzilla/Install/Util.pm b/Bugzilla/Install/Util.pm
new file mode 100644
index 000000000..2c651988b
--- /dev/null
+++ b/Bugzilla/Install/Util.pm
@@ -0,0 +1,886 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved.
+# Portions created by Everything Solved are Copyright (C) 2006
+# Everything Solved. All Rights Reserved.
+#
+# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::Install::Util;
+
+# The difference between this module and Bugzilla::Util is that this
+# module may require *only* Bugzilla::Constants and built-in
+# perl modules.
+
+use strict;
+
+use Bugzilla::Constants;
+
+use Encode;
+use ExtUtils::MM ();
+use File::Basename;
+use File::Spec;
+use POSIX qw(setlocale LC_CTYPE);
+use Safe;
+use Scalar::Util qw(tainted);
+use Term::ANSIColor qw(colored);
+use PerlIO;
+
+use base qw(Exporter);
+our @EXPORT_OK = qw(
+ bin_loc
+ get_version_and_os
+ extension_code_files
+ extension_package_directory
+ extension_requirement_packages
+ extension_template_directory
+ indicate_progress
+ install_string
+ include_languages
+ template_include_path
+ vers_cmp
+ init_console
+);
+
+sub bin_loc {
+ my ($bin, $path) = @_;
+ 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] };
+}
+
+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;
+}
+
+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);
+ }
+ 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;
+}
+
+# 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";
+}
+
+# For extensions that are in the extensions/ dir, this both sets and fetches
+# the name of the directory that stores an extension's "stuff". We need this
+# 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;
+
+ my $var;
+ { 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";
+ }
+}
+
+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;
+}
+
+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 = Bugzilla->cgi;
+ $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;
+ }
+
+ # 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);
+ }
+
+ $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.
+ 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 $supported = supported_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;
+}
+
+# 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);
+ }
+ }
+
+ # 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;
+}
+
+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);
+ }
+ }
+ return \@include_path;
+}
+
+# This is taken straight from Sort::Versions 1.5, which is not included
+# with perl by default.
+sub vers_cmp {
+ my ($a, $b) = @_;
+
+ # Remove leading zeroes - Bug 344661
+ $a =~ s/^0*(\d.+)/$1/;
+ $b =~ s/^0*(\d.+)/$1/;
+
+ my @A = ($a =~ /([-.]|\d+|[^-.\d]+)/g);
+ my @B = ($b =~ /([-.]|\d+|[^-.\d]+)/g);
+
+ my ($A, $B);
+ while (@A and @B) {
+ $A = shift @A;
+ $B = shift @B;
+ if ($A eq '-' and $B eq '-') {
+ next;
+ } elsif ( $A eq '-' ) {
+ return -1;
+ } elsif ( $B eq '-') {
+ return 1;
+ } elsif ($A eq '.' and $B eq '.') {
+ next;
+ } elsif ( $A eq '.' ) {
+ return -1;
+ } elsif ( $B eq '.' ) {
+ return 1;
+ } elsif ($A =~ /^\d+$/ and $B =~ /^\d+$/) {
+ if ($A =~ /^0/ || $B =~ /^0/) {
+ return $A cmp $B if $A cmp $B;
+ } else {
+ return $A <=> $B if $A <=> $B;
+ }
+ } else {
+ $A = uc $A;
+ $B = uc $B;
+ return $A cmp $B if $A cmp $B;
+ }
+ }
+ @A <=> @B;
+}
+
+######################
+# Helper Subroutines #
+######################
+
+# Used by install_string
+sub _get_string_from_file {
+ my ($string_id, $file) = @_;
+
+ return undef if !-e $file;
+ my $safe = new Safe;
+ $safe->rdo($file);
+ my %strings = %{$safe->varglob('strings')};
+ return $strings{$string_id};
+}
+
+# Make an ordered list out of a HTTP Accept-Language header (see RFC 2616, 14.4)
+# We ignore '*' and <language-range>;q=0
+# For languages with the same priority q the order remains unchanged.
+sub _sort_accept_language {
+ sub sortQvalue { $b->{'qvalue'} <=> $a->{'qvalue'} }
+ my $accept_language = $_[0];
+
+ # clean up string.
+ $accept_language =~ s/[^A-Za-z;q=0-9\.\-,]//g;
+ my @qlanguages;
+ my @languages;
+ foreach(split /,/, $accept_language) {
+ if (m/([A-Za-z\-]+)(?:;q=(\d(?:\.\d+)))?/) {
+ my $lang = $1;
+ my $qvalue = $2;
+ $qvalue = 1 if not defined $qvalue;
+ next if $qvalue == 0;
+ $qvalue = 1 if $qvalue > 1;
+ push(@qlanguages, {'qvalue' => $qvalue, 'language' => $lang});
+ }
+ }
+
+ return map($_->{'language'}, (sort sortQvalue @qlanguages));
+}
+
+sub get_console_locale {
+ require Locale::Language;
+ my $locale = setlocale(LC_CTYPE);
+ my $language;
+ # Some distros set e.g. LC_CTYPE = fr_CH.UTF-8. We clean it up.
+ if ($locale =~ /^([^\.]+)/) {
+ $locale = $1;
+ }
+ $locale =~ s/_/-/;
+ # It's pretty sure that there is no language pack of the form fr-CH
+ # installed, so we also include fr as a wanted language.
+ if ($locale =~ /^(\S+)\-/) {
+ $language = $1;
+ $locale .= ",$language";
+ }
+ else {
+ $language = $locale;
+ }
+
+ # Some OSs or distributions may have setlocale return a string of the form
+ # German_Germany.1252 (this example taken from a Windows XP system), which
+ # is unsuitable for our needs because Bugzilla works on language codes.
+ # We try and convert them here.
+ if ($language = Locale::Language::language2code($language)) {
+ $locale .= ",$language";
+ }
+
+ return $locale;
+}
+
+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';
+ }
+}
+
+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();
+}
+
+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";
+}
+
+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 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;
+}
+
+###############################
+# Copied from Bugzilla::Util #
+##############################
+
+sub trick_taint {
+ require Carp;
+ Carp::confess("Undef to trick_taint") unless defined $_[0];
+ my $match = $_[0] =~ /^(.*)$/s;
+ $_[0] = $match ? $1 : undef;
+ return (defined($_[0]));
+}
+
+sub trim {
+ my ($str) = @_;
+ if ($str) {
+ $str =~ s/^\s+//g;
+ $str =~ s/\s+$//g;
+ }
+ return $str;
+}
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Install::Util - Utility functions that are useful both during
+installation and afterwards.
+
+=head1 DESCRIPTION
+
+This module contains various subroutines that are used primarily
+during installation. However, these subroutines can also be useful to
+non-installation code, so they have been split out into this module.
+
+The difference between this module and L<Bugzilla::Util> is that this
+module is safe to C<use> anywhere in Bugzilla, even during installation,
+because it depends only on L<Bugzilla::Constants> and built-in perl modules.
+
+None of the subroutines are exported by default--you must explicitly
+export them.
+
+=head1 SUBROUTINES
+
+=over
+
+=item C<bin_loc>
+
+On *nix systems, given the name of a binary, returns the path to that
+binary, if the binary is in the C<PATH>.
+
+=item C<get_version_and_os>
+
+Returns a hash containing information about what version of Bugzilla we're
+running, what perl version we're using, and what OS we're running on.
+
+=item C<get_console_locale>
+
+Returns the language to use based on the LC_CTYPE value returned by the OS.
+If LC_CTYPE is of the form fr-CH, then fr is appended to the list.
+
+=item C<init_console>
+
+Sets the C<ANSI_COLORS_DISABLED> and C<HTTP_ACCEPT_LANGUAGE> environment variables.
+
+=item C<indicate_progress>
+
+=over
+
+=item B<Description>
+
+This prints out lines of dots as a long update is going on, to let the user
+know where we are and that we're not frozen. A new line of dots will start
+every 60 dots.
+
+Sample usage: C<indicate_progress({ total =E<gt> $total, current =E<gt>
+$count, every =E<gt> 1 })>
+
+=item B<Sample Output>
+
+Here's some sample output with C<total = 1000> and C<every = 10>:
+
+ ............................................................600/1000 (60%)
+ ........................................
+
+=item B<Params>
+
+=over
+
+=item C<total> - The total number of items we're processing.
+
+=item C<current> - The number of the current item we're processing.
+
+=item C<every> - How often the function should print out a dot.
+For example, if this is 10, the function will print out a dot every
+ten items. Defaults to 1 if not specified.
+
+=back
+
+=item B<Returns>: nothing
+
+=back
+
+=item C<install_string>
+
+=over
+
+=item B<Description>
+
+This is a very simple method of templating strings for installation.
+It should only be used by code that has to run before the Template Toolkit
+can be used. (See the comments at the top of the various L<Bugzilla::Install>
+modules to find out when it's safe to use Template Toolkit.)
+
+It pulls strings out of the F<strings.txt.pl> "template" and replaces
+any variable surrounded by double-hashes (##) with a value you specify.
+
+This allows for localization of strings used during installation.
+
+=item B<Example>
+
+Let's say your template string looks like this:
+
+ The ##animal## jumped over the ##plant##.
+
+Let's say that string is called 'animal_jump_plant'. So you call the function
+like this:
+
+ install_string('animal_jump_plant', { animal => 'fox', plant => 'tree' });
+
+That will output this:
+
+ The fox jumped over the tree.
+
+=item B<Params>
+
+=over
+
+=item C<$string_id> - The name of the string from F<strings.txt.pl>.
+
+=item C<$vars> - A hashref containing the replacement values for variables
+inside of the string.
+
+=back
+
+=item B<Returns>: The appropriate string, with variables replaced.
+
+=back
+
+=item C<template_include_path>
+
+Used by L<Bugzilla::Template> and L</install_string> to determine the
+directories where templates are installed. Templates can be installed
+in many places. They're listed here in the basic order that they're
+searched:
+
+=over
+
+=item extensions/C<$extension>/template/C<$language>/C<$project>
+
+=item extensions/C<$extension>/template/C<$language>/custom
+
+=item extensions/C<$extension>/template/C<$language>/default
+
+=item template/C<$language>/C<$project>
+
+=item template/C<$language>/custom
+
+=item template/C<$language>/default
+
+=back
+
+C<$project> has to do with installations that are using the C<$ENV{PROJECT}>
+variable to have different "views" on a single Bugzilla.
+
+The F<default> directory includes templates shipped with Bugzilla.
+
+The F<custom> directory is a directory for local installations to override
+the F<default> templates. Any individual template in F<custom> will
+override a template of the same name and path in F<default>.
+
+C<$language> is a language code, C<en> being the default language shipped
+with Bugzilla. Localizers ship other languages.
+
+C<$extension> is the name of any directory in the F<extensions/> directory.
+Each extension has its own directory.
+
+Note that languages are sorted by the user's preference (as specified
+in their browser, usually), and extensions are sorted alphabetically.
+
+=item C<include_languages>
+
+Used by L<Bugzilla::Template> to determine the languages' list which
+are compiled with the browser's I<Accept-Language> and the languages
+of installed templates.
+
+=item C<vers_cmp>
+
+=over
+
+=item B<Description>
+
+This is a comparison function, like you would use in C<sort>, except that
+it compares two version numbers. So, for example, 2.10 would be greater
+than 2.2.
+
+It's based on versioncmp from L<Sort::Versions>, with some Bugzilla-specific
+fixes.
+
+=item B<Params>: C<$a> and C<$b> - The versions you want to compare.
+
+=item B<Returns>
+
+C<-1> if C<$a> is less than C<$b>, C<0> if they are equal, or C<1> if C<$a>
+is greater than C<$b>.
+
+=back
+
+=back
diff --git a/Bugzilla/Job/Mailer.pm b/Bugzilla/Job/Mailer.pm
new file mode 100644
index 000000000..09c387326
--- /dev/null
+++ b/Bugzilla/Job/Mailer.pm
@@ -0,0 +1,57 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Mozilla Corporation.
+# Portions created by the Initial Developer are Copyright (C) 2008
+# Mozilla Corporation. All Rights Reserved.
+#
+# Contributor(s):
+# Mark Smith <mark@mozilla.com>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::Job::Mailer;
+use strict;
+use Bugzilla::Mailer;
+BEGIN { eval "use base 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;
+}
+
+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;
+ }
+}
+
+1;
diff --git a/Bugzilla/JobQueue.pm b/Bugzilla/JobQueue.pm
new file mode 100644
index 000000000..1046cf2c3
--- /dev/null
+++ b/Bugzilla/JobQueue.pm
@@ -0,0 +1,110 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Mozilla Corporation.
+# Portions created by the Initial Developer are Copyright (C) 2008
+# Mozilla Corporation. All Rights Reserved.
+#
+# Contributor(s):
+# Mark Smith <mark@mozilla.com>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::JobQueue;
+
+use strict;
+
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Install::Util qw(install_string);
+use base qw(TheSchwartz);
+
+# 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',
+};
+
+sub new {
+ my $class = shift;
+
+ if (!Bugzilla->feature('jobqueue')) {
+ ThrowCodeError('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_',
+ }],
+ );
+
+ 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;
+}
+
+# inserts a job into the queue to be processed and returns immediately
+sub insert {
+ my $self = shift;
+ my $job = shift;
+
+ my $mapped_job = JOB_MAP->{$job};
+ ThrowCodeError('jobqueue_no_job_mapping', { job => $job })
+ if !$mapped_job;
+ unshift(@_, $mapped_job);
+
+ my $retval = $self->SUPER::insert(@_);
+ # 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;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::JobQueue - Interface between Bugzilla and TheSchwartz.
+
+=head1 SYNOPSIS
+
+ use Bugzilla;
+
+ my $obj = Bugzilla->job_queue();
+ $obj->insert('send_mail', { msg => $message });
+
+=head1 DESCRIPTION
+
+Certain tasks should be done asyncronously. The job queue system allows
+Bugzilla to use some sort of service to schedule jobs to happen asyncronously.
+
+=head2 Inserting a Job
+
+See the synopsis above for an easy to follow example on how to insert a
+job into the queue. Give it a name and some arguments and the job will
+be sent away to be done later.
diff --git a/Bugzilla/JobQueue/Runner.pm b/Bugzilla/JobQueue/Runner.pm
new file mode 100644
index 000000000..8cfc965eb
--- /dev/null
+++ b/Bugzilla/JobQueue/Runner.pm
@@ -0,0 +1,228 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Mozilla Corporation.
+# Portions created by the Initial Developer are Copyright (C) 2008
+# Mozilla Corporation. All Rights Reserved.
+#
+# Contributor(s):
+# Mark Smith <mark@mozilla.com>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+# XXX In order to support Windows, we have to make gd_redirect_output
+# use Log4Perl or something instead of calling "logger". We probably
+# also need to use Win32::Daemon or something like that to daemonize.
+
+package Bugzilla::JobQueue::Runner;
+
+use strict;
+use Cwd qw(abs_path);
+use File::Basename;
+use File::Copy;
+use Pod::Usage;
+
+use Bugzilla::Constants;
+use Bugzilla::JobQueue;
+use Bugzilla::Util qw(get_text);
+BEGIN { eval "use base 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 $initscript = "bugzilla-queue";
+
+# The Daemon::Generic docs say that it uses all sorts of
+# things from gd_preconfig, but in fact it does not. The
+# only thing it uses from gd_preconfig is the "pidfile"
+# config parameter.
+sub gd_preconfig {
+ my $self = shift;
+
+ 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;
+
+ $self->SUPER::gd_getopt();
+
+ 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};
+}
+
+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};
+}
+
+sub gd_more_opt {
+ 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
+}
+
+sub gd_can_install {
+ 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(@_);
+ }
+
+ 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;
+#!/bin/sh
+BUGZILLA="$directory"
+USER=$owner
+END
+ 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";
+ }
+ }
+
+ 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";
+}
+
+sub gd_setup_signals {
+ my $self = shift;
+ $self->SUPER::gd_setup_signals();
+ $SIG{TERM} = sub { $self->gd_quit_event(); }
+}
+
+sub gd_run {
+ my $self = shift;
+
+ my $jq = Bugzilla->job_queue();
+ $jq->set_verbose($self->{debug});
+ foreach my $module (values %{ Bugzilla::JobQueue::JOB_MAP() }) {
+ eval "use $module";
+ $jq->can_do($module);
+ }
+ $jq->work;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::JobQueue::Runner - A class representing the daemon that runs the
+job queue.
+
+=head1 SYNOPSIS
+
+ use Bugzilla::JobQueue::Runner;
+ Bugzilla::JobQueue::Runner->new();
+
+=head1 DESCRIPTION
+
+This is a subclass of L<Daemon::Generic> that is used by L<jobqueue>
+to run the Bugzilla job queue.
diff --git a/Bugzilla/Keyword.pm b/Bugzilla/Keyword.pm
new file mode 100644
index 000000000..e2ecc29e5
--- /dev/null
+++ b/Bugzilla/Keyword.pm
@@ -0,0 +1,176 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+
+use strict;
+
+package Bugzilla::Keyword;
+
+use base qw(Bugzilla::Object);
+
+use Bugzilla::Error;
+use Bugzilla::Util;
+
+###############################
+#### Initialization ####
+###############################
+
+use constant DB_COLUMNS => qw(
+ keyworddefs.id
+ keyworddefs.name
+ keyworddefs.description
+);
+
+use constant DB_TABLE => 'keyworddefs';
+
+use constant VALIDATORS => {
+ name => \&_check_name,
+ description => \&_check_description,
+};
+
+use constant UPDATE_COLUMNS => qw(
+ name
+ description
+);
+
+###############################
+#### Accessors ######
+###############################
+
+sub description { return $_[0]->{'description'}; }
+
+sub bug_count {
+ my ($self) = @_;
+ return $self->{'bug_count'} if defined $self->{'bug_count'};
+ ($self->{'bug_count'}) =
+ Bugzilla->dbh->selectrow_array(
+ 'SELECT COUNT(*) FROM keywords WHERE keywordid = ?',
+ undef, $self->id);
+ return $self->{'bug_count'};
+}
+
+###############################
+#### Mutators #####
+###############################
+
+sub set_name { $_[0]->set('name', $_[1]); }
+sub set_description { $_[0]->set('description', $_[1]); }
+
+###############################
+#### Subroutines ######
+###############################
+
+sub get_all_with_bug_count {
+ 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;
+}
+
+###############################
+### Validators ###
+###############################
+
+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) || $self->name ne $name) {
+ my $keyword = new Bugzilla::Keyword({ name => $name });
+ ThrowUserError("keyword_already_exists", { name => $name }) if $keyword;
+ }
+
+ return $name;
+}
+
+sub _check_description {
+ my ($self, $desc) = @_;
+ $desc = trim($desc);
+ if (!defined $desc or $desc eq '') {
+ ThrowUserError("keyword_blank_description");
+ }
+ return $desc;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Keyword - A Keyword that can be added to a bug.
+
+=head1 SYNOPSIS
+
+ use Bugzilla::Keyword;
+
+ my $description = $keyword->description;
+
+ my $keywords = Bugzilla::Keyword->get_all_with_bug_count();
+
+=head1 DESCRIPTION
+
+Bugzilla::Keyword represents a keyword that can be added to a bug.
+
+This implements all standard C<Bugzilla::Object> methods. See
+L<Bugzilla::Object> for more details.
+
+=head1 SUBROUTINES
+
+This is only a list of subroutines specific to C<Bugzilla::Keyword>.
+See L<Bugzilla::Object> for more subroutines that this object
+implements.
+
+=over
+
+=item C<get_all_with_bug_count()>
+
+ Description: Returns all defined keywords. This is an efficient way
+ to get the associated bug counts, as only one SQL query
+ is executed with this method, instead of one per keyword
+ when calling get_all and then bug_count.
+ Params: none
+ Returns: A reference to an array of Keyword objects, or an empty
+ arrayref if there are no keywords.
+
+=back
+
+=cut
diff --git a/Bugzilla/Mailer.pm b/Bugzilla/Mailer.pm
new file mode 100644
index 000000000..5ee6fd2eb
--- /dev/null
+++ b/Bugzilla/Mailer.pm
@@ -0,0 +1,220 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>,
+# Bryce Nesbitt <bryce-mozilla@nextbus.com>
+# Dan Mosedale <dmose@mozilla.org>
+# Alan Raetz <al_raetz@yahoo.com>
+# Jacob Steenhagen <jake@actex.net>
+# Matthew Tuck <matty@chariot.net.au>
+# Bradley Baetz <bbaetz@student.usyd.edu.au>
+# J. Paul Reed <preed@sigkill.com>
+# Gervase Markham <gerv@gerv.net>
+# Byron Jones <bugzilla@glob.com.au>
+# Frédéric Buclin <LpSolit@gmail.com>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::Mailer;
+
+use strict;
+
+use base qw(Exporter);
+@Bugzilla::Mailer::EXPORT = qw(MessageToMTA build_thread_marker);
+
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Hook;
+use Bugzilla::Util;
+
+use Date::Format qw(time2str);
+
+use Encode qw(encode);
+use Encode::MIME::Header;
+use Email::Address;
+use Email::MIME;
+use Email::Send;
+
+sub MessageToMTA {
+ my ($msg, $send_now) = (@_);
+ my $method = Bugzilla->params->{'mail_delivery_method'};
+ return if $method eq 'None';
+
+ if (Bugzilla->params->{'use_mailer_queue'} and !$send_now) {
+ Bugzilla->job_queue->insert('send_mail', { msg => $msg });
+ return;
+ }
+
+ my $email;
+ if (ref $msg) {
+ $email = $msg;
+ }
+ else {
+ # 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
+ # We check for multiple CRs because of this Template-Toolkit bug:
+ # https://rt.cpan.org/Ticket/Display.html?id=43345
+ $msg =~ s/(?:\015+)?\012/\015\012/msg;
+ $email = new Email::MIME($msg);
+ }
+
+ # 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.
+ $email->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.
+ $email->header_set('Auto-Submitted', 'auto-generated');
+
+ $email->walk_parts(sub {
+ my ($part) = @_;
+ return if $part->parts > 1; # Top-level
+ my $content_type = $part->content_type || '';
+ if ($content_type !~ /;/) {
+ my $body = $part->body;
+ if (Bugzilla->params->{'utf8'}) {
+ $part->charset_set('UTF-8');
+ # encoding_set works only with bytes, not with utf8 strings.
+ my $raw = $part->body_raw;
+ if (utf8::is_utf8($raw)) {
+ utf8::encode($raw);
+ $part->body_set($raw);
+ }
+ }
+ $part->encoding_set('quoted-printable') if !is_7bit_clean($body);
+ }
+ });
+
+ # MIME-Version must be set otherwise some mailsystems ignore the charset
+ $email->header_set('MIME-Version', '1.0') if !$email->header('MIME-Version');
+
+ # Encode the headers correctly in quoted-printable
+ foreach my $header ($email->header_names) {
+ my @values = $email->header($header);
+ # We don't recode headers that happen multiple times.
+ next if scalar(@values) > 1;
+ if (my $value = $values[0]) {
+ if (Bugzilla->params->{'utf8'} && !utf8::is_utf8($value)) {
+ utf8::decode($value);
+ }
+
+ # avoid excessive line wrapping done by Encode.
+ local $Encode::Encoding{'MIME-Q'}->{'bpl'} = 998;
+
+ my $encoded = encode('MIME-Q', $value);
+ $email->header_set($header, $encoded);
+ }
+ }
+
+ my $from = $email->header('From');
+
+ my ($hostname, @args);
+ if ($method eq "Sendmail") {
+ if (ON_WINDOWS) {
+ $Email::Send::Sendmail::SENDMAIL = SENDMAIL_EXE;
+ }
+ push @args, "-i";
+ # We want to make sure that we pass *only* an email address.
+ if ($from) {
+ my ($email_obj) = Email::Address->parse($from);
+ if ($email_obj) {
+ my $from_email = $email_obj->address;
+ push(@args, "-f$from_email") if $from_email;
+ }
+ }
+ }
+ else {
+ # Sendmail will automatically append our hostname to the From
+ # address, but other mailers won't.
+ my $urlbase = Bugzilla->params->{'urlbase'};
+ $urlbase =~ m|//([^:/]+)[:/]?|;
+ $hostname = $1;
+ $from .= "\@$hostname" if $from !~ /@/;
+ $email->header_set('From', $from);
+
+ # Sendmail adds a Date: header also, but others may not.
+ if (!defined $email->header('Date')) {
+ $email->header_set('Date', time2str("%a, %d %b %Y %T %z", time()));
+ }
+ }
+
+ if ($method eq "SMTP") {
+ push @args, Host => Bugzilla->params->{"smtpserver"},
+ username => Bugzilla->params->{"smtp_username"},
+ password => Bugzilla->params->{"smtp_password"},
+ Hello => $hostname,
+ Debug => Bugzilla->params->{'smtp_debug'};
+ }
+
+ Bugzilla::Hook::process('mailer_before_send',
+ { email => $email, mailer_args => \@args });
+
+ if ($method eq "Test") {
+ my $filename = bz_locations()->{'datadir'} . '/mailer.testfile';
+ open TESTFILE, '>>', $filename;
+ # From - <date> is required to be a valid mbox file.
+ print TESTFILE "\n\nFrom - " . $email->header('Date') . "\n" . $email->as_string;
+ close TESTFILE;
+ }
+ else {
+ # This is useful for both Sendmail and Qmail, so we put it out here.
+ local $ENV{PATH} = SENDMAIL_PATH;
+ my $mailer = Email::Send->new({ mailer => $method,
+ mailer_args => \@args });
+ my $retval = $mailer->send($email);
+ ThrowCodeError('mail_send_error', { msg => $retval, mail => $email })
+ if !$retval;
+ }
+}
+
+# Builds header suitable for use as a threading marker in email notifications
+sub build_thread_marker {
+ my ($bug_id, $user_id, $is_new) = @_;
+
+ if (!defined $user_id) {
+ $user_id = Bugzilla->user->id;
+ }
+
+ my $sitespec = '@' . Bugzilla->params->{'urlbase'};
+ $sitespec =~ s/:\/\//\./; # Make the protocol look like part of the domain
+ $sitespec =~ s/^([^:\/]+):(\d+)/$1/; # Remove a port number, to relocate
+ if ($2) {
+ $sitespec = "-$2$sitespec"; # Put the port number back in, before the '@'
+ }
+
+ my $threadingmarker;
+ if ($is_new) {
+ $threadingmarker = "Message-ID: <bug-$bug_id-$user_id$sitespec>";
+ }
+ else {
+ 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;
+}
+
+1;
diff --git a/Bugzilla/Migrate.pm b/Bugzilla/Migrate.pm
new file mode 100644
index 000000000..62e4fcf8e
--- /dev/null
+++ b/Bugzilla/Migrate.pm
@@ -0,0 +1,1173 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is The Bugzilla Migration Tool.
+#
+# The Initial Developer of the Original Code is Lambda Research
+# Corporation. Portions created by the Initial Developer are Copyright
+# (C) 2009 the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::Migrate;
+use strict;
+
+use Bugzilla::Attachment;
+use Bugzilla::Bug qw(LogActivityEntry);
+use Bugzilla::Component;
+use Bugzilla::Constants;
+use Bugzilla::Error;
+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::Status ();
+use Bugzilla::Version;
+
+use Data::Dumper;
+use Date::Parse;
+use DateTime;
+use Fcntl qw(SEEK_SET);
+use File::Basename;
+use List::Util qw(first);
+use Safe;
+
+use constant CUSTOM_FIELDS => {};
+use constant REQUIRED_MODULES => [];
+use constant NON_COMMENT_FIELDS => ();
+
+use constant CONFIG_VARS => (
+ {
+ 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
+# an equivalent field in Bugzilla, its data will be added to the initial
+# description of each bug migrated. If the right side is an empty string, it
+# means "just put the value of this field into the initial description of the
+# bug".
+#
+# Generally, you can keep the defaults, here.
+#
+# If you want to know the internal names of various Bugzilla fields
+# (as used on the right side here), see the fielddefs table in the Bugzilla
+# database.
+#
+# If you are mapping to any custom fields in Bugzilla, you have to create
+# the custom fields using Bugzilla Administration interface before you run
+# migrate.pl. However, if they are drop down or multi-select fields, you
+# don't have to populate the list of values--migrate.pl will do that for you.
+# Some migrators create certain custom fields by default. If you see a
+# field name starting with "cf_" on the right side of this configuration
+# 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',
+# 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.
+#
+# The value of this variable should look something like this:
+#
+# {
+# bug_status => {
+# # Translate "Handled" into "RESOLVED".
+# "Handled" => "RESOLVED",
+# "In Progress" => "IN_PROGRESS",
+# },
+#
+# priority => {
+# # Translate "Serious" into "Highest"
+# "Serious" => "Highest",
+# },
+# };
+#
+# Values are translated case-insensitively, so "foo" will match "Foo", "FOO",
+# and "foo".
+#
+# Note that the field names used are *Bugzilla* field names (from the fielddefs
+# table in the database), not the field names from your current bug-tracker.
+#
+# The special field name "user" will be used to translate any field that
+# can contain a user, including reporter, assigned_to, qa_contact, and cc.
+# You should use "user" instead of specifying reporter, assigned_to, etc.
+# manually.
+#
+# The special field "bug_status_resolution" can be used to give certain
+# statuses in your bug-tracker a resolution in Bugzilla. So, for example,
+# you could translate the "fixed" status in your Bugzilla to "RESOLVED"
+# in the "bug_status" field, and then put "fixed => 'FIXED'" in the
+# "bug_status_resolution" field to translated a "fixed" bug into
+# RESOLVED FIXED in Bugzilla.
+#
+# Values that don't get translated will be imported as-is.
+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',
+# 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".
+# However, time zone abbreviations (like PST, PDT, etc.) are also acceptable,
+# but will result in a less-accurate conversion of times and dates.
+#
+# 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);
+
+#########################
+# Main Migration Method #
+#########################
+
+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();
+
+ # 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();
+ }
+}
+
+################
+# Constructors #
+################
+
+sub new {
+ 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;
+}
+
+#############
+# Accessors #
+#############
+
+sub name {
+ 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;
+}
+
+
+sub verbose {
+ 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";
+ }
+}
+
+sub bug_fields {
+ my $self = shift;
+ $self->{bug_fields} ||= { map { $_->{name} => $_ } Bugzilla->get_fields };
+ return $self->{bug_fields};
+}
+
+sub users {
+ my $self = shift;
+ if (!exists $self->{users}) {
+ print get_text('migrate_reading_users'), "\n";
+ $self->{users} = $self->_read_users();
+ }
+ return $self->{users};
+}
+
+sub products {
+ my $self = shift;
+ if (!exists $self->{products}) {
+ print get_text('migrate_reading_products'), "\n";
+ $self->{products} = $self->_read_products();
+ }
+ return $self->{products};
+}
+
+sub bugs {
+ my $self = shift;
+ if (!exists $self->{bugs}) {
+ print get_text('migrate_reading_bugs'), "\n";
+ $self->{bugs} = $self->_read_bugs();
+ }
+ return $self->{bugs};
+}
+
+###########
+# Methods #
+###########
+
+sub check_requirements {
+ my $self = shift;
+ my $missing = Bugzilla::Install::Requirements::_check_missing(
+ $self->REQUIRED_MODULES, 1);
+ my %results = (
+ 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;
+}
+
+###################
+# Bug Translation #
+###################
+
+sub translate_all_bugs {
+ my ($self, $bugs) = @_;
+ print get_text('migrate_translating_bugs'), "\n";
+ # 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;
+ }
+ }
+
+ 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};
+}
+
+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;
+}
+
+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 $tz;
+ if ($time[6]) {
+ $tz = Bugzilla->local_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;
+}
+
+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) ];
+ }
+
+
+ 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);
+ }
+ }
+
+ 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))
+ {
+ $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)};
+}
+
+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;
+ }
+ return $self->{_value_map};
+}
+
+#################
+# Configuration #
+#################
+
+sub config {
+ 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"
+}
+
+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;
+}
+
+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);
+}
+
+####################################
+# Default Implementations of Hooks #
+####################################
+
+sub after_insert {}
+sub before_insert {}
+sub after_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";
+ }
+}
+
+# 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";
+ }
+ }
+}
+
+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);
+ }
+ print get_text('migrate_field_created', { field => $created }), "\n";
+ }
+ 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;
+ }
+ }
+
+ 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;
+ print get_text('migrate_creating_bugs'), "\n";
+
+ 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} });
+ }
+ 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 his 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);
+ }
+
+ 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();
+}
+
+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);
+ }
+}
+
+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);
+ }
+}
+
+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);
+}
+
+######################
+# Helper Subroutines #
+######################
+
+sub _canonical_name {
+ my ($module) = @_;
+ $module =~ s{::}{/}g;
+ $module = basename($module);
+ $module =~ s/\.pm$//g;
+ return $module;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Migrate - Functions to migrate from other databases
+
+=head1 DESCRIPTION
+
+This module acts as a base class for the various modules that migrate
+from other bug-trackers.
+
+The documentation for this module exists mostly to assist people in
+creating new migrators for other bug-trackers than the ones currently
+supported.
+
+=head1 HOW MIGRATION WORKS
+
+Before writing anything to the Bugzilla database, the migrator will read
+everything from the other bug-tracker's database. Here's the exact order
+of what happens:
+
+=over
+
+=item 1
+
+Users are read from the other bug-tracker.
+
+=item 2
+
+Products are read from the other bug-tracker.
+
+=item 3
+
+Bugs are read from the other bug-tracker.
+
+=item 4
+
+The L</after_read> method is called.
+
+=item 5
+
+All bugs are translated from the other bug-tracker's fields/values
+into Bugzilla's fields values using L</translate_bug>.
+
+=item 6
+
+Users are inserted into Bugzilla.
+
+=item 7
+
+Products are inserted into Bugzilla.
+
+=item 8
+
+Some migrators need to create custom fields before migrating, and
+so that happens here.
+
+=item 9
+
+Any legal values that need to be created for any drop-down or
+multi-select fields are created. This is done by reading all the
+values on every bug that was read in and creating any values that
+don't already exist in Bugzilla for every drop-down or multi-select
+field on each bug. This includes creating any product versions and
+milestones that need to be created.
+
+=item 10
+
+Bugs are inserted into Bugzilla.
+
+=item 11
+
+The L</after_insert> method is called.
+
+=back
+
+Everything happens in one big transaction, so in general, if there are
+any errors during the process, nothing will be changed.
+
+The migrator never creates anything that already exists. So users, products,
+components, etc. that already exist will just be re-used by this script,
+not re-created.
+
+=head1 CONSTRUCTOR
+
+=head2 load
+
+Called like C<< Bugzilla::Migrate->load('Module') >>. Returns a new
+C<Bugzilla::Migrate> object that can be used to migrate from the
+requested bug-tracker.
+
+=head1 METHODS YOUR SUBCLASS CAN USE
+
+=head2 config
+
+Takes a single parameter, a string, and returns the value of the
+configuration variable with that name (always a scalar). The first time
+you call C<config>, if the configuration file hasn't been read, it will
+be read in.
+
+=head2 debug
+
+If the user hasn't specified C<--verbose> on the command line, this
+does nothing.
+
+Takes two arguments:
+
+The first argument is a string or reference to print to C<STDERR>.
+If it's a reference, L<Data::Dumper> will be used to print the
+data structure.
+
+The second argument is a number--the string will only be printed if the
+user specified C<--verbose> at least that many times on the command line.
+
+=head2 parse_date
+
+(Note: Usually you don't need to call this, because L</translate_bug>
+handles date translations for you, for bug data.)
+
+Parses a date string and returns a formatted date string that can be inserted
+into the database. If the input date is missing a timezone, the "timezone"
+configuration parameter will be used as the timezone of the date.
+
+=head2 translate_bug
+
+(Note: Normally you don't have to call this yourself, as
+C<Bugzilla::Migrate> does it for you.)
+
+Uses the C<$translate_fields> and C<$translate_values> configuration variables
+to convert a hashref of "other bug-tracker" fields into Bugzilla fields.
+It takes one argument, the hashref to convert. Any unrecognized fields will
+have their value prepended to the C<comment> element in the returned
+hashref, unless they are listed in L</NON_COMMENT_FIELDS>.
+
+In scalar context, returns the translated bug. In array context,
+returns both the translated bug and a second hashref containing the values
+of any untranslated fields that were listed in C<NON_COMMENT_FIELDS>.
+
+B<Note:> To save memory, the hashref that you pass in will be destroyed
+(all keys will be deleted).
+
+=head2 translate_value
+
+(Note: Generally you only need to use this during L</_read_products>
+and L</_read_users> if necessary, because the data returned from
+L</_read_bugs> will be put through L</translate_bug>.)
+
+Uses the C<$translate_values> configuration variable to convert
+field values from your bug-tracker to Bugzilla. Takes two arguments,
+the first being a field name and the second being a value. If the value
+is an arrayref, C<translate_value> will be called recursively on all
+the array elements.
+
+Also, any date field will be converted into ISO 8601 format, for
+inserting into the database.
+
+=head2 translate_field
+
+(Note: Normally you don't need to use this, because L</translate_bug>
+handles it for you.)
+
+Translates a field name in your bug-tracker to a field name in Bugzilla,
+using the rules described in the description of the C<$translate_fields>
+configuration variable.
+
+Takes a single argument--the name of a field to translate.
+
+Returns C<undef> if the field could not be translated.
+
+=head1 METHODS YOU MUST IMPLEMENT
+
+These are methods that subclasses must implement:
+
+=head2 _read_bugs
+
+Should return an arrayref of hashes. The hashes will be passed to
+L<Bugzilla::Bug/create> to create bugs in Bugzilla. In addition to
+the normal C<create> fields, the hashes can contain three additional
+items:
+
+=over
+
+=item comments
+
+An arrayref of hashes, representing comments to be added to the
+database. The keys should be the names of columns in the longdescs
+table that you want to set for each comment. C<who> must be a
+username instead of a user id, though.
+
+You don't need to specify a value for the C<bug_id> column.
+
+=item history
+
+An arrayref of hashes, representing the history of changes made
+to this bug. The keys should be the names of columns in the
+bugs_activity table to set for each change. C<who> must be a username
+instead of a user id, though, and C<field> (containing the name of some field)
+is taken instead of C<fieldid>.
+
+You don't need to specify a value for the C<bug_id> column.
+
+=item attachments
+
+An arrayref of hashes, representing values to pass to
+L<Bugzilla::Attachment/create>. (Remember that the C<data> argument
+must be a file handle--we recommend using L<IO::File/new_tmpfile> to create
+anonymous temporary files for this purpose.) You should specify a
+C<submitter> argument containing the username of the attachment's submitter.
+
+You don't need to specify a value for the the C<bug> argument.
+
+=back
+
+=head2 _read_products
+
+Should return an arrayref of hashes to pass to L<Bugzilla::Product/create>.
+In addition to the normal C<create> fields, this also accepts an additional
+argument, C<components>, which is an arrayref of hashes to pass to
+L<Bugzilla::Component/create> (though you do not need to specify the
+C<product> argument for L<Bugzilla::Component/create>).
+
+=head2 _read_users
+
+Should return an arrayref of hashes to be passed to
+L<Bugzilla::User/create>.
+
+=head1 METHODS YOU MIGHT WANT TO IMPLEMENT
+
+These are methods that you may want to override in your migrator.
+All of these methods are called on an instantiated L<Bugzilla::Migrate>
+object of your subclass by L<Bugzilla::Migrate> itself.
+
+=head2 REQUIRED_MODULES
+
+Returns an arrayref of Perl modules that must be installed in order
+for your migrator to run, in the same format as
+L<Bugzilla::Install::Requirements/REQUIRED_MODULES>.
+
+=head2 CUSTOM_FIELDS
+
+Returns a hashref, where the keys are the names of custom fields
+to create in the database before inserting bugs. The values of the
+hashref are the arguments (other than "name") that should be passed
+to Bugzilla::Field->create() when creating the field. (C<< custom => 1 >>
+will be specified automatically for you, so you don't need to specify it.)
+
+=head2 CONFIG_VARS
+
+This should return an array (not an arrayref) in the same format as
+L<Bugzilla::Install::Localconfig/LOCALCONFIG_VARS>, describing
+configuration variables for migrating from your bug-tracker. You should
+always include the default C<CONFIG_VARS> (by calling
+$self->SUPER::CONFIG_VARS) as part of your return value, if you
+override this method.
+
+=head2 NON_COMMENT_FIELDS
+
+An array (not an arrayref). If there are fields that are not translated
+and yet shouldn't be added to the initial description of the bug when
+translating bugs, then they should be listed here. See L</translate_bug> for
+more detail.
+
+=head2 after_read
+
+This is run after all data is read from the other bug-tracker, but
+before the bug fields/values have been translated, and before any data
+is inserted into Bugzilla. The default implementation does nothing.
+
+=head2 before_insert
+
+This is called after all bugs are translated from their "other bug-tracker"
+values to Bugzilla values, but before any data is inserted into the database
+or any custom fields are created. The default implementation does nothing.
+
+=head2 after_insert
+
+This is run after all data is inserted into Bugzilla. The default
+implementation does nothing.
diff --git a/Bugzilla/Migrate/Gnats.pm b/Bugzilla/Migrate/Gnats.pm
new file mode 100644
index 000000000..db628b7d5
--- /dev/null
+++ b/Bugzilla/Migrate/Gnats.pm
@@ -0,0 +1,712 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is The Bugzilla Migration Tool.
+#
+# The Initial Developer of the Original Code is Lambda Research
+# Corporation. Portions created by the Initial Developer are Copyright
+# (C) 2009 the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::Migrate::Gnats;
+use strict;
+use base qw(Bugzilla::Migrate);
+
+use Bugzilla::Constants;
+use Bugzilla::Install::Util qw(indicate_progress);
+use Bugzilla::Util qw(format_time trim generate_random_password);
+
+use Email::Address;
+use Email::MIME;
+use File::Basename;
+use IO::File;
+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,
+ },
+];
+
+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',
+};
+
+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',
+ },
+};
+
+use constant GNATS_CONFIG_VARS => (
+ {
+ 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',
+# 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',
+# 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',
+# 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
+# this regular expression, and if they match, the first match (the first set
+# of parentheses in the regular expression, also called "$1") will be used
+# 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',
+# 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;
+}
+
+# Directories that aren't projects, or that we shouldn't be parsing
+use constant SKIP_DIRECTORIES => qw(
+ gnats-adm
+ gnats-queue
+ pending
+);
+
+use constant NON_COMMENT_FIELDS => qw(
+ Audit-Trail
+ Closed-Date
+ Confidential
+ Unformatted
+ attachments
+);
+
+# Certain fields can contain things that look like fields in them,
+# because they might contain quoted emails. To avoid mis-parsing,
+# we list out here the exact order of fields at the end of a PR
+# 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
+);
+
+use constant CUSTOM_FIELDS => {
+ cf_type => {
+ type => FIELD_TYPE_SINGLE_SELECT,
+ description => 'Type',
+ },
+};
+
+use constant FIELD_REGEX => qr/^>(\S+):\s*(.*)$/;
+
+# Used for bugs that have no Synopsis.
+use constant NO_SUBJECT => "(no subject)";
+
+# This is the divider that GNATS uses between attachments in its database
+# files. It's missign two hyphens at the beginning because MIME Emails use
+# -- to start boundaries.
+use constant GNATS_BOUNDARY => '----gnatsweb-attachment----';
+
+use constant LONG_VERSION_LENGTH => 32;
+
+#########
+# Hooks #
+#########
+
+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';
+ }
+ }
+}
+
+#########
+# Users #
+#########
+
+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;
+}
+
+sub 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 });
+}
+
+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;
+}
+
+############
+# Products #
+############
+
+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;
+}
+
+################
+# Reading Bugs #
+################
+
+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;
+}
+
+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 });
+ }
+ }
+ 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;
+}
+
+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;
+ }
+ }
+ 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;
+}
+
+sub _handle_lines {
+ my ($lines) = @_;
+ my $value = join('', @$lines);
+ $value =~ s/\s+$//;
+ return $value;
+}
+
+####################
+# Translating Bugs #
+####################
+
+sub translate_bug {
+ my ($self, $fields) = @_;
+
+ my ($bug, $other_fields) = $self->SUPER::translate_bug($fields);
+
+ $bug->{attachments} = delete $other_fields->{attachments};
+
+ 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;
+
+ 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";
+ }
+ }
+ $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 });
+}
+
+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;
+From: nobody
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="$boundary"
+
+This is a multi-part message in MIME format.
+--$boundary
+Content-Type: text/plain; charset=UTF-8
+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 @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;
+}
+
+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;
+ }
+ # 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 = @_;
+ $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);
+ if (!$options->{check_only}) {
+ $self->add_user($from_value, $value);
+ }
+ }
+
+ return $value;
+}
+
+1;
diff --git a/Bugzilla/Milestone.pm b/Bugzilla/Milestone.pm
new file mode 100644
index 000000000..cb7d53da3
--- /dev/null
+++ b/Bugzilla/Milestone.pm
@@ -0,0 +1,375 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s): Tiago R. Mello <timello@async.com.br>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+# Frédéric Buclin <LpSolit@gmail.com>
+
+use strict;
+
+package Bugzilla::Milestone;
+
+use base qw(Bugzilla::Object);
+
+use Bugzilla::Constants;
+use Bugzilla::Util;
+use Bugzilla::Error;
+
+use Scalar::Util qw(blessed);
+
+################################
+##### Initialization #####
+################################
+
+use constant DEFAULT_SORTKEY => 0;
+
+use constant DB_TABLE => 'milestones';
+use constant NAME_FIELD => 'value';
+use constant LIST_ORDER => 'sortkey, value';
+
+use constant DB_COLUMNS => qw(
+ id
+ value
+ product_id
+ sortkey
+);
+
+use constant REQUIRED_FIELD_MAP => {
+ product_id => 'product',
+};
+
+use constant UPDATE_COLUMNS => qw(
+ value
+ sortkey
+);
+
+use constant VALIDATORS => {
+ product => \&_check_product,
+ sortkey => \&_check_sortkey,
+ value => \&_check_value,
+};
+
+use constant VALIDATOR_DEPENDENCIES => {
+ value => ['product'],
+};
+
+################################
+
+sub new {
+ my $class = shift;
+ my $param = shift;
+ my $dbh = Bugzilla->dbh;
+
+ my $product;
+ if (ref $param) {
+ $product = $param->{product};
+ my $name = $param->{name};
+ if (!defined $product) {
+ ThrowCodeError('bad_arg',
+ {argument => 'product',
+ function => "${class}::new"});
+ }
+ if (!defined $name) {
+ ThrowCodeError('bad_arg',
+ {argument => 'name',
+ function => "${class}::new"});
+ }
+
+ my $condition = 'product_id = ? AND value = ?';
+ my @values = ($product->id, $name);
+ $param = { condition => $condition, values => \@values };
+ }
+
+ unshift @_, $param;
+ return $class->SUPER::new(@_);
+}
+
+sub run_create_validators {
+ my $class = shift;
+ my $params = $class->SUPER::run_create_validators(@_);
+ my $product = delete $params->{product};
+ $params->{product_id} = $product->id;
+ return $params;
+}
+
+sub update {
+ my $self = shift;
+ my $changes = $self->SUPER::update(@_);
+
+ if (exists $changes->{value}) {
+ my $dbh = Bugzilla->dbh;
+ # The milestone value is stored in the bugs table instead of its ID.
+ $dbh->do('UPDATE bugs SET target_milestone = ?
+ WHERE target_milestone = ? AND product_id = ?',
+ undef, ($self->name, $changes->{value}->[0], $self->product_id));
+
+ # The default milestone also stores the value instead of the ID.
+ $dbh->do('UPDATE products SET defaultmilestone = ?
+ WHERE id = ? AND defaultmilestone = ?',
+ undef, ($self->name, $self->product_id, $changes->{value}->[0]));
+ }
+ return $changes;
+}
+
+sub remove_from_db {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ # The default milestone cannot be deleted.
+ if ($self->name eq $self->product->default_milestone) {
+ ThrowUserError('milestone_is_default', { milestone => $self });
+ }
+
+ if ($self->bug_count) {
+ # We don't want to delete bugs when deleting a milestone.
+ # Bugs concerned are reassigned to the default milestone.
+ my $bug_ids =
+ $dbh->selectcol_arrayref('SELECT bug_id FROM bugs
+ WHERE product_id = ? AND target_milestone = ?',
+ undef, ($self->product->id, $self->name));
+
+ my $timestamp = $dbh->selectrow_array('SELECT NOW()');
+
+ $dbh->do('UPDATE bugs SET target_milestone = ?, delta_ts = ?
+ WHERE ' . $dbh->sql_in('bug_id', $bug_ids),
+ undef, ($self->product->default_milestone, $timestamp));
+
+ require Bugzilla::Bug;
+ import Bugzilla::Bug qw(LogActivityEntry);
+ foreach my $bug_id (@$bug_ids) {
+ LogActivityEntry($bug_id, 'target_milestone',
+ $self->name,
+ $self->product->default_milestone,
+ Bugzilla->user->id, $timestamp);
+ }
+ }
+
+ $dbh->do('DELETE FROM milestones WHERE id = ?', undef, $self->id);
+}
+
+################################
+# Validators
+################################
+
+sub _check_value {
+ my ($invocant, $name, 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;
+}
+
+sub _check_product {
+ 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 bug_count {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ if (!defined $self->{'bug_count'}) {
+ $self->{'bug_count'} = $dbh->selectrow_array(q{
+ SELECT COUNT(*) FROM bugs
+ WHERE product_id = ? AND target_milestone = ?},
+ undef, $self->product_id, $self->name) || 0;
+ }
+ return $self->{'bug_count'};
+}
+
+################################
+##### Accessors ######
+################################
+
+sub name { return $_[0]->{'value'}; }
+sub product_id { return $_[0]->{'product_id'}; }
+sub sortkey { return $_[0]->{'sortkey'}; }
+
+sub product {
+ my $self = shift;
+
+ require Bugzilla::Product;
+ $self->{'product'} ||= new Bugzilla::Product($self->product_id);
+ return $self->{'product'};
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Milestone - Bugzilla product milestone class.
+
+=head1 SYNOPSIS
+
+ use Bugzilla::Milestone;
+
+ my $milestone = new Bugzilla::Milestone({ name => $name, product => $product });
+
+ my $name = $milestone->name;
+ my $product_id = $milestone->product_id;
+ my $product = $milestone->product;
+ my $sortkey = $milestone->sortkey;
+
+ my $milestone = Bugzilla::Milestone->create(
+ { value => $name, product => $product, sortkey => $sortkey });
+
+ $milestone->set_name($new_name);
+ $milestone->set_sortkey($new_sortkey);
+ $milestone->update();
+
+ $milestone->remove_from_db;
+
+=head1 DESCRIPTION
+
+Milestone.pm represents a Product Milestone object.
+
+=head1 METHODS
+
+=over
+
+=item C<new({name => $name, product => $product})>
+
+ Description: The constructor is used to load an existing milestone
+ by passing a product object and a milestone name.
+
+ Params: $product - a Bugzilla::Product object.
+ $name - the name of a milestone (string).
+
+ Returns: A Bugzilla::Milestone object.
+
+=item C<name()>
+
+ Description: Name (value) of the milestone.
+
+ Params: none.
+
+ Returns: The name of the milestone.
+
+=item C<product_id()>
+
+ Description: ID of the product the milestone belongs to.
+
+ Params: none.
+
+ Returns: The ID of a product.
+
+=item C<product()>
+
+ Description: The product object of the product the milestone belongs to.
+
+ Params: none.
+
+ Returns: A Bugzilla::Product object.
+
+=item C<sortkey()>
+
+ Description: Sortkey of the milestone.
+
+ Params: none.
+
+ Returns: The sortkey of the milestone.
+
+=item C<bug_count()>
+
+ Description: Returns the total of bugs that belong to the milestone.
+
+ Params: none.
+
+ Returns: Integer with the number of bugs.
+
+=item C<set_name($new_name)>
+
+ Description: Changes the name of the milestone.
+
+ Params: $new_name - new name of the milestone (string). This name
+ must be unique within the product.
+
+ Returns: Nothing.
+
+=item C<set_sortkey($new_sortkey)>
+
+ Description: Changes the sortkey of the milestone.
+
+ Params: $new_sortkey - new sortkey of the milestone (signed integer).
+
+ Returns: Nothing.
+
+=item C<update()>
+
+ Description: Writes the new name and/or the new sortkey into the DB.
+
+ Params: none.
+
+ Returns: A hashref with changes made to the milestone object.
+
+=item C<remove_from_db()>
+
+ Description: Deletes the current milestone from the DB. The object itself
+ is not destroyed.
+
+ Params: none.
+
+ Returns: Nothing.
+
+=back
+
+=head1 CLASS METHODS
+
+=over
+
+=item C<create({value => $value, product => $product, sortkey => $sortkey})>
+
+ Description: Create a new milestone for the given product.
+
+ Params: $value - name of the new milestone (string). This name
+ must be unique within the product.
+ $product - a Bugzilla::Product object.
+ $sortkey - the sortkey of the new milestone (signed integer)
+
+ Returns: A Bugzilla::Milestone object.
+
+=back
diff --git a/Bugzilla/Object.pm b/Bugzilla/Object.pm
new file mode 100644
index 000000000..5b8576512
--- /dev/null
+++ b/Bugzilla/Object.pm
@@ -0,0 +1,1197 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved.
+# Portions created by Everything Solved are Copyright (C) 2006
+# Everything Solved. All Rights Reserved.
+#
+# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+# Frédéric Buclin <LpSolit@gmail.com>
+
+use strict;
+
+package Bugzilla::Object;
+
+use Bugzilla::Constants;
+use Bugzilla::Hook;
+use Bugzilla::Util;
+use Bugzilla::Error;
+
+use Date::Parse;
+use List::MoreUtils qw(part);
+
+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 VALIDATOR_DEPENDENCIES => {};
+# XXX At some point, this will be joined with FIELD_MAP.
+use constant REQUIRED_FIELD_MAP => {};
+use constant EXTRA_REQUIRED_FIELDS => ();
+
+# 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] } }; }
+
+###############################
+#### Initialization ####
+###############################
+
+sub new {
+ my $invocant = shift;
+ my $class = ref($invocant) || $invocant;
+ my $object = $class->_init(@_);
+ bless($object, $class) if $object;
+ return $object;
+}
+
+
+# Note: Because this uses sql_istrcmp, if you make a new object use
+# Bugzilla::Object, make sure that you modify bz_setup_database
+# in Bugzilla::DB::Pg appropriately, to add the right LOWER
+# index. You can see examples already there.
+sub _init {
+ my $class = shift;
+ my ($param) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $columns = join(',', $class->_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;
+
+ if (defined $id) {
+ # We special-case if somebody specifies an ID, so that we can
+ # validate it as numeric.
+ detaint_natural($id)
+ || ThrowCodeError('param_must_be_numeric',
+ {function => $class . '::_init'});
+
+ # Too large integers make PostgreSQL crash.
+ return if $id > MAX_INT_32;
+
+ $object = $dbh->selectrow_hashref(qq{
+ SELECT $columns FROM $table
+ WHERE $id_field = ?}, undef, $id);
+ } else {
+ unless (defined $param->{name} || (defined $param->{'condition'}
+ && defined $param->{'values'}))
+ {
+ ThrowCodeError('bad_arg', { argument => 'param',
+ function => $class . '::new' });
+ }
+
+ my ($condition, @values);
+ if (defined $param->{name}) {
+ $condition = $dbh->sql_istrcmp($name_field, '?');
+ push(@values, $param->{name});
+ }
+ elsif (defined $param->{'condition'} && defined $param->{'values'}) {
+ caller->isa('Bugzilla::Object')
+ || ThrowCodeError('protection_violation',
+ { caller => caller,
+ function => $class . '::new',
+ argument => 'condition/values' });
+ $condition = $param->{'condition'};
+ push(@values, @{$param->{'values'}});
+ }
+
+ map { trick_taint($_) } @values;
+ $object = $dbh->selectrow_hashref(
+ "SELECT $columns FROM $table WHERE $condition", undef, @values);
+ }
+
+ return $object;
+}
+
+sub check {
+ my ($invocant, $param) = @_;
+ my $class = ref($invocant) || $invocant;
+ # If we were just passed a name, then just use the name.
+ if (!ref $param) {
+ $param = { name => $param };
+ }
+
+ # Don't allow empty names 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 });
+ }
+ else {
+ ThrowUserError('object_does_not_exist', { %$param, class => $class });
+ }
+ }
+ return $obj;
+}
+
+sub new_from_list {
+ my $invocant = shift;
+ my $class = ref($invocant) || $invocant;
+ my ($id_list) = @_;
+ my $id_field = $class->ID_FIELD;
+
+ my @detainted_ids;
+ foreach my $id (@$id_list) {
+ detaint_natural($id) ||
+ ThrowCodeError('param_must_be_numeric',
+ {function => $class . '::new_from_list'});
+ # 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 });
+}
+
+# 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;
+ }
+
+ $class->_check_field($field, 'match');
+
+ if (ref $value eq 'ARRAY') {
+ # IN () is invalid SQL, and if we have an empty list
+ # to match against, we're just returning an empty
+ # array anyhow.
+ return [] if !scalar @$value;
+
+ my @qmarks = ("?") x @$value;
+ push(@terms, $dbh->sql_in($field, \@qmarks));
+ push(@values, @$value);
+ }
+ elsif ($value eq NOT_NULL) {
+ push(@terms, "$field IS NOT NULL");
+ }
+ elsif ($value eq IS_NULL) {
+ push(@terms, "$field IS NULL");
+ }
+ else {
+ push(@terms, "$field = ?");
+ push(@values, $value);
+ }
+ }
+
+ my $where = join(' AND ', @terms) 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;
+
+ 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;
+ my $objects = $dbh->selectall_arrayref($sql, {Slice=>{}}, @untainted);
+ bless ($_, $class) foreach @$objects;
+ return $objects
+}
+
+###############################
+#### Accessors ######
+###############################
+
+sub id { return $_[0]->{$_[0]->ID_FIELD}; }
+sub name { return $_[0]->{$_[0]->NAME_FIELD}; }
+
+###############################
+#### Methods ####
+###############################
+
+sub set {
+ my ($self, $field, $value) = @_;
+
+ # This method is protected. It's used to help implement set_ functions.
+ 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);
+ }
+ }
+
+ $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";
+ $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;
+ }
+
+ 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 });
+
+ $dbh->bz_commit_transaction();
+
+ 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;
+ Bugzilla->dbh->do("DELETE FROM $table WHERE $id_field = ?",
+ undef, $self->id);
+ undef $self;
+}
+
+###############################
+#### Subroutines ######
+###############################
+
+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;
+}
+
+sub create {
+ my ($class, $params) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ $dbh->bz_start_transaction();
+ $class->check_required_create_fields($params);
+ my $field_values = $class->run_create_validators($params);
+ my $object = $class->insert_create_data($field_values);
+ $dbh->bz_commit_transaction();
+
+ return $object;
+}
+
+# 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" });
+ }
+}
+
+sub check_required_create_fields {
+ 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 });
+
+ 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) = @_;
+
+ my $validators = $class->_get_validators;
+ my %field_values = %$params;
+
+ my @sorted_names = $class->_sort_by_dep(keys %field_values);
+ foreach my $field (@sorted_names) {
+ 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;
+ }
+
+ 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);
+ }
+
+ 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 });
+ return $object;
+}
+
+sub get_all {
+ my $class = shift;
+ return @{$class->_do_list_select()};
+}
+
+###############################
+#### Validators ######
+###############################
+
+sub check_boolean { return $_[1] ? 1 : 0 }
+
+###################
+# General Helpers #
+###################
+
+# Sorts fields according to VALIDATOR_DEPENDENCIES. This is not a
+# traditional topological sort, because a "dependency" does not
+# *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);
+ }
+ }
+ return @result;
+}
+
+sub _insert_dep_field {
+ 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);
+}
+
+####################
+# Constant Helpers #
+####################
+
+# For some classes, some constants take time to generate, so we cache them
+# and only access them through the below methods. This also allows certain
+# hooks to only run once per request instead of multiple times on each
+# 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} };
+}
+
+# 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};
+}
+
+# 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);
+ }
+ }
+ push(@required, $class->EXTRA_REQUIRED_FIELDS);
+ return @required;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Object - A base class for objects in Bugzilla.
+
+=head1 SYNOPSIS
+
+ my $object = new Bugzilla::Object(1);
+ my $object = new Bugzilla::Object({name => 'TestProduct'});
+
+ my $id = $object->id;
+ my $name = $object->name;
+
+=head1 DESCRIPTION
+
+Bugzilla::Object is a base class for Bugzilla objects. You never actually
+create a Bugzilla::Object directly, you only make subclasses of it.
+
+Basically, Bugzilla::Object exists to allow developers to create objects
+more easily. All you have to do is define C<DB_TABLE>, C<DB_COLUMNS>,
+and sometimes C<LIST_ORDER> and you have a whole new object.
+
+You should also define accessors for any columns other than C<name>
+or C<id>.
+
+=head1 CONSTANTS
+
+Frequently, these will be the only things you have to define in your
+subclass in order to have a fully-functioning object. C<DB_TABLE>
+and C<DB_COLUMNS> are required.
+
+=over
+
+=item C<DB_TABLE>
+
+The name of the table that these objects are stored in. For example,
+for C<Bugzilla::Keyword> this would be C<keyworddefs>.
+
+=item C<DB_COLUMNS>
+
+The names of the columns that you want to read out of the database
+and into this object. This should be an array.
+
+I<Note>: Though normally you will never need to access this constant's data
+directly in your subclass, if you do, you should access it by calling the
+C<_get_db_columns> method instead of accessing the constant directly. (The
+only exception to this rule is calling C<SUPER::DB_COLUMNS> from within
+your own C<DB_COLUMNS> subroutine in a subclass.)
+
+=item C<NAME_FIELD>
+
+The name of the column that should be considered to be the unique
+"name" of this object. The 'name' is a B<string> that uniquely identifies
+this Object in the database. Defaults to 'name'. When you specify
+C<{name => $name}> to C<new()>, this is the column that will be
+matched against in the DB.
+
+=item C<ID_FIELD>
+
+The name of the column that represents the unique B<integer> ID
+of this object in the database. Defaults to 'id'.
+
+=item C<LIST_ORDER>
+
+The order that C<new_from_list> and C<get_all> should return objects
+in. This should be the name of a database column. Defaults to
+L</NAME_FIELD>.
+
+=item C<VALIDATORS>
+
+A hashref that points to a function that will validate each param to
+L</create>.
+
+Validators are called both by L</create> and L</set>. When
+they are called by L</create>, the first argument will be the name
+of the class (what we normally call C<$class>).
+
+When they are called by L</set>, the first argument will be
+a reference to the current object (what we normally call C<$self>).
+
+The second argument will be the value passed to L</create> or
+L</set>for that field.
+
+The third argument will be the name of the field being validated.
+This may be required by validators which validate several distinct fields.
+
+These functions should call L<Bugzilla::Error/ThrowUserError> if they fail.
+
+The validator must return the validated value.
+
+=item C<UPDATE_VALIDATORS>
+
+This is just like L</VALIDATORS>, but these validators are called only
+when updating an object, not when creating it. Any validator that appears
+here must not appear in L</VALIDATORS>.
+
+L<Bugzilla::Bug> has good examples in its code of when to use this.
+
+=item C<VALIDATOR_DEPENDENCIES>
+
+During L</create> and L</set_all>, validators are normally called in
+a somewhat-random order. If you need one field to be validated and set
+before another field, this constant is how you do it, by saying that
+one field "depends" on the value of other fields.
+
+This is a hashref, where the keys are field names and the values are
+arrayrefs of field names. You specify what fields a field depends on using
+the arrayrefs. So, for example, to say that a C<component> field depends
+on the C<product> field being set, you would do:
+
+ component => ['product']
+
+=item C<UPDATE_COLUMNS>
+
+A list of columns to update when L</update> is called.
+If a field can't be changed, it shouldn't be listed here. (For example,
+the L</ID_FIELD> usually can't be updated.)
+
+=item C<REQUIRED_FIELD_MAP>
+
+This is a hashref that maps database column names to L</create> argument
+names. You only need to specify values for fields where the argument passed
+to L</create> has a different name in the database than it does in the
+L</create> arguments. (For example, L<Bugzilla::Bug/create> takes a
+C<product> argument, but the column name in the C<bugs> table is
+C<product_id>.)
+
+=item C<EXTRA_REQUIRED_FIELDS>
+
+Normally, Bugzilla::Object automatically figures out which fields
+are required for L</create>. It then I<always> runs those fields' validators,
+even if those fields weren't passed as arguments to L</create>. That way,
+any default values or required checks can be done for those fields by
+the validators.
+
+L</create> figures out which fields are required by looking for database
+columns in the L</DB_TABLE> that are NOT NULL and have no DEFAULT set.
+However, there are some fields that this check doesn't work for:
+
+=over
+
+=item *
+
+Fields that have database defaults (or are marked NULL in the database)
+but actually have different defaults specified by validators. (For example,
+the qa_contact field in the C<bugs> table can be NULL, so it won't be
+caught as being required. However, in reality it defaults to the
+component's initial_qa_contact.)
+
+=item *
+
+Fields that have defaults that should be set by validators, but are
+actually stored in a table different from L</DB_TABLE> (like the "cc"
+field for bugs, which defaults to the "initialcc" of the Component, but won't
+be caught as a normal required field because it's in a separate table.)
+
+=back
+
+Any field matching the above criteria needs to have its name listed in
+this constant. For an example of use, see the code of L<Bugzilla::Bug>.
+
+=item C<NUMERIC_COLUMNS>
+
+When L</update> is called, it compares each column in the object to its
+current value in the database. It only updates columns that have changed.
+
+Any column listed in NUMERIC_COLUMNS is treated as a number, not as a string,
+during these comparisons.
+
+=item C<DATE_COLUMNS>
+
+This is much like L</NUMERIC_COLUMNS>, except that it treats strings as
+dates when being compared. So, for example, C<2007-01-01> would be
+equal to C<2007-01-01 00:00:00>.
+
+=back
+
+=head1 METHODS
+
+=head2 Constructors
+
+=over
+
+=item C<new>
+
+=over
+
+=item B<Description>
+
+The constructor is used to load an existing object from the database,
+by id or by name.
+
+=item B<Params>
+
+If you pass an integer, the integer is the id of the object,
+from the database, that we want to read in. (id is defined
+as the value in the L</ID_FIELD> column).
+
+If you pass in a hashref, you can pass a C<name> key. The
+value of the C<name> key is the case-insensitive name of the object
+(from L</NAME_FIELD>) in the DB. You can also pass in an C<id> key
+which will be interpreted as the id of the object you want (overriding the
+C<name> key).
+
+B<Additional Parameters Available for Subclasses>
+
+If you are a subclass of C<Bugzilla::Object>, you can pass
+C<condition> and C<values> as hash keys, instead of the above.
+
+C<condition> is a set of SQL conditions for the WHERE clause, which contain
+placeholders.
+
+C<values> is a reference to an array. The array contains the values
+for each placeholder in C<condition>, in order.
+
+This is to allow subclasses to have complex parameters, and then to
+translate those parameters into C<condition> and C<values> when they
+call C<$self->SUPER::new> (which is this function, usually).
+
+If you try to call C<new> outside of a subclass with the C<condition>
+and C<values> parameters, Bugzilla will throw an error. These parameters
+are intended B<only> for use by subclasses.
+
+=item B<Returns>
+
+A fully-initialized object, or C<undef> if there is no object in the
+database matching the parameters you passed in.
+
+=back
+
+=item C<check>
+
+=over
+
+=item B<Description>
+
+Checks if there is an object in the database with the specified name, and
+throws an error if you specified an empty name, or if there is no object
+in the database with that name.
+
+=item B<Params>
+
+The parameters are the same as for L</new>, except that if you don't pass
+a hashref, the single argument is the I<name> of the object, not the id.
+
+=item B<Returns>
+
+A fully initialized object, guaranteed.
+
+=item B<Notes For Implementors>
+
+If you implement this in your subclass, make sure that you also update
+the C<object_name> block at the bottom of the F<global/user-error.html.tmpl>
+template.
+
+=back
+
+=item C<new_from_list(\@id_list)>
+
+ Description: Creates an array of objects, given an array of ids.
+
+ Params: \@id_list - A reference to an array of numbers, database ids.
+ If any of these are not numeric, the function
+ will throw an error. If any of these are not
+ valid ids in the database, they will simply
+ be skipped.
+
+ Returns: A reference to an array of objects.
+
+=item C<match>
+
+=over
+
+=item B<Description>
+
+Gets a list of objects from the database based on certain criteria.
+
+Basically, a simple way of doing a sort of "SELECT" statement (like SQL)
+to get objects.
+
+All criteria are joined by C<AND>, so adding more criteria will give you
+a smaller set of results, not a larger set.
+
+=item B<Params>
+
+A hashref, where the keys are column names of the table, pointing to the
+value that you want to match against for that column.
+
+There are two special values, the constants C<NULL> and C<NOT_NULL>,
+which means "give me objects where this field is NULL or NOT NULL,
+respectively."
+
+In addition to the column keys, there are a few special keys that
+can be used to rig the underlying database queries. These are
+C<LIMIT>, C<OFFSET>, and C<WHERE>.
+
+The value for the C<LIMIT> key is expected to be an integer defining
+the number of objects to return, while the value for C<OFFSET> defines
+the position, relative to the number of objects the query would normally
+return, at which to begin the result set. If C<OFFSET> is defined without
+a corresponding C<LIMIT> it is silently ignored.
+
+The C<WHERE> key provides a mechanism for adding arbitrary WHERE
+clauses to the underlying query. Its value is expected to a hash
+reference whose keys are the columns, operators and placeholders, and the
+values are the placeholders' bind value. For example:
+
+ WHERE => { 'some_column >= ?' => $some_value }
+
+would constrain the query to only those objects in the table whose
+'some_column' column has a value greater than or equal to $some_value.
+
+If you don't specify any criteria, calling this function is the same
+as doing C<[$class-E<gt>get_all]>.
+
+=item B<Returns>
+
+An arrayref of objects, or an empty arrayref if there are no matches.
+
+=back
+
+=back
+
+=head2 Database Manipulation
+
+=over
+
+=item C<create>
+
+Description: Creates a new item in the database.
+ Throws a User Error if any of the passed-in params
+ are invalid.
+
+Params: C<$params> - hashref - A value to put in each database
+ field for this object.
+
+Returns: The Object just created in the database.
+
+Notes: In order for this function to work in your subclass,
+ your subclass's L</ID_FIELD> must be of C<SERIAL>
+ type in the database.
+
+ Subclass Implementors: This function basically just
+ calls L</check_required_create_fields>, then
+ L</run_create_validators>, and then finally
+ L</insert_create_data>. So if you have a complex system that
+ you need to implement, you can do it by calling these
+ three functions instead of C<SUPER::create>.
+
+=item C<check_required_create_fields>
+
+=over
+
+=item B<Description>
+
+Part of L</create>. Modifies the incoming C<$params> argument so that
+any field that does not have a database default will be checked
+later by L</run_create_validators>, even if that field wasn't specified
+as an argument to L</create>.
+
+=item B<Params>
+
+=over
+
+=item C<$params> - The same as C<$params> from L</create>.
+
+=back
+
+=item B<Returns> (nothing)
+
+=back
+
+=item C<run_create_validators>
+
+Description: Runs the validation of input parameters for L</create>.
+ This subroutine exists so that it can be overridden
+ by subclasses who need to do special validations
+ of their input parameters. This method is B<only> called
+ by L</create>.
+
+Params: The same as L</create>.
+
+Returns: A hash, in a similar format as C<$params>, except that
+ these are the values to be inserted into the database,
+ not the values that were input to L</create>.
+
+=item C<insert_create_data>
+
+Part of L</create>.
+
+Takes the return value from L</run_create_validators> and inserts the
+data into the database. Returns a newly created object.
+
+=item C<update>
+
+=over
+
+=item B<Description>
+
+Saves the values currently in this object to the database.
+Only the fields specified in L</UPDATE_COLUMNS> will be
+updated, and they will only be updated if their values have changed.
+
+=item B<Params> (none)
+
+=item B<Returns>
+
+B<In scalar context:>
+
+A hashref showing what changed during the update. The keys are the column
+names from L</UPDATE_COLUMNS>. If a field was not changed, it will not be
+in the hash at all. If the field was changed, the key will point to an arrayref.
+The first item of the arrayref will be the old value, and the second item
+will be the new value.
+
+If there were no changes, we return a reference to an empty hash.
+
+B<In array context:>
+
+Returns a list, where the first item is the above hashref. The second item
+is the object as it was in the database before update() was called. (This
+is mostly useful to subclasses of C<Bugzilla::Object> that are implementing
+C<update>.)
+
+=back
+
+=item C<remove_from_db>
+
+Removes this object from the database. Will throw an error if you can't
+remove it for some reason. The object will then be destroyed, as it is
+not safe to use the object after it has been removed from the database.
+
+=back
+
+=head2 Mutators
+
+These are used for updating the values in objects, before calling
+C<update>.
+
+=over
+
+=item C<set>
+
+=over
+
+=item B<Description>
+
+Sets a certain hash member of this class to a certain value.
+Used for updating fields. Calls the validator for this field,
+if it exists. Subclasses should use this function
+to implement the various C<set_> mutators for their different
+fields.
+
+If your class defines a method called C<_set_global_validator>,
+C<set> will call it with C<($value, $field)> as arguments, after running
+the validator for this particular field. C<_set_global_validator> does not
+return anything.
+
+See L</VALIDATORS> for more information.
+
+B<NOTE>: This function is intended only for use by subclasses. If
+you call it from anywhere else, it will throw a C<CodeError>.
+
+=item B<Params>
+
+=over
+
+=item C<$field> - The name of the hash member to update. This should
+be the same as the name of the field in L</VALIDATORS>, if it exists there.
+
+=item C<$value> - The value that you're setting the field to.
+
+=back
+
+=item B<Returns> (nothing)
+
+=back
+
+
+=item C<set_all>
+
+=over
+
+=item B<Description>
+
+This is a convenience function which is simpler than calling many different
+C<set_> functions in a row. You pass a hashref of parameters and it calls
+C<set_$key($value)> for every item in the hashref.
+
+=item B<Params>
+
+Takes a hashref of the fields that need to be set, pointing to the value
+that should be passed to the C<set_> function that is called.
+
+=item B<Returns> (nothing)
+
+=back
+
+
+=back
+
+=head2 Simple Validators
+
+You can use these in your subclass L</VALIDATORS> or L</UPDATE_VALIDATORS>.
+Note that you have to reference them like C<\&Bugzilla::Object::check_boolean>,
+you can't just write C<\&check_boolean>.
+
+=over
+
+=item C<check_boolean>
+
+Returns C<1> if the passed-in value is true, C<0> otherwise.
+
+=back
+
+=head1 CLASS FUNCTIONS
+
+=over
+
+=item C<any_exist>
+
+Returns C<1> if there are any of these objects in the database,
+C<0> otherwise.
+
+=item C<get_all>
+
+ Description: Returns all objects in this table from the database.
+
+ Params: none.
+
+ Returns: A list of objects, or an empty list if there are none.
+
+ Notes: Note that you must call this as C<$class->get_all>. For
+ example, C<Bugzilla::Keyword->get_all>.
+ C<Bugzilla::Keyword::get_all> will not work.
+
+=back
+
+=cut
diff --git a/Bugzilla/Product.pm b/Bugzilla/Product.pm
new file mode 100644
index 000000000..b9443e9e6
--- /dev/null
+++ b/Bugzilla/Product.pm
@@ -0,0 +1,1076 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s): Tiago R. Mello <timello@async.com.br>
+# Frédéric Buclin <LpSolit@gmail.com>
+
+package Bugzilla::Product;
+use strict;
+use base qw(Bugzilla::Field::ChoiceInterface Bugzilla::Object);
+
+use Bugzilla::Constants;
+use Bugzilla::Util;
+use Bugzilla::Error;
+use Bugzilla::Group;
+use Bugzilla::Version;
+use Bugzilla::Milestone;
+use Bugzilla::Field;
+use Bugzilla::Status;
+use Bugzilla::Install::Requirements;
+use Bugzilla::Mailer;
+use Bugzilla::Series;
+use Bugzilla::Hook;
+use Bugzilla::FlagType;
+
+use Scalar::Util qw(blessed);
+
+use constant DEFAULT_CLASSIFICATION_ID => 1;
+
+###############################
+#### Initialization ####
+###############################
+
+use constant DB_TABLE => 'products';
+
+use constant DB_COLUMNS => qw(
+ id
+ name
+ classification_id
+ description
+ isactive
+ defaultmilestone
+ allows_unconfirmed
+);
+
+use constant UPDATE_COLUMNS => qw(
+ 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
+};
+
+###############################
+#### Constructors #####
+###############################
+
+sub create {
+ my $class = shift;
+ my $dbh = Bugzilla->dbh;
+
+ $dbh->bz_start_transaction();
+
+ $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 $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 });
+
+ # 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 });
+
+ $dbh->bz_commit_transaction();
+ 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;
+
+ my $dbh = Bugzilla->dbh;
+ foreach my $field (qw(component version milestone)) {
+ my $classname = "Bugzilla::" . ucfirst($field);
+ my $objects = $classname->match({ product_id => \@prod_ids });
+
+ # Now populate the products with this set of objects.
+ foreach my $obj (@$objects) {
+ my $product_id = $obj->product_id;
+ $prods{$product_id}->{"${field}s"} ||= [];
+ push(@{$prods{$product_id}->{"${field}s"}}, $obj);
+ }
+ }
+ 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
+ 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
+ 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});
+ }
+ }
+ }
+
+ 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();
+
+ 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 he can't see,
+ # which is okay, because he's 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
+ FROM series
+ INNER JOIN series_categories
+ ON series_categories.id = series.category
+ 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 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);
+ }
+ }
+
+ $dbh->do("DELETE FROM products WHERE id = ?", undef, $self->id);
+
+ $dbh->bz_commit_transaction();
+
+ # 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};
+
+}
+
+###############################
+#### Validators ####
+###############################
+
+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;
+}
+
+sub _check_name {
+ my ($invocant, $name) = @_;
+
+ $name = trim($name);
+ $name || ThrowUserError('product_blank_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});
+ }
+ }
+ return $name;
+}
+
+sub _check_description {
+ my ($invocant, $description) = @_;
+
+ $description = trim($description);
+ $description || ThrowUserError('product_must_have_description');
+ return $description;
+}
+
+sub _check_version {
+ my ($invocant, $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;
+}
+
+sub _check_default_milestone {
+ my ($invocant, $milestone) = @_;
+
+ # Do nothing if target milestones are not in use.
+ unless (Bugzilla->params->{'usetargetmilestone'}) {
+ return (ref $invocant) ? $invocant->default_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});
+
+ $mil_obj || ThrowUserError('product_must_define_defaultmilestone',
+ {product => $invocant->name,
+ milestone => $milestone});
+ }
+ else {
+ $milestone ||= '---';
+ }
+ return $milestone;
+}
+
+sub _check_milestone_url {
+ my ($invocant, $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;
+}
+
+#####################################
+# Implement Bugzilla::Field::Choice #
+#####################################
+
+use constant FIELD_NAME => 'product';
+use constant is_default => 0;
+
+###############################
+#### Methods ####
+###############################
+
+sub _create_bug_group {
+ 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 = 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
+ (group_id, product_id, membercontrol, othercontrol)
+ 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();
+ }
+}
+
+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};
+ }
+
+ 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;
+
+ if (!defined $self->{components}) {
+ my $ids = $dbh->selectcol_arrayref(q{
+ SELECT id FROM components
+ WHERE product_id = ?
+ ORDER BY name}, undef, $self->id);
+
+ require Bugzilla::Component;
+ $self->{components} = Bugzilla::Component->new_from_list($ids);
+ }
+ return $self->{components};
+}
+
+sub group_controls {
+ my ($self, $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
+ 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};
+ }
+ }
+ 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
+ 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
+ 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};
+ }
+ }
+
+ 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};
+}
+
+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
+ 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};
+}
+
+# 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 $group_id = blessed($group) ? $group->id : $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;
+}
+
+sub group_is_valid {
+ 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
+ FROM group_control_map AS gcm
+ INNER JOIN groups ON gcm.group_id = groups.id
+ WHERE product_id = ? AND isbuggroup = 1
+ AND membercontrol != " . CONTROLMAPNA, undef, $self->id);
+ $self->{groups_valid} = Bugzilla::Group->new_from_list($ids);
+ return $self->{groups_valid};
+}
+
+sub versions {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ if (!defined $self->{versions}) {
+ my $ids = $dbh->selectcol_arrayref(q{
+ SELECT id FROM versions
+ WHERE product_id = ?}, undef, $self->id);
+
+ $self->{versions} = Bugzilla::Version->new_from_list($ids);
+ }
+ return $self->{versions};
+}
+
+sub milestones {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ if (!defined $self->{milestones}) {
+ my $ids = $dbh->selectcol_arrayref(q{
+ SELECT id FROM milestones
+ WHERE product_id = ?}, undef, $self->id);
+
+ $self->{milestones} = Bugzilla::Milestone->new_from_list($ids);
+ }
+ return $self->{milestones};
+}
+
+sub bug_count {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ if (!defined $self->{'bug_count'}) {
+ $self->{'bug_count'} = $dbh->selectrow_array(qq{
+ SELECT COUNT(bug_id) FROM bugs
+ WHERE product_id = ?}, undef, $self->id);
+
+ }
+ return $self->{'bug_count'};
+}
+
+sub bug_ids {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ if (!defined $self->{'bug_ids'}) {
+ $self->{'bug_ids'} =
+ $dbh->selectcol_arrayref(q{SELECT bug_id FROM bugs
+ WHERE product_id = ?},
+ undef, $self->id);
+ }
+ return $self->{'bug_ids'};
+}
+
+sub user_has_access {
+ my ($self, $user) = @_;
+
+ return Bugzilla->dbh->selectrow_array(
+ 'SELECT CASE WHEN group_id IS NULL THEN 1 ELSE 0 END
+ FROM products LEFT JOIN group_control_map
+ ON group_control_map.product_id = products.id
+ AND group_control_map.entry != 0
+ AND group_id NOT IN (' . $user->groups_as_string . ')
+ WHERE products.id = ? ' . Bugzilla->dbh->sql_limit(1),
+ undef, $self->id);
+}
+
+sub flag_types {
+ my $self = shift;
+
+ 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'};
+}
+
+sub classification {
+ my $self = shift;
+ $self->{'classification'} ||=
+ new Bugzilla::Classification($self->classification_id);
+ return $self->{'classification'};
+}
+
+###############################
+#### Accessors ######
+###############################
+
+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'}; }
+
+###############################
+#### 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;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Product - Bugzilla product class.
+
+=head1 SYNOPSIS
+
+ use Bugzilla::Product;
+
+ my $product = new Bugzilla::Product(1);
+ my $product = new Bugzilla::Product({ name => 'AcmeProduct' });
+
+ my @components = $product->components();
+ my $groups_controls = $product->group_controls();
+ my @milestones = $product->milestones();
+ my @versions = $product->versions();
+ my $bugcount = $product->bug_count();
+ my $bug_ids = $product->bug_ids();
+ my $has_access = $product->user_has_access($user);
+ my $flag_types = $product->flag_types();
+ my $classification = $product->classification();
+
+ my $id = $product->id;
+ my $name = $product->name;
+ my $description = $product->description;
+ my isactive = $product->is_active;
+ my $defaultmilestone = $product->default_milestone;
+ my $classificationid = $product->classification_id;
+ my $allows_unconfirmed = $product->allows_unconfirmed;
+
+=head1 DESCRIPTION
+
+Product.pm represents a product object. It is an implementation
+of L<Bugzilla::Object>, and thus provides all methods that
+L<Bugzilla::Object> provides.
+
+The methods that are specific to C<Bugzilla::Product> are listed
+below.
+
+=head1 METHODS
+
+=over
+
+=item C<components>
+
+ Description: Returns an array of component objects belonging to
+ the product.
+
+ Params: none.
+
+ Returns: An array of Bugzilla::Component object.
+
+=item C<group_controls()>
+
+ Description: Returns a hash (group id as key) with all product
+ group controls.
+
+ Params: $full_data (optional, false by default) - when true,
+ the number of bugs per group applicable to the product
+ is also returned. Moreover, bug groups which have no
+ special settings for the product are also returned.
+
+ Returns: A hash with group id as key and hash containing
+ a Bugzilla::Group object and the properties of group
+ relative to the product.
+
+=item C<groups_available>
+
+Tells you what groups are set to Default or Shown for the
+currently-logged-in user (taking into account both OtherControl and
+MemberControl). Returns an arrayref of L<Bugzilla::Group> objects with
+an extra hash keys set, C<is_default>, which is true if the group
+is set to Default for the currently-logged-in user.
+
+=item C<groups_mandatory>
+
+Tells you what groups are mandatory for bugs in this product, for the
+currently-logged-in user. Returns an arrayref of C<Bugzilla::Group> objects.
+
+=item C<group_is_settable>
+
+=over
+
+=item B<Description>
+
+Tells you whether or not the currently-logged-in user can set a group
+on a bug (whether or not they match the MemberControl/OtherControl
+settings for a group in this product). Groups that are C<Mandatory> for
+the currently-loggeed-in user are also acceptable since from Bugzilla's
+perspective, there's no problem with "setting" a Mandatory group on
+a bug. (In fact, the user I<must> set the Mandatory group on the bug.)
+
+=item B<Params>
+
+=over
+
+=item C<$group> - Either a numeric group id or a L<Bugzilla::Group> object.
+
+=back
+
+=item B<Returns>
+
+C<1> if the group is valid in this product, C<0> otherwise.
+
+=back
+
+
+=item C<groups_valid>
+
+=over
+
+=item B<Description>
+
+Returns an arrayref of L<Bugzilla::Group> objects, representing groups
+that bugs could validly be restricted to within this product. Used mostly
+when you need the list of all possible groups that could be set in a product
+by anybody, disregarding whether or not the groups are active or who the
+currently logged-in user is.
+
+B<Note>: This doesn't check whether or not the current user can add/remove
+bugs to/from these groups. It just tells you that bugs I<could be in> these
+groups, in this product.
+
+=item B<Params> (none)
+
+=item B<Returns> An arrayref of L<Bugzilla::Group> objects.
+
+=back
+
+=item C<group_is_valid>
+
+Returns C<1> if the passed-in L<Bugzilla::Group> or group id could be set
+on a bug by I<anybody>, in this product. Even inactive groups are considered
+valid. (This is a shortcut for searching L</groups_valid> to find out if
+a group is valid in a particular product.)
+
+=item C<versions>
+
+ Description: Returns all valid versions for that product.
+
+ Params: none.
+
+ Returns: An array of Bugzilla::Version objects.
+
+=item C<milestones>
+
+ Description: Returns all valid milestones for that product.
+
+ Params: none.
+
+ Returns: An array of Bugzilla::Milestone objects.
+
+=item C<bug_count()>
+
+ Description: Returns the total of bugs that belong to the product.
+
+ Params: none.
+
+ Returns: Integer with the number of bugs.
+
+=item C<bug_ids()>
+
+ Description: Returns the IDs of bugs that belong to the product.
+
+ Params: none.
+
+ Returns: An array of integer.
+
+=item C<user_has_access()>
+
+ Description: Tells you whether or not the user is allowed to enter
+ bugs into this product, based on the C<entry> group
+ control. To see whether or not a user can actually
+ enter a bug into a product, use C<$user-&gt;can_enter_product>.
+
+ Params: C<$user> - A Bugzilla::User object.
+
+ Returns C<1> If this user's groups allow him C<entry> access to
+ this Product, C<0> otherwise.
+
+=item C<flag_types()>
+
+ Description: Returns flag types available for at least one of
+ its components.
+
+ Params: none.
+
+ Returns: Two references to an array of flagtype objects.
+
+=item C<classification()>
+
+ Description: Returns the classification the product belongs to.
+
+ Params: none.
+
+ Returns: A Bugzilla::Classification object.
+
+=back
+
+=head1 SUBROUTINES
+
+=over
+
+=item C<preload>
+
+When passed an arrayref of C<Bugzilla::Product> objects, preloads their
+L</milestones>, L</components>, and L</versions>, which is much faster
+than calling those accessors on every item in the array individually.
+
+If the 2nd argument passed to C<preload> is true, flag types for these
+products and their components are also preloaded.
+
+This function is not exported, so must be called like
+C<Bugzilla::Product::preload($products)>.
+
+=back
+
+=head1 SEE ALSO
+
+L<Bugzilla::Object>
+
+=cut
diff --git a/Bugzilla/Search.pm b/Bugzilla/Search.pm
new file mode 100644
index 000000000..025c11e8c
--- /dev/null
+++ b/Bugzilla/Search.pm
@@ -0,0 +1,2516 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Gervase Markham <gerv@gerv.net>
+# Terry Weissman <terry@mozilla.org>
+# Dan Mosedale <dmose@mozilla.org>
+# Stephan Niemz <st.n@gmx.net>
+# Andreas Franke <afranke@mathweb.org>
+# Myk Melez <myk@mozilla.org>
+# Michael Schindler <michael@compressconsult.com>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+# Joel Peshkin <bugreport@peshkin.net>
+# Lance Larsh <lance.larsh@oracle.com>
+# Jesse Clark <jjclark1982@gmail.com>
+# Rémi Zara <remi_zara@mac.com>
+# Reed Loden <reed@reedloden.com>
+
+use strict;
+
+package Bugzilla::Search;
+use base qw(Exporter);
+@Bugzilla::Search::EXPORT = qw(
+ EMPTY_COLUMN
+
+ IsValidQueryType
+ split_order_term
+ translate_old_column
+);
+
+use Bugzilla::Error;
+use Bugzilla::Util;
+use Bugzilla::Constants;
+use Bugzilla::Group;
+use Bugzilla::User;
+use Bugzilla::Field;
+use Bugzilla::Status;
+use Bugzilla::Keyword;
+
+use Date::Format;
+use Date::Parse;
+
+use Storable qw(dclone);
+
+# If you specify a search type in the boolean charts, this describes
+# which operator maps to which internal function here.
+use constant OPERATORS => {
+ equals => \&_equals,
+ notequals => \&_notequals,
+ casesubstring => \&_casesubstring,
+ substring => \&_substring,
+ substr => \&_substring,
+ notsubstring => \&_notsubstring,
+ regexp => \&_regexp,
+ notregexp => \&_notregexp,
+ lessthan => \&_lessthan,
+ lessthaneq => \&_lessthaneq,
+ matches => sub { ThrowUserError("search_content_without_matches"); },
+ notmatches => sub { ThrowUserError("search_content_without_matches"); },
+ greaterthan => \&_greaterthan,
+ greaterthaneq => \&_greaterthaneq,
+ 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,
+};
+
+use constant OPERATOR_FIELD_OVERRIDE => {
+
+ # User fields
+ 'attachments.submitter' => {
+ _default => \&_attachments_submitter,
+ },
+ assigned_to => {
+ _non_changed => \&_assigned_to_reporter_nonchanged,
+ },
+ cc => {
+ _non_changed => \&_cc_nonchanged,
+ },
+ commenter => {
+ _default => \&_commenter,
+ },
+ reporter => {
+ _non_changed => \&_assigned_to_reporter_nonchanged,
+ },
+ 'requestees.login_name' => {
+ _default => \&_requestees_login_name,
+ },
+ 'setters.login_name' => {
+ _default => \&_setters_login_name,
+ },
+ qa_contact => {
+ _non_changed => \&_qa_contact_nonchanged,
+ },
+
+ # General Bug Fields
+ alias => {
+ _non_changed => \&_alias_nonchanged,
+ },
+ 'attach_data.thedata' => {
+ _non_changed => \&_attach_data_thedata,
+ },
+ # We check all attachment fields against this.
+ 'attachments' => {
+ _default => \&_attachments,
+ },
+ blocked => {
+ _non_changed => \&_blocked_nonchanged,
+ },
+ bug_group => {
+ _non_changed => \&_bug_group_nonchanged,
+ },
+ changedin => {
+ _default => \&_changedin_days_elapsed,
+ },
+ 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 => \&_changedin_days_elapsed,
+ },
+ dependson => {
+ _non_changed => \&_dependson_nonchanged,
+ },
+ keywords => {
+ equals => \&_keywords_exact,
+ anyexact => \&_keywords_exact,
+ anyword => \&_keywords_exact,
+ allwords => \&_keywords_exact,
+
+ notequals => \&_multiselect_negative,
+ notregexp => \&_multiselect_negative,
+ notsubstring => \&_multiselect_negative,
+ nowords => \&_multiselect_negative,
+ nowordssubstr => \&_multiselect_negative,
+
+ _non_changed => \&_keywords_nonchanged,
+ },
+ 'flagtypes.name' => {
+ _default => \&_flagtypes_name,
+ },
+ longdesc => {
+ changedby => \&_long_desc_changedby,
+ changedbefore => \&_long_desc_changedbefore_after,
+ changedafter => \&_long_desc_changedbefore_after,
+ _default => \&_long_desc,
+ },
+ 'longdescs.isprivate' => {
+ _default => \&_longdescs_isprivate,
+ },
+ 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,
+ },
+
+ product => {
+ _non_changed => \&_product_nonchanged,
+ },
+
+ # Custom multi-select fields
+ _multi_select => {
+ notequals => \&_multiselect_negative,
+ notregexp => \&_multiselect_negative,
+ notsubstring => \&_multiselect_negative,
+ nowords => \&_multiselect_negative,
+ nowordssubstr => \&_multiselect_negative,
+
+ allwords => \&_multiselect_multiple,
+ allwordssubstr => \&_multiselect_multiple,
+ anyexact => \&_multiselect_multiple,
+
+ _non_changed => \&_multiselect_nonchanged,
+ },
+
+ # Timetracking Fields
+ percentage_complete => {
+ _default => \&_percentage_complete,
+ },
+ work_time => {
+ changedby => \&_work_time_changedby,
+ changedbefore => \&_work_time_changedbefore_after,
+ changedafter => \&_work_time_changedbefore_after,
+ _default => \&_work_time,
+ },
+
+};
+
+# These are fields where special action is taken depending on the
+# *value* passed in to the chart, sometimes.
+use constant SPECIAL_PARSING => {
+ # Pronoun Fields (Ones that can accept %user%, etc.)
+ assigned_to => \&_contact_pronoun,
+ cc => \&_cc_pronoun,
+ commenter => \&_commenter_pronoun,
+ qa_contact => \&_contact_pronoun,
+ reporter => \&_contact_pronoun,
+
+ # Date Fields that accept the 1d, 1w, 1m, 1y, etc. format.
+ creation_ts => \&_timestamp_translate,
+ deadline => \&_timestamp_translate,
+ delta_ts => \&_timestamp_translate,
+};
+
+# Backwards compatibility for times that we changed the names of fields.
+use constant FIELD_MAP => {
+ long_desc => 'longdesc',
+};
+
+# A SELECTed expression that we use as a placeholder if somebody selects
+# <none> for the X, Y, or Z axis in report.cgi.
+use constant EMPTY_COLUMN => '-1';
+
+# 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.
+# Each field points to an array that contains the fields mapped
+# to, in order.
+use constant SPECIAL_ORDER => {
+ 'target_milestone' => [ 'ms_order.sortkey','ms_order.value' ],
+};
+
+# When we add certain fields to the ORDER BY, we need to then add a
+# table join to the FROM statement. This hash maps input fields to
+# the join statements that need to be added.
+use constant SPECIAL_ORDER_JOIN => {
+ 'target_milestone' => 'LEFT JOIN milestones AS ms_order ON ms_order.value = bugs.target_milestone AND ms_order.product_id = bugs.product_id',
+};
+
+# 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:
+#
+# 1. id: a unique identifier by which the column is referred in code;
+#
+# 2. name: The name of the column in the database (may also be an expression
+# 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.
+#
+# This is really a constant--that is, once it's been called once, the value
+# will always be the same unless somebody adds a new custom field. But
+# we have to do a lot of work inside the subroutine to get the data,
+# and we don't want it to happen at compile time, so we have it as a
+# subroutine.
+sub COLUMNS {
+ my $dbh = Bugzilla->dbh;
+ my $cache = Bugzilla->request_cache;
+ return $cache->{search_columns} if defined $cache->{search_columns};
+
+ # 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' },
+ assigned_to_realname => { title => 'Assignee' },
+ reporter_realname => { title => 'Reporter' },
+ qa_contact_realname => { title => 'QA Contact' },
+ );
+
+ # Next we define columns that have special SQL instead of just something
+ # like "bugs.bug_id".
+ my $actual_time = '(SUM(ldtime.work_time)'
+ . ' * COUNT(DISTINCT ldtime.bug_when)/COUNT(bugs.bug_id))';
+ my %special_sql = (
+ deadline => $dbh->sql_date_format('bugs.deadline', '%Y-%m-%d'),
+ actual_time => $actual_time,
+
+ percentage_complete =>
+ "(CASE WHEN $actual_time + bugs.remaining_time = 0.0"
+ . " THEN 0.0"
+ . " ELSE 100"
+ . " * ($actual_time / ($actual_time + bugs.remaining_time))"
+ . " END)",
+
+ 'flagtypes.name' => $dbh->sql_group_concat('DISTINCT '
+ . $dbh->sql_string_concat('flagtypes.name', 'flags.status')),
+
+ 'keywords' => $dbh->sql_group_concat('DISTINCT keyworddefs.name'),
+ );
+
+ # 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 (!Bugzilla->user->id) {
+ $sql = $dbh->sql_string_until($sql, $dbh->quote('@'));
+ }
+ $special_sql{$col} = $sql;
+ $columns{"${col}_realname"}->{name} = "map_${col}.realname";
+ }
+
+ foreach my $col (@id_fields) {
+ $special_sql{$col} = "map_${col}s.name";
+ }
+
+ # Do the actual column-getting from fielddefs, now.
+ foreach my $field (Bugzilla->get_fields({ obsolete => 0, buglist => 1 })) {
+ 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_bug_' . $field->name . '.value');
+ }
+ 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'};
+
+ Bugzilla::Hook::process('buglist_columns', { columns => \%columns });
+
+ $cache->{search_columns} = \%columns;
+ return $cache->{search_columns};
+}
+
+sub REPORT_COLUMNS {
+ my $columns = dclone(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 keywords relevance);
+
+ # Multi-select fields are not currently supported.
+ my @multi_selects = Bugzilla->get_fields(
+ { obsolete => 0, type => FIELD_TYPE_MULTI_SELECT });
+ push(@no_report_columns, map { $_->name } @multi_selects);
+
+ # If you're not a time-tracker, you can't use time-tracking
+ # columns.
+ if (!Bugzilla->user->is_timetracker) {
+ push(@no_report_columns, TIMETRACKING_FIELDS);
+ }
+
+ foreach my $name (@no_report_columns) {
+ delete $columns->{$name};
+ }
+ return $columns;
+}
+
+# Create a new Search
+# Note that the param argument may be modified by Bugzilla::Search
+sub new {
+ my $invocant = shift;
+ my $class = ref($invocant) || $invocant;
+
+ my $self = { @_ };
+ bless($self, $class);
+
+ $self->init();
+
+ return $self;
+}
+
+sub init {
+ my $self = shift;
+ my @fields = @{ $self->{'fields'} || [] };
+ my $params = $self->{'params'};
+ $params->convert_old_params();
+ $self->{'user'} ||= Bugzilla->user;
+ my $user = $self->{'user'};
+
+ my @inputorder = @{ $self->{'order'} || [] };
+ my @orderby;
+
+ my @supptables;
+ my @wherepart;
+ my @having;
+ my @groupby;
+ my @specialchart;
+ my @andlist;
+
+ my %special_order = %{SPECIAL_ORDER()};
+ my %special_order_join = %{SPECIAL_ORDER_JOIN()};
+
+ my @select_fields =
+ Bugzilla->get_fields({ type => FIELD_TYPE_SINGLE_SELECT });
+
+ my @multi_select_fields = Bugzilla->get_fields({
+ type => [FIELD_TYPE_MULTI_SELECT, FIELD_TYPE_BUG_URLS],
+ obsolete => 0 });
+ foreach my $field (@select_fields) {
+ next if $field->is_abnormal;
+ my $name = $field->name;
+ $special_order{$name} = [ "$name.sortkey", "$name.value" ],
+ $special_order_join{$name} =
+ "LEFT JOIN $name ON $name.value = bugs.$name";
+ }
+
+ my $dbh = Bugzilla->dbh;
+
+ # All items that are in the ORDER BY must be in the SELECT.
+ foreach my $orderitem (@inputorder) {
+ my $column_name = split_order_term($orderitem);
+ if (!grep($_ eq $column_name, @fields)) {
+ push(@fields, $column_name);
+ }
+ }
+
+ # First, deal with all the old hard-coded non-chart-based poop.
+ if (grep(/^assigned_to/, @fields)) {
+ push @supptables, "INNER JOIN profiles AS map_assigned_to " .
+ "ON bugs.assigned_to = map_assigned_to.userid";
+ }
+
+ if (grep(/^reporter/, @fields)) {
+ push @supptables, "INNER JOIN profiles AS map_reporter " .
+ "ON bugs.reporter = map_reporter.userid";
+ }
+
+ if (grep(/^qa_contact/, @fields)) {
+ push @supptables, "LEFT JOIN profiles AS map_qa_contact " .
+ "ON bugs.qa_contact = map_qa_contact.userid";
+ }
+
+ if (grep($_ eq 'product' || $_ eq 'classification', @fields))
+ {
+ push @supptables, "INNER JOIN products AS map_products " .
+ "ON bugs.product_id = map_products.id";
+ }
+
+ if (grep($_ eq 'classification', @fields)) {
+ push @supptables,
+ "INNER JOIN classifications AS map_classifications " .
+ "ON map_products.classification_id = map_classifications.id";
+ }
+
+ if (grep($_ eq 'component', @fields)) {
+ push @supptables, "INNER JOIN components AS map_components " .
+ "ON bugs.component_id = map_components.id";
+ }
+
+ if (grep($_ eq 'actual_time' || $_ eq 'percentage_complete', @fields)) {
+ push(@supptables, "LEFT JOIN longdescs AS ldtime " .
+ "ON ldtime.bug_id = bugs.bug_id");
+ }
+ foreach my $field (@multi_select_fields) {
+ my $field_name = $field->name;
+ next if !grep($_ eq $field_name, @fields);
+ push(@supptables, "LEFT JOIN bug_$field_name AS map_bug_$field_name"
+ . " ON map_bug_$field_name.bug_id = bugs.bug_id");
+ }
+
+ if (grep($_ eq 'flagtypes.name', @fields)) {
+ push(@supptables, "LEFT JOIN flags ON flags.bug_id = bugs.bug_id AND attach_id IS NULL");
+ push(@supptables, "LEFT JOIN flagtypes ON flagtypes.id = flags.type_id");
+ }
+
+ if (grep($_ eq 'keywords', @fields)) {
+ push(@supptables, "LEFT JOIN keywords ON keywords.bug_id = bugs.bug_id");
+ push(@supptables, "LEFT JOIN keyworddefs ON keyworddefs.id = keywords.keywordid");
+ }
+
+ # If the user has selected all of either status or resolution, change to
+ # selecting none. This is functionally equivalent, but quite a lot faster.
+ # Also, if the status is __open__ or __closed__, translate those
+ # into their equivalent lists of open and closed statuses.
+ if ($params->param('bug_status')) {
+ my @bug_statuses = $params->param('bug_status');
+ # Also include inactive bug statuses, as you can query them.
+ my @legal_statuses =
+ map {$_->name} @{Bugzilla::Field->new({name => 'bug_status'})->legal_values};
+
+ # Filter out any statuses that have been removed completely that are still
+ # being used by the client
+ my @valid_statuses;
+ foreach my $status (@bug_statuses) {
+ push(@valid_statuses, $status) if grep($_ eq $status, @legal_statuses);
+ }
+
+ if (scalar(@valid_statuses) == scalar(@legal_statuses)
+ || $bug_statuses[0] eq "__all__")
+ {
+ $params->delete('bug_status');
+ }
+ elsif ($bug_statuses[0] eq '__open__') {
+ $params->param('bug_status', grep(is_open_state($_),
+ @legal_statuses));
+ }
+ elsif ($bug_statuses[0] eq "__closed__") {
+ $params->param('bug_status', grep(!is_open_state($_),
+ @legal_statuses));
+ }
+ else {
+ $params->param('bug_status', @valid_statuses);
+ }
+ }
+
+ if ($params->param('resolution')) {
+ my @resolutions = $params->param('resolution');
+ # Also include inactive resolutions, as you can query them.
+ my $legal_resolutions = Bugzilla::Field->new({name => 'resolution'})->legal_values;
+ if (scalar(@resolutions) == scalar(@$legal_resolutions)) {
+ $params->delete('resolution');
+ }
+ }
+
+ # All fields that don't have a . in their name should be specifyable
+ # in the URL directly.
+ my @legal_fields = grep { $_->name !~ /\./ } Bugzilla->get_fields;
+ if (!$user->is_timetracker) {
+ foreach my $field (TIMETRACKING_FIELDS) {
+ @legal_fields = grep { $_->name ne $field } @legal_fields;
+ }
+ }
+
+ foreach my $field ($params->param()) {
+ my ($field_obj) = grep { $_->name eq $field } @legal_fields;
+ if ($field_obj) {
+ my $type = $params->param("${field}_type");
+ my @values = $params->param($field);
+ if (!$type) {
+ if ($field eq 'keywords') {
+ $type = 'anywords';
+ }
+ else {
+ $type = 'anyexact';
+ }
+ }
+ $type = 'matches' if $field eq 'content';
+ my $send_value = join(',', @values);
+ if ( $type eq 'anyexact'
+ and ($field_obj->is_select or $field eq 'version'
+ or $field eq 'target_milestone') )
+ {
+ $send_value = \@values;
+ }
+ push(@specialchart, [$field, $type, $send_value]);
+ }
+ }
+
+ foreach my $id (1, 2, 3) {
+ if (!defined ($params->param("email$id"))) {
+ next;
+ }
+ my $email = trim($params->param("email$id"));
+ if ($email eq "") {
+ next;
+ }
+ my $type = $params->param("emailtype$id");
+ $type = "anyexact" if ($type eq "exact");
+
+ my @clist;
+ foreach my $field ("assigned_to", "reporter", "cc", "qa_contact") {
+ if ($params->param("email$field$id")) {
+ push(@clist, $field, $type, $email);
+ }
+ }
+ if ($params->param("emaillongdesc$id")) {
+ push(@clist, "commenter", $type, $email);
+ }
+ if (@clist) {
+ push(@specialchart, \@clist);
+ }
+ else {
+ # No field is selected. Nothing to see here.
+ next;
+ }
+
+ if ($type eq "anyexact") {
+ foreach my $name (split(',', $email)) {
+ $name = trim($name);
+ login_to_id($name, THROW_ERROR) if $name;
+ }
+ }
+ }
+
+ my $chfieldfrom = trim(lc($params->param('chfieldfrom') || ''));
+ my $chfieldto = trim(lc($params->param('chfieldto') || ''));
+ $chfieldfrom = '' if ($chfieldfrom eq 'now');
+ $chfieldto = '' if ($chfieldto eq 'now');
+ my @chfield = $params->param('chfield');
+ my $chvalue = trim($params->param('chfieldvalue')) || '';
+
+ # 2003-05-20: The 'changedin' field is no longer in the UI, but we continue
+ # to process it because it will appear in stored queries and bookmarks.
+ my $changedin = trim($params->param('changedin')) || '';
+ if ($changedin) {
+ if ($changedin !~ /^[0-9]*$/) {
+ ThrowUserError("illegal_changed_in_last_x_days",
+ { value => $changedin });
+ }
+
+ if (!$chfieldfrom
+ && !$chfieldto
+ && scalar(@chfield) == 1
+ && $chfield[0] eq "[Bug creation]")
+ {
+ # Deal with the special case where the query is using changedin
+ # to get bugs created in the last n days by converting the value
+ # into its equivalent for the chfieldfrom parameter.
+ $chfieldfrom = "-" . ($changedin - 1) . "d";
+ }
+ else {
+ # Oh boy, the general case. Who knows why the user included
+ # the changedin parameter, but do our best to comply.
+ push(@specialchart, ["changedin", "lessthan", $changedin + 1]);
+ }
+ }
+
+ if ($chfieldfrom ne '' || $chfieldto ne '') {
+ my $sql_chfrom = $chfieldfrom ? $dbh->quote(SqlifyDate($chfieldfrom)):'';
+ my $sql_chto = $chfieldto ? $dbh->quote(SqlifyDate($chfieldto)) :'';
+ my $sql_chvalue = $chvalue ne '' ? $dbh->quote($chvalue) : '';
+ trick_taint($sql_chvalue);
+ if(!@chfield) {
+ if ($sql_chfrom) {
+ my $term = "bugs.delta_ts >= $sql_chfrom";
+ push(@wherepart, $term);
+ $self->search_description({
+ field => 'delta_ts', type => 'greaterthaneq',
+ value => $chfieldfrom, term => $term,
+ });
+ }
+ if ($sql_chto) {
+ my $term = "bugs.delta_ts <= $sql_chto";
+ push(@wherepart, $term);
+ $self->search_description({
+ field => 'delta_ts', type => 'lessthaneq',
+ value => $chfieldto, term => $term,
+ });
+ }
+ } else {
+ my $bug_creation_clause;
+ my @list;
+ my @actlist;
+ foreach my $f (@chfield) {
+ if ($f eq "[Bug creation]") {
+ # Treat [Bug creation] differently because we need to look
+ # at bugs.creation_ts rather than the bugs_activity table.
+ my @l;
+ if ($sql_chfrom) {
+ my $term = "bugs.creation_ts >= $sql_chfrom";
+ push(@l, $term);
+ $self->search_description({
+ field => 'creation_ts', type => 'greaterthaneq',
+ value => $chfieldfrom, term => $term,
+ });
+ }
+ if ($sql_chto) {
+ my $term = "bugs.creation_ts <= $sql_chto";
+ push(@l, $term);
+ $self->search_description({
+ field => 'creation_ts', type => 'lessthaneq',
+ value => $chfieldto, term => $term,
+ });
+ }
+ $bug_creation_clause = "(" . join(' AND ', @l) . ")";
+ } else {
+ push(@actlist, get_field_id($f));
+ }
+ }
+
+ # @actlist won't have any elements if the only field being searched
+ # is [Bug creation] (in which case we don't need bugs_activity).
+ if(@actlist) {
+ my $extra = " actcheck.bug_id = bugs.bug_id";
+ push(@list, "(actcheck.bug_when IS NOT NULL)");
+
+ my $from_term = " AND actcheck.bug_when >= $sql_chfrom";
+ $extra .= $from_term if $sql_chfrom;
+ my $to_term = " AND actcheck.bug_when <= $sql_chto";
+ $extra .= $to_term if $sql_chto;
+ my $value_term = " AND actcheck.added = $sql_chvalue";
+ $extra .= $value_term if $sql_chvalue;
+
+ push(@supptables, "LEFT JOIN bugs_activity AS actcheck " .
+ "ON $extra AND "
+ . $dbh->sql_in('actcheck.fieldid', \@actlist));
+
+ foreach my $field (@chfield) {
+ next if $field eq "[Bug creation]";
+ if ($sql_chvalue) {
+ $self->search_description({
+ field => $field, type => 'changedto',
+ value => $chvalue, term => $value_term,
+ });
+ }
+ if ($sql_chfrom) {
+ $self->search_description({
+ field => $field, type => 'changedafter',
+ value => $chfieldfrom, term => $from_term,
+ });
+ }
+ if ($sql_chvalue) {
+ $self->search_description({
+ field => $field, type => 'changedbefore',
+ value => $chfieldto, term => $to_term,
+ });
+ }
+ }
+ }
+
+ # Now that we're done using @list to determine if there are any
+ # regular fields to search (and thus we need bugs_activity),
+ # add the [Bug creation] criterion to the list so we can OR it
+ # together with the others.
+ push(@list, $bug_creation_clause) if $bug_creation_clause;
+
+ push(@wherepart, "(" . join(" OR ", @list) . ")");
+ }
+ }
+
+ my $sql_deadlinefrom;
+ my $sql_deadlineto;
+ if ($user->is_timetracker) {
+ if ($params->param('deadlinefrom')) {
+ my $deadlinefrom = $params->param('deadlinefrom');
+ $sql_deadlinefrom = $dbh->quote(SqlifyDate($deadlinefrom));
+ trick_taint($sql_deadlinefrom);
+ my $term = "bugs.deadline >= $sql_deadlinefrom";
+ push(@wherepart, $term);
+ $self->search_description({
+ field => 'deadline', type => 'greaterthaneq',
+ value => $deadlinefrom, term => $term,
+ });
+ }
+
+ if ($params->param('deadlineto')) {
+ my $deadlineto = $params->param('deadlineto');
+ $sql_deadlineto = $dbh->quote(SqlifyDate($deadlineto));
+ trick_taint($sql_deadlineto);
+ my $term = "bugs.deadline <= $sql_deadlineto";
+ push(@wherepart, $term);
+ $self->search_description({
+ field => 'deadline', type => 'lessthaneq',
+ value => $deadlineto, term => $term,
+ });
+ }
+ }
+
+ foreach my $f ("short_desc", "longdesc", "bug_file_loc",
+ "status_whiteboard") {
+ if (defined $params->param($f)) {
+ my $s = trim($params->param($f));
+ if ($s ne "") {
+ my $type = $params->param($f . "_type");
+ push(@specialchart, [$f, $type, $s]);
+ }
+ }
+ }
+
+ # first we delete any sign of "Chart #-1" from the HTML form hash
+ # since we want to guarantee the user didn't hide something here
+ my @badcharts = grep /^(field|type|value)-1-/, $params->param();
+ foreach my $field (@badcharts) {
+ $params->delete($field);
+ }
+
+ # now we take our special chart and stuff it into the form hash
+ my $chart = -1;
+ my $row = 0;
+ foreach my $ref (@specialchart) {
+ my $col = 0;
+ while (@$ref) {
+ $params->param("field$chart-$row-$col", shift(@$ref));
+ $params->param("type$chart-$row-$col", shift(@$ref));
+ $params->param("value$chart-$row-$col", shift(@$ref));
+ $col++;
+
+ }
+ $row++;
+ }
+
+
+# A boolean chart is a way of representing the terms in a logical
+# expression. Bugzilla builds SQL queries depending on how you enter
+# terms into the boolean chart. Boolean charts are represented in
+# urls as tree-tuples of (chart id, row, column). The query form
+# (query.cgi) may contain an arbitrary number of boolean charts where
+# each chart represents a clause in a SQL query.
+#
+# The query form starts out with one boolean chart containing one
+# row and one column. Extra rows can be created by pressing the
+# AND button at the bottom of the chart. Extra columns are created
+# by pressing the OR button at the right end of the chart. Extra
+# charts are created by pressing "Add another boolean chart".
+#
+# Each chart consists of an arbitrary number of rows and columns.
+# The terms within a row are ORed together. The expressions represented
+# by each row are ANDed together. The expressions represented by each
+# chart are ANDed together.
+#
+# ----------------------
+# | col2 | col2 | col3 |
+# --------------|------|------|
+# | row1 | a1 | a2 | |
+# |------|------|------|------| => ((a1 OR a2) AND (b1 OR b2 OR b3) AND (c1))
+# | row2 | b1 | b2 | b3 |
+# |------|------|------|------|
+# | row3 | c1 | | |
+# -----------------------------
+#
+# --------
+# | col2 |
+# --------------|
+# | row1 | d1 | => (d1)
+# ---------------
+#
+# Together, these two charts represent a SQL expression like this
+# SELECT blah FROM blah WHERE ( (a1 OR a2)AND(b1 OR b2 OR b3)AND(c1)) AND (d1)
+#
+# The terms within a single row of a boolean chart are all constraints
+# on a single piece of data. If you're looking for a bug that has two
+# different people cc'd on it, then you need to use two boolean charts.
+# This will find bugs with one CC matching 'foo@blah.org' and and another
+# CC matching 'bar@blah.org'.
+#
+# --------------------------------------------------------------
+# CC | equal to
+# foo@blah.org
+# --------------------------------------------------------------
+# CC | equal to
+# bar@blah.org
+#
+# If you try to do this query by pressing the AND button in the
+# original boolean chart then what you'll get is an expression that
+# looks for a single CC where the login name is both "foo@blah.org",
+# and "bar@blah.org". This is impossible.
+#
+# --------------------------------------------------------------
+# CC | equal to
+# foo@blah.org
+# AND
+# CC | equal to
+# bar@blah.org
+# --------------------------------------------------------------
+
+# $chartid is the number of the current chart whose SQL we're constructing
+# $row is the current row of the current chart
+
+# names for table aliases are constructed using $chartid and $row
+# SELECT blah FROM $table "$table_$chartid_$row" WHERE ....
+
+# $f = field of table in bug db (e.g. bug_id, reporter, etc)
+# $ff = qualified field name (field name prefixed by table)
+# e.g. bugs_activity.bug_id
+# $t = type of query. e.g. "equal to", "changed after", case sensitive substr"
+# $v = value - value the user typed in to the form
+# $q = sanitized version of user input trick_taint(($dbh->quote($v)))
+# @supptables = Tables and/or table aliases used in query
+# %suppseen = A hash used to store all the tables in supptables to weed
+# out duplicates.
+# @supplist = A list used to accumulate all the JOIN clauses for each
+# chart to merge the ON sections of each.
+# $suppstring = String which is pasted into query containing all table names
+
+ # get a list of field names to verify the user-submitted chart fields against
+ my %chartfields = @{$dbh->selectcol_arrayref(
+ q{SELECT name, id FROM fielddefs}, { Columns=>[1,2] })};
+
+ if (!$user->is_timetracker) {
+ foreach my $tt_field (TIMETRACKING_FIELDS) {
+ delete $chartfields{$tt_field};
+ }
+ }
+
+ my ($sequence, $chartid);
+ $row = 0;
+ for ($chart=-1 ;
+ $chart < 0 || $params->param("field$chart-0-0") ;
+ $chart++)
+ {
+ $chartid = $chart >= 0 ? $chart : "";
+ my @chartandlist;
+ for ($row = 0 ;
+ $params->param("field$chart-$row-0") ;
+ $row++)
+ {
+ my @orlist;
+ for (my $col = 0 ;
+ $params->param("field$chart-$row-$col") ;
+ $col++)
+ {
+ my $field = $params->param("field$chart-$row-$col") || "noop";
+ my $original_field = $field; # Saved for search_description
+ my $operator = $params->param("type$chart-$row-$col") || "noop";
+ my $value = $params->param("value$chart-$row-$col");
+ my @values;
+ if (ref $value) {
+ @values = @$value;
+ $value = join(',', @values);
+ }
+ $value = "" if !defined $value;
+ $value = trim($value);
+ next if ($field eq "noop" || $operator eq "noop"
+ || $value eq "");
+
+ # chart -1 is generated by other code above, not from the user-
+ # submitted form, so we'll blindly accept any values in chart -1
+ if (!$chartfields{$field} and $chart != -1) {
+ ThrowCodeError("invalid_field_name", { field => $field });
+ }
+
+ # This is either from the internal chart (in which case we
+ # already know about it), or it was in %chartfields, so it is
+ # a valid field name, which means that it's ok.
+ trick_taint($field);
+ my $quoted = $dbh->quote($value);
+ trick_taint($quoted);
+
+ my $term;
+ my $full_field = $field =~ /\./ ? $field : "bugs.$field";
+ $self->do_search_function({
+ chartid => \$chartid,
+ sequence => \$sequence,
+ f => \$field,
+ ff => \$full_field,
+ t => \$operator,
+ v => \$value,
+ all_v => \@values,
+ q => \$quoted,
+ term => \$term,
+ multi_fields => \@multi_select_fields,
+ supptables => \@supptables,
+ wherepart => \@wherepart,
+ having => \@having,
+ groupby => \@groupby,
+ chartfields => \%chartfields,
+ fields => \@fields,
+ });
+
+ if ($term) {
+ $self->search_description({
+ field => $original_field, type => $operator,
+ value => $value, term => $term,
+ });
+ push(@orlist, $term);
+ }
+ else {
+ # This field and this type don't work together.
+ ThrowCodeError("field_type_mismatch",
+ { field => $params->param("field$chart-$row-$col"),
+ type => $params->param("type$chart-$row-$col"),
+ });
+ }
+ }
+ if (@orlist) {
+ @orlist = map("($_)", @orlist) if (scalar(@orlist) > 1);
+ push(@chartandlist, "(" . join(" OR ", @orlist) . ")");
+ }
+ }
+ if (@chartandlist) {
+ if ($params->param("negate$chart")) {
+ push(@andlist, "NOT(" . join(" AND ", @chartandlist) . ")");
+ } else {
+ push(@andlist, "(" . join(" AND ", @chartandlist) . ")");
+ }
+ }
+ }
+
+ # The ORDER BY clause goes last, but can require modifications
+ # to other parts of the query, so we want to create it before we
+ # write the FROM clause.
+ foreach my $orderitem (@inputorder) {
+ BuildOrderBy(\%special_order, $orderitem, \@orderby);
+ }
+ # Now JOIN the correct tables in the FROM clause.
+ # This is done separately from the above because it's
+ # cleaner to do it this way.
+ foreach my $orderitem (@inputorder) {
+ # Grab the part without ASC or DESC.
+ my $column_name = split_order_term($orderitem);
+ if ($special_order_join{$column_name}) {
+ push(@supptables, $special_order_join{$column_name});
+ }
+ }
+
+ my %suppseen = ("bugs" => 1);
+ my $suppstring = "bugs";
+ my @supplist = (" ");
+ foreach my $str (@supptables) {
+
+ if ($str =~ /^(LEFT|INNER|RIGHT)\s+JOIN/i) {
+ $str =~ /^(.*?)\s+ON\s+(.*)$/i;
+ my ($leftside, $rightside) = ($1, $2);
+ if (defined $suppseen{$leftside}) {
+ $supplist[$suppseen{$leftside}] .= " AND ($rightside)";
+ } else {
+ $suppseen{$leftside} = scalar @supplist;
+ push @supplist, " $leftside ON ($rightside)";
+ }
+ } else {
+ # Do not accept implicit joins using comma operator
+ # as they are not DB agnostic
+ ThrowCodeError("comma_operator_deprecated");
+ }
+ }
+ $suppstring .= join('', @supplist);
+
+ # Make sure we create a legal SQL query.
+ @andlist = ("1 = 1") if !@andlist;
+
+ my @sql_fields;
+ foreach my $field (@fields) {
+ my $alias = $field;
+ # Aliases cannot contain dots in them. We convert them to underscores.
+ $alias =~ s/\./_/g;
+ my $sql_field = ($field eq EMPTY_COLUMN) ? EMPTY_COLUMN
+ : COLUMNS->{$field}->{name} . " AS $alias";
+ push(@sql_fields, $sql_field);
+ }
+ my $query = "SELECT " . join(', ', @sql_fields) .
+ " FROM $suppstring" .
+ " LEFT JOIN bug_group_map " .
+ " ON bug_group_map.bug_id = bugs.bug_id ";
+
+ if ($user->id) {
+ if (scalar @{ $user->groups }) {
+ $query .= " AND bug_group_map.group_id NOT IN ("
+ . $user->groups_as_string . ") ";
+ }
+
+ $query .= " LEFT JOIN cc ON cc.bug_id = bugs.bug_id AND cc.who = " . $user->id;
+ }
+
+ $query .= " WHERE " . join(' AND ', (@wherepart, @andlist)) .
+ " AND bugs.creation_ts IS NOT NULL AND ((bug_group_map.group_id IS NULL)";
+
+ if ($user->id) {
+ my $userid = $user->id;
+ $query .= " OR (bugs.reporter_accessible = 1 AND bugs.reporter = $userid) " .
+ " OR (bugs.cclist_accessible = 1 AND cc.who IS NOT NULL) " .
+ " OR (bugs.assigned_to = $userid) ";
+ if (Bugzilla->params->{'useqacontact'}) {
+ $query .= "OR (bugs.qa_contact = $userid) ";
+ }
+ }
+
+ # For some DBs, every field in the SELECT must be in the GROUP BY.
+ foreach my $field (@fields) {
+ # These fields never go into the GROUP BY (bug_id goes in
+ # explicitly, below).
+ my @skip_group_by = (EMPTY_COLUMN,
+ qw(bug_id actual_time percentage_complete flagtypes.name
+ keywords));
+ push(@skip_group_by, map { $_->name } @multi_select_fields);
+
+ next if grep { $_ eq $field } @skip_group_by;
+ my $col = COLUMNS->{$field}->{name};
+ push(@groupby, $col) if !grep($_ eq $col, @groupby);
+ }
+ # 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 $item (@inputorder) {
+ my $column_name = split_order_term($item);
+ if ($special_order{$column_name}) {
+ push(@groupby, @{ $special_order{$column_name} });
+ }
+ }
+ $query .= ") " . $dbh->sql_group_by("bugs.bug_id", join(', ', @groupby));
+
+
+ if (@having) {
+ $query .= " HAVING " . join(" AND ", @having);
+ }
+
+ if (@orderby) {
+ $query .= " ORDER BY " . join(',', @orderby);
+ }
+
+ $self->{'sql'} = $query;
+}
+
+###############################################################################
+# Helper functions for the init() method.
+###############################################################################
+
+# 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, $value) = @$args{qw(f t v)};
+
+ my $actual_field = FIELD_MAP->{$$field} || $$field;
+
+ if (my $parse_func = SPECIAL_PARSING->{$actual_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->{$actual_field};
+ if (!$override) {
+ # Multi-select fields get special handling.
+ if (grep { $_->name eq $actual_field } @{ $args->{multi_fields} }) {
+ $override = $operator_field_override->{_multi_select};
+ }
+ # And so do attachment fields, if they don't have a specific
+ # individual override.
+ elsif ($actual_field =~ /^attachments\./) {
+ $override = $operator_field_override->{attachments};
+ }
+ }
+
+ 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 (!${ $args->{term} }) {
+ $self->_do_operator_function($args);
+ }
+}
+
+# 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->{t};
+ my $operator_func = OPERATORS->{$$operator};
+ $self->$operator_func(%$func_args);
+}
+
+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};
+ }
+ }
+
+ return $search_func;
+}
+
+sub _get_operator_field_override {
+ my $self = shift;
+ my $cache = Bugzilla->request_cache;
+
+ 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 });
+
+ $cache->{operator_field_override} = \%operator_field_override;
+ return $cache->{operator_field_override};
+}
+
+sub SqlifyDate {
+ my ($str) = @_;
+ $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+)([hHdDwWmMyY])$/) { # relative date
+ my ($sign, $amount, $unit, $date) = ($1, $2, lc $3, time);
+ my ($sec, $min, $hour, $mday, $month, $year, $wday) = localtime($date);
+ if ($sign && $sign eq '+') { $amount = -$amount; }
+ if ($unit eq 'w') { # convert weeks to days
+ $amount = 7*$amount + $wday;
+ $unit = 'd';
+ }
+ if ($unit eq 'd') {
+ $date -= $sec + 60*$min + 3600*$hour + 24*3600*$amount;
+ return time2str("%Y-%m-%d %H:%M:%S", $date);
+ }
+ elsif ($unit eq 'y') {
+ return sprintf("%4d-01-01 00:00:00", $year+1900-$amount);
+ }
+ elsif ($unit eq 'm') {
+ $month -= $amount;
+ while ($month<0) { $year--; $month += 12; }
+ return sprintf("%4d-%02d-01 00:00:00", $year+1900, $month+1);
+ }
+ elsif ($unit eq 'h') {
+ # Special case 0h for 'beginning of this hour'
+ if ($amount == 0) {
+ $date -= $sec + 60*$min;
+ } else {
+ $date -= 3600*$amount;
+ }
+ return time2str("%Y-%m-%d %H:%M:%S", $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("%Y-%m-%d %H:%M:%S", $date);
+}
+
+sub build_subselect {
+ my ($outer, $inner, $table, $cond) = @_;
+ my $q = "SELECT $inner FROM $table WHERE $cond";
+ #return "$outer IN ($q)";
+ my $dbh = Bugzilla->dbh;
+ my $list = $dbh->selectcol_arrayref($q);
+ return "1=2" unless @$list; # Could use boolean type on dbs which support it
+ return $dbh->sql_in($outer, $list);}
+
+sub GetByWordList {
+ my ($field, $strs) = (@_);
+ my @list;
+ my $dbh = Bugzilla->dbh;
+ return [] unless defined $strs;
+
+ foreach my $w (split(/[\s,]+/, $strs)) {
+ my $word = $w;
+ if ($word ne "") {
+ $word =~ tr/A-Z/a-z/;
+ $word = $dbh->quote('(^|[^a-z0-9])' . quotemeta($word) . '($|[^a-z0-9])');
+ trick_taint($word);
+ push(@list, $dbh->sql_regexp($field, $word));
+ }
+ }
+
+ return \@list;
+}
+
+# Support for "any/all/nowordssubstr" comparison type ("words as substrings")
+sub GetByWordListSubstr {
+ my ($field, $strs) = (@_);
+ my @list;
+ my $dbh = Bugzilla->dbh;
+ my $sql_word;
+
+ foreach my $word (split(/[\s,]+/, $strs)) {
+ if ($word ne "") {
+ $sql_word = $dbh->quote($word);
+ trick_taint($sql_word);
+ push(@list, $dbh->sql_iposition($sql_word, $field) . " > 0");
+ }
+ }
+
+ return \@list;
+}
+
+sub getSQL {
+ my $self = shift;
+ return $self->{'sql'};
+}
+
+sub search_description {
+ my ($self, $params) = @_;
+ my $desc = $self->{'search_description'} ||= [];
+ if ($params) {
+ push(@$desc, $params);
+ }
+ return $self->{'search_description'};
+}
+
+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";
+ }
+ if ($noun eq "%qacontact%") {
+ return "bugs.qa_contact";
+ }
+ return 0;
+}
+
+# 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;
+}
+
+# BuildOrderBy - Private Subroutine
+# This function converts the input order to an "output" order,
+# suitable for concatenation to form an ORDER BY clause. Basically,
+# it just handles fields that have non-standard sort orders from
+# %specialorder.
+# Arguments:
+# $orderitem - A string. The next value to append to the ORDER BY clause,
+# in the format of an item in the 'order' parameter to
+# Bugzilla::Search.
+# $stringlist - A reference to the list of strings that will be join()'ed
+# to make ORDER BY. This is what the subroutine modifies.
+# $reverseorder - (Optional) A boolean. TRUE if we should reverse the order
+# of the field that we are given (from ASC to DESC or vice-versa).
+#
+# Explanation of $reverseorder
+# ----------------------------
+# The role of $reverseorder is to handle things like sorting by
+# "target_milestone DESC".
+# Let's say that we had a field "A" that normally translates to a sort
+# order of "B ASC, C DESC". If we sort by "A DESC", what we really then
+# mean is "B DESC, C ASC". So $reverseorder is only used if we call
+# BuildOrderBy recursively, to let it know that we're "reversing" the
+# order. That is, that we wanted "A DESC", not "A".
+sub BuildOrderBy {
+ my ($special_order, $orderitem, $stringlist, $reverseorder) = (@_);
+
+ my ($orderfield, $orderdirection) = split_order_term($orderitem);
+
+ if ($reverseorder) {
+ # If orderdirection is empty or ASC...
+ if (!$orderdirection || $orderdirection =~ m/asc/i) {
+ $orderdirection = "DESC";
+ } else {
+ # This has the minor side-effect of making any reversed invalid
+ # direction into ASC.
+ $orderdirection = "ASC";
+ }
+ }
+
+ # Handle fields that have non-standard sort orders, from $specialorder.
+ if ($special_order->{$orderfield}) {
+ foreach my $subitem (@{$special_order->{$orderfield}}) {
+ # DESC on a field with non-standard sort order means
+ # "reverse the normal order for each field that we map to."
+ BuildOrderBy($special_order, $subitem, $stringlist,
+ $orderdirection =~ m/desc/i);
+ }
+ return;
+ }
+ # Aliases cannot contain dots in them. We convert them to underscores.
+ $orderfield =~ s/\./_/g if exists COLUMNS->{$orderfield};
+
+ push(@$stringlist, trim($orderfield . ' ' . $orderdirection));
+}
+
+# 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;
+}
+
+# Used to translate old SQL fragments from buglist.cgi's "order" argument
+# into our modern field IDs.
+sub translate_old_column {
+ my ($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 (%{ COLUMNS() }) {
+ next unless exists COLUMNS->{$key}->{name};
+ return $key if COLUMNS->{$key}->{name} eq $column;
+ }
+
+ return $column;
+}
+
+#####################################################################
+# Search Functions
+#####################################################################
+
+sub _contact_pronoun {
+ my $self = shift;
+ my %func_args = @_;
+ my ($value, $quoted) = @func_args{qw(v q)};
+ my $user = $self->{'user'};
+
+ if ($$value =~ /^\%group/) {
+ $self->_contact_exact_group(%func_args);
+ }
+ elsif ($$value =~ /^(%\w+%)$/) {
+ $$value = pronoun($1, $user);
+ $$quoted = $$value;
+ $self->_do_operator_function(\%func_args);
+ }
+
+}
+
+sub _contact_exact_group {
+ my $self = shift;
+ my %func_args = @_;
+ my ($chartid, $supptables, $f, $t, $v, $term) =
+ @func_args{qw(chartid supptables f t v term)};
+ my $user = $self->{'user'};
+
+ $$v =~ /\%group\.([^%]+)%/;
+ my $group = $1;
+ my $groupid = Bugzilla::Group::ValidateGroupName( $group, ($user));
+ ($groupid && $user->in_group_id($groupid))
+ || ThrowUserError('invalid_group_name',{name => $group});
+ my @childgroups = @{Bugzilla::Group->flatten_group_membership($groupid)};
+ my $table = "user_group_map_$$chartid";
+ push (@$supptables, "LEFT JOIN user_group_map AS $table " .
+ "ON $table.user_id = bugs.$$f " .
+ "AND $table.group_id IN(" .
+ join(',', @childgroups) . ") " .
+ "AND $table.isbless = 0 " .
+ "AND $table.grant_type IN(" .
+ GRANT_DIRECT . "," . GRANT_REGEXP . ")"
+ );
+ if ($$t =~ /^not/) {
+ $$term = "$table.group_id IS NULL";
+ } else {
+ $$term = "$table.group_id IS NOT NULL";
+ }
+}
+
+sub _assigned_to_reporter_nonchanged {
+ my $self = shift;
+ my %func_args = @_;
+ my ($f, $ff, $t, $term) =
+ @func_args{qw(f ff t term)};
+
+ $$ff = "profiles.login_name";
+ $self->_do_operator_function(\%func_args);
+ $$term = "bugs.$$f IN (SELECT userid FROM profiles WHERE $$term)";
+}
+
+sub _qa_contact_nonchanged {
+ my $self = shift;
+ my %func_args = @_;
+ my ($supptables, $f, $ff) =
+ @func_args{qw(supptables f ff)};
+
+ push(@$supptables, "LEFT JOIN profiles AS map_qa_contact " .
+ "ON bugs.qa_contact = map_qa_contact.userid");
+ $$ff = "COALESCE(map_$$f.login_name,'')";
+}
+
+sub _cc_pronoun {
+ my $self = shift;
+ my %func_args = @_;
+ my ($full_field, $value, $quoted) = @func_args{qw(ff v q)};
+ my $user = $self->{'user'};
+
+ if ($$value =~ /\%group/) {
+ return $self->_cc_exact_group(%func_args);
+ }
+ elsif ($$value =~ /^(%\w+%)$/) {
+ $$value = pronoun($1, $user);
+ $$quoted = $$value;
+ $$full_field = "profiles.userid";
+ }
+}
+
+sub _cc_exact_group {
+ my $self = shift;
+ my %func_args = @_;
+ my ($chartid, $sequence, $supptables, $t, $v, $term) =
+ @func_args{qw(chartid sequence supptables t v term)};
+ my $user = $self->{'user'};
+
+ $$v =~ m/%group\.([^%]+)%/;
+ my $group = $1;
+ my $groupid = Bugzilla::Group::ValidateGroupName( $group, ($user));
+ ($groupid && $user->in_group_id($groupid))
+ || ThrowUserError('invalid_group_name',{name => $group});
+ my @childgroups = @{Bugzilla::Group->flatten_group_membership($groupid)};
+ my $chartseq = $$chartid;
+ if ($$chartid eq "") {
+ $chartseq = "CC$$sequence";
+ $$sequence++;
+ }
+ my $table = "user_group_map_$chartseq";
+ push(@$supptables, "LEFT JOIN cc AS cc_$chartseq " .
+ "ON bugs.bug_id = cc_$chartseq.bug_id");
+ push(@$supptables, "LEFT JOIN user_group_map AS $table " .
+ "ON $table.user_id = cc_$chartseq.who " .
+ "AND $table.group_id IN(" .
+ join(',', @childgroups) . ") " .
+ "AND $table.isbless = 0 " .
+ "AND $table.grant_type IN(" .
+ GRANT_DIRECT . "," . GRANT_REGEXP . ")"
+ );
+ if ($$t =~ /^not/) {
+ $$term = "$table.group_id IS NULL";
+ } else {
+ $$term = "$table.group_id IS NOT NULL";
+ }
+}
+
+sub _cc_nonchanged {
+ my $self = shift;
+ my %func_args = @_;
+ my ($chartid, $sequence, $f, $ff, $t, $supptables, $term, $v) =
+ @func_args{qw(chartid sequence f ff t supptables term v)};
+
+ my $chartseq = $$chartid;
+ if ($$chartid eq "") {
+ $chartseq = "CC$$sequence";
+ $$sequence++;
+ }
+ if ($$ff eq 'bugs.cc') {
+ $$ff = "profiles.login_name";
+ }
+ $self->_do_operator_function(\%func_args);
+ push(@$supptables, "LEFT JOIN cc AS cc_$chartseq " .
+ "ON bugs.bug_id = cc_$chartseq.bug_id " .
+ "AND cc_$chartseq.who IN" .
+ "(SELECT userid FROM profiles WHERE $$term)"
+ );
+ $$term = "cc_$chartseq.who IS NOT NULL";
+}
+
+sub _long_desc_changedby {
+ my $self = shift;
+ my %func_args = @_;
+ my ($chartid, $supptables, $term, $v) =
+ @func_args{qw(chartid supptables term v)};
+
+ my $table = "longdescs_$$chartid";
+ push(@$supptables, "LEFT JOIN longdescs AS $table " .
+ "ON $table.bug_id = bugs.bug_id");
+ my $id = login_to_id($$v, THROW_ERROR);
+ $$term = "$table.who = $id";
+}
+
+sub _long_desc_changedbefore_after {
+ my $self = shift;
+ my %func_args = @_;
+ my ($chartid, $t, $v, $supptables, $term) =
+ @func_args{qw(chartid t v supptables term)};
+ my $dbh = Bugzilla->dbh;
+
+ my $operator = ($$t =~ /before/) ? '<' : '>';
+ my $table = "longdescs_$$chartid";
+ push(@$supptables, "LEFT JOIN longdescs AS $table " .
+ "ON $table.bug_id = bugs.bug_id " .
+ "AND $table.bug_when $operator " .
+ $dbh->quote(SqlifyDate($$v)) );
+ $$term = "($table.bug_when IS NOT NULL)";
+}
+
+sub _content_matches {
+ my $self = shift;
+ my %func_args = @_;
+ my ($chartid, $supptables, $term, $groupby, $fields, $t, $v) =
+ @func_args{qw(chartid supptables term groupby fields t v)};
+ 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_$$chartid";
+ my $comments_col = "comments";
+ $comments_col = "comments_noprivate" unless $self->{'user'}->is_insider;
+ push(@$supptables, "LEFT JOIN bugs_fulltext AS $table " .
+ "ON bugs.bug_id = $table.bug_id");
+
+ # Create search terms to add to the SELECT and WHERE clauses.
+ my ($term1, $rterm1) = $dbh->sql_fulltext_search("$table.$comments_col",
+ $$v, 1);
+ my ($term2, $rterm2) = $dbh->sql_fulltext_search("$table.short_desc",
+ $$v, 2);
+ $rterm1 = $term1 if !$rterm1;
+ $rterm2 = $term2 if !$rterm2;
+
+ # The term to use in the WHERE clause.
+ $$term = "$term1 > 0 OR $term2 > 0";
+ if ($$t =~ /not/i) {
+ $$term = "NOT($$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 = COLUMNS->{'relevance'}->{name};
+ $current = $current ? "$current + " : '';
+ # For NOT searches, we just add 0 to the relevance.
+ my $select_term = $$t =~ /not/ ? 0 : "($current$rterm1 + $rterm2)";
+ COLUMNS->{'relevance'}->{name} = $select_term;
+}
+
+sub _timestamp_translate {
+ my $self = shift;
+ my %func_args = @_;
+ my ($value, $quoted) = @func_args{qw(v q)};
+ my $dbh = Bugzilla->dbh;
+
+ return if $$value !~ /^[\+\-]?\d+[hdwmy]$/i;
+
+ $$value = SqlifyDate($$value);
+ $$quoted = $dbh->quote($$value);
+}
+
+sub _commenter_pronoun {
+ my $self = shift;
+ my %func_args = @_;
+ my ($full_field, $value, $quoted) = @func_args{qw(ff v q)};
+ my $user = $self->{'user'};
+
+ if ($$value =~ /^(%\w+%)$/) {
+ $$value = pronoun($1, $user);
+ $$quoted = $$value;
+ $$full_field = "profiles.userid";
+ }
+}
+
+sub _commenter {
+ my $self = shift;
+ my %func_args = @_;
+ my ($chartid, $sequence, $supptables, $f, $ff, $t, $term) =
+ @func_args{qw(chartid sequence supptables f ff t term)};
+
+ my $chartseq = $$chartid;
+ if ($$chartid eq "") {
+ $chartseq = "LD$$sequence";
+ $$sequence++;
+ }
+ my $table = "longdescs_$chartseq";
+ my $extra = $self->{'user'}->is_insider ? "" : "AND $table.isprivate < 1";
+ if ($$ff eq 'bugs.commenter') {
+ $$ff = "profiles.login_name";
+ }
+ $self->_do_operator_function(\%func_args);
+ push(@$supptables, "LEFT JOIN longdescs AS $table " .
+ "ON $table.bug_id = bugs.bug_id $extra " .
+ "AND $table.who IN" .
+ "(SELECT userid FROM profiles WHERE $$term)"
+ );
+ $$term = "$table.who IS NOT NULL";
+}
+
+sub _long_desc {
+ my $self = shift;
+ my %func_args = @_;
+ my ($chartid, $supptables, $ff) =
+ @func_args{qw(chartid supptables ff)};
+
+ my $table = "longdescs_$$chartid";
+ my $extra = $self->{'user'}->is_insider ? "" : "AND $table.isprivate < 1";
+ push(@$supptables, "LEFT JOIN longdescs AS $table " .
+ "ON $table.bug_id = bugs.bug_id $extra");
+ $$ff = "$table.thetext";
+}
+
+sub _longdescs_isprivate {
+ my $self = shift;
+ my %func_args = @_;
+ my ($chartid, $supptables, $ff) =
+ @func_args{qw(chartid supptables ff)};
+
+ my $table = "longdescs_$$chartid";
+ my $extra = $self->{'user'}->is_insider ? "" : "AND $table.isprivate < 1";
+ push(@$supptables, "LEFT JOIN longdescs AS $table " .
+ "ON $table.bug_id = bugs.bug_id $extra");
+ $$ff = "$table.isprivate";
+}
+
+sub _work_time_changedby {
+ my $self = shift;
+ my %func_args = @_;
+ my ($chartid, $supptables, $v, $term) =
+ @func_args{qw(chartid supptables v term)};
+
+ my $table = "longdescs_$$chartid";
+ push(@$supptables, "LEFT JOIN longdescs AS $table " .
+ "ON $table.bug_id = bugs.bug_id");
+ my $id = login_to_id($$v, THROW_ERROR);
+ $$term = "(($table.who = $id";
+ $$term .= ") AND ($table.work_time <> 0))";
+}
+
+sub _work_time_changedbefore_after {
+ my $self = shift;
+ my %func_args = @_;
+ my ($chartid, $t, $v, $supptables, $term) =
+ @func_args{qw(chartid t v supptables term)};
+ my $dbh = Bugzilla->dbh;
+
+ my $operator = ($$t =~ /before/) ? '<' : '>';
+ my $table = "longdescs_$$chartid";
+ push(@$supptables, "LEFT JOIN longdescs AS $table " .
+ "ON $table.bug_id = bugs.bug_id " .
+ "AND $table.work_time <> 0 " .
+ "AND $table.bug_when $operator " .
+ $dbh->quote(SqlifyDate($$v)) );
+ $$term = "($table.bug_when IS NOT NULL)";
+}
+
+sub _work_time {
+ my $self = shift;
+ my %func_args = @_;
+ my ($chartid, $supptables, $ff) =
+ @func_args{qw(chartid supptables ff)};
+
+ my $table = "longdescs_$$chartid";
+ push(@$supptables, "LEFT JOIN longdescs AS $table " .
+ "ON $table.bug_id = bugs.bug_id");
+ $$ff = "$table.work_time";
+}
+
+sub _percentage_complete {
+ my $self = shift;
+ my %func_args = @_;
+ my ($t, $chartid, $supptables, $fields, $q, $v, $having, $groupby, $term) =
+ @func_args{qw(t chartid supptables fields q v having groupby term)};
+ my $dbh = Bugzilla->dbh;
+
+ my $oper;
+ if ($$t eq "equals") {
+ $oper = "=";
+ } elsif ($$t eq "greaterthan") {
+ $oper = ">";
+ } elsif ($$t eq "greaterthaneq") {
+ $oper = ">=";
+ } elsif ($$t eq "lessthan") {
+ $oper = "<";
+ } elsif ($$t eq "lessthaneq") {
+ $oper = "<=";
+ } elsif ($$t eq "notequal") {
+ $oper = "<>";
+ } elsif ($$t eq "regexp") {
+ # This is just a dummy to help catch bugs- $oper won't be used
+ # since "regexp" is treated as a special case below. But
+ # leaving $oper uninitialized seems risky...
+ $oper = "sql_regexp";
+ } elsif ($$t eq "notregexp") {
+ # This is just a dummy to help catch bugs- $oper won't be used
+ # since "notregexp" is treated as a special case below. But
+ # leaving $oper uninitialized seems risky...
+ $oper = "sql_not_regexp";
+ } else {
+ $oper = "noop";
+ }
+ if ($oper ne "noop") {
+ my $table = "longdescs_$$chartid";
+ if (!grep($_ eq 'remaining_time', @$fields)) {
+ push(@$fields, "remaining_time");
+ }
+ push(@$supptables, "LEFT JOIN longdescs AS $table " .
+ "ON $table.bug_id = bugs.bug_id");
+ my $expression = "(100 * ((SUM($table.work_time) *
+ COUNT(DISTINCT $table.bug_when) /
+ COUNT(bugs.bug_id)) /
+ ((SUM($table.work_time) *
+ COUNT(DISTINCT $table.bug_when) /
+ COUNT(bugs.bug_id)) +
+ bugs.remaining_time)))";
+ $$q = $dbh->quote($$v);
+ trick_taint($$q);
+ if ($$t eq "regexp") {
+ push(@$having, $dbh->sql_regexp($expression, $$q));
+ } elsif ($$t eq "notregexp") {
+ push(@$having, $dbh->sql_not_regexp($expression, $$q));
+ } else {
+ push(@$having, "$expression $oper " . $$q);
+ }
+ push(@$groupby, "bugs.remaining_time");
+ }
+ $$term = "0=0";
+}
+
+sub _bug_group_nonchanged {
+ my $self = shift;
+ my %func_args = @_;
+ my ($supptables, $chartid, $ff, $t, $term) =
+ @func_args{qw(supptables chartid ff t term)};
+
+ push(@$supptables,
+ "LEFT JOIN bug_group_map AS bug_group_map_$$chartid " .
+ "ON bugs.bug_id = bug_group_map_$$chartid.bug_id");
+ $$ff = "groups_$$chartid.name";
+ $self->_do_operator_function(\%func_args);
+ push(@$supptables,
+ "LEFT JOIN groups AS groups_$$chartid " .
+ "ON groups_$$chartid.id = bug_group_map_$$chartid.group_id " .
+ "AND $$term");
+ $$term = "$$ff IS NOT NULL";
+}
+
+sub _attach_data_thedata {
+ my $self = shift;
+ my %func_args = @_;
+ my ($chartid, $supptables, $ff) =
+ @func_args{qw(chartid supptables ff)};
+
+ my $atable = "attachments_$$chartid";
+ my $dtable = "attachdata_$$chartid";
+ my $extra = $self->{'user'}->is_insider ? "" : "AND $atable.isprivate = 0";
+ push(@$supptables, "LEFT JOIN attachments AS $atable " .
+ "ON bugs.bug_id = $atable.bug_id $extra");
+ push(@$supptables, "LEFT JOIN attach_data AS $dtable " .
+ "ON $dtable.id = $atable.attach_id");
+ $$ff = "$dtable.thedata";
+}
+
+sub _attachments_submitter {
+ my $self = shift;
+ my %func_args = @_;
+ my ($chartid, $supptables, $ff) =
+ @func_args{qw(chartid supptables ff)};
+
+ my $atable = "map_attachment_submitter_$$chartid";
+ my $extra = $self->{'user'}->is_insider ? "" : "AND $atable.isprivate = 0";
+ push(@$supptables, "LEFT JOIN attachments AS $atable " .
+ "ON bugs.bug_id = $atable.bug_id $extra");
+ push(@$supptables, "LEFT JOIN profiles AS attachers_$$chartid " .
+ "ON $atable.submitter_id = attachers_$$chartid.userid");
+ $$ff = "attachers_$$chartid.login_name";
+}
+
+sub _attachments {
+ my $self = shift;
+ my %func_args = @_;
+ my ($chartid, $supptables, $f, $ff, $t, $v, $q) =
+ @func_args{qw(chartid supptables f ff t v q)};
+ my $dbh = Bugzilla->dbh;
+
+ my $table = "attachments_$$chartid";
+ my $extra = $self->{'user'}->is_insider ? "" : "AND $table.isprivate = 0";
+ push(@$supptables, "LEFT JOIN attachments AS $table " .
+ "ON bugs.bug_id = $table.bug_id $extra");
+ $$f =~ m/^attachments\.(.*)$/;
+ my $field = $1;
+ if ($$t eq "changedby") {
+ $$v = login_to_id($$v, THROW_ERROR);
+ $$q = $dbh->quote($$v);
+ $field = "submitter_id";
+ $$t = "equals";
+ } elsif ($$t eq "changedbefore") {
+ $$v = SqlifyDate($$v);
+ $$q = $dbh->quote($$v);
+ $field = "creation_ts";
+ $$t = "lessthan";
+ } elsif ($$t eq "changedafter") {
+ $$v = SqlifyDate($$v);
+ $$q = $dbh->quote($$v);
+ $field = "creation_ts";
+ $$t = "greaterthan";
+ }
+ if ($field eq "ispatch" && $$v ne "0" && $$v ne "1") {
+ ThrowUserError("illegal_attachment_is_patch");
+ }
+ if ($field eq "isobsolete" && $$v ne "0" && $$v ne "1") {
+ ThrowUserError("illegal_is_obsolete");
+ }
+ $$ff = "$table.$field";
+}
+
+sub _flagtypes_name {
+ my $self = shift;
+ my %func_args = @_;
+ my ($t, $chartid, $supptables, $ff, $having, $term) =
+ @func_args{qw(t chartid supptables ff having term)};
+ my $dbh = Bugzilla->dbh;
+
+ # Matches bugs by flag name/status.
+ # Note that--for the purposes of querying--a flag comprises
+ # its name plus its status (i.e. a flag named "review"
+ # with a status of "+" can be found by searching for "review+").
+
+ # Don't do anything if this condition is about changes to flags,
+ # as the generic change condition processors can handle those.
+ return if ($$t =~ m/^changed/);
+
+ # Add the flags and flagtypes tables to the query. We do
+ # a left join here so bugs without any flags still match
+ # negative conditions (f.e. "flag isn't review+").
+ my $attachments = "attachments_$$chartid";
+ my $extra = $self->{'user'}->is_insider ? "" : "AND $attachments.isprivate = 0";
+ push(@$supptables, "LEFT JOIN attachments AS $attachments " .
+ "ON bugs.bug_id = $attachments.bug_id $extra");
+ my $flags = "flags_$$chartid";
+ push(@$supptables, "LEFT JOIN flags AS $flags " .
+ "ON bugs.bug_id = $flags.bug_id ");
+ my $flagtypes = "flagtypes_$$chartid";
+ push(@$supptables, "LEFT JOIN flagtypes AS $flagtypes " .
+ "ON $flags.type_id = $flagtypes.id");
+ push(@$supptables, "LEFT JOIN flags AS $flags " .
+ "ON $flags.attach_id = $attachments.attach_id " .
+ "OR $flags.attach_id IS NULL");
+
+ # Generate the condition by running the operator-specific
+ # function. Afterwards the condition resides in the global $term
+ # variable.
+ $$ff = $dbh->sql_string_concat("${flagtypes}.name",
+ "$flags.status");
+ $self->_do_operator_function(\%func_args);
+
+ # If this is a negative condition (f.e. flag isn't "review+"),
+ # we only want bugs where all flags match the condition, not
+ # those where any flag matches, which needs special magic.
+ # Instead of adding the condition to the WHERE clause, we select
+ # the number of flags matching the condition and the total number
+ # of flags on each bug, then compare them in a HAVING clause.
+ # If the numbers are the same, all flags match the condition,
+ # so this bug should be included.
+ if ($$t =~ m/not/) {
+ push(@$having,
+ "SUM(CASE WHEN $$ff IS NOT NULL THEN 1 ELSE 0 END) = " .
+ "SUM(CASE WHEN $$term THEN 1 ELSE 0 END)");
+ $$term = "0=0";
+ }
+}
+
+sub _requestees_login_name {
+ my $self = shift;
+ my %func_args = @_;
+ my ($ff, $chartid, $supptables) = @func_args{qw(ff chartid supptables)};
+
+ my $attachments = "attachments_$$chartid";
+ my $extra = $self->{'user'}->is_insider ? "" : "AND $attachments.isprivate = 0";
+ push(@$supptables, "LEFT JOIN attachments AS $attachments " .
+ "ON bugs.bug_id = $attachments.bug_id $extra");
+ my $flags = "flags_$$chartid";
+ push(@$supptables, "LEFT JOIN flags AS $flags " .
+ "ON bugs.bug_id = $flags.bug_id ");
+ push(@$supptables, "LEFT JOIN profiles AS requestees_$$chartid " .
+ "ON $flags.requestee_id = requestees_$$chartid.userid");
+ push(@$supptables, "LEFT JOIN flags AS $flags " .
+ "ON $flags.attach_id = $attachments.attach_id " .
+ "OR $flags.attach_id IS NULL");
+
+ $$ff = "requestees_$$chartid.login_name";
+}
+
+sub _setters_login_name {
+ my $self = shift;
+ my %func_args = @_;
+ my ($ff, $chartid, $supptables) = @func_args{qw(ff chartid supptables)};
+
+ my $attachments = "attachments_$$chartid";
+ my $extra = $self->{'user'}->is_insider ? "" : "AND $attachments.isprivate = 0";
+ push(@$supptables, "LEFT JOIN attachments AS $attachments " .
+ "ON bugs.bug_id = $attachments.bug_id $extra");
+ my $flags = "flags_$$chartid";
+ push(@$supptables, "LEFT JOIN flags AS $flags " .
+ "ON bugs.bug_id = $flags.bug_id ");
+ push(@$supptables, "LEFT JOIN profiles AS setters_$$chartid " .
+ "ON $flags.setter_id = setters_$$chartid.userid");
+ push(@$supptables, "LEFT JOIN flags AS $flags " .
+ "ON $flags.attach_id = $attachments.attach_id " .
+ "OR $flags.attach_id IS NULL");
+
+ $$ff = "setters_$$chartid.login_name";
+}
+
+sub _changedin_days_elapsed {
+ my $self = shift;
+ my %func_args = @_;
+ my ($ff) = @func_args{qw(ff)};
+ my $dbh = Bugzilla->dbh;
+
+ $$ff = "(" . $dbh->sql_to_days('NOW()') . " - " .
+ $dbh->sql_to_days('bugs.delta_ts') . ")";
+}
+
+sub _component_nonchanged {
+ my $self = shift;
+ my %func_args = @_;
+ my ($ff, $t, $term) = @func_args{qw(ff t term)};
+
+ $$ff = "components.name";
+ $self->_do_operator_function(\%func_args);
+ $$term = build_subselect("bugs.component_id",
+ "components.id",
+ "components",
+ $$term);
+}
+sub _product_nonchanged {
+ my $self = shift;
+ my %func_args = @_;
+ my ($ff, $t, $term) = @func_args{qw(ff t term)};
+
+ # Generate the restriction condition
+ $$ff = "products.name";
+ $self->_do_operator_function(\%func_args);
+ $$term = build_subselect("bugs.product_id",
+ "products.id",
+ "products",
+ $$term);
+}
+
+sub _classification_nonchanged {
+ my $self = shift;
+ my %func_args = @_;
+ my ($chartid, $v, $ff, $t, $supptables, $term) =
+ @func_args{qw(chartid v ff t supptables term)};
+
+ # Generate the restriction condition
+ push @$supptables, "INNER JOIN products AS map_products " .
+ "ON bugs.product_id = map_products.id";
+ $$ff = "classifications.name";
+ $self->_do_operator_function(\%func_args);
+ $$term = build_subselect("map_products.classification_id",
+ "classifications.id",
+ "classifications",
+ $$term);
+}
+
+sub _keywords_exact {
+ my $self = shift;
+ my %func_args = @_;
+ my ($chartid, $v, $ff, $f, $t, $term, $supptables) =
+ @func_args{qw(chartid v ff f t term supptables)};
+
+ my @keyword_ids;
+ foreach my $word (split(/[\s,]+/, $$v)) {
+ next if $word eq '';
+ my $keyword = Bugzilla::Keyword->check($word);
+ push(@keyword_ids, $keyword->id);
+ }
+
+ my $table = "keywords_$$chartid";
+ my $id_field = "$table.keywordid";
+ if ($$t eq 'anywords' or $$t eq 'anyexact') {
+ my $dbh = Bugzilla->dbh;
+ $$term = $dbh->sql_in($id_field, \@keyword_ids);
+ }
+ if ($$t eq 'allwords') {
+ my @terms =
+ map { "bugs.bug_id IN (SELECT bug_id FROM keywords WHERE keywordid = $_)" } @keyword_ids;
+ $$term = join(' AND ', @terms);
+ return;
+ }
+
+ if ($$term) {
+ push(@$supptables, "LEFT JOIN keywords AS $table " .
+ "ON $table.bug_id = bugs.bug_id");
+ }
+ else {
+ $self->_keywords_nonchanged(%func_args);
+ }
+}
+
+sub _keywords_nonchanged {
+ my $self = shift;
+ my %func_args = @_;
+ my ($chartid, $v, $ff, $t, $term, $supptables) =
+ @func_args{qw(chartid v ff t term supptables)};
+
+ my $k_table = "keywords_$$chartid";
+ my $kd_table = "keyworddefs_$$chartid";
+
+ push(@$supptables, "LEFT JOIN keywords AS $k_table " .
+ "ON $k_table.bug_id = bugs.bug_id");
+ push(@$supptables, "LEFT JOIN keyworddefs AS $kd_table " .
+ "ON $kd_table.id = $k_table.keywordid");
+
+ $$ff = "$kd_table.name";
+}
+
+sub _dependson_nonchanged {
+ my $self = shift;
+ my %func_args = @_;
+ my ($chartid, $ff, $f, $t, $term, $supptables) =
+ @func_args{qw(chartid ff f t term supptables)};
+
+ my $table = "dependson_" . $$chartid;
+ $$ff = "$table.$$f";
+ $self->_do_operator_function(\%func_args);
+ push(@$supptables, "LEFT JOIN dependencies AS $table " .
+ "ON $table.blocked = bugs.bug_id " .
+ "AND ($$term)");
+ $$term = "$$ff IS NOT NULL";
+}
+
+sub _blocked_nonchanged {
+ my $self = shift;
+ my %func_args = @_;
+ my ($chartid, $ff, $f, $t, $term, $supptables) =
+ @func_args{qw(chartid ff f t term supptables)};
+
+ my $table = "blocked_" . $$chartid;
+ $$ff = "$table.$$f";
+ $self->_do_operator_function(\%func_args);
+ push(@$supptables, "LEFT JOIN dependencies AS $table " .
+ "ON $table.dependson = bugs.bug_id " .
+ "AND ($$term)");
+ $$term = "$$ff IS NOT NULL";
+}
+
+sub _alias_nonchanged {
+ my $self = shift;
+ my %func_args = @_;
+ my ($ff, $t, $term) = @func_args{qw(ff t term)};
+
+ $$ff = "COALESCE(bugs.alias, '')";
+
+ $self->_do_operator_function(\%func_args);
+}
+
+sub _owner_idle_time_greater_less {
+ my $self = shift;
+ my %func_args = @_;
+ my ($chartid, $v, $supptables, $t, $wherepart, $term) =
+ @func_args{qw(chartid v supptables t wherepart term)};
+ my $dbh = Bugzilla->dbh;
+
+ my $table = "idle_" . $$chartid;
+ $$v =~ /^(\d+)\s*([hHdDwWmMyY])?$/;
+ my $quantity = $1 || 0;
+ my $unit = lc $2;
+ my $unitinterval = 'DAY';
+ if ($unit eq 'h') {
+ $unitinterval = 'HOUR';
+ } elsif ($unit eq 'w') {
+ $unitinterval = ' * 7 DAY';
+ } elsif ($unit eq 'm') {
+ $unitinterval = 'MONTH';
+ } elsif ($unit eq 'y') {
+ $unitinterval = 'YEAR';
+ }
+ my $cutoff = "NOW() - " .
+ $dbh->sql_interval($quantity, $unitinterval);
+ my $assigned_fieldid = get_field_id('assigned_to');
+ push(@$supptables, "LEFT JOIN longdescs AS comment_$table " .
+ "ON comment_$table.who = bugs.assigned_to " .
+ "AND comment_$table.bug_id = bugs.bug_id " .
+ "AND comment_$table.bug_when > $cutoff");
+ push(@$supptables, "LEFT JOIN bugs_activity AS activity_$table " .
+ "ON (activity_$table.who = bugs.assigned_to " .
+ "OR activity_$table.fieldid = $assigned_fieldid) " .
+ "AND activity_$table.bug_id = bugs.bug_id " .
+ "AND activity_$table.bug_when > $cutoff");
+ if ($$t =~ /greater/) {
+ push(@$wherepart, "(comment_$table.who IS NULL " .
+ "AND activity_$table.who IS NULL)");
+ } else {
+ push(@$wherepart, "(comment_$table.who IS NOT NULL " .
+ "OR activity_$table.who IS NOT NULL)");
+ }
+ $$term = "0=0";
+}
+
+sub _multiselect_negative {
+ my $self = shift;
+ my %func_args = @_;
+ my ($f, $ff, $t, $term) = @func_args{qw(f ff t term)};
+
+ my %map = (
+ notequals => 'equals',
+ notregexp => 'regexp',
+ notsubstring => 'substring',
+ nowords => 'anywords',
+ nowordssubstr => 'anywordssubstr',
+ );
+
+ my $table;
+ if ($$f eq 'keywords') {
+ $table = "keywords LEFT JOIN keyworddefs"
+ . " ON keywords.keywordid = keyworddefs.id";
+ $$ff = "keyworddefs.name";
+ }
+ else {
+ $table = "bug_$$f";
+ $$ff = "$table.value";
+ }
+
+ $$t = $map{$$t};
+ $self->_do_operator_function(\%func_args);
+ $$term = "bugs.bug_id NOT IN (SELECT bug_id FROM $table WHERE $$term)";
+}
+
+sub _multiselect_multiple {
+ my $self = shift;
+ my %func_args = @_;
+ my ($f, $ff, $t, $v, $term) = @func_args{qw(f ff t v term)};
+
+ my @terms;
+ my $table = "bug_$$f";
+ $$ff = "$table.value";
+
+ foreach my $word (split(/[\s,]+/, $$v)) {
+ $$v = $word;
+ $self->_do_operator_function(\%func_args);
+ push(@terms, "bugs.bug_id IN
+ (SELECT bug_id FROM $table WHERE $$term)");
+ }
+
+ if ($$t eq 'anyexact') {
+ $$term = "(" . join(" OR ", @terms) . ")";
+ }
+ else {
+ $$term = "(" . join(" AND ", @terms) . ")";
+ }
+}
+
+sub _multiselect_nonchanged {
+ my $self = shift;
+ my %func_args = @_;
+ my ($chartid, $f, $ff, $t, $supptables) =
+ @func_args{qw(chartid f ff t supptables)};
+
+ my $table = $$f."_".$$chartid;
+ $$ff = "$table.value";
+
+ $self->_do_operator_function(\%func_args);
+ push(@$supptables, "LEFT JOIN bug_$$f AS $table " .
+ "ON $table.bug_id = bugs.bug_id ");
+}
+
+sub _equals {
+ my $self = shift;
+ my %func_args = @_;
+ my ($ff, $q, $term) = @func_args{qw(ff q term)};
+
+ $$term = "$$ff = $$q";
+}
+
+sub _notequals {
+ my $self = shift;
+ my %func_args = @_;
+ my ($ff, $q, $term) = @func_args{qw(ff q term)};
+
+ $$term = "$$ff != $$q";
+}
+
+sub _casesubstring {
+ my $self = shift;
+ my %func_args = @_;
+ my ($ff, $q, $term) = @func_args{qw(ff q term)};
+ my $dbh = Bugzilla->dbh;
+
+ $$term = $dbh->sql_position($$q, $$ff) . " > 0";
+}
+
+sub _substring {
+ my $self = shift;
+ my %func_args = @_;
+ my ($ff, $q, $term) = @func_args{qw(ff q term)};
+ my $dbh = Bugzilla->dbh;
+
+ $$term = $dbh->sql_iposition($$q, $$ff) . " > 0";
+}
+
+sub _notsubstring {
+ my $self = shift;
+ my %func_args = @_;
+ my ($ff, $q, $term) = @func_args{qw(ff q term)};
+ my $dbh = Bugzilla->dbh;
+
+ $$term = $dbh->sql_iposition($$q, $$ff) . " = 0";
+}
+
+sub _regexp {
+ my $self = shift;
+ my %func_args = @_;
+ my ($ff, $q, $term) = @func_args{qw(ff q term)};
+ my $dbh = Bugzilla->dbh;
+
+ $$term = $dbh->sql_regexp($$ff, $$q);
+}
+
+sub _notregexp {
+ my $self = shift;
+ my %func_args = @_;
+ my ($ff, $q, $term) = @func_args{qw(ff q term)};
+ my $dbh = Bugzilla->dbh;
+
+ $$term = $dbh->sql_not_regexp($$ff, $$q);
+}
+
+sub _lessthan {
+ my $self = shift;
+ my %func_args = @_;
+ my ($ff, $q, $term) = @func_args{qw(ff q term)};
+
+ $$term = "$$ff < $$q";
+}
+
+sub _lessthaneq {
+ my $self = shift;
+ my %func_args = @_;
+ my ($ff, $q, $term) = @func_args{qw(ff q term)};
+
+ $$term = "$$ff <= $$q";
+}
+
+sub _greaterthan {
+ my $self = shift;
+ my %func_args = @_;
+ my ($ff, $q, $term) = @func_args{qw(ff q term)};
+
+ $$term = "$$ff > $$q";
+}
+
+sub _greaterthaneq {
+ my $self = shift;
+ my %func_args = @_;
+ my ($ff, $q, $term) = @func_args{qw(ff q term)};
+
+ $$term = "$$ff >= $$q";
+}
+
+sub _anyexact {
+ my $self = shift;
+ my %func_args = @_;
+ my ($f, $ff, $all_v, $v, $q, $term) = @func_args{qw(f ff all_v v q term)};
+ my $dbh = Bugzilla->dbh;
+
+ my @list;
+ my @all_values = scalar(@$all_v) ? @$all_v : split(',', $$v);
+ foreach my $w (@all_values) {
+ $w = trim($w);
+ if ($w eq "---" && $$f =~ /resolution/) {
+ $w = "";
+ }
+ $$q = $dbh->quote($w);
+ trick_taint($$q);
+ push(@list, $$q);
+ }
+ if (@list) {
+ $$term = $dbh->sql_in($$ff, \@list);
+ }
+}
+
+sub _anywordsubstr {
+ my $self = shift;
+ my %func_args = @_;
+ my ($ff, $v, $term) = @func_args{qw(ff v term)};
+
+ $$term = join(" OR ", @{GetByWordListSubstr($$ff, $$v)});
+}
+
+sub _allwordssubstr {
+ my $self = shift;
+ my %func_args = @_;
+ my ($ff, $v, $term) = @func_args{qw(ff v term)};
+
+ $$term = join(" AND ", @{GetByWordListSubstr($$ff, $$v)});
+}
+
+sub _nowordssubstr {
+ my $self = shift;
+ my %func_args = @_;
+ my ($ff, $v, $term) = @func_args{qw(ff v term)};
+
+ my @list = @{GetByWordListSubstr($$ff, $$v)};
+ if (@list) {
+ $$term = "NOT (" . join(" OR ", @list) . ")";
+ }
+}
+
+sub _anywords {
+ my $self = shift;
+ my %func_args = @_;
+ my ($ff, $v, $term) = @func_args{qw(ff v term)};
+
+ $$term = join(" OR ", @{GetByWordList($$ff, $$v)});
+}
+
+sub _allwords {
+ my $self = shift;
+ my %func_args = @_;
+ my ($ff, $v, $term) = @func_args{qw(ff v term)};
+
+ $$term = join(" AND ", @{GetByWordList($$ff, $$v)});
+}
+
+sub _nowords {
+ my $self = shift;
+ my %func_args = @_;
+ my ($ff, $v, $term) = @func_args{qw(ff v term)};
+
+ my @list = @{GetByWordList($$ff, $$v)};
+ if (@list) {
+ $$term = "NOT (" . join(" OR ", @list) . ")";
+ }
+}
+
+sub _changedbefore_changedafter {
+ my $self = shift;
+ my %func_args = @_;
+ my ($chartid, $f, $ff, $t, $v, $chartfields, $supptables, $term) =
+ @func_args{qw(chartid f ff t v chartfields supptables term)};
+ my $dbh = Bugzilla->dbh;
+
+ my $operator = ($$t =~ /before/) ? '<' : '>';
+ my $table = "act_$$chartid";
+ my $fieldid = $$chartfields{$$f};
+ if (!$fieldid) {
+ ThrowCodeError("invalid_field_name", {field => $$f});
+ }
+ push(@$supptables, "LEFT JOIN bugs_activity AS $table " .
+ "ON $table.bug_id = bugs.bug_id " .
+ "AND $table.fieldid = $fieldid " .
+ "AND $table.bug_when $operator " .
+ $dbh->quote(SqlifyDate($$v)) );
+ $$term = "($table.bug_when IS NOT NULL)";
+}
+
+sub _changedfrom_changedto {
+ my $self = shift;
+ my %func_args = @_;
+ my ($chartid, $chartfields, $f, $t, $v, $q, $supptables, $term) =
+ @func_args{qw(chartid chartfields f t v q supptables term)};
+
+ my $operator = ($$t =~ /from/) ? 'removed' : 'added';
+ my $table = "act_$$chartid";
+ my $fieldid = $$chartfields{$$f};
+ if (!$fieldid) {
+ ThrowCodeError("invalid_field_name", {field => $$f});
+ }
+ push(@$supptables, "LEFT JOIN bugs_activity AS $table " .
+ "ON $table.bug_id = bugs.bug_id " .
+ "AND $table.fieldid = $fieldid " .
+ "AND $table.$operator = $$q");
+ $$term = "($table.bug_when IS NOT NULL)";
+}
+
+sub _changedby {
+ my $self = shift;
+ my %func_args = @_;
+ my ($chartid, $chartfields, $f, $v, $supptables, $term) =
+ @func_args{qw(chartid chartfields f v supptables term)};
+
+ my $table = "act_$$chartid";
+ my $fieldid = $$chartfields{$$f};
+ if (!$fieldid) {
+ ThrowCodeError("invalid_field_name", {field => $$f});
+ }
+ my $id = login_to_id($$v, THROW_ERROR);
+ push(@$supptables, "LEFT JOIN bugs_activity AS $table " .
+ "ON $table.bug_id = bugs.bug_id " .
+ "AND $table.fieldid = $fieldid " .
+ "AND $table.who = $id");
+ $$term = "($table.bug_when IS NOT NULL)";
+}
+
+1;
diff --git a/Bugzilla/Search/Quicksearch.pm b/Bugzilla/Search/Quicksearch.pm
new file mode 100644
index 000000000..c501ed3ba
--- /dev/null
+++ b/Bugzilla/Search/Quicksearch.pm
@@ -0,0 +1,617 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s): C. Begle
+# Jesse Ruderman
+# Andreas Franke <afranke@mathweb.org>
+# Stephen Lee <slee@uk.bnsmc.com>
+# Marc Schumann <wurblzap@gmail.com>
+
+package Bugzilla::Search::Quicksearch;
+
+# Make it harder for us to do dangerous things in Perl.
+use strict;
+
+use Bugzilla::Error;
+use Bugzilla::Constants;
+use Bugzilla::Keyword;
+use Bugzilla::Status;
+use Bugzilla::Field;
+use Bugzilla::Util;
+
+use List::Util qw(min max);
+use List::MoreUtils qw(firstidx);
+
+use base qw(Exporter);
+@Bugzilla::Search::Quicksearch::EXPORT = qw(quicksearch);
+
+# 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"
+};
+
+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->get_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',
+};
+
+# We might want to put this into localconfig or somewhere
+use constant PRODUCT_EXCEPTIONS => (
+ 'row', # [Browser]
+ # ^^^
+ 'new', # [MailNews]
+ # ^^^
+);
+use constant COMPONENT_EXCEPTIONS => (
+ 'hang' # [Bugzilla: Component/Keyword Changes]
+ # ^^^^
+);
+
+# Quicksearch-wide globals for boolean charts.
+our ($chart, $and, $or);
+
+sub quicksearch {
+ my ($searchstring) = (@_);
+ my $cgi = Bugzilla->cgi;
+
+ $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);
+
+ # Globally translate " AND ", " OR ", " NOT " to space, pipe, dash.
+ $searchstring =~ s/\s+AND\s+/ /g;
+ $searchstring =~ s/\s+OR\s+/|/g;
+ $searchstring =~ s/\s+NOT\s+/ -/g;
+
+ my @words = splitString($searchstring);
+ _handle_status_and_resolution(\@words);
+
+ my (@unknownFields, %ambiguous_fields);
+
+ # Loop over all main-level QuickSearch words.
+ foreach my $qsword (@words) {
+ my $negate = substr($qsword, 0, 1) eq '-';
+ if ($negate) {
+ $qsword = substr($qsword, 1);
+ }
+
+ # No special first char
+ if (!_handle_special_first_chars($qsword, $negate)) {
+ # Split by '|' to get all operands for a boolean OR.
+ foreach my $or_operand (split(/\|/, $qsword)) {
+ if (!_handle_field_names($or_operand, $negate,
+ \@unknownFields,
+ \%ambiguous_fields))
+ {
+ # Having ruled out the special cases, we may now split
+ # by comma, which is another legal boolean OR indicator.
+ foreach my $word (split(/,/, $or_operand)) {
+ if (!_special_field_syntax($word, $negate)) {
+ _default_quicksearch_word($word, $negate);
+ }
+ _handle_urls($word, $negate);
+ }
+ }
+ }
+ }
+ $chart++;
+ $and = 0;
+ $or = 0;
+ } # foreach (@words)
+
+ # Inform user about any unknown fields
+ if (scalar(@unknownFields) || scalar(keys %ambiguous_fields)) {
+ ThrowUserError("quicksearch_unknown_field",
+ { unknown => \@unknownFields,
+ ambiguous => \%ambiguous_fields });
+ }
+
+ # Make sure we have some query terms left
+ scalar($cgi->param())>0 || ThrowUserError("buglist_parameters_required");
+ }
+
+ # List of quicksearch-specific CGI parameters to get rid of.
+ my @params_to_strip = ('quicksearch', 'load', 'run');
+ my $modified_query_string = $cgi->canonicalise_query(@params_to_strip);
+
+ if ($cgi->param('load')) {
+ 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;
+}
+
+##########################
+# Parts of quicksearch() #
+##########################
+
+sub _bug_numbers_only {
+ my $searchstring = shift;
+ my $cgi = Bugzilla->cgi;
+ # Allow separation by comma or whitespace.
+ $searchstring =~ s/[,\s]+/,/g;
+
+ if ($searchstring !~ /,/) {
+ # 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 $is_alias = Bugzilla->dbh->selectrow_array(
+ q{SELECT 1 FROM bugs WHERE alias = ?}, undef, $alias);
+ if ($is_alias) {
+ $alias = url_quote($alias);
+ print Bugzilla->cgi->redirect(
+ -uri => correct_urlbase() . "show_bug.cgi?id=$alias");
+ exit;
+ }
+ }
+}
+
+sub _handle_status_and_resolution {
+ my ($words) = @_;
+ my $legal_statuses = get_legal_field_values('bug_status');
+ my $legal_resolutions = get_legal_field_values('resolution');
+
+ my @openStates = BUG_STATE_OPEN;
+ my @closedStates;
+ my (%states, %resolutions);
+
+ foreach (@$legal_statuses) {
+ push(@closedStates, $_) unless is_open_state($_);
+ }
+ foreach (@openStates) { $states{$_} = 1 }
+ if ($words->[0] eq 'ALL') {
+ foreach (@$legal_statuses) { $states{$_} = 1 }
+ shift @$words;
+ }
+ elsif ($words->[0] eq 'OPEN') {
+ shift @$words;
+ }
+ elsif ($words->[0] =~ /^[A-Z_]+(,[_A-Z]+)*$/) {
+ # e.g. CON,IN_PR,FIX
+ undef %states;
+ if (matchPrefixes(\%states,
+ \%resolutions,
+ [split(/,/, $words->[0])],
+ $legal_statuses,
+ $legal_resolutions)) {
+ shift @$words;
+ }
+ else {
+ # Carry on if no match found
+ foreach (@openStates) { $states{$_} = 1 }
+ }
+ }
+ else {
+ # Default: search for unresolved bugs only.
+ # Put custom code here if you would like to change this behaviour.
+ }
+
+ # If we have wanted resolutions, allow closed states
+ if (keys(%resolutions)) {
+ foreach (@closedStates) { $states{$_} = 1 }
+ }
+
+ Bugzilla->cgi->param('bug_status', keys(%states));
+ Bugzilla->cgi->param('resolution', keys(%resolutions));
+}
+
+
+sub _handle_special_first_chars {
+ my ($qsword, $negate) = @_;
+
+ 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);
+ 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;
+ }
+ return 0;
+}
+
+sub _handle_field_names {
+ my ($or_operand, $negate, $unknownFields, $ambiguous_fields) = @_;
+
+ # Flag and requestee shortcut
+ if ($or_operand =~ /^(?:flag:)?([^\?]+\?)([^\?]*)$/) {
+ addChart('flagtypes.name', 'substring', $1, $negate);
+ $chart++; $and = $or = 0; # Next chart for boolean AND
+ addChart('requestees.login_name', 'substring', $2, $negate);
+ return 1;
+ }
+
+ # generic field1,field2,field3:value1,value2 notation
+ if ($or_operand =~ /^([^:]+):([^:]+)$/) {
+ my @fields = split(/,/, $1);
+ my @values = split(/,/, $2);
+ foreach my $field (@fields) {
+ my $translated = _translate_field_name($field);
+ # Skip and record any unknown fields
+ if (!defined $translated) {
+ push(@$unknownFields, $field);
+ next;
+ }
+ # 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;
+ next;
+ }
+ foreach my $value (@values) {
+ my $operator = FIELD_OPERATOR->{$translated} || 'substring';
+ addChart($translated, $operator, $value, $negate);
+ }
+ }
+ return 1;
+ }
+
+ return 0;
+}
+
+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;
+ }
+
+ # 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);
+
+ 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;
+}
+
+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])
+ }
+
+ addChart('priority', 'anyexact', $prios, $negate);
+ return 1;
+ }
+ return 0;
+}
+
+sub _default_quicksearch_word {
+ 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('status_whiteboard', 'substring', $word, $negate);
+ addChart('content', 'matches', _matches_phrase($word), $negate);
+}
+
+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);
+ }
+}
+
+###########################################################################
+# Helpers
+###########################################################################
+
+# Split string on whitespace, retaining quoted strings as one
+sub splitString {
+ my $string = shift;
+ my @quoteparts;
+ my @parts;
+ my $i = 0;
+
+ # Now split on quote sign; be tolerant about unclosed quotes
+ @quoteparts = split(/"/, $string);
+ foreach my $part (@quoteparts) {
+ # After every odd quote, quote special chars
+ if ($i++ %2) {
+ $part = url_quote($part);
+ # Protect the minus sign from being considered
+ # as negation, in quotes.
+ $part =~ s/(?<=^)\-/%2D/;
+ }
+ }
+ # Join again
+ $string = join('"', @quoteparts);
+
+ # Now split on unescaped whitespace
+ @parts = split(/\s+/, $string);
+ foreach (@parts) {
+ # Protect plus signs from becoming a blank.
+ # If "+" appears as the first character, leave it alone
+ # as it has a special meaning. Strings which start with
+ # "+" must be quoted.
+ s/(?<!^)\+/%2B/g;
+ # Remove quotes
+ s/"//g;
+ }
+ return @parts;
+}
+
+# Quote and escape a phrase appropriately for a "content matches" search.
+sub _matches_phrase {
+ my ($phrase) = @_;
+ $phrase =~ s/"/\\"/g;
+ return "\"$phrase\"";
+}
+
+# Expand found prefixes to states or resolutions
+sub matchPrefixes {
+ my $hr_states = shift;
+ my $hr_resolutions = shift;
+ my $ar_prefixes = shift;
+ my $ar_check_states = shift;
+ my $ar_check_resolutions = shift;
+ my $foundMatch = 0;
+
+ foreach my $prefix (@$ar_prefixes) {
+ foreach (@$ar_check_states) {
+ if (/^$prefix/) {
+ $$hr_states{$_} = 1;
+ $foundMatch = 1;
+ }
+ }
+ foreach (@$ar_check_resolutions) {
+ if (/^$prefix/) {
+ $$hr_resolutions{$_} = 1;
+ $foundMatch = 1;
+ }
+ }
+ }
+ return $foundMatch;
+}
+
+# Negate comparison type
+sub negateComparisonType {
+ my $comparisonType = shift;
+
+ if ($comparisonType eq '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++;
+ }
+}
+
+# Create the CGI parameters for a boolean chart
+sub makeChart {
+ my ($expr, $field, $type, $value) = @_;
+
+ my $cgi = Bugzilla->cgi;
+ $cgi->param("field$expr", $field);
+ $cgi->param("type$expr", $type);
+ $cgi->param("value$expr", url_decode($value));
+}
+
+1;
diff --git a/Bugzilla/Search/Recent.pm b/Bugzilla/Search/Recent.pm
new file mode 100644
index 000000000..89d9cf6ff
--- /dev/null
+++ b/Bugzilla/Search/Recent.pm
@@ -0,0 +1,166 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by the Initial Developer are Copyright (C) 2010 the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::Search::Recent;
+use strict;
+use base qw(Bugzilla::Object);
+
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Util;
+
+#############
+# Constants #
+#############
+
+use constant DB_TABLE => 'profile_search';
+use constant LIST_ORDER => 'id DESC';
+
+use constant DB_COLUMNS => qw(
+ 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,
+};
+
+use constant UPDATE_COLUMNS => qw(bug_list list_order);
+
+###################
+# DB Manipulation #
+###################
+
+sub create {
+ my $class = shift;
+ my $dbh = Bugzilla->dbh;
+ $dbh->bz_start_transaction();
+ my $search = $class->SUPER::create(@_);
+
+ # Enforce there only being SAVE_NUM_SEARCHES per user.
+ my ($num_searches, $min_id) = $dbh->selectrow_array(
+ 'SELECT COUNT(*), MIN(id) FROM profile_search WHERE user_id = ?',
+ undef, $search->user_id);
+ if ($num_searches > SAVE_NUM_SEARCHES) {
+ $dbh->do('DELETE FROM profile_search WHERE id = ?', undef, $min_id);
+ }
+ $dbh->bz_commit_transaction();
+ return $search;
+}
+
+sub create_placeholder {
+ my $class = shift;
+ return $class->create({ user_id => Bugzilla->user->id,
+ bug_list => '' });
+}
+
+###############
+# Constructor #
+###############
+
+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;
+}
+
+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;
+}
+
+sub new_from_cookie {
+ my ($invocant, $bug_ids) = @_;
+ my $class = ref($invocant) || $invocant;
+
+ my $search = { id => 'cookie',
+ user_id => Bugzilla->user->id,
+ bug_list => join(',', @$bug_ids) };
+
+ bless $search, $class;
+ return $search;
+}
+
+####################
+# Simple Accessors #
+####################
+
+sub bug_list { return [split(',', $_[0]->{'bug_list'})]; }
+sub list_order { return $_[0]->{'list_order'}; }
+sub user_id { return $_[0]->{'user_id'}; }
+
+############
+# Mutators #
+############
+
+sub set_bug_list { $_[0]->set('bug_list', $_[1]); }
+sub set_list_order { $_[0]->set('list_order', $_[1]); }
+
+##############
+# Validators #
+##############
+
+sub _check_user_id {
+ my ($invocant, $id) = @_;
+ require Bugzilla::User;
+ return Bugzilla::User->check({ id => $id })->id;
+}
+
+sub _check_bug_list {
+ my ($invocant, $list) = @_;
+
+ 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]) : '' }
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Search::Recent - A search recently run by a logged-in user.
+
+=head1 SYNOPSIS
+
+ use Bugzilla::Search::Recent;
+
+
+=head1 DESCRIPTION
+
+This is an implementation of L<Bugzilla::Object>, and so has all the
+same methods available as L<Bugzilla::Object>, in addition to what is
+documented below.
diff --git a/Bugzilla/Search/Saved.pm b/Bugzilla/Search/Saved.pm
new file mode 100644
index 000000000..4b46fc75c
--- /dev/null
+++ b/Bugzilla/Search/Saved.pm
@@ -0,0 +1,406 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved.
+# Portions created by Everything Solved are Copyright (C) 2006
+# Everything Solved. All Rights Reserved.
+#
+# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+
+use strict;
+
+package Bugzilla::Search::Saved;
+
+use base qw(Bugzilla::Object);
+
+use Bugzilla::CGI;
+use Bugzilla::Constants;
+use Bugzilla::Group;
+use Bugzilla::Error;
+use Bugzilla::Search qw(IsValidQueryType);
+use Bugzilla::User;
+use Bugzilla::Util;
+
+use Scalar::Util qw(blessed);
+
+#############
+# Constants #
+#############
+
+use constant DB_TABLE => 'namedqueries';
+
+use constant DB_COLUMNS => qw(
+ id
+ userid
+ name
+ query
+ query_type
+);
+
+use constant VALIDATORS => {
+ name => \&_check_name,
+ query => \&_check_query,
+ query_type => \&_check_query_type,
+ link_in_footer => \&_check_link_in_footer,
+};
+
+use constant UPDATE_COLUMNS => qw(name query query_type);
+
+###############
+# Constructor #
+###############
+
+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});
+ }
+ 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', { queryname => $search->name,
+ sharer_id => $search->user->id });
+ }
+
+ return $search;
+}
+
+##############
+# Validators #
+##############
+
+sub _check_link_in_footer { return $_[1] ? 1 : 0; }
+
+sub _check_name {
+ my ($invocant, $name) = @_;
+ $name = trim($name);
+ $name || ThrowUserError("query_name_missing");
+ $name !~ /[<>&]/ || ThrowUserError("illegal_query_name");
+ if (length($name) > MAX_LEN_QUERY_NAME) {
+ ThrowUserError("query_name_too_long");
+ }
+ return $name;
+}
+
+sub _check_query {
+ my ($invocant, $query) = @_;
+ $query || ThrowUserError("buglist_parameters_required");
+ my $cgi = new Bugzilla::CGI($query);
+ $cgi->clean_search_url;
+ # Don't store the query name as a parameter.
+ $cgi->delete('known_name');
+ return $cgi->query_string;
+}
+
+sub _check_query_type {
+ my ($invocant, $type) = @_;
+ # Right now the only query type is LIST_OF_BUGS.
+ return $type ? LIST_OF_BUGS : QUERY_LIST;
+}
+
+#########################
+# Database Manipulation #
+#########################
+
+sub create {
+ my $class = shift;
+ Bugzilla->login(LOGIN_REQUIRED);
+ my $dbh = Bugzilla->dbh;
+ $class->check_required_create_fields(@_);
+ $dbh->bz_start_transaction();
+ my $params = $class->run_create_validators(@_);
+
+ # Right now you can only create a Saved Search for the current user.
+ $params->{userid} = Bugzilla->user->id;
+
+ my $lif = delete $params->{link_in_footer};
+ my $obj = $class->insert_create_data($params);
+ if ($lif) {
+ $dbh->do('INSERT INTO namedqueries_link_in_footer
+ (user_id, namedquery_id) VALUES (?,?)',
+ undef, $params->{userid}, $obj->id);
+ }
+ $dbh->bz_commit_transaction();
+
+ return $obj;
+}
+
+sub 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);
+ }
+
+ $dbh->bz_commit_transaction();
+}
+
+sub preload {
+ my ($searches) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ return unless scalar @$searches;
+
+ my @query_ids = map { $_->id } @$searches;
+ my $queries_in_footer = $dbh->selectcol_arrayref(
+ 'SELECT namedquery_id
+ FROM namedqueries_link_in_footer
+ WHERE ' . $dbh->sql_in('namedquery_id', \@query_ids) . ' AND user_id = ?',
+ undef, Bugzilla->user->id);
+
+ my %links_in_footer = map { $_ => 1 } @$queries_in_footer;
+ foreach my $query (@$searches) {
+ $query->{link_in_footer} = ($links_in_footer{$query->id}) ? 1 : 0;
+ }
+}
+#####################
+# Complex Accessors #
+#####################
+
+sub edit_link {
+ my ($self) = @_;
+ return $self->{edit_link} if defined $self->{edit_link};
+ my $cgi = new Bugzilla::CGI($self->url);
+ if (!$cgi->param('query_type')
+ || !IsValidQueryType($cgi->param('query_type')))
+ {
+ $cgi->param('query_type', 'advanced');
+ }
+ $self->{edit_link} = $cgi->canonicalise_query;
+ return $self->{edit_link};
+}
+
+sub used_in_whine {
+ my ($self) = @_;
+ return $self->{used_in_whine} if exists $self->{used_in_whine};
+ ($self->{used_in_whine}) = Bugzilla->dbh->selectrow_array(
+ 'SELECT 1 FROM whine_events INNER JOIN whine_queries
+ ON whine_events.id = whine_queries.eventid
+ WHERE whine_events.owner_userid = ? AND query_name = ?', undef,
+ $self->{userid}, $self->name) || 0;
+ return $self->{used_in_whine};
+}
+
+sub link_in_footer {
+ my ($self, $user) = @_;
+ # We only cache link_in_footer for the current Bugzilla->user.
+ return $self->{link_in_footer} if exists $self->{link_in_footer} && !$user;
+ my $user_id = $user ? $user->id : Bugzilla->user->id;
+ my $link_in_footer = Bugzilla->dbh->selectrow_array(
+ 'SELECT 1 FROM namedqueries_link_in_footer
+ WHERE namedquery_id = ? AND user_id = ?',
+ undef, $self->id, $user_id) || 0;
+ $self->{link_in_footer} = $link_in_footer if !$user;
+ return $link_in_footer;
+}
+
+sub shared_with_group {
+ my ($self) = @_;
+ return $self->{shared_with_group} if exists $self->{shared_with_group};
+ # Bugzilla only currently supports sharing with one group, even
+ # though the database backend allows for an infinite number.
+ my ($group_id) = Bugzilla->dbh->selectrow_array(
+ 'SELECT group_id FROM namedquery_group_map WHERE namedquery_id = ?',
+ undef, $self->id);
+ $self->{shared_with_group} = $group_id ? new Bugzilla::Group($group_id)
+ : undef;
+ return $self->{shared_with_group};
+}
+
+sub shared_with_users {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ if (!exists $self->{shared_with_users}) {
+ $self->{shared_with_users} =
+ $dbh->selectrow_array('SELECT COUNT(*)
+ FROM namedqueries_link_in_footer
+ INNER JOIN namedqueries
+ ON namedquery_id = id
+ WHERE namedquery_id = ?
+ AND user_id != userid',
+ undef, $self->id);
+ }
+ return $self->{shared_with_users};
+}
+
+####################
+# Simple Accessors #
+####################
+
+sub type { return $_[0]->{'query_type'}; }
+sub url { return $_[0]->{'query'}; }
+
+sub user {
+ my ($self) = @_;
+ return $self->{user} if defined $self->{user};
+ $self->{user} = new Bugzilla::User($self->{userid});
+ return $self->{user};
+}
+
+############
+# Mutators #
+############
+
+sub set_name { $_[0]->set('name', $_[1]); }
+sub set_url { $_[0]->set('query', $_[1]); }
+sub set_query_type { $_[0]->set('query_type', $_[1]); }
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Search::Saved - A saved search
+
+=head1 SYNOPSIS
+
+ use Bugzilla::Search::Saved;
+
+ my $query = new Bugzilla::Search::Saved($query_id);
+
+ my $edit_link = $query->edit_link;
+ my $search_url = $query->url;
+ my $owner = $query->user;
+ my $num_subscribers = $query->shared_with_users;
+
+=head1 DESCRIPTION
+
+This module exists to represent a L<Bugzilla::Search> that has been
+saved to the database.
+
+This is an implementation of L<Bugzilla::Object>, and so has all the
+same methods available as L<Bugzilla::Object>, in addition to what is
+documented below.
+
+=head1 METHODS
+
+=head2 Constructors and Database Manipulation
+
+=over
+
+=item C<new>
+
+Takes either an id, or the named parameters C<user> and C<name>.
+C<user> can be either a L<Bugzilla::User> object or a numeric user id.
+
+See also: L<Bugzilla::Object/new>.
+
+=item C<preload>
+
+Sets C<link_in_footer> for all given saved searches at once, for the
+currently logged in user. This is much faster than calling this method
+for each saved search individually.
+
+=back
+
+
+=head2 Accessors
+
+These return data about the object, without modifying the object.
+
+=over
+
+=item C<edit_link>
+
+A url with which you can edit the search.
+
+=item C<url>
+
+The CGI parameters for the search, as a string.
+
+=item C<link_in_footer>
+
+Whether or not this search should be displayed in the footer for the
+I<current user> (not the owner of the search, but the person actually
+using Bugzilla right now).
+
+=item C<type>
+
+The numeric id of the type of search this is (from L<Bugzilla::Constants>).
+
+=item C<shared_with_group>
+
+The L<Bugzilla::Group> that this search is shared with. C<undef> if
+this search isn't shared.
+
+=item C<shared_with_users>
+
+Returns how many users (besides the author of the saved search) are
+using the saved search, i.e. have it displayed in their footer.
+
+=back
diff --git a/Bugzilla/Series.pm b/Bugzilla/Series.pm
new file mode 100644
index 000000000..7168bcb7e
--- /dev/null
+++ b/Bugzilla/Series.pm
@@ -0,0 +1,286 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Gervase Markham <gerv@gerv.net>
+# Lance Larsh <lance.larsh@oracle.com>
+
+use strict;
+
+# This module implements a series - a set of data to be plotted on a chart.
+#
+# This Series is in the database if and only if self->{'series_id'} is defined.
+# Note that the series being in the database does not mean that the fields of
+# this object are the same as the DB entries, as the object may have been
+# altered.
+
+package Bugzilla::Series;
+
+use Bugzilla::Error;
+use Bugzilla::Util;
+
+# This is a hack so that we can re-use the rename_field_value
+# code from Bugzilla::Search::Saved.
+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(@_);
+ }
+ 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;
+ }
+}
+
+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'}) = @_;
+
+ # 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');
+}
+
+sub writeToDatabase {
+ my $self = shift;
+
+ my $dbh = Bugzilla->dbh;
+ $dbh->bz_start_transaction();
+
+ my $category_id = getCategoryID($self->{'category'});
+ my $subcategory_id = getCategoryID($self->{'subcategory'});
+
+ my $exists;
+ if ($self->{'series_id'}) {
+ $exists =
+ $dbh->selectrow_array("SELECT series_id FROM series
+ WHERE series_id = $self->{'series_id'}");
+ }
+
+ # Is this already in the database?
+ if ($exists) {
+ # Update existing series
+ my $dbh = Bugzilla->dbh;
+ $dbh->do("UPDATE series SET " .
+ "category = ?, subcategory = ?," .
+ "name = ?, frequency = ?, is_public = ? " .
+ "WHERE series_id = ?", undef,
+ $category_id, $subcategory_id, $self->{'name'},
+ $self->{'frequency'}, $self->{'public'},
+ $self->{'series_id'});
+ }
+ else {
+ # Insert the new series into the series table
+ $dbh->do("INSERT INTO series (creator, category, subcategory, " .
+ "name, frequency, query, is_public) VALUES " .
+ "(?, ?, ?, ?, ?, ?, ?)", undef,
+ $self->{'creator_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 $category_id = getCategoryID($self->{'category'});
+ my $subcategory_id = getCategoryID($self->{'subcategory'});
+
+ trick_taint($self->{'name'});
+ my $series_id = $dbh->selectrow_array("SELECT series_id " .
+ "FROM series WHERE category = $category_id " .
+ "AND subcategory = $subcategory_id AND name = " .
+ $dbh->quote($self->{'name'}));
+
+ return($series_id);
+}
+
+# Get a category or subcategory IDs, creating the category if it doesn't exist.
+sub getCategoryID {
+ my ($category) = @_;
+ my $category_id;
+ my $dbh = Bugzilla->dbh;
+
+ # This seems for the best idiom for "Do A. Then maybe do B and A again."
+ while (1) {
+ # We are quoting this to put it in the DB, so we can remove taint
+ trick_taint($category);
+
+ $category_id = $dbh->selectrow_array("SELECT id " .
+ "from series_categories " .
+ "WHERE name =" . $dbh->quote($category));
+
+ last if defined($category_id);
+
+ $dbh->do("INSERT INTO series_categories (name) " .
+ "VALUES (" . $dbh->quote($category) . ")");
+ }
+
+ return $category_id;
+}
+
+##########
+# Methods
+##########
+sub id { return $_[0]->{'series_id'}; }
+sub name { return $_[0]->{'name'}; }
+
+sub creator {
+ my $self = shift;
+
+ 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;
+
+ $dbh->do('DELETE FROM series WHERE series_id = ?', undef, $self->id);
+}
+
+1;
diff --git a/Bugzilla/Status.pm b/Bugzilla/Status.pm
new file mode 100644
index 000000000..ffef600de
--- /dev/null
+++ b/Bugzilla/Status.pm
@@ -0,0 +1,317 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Frédéric Buclin.
+# Portions created by Frédéric Buclin are Copyright (C) 2007
+# Frédéric Buclin. All Rights Reserved.
+#
+# Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
+
+use strict;
+
+package Bugzilla::Status;
+
+use Bugzilla::Error;
+# 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 base qw(Bugzilla::Field::Choice Exporter);
+@Bugzilla::Status::EXPORT = qw(
+ BUG_STATE_OPEN
+ SPECIAL_STATUS_WORKFLOW_ACTIONS
+
+ is_open_state
+ closed_bug_statuses
+);
+
+################################
+##### Initialization #####
+################################
+
+use constant SPECIAL_STATUS_WORKFLOW_ACTIONS => qw(
+ 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');
+}
+
+sub VALIDATORS {
+ my $invocant = shift;
+ my $validators = $invocant->SUPER::VALIDATORS;
+ $validators->{is_open} = \&Bugzilla::Object::check_boolean;
+ $validators->{value} = \&_check_value;
+ return $validators;
+}
+
+#########################
+# Database Manipulation #
+#########################
+
+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;
+}
+
+sub remove_from_db {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+ my $id = $self->id;
+ $dbh->bz_start_transaction();
+ $self->SUPER::remove_from_db();
+ $dbh->do('DELETE FROM status_workflow
+ WHERE old_status = ? OR new_status = ?',
+ undef, $id, $id);
+ $dbh->bz_commit_transaction();
+ delete Bugzilla->request_cache->{status_bug_state_open};
+}
+
+###############################
+##### Accessors ####
+###############################
+
+sub is_active { return $_[0]->{'isactive'}; }
+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;
+}
+
+##############
+# Validators #
+##############
+
+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;
+}
+
+
+###############################
+##### Methods ####
+###############################
+
+sub BUG_STATE_OPEN {
+ my $dbh = Bugzilla->dbh;
+ my $cache = Bugzilla->request_cache;
+ $cache->{status_bug_state_open} ||=
+ $dbh->selectcol_arrayref('SELECT value FROM bug_status
+ WHERE is_open = 1');
+ return @{ $cache->{status_bug_state_open} };
+}
+
+# Tells you whether or not the argument is a valid "open" state.
+sub is_open_state {
+ my ($state) = @_;
+ return (grep($_ eq $state, BUG_STATE_OPEN) ? 1 : 0);
+}
+
+sub closed_bug_statuses {
+ my @bug_statuses = Bugzilla::Status->get_all;
+ @bug_statuses = grep { !$_->is_open } @bug_statuses;
+ return @bug_statuses;
+}
+
+sub can_change_to {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ if (!ref($self) || !defined $self->{'can_change_to'}) {
+ my ($cond, @args, $self_exists);
+ if (ref($self)) {
+ $cond = '= ?';
+ push(@args, $self->id);
+ $self_exists = 1;
+ }
+ else {
+ $cond = 'IS NULL';
+ # Let's do it so that the code below works in all cases.
+ $self = {};
+ }
+
+ my $new_status_ids = $dbh->selectcol_arrayref("SELECT new_status
+ FROM status_workflow
+ INNER JOIN bug_status
+ ON id = new_status
+ WHERE isactive = 1
+ AND old_status $cond
+ ORDER BY sortkey",
+ undef, @args);
+
+ # Allow the bug status to remain unchanged.
+ push(@$new_status_ids, $self->id) if $self_exists;
+ $self->{'can_change_to'} = Bugzilla::Status->new_from_list($new_status_ids);
+ }
+
+ return $self->{'can_change_to'};
+}
+
+sub comment_required_on_change_from {
+ my ($self, $old_status) = @_;
+ my ($cond, $values) = $self->_status_condition($old_status);
+
+ my ($require_comment) = Bugzilla->dbh->selectrow_array(
+ "SELECT require_comment FROM status_workflow
+ WHERE $cond", undef, @$values);
+ return $require_comment;
+}
+
+# Used as a helper for various functions that have to deal with old_status
+# sometimes being NULL and sometimes having a value.
+sub _status_condition {
+ my ($self, $old_status) = @_;
+ my @values;
+ my $cond = 'old_status IS NULL';
+ # For newly-filed bugs
+ if ($old_status) {
+ $cond = 'old_status = ?';
+ push(@values, $old_status->id);
+ }
+ $cond .= " AND new_status = ?";
+ push(@values, $self->id);
+ return ($cond, \@values);
+}
+
+sub add_missing_bug_status_transitions {
+ my $bug_status = shift || Bugzilla->params->{'duplicate_or_move_bug_status'};
+ my $dbh = Bugzilla->dbh;
+ my $new_status = new Bugzilla::Status({name => $bug_status});
+ # Silently discard invalid bug statuses.
+ $new_status || return;
+
+ my $missing_statuses = $dbh->selectcol_arrayref('SELECT id
+ FROM bug_status
+ LEFT JOIN status_workflow
+ ON old_status = id
+ AND new_status = ?
+ WHERE old_status IS NULL',
+ undef, $new_status->id);
+
+ my $sth = $dbh->prepare('INSERT INTO status_workflow
+ (old_status, new_status) VALUES (?, ?)');
+
+ foreach my $old_status_id (@$missing_statuses) {
+ next if ($old_status_id == $new_status->id);
+ $sth->execute($old_status_id, $new_status->id);
+ }
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Status - Bug status class.
+
+=head1 SYNOPSIS
+
+ use Bugzilla::Status;
+
+ my $bug_status = new Bugzilla::Status({ name => 'IN_PROGRESS' });
+ my $bug_status = new Bugzilla::Status(4);
+
+ my @closed_bug_statuses = closed_bug_statuses();
+
+ Bugzilla::Status::add_missing_bug_status_transitions($bug_status);
+
+=head1 DESCRIPTION
+
+Status.pm represents a bug status object. It is an implementation
+of L<Bugzilla::Object>, and thus provides all methods that
+L<Bugzilla::Object> provides.
+
+The methods that are specific to C<Bugzilla::Status> are listed
+below.
+
+=head1 METHODS
+
+=over
+
+=item C<closed_bug_statuses>
+
+ Description: Returns a list of C<Bugzilla::Status> objects which can have
+ a resolution associated with them ("closed" bug statuses).
+
+ Params: none.
+
+ Returns: A list of Bugzilla::Status objects.
+
+=item C<can_change_to>
+
+ Description: Returns the list of active statuses a bug can be changed to
+ given the current bug status. If this method is called as a
+ class method, then it returns all bug statuses available on
+ bug creation.
+
+ Params: none.
+
+ Returns: A list of Bugzilla::Status objects.
+
+=item C<comment_required_on_change_from>
+
+=over
+
+=item B<Description>
+
+Checks if a comment is required to change to this status from another
+status, according to the current settings in the workflow.
+
+Note that this doesn't implement the checks enforced by the various
+C<commenton> parameters--those are checked by internal checks in
+L<Bugzilla::Bug>.
+
+=item B<Params>
+
+C<$old_status> - The status you're changing from.
+
+=item B<Returns>
+
+C<1> if a comment is required on this change, C<0> if not.
+
+=back
+
+=item C<add_missing_bug_status_transitions>
+
+ Description: Insert all missing transitions to a given bug status.
+
+ Params: $bug_status - The value (name) of a bug status.
+
+ Returns: nothing.
+
+=back
+
+=cut
diff --git a/Bugzilla/Template.pm b/Bugzilla/Template.pm
new file mode 100644
index 000000000..4eaa05d29
--- /dev/null
+++ b/Bugzilla/Template.pm
@@ -0,0 +1,1120 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Dan Mosedale <dmose@mozilla.org>
+# Jacob Steenhagen <jake@bugzilla.org>
+# Bradley Baetz <bbaetz@student.usyd.edu.au>
+# Christopher Aillon <christopher@aillon.com>
+# Tobias Burnus <burnus@net-b.de>
+# Myk Melez <myk@mozilla.org>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+# Frédéric Buclin <LpSolit@gmail.com>
+# Greg Hendricks <ghendricks@novell.com>
+# David D. Kilzer <ddkilzer@kilzer.net>
+
+
+package Bugzilla::Template;
+
+use strict;
+
+use Bugzilla::Bug;
+use Bugzilla::Constants;
+use Bugzilla::Hook;
+use Bugzilla::Install::Requirements;
+use Bugzilla::Install::Util qw(install_string template_include_path
+ include_languages);
+use Bugzilla::Keyword;
+use Bugzilla::Util;
+use Bugzilla::User;
+use Bugzilla::Error;
+use Bugzilla::Search;
+use Bugzilla::Status;
+use Bugzilla::Token;
+
+use Cwd qw(abs_path);
+use MIME::Base64;
+use Date::Format ();
+use File::Basename qw(basename dirname);
+use File::Find;
+use File::Path qw(rmtree mkpath);
+use File::Spec;
+use IO::Dir;
+use List::MoreUtils qw(firstidx);
+use Scalar::Util qw(blessed);
+
+use base qw(Template);
+
+# Convert the constants in the Bugzilla::Constants module into a hash we can
+# pass to the template object for reflection into its "constants" namespace
+# (which is like its "variables" namespace, but for constants). To do so, we
+# traverse the arrays of exported and exportable symbols and ignoring the rest
+# (which, if Constants.pm exports only constants, as it should, will be nothing else).
+sub _load_constants {
+ my %constants;
+ foreach my $constant (@Bugzilla::Constants::EXPORT,
+ @Bugzilla::Constants::EXPORT_OK)
+ {
+ if (ref Bugzilla::Constants->$constant) {
+ $constants{$constant} = Bugzilla::Constants->$constant;
+ }
+ else {
+ my @list = (Bugzilla::Constants->$constant);
+ $constants{$constant} = (scalar(@list) == 1) ? $list[0] : \@list;
+ }
+ }
+ return \%constants;
+}
+
+# Returns the path to the templates based on the Accept-Language
+# settings of the user and of the available languages
+# If no Accept-Language is present it uses the defined default
+# Templates may also be found in the extensions/ tree
+sub _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"};
+}
+
+sub get_format {
+ my $self = shift;
+ my ($template, $format, $ctype) = @_;
+
+ $ctype ||= 'html';
+ $format ||= '';
+
+ # Security - allow letters and a hyphen only
+ $ctype =~ s/[^a-zA-Z\-]//g;
+ $format =~ s/[^a-zA-Z\-]//g;
+ trick_taint($ctype);
+ trick_taint($format);
+
+ $template .= ($format ? "-$format" : "");
+ $template .= ".$ctype.tmpl";
+
+ # Now check that the template actually exists. We only want to check
+ # if the template exists; any other errors (eg parse errors) will
+ # end up being detected later.
+ eval {
+ $self->context->template($template);
+ };
+ # This parsing may seem fragile, but it's OK:
+ # http://lists.template-toolkit.org/pipermail/templates/2003-March/004370.html
+ # Even if it is wrong, any sort of error is going to cause a failure
+ # eventually, so the only issue would be an incorrect error message
+ if ($@ && $@->info =~ /: not found$/) {
+ ThrowUserError('format_not_found', {'format' => $format,
+ 'ctype' => $ctype});
+ }
+
+ # Else, just return the info
+ return
+ {
+ 'template' => $template,
+ 'extension' => $ctype,
+ 'ctype' => Bugzilla::Constants::contenttypes->{$ctype}
+ };
+}
+
+# This routine quoteUrls contains inspirations from the HTML::FromText CPAN
+# module by Gareth Rees <garethr@cre.canon.co.uk>. It has been heavily hacked,
+# all that is really recognizable from the original is bits of the regular
+# expressions.
+# This has been rewritten to be faster, mainly by substituting 'as we go'.
+# If you want to modify this routine, read the comments carefully
+
+sub quoteUrls {
+ my ($text, $bug, $comment) = (@_);
+ return $text unless $text;
+
+ # We use /g for speed, but uris can have other things inside them
+ # (http://foo/bug#3 for example). Filtering that out filters valid
+ # bug refs out, so we have to do replacements.
+ # mailto can't contain space or #, so we don't have to bother for that
+ # Do this by escaping \0 to \1\0, and replacing matches with \0\0$count\0\0
+ # \0 is used because it's unlikely to occur in the text, so the cost of
+ # doing this should be very small
+
+ # escape the 2nd escape char we're using
+ my $chr1 = chr(1);
+ $text =~ s/\0/$chr1\0/g;
+
+ # However, note that adding the title (for buglinks) can affect things
+ # In particular, attachment matches go before bug titles, so that titles
+ # with 'attachment 1' don't double match.
+ # Dupe checks go afterwards, because that uses ^ and \Z, which won't occur
+ # if it was substituted as a bug title (since that always involve leading
+ # and trailing text)
+
+ # Because of entities, it's easier (and quicker) to do this before escaping
+
+ my @things;
+ my $count = 0;
+ my $tmp;
+
+ my @hook_regexes;
+ Bugzilla::Hook::process('bug_format_comment',
+ { text => \$text, bug => $bug, regexes => \@hook_regexes,
+ comment => $comment });
+
+ 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]}))
+ && ("\0\0" . ($count-1) . "\0\0")/egx;
+ }
+ else {
+ $text =~ s/$match/($things[$count++] = $replace)
+ && ("\0\0" . ($count-1) . "\0\0")/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
+ ~($things[$count++] = get_bug_link($3, $1, { comment_num => $5 })) &&
+ ("\0\0" . ($count-1) . "\0\0")
+ ~egox;
+
+ # non-mailto protocols
+ my $safe_protocols = join('|', SAFE_PROTOCOLS);
+ my $protocol_re = qr/($safe_protocols)/i;
+
+ $text =~ s~\b(${protocol_re}: # The protocol:
+ [^\s<>\"]+ # Any non-whitespace
+ [\w\/]) # so that we end in \w or /
+ ~($tmp = html_quote($1)) &&
+ ($things[$count++] = "<a href=\"$tmp\">$tmp</a>") &&
+ ("\0\0" . ($count-1) . "\0\0")
+ ~egox;
+
+ # We have to quote now, otherwise the html itself is escaped
+ # THIS MEANS THAT A LITERAL ", <, >, ' MUST BE ESCAPED FOR A MATCH
+
+ $text = html_quote($text);
+
+ # Color quoted text
+ $text =~ s~^(&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
+ $text =~ s~\b(mailto:|)?([\w\.\-\+\=]+\@[\w\-]+(?:\.[\w\-]+)+)\b
+ ~<a href=\"mailto:$2\">$1$2</a>~igx;
+
+ # attachment links
+ $text =~ s~\b(attachment\s*\#?\s*(\d+)(?:\s+\[details\])?)
+ ~($things[$count++] = get_attachment_link($2, $1)) &&
+ ("\0\0" . ($count-1) . "\0\0")
+ ~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*(\d+)/i;
+ my $comment_re = qr/comment\s*\#?\s*(\d+)/i;
+ $text =~ s~\b($bug_re(?:\s*,?\s*$comment_re)?|$comment_re)
+ ~ # We have several choices. $1 here is the link, and $2-4 are set
+ # depending on which part matched
+ (defined($2) ? get_bug_link($2, $1, { comment_num => $3 }) :
+ "<a href=\"$current_bugurl#c$4\">$1</a>")
+ ~egox;
+
+ # Old duplicate markers. These don't use $bug_word because they are old
+ # and were never customizable.
+ $text =~ s~(?<=^\*\*\*\ This\ bug\ has\ been\ marked\ as\ a\ duplicate\ of\ )
+ (\d+)
+ (?=\ \*\*\*\Z)
+ ~get_bug_link($1, $1)
+ ~egmx;
+
+ # Now remove the encoding hacks
+ $text =~ s/\0\0(\d+)\0\0/$things[$1]/eg;
+ $text =~ s/$chr1\0/\0/g;
+
+ return $text;
+}
+
+# Creates a link to an attachment, including its title.
+sub get_attachment_link {
+ my ($attachid, $link_text) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ my $attachment = new Bugzilla::Attachment($attachid);
+
+ if ($attachment) {
+ my $title = "";
+ my $className = "";
+ if (Bugzilla->user->can_see_bug($attachment->bug_id)) {
+ $title = $attachment->description;
+ }
+ if ($attachment->isobsolete) {
+ $className = "bz_obsolete";
+ }
+ # Prevent code injection in the title.
+ $title = html_quote(clean_text($title));
+
+ $link_text =~ s/ \[details\]$//;
+ my $linkval = "attachment.cgi?id=$attachid";
+
+ # 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';
+ }
+
+ # Whitespace matters here because these links are in <pre> tags.
+ return qq|<span class="$className">|
+ . qq|<a href="${linkval}${patchlink}" 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.
+# It takes either two or three parameters:
+# - The bug number
+# - The link text, to place between the <a>..</a>
+# - An optional comment number, for linking to a particular
+# comment in the bug
+
+sub get_bug_link {
+ my ($bug, $link_text, $options) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ if (!$bug) {
+ return html_quote('<missing bug number>');
+ }
+
+ $bug = blessed($bug) ? $bug : new Bugzilla::Bug($bug);
+ return $link_text if $bug->{error};
+
+ # Initialize these variables to be "" so that we don't get warnings
+ # if we don't change them below (which is highly likely).
+ my ($pre, $title, $post) = ("", "", "");
+
+ $title = get_text('get_status', { status => $bug->bug_status });
+ if ($bug->bug_status eq 'UNCONFIRMED') {
+ $pre = "<i>";
+ $post = "</i>";
+ }
+ if ($bug->resolution) {
+ $pre .= '<span class="bz_closed">';
+ $title .= ' ' . get_text('get_resolution',
+ { resolution => $bug->resolution });
+ $post .= '</span>';
+ }
+ if (Bugzilla->user->can_see_bug($bug)) {
+ $title .= " - " . $bug->short_desc;
+ if ($options->{use_alias} && $link_text =~ /^\d+$/ && $bug->alias) {
+ $link_text = $bug->alias;
+ }
+ }
+ # Prevent code injection in the title.
+ $title = html_quote(clean_text($title));
+
+ my $linkval = "show_bug.cgi?id=" . $bug->id;
+ if (defined $options->{comment_num}) {
+ $linkval .= "#c" . $options->{comment_num};
+ }
+ return qq{$pre<a href="$linkval" title="$title">$link_text</a>$post};
+}
+
+#####################
+# Header Generation #
+#####################
+
+# Returns the last modification time of a file, as an integer number of
+# seconds since the epoch.
+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";
+}
+
+# Set up the skin CSS cascade:
+#
+# 1. YUI CSS
+# 2. Standard Bugzilla stylesheet set (persistent)
+# 3. Standard Bugzilla stylesheet set (selectable)
+# 4. All third-party "skin" stylesheet sets (selectable)
+# 5. Page-specific styles
+# 6. Custom Bugzilla stylesheet set (persistent)
+#
+# "Selectable" skin file sets may be either preferred or alternate.
+# Exactly one is preferred, determined by the "skin" user preference.
+sub css_files {
+ my ($style_urls, $yui, $yui_css) = @_;
+
+ # global.css goes on every page, and so does IE-fixes.css.
+ my @requested_css = ('skins/standard/global.css', @$style_urls,
+ 'skins/standard/IE-fixes.css');
+
+ 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 => [], alternate => {}, skin => [], custom => []);
+ foreach my $set (@css_sets) {
+ foreach my $key (keys %$set) {
+ if ($key eq 'alternate') {
+ foreach my $alternate_skin (keys %{ $set->{alternate} }) {
+ my $files = $by_type{alternate}->{$alternate_skin} ||= [];
+ push(@$files, $set->{alternate}->{$alternate_skin});
+ }
+ }
+ else {
+ push(@{ $by_type{$key} }, $set->{$key});
+ }
+ }
+ }
+
+ 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 $user = Bugzilla->user;
+ my $cgi_path = bz_locations()->{'cgi_path'};
+ my $all_skins = $user->settings->{'skin'}->legal_values;
+ my %skin_urls;
+ foreach my $option (@$all_skins) {
+ next if $option eq 'standard';
+ my $skin_file_name = $file_name;
+ $skin_file_name =~ s{(^|/)skins/standard/}{skins/contrib/$option/};
+ if (my $mtime = _mtime("$cgi_path/$skin_file_name")) {
+ $skin_urls{$option} = mtime_filter($skin_file_name, $mtime);
+ }
+ }
+ $set{alternate} = \%skin_urls;
+
+ my $skin = $user->settings->{'skin'}->{'value'};
+ if ($skin ne 'standard' and defined $set{alternate}->{$skin}) {
+ $set{skin} = delete $set{alternate}->{$skin};
+ }
+
+ 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;
+}
+
+# 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;
+ }
+ return \@yui_resolved;
+}
+
+###############################################################################
+# Templatization Code
+
+# The Template Toolkit throws an error if a loop iterates >1000 times.
+# We want to raise that limit.
+# NOTE: If you change this number, you MUST RE-RUN checksetup.pl!!!
+# If you do not re-run checksetup.pl, the change you make will not apply
+$Template::Directive::WHILE_MAX = 1000000;
+
+# Use the Toolkit Template's Stash module to add utility pseudo-methods
+# to template variables.
+use Template::Stash;
+
+# 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
+# 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;
+ };
+
+# Clone the array reference to leave the original one unaltered.
+$Template::Stash::LIST_OPS->{ clone } =
+ sub {
+ my $list = shift;
+ return [@$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];
+ };
+
+# Add a "truncate" method to the Template Toolkit's "scalar" object
+# that truncates a string to a certain length.
+$Template::Stash::SCALAR_OPS->{ truncate } =
+ sub {
+ my ($string, $length, $ellipsis) = @_;
+ $ellipsis ||= "";
+
+ return $string if !$length || length($string) <= $length;
+
+ my $strlen = $length - length($ellipsis);
+ my $newstr = substr($string, 0, $strlen) . $ellipsis;
+ return $newstr;
+ };
+
+# Create the template object that processes templates and specify
+# configuration parameters that apply to all templates.
+
+###############################################################################
+
+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;
+}
+
+# Construct the Template object
+
+# Note that all of the failure cases here can't use templateable errors,
+# since we won't have a template to use...
+
+sub create {
+ my $class = shift;
+ my %opts = @_;
+
+ # 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()->{'datadir'} . "/template",
+
+ # Initialize templates (f.e. by loading plugins like Hook).
+ PRE_PROCESS => ["global/initialize.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/\@/\\x40/g; # anti-spam for email addresses
+ $var =~ s/</\\x3c/g;
+ return $var;
+ },
+
+ # Converts data to base64
+ base64 => sub {
+ my ($data) = @_;
+ return encode_base64($data);
+ },
+
+ # HTML collapses newlines in element attributes to a single space,
+ # so form elements which may have whitespace (ie comments) need
+ # to be encoded using &#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;
+ },
+
+ # Prevents line break on hyphens and whitespaces.
+ no_break => sub {
+ my ($var) = @_;
+ $var =~ s/ /\&nbsp;/g;
+ $var =~ s/-/\&#8209;/g;
+ return $var;
+ },
+
+ xml => \&Bugzilla::Util::xml_quote ,
+
+ # This filter escapes characters in a variable or value string for
+ # use in a query string. It escapes all characters NOT in the
+ # regex set: [a-zA-Z0-9_\-.]. The 'uri' filter should be used for
+ # a full URL that may have characters that need encoding.
+ url_quote => \&Bugzilla::Util::url_quote ,
+
+ # This filter is similar to url_quote but used a \ instead of a %
+ # as prefix. In addition it replaces a ' ' by a '_'.
+ css_class_quote => \&Bugzilla::Util::css_class_quote ,
+
+ quoteUrls => [ sub {
+ my ($context, $bug, $comment) = @_;
+ return sub {
+ my $text = shift;
+ return quoteUrls($text, $bug, $comment);
+ };
+ },
+ 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 = shift;
+ return join(", ", map(get_bug_link($_, $_), split(/ *, */, $buglist)));
+ },
+
+ # In CSV, quotes are doubled, and any value containing a quote or a
+ # comma is enclosed in quotes.
+ csv => sub
+ {
+ my ($var) = @_;
+ $var =~ s/\"/\"\"/g;
+ if ($var !~ /^-?(\d+\.)?\d*$/) {
+ $var = "\"$var\"";
+ }
+ return $var;
+ } ,
+
+ # Format a filesize in bytes to a human readable value
+ unitconvert => sub
+ {
+ my ($data) = @_;
+ my $retval = "";
+ my %units = (
+ 'KB' => 1024,
+ 'MB' => 1024 * 1024,
+ 'GB' => 1024 * 1024 * 1024,
+ );
+
+ if ($data < 1024) {
+ return "$data bytes";
+ }
+ else {
+ my $u;
+ foreach $u ('GB', 'MB', 'KB') {
+ if ($data >= $units{$u}) {
+ return sprintf("%.2f %s", $data/$units{$u}, $u);
+ }
+ }
+ }
+ },
+
+ # Format a time for display (more info in Bugzilla::Util)
+ time => [ 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 (Bugzilla->usage_mode == USAGE_MODE_JSON
+ or Bugzilla->usage_mode == USAGE_MODE_XMLRPC)
+ {
+ $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]; } ,
+ },
+
+ PLUGIN_BASE => 'Bugzilla::Template::Plugin',
+
+ CONSTANTS => _load_constants(),
+
+ # Default variables for all templates
+ VARIABLES => {
+ # Function for retrieving global parameters.
+ 'Param' => sub { return Bugzilla->params->{$_[0]}; },
+
+ # Function to create date strings
+ 'time2str' => \&Date::Format::time2str,
+
+ # Generic linear search function
+ 'lsearch' => 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
+ # XXX Eventually this should probably be replaced with something
+ # like Bugzilla->language.
+ 'current_language' => sub {
+ my ($language) = include_languages();
+ return $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 "corect" URLBase value
+ 'urlbase' => sub { return Bugzilla::Util::correct_urlbase(); },
+
+ # Allow templates to access docs url with users' preferred language
+ 'docs_urlbase' => sub {
+ my ($language) = include_languages();
+ my $docs_urlbase = Bugzilla->params->{'docs_urlbase'};
+ $docs_urlbase =~ s/\%lang\%/$language/;
+ return $docs_urlbase;
+ },
+
+ # Check whether the URL is safe.
+ 'is_safe_url' => sub {
+ my $url = shift;
+ return 0 unless $url;
+
+ my $safe_protocols = join('|', SAFE_PROTOCOLS);
+ return 1 if $url =~ /^($safe_protocols):[^\s<>\"]+[\w\/]$/i;
+ # 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,
+
+ # A way for all templates to get at Field data, cached.
+ 'bug_fields' => sub {
+ my $cache = Bugzilla->request_cache;
+ $cache->{template_bug_fields} ||=
+ { map { $_->name => $_ } Bugzilla->get_fields() };
+ return $cache->{template_bug_fields};
+ },
+
+ 'css_files' => \&css_files,
+ yui_resolve_deps => \&yui_resolve_deps,
+
+ # Whether or not keywords are enabled, in this Bugzilla.
+ 'use_keywords' => sub { return Bugzilla::Keyword->any_exist; },
+
+ # All the keywords.
+ 'all_keywords' => sub { return 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') },
+
+ '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;
+ },
+ },
+ };
+
+ 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());
+
+ # 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) = @_;
+
+ # Remove the compiled templates.
+ my $datadir = bz_locations()->{'datadir'};
+ if (-e "$datadir/template") {
+ 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("$datadir/template");
+
+ # Check that the directory was really removed, and if not, move it
+ # into data/deleteme/.
+ if (-e "$datadir/template") {
+ print STDERR "\n\n",
+ install_string('template_removal_failed',
+ { datadir => $datadir }), "\n\n";
+ mkpath("$datadir/deleteme");
+ my $random = generate_random_password();
+ rename("$datadir/template", "$datadir/deleteme/$random")
+ or die "move failed: $!";
+ }
+ }
+
+ print install_string('template_precompile') if $output;
+
+ # 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]);
+
+ %_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);
+ }
+ }
+
+ # 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'});
+ }
+
+ # 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;
+}
+
+# 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 $datadir = bz_locations()->{'datadir'};
+ my $container = "$datadir/template$abs_root";
+ mkpath($container);
+ my $target = "$datadir/template/$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;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Template - Wrapper around the Template Toolkit C<Template> object
+
+=head1 SYNOPSIS
+
+ my $template = Bugzilla::Template->create;
+ my $format = $template->get_format("foo/bar",
+ scalar($cgi->param('format')),
+ scalar($cgi->param('ctype')));
+
+=head1 DESCRIPTION
+
+This is basically a wrapper so that the correct arguments get passed into
+the C<Template> constructor.
+
+It should not be used directly by scripts or modules - instead, use
+C<Bugzilla-E<gt>instance-E<gt>template> to get an already created module.
+
+=head1 SUBROUTINES
+
+=over
+
+=item C<precompile_templates($output)>
+
+Description: Compiles all of Bugzilla's templates in every language.
+ Used mostly by F<checksetup.pl>.
+
+Params: C<$output> - C<true> if you want the function to print
+ out information about what it's doing.
+
+Returns: nothing
+
+=back
+
+=head1 METHODS
+
+=over
+
+=item C<get_format($file, $format, $ctype)>
+
+ Description: Construct a format object from URL parameters.
+
+ Params: $file - Name of the template to display.
+ $format - When the template exists under several formats
+ (e.g. table or graph), specify the one to choose.
+ $ctype - Content type, see Bugzilla::Constants::contenttypes.
+
+ Returns: A format object.
+
+=back
+
+=head1 SEE ALSO
+
+L<Bugzilla>, L<Template>
diff --git a/Bugzilla/Template/Context.pm b/Bugzilla/Template/Context.pm
new file mode 100644
index 000000000..7923603e5
--- /dev/null
+++ b/Bugzilla/Template/Context.pm
@@ -0,0 +1,104 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is ITA Software.
+# Portions created by the Initial Developer are Copyright (C) 2009
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+# This exists to implement the template-before_process hook.
+package Bugzilla::Template::Context;
+use strict;
+use base qw(Template::Context);
+
+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;
+}
+
+# This method is called by Template-Toolkit exactly once per template or
+# block (look at a compiled template) so this is an ideal place for us to
+# modify the variables before a template or block runs.
+#
+# We don't do it during Context::process because at that time
+# our stash hasn't been set correctly--the parameters we were passed
+# 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;
+}
+
+# We need a DESTROY sub for the same reason that Bugzilla::CGI does.
+sub DESTROY {
+ my $self = shift;
+ $self->SUPER::DESTROY(@_);
+};
+
+1;
diff --git a/Bugzilla/Template/Plugin/Bugzilla.pm b/Bugzilla/Template/Plugin/Bugzilla.pm
new file mode 100644
index 000000000..101bd06ee
--- /dev/null
+++ b/Bugzilla/Template/Plugin/Bugzilla.pm
@@ -0,0 +1,64 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Bradley Baetz <bbaetz@student.usyd.edu.au>
+#
+
+package Bugzilla::Template::Plugin::Bugzilla;
+
+use strict;
+
+use base qw(Template::Plugin);
+
+use Bugzilla;
+
+sub new {
+ my ($class, $context) = @_;
+
+ return bless {}, $class;
+}
+
+sub AUTOLOAD {
+ my $class = shift;
+ our $AUTOLOAD;
+
+ $AUTOLOAD =~ s/^.*:://;
+
+ return if $AUTOLOAD eq 'DESTROY';
+
+ return Bugzilla->$AUTOLOAD(@_);
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Template::Plugin::Bugzilla
+
+=head1 DESCRIPTION
+
+Template Toolkit plugin to allow access to the persistent C<Bugzilla>
+object.
+
+=head1 SEE ALSO
+
+L<Bugzilla>, L<Template::Plugin>
+
diff --git a/Bugzilla/Template/Plugin/Hook.pm b/Bugzilla/Template/Plugin/Hook.pm
new file mode 100644
index 000000000..f2434817c
--- /dev/null
+++ b/Bugzilla/Template/Plugin/Hook.pm
@@ -0,0 +1,165 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Myk Melez <myk@mozilla.org>
+# Zach Lipton <zach@zachlipton.com>
+# Elliotte Martin <everythingsolved.com>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::Template::Plugin::Hook;
+use strict;
+use base qw(Template::Plugin);
+
+use Bugzilla::Constants;
+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;
+}
+
+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"});
+}
+
+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;
+ }
+ }
+ }
+
+ 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};
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Template::Plugin::Hook
+
+=head1 DESCRIPTION
+
+Template Toolkit plugin to process hooks added into templates by extensions.
+
+=head1 METHODS
+
+=over
+
+=item B<process>
+
+=over
+
+=item B<Description>
+
+Processes hooks added into templates by extensions.
+
+=item B<Params>
+
+=over
+
+=item C<hook_name>
+
+The unique name of the template hook.
+
+=item C<template> (optional)
+
+The path of the calling template.
+This is used as a work around to a bug which causes the path to the hook
+to be incorrect when the hook is called from inside a block.
+
+Example: If the hook C<lastrow> is added to the template
+F<show-multiple.html.tmpl> and it is desired to force the correct template
+path, the template hook would be:
+
+ [% Hook.process("lastrow", "bug/show-multiple.html.tmpl") %]
+
+=back
+
+=item B<Returns>
+
+Output from processing template extension.
+
+=back
+
+=back
+
+=head1 SEE ALSO
+
+L<Template::Plugin>
+
+L<http://wiki.mozilla.org/Bugzilla:Writing_Extensions>
diff --git a/Bugzilla/Template/Plugin/User.pm b/Bugzilla/Template/Plugin/User.pm
new file mode 100644
index 000000000..533b999c3
--- /dev/null
+++ b/Bugzilla/Template/Plugin/User.pm
@@ -0,0 +1,65 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Bradley Baetz <bbaetz@student.usyd.edu.au>
+# Joel Peshkin <bugreport@peshkin.net>
+#
+
+package Bugzilla::Template::Plugin::User;
+
+use strict;
+
+use base qw(Template::Plugin);
+
+use Bugzilla::User;
+
+sub new {
+ my ($class, $context) = @_;
+
+ return bless {}, $class;
+}
+
+sub AUTOLOAD {
+ my $class = shift;
+ our $AUTOLOAD;
+
+ $AUTOLOAD =~ s/^.*:://;
+
+ return if $AUTOLOAD eq 'DESTROY';
+
+ return Bugzilla::User->$AUTOLOAD(@_);
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Template::Plugin::User
+
+=head1 DESCRIPTION
+
+Template Toolkit plugin to allow access to the C<User>
+object.
+
+=head1 SEE ALSO
+
+L<Bugzilla::User>, L<Template::Plugin>
+
diff --git a/Bugzilla/Token.pm b/Bugzilla/Token.pm
new file mode 100644
index 000000000..06e95bb50
--- /dev/null
+++ b/Bugzilla/Token.pm
@@ -0,0 +1,618 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Myk Melez <myk@mozilla.org>
+# Frédéric Buclin <LpSolit@gmail.com>
+
+################################################################################
+# Module Initialization
+################################################################################
+
+# Make it harder for us to do dangerous things in Perl.
+use strict;
+
+# Bundle the functions in this file together into the "Bugzilla::Token" package.
+package Bugzilla::Token;
+
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Mailer;
+use Bugzilla::Util;
+use Bugzilla::User;
+
+use Date::Format;
+use Date::Parse;
+use File::Basename;
+use Digest::MD5 qw(md5_hex);
+
+use base qw(Exporter);
+
+@Bugzilla::Token::EXPORT = qw(issue_session_token check_token_data delete_token
+ issue_hash_token check_hash_token);
+
+################################################################################
+# Public Functions
+################################################################################
+
+# Creates and sends a token to create a new user account.
+# It assumes that the login has the correct format and is not already in use.
+sub issue_new_user_account_token {
+ my $login_name = shift;
+ my $dbh = Bugzilla->dbh;
+ my $template = Bugzilla->template;
+ my $vars = {};
+
+ # Is there already a pending request for this login name? If yes, do not throw
+ # an error because the user may have lost his email with the token inside.
+ # But to prevent using this way to mailbomb an email address, make sure
+ # the last request is at least 10 minutes old before sending a new email.
+
+ my $pending_requests =
+ $dbh->selectrow_array('SELECT COUNT(*)
+ FROM tokens
+ WHERE tokentype = ?
+ AND ' . $dbh->sql_istrcmp('eventdata', '?') . '
+ AND issuedate > NOW() - ' . $dbh->sql_interval(10, 'MINUTE'),
+ undef, ('account', $login_name));
+
+ ThrowUserError('too_soon_for_new_token', {'type' => 'account'}) if $pending_requests;
+
+ my ($token, $token_ts) = _create_token(undef, 'account', $login_name);
+
+ $vars->{'email'} = $login_name . Bugzilla->params->{'emailsuffix'};
+ $vars->{'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());
+
+ # In 99% of cases, the user getting the confirmation email is the same one
+ # who made the request, and so it is reasonable to send the email in the same
+ # language used to view the "Create a New Account" page (we cannot use his
+ # user prefs as the user has no account yet!).
+ MessageToMTA($message);
+}
+
+sub IssueEmailChangeToken {
+ my ($user, $old_email, $new_email) = @_;
+ my $email_suffix = Bugzilla->params->{'emailsuffix'};
+
+ my ($token, $token_ts) = _create_token($user->id, 'emailold', $old_email . ":" . $new_email);
+
+ my $newtoken = _create_token($user->id, 'emailnew', $old_email . ":" . $new_email);
+
+ # Mail the user the token along with instructions for using it.
+
+ my $template = Bugzilla->template_inner($user->settings->{'lang'}->{'value'});
+ my $vars = {};
+
+ $vars->{'oldemailaddress'} = $old_email . $email_suffix;
+ $vars->{'newemailaddress'} = $new_email . $email_suffix;
+ $vars->{'expiration_ts'} = ctime($token_ts + MAX_TOKEN_AGE * 86400);
+ $vars->{'token'} = $token;
+ $vars->{'emailaddress'} = $old_email . $email_suffix;
+
+ my $message;
+ $template->process("account/email/change-old.txt.tmpl", $vars, \$message)
+ || ThrowTemplateError($template->error());
+
+ MessageToMTA($message);
+
+ $vars->{'token'} = $newtoken;
+ $vars->{'emailaddress'} = $new_email . $email_suffix;
+
+ $message = "";
+ $template->process("account/email/change-new.txt.tmpl", $vars, \$message)
+ || ThrowTemplateError($template->error());
+
+ MessageToMTA($message);
+}
+
+# Generates a random token, adds it to the tokens table, and sends it
+# to the user with instructions for using it to change their password.
+sub IssuePasswordToken {
+ my $user = shift;
+ my $dbh = Bugzilla->dbh;
+
+ my $too_soon =
+ $dbh->selectrow_array('SELECT 1 FROM tokens
+ WHERE userid = ?
+ AND tokentype = ?
+ AND issuedate > NOW() - ' .
+ $dbh->sql_interval(10, 'MINUTE'),
+ undef, ($user->id, 'password'));
+
+ ThrowUserError('too_soon_for_new_token', {'type' => 'password'}) if $too_soon;
+
+ my ($token, $token_ts) = _create_token($user->id, 'password', remote_ip());
+
+ # Mail the user the token along with instructions for using it.
+ my $template = Bugzilla->template_inner($user->settings->{'lang'}->{'value'});
+ my $vars = {};
+
+ $vars->{'token'} = $token;
+ $vars->{'emailaddress'} = $user->email;
+ $vars->{'expiration_ts'} = ctime($token_ts + MAX_TOKEN_AGE * 86400);
+ # The user is not logged in (else he 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());
+
+ MessageToMTA($message);
+}
+
+sub issue_session_token {
+ # Generates a random token, adds it to the tokens table, and returns
+ # the token to the caller.
+
+ my $data = shift;
+ return _create_token(Bugzilla->user->id, 'session', $data);
+}
+
+sub issue_hash_token {
+ my ($data, $time) = @_;
+ $data ||= [];
+ $time ||= time();
+
+ # The concatenated string is of the form
+ # token creation time + site-wide secret + user ID + data
+ my @args = ($time, Bugzilla->localconfig->{'site_wide_secret'}, Bugzilla->user->id, @$data);
+
+ my $token = join('*', @args);
+ # Wide characters cause md5_hex() to die.
+ if (Bugzilla->params->{'utf8'}) {
+ utf8::encode($token) if utf8::is_utf8($token);
+ }
+ $token = md5_hex($token);
+
+ # 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);
+ }
+
+ 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;
+}
+
+sub CleanTokenTable {
+ my $dbh = Bugzilla->dbh;
+ $dbh->do('DELETE FROM tokens
+ WHERE ' . $dbh->sql_to_days('NOW()') . ' - ' .
+ $dbh->sql_to_days('issuedate') . ' >= ?',
+ undef, MAX_TOKEN_AGE);
+}
+
+sub GenerateUniqueToken {
+ # Generates a unique random token. Uses generate_random_password
+ # for the tokens themselves and checks uniqueness by searching for
+ # the token in the "tokens" table. Gives up if it can't come up
+ # with a token after about one hundred tries.
+ my ($table, $column) = @_;
+
+ my $token;
+ my $duplicate = 1;
+ my $tries = 0;
+ $table ||= "tokens";
+ $column ||= "token";
+
+ my $dbh = Bugzilla->dbh;
+ my $sth = $dbh->prepare("SELECT userid FROM $table WHERE $column = ?");
+
+ while ($duplicate) {
+ ++$tries;
+ if ($tries > 100) {
+ ThrowCodeError("token_generation_error");
+ }
+ $token = generate_random_password();
+ $sth->execute($token);
+ $duplicate = $sth->fetchrow_array;
+ }
+ return $token;
+}
+
+# Cancels a previously issued token and notifies the user.
+# This should only happen when the user accidentally makes a token request
+# or when a malicious hacker makes a token request on behalf of a user.
+sub Cancel {
+ my ($token, $cancelaction, $vars) = @_;
+ my $dbh = Bugzilla->dbh;
+ $vars ||= {};
+
+ # Get information about the token being canceled.
+ trick_taint($token);
+ my ($issuedate, $tokentype, $eventdata, $userid) =
+ $dbh->selectrow_array('SELECT ' . $dbh->sql_date_format('issuedate') . ',
+ tokentype, eventdata, userid
+ FROM tokens
+ WHERE token = ?',
+ undef, $token);
+
+ # If we are canceling the creation of a new user account, then there
+ # is no entry in the 'profiles' table.
+ my $user = new Bugzilla::User($userid);
+
+ $vars->{'emailaddress'} = $userid ? $user->email : $eventdata;
+ $vars->{'remoteaddress'} = 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->settings->{'lang'}->{'value'});
+
+ 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;
+
+ detaint_natural($userid);
+ my $tokens = $dbh->selectcol_arrayref('SELECT token FROM tokens
+ WHERE userid = ? AND tokentype = ?',
+ undef, ($userid, 'password'));
+
+ foreach my $token (@$tokens) {
+ Bugzilla::Token::Cancel($token, $reason);
+ }
+}
+
+# Returns an email change token if the user has one.
+sub HasEmailChangeToken {
+ my $userid = shift;
+ my $dbh = Bugzilla->dbh;
+
+ my $token = $dbh->selectrow_array('SELECT token FROM tokens
+ WHERE userid = ?
+ AND (tokentype = ? OR tokentype = ?) ' .
+ $dbh->sql_limit(1),
+ undef, ($userid, 'emailnew', 'emailold'));
+ return $token;
+}
+
+# Returns the userid, issuedate and eventdata for the specified token
+sub GetTokenData {
+ my ($token) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ return unless defined $token;
+ $token = clean_text($token);
+ trick_taint($token);
+
+ return $dbh->selectrow_array(
+ "SELECT userid, " . $dbh->sql_date_format('issuedate') . ", eventdata
+ FROM tokens
+ WHERE token = ?", undef, $token);
+}
+
+# Deletes specified token
+sub delete_token {
+ my ($token) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ return unless defined $token;
+ trick_taint($token);
+
+ $dbh->do("DELETE FROM tokens WHERE token = ?", undef, $token);
+}
+
+# Given a token, makes sure it comes from the currently logged in user
+# and match the expected event. Returns 1 on success, else displays a warning.
+# Note: this routine must not be called while tables are locked as it will try
+# to lock some tables itself, see CleanTokenTable().
+sub check_token_data {
+ my ($token, $expected_action, $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 his name!
+ require Bugzilla::User;
+
+ my $vars = {};
+ $vars->{'abuser'} = Bugzilla::User->new($creator_id)->identity;
+ $vars->{'token_action'} = $token_action;
+ $vars->{'expected_action'} = $expected_action;
+ $vars->{'script_name'} = basename($0);
+ $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;
+ }
+ return 1;
+}
+
+################################################################################
+# Internal Functions
+################################################################################
+
+# Generates a unique token and inserts it into the database
+# Returns the token and the token timestamp
+sub _create_token {
+ my ($userid, $tokentype, $eventdata) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ detaint_natural($userid) if defined $userid;
+ trick_taint($tokentype);
+ trick_taint($eventdata);
+
+ $dbh->bz_start_transaction();
+
+ my $token = GenerateUniqueToken();
+
+ $dbh->do("INSERT INTO tokens (userid, issuedate, token, tokentype, eventdata)
+ VALUES (?, NOW(), ?, ?, ?)", undef, ($userid, $token, $tokentype, $eventdata));
+
+ $dbh->bz_commit_transaction();
+
+ if (wantarray) {
+ my (undef, $token_ts, undef) = GetTokenData($token);
+ $token_ts = str2time($token_ts);
+ return ($token, $token_ts);
+ } else {
+ return $token;
+ }
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Token - Provides different routines to manage tokens.
+
+=head1 SYNOPSIS
+
+ use Bugzilla::Token;
+
+ Bugzilla::Token::issue_new_user_account_token($login_name);
+ Bugzilla::Token::IssueEmailChangeToken($user, $old_email, $new_email);
+ Bugzilla::Token::IssuePasswordToken($user);
+ Bugzilla::Token::DeletePasswordTokens($user_id, $reason);
+ Bugzilla::Token::Cancel($token, $cancelaction, $vars);
+
+ Bugzilla::Token::CleanTokenTable();
+
+ my $token = issue_session_token($event);
+ check_token_data($token, $event)
+ delete_token($token);
+
+ my $token = Bugzilla::Token::GenerateUniqueToken($table, $column);
+ my $token = Bugzilla::Token::HasEmailChangeToken($user_id);
+ my ($token, $date, $data) = Bugzilla::Token::GetTokenData($token);
+
+=head1 SUBROUTINES
+
+=over
+
+=item C<issue_new_user_account_token($login_name)>
+
+ Description: Creates and sends a token per email to the email address
+ requesting a new user account. It doesn't check whether
+ the user account already exists. The user will have to
+ use this token to confirm the creation of his user account.
+
+ Params: $login_name - The new login name requested by the user.
+
+ Returns: Nothing. It throws an error if the same user made the same
+ request in the last few minutes.
+
+=item C<sub IssueEmailChangeToken($user, $old_email, $new_email)>
+
+ Description: Sends two distinct tokens per email to the old and new email
+ addresses to confirm the email address change for the given
+ user. These tokens remain valid for the next MAX_TOKEN_AGE days.
+
+ Params: $user - User object of the user requesting a new
+ email address.
+ $old_email - The current (old) email address of the user.
+ $new_email - The new email address of the user.
+
+ Returns: Nothing.
+
+=item C<IssuePasswordToken($user)>
+
+ Description: Sends a token per email to the given user. This token
+ can be used to change the password (e.g. in case the user
+ cannot remember his password and wishes to enter a new one).
+
+ Params: $user - User object of the user requesting a new password.
+
+ Returns: Nothing. It throws an error if the same user made the same
+ request in the last few minutes.
+
+=item C<CleanTokenTable()>
+
+ Description: Removes all tokens older than MAX_TOKEN_AGE days from the DB.
+ This means that these tokens will now be considered as invalid.
+
+ Params: None.
+
+ Returns: Nothing.
+
+=item C<GenerateUniqueToken($table, $column)>
+
+ Description: Generates and returns a unique token. This token is unique
+ in the $column of the $table. This token is NOT stored in the DB.
+
+ Params: $table (optional): The table to look at (default: tokens).
+ $column (optional): The column to look at for uniqueness (default: token).
+
+ Returns: A token which is unique in $column.
+
+=item C<Cancel($token, $cancelaction, $vars)>
+
+ Description: Invalidates an existing token, generally when the token is used
+ for an action which is not the one expected. An email is sent
+ to the user who originally requested this token to inform him
+ that this token has been invalidated (e.g. because an hacker
+ tried to use this token for some malicious action).
+
+ Params: $token: The token to invalidate.
+ $cancelaction: The reason why this token is invalidated.
+ $vars: Some additional information about this action.
+
+ Returns: Nothing.
+
+=item C<DeletePasswordTokens($user_id, $reason)>
+
+ Description: Cancels all password tokens for the given user. Emails are sent
+ to the user to inform him about this action.
+
+ Params: $user_id: The user ID of the user account whose password tokens
+ are canceled.
+ $reason: The reason why these tokens are canceled.
+
+ Returns: Nothing.
+
+=item C<HasEmailChangeToken($user_id)>
+
+ Description: Returns any existing token currently used for an email change
+ for the given user.
+
+ Params: $user_id - A user ID.
+
+ Returns: A token if it exists, else undef.
+
+=item C<GetTokenData($token)>
+
+ Description: Returns all stored data for the given token.
+
+ Params: $token - A valid token.
+
+ Returns: The user ID, the date and time when the token was created and
+ the (event)data stored with that token.
+
+=back
+
+=head2 Security related routines
+
+The following routines have been written to be used together as described below,
+although they can be used separately.
+
+=over
+
+=item C<issue_session_token($event)>
+
+ Description: Creates and returns a token used internally.
+
+ Params: $event - The event which needs to be stored in the DB for future
+ reference/checks.
+
+ Returns: A unique token.
+
+=item C<check_token_data($token, $event)>
+
+ Description: Makes sure the $token has been created by the currently logged in
+ user and to be used for the given $event. If this token is used for
+ an unexpected action (i.e. $event doesn't match the information stored
+ with the token), a warning is displayed asking whether the user really
+ wants to continue. On success, it returns 1.
+ This is the routine to use for security checks, combined with
+ issue_session_token() and delete_token() as follows:
+
+ 1. First, create a token for some coming action.
+ my $token = issue_session_token($action);
+ 2. Some time later, it's time to make sure that the expected action
+ is going to be executed, and by the expected user.
+ check_token_data($token, $action);
+ 3. The check has been done and we no longer need this token.
+ delete_token($token);
+
+ Params: $token - The token used for security checks.
+ $event - The expected event to be run.
+
+ Returns: 1 on success, else a warning is thrown.
+
+=item C<delete_token($token)>
+
+ Description: Deletes the specified token. No notification is sent.
+
+ Params: $token - The token to delete.
+
+ Returns: Nothing.
+
+=back
+
+=cut
diff --git a/Bugzilla/Update.pm b/Bugzilla/Update.pm
new file mode 100644
index 000000000..a94fd167d
--- /dev/null
+++ b/Bugzilla/Update.pm
@@ -0,0 +1,209 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
+
+package Bugzilla::Update;
+
+use strict;
+
+use Bugzilla::Constants;
+
+use constant REMOTE_FILE => 'http://updates.bugzilla.org/bugzilla-update.xml';
+use constant LOCAL_FILE => "/bugzilla-update.xml"; # Relative to datadir.
+use constant TIME_INTERVAL => 86400; # Default is one day, in seconds.
+use constant TIMEOUT => 5; # Number of seconds before timeout.
+
+# Look for new releases and notify logged in administrators about them.
+sub get_notifications {
+ return if !Bugzilla->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.
+ if (-e $local_file) {
+ return { 'error' => 'no_update', xml_file => $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', 'xml_file' => $local_file} unless (-r $local_file);
+
+ my $twig = XML::Twig->new();
+ $twig->safe_parsefile($local_file);
+ # If the XML file is invalid, return.
+ return {'error' => 'corrupted', 'xml_file' => $local_file} if $@;
+ my $root = $twig->root;
+
+ my @releases;
+ foreach my $branch ($root->children('branch')) {
+ my $release = {
+ 'branch_ver' => $branch->{'att'}->{'id'},
+ 'latest_ver' => $branch->{'att'}->{'vid'},
+ 'status' => $branch->{'att'}->{'status'},
+ 'url' => $branch->{'att'}->{'url'},
+ 'date' => $branch->{'att'}->{'date'}
+ };
+ push(@releases, $release);
+ }
+
+ # On which branch is the current installation running?
+ my @current_version =
+ (BUGZILLA_VERSION =~ m/^(\d+)\.(\d+)(?:(rc|\.)(\d+))?\+?$/);
+
+ my @release;
+ if (Bugzilla->params->{'upgrade_notification'} eq 'development_snapshot') {
+ @release = grep {$_->{'status'} eq 'development'} @releases;
+ # If there is no development snapshot available, then we are in the
+ # process of releasing a release candidate. That's the release we want.
+ unless (scalar(@release)) {
+ @release = grep {$_->{'status'} eq 'release-candidate'} @releases;
+ }
+ }
+ elsif (Bugzilla->params->{'upgrade_notification'} eq 'latest_stable_release') {
+ @release = grep {$_->{'status'} eq 'stable'} @releases;
+ }
+ elsif (Bugzilla->params->{'upgrade_notification'} eq 'stable_branch_release') {
+ # We want the latest stable version for the current branch.
+ # If we are running a development snapshot, we won't match anything.
+ my $branch_version = $current_version[0] . '.' . $current_version[1];
+
+ # We do a string comparison instead of a numerical one, because
+ # e.g. 2.2 == 2.20, but 2.2 ne 2.20 (and 2.2 is indeed much older).
+ @release = grep {$_->{'branch_ver'} eq $branch_version} @releases;
+
+ # If the branch is now closed, we should strongly suggest
+ # to upgrade to the latest stable release available.
+ if (scalar(@release) && $release[0]->{'status'} eq 'closed') {
+ @release = grep {$_->{'status'} eq 'stable'} @releases;
+ return {'data' => $release[0], 'deprecated' => $branch_version};
+ }
+ }
+ else {
+ # Unknown parameter.
+ return {'error' => 'unknown_parameter'};
+ }
+
+ # Return if no new release is available.
+ return unless scalar(@release);
+
+ # Only notify the administrator if the latest version available
+ # is newer than the current one.
+ my @new_version =
+ ($release[0]->{'latest_ver'} =~ m/^(\d+)\.(\d+)(?:(rc|\.)(\d+))?\+?$/);
+
+ # We convert release candidates 'rc' to integers (rc ? 0 : 1) in order
+ # to compare versions easily.
+ $current_version[2] = ($current_version[2] && $current_version[2] eq 'rc') ? 0 : 1;
+ $new_version[2] = ($new_version[2] && $new_version[2] eq 'rc') ? 0 : 1;
+
+ my $is_newer = _compare_versions(\@current_version, \@new_version);
+ return ($is_newer == 1) ? {'data' => $release[0]} : undef;
+}
+
+sub _synchronize_data {
+ my $local_file = bz_locations()->{'datadir'} . LOCAL_FILE;
+
+ my $ua = LWP::UserAgent->new();
+ $ua->timeout(TIMEOUT);
+ $ua->protocols_allowed(['http', 'https']);
+ # If the URL of the proxy is given, use it, else get this information
+ # from the environment variable.
+ my $proxy_url = Bugzilla->params->{'proxy_url'};
+ if ($proxy_url) {
+ $ua->proxy(['http', 'https'], $proxy_url);
+ }
+ else {
+ $ua->env_proxy;
+ }
+ $ua->mirror(REMOTE_FILE, $local_file);
+
+ # $ua->mirror() forces the modification time of the local XML file
+ # to match the modification time of the remote one.
+ # So we have to update it manually to reflect that a newer version
+ # of the file has effectively been requested. This will avoid
+ # any new download for the next TIME_INTERVAL.
+ if (-e $local_file) {
+ # Try to alter its last modification time.
+ my $can_alter = utime(undef, undef, $local_file);
+ # This error should never happen.
+ $can_alter || return {'error' => 'no_update', 'xml_file' => $local_file};
+ }
+ else {
+ # We have been unable to download the file.
+ return {'error' => 'cannot_download', 'xml_file' => $local_file};
+ }
+
+ # Everything went well.
+ return 0;
+}
+
+sub _compare_versions {
+ my ($old_ver, $new_ver) = @_;
+ while (scalar(@$old_ver) && scalar(@$new_ver)) {
+ my $old = shift(@$old_ver) || 0;
+ my $new = shift(@$new_ver) || 0;
+ return $new <=> $old if ($new <=> $old);
+ }
+ return scalar(@$new_ver) <=> scalar(@$old_ver);
+
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Update - Update routines for Bugzilla
+
+=head1 SYNOPSIS
+
+ use Bugzilla::Update;
+
+ # Get information about new releases
+ my $new_release = Bugzilla::Update::get_notifications();
+
+=head1 DESCRIPTION
+
+This module contains all required routines to notify you
+about new releases. It downloads an XML file from bugzilla.org
+and parses it, in order to display information based on your
+preferences. Absolutely no information about the Bugzilla version
+you are running is sent to bugzilla.org.
+
+=head1 FUNCTIONS
+
+=over
+
+=item C<get_notifications()>
+
+ Description: This function informs you about new releases, if any.
+
+ Params: None.
+
+ Returns: On success, a reference to a hash with data about
+ new releases, if any.
+ On failure, a reference to a hash with the reason
+ of the failure and the name of the unusable XML file.
+
+=back
+
+=cut
diff --git a/Bugzilla/User.pm b/Bugzilla/User.pm
new file mode 100644
index 000000000..f81ed2b2f
--- /dev/null
+++ b/Bugzilla/User.pm
@@ -0,0 +1,2456 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Myk Melez <myk@mozilla.org>
+# Erik Stambaugh <erik@dasbistro.com>
+# Bradley Baetz <bbaetz@acm.org>
+# Joel Peshkin <bugreport@peshkin.net>
+# Byron Jones <bugzilla@glob.com.au>
+# Shane H. W. Travis <travis@sedsystems.ca>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+# Gervase Markham <gerv@gerv.net>
+# Lance Larsh <lance.larsh@oracle.com>
+# Justin C. De Vries <judevries@novell.com>
+# Dennis Melentyev <dennis.melentyev@infopulse.com.ua>
+# Frédéric Buclin <LpSolit@gmail.com>
+# Mads Bondo Dydensborg <mbd@dbc.dk>
+
+################################################################################
+# Module Initialization
+################################################################################
+
+# Make it harder for us to do dangerous things in Perl.
+use strict;
+
+# This module implements utilities for dealing with Bugzilla users.
+package Bugzilla::User;
+
+use Bugzilla::Error;
+use Bugzilla::Util;
+use Bugzilla::Constants;
+use Bugzilla::Search::Recent;
+use Bugzilla::User::Setting;
+use Bugzilla::Product;
+use Bugzilla::Classification;
+use Bugzilla::Field;
+use Bugzilla::Group;
+
+use DateTime::TimeZone;
+use List::Util qw(max);
+use Scalar::Util qw(blessed);
+use Storable qw(dclone);
+use URI;
+use URI::QueryParam;
+
+use base qw(Bugzilla::Object Exporter);
+@Bugzilla::User::EXPORT = qw(is_available_username
+ login_to_id user_id_to_login validate_password
+ USER_MATCH_MULTIPLE USER_MATCH_FAILED USER_MATCH_SUCCESS
+ MATCH_SKIP_CONFIRM
+);
+
+#####################################################################
+# Constants
+#####################################################################
+
+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 DEFAULT_USER => {
+ 'userid' => 0,
+ 'realname' => '',
+ 'login_name' => '',
+ 'showmybugslink' => 0,
+ 'disabledtext' => '',
+ 'disable_mail' => 0,
+};
+
+use constant DB_TABLE => 'profiles';
+
+# XXX Note that Bugzilla::User->name does not return the same thing
+# that you passed in for "name" to new(). That's because historically
+# Bugzilla::User used "name" for the realname field. This should be
+# fixed one day.
+use constant DB_COLUMNS => (
+ 'profiles.userid',
+ 'profiles.login_name',
+ 'profiles.realname',
+ 'profiles.mybugslink AS showmybugslink',
+ 'profiles.disabledtext',
+ 'profiles.disable_mail',
+);
+use constant NAME_FIELD => 'login_name';
+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_for_creation,
+ realname => \&_check_realname,
+};
+
+sub UPDATE_COLUMNS {
+ my $self = shift;
+ my @cols = qw(
+ disable_mail
+ disabledtext
+ login_name
+ realname
+ );
+ push(@cols, 'cryptpassword') if exists $self->{cryptpassword};
+ return @cols;
+};
+
+################################################################################
+# Functions
+################################################################################
+
+sub new {
+ my $invocant = shift;
+ my $class = ref($invocant) || $invocant;
+ my ($param) = @_;
+
+ my $user = DEFAULT_USER;
+ bless ($user, $class);
+ return $user unless $param;
+
+ return $class->SUPER::new(@_);
+}
+
+sub super_user {
+ my $invocant = shift;
+ my $class = ref($invocant) || $invocant;
+ my ($param) = @_;
+
+ my $user = dclone(DEFAULT_USER);
+ $user->{groups} = [Bugzilla::Group->get_all];
+ $user->{bless_groups} = [Bugzilla::Group->get_all];
+ bless $user, $class;
+ return $user;
+}
+
+sub update {
+ my $self = shift;
+ my $changes = $self->SUPER::update(@_);
+ my $dbh = Bugzilla->dbh;
+
+ if (exists $changes->{login_name}) {
+ # If we changed the login, silently delete any tokens.
+ $dbh->do('DELETE FROM tokens WHERE userid = ?', undef, $self->id);
+ # And rederive regex groups
+ $self->derive_regexp_groups();
+ }
+
+ # Logout the user if necessary.
+ Bugzilla->logout_user($self)
+ if (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;
+}
+
+################################################################################
+# Validators
+################################################################################
+
+sub _check_disable_mail { return $_[1] ? 1 : 0; }
+sub _check_disabledtext { return trim($_[1]) || ''; }
+
+# This is public since createaccount.cgi needs to use it before issuing
+# a token for account creation.
+sub check_login_name_for_creation {
+ my ($invocant, $name) = @_;
+ $name = trim($name);
+ $name || ThrowUserError('user_login_required');
+ validate_email_syntax($name)
+ || ThrowUserError('illegal_email_address', { addr => $name });
+
+ # Check the name if it's a new user, or if we're changing the name.
+ if (!ref($invocant) || $invocant->login ne $name) {
+ is_available_username($name)
+ || ThrowUserError('account_exists', { email => $name });
+ }
+
+ return $name;
+}
+
+sub _check_password {
+ 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 '*';
+
+ validate_password($pass);
+ my $cryptpassword = bz_crypt($pass);
+ return $cryptpassword;
+}
+
+sub _check_realname { return trim($_[1]) || ''; }
+
+################################################################################
+# Mutators
+################################################################################
+
+sub set_disabledtext { $_[0]->set('disabledtext', $_[1]); }
+sub set_disable_mail { $_[0]->set('disable_mail', $_[1]); }
+
+sub set_login {
+ 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};
+}
+
+sub set_password { $_[0]->set('cryptpassword', $_[1]); }
+
+
+################################################################################
+# Methods
+################################################################################
+
+# Accessors for user attributes
+sub name { $_[0]->{realname}; }
+sub login { $_[0]->{login_name}; }
+sub email { $_[0]->login . Bugzilla->params->{'emailsuffix'}; }
+sub disabledtext { $_[0]->{'disabledtext'}; }
+sub is_disabled { $_[0]->disabledtext ? 1 : 0; }
+sub showmybugslink { $_[0]->{showmybugslink}; }
+sub email_disabled { $_[0]->{disable_mail}; }
+sub email_enabled { !($_[0]->{disable_mail}); }
+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;
+}
+
+sub set_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};
+}
+
+# Generate a string to identify the user by name + login if the user
+# has a name or by login only if she doesn't.
+sub identity {
+ my $self = shift;
+
+ return "" unless $self->id;
+
+ if (!defined $self->{identity}) {
+ $self->{identity} =
+ $self->name ? $self->name . " <" . $self->login. ">" : $self->login;
+ }
+
+ return $self->{identity};
+}
+
+sub nick {
+ my $self = shift;
+
+ return "" unless $self->id;
+
+ if (!defined $self->{nick}) {
+ $self->{nick} = (split(/@/, $self->login, 2))[0];
+ }
+
+ return $self->{nick};
+}
+
+sub queries {
+ 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);
+
+ # 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};
+}
+
+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
+ 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};
+}
+
+sub queries_available {
+ 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';
+
+ 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};
+}
+
+##########################
+# Saved Recent Bug Lists #
+##########################
+
+sub 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;
+
+ foreach my $search (@$searches) {
+ return $search if grep($_ == $bug_id, @{ $search->bug_list });
+ }
+
+ 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');
+ }
+ }
+ }
+
+ 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 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;
+}
+
+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');
+ }
+
+ return if !@$bug_ids;
+
+ if ($self->id) {
+ on_main_db {
+ my $search;
+ if ($list_id) {
+ # Use eval so that people can still use old search links or
+ # links that don't belong to them.
+ $search = eval { Bugzilla::Search::Recent->check(
+ { id => $list_id }) };
+ }
+
+ if ($search) {
+ # We only update placeholders. (Placeholders are
+ # Saved::Search::Recent objects with empty bug lists.)
+ # Otherwise, we could just keep creating new searches
+ # for the same refreshed list over and over.
+ if (!@{ $search->bug_list }) {
+ $search->set_list_order($order);
+ $search->set_bug_list($bug_ids);
+ $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)) {
+ Bugzilla::Search::Recent->create({
+ user_id => $self->id,
+ bug_list => $bug_ids,
+ list_order => $order });
+ }
+ }
+ };
+ 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');
+ }
+ else {
+ $cgi->remove_cookie('BUGLIST');
+ $vars->{'toolong'} = 1;
+ }
+ }
+}
+
+sub settings {
+ my ($self) = @_;
+
+ 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();
+ }
+
+ return $self->{'settings'};
+}
+
+sub timezone {
+ my $self = shift;
+
+ if (!defined $self->{timezone}) {
+ my $tz = $self->settings->{timezone}->{value};
+ 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);
+ }
+ }
+ return $self->{timezone};
+}
+
+sub flush_queries_cache {
+ my $self = shift;
+
+ delete $self->{queries};
+ delete $self->{queries_subscribed};
+ delete $self->{queries_available};
+}
+
+sub groups {
+ my $self = shift;
+
+ return $self->{groups} if defined $self->{groups};
+ return [] unless $self->id;
+
+ my $dbh = Bugzilla->dbh;
+ my $groups_to_check = $dbh->selectcol_arrayref(
+ q{SELECT DISTINCT group_id
+ FROM user_group_map
+ WHERE user_id = ? AND isbless = 0}, undef, $self->id);
+
+ my $rows = $dbh->selectall_arrayref(
+ "SELECT DISTINCT grantor_id, member_id
+ FROM group_group_map
+ WHERE grant_type = " . GROUP_MEMBERSHIP);
+
+ my %group_membership;
+ foreach my $row (@$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;
+ }
+ }
+
+ $self->{groups} = Bugzilla::Group->new_from_list([keys %groups]);
+
+ return $self->{groups};
+}
+
+sub groups_as_string {
+ my $self = shift;
+ my @ids = map { $_->id } @{ $self->groups };
+ return scalar(@ids) ? join(',', @ids) : '-1';
+}
+
+sub groups_in_sql {
+ my ($self, $field) = @_;
+ $field ||= 'group_id';
+ my @ids = map { $_->id } @{ $self->groups };
+ @ids = (-1) if !scalar @ids;
+ return Bugzilla->dbh->sql_in($field, \@ids);
+}
+
+sub bless_groups {
+ my $self = shift;
+
+ 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'};
+ }
+
+ my $dbh = Bugzilla->dbh;
+
+ # Get all groups for the user where:
+ # + They have direct bless privileges
+ # + They are a member of a group that inherits bless privs.
+ my @group_ids = map {$_->id} @{ $self->groups };
+ @group_ids = (-1) if !@group_ids;
+ my $query =
+ 'SELECT DISTINCT groups.id
+ FROM groups, user_group_map, group_group_map AS ggm
+ WHERE user_group_map.user_id = ?
+ AND ( (user_group_map.isbless = 1
+ AND groups.id=user_group_map.group_id)
+ OR (groups.id = ggm.grantor_id
+ AND ggm.grant_type = ' . GROUP_BLESS . '
+ AND ' . $dbh->sql_in('ggm.member_id', \@group_ids)
+ . ') )';
+
+ # If visibilitygroups are used, restrict the set of groups.
+ if (Bugzilla->params->{'usevisibilitygroups'}) {
+ return [] if !$self->visible_groups_as_string;
+ # Users need to see a group in order to bless it.
+ $query .= " AND "
+ . $dbh->sql_in('groups.id', $self->visible_groups_inherited);
+ }
+
+ 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
+ FROM group_control_map
+ WHERE product_id = ?
+ AND $group != 0
+ 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};
+ }
+ # 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;
+}
+
+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 $product_ids = Bugzilla->dbh->selectcol_arrayref(
+ "SELECT DISTINCT product_id
+ FROM group_control_map
+ WHERE $group != 0
+ 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;
+
+ # 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;
+
+ 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)
+ FROM profiles
+ WHERE userid = ?
+ };
+ }
+ return Bugzilla->dbh->selectrow_array($query, undef, $otherUser->id);
+}
+
+sub can_edit_product {
+ my ($self, $prod_id) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ 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);
+
+ return !$has_external_groups;
+}
+
+sub can_see_bug {
+ 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) {
+ 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
+ LEFT JOIN cc
+ ON cc.bug_id = bugs.bug_id
+ AND cc.who = $user_id
+ 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 . ')
+ 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;
+ }
+ }
+
+ return [grep { $visible_cache->{blessed $_ ? $_->id : $_} } @$bugs];
+}
+
+sub clear_product_cache {
+ my $self = shift;
+ delete $self->{enterable_products};
+ delete $self->{selectable_products};
+ delete $self->{selectable_classifications};
+}
+
+sub can_see_product {
+ my ($self, $product_name) = @_;
+
+ 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;
+
+ 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 .
+ " AND group_id NOT IN(" . $self->groups_as_string . ") " .
+ " WHERE group_id IS NULL " .
+ "ORDER BY name";
+
+ 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) = @_;
+
+ 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};
+}
+
+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' });
+ }
+
+ if (!scalar @{ $self->get_enterable_products }) {
+ return unless $warn == THROW_ERROR;
+ ThrowUserError('no_products');
+ }
+
+ my $product = blessed($input) ? $input
+ : new Bugzilla::Product({ name => $input });
+ my $can_enter =
+ $product && grep($_->name eq $product->name,
+ @{ $self->get_enterable_products });
+
+ return $product if $can_enter;
+
+ return 0 unless $warn == THROW_ERROR;
+
+ # Check why access was denied. These checks are slow,
+ # but that's fine, because they only happen if we fail.
+
+ # 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}) {
+ ThrowUserError('missing_component', { product => $product });
+ }
+ # It could have no versions...
+ elsif (!@{$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;
+
+ if (defined $self->{enterable_products}) {
+ return $self->{enterable_products};
+ }
+
+ # All products which the user has "Entry" access to.
+ my @enterable_ids =@{$dbh->selectcol_arrayref(
+ 'SELECT products.id 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 (' . $self->groups_as_string . ')
+ WHERE group_id IS NULL
+ AND products.isactive = 1') || []};
+
+ if (@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
+ INNER JOIN components ON components.product_id = products.id
+ INNER JOIN versions ON versions.product_id = products.id
+ WHERE products.id IN (' . (join(',', @enterable_ids)) .
+ ')') || []};
+ }
+
+ $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});
+}
+
+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 [ values %products ];
+}
+
+sub check_can_admin_product {
+ my ($self, $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});
+
+ # Return the validated product object.
+ return $product;
+}
+
+sub can_request_flag {
+ 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;
+}
+
+sub can_set_flag {
+ my ($self, $flag_type) = @_;
+
+ return (!$flag_type->grant_group_id
+ || $self->in_group_id($flag_type->grant_group_id)) ? 1 : 0;
+}
+
+sub direct_group_membership {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ if (!$self->{'direct_group_membership'}) {
+ my $gid = $dbh->selectcol_arrayref('SELECT id
+ FROM groups
+ INNER JOIN user_group_map
+ ON groups.id = user_group_map.group_id
+ WHERE user_id = ?
+ AND isbless = 0',
+ undef, $self->id);
+ $self->{'direct_group_membership'} = Bugzilla::Group->new_from_list($gid);
+ }
+ return $self->{'direct_group_membership'};
+}
+
+
+# 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};
+}
+
+# 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
+ 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();
+
+ while (my ($row) = $sth->fetchrow_array) {
+ push @visgroups,$row;
+ }
+ $self->{visible_groups_direct} = \@visgroups;
+
+ return $self->{visible_groups_direct};
+}
+
+sub visible_groups_as_string {
+ 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 = map { $_->id } @{ $self->groups };
+ }
+ }
+
+ return $self->{queryshare_groups} = \@queryshare_groups;
+}
+
+sub queryshare_groups_as_string {
+ my $self = shift;
+ return join(', ', @{$self->queryshare_groups()});
+}
+
+sub derive_regexp_groups {
+ my ($self) = @_;
+
+ my $id = $self->id;
+ return unless $id;
+
+ my $dbh = Bugzilla->dbh;
+
+ my $sth;
+
+ # add derived records for any matching regexps
+
+ $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);
+
+ 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
+ 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;
+ }
+ }
+}
+
+sub product_responsibilities {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ return $self->{'product_resp'} if defined $self->{'product_resp'};
+ return [] unless $self->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'};
+ }
+
+ 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;
+
+ 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;
+ }
+
+ # 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 disabledtext = '' " 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;
+ }
+
+ # 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 disabledtext = '' " 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};
+ }
+ }
+ }
+ }
+ }
+ $fields = $expanded_fields;
+
+ 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',
+ });
+ }
+
+ # 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);
+ 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;
+ }
+
+ # 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 @logins) {
+ $data->{$field} = \@logins;
+ }
+ }
+
+ 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 = {};
+
+ $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();
+
+ $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. We really should be using proper field names
+# throughout.
+our %names_to_events = (
+ 'Resolution' => EVT_OPENED_CLOSED,
+ 'Keywords' => EVT_KEYWORD,
+ 'CC' => EVT_CC,
+ 'Severity' => EVT_PROJ_MANAGEMENT,
+ 'Priority' => EVT_PROJ_MANAGEMENT,
+ 'Status' => EVT_PROJ_MANAGEMENT,
+ 'Target Milestone' => EVT_PROJ_MANAGEMENT,
+ 'Attachment description' => EVT_ATTACHMENT_DATA,
+ 'Attachment mime type' => EVT_ATTACHMENT_DATA,
+ 'Attachment is patch' => EVT_ATTACHMENT_DATA,
+ 'Depends on' => EVT_DEPEND_BLOCK,
+ 'Blocks' => EVT_DEPEND_BLOCK);
+
+# 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_id, $relationship, $fieldDiffs, $comments, $dependencyText,
+ $changer, $bug_is_new) = @_;
+
+ # 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 $ref (@$fieldDiffs) {
+ my ($who, $whoname, $fieldName, $when, $old, $new) = @$ref;
+ # 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;
+ }
+
+ # If the user is in a particular role and the value of that role
+ # changed, we need the ADDED_REMOVED event.
+ if (($fieldName eq "AssignedTo" && $relationship == REL_ASSIGNEE) ||
+ ($fieldName eq "QAContact" && $relationship == REL_QA))
+ {
+ $events{+EVT_ADDED_REMOVED} = 1;
+ }
+
+ if ($fieldName eq "CC") {
+ my $login = $self->login;
+ my $inold = ($old =~ /^(.*,\s*)?\Q$login\E(,.*)?$/);
+ my $innew = ($new =~ /^(.*,\s*)?\Q$login\E(,.*)?$/);
+ if ($inold != $innew)
+ {
+ $events{+EVT_ADDED_REMOVED} = 1;
+ }
+ }
+ }
+
+ if ($bug_is_new) {
+ # 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;
+ }
+ }
+
+ 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 ($dependencyText ne '') {
+ $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) {
+ my $dbh = Bugzilla->dbh;
+ # We don't create a Bug object from the bug_id here because we only
+ # need one piece of information, and doing so (as of 2004-11-23) slows
+ # down bugmail sending by a factor of 2. If Bug creation was more
+ # lazy, this might not be so bad.
+ my $bug_status = $dbh->selectrow_array('SELECT bug_status
+ FROM bugs WHERE bug_id = ?',
+ undef, $bug_id);
+
+ if ($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;
+ }
+
+ # 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'};
+}
+
+sub is_insider {
+ 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'};
+}
+
+sub is_global_watcher {
+ 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'};
+}
+
+sub is_timetracker {
+ 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'};
+}
+
+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 disabledtext = '' ";
+ $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));
+ }
+
+ $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');
+
+ $dbh->do('INSERT INTO profiles_activity
+ (userid, who, profiles_when, fieldid, newvalue)
+ VALUES (?, ?, NOW(), ?, NOW())',
+ undef, ($user->id, $who, $creation_date_fieldid));
+
+ $dbh->bz_commit_transaction();
+
+ # Return the newly created user account.
+ return $user;
+}
+
+###########################
+# Account Lockout Methods #
+###########################
+
+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;
+}
+
+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};
+}
+
+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};
+}
+
+sub account_ip_login_failures {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+ my $time = $dbh->sql_interval(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 > LOCALTIMESTAMP(0) - $time
+ AND ip_addr = ?
+ ORDER BY login_time", {Slice => {}}, $self->id, $ip_addr);
+ return $self->{account_ip_login_failures};
+}
+
+###############
+# Subroutines #
+###############
+
+sub is_available_username {
+ my ($username, $old_username) = @_;
+
+ 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 $eventdata = $dbh->selectrow_array(
+ "SELECT eventdata
+ FROM tokens
+ WHERE (tokentype = 'emailold'
+ 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 && ($eventdata eq "$old_username:$username")) {
+ return 1;
+ }
+ return 0;
+ }
+
+ return 1;
+}
+
+sub login_to_id {
+ my ($login, $throw_error) = @_;
+ my $dbh = Bugzilla->dbh;
+ # 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);
+ my $user_id = $dbh->selectrow_array("SELECT userid FROM profiles WHERE " .
+ $dbh->sql_istrcmp('login_name', '?'),
+ undef, $login);
+ if ($user_id) {
+ return $user_id;
+ } elsif ($throw_error) {
+ ThrowUserError('invalid_username', { name => $login });
+ } else {
+ return 0;
+ }
+}
+
+sub user_id_to_login {
+ my $user_id = shift;
+ my $dbh = Bugzilla->dbh;
+
+ return '' unless ($user_id && detaint_natural($user_id));
+
+ my $login = $dbh->selectrow_array('SELECT login_name FROM profiles
+ WHERE userid = ?', undef, $user_id);
+ return $login || '';
+}
+
+sub validate_password {
+ my ($password, $matchpassword) = @_;
+
+ if (length($password) < USER_PASSWORD_MIN_LENGTH) {
+ ThrowUserError('password_too_short');
+ } elsif ((defined $matchpassword) && ($password ne $matchpassword)) {
+ ThrowUserError('passwords_dont_match');
+ }
+ # Having done these checks makes us consider the password untainted.
+ trick_taint($_[0]);
+ return 1;
+}
+
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::User - Object for a Bugzilla user
+
+=head1 SYNOPSIS
+
+ use Bugzilla::User;
+
+ my $user = new Bugzilla::User($id);
+
+ my @get_selectable_classifications =
+ $user->get_selectable_classifications;
+
+ # Class Functions
+ $user = Bugzilla::User->create({
+ login_name => $username,
+ realname => $realname,
+ cryptpassword => $plaintext_password,
+ disabledtext => $disabledtext,
+ disable_mail => 0});
+
+=head1 DESCRIPTION
+
+This package handles Bugzilla users. Data obtained from here is read-only;
+there is currently no way to modify a user from this package.
+
+Note that the currently logged in user (if any) is available via
+L<Bugzilla-E<gt>user|Bugzilla/"user">.
+
+C<Bugzilla::User> is an implementation of L<Bugzilla::Object>, and thus
+provides all the methods of L<Bugzilla::Object> in addition to the
+methods listed below.
+
+=head1 CONSTANTS
+
+=over
+
+=item C<USER_MATCH_MULTIPLE>
+
+Returned by C<match_field()> when at least one field matched more than
+one user, but no matches failed.
+
+=item C<USER_MATCH_FAILED>
+
+Returned by C<match_field()> when at least one field failed to match
+anything.
+
+=item C<USER_MATCH_SUCCESS>
+
+Returned by C<match_field()> when all fields successfully matched only one
+user.
+
+=item C<MATCH_SKIP_CONFIRM>
+
+Passed in to match_field to tell match_field to never display a
+confirmation screen.
+
+=back
+
+=head1 METHODS
+
+=head2 Constructors
+
+=over
+
+=item C<super_user>
+
+Returns a user who is in all groups, but who does not really exist in the
+database. Used for non-web scripts like L<checksetup> that need to make
+database changes and so on.
+
+=back
+
+=head2 Saved and Shared Queries
+
+=over
+
+=item C<queries>
+
+Returns an arrayref of the user's own saved queries, sorted by name. The
+array contains L<Bugzilla::Search::Saved> objects.
+
+=item C<queries_subscribed>
+
+Returns an arrayref of shared queries that the user has subscribed to.
+That is, these are shared queries that the user sees in their footer.
+This array contains L<Bugzilla::Search::Saved> objects.
+
+=item C<queries_available>
+
+Returns an arrayref of all queries to which the user could possibly
+subscribe. This includes the contents of L</queries_subscribed>.
+An array of L<Bugzilla::Search::Saved> objects.
+
+=item C<flush_queries_cache>
+
+Some code modifies the set of stored queries. Because C<Bugzilla::User> does
+not handle these modifications, but does cache the result of calling C<queries>
+internally, such code must call this method to flush the cached result.
+
+=item C<queryshare_groups>
+
+An arrayref of group ids. The user can share their own queries with these
+groups.
+
+=back
+
+=head2 Account Lockout
+
+=over
+
+=item C<account_is_locked_out>
+
+Returns C<1> if the account has failed to log in too many times recently,
+and thus is locked out for a period of time. Returns C<0> otherwise.
+
+=item C<account_ip_login_failures>
+
+Returns an arrayref of hashrefs, that contains information about the recent
+times that this account has failed to log in from the current remote IP.
+The hashes contain C<ip_addr>, C<login_time>, and C<user_id>.
+
+=item C<note_login_failure>
+
+This notes that this account has failed to log in, and stores the fact
+in the database. The storing happens immediately, it does not wait for
+you to call C<update>.
+
+=back
+
+=head2 Other Methods
+
+=over
+
+=item C<id>
+
+Returns the userid for this user.
+
+=item C<login>
+
+Returns the login name for this user.
+
+=item C<email>
+
+Returns the user's email address. Currently this is the same value as the
+login.
+
+=item C<name>
+
+Returns the 'real' name for this user, if any.
+
+=item C<showmybugslink>
+
+Returns C<1> if the user has set his preference to show the 'My Bugs' link in
+the page footer, and C<0> otherwise.
+
+=item C<identity>
+
+Returns a string for the identity of the user. This will be of the form
+C<name E<lt>emailE<gt>> if the user has specified a name, and C<email>
+otherwise.
+
+=item C<nick>
+
+Returns a user "nickname" -- i.e. a shorter, not-necessarily-unique name by
+which to identify the user. Currently the part of the user's email address
+before the at sign (@), but that could change, especially if we implement
+usernames not dependent on email address.
+
+=item C<authorizer>
+
+This is the L<Bugzilla::Auth> object that the User logged in with.
+If the user hasn't logged in yet, a new, empty Bugzilla::Auth() object is
+returned.
+
+=item C<set_authorizer($authorizer)>
+
+Sets the L<Bugzilla::Auth> object to be returned by C<authorizer()>.
+Should only be called by C<Bugzilla::Auth::login>, for the most part.
+
+=item C<disabledtext>
+
+Returns the disable text of the user, if any.
+
+=item C<settings>
+
+Returns a hash of hashes which holds the user's settings. The first key is
+the name of the setting, as found in setting.name. The second key is one of:
+is_enabled - true if the user is allowed to set the preference themselves;
+ false to force the site defaults
+ for themselves or must accept the global site default value
+default_value - the global site default for this setting
+value - the value of this setting for this user. Will be the same
+ as the default_value if the user is not logged in, or if
+ is_default is true.
+is_default - a boolean to indicate whether the user has chosen to make
+ a preference for themself or use the site default.
+
+=item C<timezone>
+
+Returns the timezone used to display dates and times to the user,
+as a DateTime::TimeZone object.
+
+=item C<groups>
+
+Returns an arrayref of L<Bugzilla::Group> objects representing
+groups that this user is a member of.
+
+=item C<groups_as_string>
+
+Returns a string containing a comma-separated list of numeric group ids. If
+the user is not a member of any groups, returns "-1". This is most often used
+within an SQL IN() function.
+
+=item C<groups_in_sql>
+
+This returns an C<IN> clause for SQL, containing either all of the groups
+the user is in, or C<-1> if the user is in no groups. This takes one
+argument--the name of the SQL field that should be on the left-hand-side
+of the C<IN> statement, which defaults to C<group_id> if not specified.
+
+=item C<in_group($group_name, $product_id)>
+
+Determines whether or not a user is in the given group by name.
+If $product_id is given, it also checks for local privileges for
+this product.
+
+=item C<in_group_id>
+
+Determines whether or not a user is in the given group by id.
+
+=item C<bless_groups>
+
+Returns an arrayref of L<Bugzilla::Group> objects.
+
+The arrayref consists of the groups the user can bless, taking into account
+that having editusers permissions means that you can bless all groups, and
+that you need to be able to see a group in order to bless it.
+
+=item C<get_products_by_permission($group)>
+
+Returns a list of product objects for which the user has $group privileges
+and which he can access.
+$group must be one of the groups defined in PER_PRODUCT_PRIVILEGES.
+
+=item C<can_see_user(user)>
+
+Returns 1 if the specified user account exists and is visible to the user,
+0 otherwise.
+
+=item C<can_edit_product(prod_id)>
+
+Determines if, given a product id, the user can edit bugs in this product
+at all.
+
+=item C<can_see_bug(bug_id)>
+
+Determines if the user can see the specified bug.
+
+=item C<can_see_product(product_name)>
+
+Returns 1 if the user can access the specified product, and 0 if the user
+should not be aware of the existence of the product.
+
+=item C<derive_regexp_groups>
+
+Bugzilla allows for group inheritance. When data about the user (or any of the
+groups) changes, the database must be updated. Handling updated groups is taken
+care of by the constructor. However, when updating the email address, the
+user may be placed into different groups, based on a new email regexp. This
+method should be called in such a case to force reresolution of these groups.
+
+=item C<clear_product_cache>
+
+Clears the stored values for L</get_selectable_products>,
+L</get_enterable_products>, etc. so that their data will be read from
+the database again. Used mostly by L<Bugzilla::Product>.
+
+=item C<get_selectable_products>
+
+ Description: Returns all products the user is allowed to access. This list
+ is restricted to some given classification if $classification_id
+ is given.
+
+ Params: $classification_id - (optional) The ID of the classification
+ the products belong to.
+
+ Returns: An array of product objects, sorted by the product name.
+
+=item C<get_selectable_classifications>
+
+ Description: Returns all classifications containing at least one product
+ the user is allowed to view.
+
+ Params: none
+
+ Returns: An array of Bugzilla::Classification objects, sorted by
+ the classification name.
+
+=item C<can_enter_product($product_name, $warn)>
+
+ Description: Returns 1 if the user can enter bugs into the specified product.
+ If the user cannot enter bugs into the product, the behavior of
+ this method depends on the value of $warn:
+ - if $warn is false (or not given), a 'false' value is returned;
+ - if $warn is true, an error is thrown.
+
+ Params: $product_name - a product name.
+ $warn - optional parameter, indicating whether an error
+ must be thrown if the user cannot enter bugs
+ into the specified product.
+
+ Returns: 1 if the user can enter bugs into the product,
+ 0 if the user cannot enter bugs into the product and if $warn
+ is false (an error is thrown if $warn is true).
+
+=item C<get_enterable_products>
+
+ Description: Returns an array of product objects into which the user is
+ allowed to enter bugs.
+
+ Params: none
+
+ Returns: an array of product objects.
+
+=item C<can_access_product($product)>
+
+Returns 1 if the user can search or enter bugs into the specified product
+(either a L<Bugzilla::Product> or a product name), and 0 if the user should
+not be aware of the existence of the product.
+
+=item C<get_accessible_products>
+
+ Description: Returns an array of product objects the user can search
+ or enter bugs against.
+
+ Params: none
+
+ Returns: an array of product objects.
+
+=item C<check_can_admin_product($product_name)>
+
+ Description: Checks whether the user is allowed to administrate the product.
+
+ Params: $product_name - a product name.
+
+ Returns: On success, a product object. On failure, an error is thrown.
+
+=item C<can_request_flag($flag_type)>
+
+ Description: Checks whether the user can request flags of the given type.
+
+ Params: $flag_type - a Bugzilla::FlagType object.
+
+ Returns: 1 if the user can request flags of the given type,
+ 0 otherwise.
+
+=item C<can_set_flag($flag_type)>
+
+ Description: Checks whether the user can set flags of the given type.
+
+ Params: $flag_type - a Bugzilla::FlagType object.
+
+ Returns: 1 if the user can set flags of the given type,
+ 0 otherwise.
+
+=item C<get_userlist>
+
+Returns a reference to an array of users. The array is populated with hashrefs
+containing the login, identity and visibility. Users that are not visible to this
+user will have 'visible' set to zero.
+
+=item C<direct_group_membership>
+
+Returns a reference to an array of group objects. Groups the user belong to
+by group inheritance are excluded from the list.
+
+=item C<visible_groups_inherited>
+
+Returns a list of all groups whose members should be visible to this user.
+Since this list is flattened already, there is no need for all users to
+be have derived groups up-to-date to select the users meeting this criteria.
+
+=item C<visible_groups_direct>
+
+Returns a list of groups that the user is aware of.
+
+=item C<visible_groups_as_string>
+
+Returns the result of C<visible_groups_inherited> as a string (a comma-separated
+list).
+
+=item C<product_responsibilities>
+
+Retrieve user's product responsibilities as a list of component objects.
+Each object is a component the user has a responsibility for.
+
+=item C<can_bless>
+
+When called with no arguments:
+Returns C<1> if the user can bless at least one group, returns C<0> otherwise.
+
+When called with one argument:
+Returns C<1> if the user can bless the group with that id, returns
+C<0> otherwise.
+
+=item C<wants_bug_mail>
+
+Returns true if the user wants mail for a given bug change.
+
+=item C<wants_mail>
+
+Returns true if the user wants mail for a given set of events. This method is
+more general than C<wants_bug_mail>, allowing you to check e.g. permissions
+for flag mail.
+
+=item C<is_mover>
+
+Returns true if the user is in the list of users allowed to move bugs
+to another database. Note that this method doesn't check whether bug
+moving is enabled.
+
+=item C<is_insider>
+
+Returns true if the user can access private comments and attachments,
+i.e. if the 'insidergroup' parameter is set and the user belongs to this group.
+
+=item C<is_global_watcher>
+
+Returns true if the user is a global watcher,
+i.e. if the 'globalwatchers' parameter contains the user.
+
+=back
+
+=head1 CLASS FUNCTIONS
+
+These are functions that are not called on a User object, but instead are
+called "statically," just like a normal procedural function.
+
+=over 4
+
+=item C<create>
+
+The same as L<Bugzilla::Object/create>.
+
+Params: login_name - B<Required> The login name for the new user.
+ realname - The full name for the new user.
+ cryptpassword - B<Required> The password for the new user.
+ Even though the name says "crypt", you should just specify
+ a plain-text password. If you specify '*', the user will not
+ be able to log in using DB authentication.
+ disabledtext - The disable-text for the new user. If given, the user
+ will be disabled, meaning he cannot log in. Defaults to an
+ empty string.
+ disable_mail - If 1, bug-related mail will not be sent to this user;
+ if 0, mail will be sent depending on the user's email preferences.
+
+=item C<check>
+
+Takes a username as its only argument. Throws an error if there is no
+user with that username. Returns a C<Bugzilla::User> object.
+
+=item C<is_available_username>
+
+Returns a boolean indicating whether or not the supplied username is
+already taken in Bugzilla.
+
+Params: $username (scalar, string) - The full login name of the username
+ that you are checking.
+ $old_username (scalar, string) - If you are checking an email-change
+ token, insert the "old" username that the user is changing from,
+ here. Then, as long as it's the right user for that token, he
+ can change his username to $username. (That is, this function
+ will return a boolean true value).
+
+=item C<login_to_id($login, $throw_error)>
+
+Takes a login name of a Bugzilla user and changes that into a numeric
+ID for that user. This ID can then be passed to Bugzilla::User::new to
+create a new user.
+
+If no valid user exists with that login name, then the function returns 0.
+However, if $throw_error is set, the function will throw a user error
+instead of returning.
+
+This function can also be used when you want to just find out the userid
+of a user, but you don't want the full weight of Bugzilla::User.
+
+However, consider using a Bugzilla::User object instead of this function
+if you need more information about the user than just their ID.
+
+=item C<user_id_to_login($user_id)>
+
+Returns the login name of the user account for the given user ID. If no
+valid user ID is given or the user has no entry in the profiles table,
+we return an empty string.
+
+=item C<validate_password($passwd1, $passwd2)>
+
+Returns true if a password is valid (i.e. meets Bugzilla's
+requirements for length and content), else returns false.
+Untaints C<$passwd1> if successful.
+
+If a second password is passed in, this function also verifies that
+the two passwords match.
+
+=item C<match_field($data, $fields, $behavior)>
+
+=over
+
+=item B<Description>
+
+Wrapper for the C<match()> function.
+
+=item B<Params>
+
+=over
+
+=item C<$fields> - A hashref with field names as keys and a hash as values.
+Each hash is of the form { 'type' => 'single|multi' }, which specifies
+whether the field can take a single login name only or several.
+
+=item C<$data> (optional) - A hashref with field names as keys and field values
+as values. If undefined, C<Bugzilla-E<gt>input_params> is used.
+
+=item C<$behavior> (optional) - If set to C<MATCH_SKIP_CONFIRM>, no confirmation
+screen is displayed. In that case, the fields which don't match a unique user
+are left undefined. If not set, a confirmation screen is displayed if at
+least one field doesn't match any login name or match more than one.
+
+=back
+
+=item B<Returns>
+
+If the third parameter is set to C<MATCH_SKIP_CONFIRM>, the function returns
+either C<USER_MATCH_SUCCESS> if all fields can be set unambiguously,
+C<USER_MATCH_FAILED> if at least one field doesn't match any user account,
+or C<USER_MATCH_MULTIPLE> if some fields match more than one user account.
+
+If the third parameter is not set, then if all fields could be set
+unambiguously, nothing is returned, else a confirmation page is displayed.
+
+=item B<Note>
+
+This function must be called early in a script, before anything external
+is done with the data.
+
+=back
+
+=back
+
+=head1 SEE ALSO
+
+L<Bugzilla|Bugzilla>
diff --git a/Bugzilla/User/Setting.pm b/Bugzilla/User/Setting.pm
new file mode 100644
index 000000000..7a6c72fd3
--- /dev/null
+++ b/Bugzilla/User/Setting.pm
@@ -0,0 +1,433 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s): Shane H. W. Travis <travis@sedsystems.ca>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+# Marc Schumann <wurblzap@gmail.com>
+# Frédéric Buclin <LpSolit@gmail.com>
+
+
+package Bugzilla::User::Setting;
+
+use strict;
+use base qw(Exporter);
+
+
+# Module stuff
+@Bugzilla::User::Setting::EXPORT = qw(get_all_settings get_defaults
+ add_setting);
+
+use Bugzilla::Error;
+use Bugzilla::Util qw(trick_taint get_text);
+
+###############################
+### Module Initialization ###
+###############################
+
+sub new {
+ my $invocant = shift;
+ my $setting_name = shift;
+ my $user_id = shift;
+
+ my $class = ref($invocant) || $invocant;
+ my $subclass = '';
+
+ # Create a ref to an empty hash and bless it
+ my $self = {};
+
+ my $dbh = Bugzilla->dbh;
+
+ # Confirm that the $setting_name is properly formed;
+ # if not, throw a code error.
+ #
+ # NOTE: due to the way that setting names are used in templates,
+ # they must conform to to the limitations set for HTML NAMEs and IDs.
+ #
+ if ( !($setting_name =~ /^[a-zA-Z][-.:\w]*$/) ) {
+ ThrowCodeError("setting_name_invalid", { name => $setting_name });
+ }
+
+ # If there were only two parameters passed in, then we need
+ # to retrieve the information for this setting ourselves.
+ if (scalar @_ == 0) {
+
+ my ($default, $is_enabled, $value);
+ ($default, $is_enabled, $value, $subclass) =
+ $dbh->selectrow_array(
+ q{SELECT default_value, is_enabled, setting_value, subclass
+ FROM setting
+ LEFT JOIN profile_setting
+ ON setting.name = profile_setting.setting_name
+ WHERE name = ?
+ AND profile_setting.user_id = ?},
+ undef,
+ $setting_name, $user_id);
+
+ # if not defined, then grab the default value
+ if (! defined $value) {
+ ($default, $is_enabled, $subclass) =
+ $dbh->selectrow_array(
+ q{SELECT default_value, is_enabled, subclass
+ FROM setting
+ WHERE name = ?},
+ undef,
+ $setting_name);
+ }
+
+ $self->{'is_enabled'} = $is_enabled;
+ $self->{'default_value'} = $default;
+
+ # IF the setting is enabled, AND the user has chosen a setting
+ # THEN return that value
+ # ELSE return the site default, and note that it is the default.
+ if ( ($is_enabled) && (defined $value) ) {
+ $self->{'value'} = $value;
+ } else {
+ $self->{'value'} = $default;
+ $self->{'isdefault'} = 1;
+ }
+ }
+ else {
+ # If the values were passed in, simply assign them and return.
+ $self->{'is_enabled'} = shift;
+ $self->{'default_value'} = shift;
+ $self->{'value'} = shift;
+ $self->{'is_default'} = shift;
+ $subclass = shift;
+ }
+ if ($subclass) {
+ eval('require ' . $class . '::' . $subclass);
+ $@ && ThrowCodeError('setting_subclass_invalid',
+ {'subclass' => $subclass});
+ $class = $class . '::' . $subclass;
+ }
+ bless($self, $class);
+
+ $self->{'_setting_name'} = $setting_name;
+ $self->{'_user_id'} = $user_id;
+
+ return $self;
+}
+
+###############################
+### Subroutine Definitions ###
+###############################
+
+sub add_setting {
+ my ($name, $values, $default_value, $subclass, $force_check) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ my $exists = _setting_exists($name);
+ return if ($exists && !$force_check);
+
+ ($name && $default_value)
+ || ThrowCodeError("setting_info_invalid");
+
+ if ($exists) {
+ # If this setting exists, we delete it and regenerate it.
+ $dbh->do('DELETE FROM setting_value WHERE name = ?', undef, $name);
+ $dbh->do('DELETE FROM setting WHERE name = ?', undef, $name);
+ # Remove obsolete user preferences for this setting.
+ if (defined $values && scalar(@$values)) {
+ my $list = join(', ', map {$dbh->quote($_)} @$values);
+ $dbh->do("DELETE FROM profile_setting
+ WHERE setting_name = ? AND setting_value NOT IN ($list)",
+ undef, $name);
+ }
+ }
+ else {
+ print get_text('install_setting_new', { name => $name }) . "\n";
+ }
+ $dbh->do(q{INSERT INTO setting (name, default_value, is_enabled, subclass)
+ VALUES (?, ?, 1, ?)},
+ undef, ($name, $default_value, $subclass));
+
+ my $sth = $dbh->prepare(q{INSERT INTO setting_value (name, value, sortindex)
+ VALUES (?, ?, ?)});
+
+ my $sortindex = 5;
+ foreach my $key (@$values){
+ $sth->execute($name, $key, $sortindex);
+ $sortindex += 5;
+ }
+}
+
+sub get_all_settings {
+ my ($user_id) = @_;
+ my $settings = get_defaults($user_id); # first get the defaults
+ my $dbh = Bugzilla->dbh;
+
+ my $sth = $dbh->prepare(
+ q{SELECT name, default_value, is_enabled, setting_value, subclass
+ FROM setting
+ LEFT JOIN profile_setting
+ ON setting.name = profile_setting.setting_name
+ WHERE profile_setting.user_id = ?
+ ORDER BY name});
+
+ $sth->execute($user_id);
+ while (my ($name, $default_value, $is_enabled, $value, $subclass)
+ = $sth->fetchrow_array())
+ {
+
+ my $is_default;
+
+ if ( ($is_enabled) && (defined $value) ) {
+ $is_default = 0;
+ } else {
+ $value = $default_value;
+ $is_default = 1;
+ }
+
+ $settings->{$name} = new Bugzilla::User::Setting(
+ $name, $user_id, $is_enabled,
+ $default_value, $value, $is_default, $subclass);
+ }
+
+ return $settings;
+}
+
+sub get_defaults {
+ my ($user_id) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $default_settings = {};
+
+ $user_id ||= 0;
+
+ my $sth = $dbh->prepare(q{SELECT name, default_value, is_enabled, subclass
+ FROM setting
+ ORDER BY name});
+ $sth->execute();
+ while (my ($name, $default_value, $is_enabled, $subclass)
+ = $sth->fetchrow_array())
+ {
+
+ $default_settings->{$name} = new Bugzilla::User::Setting(
+ $name, $user_id, $is_enabled, $default_value, $default_value, 1,
+ $subclass);
+ }
+
+ return $default_settings;
+}
+
+sub set_default {
+ my ($setting_name, $default_value, $is_enabled) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ my $sth = $dbh->prepare(q{UPDATE setting
+ SET default_value = ?, is_enabled = ?
+ WHERE name = ?});
+ $sth->execute($default_value, $is_enabled, $setting_name);
+}
+
+sub _setting_exists {
+ my ($setting_name) = @_;
+ my $dbh = Bugzilla->dbh;
+ return $dbh->selectrow_arrayref(
+ "SELECT 1 FROM setting WHERE name = ?", undef, $setting_name) || 0;
+}
+
+
+sub legal_values {
+ my ($self) = @_;
+
+ return $self->{'legal_values'} if defined $self->{'legal_values'};
+
+ my $dbh = Bugzilla->dbh;
+ $self->{'legal_values'} = $dbh->selectcol_arrayref(
+ q{SELECT value
+ FROM setting_value
+ WHERE name = ?
+ ORDER BY sortindex},
+ undef, $self->{'_setting_name'});
+
+ return $self->{'legal_values'};
+}
+
+sub validate_value {
+ my $self = shift;
+
+ if (grep(/^$_[0]$/, @{$self->legal_values()})) {
+ trick_taint($_[0]);
+ }
+ else {
+ ThrowCodeError('setting_value_invalid',
+ {'name' => $self->{'_setting_name'},
+ 'value' => $_[0]});
+ }
+}
+
+sub reset_to_default {
+ my ($self) = @_;
+
+ my $dbh = Bugzilla->dbh;
+ my $sth = $dbh->do(q{ DELETE
+ FROM profile_setting
+ WHERE setting_name = ?
+ AND user_id = ?},
+ undef, $self->{'_setting_name'}, $self->{'_user_id'});
+ $self->{'value'} = $self->{'default_value'};
+ $self->{'is_default'} = 1;
+}
+
+sub set {
+ my ($self, $value) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $query;
+
+ if ($self->{'is_default'}) {
+ $query = q{INSERT INTO profile_setting
+ (setting_value, setting_name, user_id)
+ VALUES (?,?,?)};
+ } else {
+ $query = q{UPDATE profile_setting
+ SET setting_value = ?
+ WHERE setting_name = ?
+ AND user_id = ?};
+ }
+ $dbh->do($query, undef, $value, $self->{'_setting_name'}, $self->{'_user_id'});
+
+ $self->{'value'} = $value;
+ $self->{'is_default'} = 0;
+}
+
+
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::User::Setting - Object for a user preference setting
+
+=head1 SYNOPSIS
+
+Setting.pm creates a setting object, which is a hash containing the user
+preference information for a single preference for a single user. These
+are usually accessed through the "settings" object of a user, and not
+directly.
+
+=head1 DESCRIPTION
+
+use Bugzilla::User::Setting;
+my $settings;
+
+$settings->{$setting_name} = new Bugzilla::User::Setting(
+ $setting_name, $user_id);
+
+OR
+
+$settings->{$setting_name} = new Bugzilla::User::Setting(
+ $setting_name, $user_id, $is_enabled,
+ $default_value, $value, $is_default);
+
+=head1 CLASS FUNCTIONS
+
+=over 4
+
+=item C<add_setting($name, \@values, $default_value, $subclass, $force_check)>
+
+Description: Checks for the existence of a setting, and adds it
+ to the database if it does not yet exist.
+
+Params: C<$name> - string - the name of the new setting
+ C<$values> - arrayref - contains the new choices
+ for the new Setting.
+ C<$default_value> - string - the site default
+ C<$subclass> - string - name of the module returning
+ the list of valid values. This means legal values are
+ not stored in the DB.
+ C<$force_check> - boolean - when true, the existing setting
+ and all its values are deleted and replaced by new data.
+
+Returns: a pointer to a hash of settings
+
+
+=item C<get_all_settings($user_id)>
+
+Description: Provides the user's choices for each setting in the
+ system; if the user has made no choice, uses the site
+ default instead.
+Params: C<$user_id> - integer - the user id.
+Returns: a pointer to a hash of settings
+
+=item C<get_defaults($user_id)>
+
+Description: When a user is not logged in, they must use the site
+ defaults for every settings; this subroutine provides them.
+Params: C<$user_id> (optional) - integer - the user id. Note that
+ this optional parameter is mainly for internal use only.
+Returns: A pointer to a hash of settings. If $user_id was passed, set
+ the user_id value for each setting.
+
+=item C<set_default($setting_name, $default_value, $is_enabled)>
+
+Description: Sets the global default for a given setting. Also sets
+ whether users are allowed to choose their own value for
+ this setting, or if they must use the global default.
+Params: C<$setting_name> - string - the name of the setting
+ C<$default_value> - string - the new default value for this setting
+ C<$is_enabled> - boolean - if false, all users must use the global default
+Returns: nothing
+
+=begin private
+
+=item C<_setting_exists>
+
+Description: Determines if a given setting exists in the database.
+Params: C<$setting_name> - string - the setting name
+Returns: boolean - true if the setting already exists in the DB.
+
+=back
+
+=end private
+
+=head1 METHODS
+
+=over 4
+
+=item C<legal_values($setting_name)>
+
+Description: Returns all legal values for this setting
+Params: none
+Returns: A reference to an array containing all legal values
+
+=item C<validate_value>
+
+Description: Determines whether a value is valid for the setting
+ by checking against the list of legal values.
+ Untaints the parameter if the value is indeed valid,
+ and throws a setting_value_invalid code error if not.
+Params: An lvalue containing a candidate for a setting value
+Returns: nothing
+
+=item C<reset_to_default>
+
+Description: If a user chooses to use the global default for a given
+ setting, their saved entry is removed from the database via
+ this subroutine.
+Params: none
+Returns: nothing
+
+=item C<set($value)>
+
+Description: If a user chooses to use their own value rather than the
+ global value for a given setting, OR changes their value for
+ a given setting, this subroutine is called to insert or
+ update the database as appropriate.
+Params: C<$value> - string - the new value for this setting for this user.
+Returns: nothing
+
+=back
diff --git a/Bugzilla/User/Setting/Lang.pm b/Bugzilla/User/Setting/Lang.pm
new file mode 100644
index 000000000..79372704d
--- /dev/null
+++ b/Bugzilla/User/Setting/Lang.pm
@@ -0,0 +1,60 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Marc Schumann.
+# Portions created by Marc Schumann are Copyright (c) 2007 Marc Schumann.
+# All rights reserved.
+#
+# Contributor(s): Marc Schumann <wurblzap@gmail.com>
+
+package Bugzilla::User::Setting::Lang;
+
+use strict;
+
+use base qw(Bugzilla::User::Setting);
+
+use Bugzilla::Constants;
+
+sub legal_values {
+ my ($self) = @_;
+
+ return $self->{'legal_values'} if defined $self->{'legal_values'};
+
+ return $self->{'legal_values'} = Bugzilla->languages;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::User::Setting::Lang - Object for a user preference setting for preferred language
+
+=head1 DESCRIPTION
+
+Lang.pm extends Bugzilla::User::Setting and implements a class specialized for
+setting the preferred language.
+
+=head1 METHODS
+
+=over
+
+=item C<legal_values()>
+
+Description: Returns all legal languages
+Params: none
+Returns: A reference to an array containing the names of all legal languages
+
+=back
diff --git a/Bugzilla/User/Setting/Skin.pm b/Bugzilla/User/Setting/Skin.pm
new file mode 100644
index 000000000..f69f3e66c
--- /dev/null
+++ b/Bugzilla/User/Setting/Skin.pm
@@ -0,0 +1,79 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s): Marc Schumann <wurblzap@gmail.com>
+#
+
+
+package Bugzilla::User::Setting::Skin;
+
+use strict;
+
+use base qw(Bugzilla::User::Setting);
+
+use Bugzilla::Constants;
+use File::Spec::Functions;
+use File::Basename;
+
+use constant BUILTIN_SKIN_NAMES => ['standard'];
+
+sub legal_values {
+ my ($self) = @_;
+
+ return $self->{'legal_values'} if defined $self->{'legal_values'};
+
+ my $dirbase = bz_locations()->{'skinsdir'} . '/contrib';
+ # Avoid modification of the list BUILTIN_SKIN_NAMES points to by copying the
+ # list over instead of simply writing $legal_values = BUILTIN_SKIN_NAMES.
+ my @legal_values = @{(BUILTIN_SKIN_NAMES)};
+
+ foreach my $direntry (glob(catdir($dirbase, '*'))) {
+ if (-d $direntry) {
+ # Stylesheet set
+ next if basename($direntry) =~ /^cvs$/i;
+ push(@legal_values, basename($direntry));
+ }
+ elsif ($direntry =~ /\.css$/) {
+ # Single-file stylesheet
+ push(@legal_values, basename($direntry));
+ }
+ }
+
+ return $self->{'legal_values'} = \@legal_values;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::User::Setting::Skin - Object for a user preference setting for skins
+
+=head1 DESCRIPTION
+
+Skin.pm extends Bugzilla::User::Setting and implements a class specialized for
+skins settings.
+
+=head1 METHODS
+
+=over
+
+=item C<legal_values()>
+
+Description: Returns all legal skins
+Params: none
+Returns: A reference to an array containing the names of all legal skins
+
+=back
diff --git a/Bugzilla/User/Setting/Timezone.pm b/Bugzilla/User/Setting/Timezone.pm
new file mode 100644
index 000000000..d75b1113b
--- /dev/null
+++ b/Bugzilla/User/Setting/Timezone.pm
@@ -0,0 +1,71 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Frédéric Buclin.
+# Portions created by Frédéric Buclin are Copyright (c) 2008 Frédéric Buclin.
+# All rights reserved.
+#
+# Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
+
+package Bugzilla::User::Setting::Timezone;
+
+use strict;
+
+use DateTime::TimeZone;
+
+use base qw(Bugzilla::User::Setting);
+
+use Bugzilla::Constants;
+
+sub legal_values {
+ my ($self) = @_;
+
+ 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');
+
+ return $self->{'legal_values'} = \@timezones;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::User::Setting::Timezone - Object for a user preference setting for desired timezone
+
+=head1 DESCRIPTION
+
+Timezone.pm extends Bugzilla::User::Setting and implements a class specialized for
+setting the desired timezone.
+
+=head1 METHODS
+
+=over
+
+=item C<legal_values()>
+
+Description: Returns all legal timezones
+
+Params: none
+
+Returns: A reference to an array containing the names of all legal timezones
+
+=back
diff --git a/Bugzilla/Util.pm b/Bugzilla/Util.pm
new file mode 100644
index 000000000..b1655f7ca
--- /dev/null
+++ b/Bugzilla/Util.pm
@@ -0,0 +1,1085 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Dan Mosedale <dmose@mozilla.org>
+# Jacob Steenhagen <jake@bugzilla.org>
+# Bradley Baetz <bbaetz@student.usyd.edu.au>
+# Christopher Aillon <christopher@aillon.com>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+# Frédéric Buclin <LpSolit@gmail.com>
+# Marc Schumann <wurblzap@gmail.com>
+
+package Bugzilla::Util;
+
+use strict;
+
+use base qw(Exporter);
+@Bugzilla::Util::EXPORT = qw(trick_taint detaint_natural
+ detaint_signed
+ html_quote url_quote xml_quote
+ css_class_quote html_light_quote url_decode
+ i_am_cgi correct_urlbase remote_ip
+ do_ssl_redirect_if_required use_attachbase
+ diff_arrays on_main_db
+ trim wrap_hard wrap_comment find_wrap_point
+ format_time format_time_decimal validate_date
+ validate_time datetime_from
+ file_mod_time is_7bit_clean
+ bz_crypt generate_random_password
+ validate_email_syntax clean_text
+ get_text template_var disable_utf8);
+
+use Bugzilla::Constants;
+
+use Date::Parse;
+use Date::Format;
+use DateTime;
+use DateTime::TimeZone;
+use Digest;
+use Email::Address;
+use List::Util qw(first);
+use Scalar::Util qw(tainted);
+use Template::Filters;
+use Text::Wrap;
+
+sub trick_taint {
+ require Carp;
+ Carp::confess("Undef to trick_taint") unless defined $_[0];
+ my $match = $_[0] =~ /^(.*)$/s;
+ $_[0] = $match ? $1 : undef;
+ return (defined($_[0]));
+}
+
+sub detaint_natural {
+ my $match = $_[0] =~ /^(\d+)$/;
+ $_[0] = $match ? int($1) : undef;
+ return (defined($_[0]));
+}
+
+sub detaint_signed {
+ my $match = $_[0] =~ /^([-+]?\d+)$/;
+ # 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) = Template::Filters::html_filter(@_);
+ # Obscure '@'.
+ $var =~ s/\@/\&#64;/g;
+ if (Bugzilla->params->{'utf8'}) {
+ # Remove the following characters because they're
+ # influencing BiDi:
+ # --------------------------------------------------------
+ # |Code |Name |UTF-8 representation|
+ # |------|--------------------------|--------------------|
+ # |U+202a|Left-To-Right Embedding |0xe2 0x80 0xaa |
+ # |U+202b|Right-To-Left Embedding |0xe2 0x80 0xab |
+ # |U+202c|Pop Directional Formatting|0xe2 0x80 0xac |
+ # |U+202d|Left-To-Right Override |0xe2 0x80 0xad |
+ # |U+202e|Right-To-Left Override |0xe2 0x80 0xae |
+ # --------------------------------------------------------
+ #
+ # The following are characters influencing BiDi, too, but
+ # they can be spared from filtering because they don't
+ # influence more than one character right or left:
+ # --------------------------------------------------------
+ # |Code |Name |UTF-8 representation|
+ # |------|--------------------------|--------------------|
+ # |U+200e|Left-To-Right Mark |0xe2 0x80 0x8e |
+ # |U+200f|Right-To-Left Mark |0xe2 0x80 0x8f |
+ # --------------------------------------------------------
+ $var =~ s/[\x{202a}-\x{202e}]//g;
+ }
+ return $var;
+}
+
+sub html_light_quote {
+ my ($text) = @_;
+
+ # List of allowed HTML elements having no attributes.
+ my @allow = qw(b strong em i u p br abbr acronym ins del cite code var
+ dfn samp kbd big small sub sup tt dd dt dl ul li ol
+ fieldset legend);
+
+ 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;
+ }
+ else {
+ # We can be less restrictive. We can accept elements with attributes.
+ push(@allow, qw(a blockquote q span));
+
+ # Allowed protocols.
+ my $safe_protocols = join('|', SAFE_PROTOCOLS);
+ my $protocol_regexp = qr{(^(?:$safe_protocols):|^[^:]+$)}i;
+
+ # Deny all elements and attributes unless explicitly authorized.
+ my @default = (0 => {
+ id => 1,
+ name => 1,
+ class => 1,
+ '*' => 0, # Reject all other attributes.
+ }
+ );
+
+ # Specific rules for allowed elements. If no specific rule is set
+ # for a given element, then the default is used.
+ my @rules = (a => {
+ href => $protocol_regexp,
+ title => 1,
+ id => 1,
+ name => 1,
+ class => 1,
+ '*' => 0, # Reject all other attributes.
+ },
+ blockquote => {
+ cite => $protocol_regexp,
+ id => 1,
+ name => 1,
+ class => 1,
+ '*' => 0, # Reject all other attributes.
+ },
+ 'q' => {
+ cite => $protocol_regexp,
+ id => 1,
+ name => 1,
+ class => 1,
+ '*' => 0, # Reject all other attributes.
+ },
+ );
+
+ my $scrubber = HTML::Scrubber->new(default => \@default,
+ allow => \@allow,
+ rules => \@rules,
+ comment => 0,
+ process => 0);
+
+ return $scrubber->scrub($text);
+ }
+}
+
+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;
+ }
+ }
+ 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;
+}
+
+sub css_class_quote {
+ my ($toencode) = (@_);
+ $toencode =~ s#[ /]#_#g;
+ $toencode =~ s/([^a-zA-Z0-9_\-.])/uc sprintf("&#x%x;",ord($1))/eg;
+ return $toencode;
+}
+
+sub xml_quote {
+ my ($var) = (@_);
+ $var =~ s/\&/\&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;
+}
+
+# This function must not be relied upon to return a valid string to pass to
+# the DB or the user in UTF-8 situations. The only thing you can rely upon
+# it for is that if you url_decode a string, it will url_encode back to the
+# exact same thing.
+sub url_decode {
+ my ($todecode) = (@_);
+ $todecode =~ tr/+/ /; # pluses become spaces
+ $todecode =~ s/%([0-9a-fA-F]{2})/pack("c",hex($1))/ge;
+ return $todecode;
+}
+
+sub i_am_cgi {
+ # I use SERVER_SOFTWARE because it's required to be
+ # defined for all requests in the CGI spec.
+ return exists $ENV{'SERVER_SOFTWARE'} ? 1 : 0;
+}
+
+# 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();
+}
+
+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;
+ }
+}
+
+sub remote_ip {
+ my $ip = $ENV{'REMOTE_ADDR'} || '127.0.0.1';
+ my @proxies = split(/[\s,]+/, Bugzilla->params->{'inbound_proxies'});
+ if (first { $_ eq $ip } @proxies) {
+ $ip = $ENV{'HTTP_X_FORWARDED_FOR'} if $ENV{'HTTP_X_FORWARDED_FOR'};
+ }
+ return $ip;
+}
+
+sub use_attachbase {
+ 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) = @_;
+
+ my @old = @$old_ref;
+ my @new = @$new_ref;
+
+ # For each pair of (old, new) entries:
+ # If they're equal, set them to empty. When done, @old contains entries
+ # that were removed; @new contains ones that got added.
+ foreach my $oldv (@old) {
+ foreach my $newv (@new) {
+ next if ($newv eq '');
+ if ($oldv eq $newv) {
+ $newv = $oldv = '';
+ }
+ }
+ }
+
+ my @removed = grep { $_ ne '' } @old;
+ my @added = grep { $_ ne '' } @new;
+ return (\@removed, \@added);
+}
+
+sub trim {
+ my ($str) = @_;
+ if ($str) {
+ $str =~ s/^\s+//g;
+ $str =~ s/\s+$//g;
+ }
+ return $str;
+}
+
+sub wrap_comment {
+ my ($comment, $cols) = @_;
+ my $wrappedcomment = "";
+
+ # Use 'local', as recommended by Text::Wrap's perldoc.
+ local $Text::Wrap::columns = $cols || COMMENT_COLS;
+ # Make words that are longer than COMMENT_COLS not wrap.
+ local $Text::Wrap::huge = 'overflow';
+ # Don't mess with tabs.
+ local $Text::Wrap::unexpand = 0;
+
+ # If the line starts with ">", don't wrap it. Otherwise, wrap.
+ foreach my $line (split(/\r\n|\r|\n/, $comment)) {
+ if ($line =~ qr/^>/) {
+ $wrappedcomment .= ($line . "\n");
+ }
+ else {
+ # Due to a segfault in Text::Tabs::expand() when processing tabs with
+ # Unicode (see http://rt.perl.org/rt3/Public/Bug/Display.html?id=52104),
+ # we have to remove tabs before processing the comment. This restriction
+ # can go away when we require Perl 5.8.9 or newer.
+ $line =~ s/\t/ /g;
+ $wrappedcomment .= (wrap('', '', $line) . "\n");
+ }
+ }
+
+ chomp($wrappedcomment); # Text::Wrap adds an extra newline at the end.
+ return $wrappedcomment;
+}
+
+sub find_wrap_point {
+ my ($string, $maxpos) = @_;
+ if (!$string) { return 0 }
+ if (length($string) < $maxpos) { return length($string) }
+ my $wrappoint = rindex($string, ",", $maxpos); # look for comma
+ if ($wrappoint < 0) { # can't find comma
+ $wrappoint = rindex($string, " ", $maxpos); # look for space
+ if ($wrappoint < 0) { # can't find space
+ $wrappoint = rindex($string, "-", $maxpos); # look for hyphen
+ if ($wrappoint < 0) { # can't find hyphen
+ $wrappoint = $maxpos; # just truncate it
+ } else {
+ $wrappoint++; # leave hyphen on the left side
+ }
+ }
+ }
+ return $wrappoint;
+}
+
+sub wrap_hard {
+ my ($string, $columns) = @_;
+ local $Text::Wrap::columns = $columns;
+ local $Text::Wrap::unexpand = 0;
+ local $Text::Wrap::huge = 'wrap';
+
+ my $wrapped = wrap('', '', $string);
+ chomp($wrapped);
+ return $wrapped;
+}
+
+sub format_time {
+ my ($date, $format, $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/;
+
+ # strptime($date) returns an empty array if $date has an invalid
+ # date format.
+ my @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, and months from 0 (January).
+ # We have to fix both values.
+ my %args = (
+ year => $time[5] + 1900,
+ 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 => Bugzilla->local_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};
+ }
+
+ 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 format_time_decimal {
+ my ($time) = (@_);
+
+ my $newtime = sprintf("%.2f", $time);
+
+ if ($newtime =~ /0\Z/) {
+ $newtime = sprintf("%.1f", $time);
+ }
+
+ return $newtime;
+}
+
+sub file_mod_time {
+ my ($filename) = (@_);
+ my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
+ $atime,$mtime,$ctime,$blksize,$blocks)
+ = stat($filename);
+ return $mtime;
+}
+
+sub bz_crypt {
+ my ($password, $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;
+ }
+
+ my $crypted_password;
+ if (!$algorithm) {
+ # Wide characters cause crypt to die
+ if (Bugzilla->params->{'utf8'}) {
+ utf8::encode($password) if utf8::is_utf8($password);
+ }
+
+ # Crypt the password.
+ $crypted_password = crypt($password, $salt);
+
+ # HACK: Perl has bug where returned crypted password is considered
+ # tainted. See http://rt.perl.org/rt3/Public/Bug/Display.html?id=59998
+ unless(tainted($password) || tainted($salt)) {
+ trick_taint($crypted_password);
+ }
+ }
+ else {
+ my $hasher = Digest->new($algorithm);
+ # We only want to use the first characters of the salt, no
+ # matter how long of a salt we may have been passed.
+ $salt = substr($salt, 0, PASSWORD_SALT_LENGTH);
+ $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
+# function, here's a quick formula that will help you estimate:
+# We pick from 62 characters, which is close to 64, which is 2^6.
+# So 8 characters is (2^6)^8 == 2^48 combinations. Just multiply 6
+# 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
+ my $rand;
+ if (Bugzilla->feature('rand_security')) {
+ $rand = \&Math::Random::Secure::irand;
+ }
+ else {
+ # For details on why this block works the way it does, see bug 619594.
+ # (Note that we don't do this if Math::Random::Secure is installed,
+ # because we don't need to.)
+ my $counter = 0;
+ $rand = sub {
+ # If we regenerate the seed every 5 characters, our seed is roughly
+ # as strong (in terms of bit size) as our randomly-generated
+ # string itself.
+ _do_srand() if ($counter % 5) == 0;
+ $counter++;
+ return int(rand $_[0]);
+ };
+ }
+ return join("", map{ ('0'..'9','a'..'z','A'..'Z')[$rand->(62)] }
+ (1..$size));
+}
+
+sub _do_srand {
+ # On Windows, calling srand over and over in the same process produces
+ # very bad results. We need a stronger seed.
+ if (ON_WINDOWS) {
+ require Win32;
+ # GuidGen generates random data via Windows's CryptGenRandom
+ # interface, which is documented as being cryptographically secure.
+ my $guid = Win32::GuidGen();
+ # GUIDs look like:
+ # {09531CF1-D0C7-4860-840C-1C8C8735E2AD}
+ $guid =~ s/[-{}]+//g;
+ # Get a 32-bit integer using the first eight hex digits.
+ my $seed = hex(substr($guid, 0, 8));
+ srand($seed);
+ return;
+ }
+
+ # On *nix-like platforms, this uses /dev/urandom, so the seed changes
+ # enough on every invocation.
+ srand();
+}
+
+sub validate_email_syntax {
+ my ($addr) = @_;
+ my $match = Bugzilla->params->{'emailregexp'};
+ my $ret = ($addr =~ /$match/ && $addr !~ /[\\\(\)<>&,;:"\[\] \t\r\n]/);
+ if ($ret) {
+ # We assume these checks to suffice to consider the address untainted.
+ trick_taint($_[0]);
+ }
+ return $ret ? 1 : 0;
+}
+
+sub validate_date {
+ my ($date) = @_;
+ my $date2;
+
+ # $ts is undefined if the parser fails.
+ my $ts = str2time($date);
+ if ($ts) {
+ $date2 = time2str("%Y-%m-%d", $ts);
+
+ $date =~ s/(\d+)-0*(\d+?)-0*(\d+?)/$1-$2-$3/;
+ $date2 =~ s/(\d+)-0*(\d+?)-0*(\d+?)/$1-$2-$3/;
+ }
+ my $ret = ($ts && $date eq $date2);
+ return $ret ? 1 : 0;
+}
+
+sub validate_time {
+ my ($time) = @_;
+ my $time2;
+
+ # $ts is undefined if the parser fails.
+ my $ts = str2time($time);
+ if ($ts) {
+ $time2 = time2str("%H:%M:%S", $ts);
+ if ($time =~ /^(\d{1,2}):(\d\d)(?::(\d\d))?$/) {
+ $time = sprintf("%02d:%02d:%02d", $1, $2, $3 || 0);
+ }
+ }
+ my $ret = ($ts && $time eq $time2);
+ return $ret ? 1 : 0;
+}
+
+sub is_7bit_clean {
+ return $_[0] !~ /[^\x20-\x7E\x0A\x0D]/;
+}
+
+sub clean_text {
+ my $dtext = shift;
+ 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;
+}
+
+sub get_text {
+ my ($name, $vars) = @_;
+ my $template = Bugzilla->template_inner;
+ $vars ||= {};
+ $vars->{'message'} = $name;
+ my $message;
+ if (!$template->process('global/message.txt.tmpl', $vars, \$message)) {
+ require Bugzilla::Error;
+ Bugzilla::Error::ThrowTemplateError($template->error());
+ }
+ # Remove the indenting that exists in messages.html.tmpl.
+ $message =~ s/^ //gm;
+ return $message;
+}
+
+sub template_var {
+ my $name = shift;
+ my $cache = Bugzilla->request_cache->{util_template_var} ||= {};
+ my $template = Bugzilla->template_inner;
+ my $lang = $template->context->{bz_language};
+ return $cache->{$lang}->{$name} if defined $cache->{$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.
+ my $result = $template->process('global/field-descs.none.tmpl',
+ { vars => \%vars, in_template_var => 1 });
+ # Bugzilla::Error can't be "use"d in Bugzilla::Util.
+ if (!$result) {
+ require Bugzilla::Error;
+ Bugzilla::Error::ThrowTemplateError($template->error);
+ }
+ $cache->{$lang} = \%vars;
+ return $vars{$name};
+}
+
+sub disable_utf8 {
+ if (Bugzilla->params->{'utf8'}) {
+ binmode STDOUT, ':bytes'; # Turn off UTF8 encoding.
+ }
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Util - Generic utility functions for bugzilla
+
+=head1 SYNOPSIS
+
+ use Bugzilla::Util;
+
+ # Functions for dealing with variable tainting
+ trick_taint($var);
+ detaint_natural($var);
+ detaint_signed($var);
+
+ # Functions for quoting
+ html_quote($var);
+ url_quote($var);
+ xml_quote($var);
+ email_filter($var);
+
+ # Functions for decoding
+ $rv = url_decode($var);
+
+ # Functions that tell you about your environment
+ my $is_cgi = i_am_cgi();
+ my $urlbase = correct_urlbase();
+
+ # Data manipulation
+ ($removed, $added) = diff_arrays(\@old, \@new);
+
+ # Functions for manipulating strings
+ $val = trim(" abc ");
+ $wrapped = wrap_comment($comment);
+
+ # Functions for formatting time
+ format_time($time);
+ datetime_from($time, $timezone);
+
+ # Functions for dealing with files
+ $time = file_mod_time($filename);
+
+ # Cryptographic Functions
+ $crypted_password = bz_crypt($password);
+ $new_password = generate_random_password($password_length);
+
+ # Validation Functions
+ validate_email_syntax($email);
+ validate_date($date);
+
+ # DB-related functions
+ on_main_db {
+ ... code here ...
+ };
+
+=head1 DESCRIPTION
+
+This package contains various utility functions which do not belong anywhere
+else.
+
+B<It is not intended as a general dumping group for something which
+people feel might be useful somewhere, someday>. Do not add methods to this
+package unless it is intended to be used for a significant number of files,
+and it does not belong anywhere else.
+
+=head1 FUNCTIONS
+
+This package provides several types of routines:
+
+=head2 Tainting
+
+Several functions are available to deal with tainted variables. B<Use these
+with care> to avoid security holes.
+
+=over 4
+
+=item C<trick_taint($val)>
+
+Tricks perl into untainting a particular variable.
+
+Use trick_taint() when you know that there is no way that the data
+in a scalar can be tainted, but taint mode still bails on it.
+
+B<WARNING!! Using this routine on data that really could be tainted defeats
+the purpose of taint mode. It should only be used on variables that have been
+sanity checked in some way and have been determined to be OK.>
+
+=item C<detaint_natural($num)>
+
+This routine detaints a natural number. It returns a true value if the
+value passed in was a valid natural number, else it returns false. You
+B<MUST> check the result of this routine to avoid security holes.
+
+=item C<detaint_signed($num)>
+
+This routine detaints a signed integer. It returns a true value if the
+value passed in was a valid signed integer, else it returns false. You
+B<MUST> check the result of this routine to avoid security holes.
+
+=back
+
+=head2 Quoting
+
+Some values may need to be quoted from perl. However, this should in general
+be done in the template where possible.
+
+=over 4
+
+=item C<html_quote($val)>
+
+Returns a value quoted for use in HTML, with &, E<lt>, E<gt>, E<34> and @ being
+replaced with their appropriate HTML entities. Also, Unicode BiDi controls are
+deleted.
+
+=item C<html_light_quote($val)>
+
+Returns a string where only explicitly allowed HTML elements and attributes
+are kept. All HTML elements and attributes not being in the whitelist are either
+escaped (if HTML::Scrubber is not installed) or removed.
+
+=item C<url_quote($val)>
+
+Quotes characters so that they may be included as part of a url.
+
+=item C<css_class_quote($val)>
+
+Quotes characters so that they may be used as CSS class names. Spaces
+and forward slashes are replaced by underscores.
+
+=item C<xml_quote($val)>
+
+This is similar to C<html_quote>, except that ' is escaped to &apos;. This
+is kept separate from html_quote partly for compatibility with previous code
+(for &apos;) and partly for future handling of non-ASCII characters.
+
+=item C<url_decode($val)>
+
+Converts the %xx encoding from the given URL back to its original form.
+
+=item C<email_filter>
+
+Removes the hostname from email addresses in the string, if the user
+currently viewing Bugzilla is logged out. If the user is logged-in,
+this filter just returns the input string.
+
+=back
+
+=head2 Environment and Location
+
+Functions returning information about your environment or location.
+
+=over 4
+
+=item C<i_am_cgi()>
+
+Tells you whether or not you are being run as a CGI script in a web
+server. For example, it would return false if the caller is running
+in a command-line script.
+
+=item C<correct_urlbase()>
+
+Returns either the C<sslbase> or C<urlbase> parameter, depending on the
+current setting for the C<ssl_redirect> parameter.
+
+=item C<use_attachbase()>
+
+Returns true if an alternate host is used to display attachments; false
+otherwise.
+
+=back
+
+=head2 Data Manipulation
+
+=over 4
+
+=item C<diff_arrays(\@old, \@new)>
+
+ Description: Takes two arrayrefs, and will tell you what it takes to
+ get from @old to @new.
+ Params: @old = array that you are changing from
+ @new = array that you are changing to
+ Returns: A list of two arrayrefs. The first is a reference to an
+ array containing items that were removed from @old. The
+ second is a reference to an array containing items
+ that were added to @old. If both returned arrays are
+ empty, @old and @new contain the same values.
+
+=back
+
+=head2 String Manipulation
+
+=over 4
+
+=item C<trim($str)>
+
+Removes any leading or trailing whitespace from a string. This routine does not
+modify the existing string.
+
+=item C<wrap_hard($string, $size)>
+
+Wraps a string, so that a line is I<never> longer than C<$size>.
+Returns the string, wrapped.
+
+=item C<wrap_comment($comment)>
+
+Takes a bug comment, and wraps it to the appropriate length. The length is
+currently specified in C<Bugzilla::Constants::COMMENT_COLS>. Lines beginning
+with ">" are assumed to be quotes, and they will not be wrapped.
+
+The intended use of this function is to wrap comments that are about to be
+displayed or emailed. Generally, wrapped text should not be stored in the
+database.
+
+=item C<find_wrap_point($string, $maxpos)>
+
+Search for a comma, a whitespace or a hyphen to split $string, within the first
+$maxpos characters. If none of them is found, just split $string at $maxpos.
+The search starts at $maxpos and goes back to the beginning of the string.
+
+=item C<is_7bit_clean($str)>
+
+Returns true is the string contains only 7-bit characters (ASCII 32 through 126,
+ASCII 10 (LineFeed) and ASCII 13 (Carrage Return).
+
+=item C<disable_utf8()>
+
+Disable utf8 on STDOUT (and display raw data instead).
+
+=item C<clean_text($str)>
+Returns the parameter "cleaned" by exchanging non-printable characters with spaces.
+Specifically characters (ASCII 0 through 31) and (ASCII 127) will become ASCII 32 (Space).
+
+=item C<get_text>
+
+=over
+
+=item B<Description>
+
+This is a method of getting localized strings within Bugzilla code.
+Use this when you don't want to display a whole template, you just
+want a particular string.
+
+It uses the F<global/message.txt.tmpl> template to return a string.
+
+=item B<Params>
+
+=over
+
+=item C<$message> - The identifier for the message.
+
+=item C<$vars> - A hashref. Any variables you want to pass to the template.
+
+=back
+
+=item B<Returns>
+
+A string.
+
+=back
+
+
+=item C<template_var>
+
+This is a method of getting the value of a variable from a template in
+Perl code. The available variables are in the C<global/field-descs.none.tmpl>
+template. Just pass in the name of the variable that you want the value of.
+
+
+=back
+
+=head2 Formatting Time
+
+=over 4
+
+=item C<format_time($time)>
+
+Takes a time and converts it to the desired format and timezone.
+If no format is given, the routine guesses the correct one and returns
+an empty array if it cannot. If no timezone is given, the user's timezone
+is used, as defined in his preferences.
+
+This routine is mainly called from templates to filter dates, see
+"FILTER time" in L<Bugzilla::Template>.
+
+=item C<format_time_decimal($time)>
+
+Returns a number with 2 digit precision, unless the last digit is a 0. Then it
+returns only 1 digit precision.
+
+=item C<datetime_from($time, $timezone)>
+
+Returns a DateTime object given a date string. If the string is not in some
+valid date format that C<strptime> understands, we return C<undef>.
+
+You can optionally specify a timezone for the returned date. If not
+specified, defaults to the currently-logged-in user's timezone, or
+the Bugzilla server's local timezone if there isn't a logged-in user.
+
+=back
+
+
+=head2 Files
+
+=over 4
+
+=item C<file_mod_time($filename)>
+
+Takes a filename and returns the modification time. It returns it in the format
+of the "mtime" parameter of the perl "stat" function.
+
+=back
+
+=head2 Cryptography
+
+=over 4
+
+=item C<bz_crypt($password, $salt)>
+
+Takes a string and returns a hashed (encrypted) value for it, using a
+random salt. An optional salt string may also be passed in.
+
+Please always use this function instead of the built-in perl C<crypt>
+function, when checking or setting a password. Bugzilla does not use
+C<crypt>.
+
+=begin undocumented
+
+Random salts are generated because the alternative is usually
+to use the first two characters of the password itself, and since
+the salt appears in plaintext at the beginning of the encrypted
+password string this has the effect of revealing the first two
+characters of the password to anyone who views the encrypted version.
+
+=end undocumented
+
+=item C<generate_random_password($password_length)>
+
+Returns an alphanumeric string with the specified length
+(10 characters by default). Use this function to generate passwords
+and tokens.
+
+=back
+
+=head2 Validation
+
+=over 4
+
+=item C<validate_email_syntax($email)>
+
+Do a syntax checking for a legal email address and returns 1 if
+the check is successful, else returns 0.
+Untaints C<$email> if successful.
+
+=item C<validate_date($date)>
+
+Make sure the date has the correct format and returns 1 if
+the check is successful, else returns 0.
+
+=back
+
+=head2 Database
+
+=over
+
+=item C<on_main_db>
+
+Runs a block of code always on the main DB. Useful for when you're inside
+a subroutine and need to do some writes to the database, but don't know
+if Bugzilla is currently using the shadowdb or not. Used like:
+
+ on_main_db {
+ my $dbh = Bugzilla->dbh;
+ $dbh->do("INSERT ...");
+ }
+
+=back
diff --git a/Bugzilla/Version.pm b/Bugzilla/Version.pm
new file mode 100644
index 000000000..4270b1e5f
--- /dev/null
+++ b/Bugzilla/Version.pm
@@ -0,0 +1,244 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s): Tiago R. Mello <timello@async.com.br>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+# Frédéric Buclin <LpSolit@gmail.com>
+
+use strict;
+
+package Bugzilla::Version;
+
+use base qw(Bugzilla::Object);
+
+use Bugzilla::Install::Util qw(vers_cmp);
+use Bugzilla::Util;
+use Bugzilla::Error;
+
+use Scalar::Util qw(blessed);
+
+################################
+##### Initialization #####
+################################
+
+use constant DEFAULT_VERSION => 'unspecified';
+
+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
+);
+
+use constant REQUIRED_FIELD_MAP => {
+ product_id => 'product',
+};
+
+use constant UPDATE_COLUMNS => qw(
+ value
+);
+
+use constant VALIDATORS => {
+ product => \&_check_product,
+ value => \&_check_value,
+};
+
+use constant VALIDATOR_DEPENDENCIES => {
+ value => ['product'],
+};
+
+################################
+# Methods
+################################
+
+sub new {
+ my $class = shift;
+ my $param = shift;
+ my $dbh = Bugzilla->dbh;
+
+ my $product;
+ if (ref $param) {
+ $product = $param->{product};
+ my $name = $param->{name};
+ if (!defined $product) {
+ ThrowCodeError('bad_arg',
+ {argument => 'product',
+ function => "${class}::new"});
+ }
+ if (!defined $name) {
+ ThrowCodeError('bad_arg',
+ {argument => 'name',
+ function => "${class}::new"});
+ }
+
+ my $condition = 'product_id = ? AND value = ?';
+ my @values = ($product->id, $name);
+ $param = { condition => $condition, values => \@values };
+ }
+
+ unshift @_, $param;
+ return $class->SUPER::new(@_);
+}
+
+sub new_from_list {
+ my $self = shift;
+ my $list = $self->SUPER::new_from_list(@_);
+ return [sort { vers_cmp(lc($a->name), lc($b->name)) } @$list];
+}
+
+sub 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;
+}
+
+sub bug_count {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ if (!defined $self->{'bug_count'}) {
+ $self->{'bug_count'} = $dbh->selectrow_array(qq{
+ SELECT COUNT(*) FROM bugs
+ WHERE product_id = ? AND version = ?}, undef,
+ ($self->product_id, $self->name)) || 0;
+ }
+ return $self->{'bug_count'};
+}
+
+sub update {
+ my $self = shift;
+ my ($changes, $old_self) = $self->SUPER::update(@_);
+
+ if (exists $changes->{value}) {
+ my $dbh = Bugzilla->dbh;
+ $dbh->do('UPDATE bugs SET version = ?
+ WHERE version = ? AND product_id = ?',
+ undef, ($self->name, $old_self->name, $self->product_id));
+ }
+ return $changes;
+}
+
+sub remove_from_db {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ # The version cannot be removed if there are bugs
+ # associated with it.
+ if ($self->bug_count) {
+ ThrowUserError("version_has_bugs", { nb => $self->bug_count });
+ }
+ $self->SUPER::remove_from_db();
+}
+
+###############################
+##### Accessors ####
+###############################
+
+sub product_id { return $_[0]->{'product_id'}; }
+
+sub product {
+ my $self = shift;
+
+ require Bugzilla::Product;
+ $self->{'product'} ||= new Bugzilla::Product($self->product_id);
+ return $self->{'product'};
+}
+
+################################
+# Validators
+################################
+
+sub set_name { $_[0]->set('value', $_[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;
+}
+
+sub _check_product {
+ my ($invocant, $product) = @_;
+ $product || ThrowCodeError('param_required',
+ { function => "$invocant->create", param => 'product' });
+ return Bugzilla->user->check_can_admin_product($product->name);
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Version - Bugzilla product version class.
+
+=head1 SYNOPSIS
+
+ use Bugzilla::Version;
+
+ my $version = new Bugzilla::Version({ name => $name, product => $product });
+
+ my $value = $version->name;
+ my $product_id = $version->product_id;
+ my $product = $version->product;
+
+ my $version = Bugzilla::Version->create(
+ { value => $name, product => $product });
+
+ $version->set_name($new_name);
+ $version->update();
+
+ $version->remove_from_db;
+
+=head1 DESCRIPTION
+
+Version.pm represents a Product Version object. It is an implementation
+of L<Bugzilla::Object>, and thus provides all methods that
+L<Bugzilla::Object> provides.
+
+The methods that are specific to C<Bugzilla::Version> are listed
+below.
+
+=head1 METHODS
+
+=over
+
+=item C<bug_count()>
+
+ Description: Returns the total of bugs that belong to the version.
+
+ Params: none.
+
+ Returns: Integer with the number of bugs.
+
+=back
+
+=cut
diff --git a/Bugzilla/WebService.pm b/Bugzilla/WebService.pm
new file mode 100644
index 000000000..0ca5da267
--- /dev/null
+++ b/Bugzilla/WebService.pm
@@ -0,0 +1,339 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s): Marc Schumann <wurblzap@gmail.com>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+# 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;
+use strict;
+use Bugzilla::WebService::Server;
+
+use XMLRPC::Lite;
+
+# 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 => { };
+
+# Used to allow methods to be called in the JSON-RPC WebService via GET.
+# Methods that can modify data MUST not be listed here.
+use constant READ_ONLY => ();
+
+sub login_exempt {
+ my ($class, $method) = @_;
+ return $class->LOGIN_EXEMPT->{$method};
+}
+
+sub type {
+ my ($self, $type, $value) = @_;
+ if ($type eq 'dateTime') {
+ $value = $self->datetime_format_outbound($value);
+ }
+ return XMLRPC::Data->type($type)->value($value);
+}
+
+# This is the XML-RPC implementation, see the README in Bugzilla/WebService/.
+# Our "base" implementation is in Bugzilla::WebService::Server.
+sub datetime_format_outbound {
+ my $self = shift;
+ my $value = Bugzilla::WebService::Server->datetime_format_outbound(@_);
+ # XML-RPC uses an ISO-8601 format that doesn't have any hyphens.
+ $value =~ s/-//g;
+ return $value;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::WebService - The Web Service interface to Bugzilla
+
+=head1 DESCRIPTION
+
+This is the standard API for external programs that want to interact
+with Bugzilla. It provides various methods in various modules.
+
+You can interact with this API via
+L<XML-RPC|Bugzilla::WebService::Server::XMLRPC> or
+L<JSON-RPC|Bugzilla::WebService::Server::JSONRPC>.
+
+=head1 CALLING METHODS
+
+Methods are grouped into "packages", like C<Bug> for
+L<Bugzilla::WebService::Bug>. So, for example,
+L<Bugzilla::WebService::Bug/get>, is called as C<Bug.get>.
+
+=head1 PARAMETERS
+
+The Bugzilla API takes the following various types of parameters:
+
+=over
+
+=item C<int>
+
+Integer. May be null.
+
+=item C<double>
+
+A floating-point number. May be null.
+
+=item C<string>
+
+A string. May be null.
+
+=item C<dateTime>
+
+A date/time. Represented differently in different interfaces to this API.
+May be null.
+
+=item C<boolean>
+
+True or false.
+
+=item C<base64>
+
+A base64-encoded string. This is the only way to transfer
+binary data via the WebService.
+
+=item C<array>
+
+An array. There may be mixed types in an array.
+
+In example code, you will see the characters C<[> and C<]> used to
+represent the beginning and end of arrays.
+
+In our example code in these API docs, an array that contains the numbers
+1, 2, and 3 would look like:
+
+ [1, 2, 3]
+
+=item C<struct>
+
+A mapping of keys to values. Called a "hash", "dict", or "map" in some
+other programming languages. We sometimes call this a "hash" in the API
+documentation.
+
+The keys are strings, and the values can be any type.
+
+In example code, you will see the characters C<{> and C<}> used to represent
+the beginning and end of structs.
+
+For example, a struct with an "fruit" key whose value is "oranges",
+and a "vegetable" key whose value is "lettuce" would look like:
+
+ { fruit => 'oranges', vegetable => 'lettuce' }
+
+=back
+
+=head2 How Bugzilla WebService Methods Take Parameters
+
+B<All> Bugzilla WebService functions use I<named> parameters.
+The individual C<Bugzilla::WebService::Server> modules explain
+how this is implemented for those frontends.
+
+=head1 LOGGING IN
+
+There are various ways to log in:
+
+=over
+
+=item C<User.login>
+
+You can use L<Bugzilla::WebService::User/login> to log in as a Bugzilla
+user. This issues standard HTTP cookies that you must then use in future
+calls, so your client must be capable of receiving and transmitting
+cookies.
+
+=item C<Bugzilla_login> and C<Bugzilla_password>
+
+B<Added in Bugzilla 3.6>
+
+You can specify C<Bugzilla_login> and C<Bugzilla_password> as arguments
+to any WebService method, and you will be logged in as that user if your
+credentials are correct. Here are the arguments you can specify to any
+WebService method to perform a login:
+
+=over
+
+=item C<Bugzilla_login> (string) - A user's login name.
+
+=item C<Bugzilla_password> (string) - That user's password.
+
+=item C<Bugzilla_restrictlogin> (boolean) - Optional. If true,
+then your login will only be valid for your IP address.
+
+=item C<Bugzilla_rememberlogin> (boolean) - Optional. If true,
+then the cookie sent back to you with the method response will
+not expire.
+
+=back
+
+The C<Bugzilla_restrictlogin> and C<Bugzilla_rememberlogin> options
+are only used when you have also specified C<Bugzilla_login> and
+C<Bugzilla_password>.
+
+Note that Bugzilla will return HTTP cookies along with the method
+response when you use these arguments (just like the C<User.login> method
+above).
+
+=back
+
+=head1 STABLE, EXPERIMENTAL, and UNSTABLE
+
+Methods are marked B<STABLE> if you can expect their parameters and
+return values not to change between versions of Bugzilla. You are
+best off always using methods marked B<STABLE>. We may add parameters
+and additional items to the return values, but your old code will
+always continue to work with any new changes we make. If we ever break
+a B<STABLE> interface, we'll post a big notice in the Release Notes,
+and it will only happen during a major new release.
+
+Methods (or parts of methods) are marked B<EXPERIMENTAL> if
+we I<believe> they will be stable, but there's a slight chance that
+small parts will change in the future.
+
+Certain parts of a method's description may be marked as B<UNSTABLE>,
+in which case those parts are not guaranteed to stay the same between
+Bugzilla versions.
+
+=head1 ERRORS
+
+If a particular webservice call fails, it will throw an error in the
+appropriate format for the frontend that you are using. For all frontends,
+there is at least a numeric error code and descriptive text for the error.
+
+The various errors that functions can throw are specified by the
+documentation of those functions.
+
+Each error that Bugzilla can throw has a specific numeric code that will
+not change between versions of Bugzilla. If your code needs to know what
+error Bugzilla threw, use the numeric code. Don't try to parse the
+description, because that may change from version to version of Bugzilla.
+
+Note that if you display the error to the user in an HTML program, make
+sure that you properly escape the error, as it will not be HTML-escaped.
+
+=head2 Transient vs. Fatal Errors
+
+If the error code is a number greater than 0, the error is considered
+"transient," which means that it was an error made by the user, not
+some problem with Bugzilla itself.
+
+If the error code is a number less than 0, the error is "fatal," which
+means that it's some error in Bugzilla itself that probably requires
+administrative attention.
+
+Negative numbers and positive numbers don't overlap. That is, if there's
+an error 302, there won't be an error -302.
+
+=head2 Unknown Errors
+
+Sometimes a function will throw an error that doesn't have a specific
+error code. In this case, the code will be C<-32000> if it's a "fatal"
+error, and C<32000> if it's a "transient" error.
+
+=head1 COMMON PARAMETERS
+
+Many Webservice methods take similar arguments. Instead of re-writing
+the documentation for each method, we document the parameters here, once,
+and then refer back to this documentation from the individual methods
+where these parameters are used.
+
+=head2 Limiting What Fields Are Returned
+
+Many WebService methods return an array of structs with various
+fields in the structs. (For example, L<Bugzilla::WebService::Bug/get>
+returns a list of C<bugs> that have fields like C<id>, C<summary>,
+C<creation_time>, etc.)
+
+These parameters allow you to limit what fields are present in
+the structs, to possibly improve performance or save some bandwidth.
+
+=over
+
+=item C<include_fields>
+
+C<array> An array of strings, representing the (case-sensitive) names of
+fields in the return value. Only the fields specified in this hash will
+be returned, the rest will not be included.
+
+If you specify an empty array, then this function will return empty
+hashes.
+
+Invalid field names are ignored.
+
+Example:
+
+ User.get( ids => [1], include_fields => ['id', 'name'] )
+
+would return something like:
+
+ { users => [{ id => 1, name => 'user@domain.com' }] }
+
+=item C<exclude_fields>
+
+C<array> An array of strings, representing the (case-sensitive) names of
+fields in the return value. The fields specified will not be included in
+the returned hashes.
+
+If you specify all the fields, then this function will return empty
+hashes.
+
+Invalid field names are ignored.
+
+Specifying fields here overrides C<include_fields>, so if you specify a
+field in both, it will be excluded, not included.
+
+Example:
+
+ User.get( ids => [1], exclude_fields => ['name'] )
+
+would return something like:
+
+ { users => [{ id => 1, real_name => 'John Smith' }] }
+
+=back
+
+=head1 SEE ALSO
+
+=head2 Server Types
+
+=over
+
+=item L<Bugzilla::WebService::Server::XMLRPC>
+
+=item L<Bugzilla::WebService::Server::JSONRPC>
+
+=back
+
+=head2 WebService Methods
+
+=over
+
+=item L<Bugzilla::WebService::Bug>
+
+=item L<Bugzilla::WebService::Bugzilla>
+
+=item L<Bugzilla::WebService::Product>
+
+=item L<Bugzilla::WebService::User>
+
+=back
diff --git a/Bugzilla/WebService/Bug.pm b/Bugzilla/WebService/Bug.pm
new file mode 100644
index 000000000..0c88d217f
--- /dev/null
+++ b/Bugzilla/WebService/Bug.pm
@@ -0,0 +1,3149 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s): Marc Schumann <wurblzap@gmail.com>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+# Mads Bondo Dydensborg <mbd@dbc.dk>
+# Tsahi Asher <tsahi_75@yahoo.com>
+# Noura Elhawary <nelhawar@redhat.com>
+# Frank Becker <Frank@Frank-Becker.de>
+# Dave Lawrence <dkl@redhat.com>
+
+package Bugzilla::WebService::Bug;
+
+use strict;
+use base qw(Bugzilla::WebService);
+
+use Bugzilla::Comment;
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Field;
+use Bugzilla::WebService::Constants;
+use Bugzilla::WebService::Util qw(filter filter_wants validate);
+use Bugzilla::Bug;
+use Bugzilla::BugMail;
+use Bugzilla::Util qw(trick_taint trim);
+use Bugzilla::Version;
+use Bugzilla::Milestone;
+use Bugzilla::Status;
+use Bugzilla::Token qw(issue_hash_token);
+
+#############
+# Constants #
+#############
+
+use constant PRODUCT_SPECIFIC_FIELDS => qw(version target_milestone component);
+
+use constant DATE_FIELDS => {
+ comments => ['new_since'],
+ search => ['last_change_time', 'creation_time'],
+};
+
+use constant BASE64_FIELDS => {
+ add_attachment => ['data'],
+};
+
+use constant READ_ONLY => qw(
+ attachments
+ comments
+ fields
+ get
+ history
+ legal_values
+ search
+);
+
+######################################################
+# Add aliases here for old method name compatibility #
+######################################################
+
+BEGIN {
+ # In 3.0, get was called get_bugs
+ *get_bugs = \&get;
+ # Before 3.4rc1, "history" was get_history.
+ *get_history = \&history;
+}
+
+###########
+# Methods #
+###########
+
+sub fields {
+ my ($self, $params) = validate(@_, 'ids', 'names');
+
+ 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->{ids} and !defined $params->{names}) {
+ @fields = Bugzilla->get_fields({ obsolete => 0 });
+ }
+
+ my @fields_out;
+ foreach my $field (@fields) {
+ my $visibility_field = $field->visibility_field
+ ? $field->visibility_field->name : undef;
+ my $vis_value = $field->visibility_value;
+ 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))
+ {
+ $has_values = 1;
+ @values = @{ $self->_legal_field_values({ field => $field }) };
+ }
+
+ 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 => [
+ defined $vis_value ? $self->type('string', $vis_value->name)
+ : ()
+ ],
+ );
+ 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 };
+}
+
+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;
+ }
+
+ 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),
+ sortkey => $self->type('int', $sortkey),
+ visibility_values => [$self->type('string', $product_name)],
+ });
+ }
+ }
+ }
+
+ elsif ($field_name eq 'bug_status') {
+ my @status_all = Bugzilla::Status->get_all;
+ 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),
+ sortkey => $self->type('int', $status->sortkey),
+ can_change_to => \@can_change_to,
+ visibility_values => [],
+ });
+ }
+ }
+
+ 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),
+ sortkey => $self->type('int' , $value->sortkey),
+ visibility_values => [
+ defined $vis_val ? $self->type('string', $vis_val->name)
+ : ()
+ ],
+ });
+ }
+ }
+
+ return \@result;
+}
+
+sub comments {
+ 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'] });
+ }
+
+ my $bug_ids = $params->{ids} || [];
+ my $comment_ids = $params->{comment_ids} || [];
+
+ my $dbh = Bugzilla->dbh;
+ 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 });
+ }
+ }
+
+ # 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 };
+}
+
+# Helper for Bug.comments
+sub _translate_comment {
+ my ($self, $comment, $filters) = @_;
+ my $attach_id = $comment->is_about_attachment ? $comment->extra_data
+ : undef;
+ return filter $filters, {
+ id => $self->type('int', $comment->id),
+ bug_id => $self->type('int', $comment->bug_id),
+ creator => $self->type('string', $comment->author->login),
+ author => $self->type('string', $comment->author->login),
+ 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),
+ };
+}
+
+sub get {
+ my ($self, $params) = validate(@_, 'ids');
+
+ my $ids = $params->{ids};
+ defined $ids || ThrowCodeError('param_required', { param => 'ids' });
+
+ my @bugs;
+ my @faults;
+ 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, $self->_bug_to_hash($bug, $params));
+ }
+
+ return { bugs => \@bugs, faults => \@faults };
+}
+
+# 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');
+
+ my $ids = $params->{ids};
+ defined $ids || ThrowCodeError('param_required', { param => 'ids' });
+
+ 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) = Bugzilla::Bug::GetBugActivity($bug_id);
+
+ 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 $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',
+ delete $change->{fieldname});
+ push (@{$bug_history{changes}}, $change);
+ }
+
+ push (@history, \%bug_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
+ if (Bugzilla->params->{'usebugaliases'}) {
+ $item{alias} = $self->type('string', $bug->alias);
+ }
+ else {
+ # For API reasons, we always want the value to appear, we just
+ # don't want it to have a value if aliases are turned off.
+ $item{alias} = undef;
+ }
+
+ push(@return, \%item);
+ }
+
+ return { bugs => \@return };
+}
+
+sub search {
+ my ($self, $params) = @_;
+
+ if ( defined($params->{offset}) and !defined($params->{limit}) ) {
+ ThrowCodeError('param_required',
+ { param => 'limit', function => 'Bug.search()' });
+ }
+
+ $params = Bugzilla::Bug::map_fields($params);
+ delete $params->{WHERE};
+
+ unless (Bugzilla->user->is_timetracker) {
+ delete $params->{$_} foreach qw(estimated_time remaining_time deadline);
+ }
+
+ # Do special search types for certain fields.
+ if ( my $bug_when = delete $params->{delta_ts} ) {
+ $params->{WHERE}->{'delta_ts >= ?'} = $bug_when;
+ }
+ if (my $when = delete $params->{creation_ts}) {
+ $params->{WHERE}->{'creation_ts >= ?'} = $when;
+ }
+ if (my $summary = delete $params->{short_desc}) {
+ my @strings = ref $summary ? @$summary : ($summary);
+ my @likes = ("short_desc LIKE ?") x @strings;
+ my $clause = join(' OR ', @likes);
+ $params->{WHERE}->{"($clause)"} = [map { "\%$_\%" } @strings];
+ }
+ if (my $whiteboard = delete $params->{status_whiteboard}) {
+ my @strings = ref $whiteboard ? @$whiteboard : ($whiteboard);
+ my @likes = ("status_whiteboard LIKE ?") x @strings;
+ my $clause = join(' OR ', @likes);
+ $params->{WHERE}->{"($clause)"} = [map { "\%$_\%" } @strings];
+ }
+
+ # We want include_fields and exclude_fields to be passed to
+ # _bug_to_hash but not to Bugzilla::Bug->match so we copy the
+ # params and delete those before passing to Bugzilla::Bug->match.
+ my %match_params = %{ $params };
+ delete $match_params{'include_fields'};
+ delete $match_params{'exclude_fields'};
+
+ my $bugs = Bugzilla::Bug->match(\%match_params);
+ my $visible = Bugzilla->user->visible_bugs($bugs);
+
+ my @hashes = map { $self->_bug_to_hash($_, $params) } @$visible;
+ return { bugs => \@hashes };
+}
+
+sub possible_duplicates {
+ my ($self, $params) = validate(@_, 'product');
+ my $user = Bugzilla->user;
+
+ # 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->{'product'} || [] }) {
+ 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;
+ return { bugs => \@hashes };
+}
+
+sub update {
+ my ($self, $params) = validate(@_, 'ids');
+
+ 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 });
+
+ my $ids = delete $params->{ids};
+ defined $ids || ThrowCodeError('param_required', { param => 'ids' });
+
+ my @bugs = map { Bugzilla::Bug->check($_) } @$ids;
+
+ my %values = %$params;
+ $values{other_bugs} = \@bugs;
+
+ 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};
+ delete $values{flags};
+
+ foreach my $bug (@bugs) {
+ if (!$user->can_edit_product($bug->product_obj->id) ) {
+ ThrowUserError("product_edit_denied",
+ { product => $bug->product });
+ }
+
+ $bug->set_all(\%values);
+ }
+
+ 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 => {},
+ );
+
+ # 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.
+ if (Bugzilla->params->{'usebugaliases'}) {
+ $hash{alias} = $self->type('string', $bug->alias);
+ }
+ else {
+ # For API reasons, we always want the alias field to appear, we
+ # just don't want it to have a value if aliases are turned off.
+ $hash{alias} = $self->type('string', '');
+ }
+
+ 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);
+ }
+
+ return { bugs => \@result };
+}
+
+sub create {
+ my ($self, $params) = @_;
+ Bugzilla->login(LOGIN_REQUIRED);
+ $params = Bugzilla::Bug::map_fields($params);
+ my $bug = Bugzilla::Bug->create($params);
+ Bugzilla::BugMail::Send($bug->bug_id, { changer => $bug->reporter });
+ return { id => $self->type('int', $bug->bug_id) };
+}
+
+sub legal_values {
+ my ($self, $params) = @_;
+
+ defined $params->{field}
+ or ThrowCodeError('param_required', { param => 'field' });
+
+ my $field = Bugzilla::Bug::FIELD_MAP->{$params->{field}}
+ || $params->{field};
+
+ my @global_selects = grep { !$_->is_abnormal }
+ Bugzilla->get_fields({ is_select => 1 });
+
+ 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};
+ }
+
+ $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($_) } @{ $params->{ids} };
+ foreach my $bug (@bugs) {
+ Bugzilla->user->can_edit_product($bug->product_id)
+ || ThrowUserError("product_edit_denied", {product => $bug->product});
+ }
+
+ my @created;
+ $dbh->bz_start_transaction();
+ foreach my $bug (@bugs) {
+ my $attachment = Bugzilla::Attachment->create({
+ bug => $bug,
+ data => $params->{data},
+ description => $params->{summary},
+ filename => $params->{file_name},
+ mimetype => $params->{content_type},
+ ispatch => $params->{is_patch},
+ isprivate => $params->{is_private},
+ isurl => $params->{is_url},
+ });
+ 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($_->attached) foreach @created;
+ $dbh->bz_commit_transaction();
+
+ $_->send_changes() foreach @bugs;
+
+ my %attachments = map { $_->id => $self->_attachment_to_hash($_, $params) }
+ @created;
+
+ return { attachments => \%attachments };
+}
+
+sub add_comment {
+ my ($self, $params) = @_;
+
+ #The user must login in order add a comment
+ Bugzilla->login(LOGIN_REQUIRED);
+
+ # Check parameters
+ defined $params->{id}
+ || ThrowCodeError('param_required', { param => 'id' });
+ my $comment = $params->{comment};
+ (defined $comment && trim($comment) ne '')
+ || ThrowCodeError('param_required', { param => 'comment' });
+
+ my $bug = Bugzilla::Bug->check($params->{id});
+
+ Bugzilla->user->can_edit_product($bug->product_id)
+ || ThrowUserError("product_edit_denied", {product => $bug->product});
+
+ # 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} });
+
+ # Capture the call to bug->update (which creates the new comment) in
+ # a transaction so we're sure to get the correct comment_id.
+
+ my $dbh = Bugzilla->dbh;
+ $dbh->bz_start_transaction();
+
+ $bug->update();
+
+ my $new_comment_id = $dbh->bz_last_key('longdescs', 'comment_id');
+
+ $dbh->bz_commit_transaction();
+
+ # Send mail.
+ Bugzilla::BugMail::Send($bug->bug_id, { changer => Bugzilla->user });
+
+ 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($id);
+ $user->can_edit_product($bug->product_id)
+ || ThrowUserError("product_edit_denied",
+ { product => $bug->product });
+ push(@bugs, $bug);
+ if ($remove) {
+ $bug->remove_see_also($_) foreach @$remove;
+ }
+ 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 };
+}
+
+sub attachments {
+ my ($self, $params) = validate(@_, 'ids', 'attachment_ids');
+
+ 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);
+ }
+
+ return { bugs => \%bugs, attachments => \%attachments };
+}
+
+##############################
+# Private Helper Subroutines #
+##############################
+
+# A helper for get() and search(). This is done in this fashion in order
+# to produce a stable API and to explicitly type return values.
+# The internals of Bugzilla::Bug are not stable enough to just
+# 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 = (
+ alias => $self->type('string', $bug->alias),
+ classification => $self->type('string', $bug->classification),
+ component => $self->type('string', $bug->component),
+ creation_time => $self->type('dateTime', $bug->creation_ts),
+ id => $self->type('int', $bug->bug_id),
+ is_confirmed => $self->type('boolean', $bug->everconfirmed),
+ last_change_time => $self->type('dateTime', $bug->delta_ts),
+ op_sys => $self->type('string', $bug->op_sys),
+ platform => $self->type('string', $bug->rep_platform),
+ priority => $self->type('string', $bug->priority),
+ product => $self->type('string', $bug->product),
+ 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 SQL calls.
+ # We don't do the SQL calls at all if the filter would just
+ # eliminate them anyway.
+ if (filter_wants $params, 'assigned_to') {
+ $item{'assigned_to'} = $self->type('string', $bug->assigned_to->login);
+ }
+ if (filter_wants $params, 'blocks') {
+ my @blocks = map { $self->type('int', $_) } @{ $bug->blocked };
+ $item{'blocks'} = \@blocks;
+ }
+ if (filter_wants $params, 'cc') {
+ my @cc = map { $self->type('string', $_) } @{ $bug->cc || [] };
+ $item{'cc'} = \@cc;
+ }
+ if (filter_wants $params, 'creator') {
+ $item{'creator'} = $self->type('string', $bug->reporter->login);
+ }
+ 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, 'qa_contact') {
+ my $qa_login = $bug->qa_contact ? $bug->qa_contact->login : '';
+ $item{'qa_contact'} = $self->type('string', $qa_login);
+ }
+ if (filter_wants $params, 'see_also') {
+ my @see_also = map { $self->type('string', $_) } @{ $bug->see_also };
+ $item{'see_also'} = \@see_also;
+ }
+
+ # 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;
+ if ($field->type == FIELD_TYPE_BUG_ID) {
+ $item{$name} = $self->type('int', $bug->$name);
+ }
+ elsif ($field->type == FIELD_TYPE_DATETIME) {
+ $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);
+ }
+ }
+
+ # Timetracking fields are only sent if the user can see them.
+ if (Bugzilla->user->is_timetracker) {
+ $item{'estimated_time'} = $self->type('double', $bug->estimated_time);
+ $item{'remaining_time'} = $self->type('double', $bug->remaining_time);
+ # No need to format $bug->deadline specially, because Bugzilla::Bug
+ # already does it for us.
+ $item{'deadline'} = $self->type('string', $bug->deadline);
+ }
+
+ if (Bugzilla->user->id) {
+ my $token = issue_hash_token([$bug->id, $bug->delta_ts]);
+ $item{'update_token'} = $self->type('string', $token);
+ }
+
+ # The "accessible" bits go here because they have long names and it
+ # makes the code look nicer to separate them out.
+ $item{'is_cc_accessible'} = $self->type('boolean',
+ $bug->cclist_accessible);
+ $item{'is_creator_accessible'} = $self->type('boolean',
+ $bug->reporter_accessible);
+
+ return filter $params, \%item;
+}
+
+sub _attachment_to_hash {
+ my ($self, $attach, $filters) = @_;
+
+ # Skipping attachment flags for now.
+ delete $attach->{flags};
+
+ 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),
+ description => $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_url => $self->type('int', $attach->isurl),
+ is_patch => $self->type('int', $attach->ispatch),
+ };
+
+ # creator/attacher require an extra lookup, so we only send them if
+ # the filter wants them.
+ foreach my $field (qw(creator attacher)) {
+ if (filter_wants $filters, $field) {
+ $item->{$field} = $self->type('string', $attach->attacher->login);
+ }
+ }
+
+ if (filter_wants $filters, 'data') {
+ $item->{'data'} = $self->type('base64', $attach->data);
+ }
+
+ return $item;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Webservice::Bug - The API for creating, changing, and getting the
+details of bugs.
+
+=head1 DESCRIPTION
+
+This part of the Bugzilla API allows you to file a new bug in Bugzilla,
+or get information about bugs that have already been filed.
+
+=head1 METHODS
+
+See L<Bugzilla::WebService> for a description of how parameters are passed,
+and what B<STABLE>, B<UNSTABLE>, and B<EXPERIMENTAL> mean.
+
+=head1 Utility Functions
+
+=head2 fields
+
+B<UNSTABLE>
+
+=over
+
+=item B<Description>
+
+Get information about valid bug fields, including the lists of legal values
+for each field.
+
+=item B<Params>
+
+You can pass either field ids or field names.
+
+B<Note>: If neither C<ids> nor C<names> is specified, then all
+non-obsolete fields will be returned.
+
+In addition to the parameters below, this method also accepts the
+standard L<include_fields|Bugzilla::WebService/include_fields> and
+L<exclude_fields|Bugzilla::WebService/exclude_fields> arguments.
+
+=over
+
+=item C<ids> (array) - An array of integer field ids.
+
+=item C<names> (array) - An array of strings representing field names.
+
+=back
+
+=item B<Returns>
+
+A hash containing a single element, C<fields>. This is an array of hashes,
+containing the following keys:
+
+=over
+
+=item C<id>
+
+C<int> An integer id uniquely idenfifying this field in this installation only.
+
+=item C<type>
+
+C<int> The number of the fieldtype. The following values are defined:
+
+=over
+
+=item C<0> Unknown
+
+=item C<1> Free Text
+
+=item C<2> Drop Down
+
+=item C<3> Multiple-Selection Box
+
+=item C<4> Large Text Box
+
+=item C<5> Date/Time
+
+=item C<6> Bug Id
+
+=item C<7> Bug URLs ("See Also")
+
+=back
+
+=item C<is_custom>
+
+C<boolean> True when this is a custom field, false otherwise.
+
+=item C<name>
+
+C<string> The internal name of this field. This is a unique identifier for
+this field. If this is not a custom field, then this name will be the same
+across all Bugzilla installations.
+
+=item C<display_name>
+
+C<string> The name of the field, as it is shown in the user interface.
+
+=item C<is_mandatory>
+
+C<boolean> True if the field must have a value when filing new bugs.
+Also, mandatory fields cannot have their value cleared when updating
+bugs.
+
+=item C<is_on_bug_entry>
+
+C<boolean> For custom fields, this is true if the field is shown when you
+enter a new bug. For standard fields, this is currently always false,
+even if the field shows up when entering a bug. (To know whether or not
+a standard field is valid on bug entry, see L</create>.)
+
+=item C<visibility_field>
+
+C<string> The name of a field that controls the visibility of this field
+in the user interface. This field only appears in the user interface when
+the named field is equal to one of the values in C<visibility_values>.
+Can be null.
+
+=item C<visibility_values>
+
+C<array> of C<string>s This field is only shown when C<visibility_field>
+matches one of these values. When C<visibility_field> is null,
+then this is an empty array.
+
+=item C<value_field>
+
+C<string> The name of the field that controls whether or not particular
+values of the field are shown in the user interface. Can be null.
+
+=item C<values>
+
+This is an array of hashes, representing the legal values for
+select-type (drop-down and multiple-selection) fields. This is also
+populated for the C<component>, C<version>, and C<target_milestone>
+fields, but not for the C<product> field (you must use
+L<Product.get_accessible_products|Bugzilla::WebService::Product/get_accessible_products>
+for that.
+
+For fields that aren't select-type fields, this will simply be an empty
+array.
+
+Each hash has the following keys:
+
+=over
+
+=item C<name>
+
+C<string> The actual value--this is what you would specify for this
+field in L</create>, etc.
+
+=item C<sortkey>
+
+C<int> Values, when displayed in a list, are sorted first by this integer
+and then secondly by their name.
+
+=item C<visibility_values>
+
+If C<value_field> is defined for this field, then this value is only shown
+if the C<value_field> is set to one of the values listed in this array.
+Note that for per-product fields, C<value_field> is set to C<'product'>
+and C<visibility_values> will reflect which product(s) this value appears in.
+
+=item C<is_open>
+
+C<boolean> For C<bug_status> values, determines whether this status
+specifies that the bug is "open" (true) or "closed" (false). This item
+is only included for the C<bug_status> field.
+
+=item C<can_change_to>
+
+For C<bug_status> values, this is an array of hashes that determines which
+statuses you can transition to from this status. (This item is only included
+for the C<bug_status> field.)
+
+Each hash contains the following items:
+
+=over
+
+=item C<name>
+
+the name of the new status
+
+=item C<comment_required>
+
+this C<boolean> True if a comment is required when you change a bug into
+this status using this transition.
+
+=back
+
+=back
+
+=back
+
+=item B<Errors>
+
+=over
+
+=item 51 (Invalid Field Name or Id)
+
+You specified an invalid field name or id.
+
+=back
+
+=item B<History>
+
+=over
+
+=item Added in Bugzilla B<3.6>.
+
+=item The C<is_mandatory> return value was added in Bugzilla B<4.0>.
+
+=back
+
+=back
+
+
+=head2 legal_values
+
+B<DEPRECATED> - Use L</fields> instead.
+
+=over
+
+=item B<Description>
+
+Tells you what values are allowed for a particular field.
+
+=item B<Params>
+
+=over
+
+=item C<field> - The name of the field you want information about.
+This should be the same as the name you would use in L</create>, below.
+
+=item C<product_id> - If you're picking a product-specific field, you have
+to specify the id of the product you want the values for.
+
+=back
+
+=item B<Returns>
+
+C<values> - An array of strings: the legal values for this field.
+The values will be sorted as they normally would be in Bugzilla.
+
+=item B<Errors>
+
+=over
+
+=item 106 (Invalid Product)
+
+You were required to specify a product, and either you didn't, or you
+specified an invalid product (or a product that you can't access).
+
+=item 108 (Invalid Field Name)
+
+You specified a field that doesn't exist or isn't a drop-down field.
+
+=back
+
+=back
+
+=head1 Bug Information
+
+=head2 attachments
+
+B<EXPERIMENTAL>
+
+=over
+
+=item B<Description>
+
+It allows you to get data about attachments, given a list of bugs
+and/or attachment ids.
+
+B<Note>: Private attachments will only be returned if you are in the
+insidergroup or if you are the submitter of the attachment.
+
+=item B<Params>
+
+B<Note>: At least one of C<ids> or C<attachment_ids> is required.
+
+=over
+
+=item C<ids>
+
+See the description of the C<ids> parameter in the L</get> method.
+
+=item C<attachment_ids>
+
+C<array> An array of integer attachment ids.
+
+=back
+
+Also accepts the L<include_fields|Bugzilla::WebService/include_fields>,
+and L<exclude_fields|Bugzilla::WebService/exclude_fields> arguments.
+
+=item B<Returns>
+
+A hash containing two elements: C<bugs> and C<attachments>. The return
+value looks like this:
+
+ {
+ bugs => {
+ 1345 => {
+ attachments => [
+ { (attachment) },
+ { (attachment) }
+ ]
+ },
+ 9874 => {
+ attachments => [
+ { (attachment) },
+ { (attachment) }
+ ]
+
+ },
+ },
+
+ attachments => {
+ 234 => { (attachment) },
+ 123 => { (attachment) },
+ }
+ }
+
+The attachments of any bugs that you specified in the C<ids> argument in
+input are returned in C<bugs> on output. C<bugs> is a hash that has integer
+bug IDs for keys and contains a single key, C<attachments>. That key points
+to an arrayref that contains attachments as a hash. (Fields for attachments
+are described below.)
+
+For any attachments that you specified directly in C<attachment_ids>, they
+are returned in C<attachments> on output. This is a hash where the attachment
+ids point directly to hashes describing the individual attachment.
+
+The fields for each attachment (where it says C<(attachment)> in the
+diagram above) are:
+
+=over
+
+=item C<data>
+
+C<base64> The raw data of the attachment, encoded as Base64.
+
+=item C<creation_time>
+
+C<dateTime> The time the attachment was created.
+
+=item C<last_change_time>
+
+C<dateTime> The last time the attachment was modified.
+
+=item C<id>
+
+C<int> The numeric id of the attachment.
+
+=item C<bug_id>
+
+C<int> The numeric id of the bug that the attachment is attached to.
+
+=item C<file_name>
+
+C<string> The file name of the attachment.
+
+=item C<summary>
+
+C<string> A short string describing the attachment.
+
+Also returned as C<description>, for backwards-compatibility with older
+Bugzillas. (However, this backwards-compatibility will go away in Bugzilla
+5.0.)
+
+=item C<content_type>
+
+C<string> The MIME type of the attachment.
+
+=item C<is_private>
+
+C<boolean> True if the attachment is private (only visible to a certain
+group called the "insidergroup"), False otherwise.
+
+=item C<is_obsolete>
+
+C<boolean> True if the attachment is obsolete, False otherwise.
+
+=item C<is_url>
+
+C<boolean> True if the attachment is a URL instead of actual data,
+False otherwise. Note that such attachments only happen when the
+Bugzilla installation has at some point had the C<allow_attach_url>
+parameter enabled.
+
+=item C<is_patch>
+
+C<boolean> True if the attachment is a patch, False otherwise.
+
+=item C<creator>
+
+C<string> The login name of the user that created the attachment.
+
+Also returned as C<attacher>, for backwards-compatibility with older
+Bugzillas. (However, this backwards-compatibility will go away in Bugzilla
+5.0.)
+
+=back
+
+=item B<Errors>
+
+This method can throw all the same errors as L</get>. In addition,
+it can also throw the following error:
+
+=over
+
+=item 304 (Auth Failure, Attachment is Private)
+
+You specified the id of a private attachment in the C<attachment_ids>
+argument, and you are not in the "insider group" that can see
+private attachments.
+
+=back
+
+=item B<History>
+
+=over
+
+=item Added in Bugzilla B<3.6>.
+
+=item In Bugzilla B<4.0>, the C<attacher> return value was renamed to
+C<creator>.
+
+=item In Bugzilla B<4.0>, the C<description> return value was renamed to
+C<summary>.
+
+=item The C<data> return value was added in Bugzilla B<4.0>.
+
+=back
+
+=back
+
+
+=head2 comments
+
+B<STABLE>
+
+=over
+
+=item B<Description>
+
+This allows you to get data about comments, given a list of bugs
+and/or comment ids.
+
+=item B<Params>
+
+B<Note>: At least one of C<ids> or C<comment_ids> is required.
+
+In addition to the parameters below, this method also accepts the
+standard L<include_fields|Bugzilla::WebService/include_fields> and
+L<exclude_fields|Bugzilla::WebService/exclude_fields> arguments.
+
+=over
+
+=item C<ids>
+
+C<array> An array that can contain both bug IDs and bug aliases.
+All of the comments (that are visible to you) will be returned for the
+specified bugs.
+
+=item C<comment_ids>
+
+C<array> An array of integer comment_ids. These comments will be
+returned individually, separate from any other comments in their
+respective bugs.
+
+=item C<new_since>
+
+C<dateTime> If specified, the method will only return comments I<newer>
+than this time. This only affects comments returned from the C<ids>
+argument. You will always be returned all comments you request in the
+C<comment_ids> argument, even if they are older than this date.
+
+=back
+
+=item B<Returns>
+
+Two items are returned:
+
+=over
+
+=item C<bugs>
+
+This is used for bugs specified in C<ids>. This is a hash,
+where the keys are the numeric ids of the bugs, and the value is
+a hash with a single key, C<comments>, which is an array of comments.
+(The format of comments is described below.)
+
+Note that any individual bug will only be returned once, so if you
+specify an id multiple times in C<ids>, it will still only be
+returned once.
+
+=item C<comments>
+
+Each individual comment requested in C<comment_ids> is returned here,
+in a hash where the numeric comment id is the key, and the value
+is the comment. (The format of comments is described below.)
+
+=back
+
+A "comment" as described above is a hash that contains the following
+keys:
+
+=over
+
+=item id
+
+C<int> The globally unique ID for the comment.
+
+=item bug_id
+
+C<int> The ID of the bug that this comment is on.
+
+=item attachment_id
+
+C<int> If the comment was made on an attachment, this will be the
+ID of that attachment. Otherwise it will be null.
+
+=item text
+
+C<string> The actual text of the comment.
+
+=item creator
+
+C<string> The login name of the comment's author.
+
+Also returned as C<author>, for backwards-compatibility with older
+Bugzillas. (However, this backwards-compatibility will go away in Bugzilla
+5.0.)
+
+=item time
+
+C<dateTime> The time (in Bugzilla's timezone) that the comment was added.
+
+=item is_private
+
+C<boolean> True if this comment is private (only visible to a certain
+group called the "insidergroup"), False otherwise.
+
+=back
+
+=item B<Errors>
+
+This method can throw all the same errors as L</get>. In addition,
+it can also throw the following errors:
+
+=over
+
+=item 110 (Comment Is Private)
+
+You specified the id of a private comment in the C<comment_ids>
+argument, and you are not in the "insider group" that can see
+private comments.
+
+=item 111 (Invalid Comment ID)
+
+You specified an id in the C<comment_ids> argument that is invalid--either
+you specified something that wasn't a number, or there is no comment with
+that id.
+
+=back
+
+=item B<History>
+
+=over
+
+=item Added in Bugzilla B<3.4>.
+
+=item C<attachment_id> was added to the return value in Bugzilla B<3.6>.
+
+=item In Bugzilla B<4.0>, the C<author> return value was renamed to
+C<creator>.
+
+=back
+
+=back
+
+
+=head2 get
+
+B<STABLE>
+
+=over
+
+=item B<Description>
+
+Gets information about particular bugs in the database.
+
+Note: Can also be called as "get_bugs" for compatibilty with Bugzilla 3.0 API.
+
+=item B<Params>
+
+In addition to the parameters below, this method also accepts the
+standard L<include_fields|Bugzilla::WebService/include_fields> and
+L<exclude_fields|Bugzilla::WebService/exclude_fields> arguments.
+
+=over
+
+=item C<ids>
+
+An array of numbers and strings.
+
+If an element in the array is entirely numeric, it represents a bug_id
+from the Bugzilla database to fetch. If it contains any non-numeric
+characters, it is considered to be a bug alias instead, and the bug with
+that alias will be loaded.
+
+Note that it's possible for aliases to be disabled in Bugzilla, in which
+case you will be told that you have specified an invalid bug_id if you
+try to specify an alias. (It will be error 100.)
+
+=item C<permissive> B<EXPERIMENTAL>
+
+C<boolean> Normally, if you request any inaccessible or invalid bug ids,
+Bug.get will throw an error. If this parameter is True, instead of throwing an
+error we return an array of hashes with a C<id>, C<faultString> and C<faultCode>
+for each bug that fails, and return normal information for the other bugs that
+were accessible.
+
+=back
+
+=item B<Returns>
+
+Two items are returned:
+
+=over
+
+=item C<bugs>
+
+An array of hashes that contains information about the bugs with
+the valid ids. Each hash contains the following items:
+
+=over
+
+=item C<alias>
+
+C<string> The unique alias of this bug.
+
+=item C<assigned_to>
+
+C<string> The login name of the user to whom the bug is assigned.
+
+=item C<blocks>
+
+C<array> of C<int>s. The ids of bugs that are "blocked" by this bug.
+
+=item C<cc>
+
+C<array> of C<string>s. The login names of users on the CC list of this
+bug.
+
+=item C<classification>
+
+C<string> The name of the current classification the bug is in.
+
+=item C<component>
+
+C<string> The name of the current component of this bug.
+
+=item C<creation_time>
+
+C<dateTime> When the bug was created.
+
+=item C<creator>
+
+C<string> The login name of the person who filed this bug (the reporter).
+
+=item C<deadline>
+
+C<string> The day that this bug is due to be completed, in the format
+C<YYYY-MM-DD>.
+
+If you are not in the time-tracking group, this field will not be included
+in the return value.
+
+=item C<depends_on>
+
+C<array> of C<int>s. The ids of bugs that this bug "depends on".
+
+=item C<dupe_of>
+
+C<int> The bug ID of the bug that this bug is a duplicate of. If this bug
+isn't a duplicate of any bug, this will be null.
+
+=item C<estimated_time>
+
+C<double> The number of hours that it was estimated that this bug would
+take.
+
+If you are not in the time-tracking group, this field will not be included
+in the return value.
+
+=item C<groups>
+
+C<array> of C<string>s. The names of all the groups that this bug is in.
+
+=item C<id>
+
+C<int> The unique numeric id of this bug.
+
+=item C<is_cc_accessible>
+
+C<boolean> If true, this bug can be accessed by members of the CC list,
+even if they are not in the groups the bug is restricted to.
+
+=item C<is_confirmed>
+
+C<boolean> True if the bug has been confirmed. Usually this means that
+the bug has at some point been moved out of the C<UNCONFIRMED> status
+and into another open status.
+
+=item C<is_open>
+
+C<boolean> True if this bug is open, false if it is closed.
+
+=item C<is_creator_accessible>
+
+C<boolean> If true, this bug can be accessed by the creator (reporter)
+of the bug, even if he or she is not a member of the groups the bug
+is restricted to.
+
+=item C<keywords>
+
+C<array> of C<string>s. Each keyword that is on this bug.
+
+=item C<last_change_time>
+
+C<dateTime> When the bug was last changed.
+
+=item C<op_sys>
+
+C<string> The name of the operating system that the bug was filed against.
+
+=item C<platform>
+
+C<string> The name of the platform (hardware) that the bug was filed against.
+
+=item C<priority>
+
+C<string> The priority of the bug.
+
+=item C<product>
+
+C<string> The name of the product this bug is in.
+
+=item C<qa_contact>
+
+C<string> The login name of the current QA Contact on the bug.
+
+=item C<remaining_time>
+
+C<double> The number of hours of work remaining until work on this bug
+is complete.
+
+If you are not in the time-tracking group, this field will not be included
+in the return value.
+
+=item C<resolution>
+
+C<string> The current resolution of the bug, or an empty string if the bug
+is open.
+
+=item C<see_also>
+
+B<UNSTABLE>
+
+C<array> of C<string>s. The URLs in the See Also field on the bug.
+
+=item C<severity>
+
+C<string> The current severity of the bug.
+
+=item C<status>
+
+C<string> The current status of the bug.
+
+=item C<summary>
+
+C<string> The summary of this bug.
+
+=item C<target_milestone>
+
+C<string> The milestone that this bug is supposed to be fixed by, or for
+closed bugs, the milestone that it was fixed for.
+
+=item C<update_token>
+
+C<string> The token that you would have to pass to the F<process_bug.cgi>
+page in order to update this bug. This changes every time the bug is
+updated.
+
+This field is not returned to logged-out users.
+
+=item C<url>
+
+B<UNSTABLE>
+
+C<string> A URL that demonstrates the problem described in
+the bug, or is somehow related to the bug report.
+
+=item C<version>
+
+C<string> The version the bug was reported against.
+
+=item C<whiteboard>
+
+C<string> The value of the "status whiteboard" field on the bug.
+
+=item I<custom fields>
+
+Every custom field in this installation will also be included in the
+return value. Most fields are returned as C<string>s. However, some
+field types have different return values:
+
+=over
+
+=item Bug ID Fields - C<int>
+
+=item Multiple-Selection Fields - C<array> of C<string>s.
+
+=item Date/Time Fields - C<dateTime>
+
+=back
+
+=back
+
+=item C<faults> B<EXPERIMENTAL>
+
+An array of hashes that contains invalid bug ids with error messages
+returned for them. Each hash contains the following items:
+
+=over
+
+=item id
+
+C<int> The numeric bug_id of this bug.
+
+=item faultString
+
+c<string> This will only be returned for invalid bugs if the C<permissive>
+argument was set when calling Bug.get, and it is an error indicating that
+the bug id was invalid.
+
+=item faultCode
+
+c<int> This will only be returned for invalid bugs if the C<permissive>
+argument was set when calling Bug.get, and it is the error code for the
+invalid bug error.
+
+=back
+
+=back
+
+=item B<Errors>
+
+=over
+
+=item 100 (Invalid Bug Alias)
+
+If you specified an alias and either: (a) the Bugzilla you're querying
+doesn't support aliases or (b) there is no bug with that alias.
+
+=item 101 (Invalid Bug ID)
+
+The bug_id you specified doesn't exist in the database.
+
+=item 102 (Access Denied)
+
+You do not have access to the bug_id you specified.
+
+=back
+
+=item B<History>
+
+=over
+
+=item C<permissive> argument added to this method's params in Bugzilla B<3.4>.
+
+=item The following properties were added to this method's return values
+in Bugzilla B<3.4>:
+
+=over
+
+=item For C<bugs>
+
+=over
+
+=item assigned_to
+
+=item component
+
+=item dupe_of
+
+=item is_open
+
+=item priority
+
+=item product
+
+=item resolution
+
+=item severity
+
+=item status
+
+=back
+
+=item C<faults>
+
+=back
+
+=item In Bugzilla B<4.0>, the following items were added to the C<bugs>
+return value: C<blocks>, C<cc>, C<classification>, C<creator>,
+C<deadline>, C<depends_on>, C<estimated_time>, C<is_cc_accessible>,
+C<is_confirmed>, C<is_creator_accessible>, C<groups>, C<keywords>,
+C<op_sys>, C<platform>, C<qa_contact>, C<remaining_time>, C<see_also>,
+C<target_milestone>, C<update_token>, C<url>, C<version>, C<whiteboard>,
+and all custom fields.
+
+=back
+
+
+=back
+
+=head2 history
+
+B<EXPERIMENTAL>
+
+=over
+
+=item B<Description>
+
+Gets the history of changes for particular bugs in the database.
+
+=item B<Params>
+
+=over
+
+=item C<ids>
+
+An array of numbers and strings.
+
+If an element in the array is entirely numeric, it represents a bug_id
+from the Bugzilla database to fetch. If it contains any non-numeric
+characters, it is considered to be a bug alias instead, and the data bug
+with that alias will be loaded.
+
+Note that it's possible for aliases to be disabled in Bugzilla, in which
+case you will be told that you have specified an invalid bug_id if you
+try to specify an alias. (It will be error 100.)
+
+=back
+
+=item B<Returns>
+
+A hash containing a single element, C<bugs>. This is an array of hashes,
+containing the following keys:
+
+=over
+
+=item id
+
+C<int> The numeric id of the bug.
+
+=item alias
+
+C<string> The alias of this bug. If there is no alias or aliases are
+disabled in this Bugzilla, this will be undef.
+
+=item history
+
+C<array> An array of hashes, each hash having the following keys:
+
+=over
+
+=item when
+
+C<dateTime> The date the bug activity/change happened.
+
+=item who
+
+C<string> The login name of the user who performed the bug change.
+
+=item changes
+
+C<array> An array of hashes which contain all the changes that happened
+to the bug at this time (as specified by C<when>). Each hash contains
+the following items:
+
+=over
+
+=item field_name
+
+C<string> The name of the bug field that has changed.
+
+=item removed
+
+C<string> The previous value of the bug field which has been deleted
+by the change.
+
+=item added
+
+C<string> The new value of the bug field which has been added by the change.
+
+=item attachment_id
+
+C<int> The id of the attachment that was changed. This only appears if
+the change was to an attachment, otherwise C<attachment_id> will not be
+present in this hash.
+
+=back
+
+=back
+
+=back
+
+=item B<Errors>
+
+The same as L</get>.
+
+=item B<History>
+
+=over
+
+=item Added in Bugzilla B<3.4>.
+
+=back
+
+=back
+
+
+=head2 search
+
+B<UNSTABLE>
+
+=over
+
+=item B<Description>
+
+Allows you to search for bugs based on particular criteria.
+
+=item B<Params>
+
+Unless otherwise specified in the description of a parameter, bugs are
+returned if they match I<exactly> the criteria you specify in these
+parameters. That is, we don't match against substrings--if a bug is in
+the "Widgets" product and you ask for bugs in the "Widg" product, you
+won't get anything.
+
+Criteria are joined in a logical AND. That is, you will be returned
+bugs that match I<all> of the criteria, not bugs that match I<any> of
+the criteria.
+
+Each parameter can be either the type it says, or an array of the types
+it says. If you pass an array, it means "Give me bugs with I<any> of
+these values." For example, if you wanted bugs that were in either
+the "Foo" or "Bar" products, you'd pass:
+
+ product => ['Foo', 'Bar']
+
+Some Bugzillas may treat your arguments case-sensitively, depending
+on what database system they are using. Most commonly, though, Bugzilla is
+not case-sensitive with the arguments passed (because MySQL is the
+most-common database to use with Bugzilla, and MySQL is not case sensitive).
+
+=over
+
+=item C<alias>
+
+C<string> The unique alias for this bug. Note that you can search
+by alias even if the alias field is disabled in this Bugzilla, but
+it's likely that there won't be any aliases set on bugs, in that case.
+
+=item C<assigned_to>
+
+C<string> The login name of a user that a bug is assigned to.
+
+=item C<component>
+
+C<string> The name of the Component that the bug is in. Note that
+if there are multiple Components with the same name, and you search
+for that name, bugs in I<all> those Components will be returned. If you
+don't want this, be sure to also specify the C<product> argument.
+
+=item C<creation_time>
+
+C<dateTime> Searches for bugs that were created at this time or later.
+May not be an array.
+
+=item C<creator>
+
+C<string> The login name of the user who created the bug.
+
+=item C<id>
+
+C<int> The numeric id of the bug.
+
+=item C<last_change_time>
+
+C<dateTime> Searches for bugs that were modified at this time or later.
+May not be an array.
+
+=item C<limit>
+
+C<int> Limit the number of results returned to C<int> records.
+
+=item C<offset>
+
+C<int> Used in conjunction with the C<limit> argument, C<offset> defines
+the starting position for the search. For example, given a search that
+would return 100 bugs, setting C<limit> to 10 and C<offset> to 10 would return
+bugs 11 through 20 from the set of 100.
+
+=item C<op_sys>
+
+C<string> The "Operating System" field of a bug.
+
+=item C<platform>
+
+C<string> The Platform (sometimes called "Hardware") field of a bug.
+
+=item C<priority>
+
+C<string> The Priority field on a bug.
+
+=item C<product>
+
+C<string> The name of the Product that the bug is in.
+
+=item C<creator>
+
+C<string> The login name of the user who reported the bug.
+
+You can also pass this argument with the name C<reporter>, for
+backwards compatibility with older Bugzillas.
+
+=item C<resolution>
+
+C<string> The current resolution--only set if a bug is closed. You can
+find open bugs by searching for bugs with an empty resolution.
+
+=item C<severity>
+
+C<string> The Severity field on a bug.
+
+=item C<status>
+
+C<string> The current status of a bug (not including its resolution,
+if it has one, which is a separate field above).
+
+=item C<summary>
+
+C<string> Searches for substrings in the single-line Summary field on
+bugs. If you specify an array, then bugs whose summaries match I<any> of the
+passed substrings will be returned.
+
+Note that unlike searching in the Bugzilla UI, substrings are not split
+on spaces. So searching for C<foo bar> will match "This is a foo bar"
+but not "This foo is a bar". C<['foo', 'bar']>, would, however, match
+the second item.
+
+=item C<target_milestone>
+
+C<string> The Target Milestone field of a bug. Note that even if this
+Bugzilla does not have the Target Milestone field enabled, you can
+still search for bugs by Target Milestone. However, it is likely that
+in that case, most bugs will not have a Target Milestone set (it
+defaults to "---" when the field isn't enabled).
+
+=item C<qa_contact>
+
+C<string> The login name of the bug's QA Contact. Note that even if
+this Bugzilla does not have the QA Contact field enabled, you can
+still search for bugs by QA Contact (though it is likely that no bug
+will have a QA Contact set, if the field is disabled).
+
+=item C<url>
+
+C<string> The "URL" field of a bug.
+
+=item C<version>
+
+C<string> The Version field of a bug.
+
+=item C<whiteboard>
+
+C<string> Search the "Status Whiteboard" field on bugs for a substring.
+Works the same as the C<summary> field described above, but searches the
+Status Whiteboard field.
+
+=back
+
+=item B<Returns>
+
+The same as L</get>.
+
+Note that you will only be returned information about bugs that you
+can see. Bugs that you can't see will be entirely excluded from the
+results. So, if you want to see private bugs, you will have to first
+log in and I<then> call this method.
+
+=item B<Errors>
+
+Currently, this function doesn't throw any special errors (other than
+the ones that all webservice functions can throw). If you specify
+an invalid value for a particular field, you just won't get any results
+for that value.
+
+=item B<History>
+
+=over
+
+=item Added in Bugzilla B<3.4>.
+
+=item Searching by C<votes> was removed in Bugzilla B<4.0>.
+
+=item The C<reporter> input parameter was renamed to C<creator>
+in Bugzilla B<4.0>.
+
+=back
+
+=back
+
+
+=head1 Bug Creation and Modification
+
+=head2 create
+
+B<STABLE>
+
+=over
+
+=item B<Description>
+
+This allows you to create a new bug in Bugzilla. If you specify any
+invalid fields, they will be ignored. If you specify any fields you
+are not allowed to set, they will just be set to their defaults or ignored.
+
+You cannot currently set all the items here that you can set on enter_bug.cgi.
+
+The WebService interface may allow you to set things other than those listed
+here, but realize that anything undocumented is B<UNSTABLE> and will very
+likely change in the future.
+
+=item B<Params>
+
+Some params must be set, or an error will be thrown. These params are
+marked B<Required>.
+
+Some parameters can have defaults set in Bugzilla, by the administrator.
+If these parameters have defaults set, you can omit them. These parameters
+are marked B<Defaulted>.
+
+Clients that want to be able to interact uniformly with multiple
+Bugzillas should always set both the params marked B<Required> and those
+marked B<Defaulted>, because some Bugzillas may not have defaults set
+for B<Defaulted> parameters, and then this method will throw an error
+if you don't specify them.
+
+The descriptions of the parameters below are what they mean when Bugzilla is
+being used to track software bugs. They may have other meanings in some
+installations.
+
+=over
+
+=item C<product> (string) B<Required> - The name of the product the bug
+is being filed against.
+
+=item C<component> (string) B<Required> - The name of a component in the
+product above.
+
+=item C<summary> (string) B<Required> - A brief description of the bug being
+filed.
+
+=item C<version> (string) B<Required> - A version of the product above;
+the version the bug was found in.
+
+=item C<description> (string) B<Defaulted> - The initial description for
+this bug. Some Bugzilla installations require this to not be blank.
+
+=item C<op_sys> (string) B<Defaulted> - The operating system the bug was
+discovered on.
+
+=item C<platform> (string) B<Defaulted> - What type of hardware the bug was
+experienced on.
+
+=item C<priority> (string) B<Defaulted> - What order the bug will be fixed
+in by the developer, compared to the developer's other bugs.
+
+=item C<severity> (string) B<Defaulted> - How severe the bug is.
+
+=item C<alias> (string) - A brief alias for the bug that can be used
+instead of a bug number when accessing this bug. Must be unique in
+all of this Bugzilla.
+
+=item C<assigned_to> (username) - A user to assign this bug to, if you
+don't want it to be assigned to the component owner.
+
+=item C<cc> (array) - An array of usernames to CC on this bug.
+
+=item C<comment_is_private> (boolean) - If set to true, the description
+is private, otherwise it is assumed to be public.
+
+=item C<groups> (array) - An array of group names to put this
+bug into. You can see valid group names on the Permissions
+tab of the Preferences screen, or, if you are an administrator,
+in the Groups control panel. Note that invalid group names or
+groups that the bug can't be restricted to are silently ignored. If
+you don't specify this argument, then a bug will be added into
+all the groups that are set as being "Default" for this product. (If
+you want to avoid that, you should specify C<groups> as an empty array.)
+
+=item C<qa_contact> (username) - If this installation has QA Contacts
+enabled, you can set the QA Contact here if you don't want to use
+the component's default QA Contact.
+
+=item C<status> (string) - The status that this bug should start out as.
+Note that only certain statuses can be set on bug creation.
+
+=item C<target_milestone> (string) - A valid target milestone for this
+product.
+
+=back
+
+In addition to the above parameters, if your installation has any custom
+fields, you can set them just by passing in the name of the field and
+its value as a string.
+
+=item B<Returns>
+
+A hash with one element, C<id>. This is the id of the newly-filed bug.
+
+=item B<Errors>
+
+=over
+
+=item 51 (Invalid Object)
+
+You specified a field value that is invalid. The error message will have
+more details.
+
+=item 103 (Invalid Alias)
+
+The alias you specified is invalid for some reason. See the error message
+for more details.
+
+=item 104 (Invalid Field)
+
+One of the drop-down fields has an invalid value, or a value entered in a
+text field is too long. The error message will have more detail.
+
+=item 105 (Invalid Component)
+
+You didn't specify a component.
+
+=item 106 (Invalid Product)
+
+Either you didn't specify a product, this product doesn't exist, or
+you don't have permission to enter bugs in this product.
+
+=item 107 (Invalid Summary)
+
+You didn't specify a summary for the bug.
+
+=item 116 (Dependency Loop)
+
+You specified values in the C<blocks> or C<depends_on> fields
+that would cause a circular dependency between bugs.
+
+=item 504 (Invalid User)
+
+Either the QA Contact, Assignee, or CC lists have some invalid user
+in them. The error message will have more details.
+
+=back
+
+=item B<History>
+
+=over
+
+=item Before B<3.0.4>, parameters marked as B<Defaulted> were actually
+B<Required>, due to a bug in Bugzilla.
+
+=item The C<groups> argument was added in Bugzilla B<4.0>. Before
+Bugzilla 4.0, bugs were only added into Mandatory groups by this
+method.
+
+=item The C<comment_is_private> argument was added in Bugzilla B<4.0>.
+Before Bugzilla 4.0, you had to use the undocumented C<commentprivacy>
+argument.
+
+=item Error 116 was added in Bugzilla B<4.0>. Before that, dependency
+loop errors had a generic code of C<32000>.
+
+=back
+
+=back
+
+
+=head2 add_attachment
+
+B<UNSTABLE>
+
+=over
+
+=item B<Description>
+
+This allows you to add an attachment to a bug in Bugzilla.
+
+=item B<Params>
+
+=over
+
+=item C<ids>
+
+B<Required> C<array> An array of ints and/or strings--the ids
+or aliases of bugs that you want to add this attachment to.
+The same attachment and comment will be added to all
+these bugs.
+
+=item C<data>
+
+B<Required> C<base64> The content of the attachment.
+
+=item C<file_name>
+
+B<Required> C<string> The "file name" that will be displayed
+in the UI for this attachment.
+
+=item C<summary>
+
+B<Required> C<string> A short string describing the
+attachment.
+
+=item C<content_type>
+
+B<Required> C<string> The MIME type of the attachment, like
+C<text/plain> or C<image/png>.
+
+=item C<comment>
+
+C<string> A comment to add along with this attachment.
+
+=item C<is_patch>
+
+C<boolean> True if Bugzilla should treat this attachment as a patch.
+If you specify this, you do not need to specify a C<content_type>.
+The C<content_type> of the attachment will be forced to C<text/plain>.
+
+Defaults to False if not specified.
+
+=item C<is_private>
+
+C<boolean> True if the attachment should be private (restricted
+to the "insidergroup"), False if the attachment should be public.
+
+Defaults to False if not specified.
+
+=item C<is_url>
+
+C<boolean> True if the attachment is just a URL, pointing to data elsewhere.
+If so, the C<data> item should just contain the URL.
+
+Defaults to False if not specified.
+
+=back
+
+=item B<Returns>
+
+A single item C<attachments>, which contains the created
+attachments in the same format as the C<attachments> return
+value from L</attachments>.
+
+=item B<Errors>
+
+This method can throw all the same errors as L</get>, plus:
+
+=over
+
+=item 600 (Attachment Too Large)
+
+You tried to attach a file that was larger than Bugzilla will accept.
+
+=item 601 (Invalid MIME Type)
+
+You specified a C<content_type> argument that was blank, not a valid
+MIME type, or not a MIME type that Bugzilla accepts for attachments.
+
+=item 602 (Illegal URL)
+
+You specified C<is_url> as True, but the data that you attempted
+to attach was not a valid URL.
+
+=item 603 (File Name Not Specified)
+
+You did not specify a valid for the C<file_name> argument.
+
+=item 604 (Summary Required)
+
+You did not specify a value for the C<summary> argument.
+
+=item 605 (URL Attaching Disabled)
+
+You attempted to attach a URL, setting C<is_url> to True,
+but this Bugzilla does not support attaching URLs.
+
+=item 606 (Empty Data)
+
+You set the "data" field to an empty string.
+
+=back
+
+=back
+
+
+=head2 add_comment
+
+B<STABLE>
+
+=over
+
+=item B<Description>
+
+This allows you to add a comment to a bug in Bugzilla.
+
+=item B<Params>
+
+=over
+
+=item C<id> (int) B<Required> - The id or alias of the bug to append a
+comment to.
+
+=item C<comment> (string) B<Required> - The comment to append to the bug.
+If this is empty or all whitespace, an error will be thrown saying that
+you did not set the C<comment> parameter.
+
+=item C<is_private> (boolean) - If set to true, the comment is private,
+otherwise it is assumed to be public.
+
+=item C<work_time> (double) - Adds this many hours to the "Hours Worked"
+on the bug. If you are not in the time tracking group, this value will
+be ignored.
+
+
+=back
+
+=item B<Returns>
+
+A hash with one element, C<id> whose value is the id of the newly-created comment.
+
+=item B<Errors>
+
+=over
+
+=item 54 (Hours Worked Too Large)
+
+You specified a C<work_time> larger than the maximum allowed value of
+C<99999.99>.
+
+=item 100 (Invalid Bug Alias)
+
+If you specified an alias and either: (a) the Bugzilla you're querying
+doesn't support aliases or (b) there is no bug with that alias.
+
+=item 101 (Invalid Bug ID)
+
+The id you specified doesn't exist in the database.
+
+=item 109 (Bug Edit Denied)
+
+You did not have the necessary rights to edit the bug.
+
+=item 113 (Can't Make Private Comments)
+
+You tried to add a private comment, but don't have the necessary rights.
+
+=item 114 (Comment Too Long)
+
+You tried to add a comment longer than the maximum allowed length
+(65,535 characters).
+
+=back
+
+=item B<History>
+
+=over
+
+=item Added in Bugzilla B<3.2>.
+
+=item Modified to return the new comment's id in Bugzilla B<3.4>
+
+=item Modified to throw an error if you try to add a private comment
+but can't, in Bugzilla B<3.4>.
+
+=item Before Bugzilla B<3.6>, the C<is_private> argument was called
+C<private>, and you can still call it C<private> for backwards-compatibility
+purposes if you wish.
+
+=item Before Bugzilla B<3.6>, error 54 and error 114 had a generic error
+code of 32000.
+
+=back
+
+=back
+
+
+=head2 update
+
+B<UNSTABLE>
+
+=over
+
+=item B<Description>
+
+Allows you to update the fields of a bug. Automatically sends emails
+out about the changes.
+
+=item B<Params>
+
+=over
+
+=item C<ids>
+
+Array of C<int>s or C<string>s. The ids or aliases of the bugs that
+you want to modify.
+
+=back
+
+B<Note>: All following fields specify the values you want to set on the
+bugs you are updating.
+
+=over
+
+=item C<alias>
+
+(string) The alias of the bug. You can only set this if you are modifying
+a single bug. If there is more than one bug specified in C<ids>, passing in
+a value for C<alias> will cause an error to be thrown.
+
+=item C<assigned_to>
+
+C<string> The full login name of the user this bug is assigned to.
+
+=item C<blocks>
+
+=item C<depends_on>
+
+C<hash> These specify the bugs that this bug blocks or depends on,
+respectively. To set these, you should pass a hash as the value. The hash
+may contain the following fields:
+
+=over
+
+=item C<add> An array of C<int>s. Bug ids to add to this field.
+
+=item C<remove> An array of C<int>s. Bug ids to remove from this field.
+If the bug ids are not already in the field, they will be ignored.
+
+=item C<set> An array of C<int>s. An exact set of bug ids to set this
+field to, overriding the current value. If you specify C<set>, then C<add>
+and C<remove> will be ignored.
+
+=back
+
+=item C<cc>
+
+C<hash> The users on the cc list. To modify this field, pass a hash, which
+may have the following fields:
+
+=over
+
+=item C<add> Array of C<string>s. User names to add to the CC list.
+They must be full user names, and an error will be thrown if you pass
+in an invalid user name.
+
+=item C<remove> Array of C<string>s. User names to remove from the CC
+list. They must be full user names, and an error will be thrown if you
+pass in an invalid user name.
+
+=back
+
+=item C<is_cc_accessible>
+
+C<boolean> Whether or not users in the CC list are allowed to access
+the bug, even if they aren't in a group that can normally access the bug.
+
+=item C<comment>
+
+C<hash>. A comment on the change. The hash may contain the following fields:
+
+=over
+
+=item C<body> C<string> The actual text of the comment.
+B<Note>: For compatibility with the parameters to L</add_comment>,
+you can also call this field C<comment>, if you want.
+
+=item C<is_private> C<boolean> Whether the comment is private or not.
+If you try to make a comment private and you don't have the permission
+to, an error will be thrown.
+
+=back
+
+=item C<comment_is_private>
+
+C<hash> This is how you update the privacy of comments that are already
+on a bug. This is a hash, where the keys are the C<int> id of comments (not
+their count on a bug, like #1, #2, #3, but their globally-unique id,
+as returned by L</comments>) and the value is a C<boolean> which specifies
+whether that comment should become private (C<true>) or public (C<false>).
+
+The comment ids must be valid for the bug being updated. Thus, it is not
+practical to use this while updating multiple bugs at once, as a single
+comment id will never be valid on multiple bugs.
+
+=item C<component>
+
+C<string> The Component the bug is in.
+
+=item C<deadline>
+
+C<string> The Deadline field--a date specifying when the bug must
+be completed by, in the format C<YYYY-MM-DD>.
+
+=item C<dupe_of>
+
+C<int> The bug that this bug is a duplicate of. If you want to mark
+a bug as a duplicate, the safest thing to do is to set this value
+and I<not> set the C<status> or C<resolution> fields. They will
+automatically be set by Bugzilla to the appropriate values for
+duplicate bugs.
+
+=item C<estimated_time>
+
+C<double> The total estimate of time required to fix the bug, in hours.
+This is the I<total> estimate, not the amount of time remaining to fix it.
+
+=item C<groups>
+
+C<hash> The groups a bug is in. To modify this field, pass a hash, which
+may have the following fields:
+
+=over
+
+=item C<add> Array of C<string>s. The names of groups to add. Passing
+in an invalid group name or a group that you cannot add to this bug will
+cause an error to be thrown.
+
+=item C<remove> Array of C<string>s. The names of groups to remove. Passing
+in an invalid group name or a group that you cannot remove from this bug
+will cause an error to be thrown.
+
+=back
+
+=item C<keywords>
+
+C<hash> Keywords on the bug. To modify this field, pass a hash, which
+may have the following fields:
+
+=over
+
+=item C<add> An array of C<strings>s. The names of keywords to add to
+the field on the bug. Passing something that isn't a valid keyword name
+will cause an error to be thrown.
+
+=item C<remove> An array of C<string>s. The names of keywords to remove
+from the field on the bug. Passing something that isn't a valid keyword
+name will cause an error to be thrown.
+
+=item C<set> An array of C<strings>s. An exact set of keywords to set the
+field to, on the bug. Passing something that isn't a valid keyword name
+will cause an error to be thrown. Specifying C<set> overrides C<add> and
+C<remove>.
+
+=back
+
+=item C<op_sys>
+
+C<string> The Operating System ("OS") field on the bug.
+
+=item C<platform>
+
+C<string> The Platform or "Hardware" field on the bug.
+
+=item C<priority>
+
+C<string> The Priority field on the bug.
+
+=item C<product>
+
+C<string> The name of the product that the bug is in. If you change
+this, you will probably also want to change C<target_milestone>,
+C<version>, and C<component>, since those have different legal
+values in every product.
+
+If you cannot change the C<target_milestone> field, it will be reset to
+the default for the product, when you move a bug to a new product.
+
+You may also wish to add or remove groups, as which groups are
+valid on a bug depends on the product. Groups that are not valid
+in the new product will be automatically removed, and groups which
+are mandatory in the new product will be automaticaly added, but no
+other automatic group changes will be done.
+
+Note that users can only move a bug into a product if they would
+normally have permission to file new bugs in that product.
+
+=item C<qa_contact>
+
+C<string> The full login name of the bug's QA Contact.
+
+=item C<is_creator_accessible>
+
+C<boolean> Whether or not the bug's reporter is allowed to access
+the bug, even if he or she isn't in a group that can normally access
+the bug.
+
+=item C<remaining_time>
+
+C<double> How much work time is remaining to fix the bug, in hours.
+If you set C<work_time> but don't explicitly set C<remaining_time>,
+then the C<work_time> will be deducted from the bug's C<remaining_time>.
+
+=item C<reset_assigned_to>
+
+C<boolean> If true, the C<assigned_to> field will be reset to the
+default for the component that the bug is in. (If you have set the
+component at the same time as using this, then the component used
+will be the new component, not the old one.)
+
+=item C<reset_qa_contact>
+
+C<boolean> If true, the C<qa_contact> field will be reset to the
+default for the component that the bug is in. (If you have set the
+component at the same time as using this, then the component used
+will be the new component, not the old one.)
+
+=item C<resolution>
+
+C<string> The current resolution. May only be set if you are closing
+a bug or if you are modifying an already-closed bug. Attempting to set
+the resolution to I<any> value (even an empty or null string) on an
+open bug will cause an error to be thrown.
+
+If you change the C<status> field to an open status, the resolution
+field will automatically be cleared, so you don't have to clear it
+manually.
+
+=item C<see_also>
+
+C<hash> The See Also field on a bug, specifying URLs to bugs in other
+bug trackers. To modify this field, pass a hash, which may have the
+following fields:
+
+=over
+
+=item C<add> An array of C<strings>s. URLs to add to the field.
+Each URL must be a valid URL to a bug-tracker, or an error will
+be thrown.
+
+=item C<remove> An array of C<string>s. URLs to remove from the field.
+Invalid URLs will be ignored.
+
+=back
+
+=item C<severity>
+
+C<string> The Severity field of a bug.
+
+=item C<status>
+
+C<string> The status you want to change the bug to. Note that if
+a bug is changing from open to closed, you should also specify
+a C<resolution>.
+
+=item C<summary>
+
+C<string> The Summary field of the bug.
+
+=item C<target_milestone>
+
+C<string> The bug's Target Milestone.
+
+=item C<url>
+
+C<string> The "URL" field of a bug.
+
+=item C<version>
+
+C<string> The bug's Version field.
+
+=item C<whiteboard>
+
+C<string> The Status Whiteboard field of a bug.
+
+=item C<work_time>
+
+C<double> The number of hours worked on this bug as part of this change.
+If you set C<work_time> but don't explicitly set C<remaining_time>,
+then the C<work_time> will be deducted from the bug's C<remaining_time>.
+
+=back
+
+You can also set the value of any custom field by passing its name as
+a parameter, and the value to set the field to. For multiple-selection
+fields, the value should be an array of strings.
+
+=item B<Returns>
+
+A C<hash> with a single field, "bugs". This points to an array of hashes
+with the following fields:
+
+=over
+
+=item C<id>
+
+C<int> The id of the bug that was updated.
+
+=item C<alias>
+
+C<string> The alias of the bug that was updated, if aliases are enabled and
+this bug has an alias.
+
+=item C<last_change_time>
+
+C<dateTime> The exact time that this update was done at, for this bug.
+If no update was done (that is, no fields had their values changed and
+no comment was added) then this will instead be the last time the bug
+was updated.
+
+=item C<changes>
+
+C<hash> The changes that were actually done on this bug. The keys are
+the names of the fields that were changed, and the values are a hash
+with two keys:
+
+=over
+
+=item C<added> (C<string>) The values that were added to this field,
+possibly a comma-and-space-separated list if multiple values were added.
+
+=item C<removed> (C<string>) The values that were removed from this
+field, possibly a comma-and-space-separated list if multiple values were
+removed.
+
+=back
+
+=back
+
+Here's an example of what a return value might look like:
+
+ {
+ bugs => [
+ {
+ id => 123,
+ alias => 'foo',
+ last_change_time => '2010-01-01T12:34:56',
+ changes => {
+ status => {
+ removed => 'NEW',
+ added => 'ASSIGNED'
+ },
+ keywords => {
+ removed => 'bar',
+ added => 'qux, quo, qui',
+ }
+ },
+ }
+ ]
+ }
+
+Currently, some fields are not tracked in changes: C<comment>,
+C<comment_is_private>, and C<work_time>. This means that they will not
+show up in the return value even if they were successfully updated.
+This may change in a future version of Bugzilla.
+
+=item B<Errors>
+
+This function can throw all of the errors that L</get>, L</create>,
+and L</add_comment> can throw, plus:
+
+=over
+
+=item 50 (Empty Field)
+
+You tried to set some field to be empty, but that field cannot be empty.
+The error message will have more details.
+
+=item 52 (Input Not A Number)
+
+You tried to set a numeric field to a value that wasn't numeric.
+
+=item 54 (Number Too Large)
+
+You tried to set a numeric field to a value larger than that field can
+accept.
+
+=item 55 (Number Too Small)
+
+You tried to set a negative value in a numeric field that does not accept
+negative values.
+
+=item 56 (Bad Date/Time)
+
+You specified an invalid date or time in a date/time field (such as
+the C<deadline> field or a custom date/time field).
+
+=item 112 (See Also Invalid)
+
+You attempted to add an invalid value to the C<see_also> field.
+
+=item 115 (Permission Denied)
+
+You don't have permission to change a particular field to a particular value.
+The error message will have more detail.
+
+=item 116 (Dependency Loop)
+
+You specified a value in the C<blocks> or C<depends_on> fields that causes
+a dependency loop.
+
+=item 117 (Invalid Comment ID)
+
+You specified a comment id in C<comment_is_private> that isn't on this bug.
+
+=item 118 (Duplicate Loop)
+
+You specified a value for C<dupe_of> that causes an infinite loop of
+duplicates.
+
+=item 119 (dupe_of Required)
+
+You changed the resolution to C<DUPLICATE> but did not specify a value
+for the C<dupe_of> field.
+
+=item 120 (Group Add/Remove Denied)
+
+You tried to add or remove a group that you don't have permission to modify
+for this bug, or you tried to add a group that isn't valid in this product.
+
+=item 121 (Resolution Required)
+
+You tried to set the C<status> field to a closed status, but you didn't
+specify a resolution.
+
+=item 122 (Resolution On Open Status)
+
+This bug has an open status, but you specified a value for the C<resolution>
+field.
+
+=item 123 (Invalid Status Transition)
+
+You tried to change from one status to another, but the status workflow
+rules don't allow that change.
+
+=back
+
+=item B<History>
+
+=over
+
+=item Added in Bugzilla B<4.0>.
+
+=back
+
+=back
+
+
+=head2 update_see_also
+
+B<EXPERIMENTAL>
+
+=over
+
+=item B<Description>
+
+Adds or removes URLs for the "See Also" field on bugs. These URLs must
+point to some valid bug in some Bugzilla installation or in Launchpad.
+
+=item B<Params>
+
+=over
+
+=item C<ids>
+
+Array of C<int>s or C<string>s. The ids or aliases of bugs that you want
+to modify.
+
+=item C<add>
+
+Array of C<string>s. URLs to Bugzilla bugs. These URLs will be added to
+the See Also field. They must be valid URLs to C<show_bug.cgi> in a
+Bugzilla installation or to a bug filed at launchpad.net.
+
+If the URLs don't start with C<http://> or C<https://>, it will be assumed
+that C<http://> should be added to the beginning of the string.
+
+It is safe to specify URLs that are already in the "See Also" field on
+a bug--they will just be silently ignored.
+
+=item C<remove>
+
+Array of C<string>s. These URLs will be removed from the See Also field.
+You must specify the full URL that you want removed. However, matching
+is done case-insensitively, so you don't have to specify the URL in
+exact case, if you don't want to.
+
+If you specify a URL that is not in the See Also field of a particular bug,
+it will just be silently ignored. Invaild URLs are currently silently ignored,
+though this may change in some future version of Bugzilla.
+
+=back
+
+NOTE: If you specify the same URL in both C<add> and C<remove>, it will
+be I<added>. (That is, C<add> overrides C<remove>.)
+
+=item B<Returns>
+
+C<changes>, a hash where the keys are numeric bug ids and the contents
+are a hash with one key, C<see_also>. C<see_also> points to a hash, which
+contains two keys, C<added> and C<removed>. These are arrays of strings,
+representing the actual changes that were made to the bug.
+
+Here's a diagram of what the return value looks like for updating
+bug ids 1 and 2:
+
+ {
+ changes => {
+ 1 => {
+ see_also => {
+ added => (an array of bug URLs),
+ removed => (an array of bug URLs),
+ }
+ },
+ 2 => {
+ see_also => {
+ added => (an array of bug URLs),
+ removed => (an array of bug URLs),
+ }
+ }
+ }
+ }
+
+This return value allows you to tell what this method actually did. It is in
+this format to be compatible with the return value of a future C<Bug.update>
+method.
+
+=item B<Errors>
+
+This method can throw all of the errors that L</get> throws, plus:
+
+=over
+
+=item 109 (Bug Edit Denied)
+
+You did not have the necessary rights to edit the bug.
+
+=item 112 (Invalid Bug URL)
+
+One of the URLs you provided did not look like a valid bug URL.
+
+=item 115 (See Also Edit Denied)
+
+You did not have the necessary rights to edit the See Also field for
+this bug.
+
+=back
+
+=item B<History>
+
+=over
+
+=item Added in Bugzilla B<3.4>.
+
+=item Before Bugzilla B<3.6>, error 115 had a generic error code of 32000.
+
+=back
+
+=back
diff --git a/Bugzilla/WebService/Bugzilla.pm b/Bugzilla/WebService/Bugzilla.pm
new file mode 100644
index 000000000..efc822311
--- /dev/null
+++ b/Bugzilla/WebService/Bugzilla.pm
@@ -0,0 +1,270 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s): Marc Schumann <wurblzap@gmail.com>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+# Mads Bondo Dydensborg <mbd@dbc.dk>
+
+package Bugzilla::WebService::Bugzilla;
+
+use strict;
+use base qw(Bugzilla::WebService);
+use Bugzilla::Constants;
+use Bugzilla::Util qw(datetime_from);
+
+use DateTime;
+
+# Basic info that is needed before logins
+use constant LOGIN_EXEMPT => {
+ timezone => 1,
+ version => 1,
+};
+
+use constant READ_ONLY => qw(
+ extensions
+ timezone
+ time
+ version
+);
+
+sub 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 };
+}
+
+sub timezone {
+ 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'),
+ };
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::WebService::Bugzilla - Global functions for the webservice interface.
+
+=head1 DESCRIPTION
+
+This provides functions that tell you about Bugzilla in general.
+
+=head1 METHODS
+
+See L<Bugzilla::WebService> for a description of how parameters are passed,
+and what B<STABLE>, B<UNSTABLE>, and B<EXPERIMENTAL> mean.
+
+=head2 version
+
+B<STABLE>
+
+=over
+
+=item B<Description>
+
+Returns the current version of Bugzilla.
+
+=item B<Params> (none)
+
+=item B<Returns>
+
+A hash with a single item, C<version>, that is the version as a
+string.
+
+=item B<Errors> (none)
+
+=back
+
+=head2 extensions
+
+B<EXPERIMENTAL>
+
+=over
+
+=item B<Description>
+
+Gets information about the extensions that are currently installed and enabled
+in this Bugzilla.
+
+=item B<Params> (none)
+
+=item B<Returns>
+
+A hash with a single item, C<extensions>. This points to a hash. I<That> hash
+contains the names of extensions as keys, and the values are a hash.
+That hash contains a single key C<version>, which is the version of the
+extension, or C<0> if the extension hasn't defined a version.
+
+The return value looks something like this:
+
+ extensions => {
+ Example => {
+ version => '3.6',
+ },
+ BmpConvert => {
+ version => '1.0',
+ },
+ }
+
+=item B<History>
+
+=over
+
+=item Added in Bugzilla B<3.2>.
+
+=item As of Bugzilla B<3.6>, the names of extensions are canonical names
+that the extensions define themselves. Before 3.6, the names of the
+extensions depended on the directory they were in on the Bugzilla server.
+
+=back
+
+=back
+
+=head2 timezone
+
+B<DEPRECATED> This method may be removed in a future version of Bugzilla.
+Use L</time> instead.
+
+=over
+
+=item B<Description>
+
+Returns the timezone that Bugzilla expects dates and times in.
+
+=item B<Params> (none)
+
+=item B<Returns>
+
+A hash with a single item, C<timezone>, that is the timezone offset as a
+string in (+/-)XXXX (RFC 2822) format.
+
+=item B<History>
+
+=over
+
+=item As of Bugzilla B<3.6>, the timezone returned is always C<+0000>
+(the UTC timezone).
+
+=back
+
+=back
+
+
+=head2 time
+
+B<STABLE>
+
+=over
+
+=item B<Description>
+
+Gets information about what time the Bugzilla server thinks it is, and
+what timezone it's running in.
+
+=item B<Params> (none)
+
+=item B<Returns>
+
+A struct with the following items:
+
+=over
+
+=item C<db_time>
+
+C<dateTime> The current time in UTC, according to the Bugzilla
+I<database server>.
+
+Note that Bugzilla assumes that the database and the webserver are running
+in the same time zone. However, if the web server and the database server
+aren't synchronized for some reason, I<this> is the time that you should
+rely on for doing searches and other input to the WebService.
+
+=item C<web_time>
+
+C<dateTime> This is the current time in UTC, according to Bugzilla's
+I<web server>.
+
+This might be different by a second from C<db_time> since this comes from
+a different source. If it's any more different than a second, then there is
+likely some problem with this Bugzilla instance. In this case you should
+rely on the C<db_time>, not the C<web_time>.
+
+=item C<web_time_utc>
+
+Identical to C<web_time>. (Exists only for backwards-compatibility with
+versions of Bugzilla before 3.6.)
+
+=item C<tz_name>
+
+C<string> The literal string C<UTC>. (Exists only for backwards-compatibility
+with versions of Bugzilla before 3.6.)
+
+=item C<tz_short_name>
+
+C<string> The literal string C<UTC>. (Exists only for backwards-compatibility
+with versions of Bugzilla before 3.6.)
+
+=item C<tz_offset>
+
+C<string> The literal string C<+0000>. (Exists only for backwards-compatibility
+with versions of Bugzilla before 3.6.)
+
+=back
+
+=item B<History>
+
+=over
+
+=item Added in Bugzilla B<3.4>.
+
+=item As of Bugzilla B<3.6>, this method returns all data as though the server
+were in the UTC timezone, instead of returning information in the server's
+local timezone.
+
+=back
+
+=back
diff --git a/Bugzilla/WebService/Constants.pm b/Bugzilla/WebService/Constants.pm
new file mode 100644
index 000000000..ac4aa712e
--- /dev/null
+++ b/Bugzilla/WebService/Constants.pm
@@ -0,0 +1,185 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s): Marc Schumann <wurblzap@gmail.com>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::WebService::Constants;
+
+use strict;
+use base qw(Exporter);
+
+our @EXPORT = qw(
+ WS_ERROR_CODE
+ ERROR_UNKNOWN_FATAL
+ ERROR_UNKNOWN_TRANSIENT
+
+ WS_DISPATCH
+);
+
+# This maps the error names in global/*-error.html.tmpl to numbers.
+# Generally, transient errors should have a number above 0, and
+# fatal errors should have a number below 0.
+#
+# This hash should generally contain any error that could be thrown
+# by the WebService interface. If it's extremely unlikely that the
+# error could be thrown (like some CodeErrors), it doesn't have to
+# be listed here.
+#
+# "Transient" means "If you resubmit that request with different data,
+# it may work."
+#
+# "Fatal" means, "There's something wrong with Bugzilla, probably
+# something an administrator would have to fix."
+#
+# NOTE: Numbers must never be recycled. If you remove a number, leave a
+# comment that it was retired. Also, if an error changes its name, you'll
+# have to fix it here.
+use constant WS_ERROR_CODE => {
+ # Generic 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,
+ # 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,
+ # 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,
+ # 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,
+ # Group errors
+ group_change_denied => 120,
+ group_invalid_restriction => 120,
+ # Status/Resolution errors
+ missing_resolution => 121,
+ resolution_not_allowed => 122,
+ illegal_bug_status_transition => 123,
+
+ # Authentication errors are usually 300-400.
+ invalid_username_or_password => 300,
+ account_disabled => 301,
+ auth_invalid_email => 302,
+ extern_id_conflict => -303,
+ auth_failure => 304,
+ password_current_too_short => 305,
+
+ # Except, historically, AUTH_NODATA, which is 410.
+ login_required => 410,
+
+ # User errors are 500-600.
+ account_exists => 500,
+ illegal_email_address => 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.
+ patch_too_large => 600,
+ local_file_too_large => 600,
+ file_too_large => 600,
+ invalid_content_type => 601,
+ attachment_illegal_url => 602,
+ file_not_specified => 603,
+ missing_attachment_description => 604,
+ attachment_url_disabled => 605,
+ zero_length_file => 606,
+
+ # 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,
+};
+
+# 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;
+
+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',
+ 'User' => 'Bugzilla::WebService::User',
+ 'Product' => 'Bugzilla::WebService::Product',
+ %hook_dispatch
+ };
+ return $dispatch;
+};
+
+1;
diff --git a/Bugzilla/WebService/Product.pm b/Bugzilla/WebService/Product.pm
new file mode 100644
index 000000000..b25226a45
--- /dev/null
+++ b/Bugzilla/WebService/Product.pm
@@ -0,0 +1,200 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s): Marc Schumann <wurblzap@gmail.com>
+# Mads Bondo Dydensborg <mbd@dbc.dk>
+
+package Bugzilla::WebService::Product;
+
+use strict;
+use base qw(Bugzilla::WebService);
+use Bugzilla::Product;
+use Bugzilla::User;
+use Bugzilla::WebService::Util qw(validate);
+
+use constant READ_ONLY => qw(
+ get
+ get_accessible_products
+ get_enterable_products
+ get_selectable_products
+);
+
+##################################################
+# Add aliases here for method name compatibility #
+##################################################
+
+BEGIN { *get_products = \&get }
+
+# Get the ids of the products the user can search
+sub get_selectable_products {
+ return {ids => [map {$_->id} @{Bugzilla->user->get_selectable_products}]};
+}
+
+# Get the ids of the products the user can enter bugs against
+sub get_enterable_products {
+ return {ids => [map {$_->id} @{Bugzilla->user->get_enterable_products}]};
+}
+
+# Get the union of the products the user can search and enter bugs against.
+sub get_accessible_products {
+ return {ids => [map {$_->id} @{Bugzilla->user->get_accessible_products}]};
+}
+
+# Get a list of actual products, based on list of ids
+sub get {
+ my ($self, $params) = validate(@_, 'ids');
+
+ # Only products that are in the users accessible products,
+ # can be allowed to be returned
+ my $accessible_products = Bugzilla->user->get_accessible_products;
+
+ # Create a hash with the ids the user wants
+ my %ids = map { $_ => 1 } @{$params->{ids}};
+
+ # Return the intersection of this, by grepping the ids from
+ # accessible products.
+ my @requested_accessible = grep { $ids{$_->id} } @$accessible_products;
+
+ # Now create a result entry for each.
+ my @products =
+ map {{
+ internals => $_,
+ id => $self->type('int', $_->id),
+ name => $self->type('string', $_->name),
+ description => $self->type('string', $_->description),
+ }
+ } @requested_accessible;
+
+ return { products => \@products };
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Webservice::Product - The Product API
+
+=head1 DESCRIPTION
+
+This part of the Bugzilla API allows you to list the available Products and
+get information about them.
+
+=head1 METHODS
+
+See L<Bugzilla::WebService> for a description of how parameters are passed,
+and what B<STABLE>, B<UNSTABLE>, and B<EXPERIMENTAL> mean.
+
+=head1 List Products
+
+=head2 get_selectable_products
+
+B<EXPERIMENTAL>
+
+=over
+
+=item B<Description>
+
+Returns a list of the ids of the products the user can search on.
+
+=item B<Params> (none)
+
+=item B<Returns>
+
+A hash containing one item, C<ids>, that contains an array of product
+ids.
+
+=item B<Errors> (none)
+
+=back
+
+=head2 get_enterable_products
+
+B<EXPERIMENTAL>
+
+=over
+
+=item B<Description>
+
+Returns a list of the ids of the products the user can enter bugs
+against.
+
+=item B<Params> (none)
+
+=item B<Returns>
+
+A hash containing one item, C<ids>, that contains an array of product
+ids.
+
+=item B<Errors> (none)
+
+=back
+
+=head2 get_accessible_products
+
+B<UNSTABLE>
+
+=over
+
+=item B<Description>
+
+Returns a list of the ids of the products the user can search or enter
+bugs against.
+
+=item B<Params> (none)
+
+=item B<Returns>
+
+A hash containing one item, C<ids>, that contains an array of product
+ids.
+
+=item B<Errors> (none)
+
+=back
+
+=head2 get
+
+B<EXPERIMENTAL>
+
+=over
+
+=item B<Description>
+
+Returns a list of information about the products passed to it.
+
+Note: Can also be called as "get_products" for compatibilty with Bugzilla 3.0 API.
+
+=item B<Params>
+
+A hash containing one item, C<ids>, that is an array of product ids.
+
+=item B<Returns>
+
+A hash containing one item, C<products>, that is an array of
+hashes. Each hash describes a product, and has the following items:
+C<id>, C<name>, C<description>, and C<internals>. The C<id> item is
+the id of the product. The C<name> item is the name of the
+product. The C<description> is the description of the
+product. Finally, the C<internals> is an internal representation of
+the product.
+
+Note, that if the user tries to access a product that is not in the
+list of accessible products for the user, or a product that does not
+exist, that is silently ignored, and no information about that product
+is returned.
+
+=item B<Errors> (none)
+
+=back
diff --git a/Bugzilla/WebService/README b/Bugzilla/WebService/README
new file mode 100644
index 000000000..bbe320979
--- /dev/null
+++ b/Bugzilla/WebService/README
@@ -0,0 +1,18 @@
+The class structure of these files is a little strange, and this README
+explains it.
+
+Our goal is to make JSON::RPC and XMLRPC::Lite both work with the same code.
+(That is, we want to have one WebService API, and have two frontends for it.)
+
+The problem is that these both pass different things for $self to WebService
+methods.
+
+When XMLRPC::Lite calls a method, $self is the name of the *class* the
+method is in. For example, if we call Bugzilla.version(), the first argument
+is Bugzilla::WebService::Bugzilla. So in order to have $self
+(our first argument) act correctly in XML-RPC, we make all WebService
+classes use base qw(Bugzilla::WebService).
+
+When JSON::RPC calls a method, $self is the JSON-RPC *server object*. In other
+words, it's an instance of Bugzilla::WebService::Server::JSONRPC. So we have
+Bugzilla::WebService::Server::JSONRPC inherit from Bugzilla::WebService.
diff --git a/Bugzilla/WebService/Server.pm b/Bugzilla/WebService/Server.pm
new file mode 100644
index 000000000..4e0315219
--- /dev/null
+++ b/Bugzilla/WebService/Server.pm
@@ -0,0 +1,63 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s): Marc Schumann <wurblzap@gmail.com>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::WebService::Server;
+use strict;
+
+use Bugzilla::Error;
+use Bugzilla::Util qw(datetime_from);
+
+use Scalar::Util qw(blessed);
+
+sub handle_login {
+ my ($self, $class, $method, $full_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();
+}
+
+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
+}
+
+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();
+}
+
+1;
diff --git a/Bugzilla/WebService/Server/JSONRPC.pm b/Bugzilla/WebService/Server/JSONRPC.pm
new file mode 100644
index 000000000..8fe724080
--- /dev/null
+++ b/Bugzilla/WebService/Server/JSONRPC.pm
@@ -0,0 +1,559 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla JSON Webservices Interface.
+#
+# The Initial Developer of the Original Code is the San Jose State
+# University Foundation. Portions created by the Initial Developer
+# are Copyright (C) 2008 the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::WebService::Server::JSONRPC;
+
+use strict;
+use base qw(JSON::RPC::Server::CGI Bugzilla::WebService::Server);
+
+use Bugzilla::Error;
+use Bugzilla::WebService::Constants;
+use Bugzilla::WebService::Util qw(taint_data);
+use Bugzilla::Util qw(correct_urlbase trim disable_utf8);
+
+use HTTP::Message;
+use MIME::Base64 qw(decode_base64 encode_base64);
+
+#####################################
+# Public JSON::RPC Method Overrides #
+#####################################
+
+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;
+}
+
+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;
+}
+
+# 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(@_);
+}
+
+sub response {
+ my ($self, $response) = @_;
+
+ # Implement JSONP.
+ if (my $callback = $self->_bz_callback) {
+ my $content = $response->content;
+ $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);
+ }
+ }
+ my $cgi = $self->cgi;
+ 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
+# data structures as parameters. However, the JSON-RPC 2.0 "JSON-RPC over
+# HTTP" spec is excellent, so we are using that for GET requests, instead.
+# Spec: http://groups.google.com/group/json-rpc/web/json-rpc-over-http
+#
+# The one exception is that we don't require the "params" argument to be
+# 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;
+}
+
+#######################################
+# Bugzilla::WebService Implementation #
+#######################################
+
+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, '');
+ }
+
+ 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';
+}
+
+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);
+}
+
+######################################
+# Private JSON::RPC Method Overrides #
+######################################
+
+# 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};
+ return $self->SUPER::_handle(@_);
+}
+
+# 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;
+}
+
+# This handles dispatching our calls to the appropriate class based on
+# the name of the method.
+sub _find_procedure {
+ my $self = shift;
+
+ 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);
+
+ 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});
+ }
+ }
+
+ Bugzilla->input_params($params);
+
+ if ($self->request->method ne 'POST') {
+ # 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 });
+ }
+ }
+
+ # 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;
+}
+
+##########################
+# Private Custom Methods #
+##########################
+
+# _bz_method_name is stored by _find_procedure for later use.
+sub _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');
+ }
+ return $self->{_bz_callback};
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::WebService::Server::JSONRPC - The JSON-RPC Interface to Bugzilla
+
+=head1 DESCRIPTION
+
+This documentation describes things about the Bugzilla WebService that
+are specific to JSON-RPC. For a general overview of the Bugzilla WebServices,
+see L<Bugzilla::WebService>.
+
+Please note that I<everything> about this JSON-RPC interface is
+B<EXPERIMENTAL>. If you want a fully stable API, please use the
+C<Bugzilla::WebService::Server::XMLRPC|XML-RPC> interface.
+
+=head1 JSON-RPC
+
+Bugzilla supports both JSON-RPC 1.0 and 1.1. We recommend that you use
+JSON-RPC 1.0 instead of 1.1, though, because 1.1 is deprecated.
+
+At some point in the future, Bugzilla may also support JSON-RPC 2.0.
+
+The JSON-RPC standards are described at L<http://json-rpc.org/>.
+
+=head1 CONNECTING
+
+The endpoint for the JSON-RPC interface is the C<jsonrpc.cgi> script in
+your Bugzilla installation. For example, if your Bugzilla is at
+C<bugzilla.yourdomain.com>, then your JSON-RPC client would access the
+API via: C<http://bugzilla.yourdomain.com/jsonrpc.cgi>
+
+=head2 Connecting via GET
+
+The most powerful way to access the JSON-RPC interface is by HTTP POST.
+However, for convenience, you can also access certain methods by using GET
+(a normal webpage load). Methods that modify the database or cause some
+action to happen in Bugzilla cannot be called over GET. Only methods that
+simply return data can be used over GET.
+
+For security reasons, when you connect over GET, cookie authentication
+is not accepted. If you want to authenticate using GET, you have to
+use the C<Bugzilla_login> and C<Bugzilla_password> method described at
+L<Bugzilla::WebService/LOGGING IN>.
+
+To connect over GET, simply send the values that you'd normally send for
+each JSON-RPC argument as URL parameters, with the C<params> item being
+a JSON string.
+
+The simplest example is a call to C<Bugzilla.time>:
+
+ jsonrpc.cgi?method=Bugzilla.time
+
+Here's a call to C<User.get>, with several parameters:
+
+ jsonrpc.cgi?method=User.get&params=[ { "ids": [1,2], "names": ["user@domain.com"] } ]
+
+Although in reality you would url-encode the C<params> argument, so it would
+look more like this:
+
+ jsonrpc.cgi?method=User.get&params=%5B+%7B+%22ids%22%3A+%5B1%2C2%5D%2C+%22names%22%3A+%5B%22user%40domain.com%22%5D+%7D+%5D
+
+You can also specify C<version> as a URL parameter, if you want to specify
+what version of the JSON-RPC protocol you're using, and C<id> as a URL
+parameter if you want there to be a specific C<id> value in the returned
+JSON-RPC response.
+
+=head2 JSONP
+
+When calling the JSON-RPC WebService over GET, you can use the "JSONP"
+method of doing cross-domain requests, if you want to access the WebService
+directly on a web page from another site. JSONP is described at
+L<http://bob.pythonmac.org/archives/2005/12/05/remote-json-jsonp/>.
+
+To use JSONP with Bugzilla's JSON-RPC WebService, simply specify a
+C<callback> parameter to jsonrpc.cgi when using it via GET as described above.
+For example, here's some HTML you could use to get the data from
+C<Bugzilla.time> on a remote website, using JSONP:
+
+ <script type="text/javascript"
+ src="http://bugzilla.example.com/jsonrpc.cgi?method=Bugzilla.time&amp;callback=foo">
+
+That would call the C<Bugzilla.time> method and pass its value to a function
+called C<foo> as the only argument. All the other URL parameters (such as
+C<params>, for passing in arguments to methods) that can be passed to
+C<jsonrpc.cgi> during GET requests are also available, of course. The above
+is just the simplest possible example.
+
+The values returned when using JSONP are identical to the values returned
+when not using JSONP, so you will also get error messages if there is an
+error.
+
+The C<callback> URL parameter may only contain letters, numbers, periods, and
+the underscore (C<_>) character. Including any other characters will cause
+Bugzilla to throw an error. (This error will be a normal JSON-RPC response,
+not JSONP.)
+
+=head1 PARAMETERS
+
+For JSON-RPC 1.0, the very first parameter should be an object containing
+the named parameters. For example, if you were passing two named parameters,
+one called C<foo> and the other called C<bar>, the C<params> element of
+your JSON-RPC call would look like:
+
+ "params": [{ "foo": 1, "bar": "something" }]
+
+For JSON-RPC 1.1, you can pass parameters either in the above fashion
+or using the standard named-parameters mechanism of JSON-RPC 1.1.
+
+C<dateTime> fields are strings in the standard ISO-8601 format:
+C<YYYY-MM-DDTHH:MM:SSZ>, where C<T> and C<Z> are a literal T and Z,
+respectively. The "Z" means that all times are in UTC timezone--times are
+always returned in UTC, and should be passed in as UTC. (Note: The JSON-RPC
+interface currently also accepts non-UTC times for any values passed in, if
+they include a time-zone specifier that follows the ISO-8601 standard, instead
+of "Z" at the end. This behavior is expected to continue into the future, but
+to be fully safe for forward-compatibility with all future versions of
+Bugzilla, it is safest to pass in all times as UTC with the "Z" timezone
+specifier.)
+
+C<base64> fields are strings that have been base64 encoded. Note that
+although normal base64 encoding includes newlines to break up the data,
+newlines within a string are not valid JSON, so you should not insert
+newlines into your base64-encoded string.
+
+All other types are standard JSON types.
+
+=head1 ERRORS
+
+JSON-RPC 1.0 and JSON-RPC 1.1 both return an C<error> element when they
+throw an error. In Bugzilla, the error contents look like:
+
+ { message: 'Some message here', code: 123 }
+
+So, for example, in JSON-RPC 1.0, an error response would look like:
+
+ {
+ result: null,
+ error: { message: 'Some message here', code: 123 },
+ id: 1
+ }
+
+Every error has a "code", as described in L<Bugzilla::WebService/ERRORS>.
+Errors with a numeric C<code> higher than 100000 are errors thrown by
+the JSON-RPC library that Bugzilla uses, not by Bugzilla.
+
+=head1 SEE ALSO
+
+L<Bugzilla::WebService>
diff --git a/Bugzilla/WebService/Server/XMLRPC.pm b/Bugzilla/WebService/Server/XMLRPC.pm
new file mode 100644
index 000000000..18757c0ec
--- /dev/null
+++ b/Bugzilla/WebService/Server/XMLRPC.pm
@@ -0,0 +1,347 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s): Marc Schumann <wurblzap@gmail.com>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+# Rosie Clarkson <rosie.clarkson@planningportal.gov.uk>
+#
+# Portions © Crown copyright 2009 - Rosie Clarkson (development@planningportal.gov.uk) for the Planning Portal
+
+package Bugzilla::WebService::Server::XMLRPC;
+
+use strict;
+use XMLRPC::Transport::HTTP;
+use Bugzilla::WebService::Server;
+our @ISA = qw(XMLRPC::Transport::HTTP::CGI Bugzilla::WebService::Server);
+
+use Bugzilla::WebService::Constants;
+
+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;
+}
+
+sub make_response {
+ my $self = shift;
+
+ $self->SUPER::make_response(@_);
+
+ # XMLRPC::Transport::HTTP::CGI doesn't know about Bugzilla carrying around
+ # its cookies in Bugzilla::CGI, so we need to copy them over.
+ foreach (@{Bugzilla->cgi->{'Bugzilla_cookie_list'}}) {
+ $self->response->headers->push_header('Set-Cookie', $_);
+ }
+}
+
+sub handle_login {
+ my ($self, $classes, $action, $uri, $method) = @_;
+ my $class = $classes->{$uri};
+ my $full_method = $uri . "." . $method;
+ $self->SUPER::handle_login($class, $method, $full_method);
+ return;
+}
+
+1;
+
+# This exists to validate input parameters (which XMLRPC::Lite doesn't do)
+# and also, in some cases, to more-usefully decode them.
+package Bugzilla::XMLRPC::Deserializer;
+use strict;
+# We can't use "use base" because XMLRPC::Serializer doesn't return
+# a true value.
+use XMLRPC::Lite;
+our @ISA = qw(XMLRPC::Deserializer);
+
+use Bugzilla::Error;
+use Scalar::Util qw(tainted);
+
+sub deserialize {
+ 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';
+ 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);
+ }
+
+ 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;
+}
+
+1;
+
+package Bugzilla::XMLRPC::SOM;
+use strict;
+use XMLRPC::Lite;
+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 $params = $self->{bz_params_in};
+ return wantarray ? @$params : $params->[0];
+}
+
+1;
+
+# This package exists to fix a UTF-8 bug in SOAP::Lite.
+# See http://rt.cpan.org/Public/Bug/Display.html?id=32952.
+package Bugzilla::XMLRPC::Serializer;
+use Scalar::Util qw(blessed);
+use strict;
+# We can't use "use base" 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;
+}
+
+# Here the XMLRPC::Serializer is extended to use the XMLRPC nil extension.
+sub encode_object {
+ my $self = shift;
+ my @encoded = $self->SUPER::encode_object(@_);
+
+ 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'){
+ $data = _strip_undefs($data);
+ }
+ return $self->SUPER::envelope($type, $method, $data);
+}
+
+# In an XMLRPC response we have to handle hashes of arrays, hashes, scalars,
+# Bugzilla objects (reftype = 'HASH') and XMLRPC::Data objects.
+# 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) = @_;
+ if (ref $initial eq "HASH" || (blessed $initial && $initial->isa("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 {
+ $initial->{$key} = _strip_undefs($value);
+ }
+ }
+ }
+ if (ref $initial eq "ARRAY" || (blessed $initial && $initial->isa("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 {
+ $initial->[$count] = _strip_undefs($value);
+ }
+ }
+ }
+ return $initial;
+}
+
+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);
+ }
+ }
+ }
+}
+
+sub as_nil {
+ return ['nil', {}];
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::WebService::Server::XMLRPC - The XML-RPC Interface to Bugzilla
+
+=head1 DESCRIPTION
+
+This documentation describes things about the Bugzilla WebService that
+are specific to XML-RPC. For a general overview of the Bugzilla WebServices,
+see L<Bugzilla::WebService>.
+
+=head1 XML-RPC
+
+The XML-RPC standard is described here: L<http://www.xmlrpc.com/spec>
+
+=head1 CONNECTING
+
+The endpoint for the XML-RPC interface is the C<xmlrpc.cgi> script in
+your Bugzilla installation. For example, if your Bugzilla is at
+C<bugzilla.yourdomain.com>, then your XML-RPC client would access the
+API via: C<http://bugzilla.yourdomain.com/xmlrpc.cgi>
+
+=head1 PARAMETERS
+
+C<dateTime> fields are the standard C<dateTime.iso8601> XML-RPC field. They
+should be in C<YYYY-MM-DDTHH:MM:SS> format (where C<T> is a literal T). As
+of Bugzilla B<3.6>, Bugzilla always expects C<dateTime> fields to be in the
+UTC timezone, and all returned C<dateTime> values are in the UTC timezone.
+
+All other fields are standard XML-RPC types.
+
+=head2 How XML-RPC WebService Methods Take Parameters
+
+All functions take a single argument, a C<< <struct> >> that contains all parameters.
+The names of the parameters listed in the API docs for each function are the
+C<< <name> >> element for the struct C<< <member> >>s.
+
+=head1 EXTENSIONS TO THE XML-RPC STANDARD
+
+=head2 Undefined Values
+
+Normally, XML-RPC does not allow empty values for C<int>, C<double>, or
+C<dateTime.iso8601> fields. Bugzilla does--it treats empty values as
+C<undef> (called C<NULL> or C<None> in some programming languages).
+
+Bugzilla accepts a timezone specifier at the end of C<dateTime.iso8601>
+fields that are specified as method arguments. The format of the timezone
+specifier is specified in the ISO-8601 standard. If no timezone specifier
+is included, the passed-in time is assumed to be in the UTC timezone.
+Bugzilla will never output a timezone specifier on returned data, because
+doing so would violate the XML-RPC specification. All returned times are in
+the UTC timezone.
+
+Bugzilla also accepts an element called C<< <nil> >>, as specified by the
+XML-RPC extension here: L<http://ontosys.com/xml-rpc/extensions.php>, which
+is always considered to be C<undef>, no matter what it contains.
+
+Bugzilla does not use C<< <nil> >> values in returned data, because currently
+most clients do not support C<< <nil> >>. Instead, any fields with C<undef>
+values will be stripped from the response completely. Therefore
+B<the client must handle the fact that some expected fields may not be
+returned>.
+
+=begin private
+
+nil is implemented by XMLRPC::Lite, in XMLRPC::Deserializer::decode_value
+in the CPAN SVN since 14th Dec 2008
+L<http://rt.cpan.org/Public/Bug/Display.html?id=20569> and in Fedora's
+perl-SOAP-Lite package in versions 0.68-1 and above.
+
+=end private
+
+=head1 SEE ALSO
+
+L<Bugzilla::WebService>
diff --git a/Bugzilla/WebService/User.pm b/Bugzilla/WebService/User.pm
new file mode 100644
index 000000000..9f118d4a6
--- /dev/null
+++ b/Bugzilla/WebService/User.pm
@@ -0,0 +1,631 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s): Marc Schumann <wurblzap@gmail.com>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+# Mads Bondo Dydensborg <mbd@dbc.dk>
+# Noura Elhawary <nelhawar@redhat.com>
+
+package Bugzilla::WebService::User;
+
+use strict;
+use base qw(Bugzilla::WebService);
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Group;
+use Bugzilla::User;
+use Bugzilla::Util qw(trim);
+use Bugzilla::Token;
+use Bugzilla::WebService::Util qw(filter validate);
+
+# Don't need auth to login
+use constant LOGIN_EXEMPT => {
+ login => 1,
+ offer_account_by_email => 1,
+};
+
+use constant READ_ONLY => qw(
+ get
+);
+
+##############
+# User Login #
+##############
+
+sub login {
+ my ($self, $params) = @_;
+ my $remember = $params->{remember};
+
+ # Username and password params are required
+ foreach my $param ("login", "password") {
+ defined $params->{$param}
+ || ThrowCodeError('param_required', { param => $param });
+ }
+
+ # Convert $remember from a boolean 0/1 value to a CGI-compatible one.
+ if (defined($remember)) {
+ $remember = $remember? 'on': '';
+ }
+ else {
+ # Use Bugzilla's default if $remember is not supplied.
+ $remember =
+ Bugzilla->params->{'rememberlogin'} eq 'defaulton'? 'on': '';
+ }
+
+ # Make sure the CGI user info class works if necessary.
+ my $input_params = Bugzilla->input_params;
+ $input_params->{'Bugzilla_login'} = $params->{login};
+ $input_params->{'Bugzilla_password'} = $params->{password};
+ $input_params->{'Bugzilla_remember'} = $remember;
+
+ Bugzilla->login();
+ return { id => $self->type('int', Bugzilla->user->id) };
+}
+
+sub logout {
+ my $self = shift;
+ Bugzilla->logout;
+ return undef;
+}
+
+#################
+# User Creation #
+#################
+
+sub offer_account_by_email {
+ my $self = shift;
+ my ($params) = @_;
+ my $email = trim($params->{email})
+ || ThrowCodeError('param_required', { param => 'email' });
+
+ my $createexp = Bugzilla->params->{'createemailregexp'};
+ if (!$createexp) {
+ ThrowUserError("account_creation_disabled");
+ }
+ elsif ($email !~ /$createexp/) {
+ ThrowUserError("account_creation_restricted");
+ }
+
+ $email = Bugzilla::User->check_login_name_for_creation($email);
+
+ # Create and send a token for this new account.
+ Bugzilla::Token::issue_new_user_account_token($email);
+
+ return undef;
+}
+
+sub create {
+ my $self = shift;
+ my ($params) = @_;
+
+ Bugzilla->user->in_group('editusers')
+ || ThrowUserError("auth_failure", { group => "editusers",
+ action => "add",
+ object => "users"});
+
+ my $email = trim($params->{email})
+ || ThrowCodeError('param_required', { param => 'email' });
+ my $realname = trim($params->{full_name});
+ my $password = trim($params->{password}) || '*';
+
+ my $user = Bugzilla::User->create({
+ login_name => $email,
+ realname => $realname,
+ cryptpassword => $password
+ });
+
+ return { id => $self->type('int', $user->id) };
+}
+
+
+# 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],
+# names => ['testusera@redhat.com', 'testuserb@redhat.com'] });
+sub get {
+ my ($self, $params) = validate(@_, 'names', 'ids');
+
+ 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('string', $_->login),
+ }} @$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 he 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;
+ if ($params->{'maxusermatches'}) {
+ $limit = $params->{'maxusermatches'} + 1;
+ }
+ 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);
+ if (Bugzilla->user->in_group('editusers')) {
+ @users =
+ map {filter $params, {
+ 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_disabled ? 0 : 1),
+ email_enabled => $self->type('boolean', $_->email_enabled),
+ login_denied_text => $self->type('string', $_->disabledtext),
+ }} @$in_group;
+
+ }
+ else {
+ @users =
+ map {filter $params, {
+ 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_disabled ? 0 : 1),
+ }} @$in_group;
+ }
+
+ return { users => \@users };
+}
+
+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 @groups = map { Bugzilla::Group->check({ id => $_ }) }
+ @{ $group_ids || [] };
+ my @name_groups = map { Bugzilla::Group->check($_) }
+ @{ $group_names || [] };
+ push(@groups, @name_groups);
+
+
+ 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;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Webservice::User - The User Account and Login API
+
+=head1 DESCRIPTION
+
+This part of the Bugzilla API allows you to create User Accounts and
+log in/out using an existing account.
+
+=head1 METHODS
+
+See L<Bugzilla::WebService> for a description of how parameters are passed,
+and what B<STABLE>, B<UNSTABLE>, and B<EXPERIMENTAL> mean.
+
+=head1 Logging In and Out
+
+=head2 login
+
+B<STABLE>
+
+=over
+
+=item B<Description>
+
+Logging in, with a username and password, is required for many
+Bugzilla installations, in order to search for bugs, post new bugs,
+etc. This method logs in an user.
+
+=item B<Params>
+
+=over
+
+=item C<login> (string) - The user's login name.
+
+=item C<password> (string) - The user's password.
+
+=item C<remember> (bool) B<Optional> - if the cookies returned by the
+call to login should expire with the session or not. In order for
+this option to have effect the Bugzilla server must be configured to
+allow the user to set this option - the Bugzilla parameter
+I<rememberlogin> must be set to "defaulton" or
+"defaultoff". Addionally, the client application must implement
+management of cookies across sessions.
+
+=back
+
+=item B<Returns>
+
+On success, a hash containing one item, C<id>, the numeric id of the
+user that was logged in. A set of http cookies is also sent with the
+response. These cookies must be sent along with any future requests
+to the webservice, for the duration of the session.
+
+=item B<Errors>
+
+=over
+
+=item 300 (Invalid Username or Password)
+
+The username does not exist, or the password is wrong.
+
+=item 301 (Account Disabled)
+
+The account has been disabled. A reason may be specified with the
+error.
+
+=item 305 (New Password Required)
+
+The current password is correct, but the user is asked to change
+his password.
+
+=item 50 (Param Required)
+
+A login or password parameter was not provided.
+
+=back
+
+=back
+
+=head2 logout
+
+B<STABLE>
+
+=over
+
+=item B<Description>
+
+Log out the user. Does nothing if there is no user logged in.
+
+=item B<Params> (none)
+
+=item B<Returns> (nothing)
+
+=item B<Errors> (none)
+
+=back
+
+=head1 Account Creation
+
+=head2 offer_account_by_email
+
+B<STABLE>
+
+=over
+
+=item B<Description>
+
+Sends an email to the user, offering to create an account. The user
+will have to click on a URL in the email, and choose their password
+and real name.
+
+This is the recommended way to create a Bugzilla account.
+
+=item B<Param>
+
+=over
+
+=item C<email> (string) - the email to send the offer to.
+
+=back
+
+=item B<Returns> (nothing)
+
+=item B<Errors>
+
+=over
+
+=item 500 (Illegal Email Address)
+
+This Bugzilla does not allow you to create accounts with the format of
+email address you specified. Account creation may be entirely disabled.
+
+=item 501 (Account Already Exists)
+
+An account with that email address already exists in Bugzilla.
+
+=back
+
+=back
+
+=head2 create
+
+B<STABLE>
+
+=over
+
+=item B<Description>
+
+Creates a user account directly in Bugzilla, password and all.
+Instead of this, you should use L</offer_account_by_email> when
+possible, because that makes sure that the email address specified can
+actually receive an email. This function does not check that.
+
+You must be logged in and have the C<editusers> privilege in order to
+call this function.
+
+=item B<Params>
+
+=over
+
+=item C<email> (string) - The email address for the new user.
+
+=item C<full_name> (string) B<Optional> - The user's full name. Will
+be set to empty if not specified.
+
+=item C<password> (string) B<Optional> - The password for the new user
+account, in plain text. It will be stripped of leading and trailing
+whitespace. If blank or not specified, the newly created account will
+exist in Bugzilla, but will not be allowed to log in using DB
+authentication until a password is set either by the user (through
+resetting their password) or by the administrator.
+
+=back
+
+=item B<Returns>
+
+A hash containing one item, C<id>, the numeric id of the user that was
+created.
+
+=item B<Errors>
+
+The same as L</offer_account_by_email>. If a password is specified,
+the function may also throw:
+
+=over
+
+=item 502 (Password Too Short)
+
+The password specified is too short. (Usually, this means the
+password is under three characters.)
+
+=back
+
+=item B<History>
+
+=over
+
+=item Error 503 (Password Too Long) removed in Bugzilla B<3.6>.
+
+=back
+
+=back
+
+=head1 User Info
+
+=head2 get
+
+B<STABLE>
+
+=over
+
+=item B<Description>
+
+Gets information about user accounts in Bugzilla.
+
+=item B<Params>
+
+B<Note>: At least one of C<ids>, C<names>, or C<match> must be specified.
+
+B<Note>: Users will not be returned more than once, so even if a user
+is matched by more than one argument, only one user will be returned.
+
+In addition to the parameters below, this method also accepts the
+standard L<include_fields|Bugzilla::WebService/include_fields> and
+L<exclude_fields|Bugzilla::WebService/exclude_fields> arguments.
+
+=over
+
+=item C<ids> (array)
+
+An array of integers, representing user ids.
+
+Logged-out users cannot pass this parameter to this function. If they try,
+they will get an error. Logged-in users will get an error if they specify
+the id of a user they cannot see.
+
+=item C<names> (array)
+
+An array of login names (strings).
+
+=item C<match> (array)
+
+An array of strings. This works just like "user matching" in
+Bugzilla itself. Users will be returned whose real name or login name
+contains any one of the specified strings. Users that you cannot see will
+not be included in the returned list.
+
+Some Bugzilla installations have user-matching turned off, in which
+case you will only be returned exact matches.
+
+Most installations have a limit on how many matches are returned for
+each string, which defaults to 1000 but can be changed by the Bugzilla
+administrator.
+
+Logged-out users cannot use this argument, and an error will be thrown
+if they try. (This is to make it harder for spammers to harvest email
+addresses from Bugzilla, and also to enforce the user visibility
+restrictions that are implemented on some Bugzillas.)
+
+=item C<group_ids> (array)
+
+=item C<groups> (array)
+
+C<group_ids> is an array of numeric ids for groups that a user can be in.
+C<groups> is an array of names of groups that a user can be in.
+If these are specified, they limit the return value to users who are
+in I<any> of the groups specified.
+
+=item C<include_disabled> (boolean)
+
+By default, when using the C<match> parameter, disabled users are excluded
+from the returned results unless their full username is identical to the
+match string. Setting C<include_disabled> to C<true> will include disabled
+users in the returned results even if their username doesn't fully match
+the input string.
+
+=back
+
+=item B<Returns>
+
+A hash containing one item, C<users>, that is an array of
+hashes. Each hash describes a user, and has the following items:
+
+=over
+
+=item id
+
+C<int> The unique integer ID that Bugzilla uses to represent this user.
+Even if the user's login name changes, this will not change.
+
+=item real_name
+
+C<string> The actual name of the user. May be blank.
+
+=item email
+
+C<string> The email address of the user.
+
+=item name
+
+C<string> The login name of the user. Note that in some situations this is
+different than their email.
+
+=item can_login
+
+C<boolean> A boolean value to indicate if the user can login into bugzilla.
+
+=item email_enabled
+
+C<boolean> A boolean value to indicate if bug-related mail will be sent
+to the user or not.
+
+=item login_denied_text
+
+C<string> A text field that holds the reason for disabling a user from logging
+into bugzilla, if empty then the user account is enabled. Otherwise it is
+disabled/closed.
+
+B<Note>: If you are not logged in to Bugzilla when you call this function, you
+will only be returned the C<id>, C<name>, and C<real_name> items. If you are
+logged in and not in editusers group, you will only be returned the C<id>, C<name>,
+C<real_name>, C<email>, and C<can_login> items.
+
+=back
+
+=item B<Errors>
+
+=over
+
+=item 51 (Bad Login Name or Group Name)
+
+You passed an invalid login name in the "names" array or a bad
+group name/id in the C<groups>/C<group_ids> arguments.
+
+=item 304 (Authorization Required)
+
+You are logged in, but you are not authorized to see one of the users you
+wanted to get information about by user id.
+
+=item 505 (User Access By Id or User-Matching Denied)
+
+Logged-out users cannot use the "ids" or "match" arguments to this
+function.
+
+=back
+
+=item B<History>
+
+=over
+
+=item Added in Bugzilla B<3.4>.
+
+=item C<group_ids> and C<groups> were added in Bugzilla B<4.0>.
+
+=item C<include_disabled> added in Bugzilla B<4.0>. Default behavior
+for C<match> has changed to only returning enabled accounts.
+
+=back
+
+=back
diff --git a/Bugzilla/WebService/Util.pm b/Bugzilla/WebService/Util.pm
new file mode 100644
index 000000000..adb7fb43a
--- /dev/null
+++ b/Bugzilla/WebService/Util.pm
@@ -0,0 +1,149 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by the Initial Developer are Copyright (C) 2008
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::WebService::Util;
+use strict;
+use base qw(Exporter);
+
+# We have to "require", not "use" this, because otherwise it tries to
+# use features of Test::More during import().
+require Test::Taint;
+
+our @EXPORT_OK = qw(
+ filter
+ filter_wants
+ taint_data
+ validate
+);
+
+sub filter ($$) {
+ my ($params, $hash) = @_;
+ my %newhash = %$hash;
+
+ foreach my $key (keys %$hash) {
+ delete $newhash{$key} if !filter_wants($params, $key);
+ }
+
+ return \%newhash;
+}
+
+sub filter_wants ($$) {
+ my ($params, $field) = @_;
+ my %include = map { $_ => 1 } @{ $params->{'include_fields'} || [] };
+ my %exclude = map { $_ => 1 } @{ $params->{'exclude_fields'} || [] };
+
+ if (defined $params->{include_fields}) {
+ return 0 if !$include{$field};
+ }
+ if (defined $params->{exclude_fields}) {
+ return 0 if $exclude{$field};
+ }
+
+ return 1;
+}
+
+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);
+}
+
+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+, so we delete any key that doesn't match that.
+ if ($key !~ /^\w+$/) {
+ delete $item->{$key};
+ }
+ }
+ }
+ 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);
+
+ # 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} = ref $params->{$key}
+ ? $params->{$key}
+ : [ $params->{$key} ];
+ }
+ }
+
+ return ($self, $params);
+}
+
+__END__
+
+=head1 NAME
+
+Bugzilla::WebService::Util - Utility functions used inside of the WebService
+code. These are B<not> functions that can be called via the WebService.
+
+=head1 DESCRIPTION
+
+This is somewhat like L<Bugzilla::Util>, but these functions are only used
+internally in the WebService code.
+
+=head1 SYNOPSIS
+
+ filter({ include_fields => ['id', 'name'],
+ exclude_fields => ['name'] }, $hash);
+ my $wants = filter_wants $params, 'field_name';
+ validate(@_, 'ids');
+
+=head1 METHODS
+
+=head2 filter
+
+This helps implement the C<include_fields> and C<exclude_fields> arguments
+of WebService methods. Given a hash (the second argument to this subroutine),
+this will remove any keys that are I<not> in C<include_fields> and then remove
+any keys that I<are> in C<exclude_fields>.
+
+=head2 filter_wants
+
+Returns C<1> if a filter would preserve the specified field when passing
+a hash to L</filter>, C<0> otherwise.
+
+=head2 validate
+
+This helps in the validation of parameters passed into the WebSerice
+methods. Currently it converts listed parameters into an array reference
+if the client only passed a single scalar value. It modifies the parameters
+hash in place so other parameters should be unaltered.
diff --git a/Bugzilla/Whine.pm b/Bugzilla/Whine.pm
new file mode 100644
index 000000000..73b0802b1
--- /dev/null
+++ b/Bugzilla/Whine.pm
@@ -0,0 +1,132 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Eric Black.
+# Portions created by the Initial Developer are Copyright (C) 2010
+# Eric Black. All Rights Reserved.
+#
+# Contributor(s): Eric Black <black.eric@gmail.com>
+
+use strict;
+
+package Bugzilla::Whine;
+
+use base qw(Bugzilla::Object);
+
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::User;
+use Bugzilla::Util;
+use Bugzilla::Whine::Schedule;
+use Bugzilla::Whine::Query;
+
+#############
+# Constants #
+#############
+
+use constant DB_TABLE => 'whine_events';
+
+use constant DB_COLUMNS => qw(
+ id
+ owner_userid
+ subject
+ body
+ mailifnobugs
+);
+
+use constant LIST_ORDER => 'id';
+
+####################
+# Simple Accessors #
+####################
+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};
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Whine - A Whine event
+
+=head1 SYNOPSIS
+
+ use Bugzilla::Whine;
+
+ my $event = new Bugzilla::Whine($event_id);
+
+ my $subject = $event->subject;
+ my $body = $event->body;
+ my $mailifnobugs = $event->mail_if_no_bugs;
+ my $user = $event->user;
+
+=head1 DESCRIPTION
+
+This module exists to represent a whine event that has been
+saved to the database.
+
+This is an implementation of L<Bugzilla::Object>, and so has all the
+same methods available as L<Bugzilla::Object>, in addition to what is
+documented below.
+
+=head1 METHODS
+
+=head2 Constructors
+
+=over
+
+=item C<new>
+
+Does not accept a bare C<name> argument. Instead, accepts only an id.
+
+See also: L<Bugzilla::Object/new>.
+
+=back
+
+
+=head2 Accessors
+
+These return data about the object, without modifying the object.
+
+=over
+
+=item C<subject>
+
+Returns the subject of the whine event.
+
+=item C<body>
+
+Returns the body of the whine event.
+
+=item C<mail_if_no_bugs>
+
+Returns a numeric 1(C<true>) or 0(C<false>) to represent whether this
+whine event object is supposed to be mailed even if there are no bugs
+returned by the query.
+
+=item C<user>
+
+Returns the L<Bugzilla::User> object for the owner of the L<Bugzilla::Whine>
+event.
+
+=back
diff --git a/Bugzilla/Whine/Query.pm b/Bugzilla/Whine/Query.pm
new file mode 100644
index 000000000..11215759b
--- /dev/null
+++ b/Bugzilla/Whine/Query.pm
@@ -0,0 +1,136 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Eric Black.
+# Portions created by the Initial Developer are Copyright (C) 2009
+# Eric Black. All Rights Reserved.
+#
+# Contributor(s): Eric Black <black.eric@gmail.com>
+
+package Bugzilla::Whine::Query;
+
+use strict;
+
+use base qw(Bugzilla::Object);
+
+use Bugzilla::Constants;
+use Bugzilla::Search::Saved;
+
+#############
+# Constants #
+#############
+
+use constant DB_TABLE => 'whine_queries';
+
+use constant DB_COLUMNS => qw(
+ id
+ eventid
+ query_name
+ sortkey
+ onemailperbug
+ title
+);
+
+use constant NAME_FIELD => 'id';
+use constant LIST_ORDER => 'sortkey';
+
+####################
+# Simple Accessors #
+####################
+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'}; }
+
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Whine::Query - A query object used by L<Bugzilla::Whine>.
+
+=head1 SYNOPSIS
+
+ use Bugzilla::Whine::Query;
+
+ my $query = new Bugzilla::Whine::Query($id);
+
+ my $event_id = $query->eventid;
+ my $id = $query->id;
+ my $query_name = $query->name;
+ my $sortkey = $query->sortkey;
+ my $one_email_per_bug = $query->one_email_per_bug;
+ my $title = $query->title;
+
+=head1 DESCRIPTION
+
+This module exists to represent a query for a L<Bugzilla::Whine::Event>.
+Each event, which are groups of schedules and queries based on how the
+user configured the event, may have zero or more queries associated
+with it. Additionally, the queries are selected from the user's saved
+searches, or L<Bugzilla::Search::Saved> object with a matching C<name>
+attribute for the user.
+
+This is an implementation of L<Bugzilla::Object>, and so has all the
+same methods available as L<Bugzilla::Object>, in addition to what is
+documented below.
+
+=head1 METHODS
+
+=head2 Constructors
+
+=over
+
+=item C<new>
+
+Does not accept a bare C<name> argument. Instead, accepts only an id.
+
+See also: L<Bugzilla::Object/new>.
+
+=back
+
+
+=head2 Accessors
+
+These return data about the object, without modifying the object.
+
+=over
+
+=item C<event_id>
+
+The L<Bugzilla::Whine::Event> object id for this object.
+
+=item C<name>
+
+The L<Bugzilla::Search::Saved> query object name for this object.
+
+=item C<sortkey>
+
+The relational sorting key as compared with other L<Bugzilla::Whine::Query>
+objects.
+
+=item C<one_email_per_bug>
+
+Returns a numeric 1(C<true>) or 0(C<false>) to represent whether this
+L<Bugzilla::Whine::Query> object is supposed to be mailed as a list of
+bugs or one email per bug.
+
+=item C<title>
+
+The title of this object as it appears in the user forms and emails.
+
+=back
diff --git a/Bugzilla/Whine/Schedule.pm b/Bugzilla/Whine/Schedule.pm
new file mode 100644
index 000000000..63148856c
--- /dev/null
+++ b/Bugzilla/Whine/Schedule.pm
@@ -0,0 +1,170 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Eric Black.
+# Portions created by the Initial Developer are Copyright (C) 2009
+# Eric Black. All Rights Reserved.
+#
+# Contributor(s): Eric Black <black.eric@gmail.com>
+
+use strict;
+
+package Bugzilla::Whine::Schedule;
+
+use base qw(Bugzilla::Object);
+
+use Bugzilla::Constants;
+
+#############
+# Constants #
+#############
+
+use constant DB_TABLE => 'whine_schedules';
+
+use constant DB_COLUMNS => qw(
+ 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
+);
+use constant NAME_FIELD => 'id';
+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 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};
+}
+
+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;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Whine::Schedule - A schedule object used by L<Bugzilla::Whine>.
+
+=head1 SYNOPSIS
+
+ use Bugzilla::Whine::Schedule;
+
+ my $schedule = new Bugzilla::Whine::Schedule($schedule_id);
+
+ my $event_id = $schedule->eventid;
+ my $run_day = $schedule->run_day;
+ my $run_time = $schedule->run_time;
+ my $is_group = $schedule->mailto_is_group;
+ my $object = $schedule->mailto;
+ my $array_ref = $schedule->mailto_users;
+
+=head1 DESCRIPTION
+
+This module exists to represent a L<Bugzilla::Whine> event schedule.
+
+This is an implementation of L<Bugzilla::Object>, and so has all the
+same methods available as L<Bugzilla::Object>, in addition to what is
+documented below.
+
+=head1 METHODS
+
+=head2 Constructors
+
+=over
+
+=item C<new>
+
+Does not accept a bare C<name> argument. Instead, accepts only an id.
+
+See also: L<Bugzilla::Object/new>.
+
+=back
+
+
+=head2 Accessors
+
+These return data about the object, without modifying the object.
+
+=over
+
+=item C<event_id>
+
+The L<Bugzilla::Whine> event object id for this object.
+
+=item C<run_day>
+
+The day or day pattern that a L<Bugzilla::Whine> event is scheduled to run.
+
+=item C<run_time>
+
+The time or time pattern that a L<Bugzilla::Whine> event is scheduled to run.
+
+=item C<mailto_is_group>
+
+Returns a numeric 1 (C<group>) or 0 (C<user>) to represent whether
+L</mailto> is a group or user.
+
+=item C<mailto>
+
+This is either a L<Bugzilla::User> or L<Bugzilla::Group> object to represent
+the user or group this scheduled event is set to be mailed to.
+
+=item C<mailto_users>
+
+Returns an array reference of L<Bugzilla::User>s. This is derived from the
+L<Bugzilla::Group> stored in L</mailto> if L</mailto_is_group> is true and
+the group is still active, otherwise it will contain a single array element
+for the L<Bugzilla::User> in L</mailto>.
+
+=back
diff --git a/README b/README
new file mode 100644
index 000000000..041aebc13
--- /dev/null
+++ b/README
@@ -0,0 +1,92 @@
+What is Bugzilla?
+=================
+Bugzilla is a free bug-tracking system that is developed by an active
+community of volunteers in the Mozilla community. You can install and
+use it without having to pay any license fee.
+
+Minimum requirements
+====================
+It can be installed on Windows, Mac OS X, Linux and other Unix flavors.
+Bugzilla is written in Perl, meaning that Perl must be installed on your system.
+You will also need a web server as well as a DB server (see below).
+
+Installation & Upgrading
+========================
+The documentation to install, upgrade, configure and use Bugzilla can be found
+in different formats:
+* docs/en/html/Bugzilla-Guide.html (HTML version)
+* docs/en/txt/Bugzilla-Guide.txt (text version)
+* docs/en/pdf/Bugzilla-Guide.pdf (PDF version)
+
+If the documentation is missing, you can get it online by visiting
+http://www.bugzilla.org/docs/ from where you can select the documentation
+corresponding to the Bugzilla version you are installing.
+
+Bugzilla Quick Start Guide
+==========================
+(or, how to get Bugzilla up and running in 10 steps)
+Christian Reis <kiko@async.com.br>
+
+This express installation guide is for "normal" Bugzilla installations,
+which means a Linux or Unix system on which Apache, Perl, MySQL or PostgreSQL
+and a Sendmail compatible MTA are available. For other configurations, please
+see the "Installing Bugzilla" section of the Bugzilla Guide in the docs/ directory.
+
+1. Decide from which URL and directory under your webserver root you
+ will be serving the Bugzilla webpages.
+
+2. Unpack the distribution into the chosen directory (there is no copying or
+ installation involved).
+
+3. Run ./checksetup.pl, look for unsolved requirements, and install them.
+ You can run checksetup as many times as necessary to check if
+ everything required has been installed.
+
+ These will usually include assorted Perl modules, MySQL or PostgreSQL,
+ and a MTA.
+
+ After a successful dependency check, checksetup should complain that
+ localconfig needs to be edited.
+
+4. Edit the localconfig file, in particular the $webservergroup and
+ $db_* variables. In particular, $db_name and $db_user will define
+ your database setup in step 5.
+
+5. Create a user permission for the name supplied as $db_user with
+ read/write access to the database whose name is given by $db_name.
+
+ If you are not familiar with MySQL permissions, it's a good idea to
+ use the mysql_setpermission script that is installed with the MySQL
+ distribution, and be sure to read Bugzilla Security - MySQL section
+ in the Bugzilla Guide or PostgreSQL documentation.
+
+6. Run checksetup.pl once more; if all goes well, it should set up the
+ Bugzilla database for you. If not, return to step 5.
+
+ checksetup.pl should ask you, this time, for the administrator's
+ email address and password. These will be used for the initial
+ Bugzilla administrator account.
+
+7. Configure Apache (or install and configure, if you don't have it up
+ yet) to point to the Bugzilla directory. You can choose between
+ mod_cgi and mod_perl. The Bugzilla documentation has detailed information
+ for both modes.
+
+8. Visit the URL you chose for Bugzilla. Your browser should display the
+ default Bugzilla home page. You should then log in as the
+ administrator by following the "Log in" link and supplying the
+ account information you provided in step 6.
+
+9. Visit the "Parameters" page, as suggested by the page displayed to you.
+ Set up the relevant parameters for your local setup.
+
+10. That's it. If anything unexpected comes up:
+
+ - read the error message carefully,
+ - backtrack through the steps above,
+ - check the official installation guide.
+
+Support and installation questions should be directed to the
+support-bugzilla@lists.mozilla.org mailing list.
+
+Further support information is at http://www.bugzilla.org/support/
diff --git a/admin.cgi b/admin.cgi
new file mode 100755
index 000000000..83cc55d8b
--- /dev/null
+++ b/admin.cgi
@@ -0,0 +1,49 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Frédéric Buclin.
+# Portions created by Frédéric Buclin are Copyright (C) 2007
+# Frédéric Buclin. All Rights Reserved.
+#
+# Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
+
+use strict;
+
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Error;
+
+my $cgi = Bugzilla->cgi;
+my $template = Bugzilla->template;
+my $user = Bugzilla->login(LOGIN_REQUIRED);
+
+print $cgi->header();
+
+$user->in_group('admin')
+ || $user->in_group('tweakparams')
+ || $user->in_group('editusers')
+ || $user->can_bless
+ || (Bugzilla->params->{'useclassification'} && $user->in_group('editclassifications'))
+ || $user->in_group('editcomponents')
+ || scalar(@{$user->get_products_by_permission('editcomponents')})
+ || $user->in_group('creategroups')
+ || $user->in_group('editkeywords')
+ || $user->in_group('bz_canusewhines')
+ || 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
new file mode 100755
index 000000000..d1836ca92
--- /dev/null
+++ b/attachment.cgi
@@ -0,0 +1,761 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Myk Melez <myk@mozilla.org>
+# Daniel Raichle <draichle@gmx.net>
+# Dave Miller <justdave@syndicomm.com>
+# Alexander J. Vincent <ajvincent@juno.com>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+# Greg Hendricks <ghendricks@novell.com>
+# Frédéric Buclin <LpSolit@gmail.com>
+# Marc Schumann <wurblzap@gmail.com>
+# Byron Jones <bugzilla@glob.com.au>
+
+################################################################################
+# Script Initialization
+################################################################################
+
+# Make it harder for us to do dangerous things in Perl.
+use strict;
+
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::BugMail;
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Flag;
+use Bugzilla::FlagType;
+use Bugzilla::User;
+use Bugzilla::Util;
+use Bugzilla::Bug;
+use Bugzilla::Field;
+use Bugzilla::Attachment;
+use Bugzilla::Attachment::PatchReader;
+use Bugzilla::Token;
+use Bugzilla::Keyword;
+
+use Encode qw(encode);
+
+# 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 = {};
+
+################################################################################
+# Main Body Execution
+################################################################################
+
+# All calls to this script should contain an "action" variable whose
+# value determines what the user wants to do. The code below checks
+# the value of that variable and runs the appropriate code. If none is
+# supplied, we default to 'view'.
+
+# Determine whether to use the action specified by the user or the default.
+my $action = $cgi->param('action') || 'view';
+
+# You must use the appropriate urlbase/sslbase param when doing anything
+# but viewing an attachment.
+if ($action ne 'view') {
+ 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();
+}
+elsif ($action eq "interdiff")
+{
+ interdiff();
+}
+elsif ($action eq "diff")
+{
+ diff();
+}
+elsif ($action eq "viewall")
+{
+ viewall();
+}
+elsif ($action eq "enter")
+{
+ Bugzilla->login(LOGIN_REQUIRED);
+ enter();
+}
+elsif ($action eq "insert")
+{
+ Bugzilla->login(LOGIN_REQUIRED);
+ insert();
+}
+elsif ($action eq "edit")
+{
+ edit();
+}
+elsif ($action eq "update")
+{
+ Bugzilla->login(LOGIN_REQUIRED);
+ update();
+}
+elsif ($action eq "delete") {
+ delete_attachment();
+}
+else
+{
+ ThrowUserError('unknown_action', {action => $action});
+}
+
+exit;
+
+################################################################################
+# Data Validation / Security Authorization
+################################################################################
+
+# Validates an attachment ID. Optionally takes a parameter of a form
+# variable name that contains the ID to be validated. If not specified,
+# uses 'id'.
+# If the second parameter is true, the attachment ID will be validated,
+# however the current user's access to the attachment will not be checked.
+# Will throw an error if 1) attachment ID is not a valid number,
+# 2) attachment does not exist, or 3) user isn't allowed to access the
+# attachment.
+#
+# Returns an attachment object.
+
+sub validateID {
+ 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 => $cgi->param($param) });
+
+ # Make sure the attachment exists in the database.
+ my $attachment = new Bugzilla::Attachment($attach_id)
+ || 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($attachment->bug_id);
+ if ($attachment->isprivate && $user->id != $attachment->attacher->id
+ && !$user->is_insider)
+ {
+ ThrowUserError('auth_failure', {action => 'access',
+ object => 'attachment'});
+ }
+ 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;
+
+ 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);
+}
+
+# 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 => \@_ });
+ }
+
+ return $format;
+}
+
+# Validates context of a diff/interdiff. Will throw an error if the context
+# is not number, "file" or "patch". Returns the validated, detainted context.
+sub validateContext
+{
+ my $context = $cgi->param('context') || "patch";
+ if ($context ne "file" && $context ne "patch") {
+ detaint_natural($context)
+ || ThrowUserError("invalid_context", { context => $cgi->param('context') });
+ }
+
+ return $context;
+}
+
+################################################################################
+# Functions
+################################################################################
+
+# Display an attachment.
+sub view {
+ my $attachment;
+
+ if (use_attachbase()) {
+ $attachment = validateID(undef, 1);
+ my $path = 'attachment.cgi?id=' . $attachment->id;
+ # The user is allowed to override the content type of the attachment.
+ if (defined $cgi->param('content_type')) {
+ $path .= '&content_type=' . url_quote($cgi->param('content_type'));
+ }
+
+ # Make sure the attachment is served from the correct server.
+ my $bug_id = $attachment->bug_id;
+ 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 (!attachmentIsPublic($attachment)) {
+ my $token = $cgi->param('t');
+ my ($userid, undef, $token_attach_id) = Bugzilla::Token::GetTokenData($token);
+ unless ($userid
+ && detaint_natural($token_attach_id)
+ && ($token_attach_id == $attachment->id))
+ {
+ # 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;
+ }
+ 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 (attachmentIsPublic($attachment)) {
+ # 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.
+ check_can_access($attachment);
+ # Create a token and redirect.
+ my $token = url_quote(issue_session_token($attachment->id));
+ 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();
+ $attachment = validateID();
+ }
+
+ # 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'));
+ }
+
+ # 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
+
+ # 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);
+
+ my $disposition = Bugzilla->params->{'allow_attachment_display'} ? '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(' ');
+ }
+ print $cgi->header(-type=>"$contenttype; name=\"$filename\"",
+ -content_disposition=> "$disposition; filename=\"$filename\"",
+ -content_length => $attachment->datasize,
+ -x_content_type_options => "nosniff");
+ disable_utf8();
+ print $attachment->data;
+}
+
+sub interdiff {
+ # Retrieve and validate parameters
+ my $old_attachment = validateID('oldid');
+ my $new_attachment = validateID('newid');
+ my $format = validateFormat('html', 'raw');
+ my $context = validateContext();
+
+ Bugzilla::Attachment::PatchReader::process_interdiff(
+ $old_attachment, $new_attachment, $format, $context);
+}
+
+sub diff {
+ # Retrieve and validate parameters
+ my $attachment = validateID();
+ my $format = validateFormat('html', 'raw');
+ my $context = validateContext();
+
+ # If it is not a patch, view normally.
+ if (!$attachment->ispatch) {
+ view();
+ return;
+ }
+
+ Bugzilla::Attachment::PatchReader::process_diff($attachment, $format, $context);
+}
+
+# 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(scalar $cgi->param('bugid'));
+ my $bugid = $bug->id;
+
+ my $attachments = Bugzilla::Attachment->get_attachments_by_bug($bugid);
+ # Ignore deleted attachments.
+ @$attachments = grep { $_->datasize } @$attachments;
+
+ # 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 $canEdit = "";
+ if (!$user->in_group('editbugs', $bug->product_id)) {
+ $canEdit = "AND submitter_id = " . $user->id;
+ }
+ my $attach_ids = $dbh->selectcol_arrayref("SELECT attach_id FROM attachments
+ WHERE bug_id = ? AND isobsolete = 0 $canEdit
+ 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;
+
+ # 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'));
+ if ($token) {
+ my ($creator_id, $date, $old_attach_id) = Bugzilla::Token::GetTokenData($token);
+ unless ($creator_id
+ && ($creator_id == $user->id)
+ && ($old_attach_id =~ "^create_attachment:"))
+ {
+ # The token is invalid.
+ ThrowUserError('token_does_not_exist');
+ }
+
+ $old_attach_id =~ s/^create_attachment://;
+
+ if ($old_attach_id) {
+ $vars->{'bugid'} = $bugid;
+ $vars->{'attachid'} = $old_attach_id;
+ print $cgi->header();
+ $template->process("attachment/cancel-create-dupe.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
+ }
+ }
+
+ # 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);
+ }
+
+ # Must be called before create() as it may alter $cgi->param('ispatch').
+ my $content_type = Bugzilla::Attachment::get_content_type();
+
+ my $attachment = Bugzilla::Attachment->create(
+ {bug => $bug,
+ creation_ts => $timestamp,
+ data => scalar $cgi->param('attachurl') || $cgi->upload('data'),
+ description => scalar $cgi->param('description'),
+ filename => $cgi->param('attachurl') ? '' : scalar $cgi->upload('data'),
+ ispatch => scalar $cgi->param('ispatch'),
+ isprivate => scalar $cgi->param('isprivate'),
+ isurl => scalar $cgi->param('attachurl'),
+ mimetype => $content_type,
+ store_in_file => scalar $cgi->param('bigfile'),
+ });
+
+ foreach my $obsolete_attachment (@obsolete_attachments) {
+ $obsolete_attachment->set_is_obsolete(1);
+ $obsolete_attachment->update($timestamp);
+ }
+
+ 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);
+
+ # 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->update($timestamp);
+
+ if ($token) {
+ trick_taint($token);
+ $dbh->do('UPDATE tokens SET eventdata = ? WHERE token = ?', undef,
+ ("create_attachment:" . $attachment->id, $token));
+ }
+
+ $dbh->bz_commit_transaction;
+
+ # 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');
+
+ 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());
+}
+
+# Displays a form for editing attachment properties.
+# Any user is allowed to access this page, unless the attachment
+# 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 $bugattachments =
+ Bugzilla::Attachment->get_attachments_by_bug($attachment->bug_id);
+ # We only want attachment IDs.
+ @$bugattachments = map { $_->id } @$bugattachments;
+
+ 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;
+
+ print $cgi->header();
+
+ # 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($bug->product_id);
+
+ 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.
+ if (defined $cgi->param('delta_ts')
+ && $cgi->param('delta_ts') ne $attachment->modification_time)
+ {
+ ($vars->{'operations'}) =
+ Bugzilla::Bug::GetBugActivity($bug->id, $attachment->id, $cgi->param('delta_ts'));
+
+ # The token contains the old modification_time. We need a new one.
+ $cgi->param('token', issue_hash_token([$attachment->id, $attachment->modification_time]));
+
+ # 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', $attachment->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;
+ }
+ }
+ }
+
+ # 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 });
+ }
+
+ if ($can_edit) {
+ my ($flags, $new_flags) =
+ Bugzilla::Flag->extract_flags_from_cgi($bug, $attachment, $vars);
+ $attachment->set_flags($flags, $new_flags);
+ }
+
+ # Figure out when the changes were made.
+ my $timestamp = $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
+
+ 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);
+ }
+
+ # Commit the comment, if any.
+ $bug->update($timestamp);
+
+ # Commit the transaction now that we are finished updating the database.
+ $dbh->bz_commit_transaction();
+
+ # 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 });
+
+ 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;
+
+ 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');
+ }
+
+ my $bug = new Bugzilla::Bug($attachment->bug_id);
+
+ # The token is valid. Delete the content of the attachment.
+ my $msg;
+ $vars->{'attachment'} = $attachment;
+ $vars->{'date'} = $date;
+ $vars->{'reason'} = clean_text($cgi->param('reason') || '');
+
+ $template->process("attachment/delete_reason.txt.tmpl", $vars, \$msg)
+ || ThrowTemplateError($template->error());
+
+ # Paste the reason provided by the admin into a comment.
+ $bug->add_comment($msg);
+
+ # If the attachment is stored locally, remove it.
+ if (-e $attachment->_get_local_filename) {
+ unlink $attachment->_get_local_filename;
+ }
+ $attachment->remove_from_db();
+
+ # Now delete the token.
+ delete_token($token);
+
+ # Insert the comment.
+ $bug->update();
+
+ # 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
new file mode 100755
index 000000000..ba80bb035
--- /dev/null
+++ b/buglist.cgi
@@ -0,0 +1,1228 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Dan Mosedale <dmose@mozilla.org>
+# Stephan Niemz <st.n@gmx.net>
+# Andreas Franke <afranke@mathweb.org>
+# Myk Melez <myk@mozilla.org>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+################################################################################
+# Script Initialization
+################################################################################
+
+# Make it harder for us to do dangerous things in Perl.
+use strict;
+
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Util;
+use Bugzilla::Search;
+use Bugzilla::Search::Quicksearch;
+use Bugzilla::Search::Recent;
+use Bugzilla::Search::Saved;
+use Bugzilla::User;
+use Bugzilla::Bug;
+use Bugzilla::Product;
+use Bugzilla::Keyword;
+use Bugzilla::Field;
+use Bugzilla::Status;
+use Bugzilla::Token;
+
+use Date::Parse;
+
+my $cgi = Bugzilla->cgi;
+my $dbh = Bugzilla->dbh;
+my $template = Bugzilla->template;
+my $vars = {};
+my $buffer = $cgi->query_string();
+
+# 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'
+# is turned 'on'.
+my $user = Bugzilla->login();
+
+if (length($buffer) == 0) {
+ print $cgi->header(-refresh=> '10; URL=query.cgi');
+ ThrowUserError("buglist_parameters_required");
+}
+
+# If a parameter starts with cmd-, this means the And or Or button has been
+# pressed in the advanced search page with JS turned off.
+if (grep { $_ =~ /^cmd\-/ } $cgi->param()) {
+ my $url = "query.cgi?$buffer#chart";
+ print $cgi->redirect(-location => $url);
+ # Generate and return the UI (HTML page) from the appropriate template.
+ $vars->{'message'} = "buglist_adding_field";
+ $vars->{'url'} = $url;
+ $template->process("global/message.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
+}
+
+$cgi->redirect_search_url();
+
+# 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.
+}
+
+# If configured to not allow empty words, reject empty searches from the
+# Find a Specific Bug search form, including words being a single or
+# several consecutive whitespaces only.
+if (!Bugzilla->params->{'specific_search_allow_empty_words'}
+ && defined($cgi->param('content')) && $cgi->param('content') =~ /^\s*$/)
+{
+ ThrowUserError("buglist_parameters_required");
+}
+
+################################################################################
+# Data and Security Validation
+################################################################################
+
+# Whether or not the user wants to change multiple bugs.
+my $dotweak = $cgi->param('tweak') ? 1 : 0;
+
+# Log the user in
+if ($dotweak) {
+ 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');
+}
+
+# Treat requests for ctype=rss as requests for ctype=atom
+if (defined $cgi->param('ctype') && $cgi->param('ctype') eq "rss") {
+ $cgi->param('ctype', "atom");
+}
+
+# The js ctype presents a security risk; a malicious site could use it
+# to gather information about secure bugs. So, we only allow public bugs to be
+# retrieved with this format.
+#
+# Note that if and when this call clears cookies or has other persistent
+# effects, we'll need to do this another way instead.
+if ((defined $cgi->param('ctype')) && ($cgi->param('ctype') eq "js")) {
+ Bugzilla->logout_request();
+}
+
+# An agent is a program that automatically downloads and extracts data
+# on its user's behalf. If this request comes from an agent, we turn off
+# various aspects of bug list functionality so agent requests succeed
+# and coexist nicely with regular user requests. Currently the only agent
+# we know about is Firefox's microsummary feature.
+my $agent = ($cgi->http('X-Moz') && $cgi->http('X-Moz') =~ /\bmicrosummary\b/);
+
+# 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'));
+
+# 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
+# the bug list as HTML and they have not disabled it by adding &serverpush=0
+# to the URL.
+#
+# Server push is a Netscape 3+ hack incompatible with MSIE, Lynx, and others.
+# Even Communicator 4.51 has bugs with it, especially during page reload.
+# http://www.browsercaps.org used as source of compatible browsers.
+# Safari (WebKit) does not support it, despite a UA that says otherwise (bug 188712)
+# MSIE 5+ supports it on Mac (but not on Windows) (bug 190370)
+#
+my $serverpush =
+ $format->{'extension'} eq "html"
+ && exists $ENV{'HTTP_USER_AGENT'}
+ && $ENV{'HTTP_USER_AGENT'} =~ /Mozilla.[3-9]/
+ && (($ENV{'HTTP_USER_AGENT'} !~ /[Cc]ompatible/) || ($ENV{'HTTP_USER_AGENT'} =~ /MSIE 5.*Mac_PowerPC/))
+ && $ENV{'HTTP_USER_AGENT'} !~ /WebKit/
+ && !$agent
+ && !defined($cgi->param('serverpush'))
+ || $cgi->param('serverpush');
+
+my $order = $cgi->param('order') || "";
+
+# The params object to use for the actual query itself
+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, $order);
+
+ # Logged-out users use the old cookie method for storing the last search.
+ if (!$user->id or $last_list eq 'cookie') {
+ $cgi->cookie('BUGLIST') || ThrowUserError("missing_cookie");
+ $order = "reuse last sort" unless $order;
+ $bug_ids = $cgi->cookie('BUGLIST');
+ $bug_ids =~ s/[:-]/,/g;
+ }
+ # 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 if !$order;
+ }
+ # set up the params for this new query
+ $params = new Bugzilla::CGI({ bug_id => $bug_ids, order => $order });
+}
+
+# Figure out whether or not the user is doing a fulltext search. If not,
+# we'll remove the relevance column from the lists of columns to display
+# and order by, since relevance only exists when doing a fulltext search.
+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;
+ }
+}
+
+################################################################################
+# Utilities
+################################################################################
+
+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;
+}
+
+sub LookupNamedQuery {
+ my ($name, $sharer_id, $query_type, $throw_error) = @_;
+ $throw_error = 1 unless defined $throw_error;
+
+ Bugzilla->login(LOGIN_REQUIRED);
+
+ my $constructor = $throw_error ? 'check' : 'new';
+ my $query = Bugzilla::Search::Saved->$constructor(
+ { user => $sharer_id, name => $name });
+
+ return $query if (!$query and !$throw_error);
+
+ if (defined $query_type and $query->type != $query_type) {
+ ThrowUserError("missing_query", { queryname => $name,
+ sharer_id => $sharer_id });
+ }
+
+ $query->url
+ || ThrowUserError("buglist_parameters_required", { queryname => $name });
+
+ return wantarray ? ($query->url, $query->id) : $query->url;
+}
+
+# Inserts a Named Query (a "Saved Search") into the database, or
+# updates a Named Query that already exists..
+# Takes four arguments:
+# userid - The userid who the Named Query will belong to.
+# query_name - A string that names the new Named Query, or the name
+# of an old Named Query to update. If this is blank, we
+# 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
+# empty, or we will throw a UserError.
+# link_in_footer (optional) - 1 if the Named Query should be
+# displayed in the user's footer, 0 otherwise.
+# query_type (optional) - 1 if the Named Query contains a list of
+# bug IDs only, 0 otherwise (default).
+#
+# All parameters are validated before passing them into 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, $query_type) = @_;
+ 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->set_query_type($query_type);
+ $query_obj->update();
+ } else {
+ Bugzilla::Search::Saved->create({
+ name => $query_name,
+ query => $query,
+ query_type => $query_type,
+ 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;
+}
+
+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;
+}
+
+# Return groups available for at least one product of the buglist.
+sub GetGroups {
+ my $product_names = shift;
+ my $user = Bugzilla->user;
+ my %legal_groups;
+
+ foreach my $product_name (@$product_names) {
+ my $product = new Bugzilla::Product({name => $product_name});
+
+ foreach my $gid (keys %{$product->group_controls}) {
+ # The user can only edit groups he belongs to.
+ next unless $user->in_group_id($gid);
+
+ # 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];
+}
+
+sub _close_standby_message {
+ my ($contenttype, $disposition, $serverpush) = @_;
+ my $cgi = Bugzilla->cgi;
+
+ # Close the "please wait" page, then open the buglist page
+ if ($serverpush) {
+ print $cgi->multipart_end();
+ print $cgi->multipart_start(-type => $contenttype,
+ -content_disposition => $disposition);
+ }
+ else {
+ print $cgi->header(-type => $contenttype,
+ -content_disposition => $disposition);
+ }
+}
+
+
+################################################################################
+# Command Execution
+################################################################################
+
+my $cmdtype = $cgi->param('cmdtype') || '';
+my $remaction = $cgi->param('remaction') || '';
+
+# 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";
+}
+
+# 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
+# earlier, for example by setting up a named query search.
+
+# This will be modified, so make a copy.
+$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
+# forgotten in the process of retrieving it.
+my @time = localtime(time());
+my $date = sprintf "%04d-%02d-%02d", 1900+$time[5],$time[4]+1,$time[3];
+my $filename = "bugs-$date.$format->{extension}";
+if ($cmdtype eq "dorem" && $remaction =~ /^run/) {
+ $filename = $cgi->param('namedcmd') . "-$date.$format->{extension}";
+ # Remove white-space from the filename so the user cannot tamper
+ # with the HTTP headers.
+ $filename =~ s/\s/_/g;
+}
+$filename =~ s/\\/\\\\/g; # escape backslashes
+$filename =~ s/"/\\"/g; # escape quotes
+
+# Take appropriate action based on user's request.
+if ($cmdtype eq "dorem") {
+ if ($remaction eq "run") {
+ my $query_id;
+ ($buffer, $query_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') == Bugzilla->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;
+ }
+ 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
+ = whine_events.id
+ WHERE whine_events.owner_userid
+ = ?
+ 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) {
+ # The user has no query of this name. Play along.
+ }
+ else {
+ # Make sure the user really wants to delete his 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);
+ }
+
+ # 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);
+ $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);
+ 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 $query_type = QUERY_LIST;
+ my $token = $cgi->param('token');
+ check_hash_token($token, ['savedsearch']);
+ # If list_of_bugs is true, we are adding/removing individual bugs
+ # to a saved search. We get the existing list of bug IDs (if any)
+ # and add/remove the passed ones.
+ if ($cgi->param('list_of_bugs')) {
+ # We add or remove bugs based on the action choosen.
+ my $action = trim($cgi->param('action') || '');
+ $action =~ /^(add|remove)$/
+ || ThrowUserError('unknown_action', {action => $action});
+
+ # If we are removing bugs, then we must have an existing
+ # saved search selected.
+ if ($action eq 'remove') {
+ $query_name && ThrowUserError('no_bugs_to_remove');
+ }
+
+ my %bug_ids;
+ my $is_new_name = 0;
+ if ($query_name) {
+ my ($query, $query_id) =
+ LookupNamedQuery($query_name, undef, QUERY_LIST, !THROW_ERROR);
+ # Make sure this name is not already in use by a normal saved search.
+ if ($query) {
+ ThrowUserError('query_name_exists', {name => $query_name,
+ query_id => $query_id});
+ }
+ $is_new_name = 1;
+ }
+ # If no new tag name has been given, use the selected one.
+ $query_name ||= $cgi->param('oldqueryname');
+
+ # Don't throw an error if it's a new tag name: if the tag already
+ # exists, add/remove bugs to it, else create it. But if we are
+ # considering an existing tag, then it has to exist and we throw
+ # an error if it doesn't (hence the usage of !$is_new_name).
+ my ($old_query, $query_id) =
+ LookupNamedQuery($query_name, undef, LIST_OF_BUGS, !$is_new_name);
+
+ if ($old_query) {
+ # We get the encoded query. We need to decode it.
+ my $old_cgi = new Bugzilla::CGI($old_query);
+ foreach my $bug_id (split /[\s,]+/, scalar $old_cgi->param('bug_id')) {
+ $bug_ids{$bug_id} = 1 if detaint_natural($bug_id);
+ }
+ }
+
+ my $keep_bug = ($action eq 'add') ? 1 : 0;
+ my $changes = 0;
+ foreach my $bug_id (split(/[\s,]+/, $cgi->param('bug_ids'))) {
+ next unless $bug_id;
+ my $bug = Bugzilla::Bug->check($bug_id);
+ $bug_ids{$bug->id} = $keep_bug;
+ $changes = 1;
+ }
+ ThrowUserError('no_bug_ids',
+ {'action' => $action,
+ 'tag' => $query_name})
+ unless $changes;
+
+ # Only keep bug IDs we want to add/keep. Disregard deleted ones.
+ my @bug_ids = grep { $bug_ids{$_} == 1 } keys %bug_ids;
+ # If the list is now empty, we could as well delete it completely.
+ if (!scalar @bug_ids) {
+ ThrowUserError('no_bugs_in_list', {name => $query_name,
+ query_id => $query_id});
+ }
+ $new_query = "bug_id=" . join(',', sort {$a <=> $b} @bug_ids);
+ $query_type = LIST_OF_BUGS;
+ }
+ my $tofooter = 1;
+ my $existed_before = InsertNamedQuery($query_name, $new_query,
+ $tofooter, $query_type);
+ if ($existed_before) {
+ $vars->{'message'} = "buglist_updated_named_query";
+ }
+ else {
+ $vars->{'message'} = "buglist_new_named_query";
+ }
+
+ # Make sure to invalidate any cached query data, so that the footer is
+ # correctly displayed
+ $user->flush_queries_cache();
+
+ $vars->{'queryname'} = $query_name;
+
+ 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;
+}
+
+################################################################################
+# Column Definition
+################################################################################
+
+my $columns = Bugzilla::Search::COLUMNS;
+
+################################################################################
+# Display Column Determination
+################################################################################
+
+# 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'));
+ }
+}
+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);
+}
+else {
+ # 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
+# should not have access. Detaint the data along the way.
+@displaycolumns = grep($columns->{$_} && trick_taint($_), @displaycolumns);
+
+# Remove the "ID" column from the list because bug IDs are always displayed
+# and are hard-coded into the display templates.
+@displaycolumns = grep($_ ne 'bug_id', @displaycolumns);
+
+# 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 (!Bugzilla->user->is_timetracker) {
+ @displaycolumns = grep($_ ne 'estimated_time', @displaycolumns);
+ @displaycolumns = grep($_ ne 'remaining_time', @displaycolumns);
+ @displaycolumns = grep($_ ne 'actual_time', @displaycolumns);
+ @displaycolumns = grep($_ ne 'percentage_complete', @displaycolumns);
+ @displaycolumns = grep($_ ne 'deadline', @displaycolumns);
+}
+
+# Remove the relevance column if the user is not doing a fulltext search.
+if (grep('relevance', @displaycolumns) && !$fulltext) {
+ @displaycolumns = grep($_ ne 'relevance', @displaycolumns);
+}
+
+
+################################################################################
+# Select Column Determination
+################################################################################
+
+# Generate the list of columns that will be selected in the SQL query.
+
+# 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");
+
+# remaining and actual_time are required for percentage_complete calculation:
+if (grep { $_ eq "percentage_complete" } @displaycolumns) {
+ push (@selectcolumns, "remaining_time");
+ push (@selectcolumns, "actual_time");
+}
+
+# Make sure that the login_name version of a field is always also
+# requested if the realname version is requested, so that we can
+# 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);
+ }
+}
+
+# Display columns are selected because otherwise we could not display them.
+foreach my $col (@displaycolumns) {
+ push (@selectcolumns, $col) if !grep($_ eq $col, @selectcolumns);
+}
+
+# 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);
+}
+
+if ($format->{'extension'} eq 'ics') {
+ push(@selectcolumns, "opendate") if !grep($_ eq 'opendate', @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);
+ }
+}
+
+################################################################################
+# Sort Order Determination
+################################################################################
+
+# Add to the query some instructions for sorting the bug list.
+
+# 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 ($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 = "bug_id";
+ last ORDER;
+ };
+ /^Importance$/ && do {
+ $order = "priority,bug_severity";
+ last ORDER;
+ };
+ /^Assignee$/ && do {
+ $order = "assigned_to,bug_status,priority,bug_id";
+ last ORDER;
+ };
+ /^Last Changed$/ && do {
+ $order = "changeddate,bug_status,priority,assigned_to,bug_id";
+ last ORDER;
+ };
+ do {
+ my (@order, @invalid_fragments);
+
+ # A custom list of columns. Make sure each column is valid.
+ foreach my $fragment (split(/,/, $order)) {
+ $fragment = trim($fragment);
+ next unless $fragment;
+ my ($column_name, $direction) = split_order_term($fragment);
+ $column_name = translate_old_column($column_name);
+
+ # Special handlings for certain columns
+ next if $column_name eq 'relevance' && !$fulltext;
+
+ if (exists $columns->{$column_name}) {
+ $direction = " $direction" if $direction;
+ push(@order, "$column_name$direction");
+ }
+ else {
+ push(@invalid_fragments, $fragment);
+ }
+ }
+ if (scalar @invalid_fragments) {
+ $vars->{'message'} = 'invalid_column_name';
+ $vars->{'invalid_fragments'} = \@invalid_fragments;
+ }
+
+ $order = join(",", @order);
+ # Now that we have checked that all columns in the order are valid,
+ # detaint the order string.
+ trick_taint($order) if $order;
+ };
+ }
+}
+
+if (!$order) {
+ # DEFAULT
+ $order = "bug_status,priority,assigned_to,bug_id";
+}
+
+my @orderstrings = split(/,\s*/, $order);
+
+# The bug status defined by a specific search is of type __foo__, but
+# Search.pm converts it into a list of real bug statuses, which cannot
+# be used when editing the specific search again. So we restore this
+# parameter manually.
+my $input_bug_status;
+if ($params->param('query_format') eq 'specific') {
+ $input_bug_status = $params->param('bug_status');
+}
+
+# Generate the basic SQL query that will be used to generate the bug list.
+my $search = new Bugzilla::Search('fields' => \@selectcolumns,
+ 'params' => $params,
+ 'order' => \@orderstrings);
+my $query = $search->getSQL();
+$vars->{'search_description'} = $search->search_description;
+
+if (defined $cgi->param('limit')) {
+ my $limit = $cgi->param('limit');
+ if (detaint_natural($limit)) {
+ $query .= " " . $dbh->sql_limit($limit);
+ }
+}
+elsif ($fulltext) {
+ if ($cgi->param('order') && $cgi->param('order') =~ /^relevance/) {
+ $vars->{'message'} = 'buglist_sorted_by_relevance';
+ }
+}
+
+
+################################################################################
+# Query Execution
+################################################################################
+
+if ($cgi->param('debug')) {
+ $vars->{'debug'} = 1;
+ $vars->{'query'} = $query;
+ # 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 (Bugzilla->user->in_group('admin')) {
+ $vars->{'query_explain'} = $dbh->bz_explain($query);
+ }
+}
+
+# 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.
+}
+
+# Connect to the shadow database if this installation is using one to improve
+# query performance.
+$dbh = Bugzilla->switch_to_shadow_db();
+
+# Normally, we ignore SIGTERM and SIGPIPE, but we need to
+# respond to them here to prevent someone DOSing us by reloading a query
+# a large number of times.
+$::SIG{TERM} = 'DEFAULT';
+$::SIG{PIPE} = 'DEFAULT';
+
+# Execute the query.
+my $buglist_sth = $dbh->prepare($query);
+$buglist_sth->execute();
+
+
+################################################################################
+# Results Retrieval
+################################################################################
+
+# Retrieve the query results one row at a time and write the data into a list
+# of Perl records.
+
+# 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 $bugstatuses = {};
+my @bugidlist;
+
+my @bugs; # the list of records
+
+while (my @row = $buglist_sth->fetchrow_array()) {
+ 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'};
+ $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
+ 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'
+# based on whether the privacy is simply product implied (by mandatory groups)
+# 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;
+ }
+ 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';
+ }
+ }
+}
+
+# Compute percentage complete without rounding.
+my $sum = $time_info->{'actual_time'}+$time_info->{'remaining_time'};
+if ($sum > 0) {
+ $time_info->{'percentage_complete'} = 100*$time_info->{'actual_time'}/$sum;
+}
+else { # remaining_time <= 0
+ $time_info->{'percentage_complete'} = 0
+}
+
+################################################################################
+# Template Variable Definition
+################################################################################
+
+# Define the variables and functions that will be passed to the UI template.
+
+$vars->{'bugs'} = \@bugs;
+$vars->{'buglist'} = \@bugidlist;
+$vars->{'buglist_joined'} = join(',', @bugidlist);
+$vars->{'columns'} = $columns;
+$vars->{'displaycolumns'} = \@displaycolumns;
+
+$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++;
+ }
+}
+
+# Restore the bug status used by the specific search.
+$params->param('bug_status', $input_bug_status) if $input_bug_status;
+
+# The list of query fields in URL query string format, used when creating
+# URLs to the same query results page with different parameters (such as
+# a different sort order or when taking some action on the set of query
+# results). To get this string, we call the Bugzilla::CGI::canoncalise_query
+# function with a list of elements to be removed from the URL.
+$vars->{'urlquerypart'} = $params->canonicalise_query('order',
+ 'cmdtype',
+ 'query_based_on');
+$vars->{'order'} = $order;
+$vars->{'caneditbugs'} = 1;
+$vars->{'time_info'} = $time_info;
+
+if (!Bugzilla->user->in_group('editbugs')) {
+ foreach my $product (keys %$bugproducts) {
+ my $prod = new Bugzilla::Product({name => $product});
+ if (!Bugzilla->user->in_group('editbugs', $prod->id)) {
+ $vars->{'caneditbugs'} = 0;
+ last;
+ }
+ }
+}
+
+my @bugowners = keys %$bugowners;
+if (scalar(@bugowners) > 1 && Bugzilla->user->in_group('editbugs')) {
+ 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
+# the list more compact.
+$vars->{'splitheader'} = $cgi->cookie('SPLITHEADER') ? 1 : 0;
+
+$vars->{'quip'} = GetQuip();
+$vars->{'currenttime'} = localtime(time());
+
+# See if there's only one product in all the results (or only one product
+# that we searched for), which allows us to provide more helpful links.
+my @products = keys %$bugproducts;
+my $one_product;
+if (scalar(@products) == 1) {
+ $one_product = new Bugzilla::Product({ name => $products[0] });
+}
+# 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 = new Bugzilla::Product({ name => $cgi->param('product') });
+ }
+}
+# We only want the template to use it if the user can actually
+# enter bugs against it.
+if ($one_product && Bugzilla->user->can_enter_product($one_product)) {
+ $vars->{'one_product'} = $one_product;
+}
+
+# The following variables are used when the user is making changes to multiple bugs.
+if ($dotweak && scalar @bugs) {
+ if (!$vars->{'caneditbugs'}) {
+ _close_standby_message('text/html', 'inline', $serverpush);
+ 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'} = Bugzilla->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');
+
+ # 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
+ WHERE bug_status.isactive = 1
+ AND NOT EXISTS
+ (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
+ (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 ,@{ $one_product->versions })];
+ $vars->{'components'} = [map($_->name, @{ $one_product->components })];
+ if (Bugzilla->params->{'usetargetmilestone'}) {
+ $vars->{'targetmilestones'} = [map($_->name,
+ @{ $one_product->milestones })];
+ }
+ }
+}
+
+# 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
+# string in the text field.
+$vars->{'quicksearch'} = $searchstring;
+
+################################################################################
+# HTTP Header Generation
+################################################################################
+
+# Generate HTTP headers
+
+my $contenttype;
+my $disposition = "inline";
+
+if ($format->{'extension'} eq "html" && !$agent) {
+ if (!$cgi->param('regetlastlist')) {
+ Bugzilla->user->save_last_search(
+ { bugs => \@bugidlist, order => $order, vars => $vars,
+ list_id => scalar $cgi->param('list_id') });
+ }
+ $contenttype = "text/html";
+}
+else {
+ $contenttype = $format->{'ctype'};
+}
+
+if ($format->{'extension'} eq "csv") {
+ # We set CSV files to be downloaded, as they are designed for importing
+ # into other programs.
+ $disposition = "attachment";
+}
+
+# Suggest a name for the bug list if the user wants to save it as a file.
+$disposition .= "; filename=\"$filename\"";
+
+_close_standby_message($contenttype, $disposition, $serverpush);
+
+################################################################################
+# Content Generation
+################################################################################
+
+# Generate and return the UI (HTML page) from the appropriate template.
+$template->process($format->{'template'}, $vars)
+ || ThrowTemplateError($template->error());
+
+
+################################################################################
+# Script Conclusion
+################################################################################
+
+print $cgi->multipart_final() if $serverpush;
diff --git a/bugzilla.dtd b/bugzilla.dtd
new file mode 100644
index 000000000..b449d6ba4
--- /dev/null
+++ b/bugzilla.dtd
@@ -0,0 +1,88 @@
+<!ELEMENT bugzilla (bug+)>
+<!ATTLIST bugzilla
+ version CDATA #REQUIRED
+ urlbase CDATA #REQUIRED
+ maintainer CDATA #REQUIRED
+ exporter CDATA #IMPLIED
+>
+<!ELEMENT bug (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?, bug_file_loc?, status_whiteboard?, keywords*, priority, bug_severity, target_milestone?, dependson*, blocked*, everconfirmed, reporter, assigned_to, qa_contact?, cc*, (estimated_time, remaining_time, actual_time, deadline)?, group*, flag*, long_desc*, attachment*)?)>
+<!ATTLIST bug
+ error (NotFound | NotPermitted | InvalidBugId) #IMPLIED
+>
+<!ELEMENT bug_id (#PCDATA)>
+<!ELEMENT alias (#PCDATA)>
+<!ELEMENT reporter_accessible (#PCDATA)>
+<!ELEMENT cclist_accessible (#PCDATA)>
+<!ELEMENT exporter (#PCDATA)>
+<!ELEMENT urlbase (#PCDATA)>
+<!ELEMENT bug_status (#PCDATA)>
+<!ELEMENT classification_id (#PCDATA)>
+<!ELEMENT classification (#PCDATA)>
+<!ELEMENT product (#PCDATA)>
+<!ELEMENT priority (#PCDATA)>
+<!ELEMENT version (#PCDATA)>
+<!ELEMENT rep_platform (#PCDATA)>
+<!ELEMENT assigned_to (#PCDATA)>
+<!ELEMENT delta_ts (#PCDATA)>
+<!ELEMENT component (#PCDATA)>
+<!ELEMENT reporter (#PCDATA)>
+<!ELEMENT target_milestone (#PCDATA)>
+<!ELEMENT bug_severity (#PCDATA)>
+<!ELEMENT creation_ts (#PCDATA)>
+<!ELEMENT qa_contact (#PCDATA)>
+<!ELEMENT status_whiteboard (#PCDATA)>
+<!ELEMENT op_sys (#PCDATA)>
+<!ELEMENT resolution (#PCDATA)>
+<!ELEMENT dup_id (#PCDATA)>
+<!ELEMENT bug_file_loc (#PCDATA)>
+<!ELEMENT short_desc (#PCDATA)>
+<!ELEMENT keywords (#PCDATA)>
+<!ELEMENT dependson (#PCDATA)>
+<!ELEMENT blocked (#PCDATA)>
+<!ELEMENT everconfirmed (#PCDATA)>
+<!ELEMENT cc (#PCDATA)>
+<!ELEMENT group (#PCDATA)>
+<!ATTLIST group
+ id CDATA #REQUIRED
+ >
+<!ELEMENT estimated_time (#PCDATA)>
+<!ELEMENT remaining_time (#PCDATA)>
+<!ELEMENT actual_time (#PCDATA)>
+<!ELEMENT deadline (#PCDATA)>
+<!ELEMENT long_desc (who, bug_when, work_time?, thetext)>
+<!ATTLIST long_desc
+ encoding (base64) #IMPLIED
+ isprivate (0|1) #IMPLIED
+ >
+<!ELEMENT commentid (#PCDATA)>
+<!ELEMENT who (#PCDATA)>
+<!ELEMENT bug_when (#PCDATA)>
+<!ELEMENT work_time (#PCDATA)>
+<!ELEMENT thetext (#PCDATA)>
+<!ELEMENT attachment (attachid, date, desc, filename?, type?, size?, data?, flag*)>
+<!ATTLIST attachment
+ isobsolete (0|1) #IMPLIED
+ ispatch (0|1) #IMPLIED
+ isprivate (0|1) #IMPLIED
+ isurl (0|1) #IMPLIED
+>
+<!ELEMENT attachid (#PCDATA)>
+<!ELEMENT date (#PCDATA)>
+<!ELEMENT delta_ts (#PCDATA)>
+<!ELEMENT desc (#PCDATA)>
+<!ELEMENT filename (#PCDATA)>
+<!ELEMENT type (#PCDATA)>
+<!ELEMENT size (#PCDATA)>
+<!ELEMENT data (#PCDATA)>
+<!ATTLIST data
+ encoding (base64) #IMPLIED
+>
+<!ELEMENT flag EMPTY>
+<!ATTLIST flag
+ name CDATA #REQUIRED
+ id CDATA #REQUIRED
+ type_id CDATA
+ status CDATA #REQUIRED
+ setter CDATA #IMPLIED
+ requestee CDATA #IMPLIED
+>
diff --git a/chart.cgi b/chart.cgi
new file mode 100755
index 000000000..e7a0f5e8b
--- /dev/null
+++ b/chart.cgi
@@ -0,0 +1,371 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Gervase Markham <gerv@gerv.net>
+# Lance Larsh <lance.larsh@oracle.com>
+# Frédéric Buclin <LpSolit@gmail.com>
+
+# Glossary:
+# series: An individual, defined set of data plotted over time.
+# data set: What a series is called in the UI.
+# line: A set of one or more series, to be summed and drawn as a single
+# line when the series is plotted.
+# chart: A set of lines
+#
+# So when you select rows in the UI, you are selecting one or more lines, not
+# series.
+
+# Generic Charting TODO:
+#
+# JS-less chart creation - hard.
+# Broken image on error or no data - need to do much better.
+# Centralise permission checking, so Bugzilla->user->in_group('editbugs')
+# not scattered everywhere.
+# User documentation :-)
+#
+# Bonus:
+# Offer subscription when you get a "series already exists" error?
+
+use strict;
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::CGI;
+use Bugzilla::Error;
+use Bugzilla::Util;
+use Bugzilla::Chart;
+use Bugzilla::Series;
+use Bugzilla::User;
+use Bugzilla::Token;
+
+# 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 = {};
+my $dbh = Bugzilla->dbh;
+
+my $user = Bugzilla->login(LOGIN_REQUIRED);
+
+if (!Bugzilla->feature('new_charts')) {
+ ThrowCodeError('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 $action = $cgi->param('action');
+my $series_id = $cgi->param('series_id');
+$vars->{'doc_section'} = 'reporting.html#charts';
+
+# Because some actions are chosen by buttons, we can't encode them as the value
+# of the action param, because that value is localization-dependent. So, we
+# encode it in the name, as "action-<action>". Some params even contain the
+# 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 ||= "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;
+}
+
+$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);
+}
+elsif ($action eq "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();
+ }
+}
+elsif ($action eq "create") {
+ assertCanCreate($cgi);
+ my $token = $cgi->param('token');
+ check_hash_token($token, ['create-series']);
+
+ my $series = new Bugzilla::Series($cgi);
+
+ ThrowUserError("series_already_exists", {'series' => $series})
+ if $series->existsInDatabase;
+
+ $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);
+}
+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);
+}
+elsif ($action eq "confirm-delete") {
+ $vars->{'series'} = assertCanEdit($series_id);
+
+ 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"});
+ }
+ }
+ $dbh->bz_commit_transaction();
+
+ $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");
+}
+else {
+ 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"));
+
+ 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();
+
+ return @ids;
+}
+
+# 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 = new Bugzilla::Series($series_id)
+ || ThrowCodeError('invalid_series_id');
+
+ if (!$user->in_group('admin') && $series->{creator_id} != $user->id) {
+ ThrowUserError('illegal_series_edit');
+ }
+
+ return $series;
+}
+
+# Check if the user is permitted to create this series with these parameters.
+sub assertCanCreate {
+ my ($cgi) = shift;
+ my $user = Bugzilla->user;
+
+ $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 });
+ }
+}
+
+sub validateWidthAndHeight {
+ $vars->{'width'} = $cgi->param('width');
+ $vars->{'height'} = $cgi->param('height');
+
+ if (defined($vars->{'width'})) {
+ (detaint_natural($vars->{'width'}) && $vars->{'width'} > 0)
+ || ThrowCodeError("invalid_dimensions");
+ }
+
+ if (defined($vars->{'height'})) {
+ (detaint_natural($vars->{'height'}) && $vars->{'height'} > 0)
+ || ThrowCodeError("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;
+
+ $vars->{'category'} = Bugzilla::Chart::getVisibleSeries();
+ $vars->{'default'} = $series;
+
+ print $cgi->header();
+ $template->process("reports/edit-series.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+}
+
+sub plot {
+ validateWidthAndHeight();
+ $vars->{'chart'} = new Bugzilla::Chart($cgi);
+
+ my $format = $template->get_format("reports/chart", "", scalar($cgi->param('ctype')));
+
+ # Debugging PNGs is a pain; we need to be able to see the error messages
+ if ($cgi->param('debug')) {
+ print $cgi->header();
+ $vars->{'chart'}->dump();
+ }
+
+ print $cgi->header($format->{'ctype'});
+ disable_utf8() if ($format->{'ctype'} =~ /^image\//);
+
+ $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());
+}
+
+sub view {
+ my $chart = shift;
+
+ # 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();
+
+ 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');
+
+ $template->process("reports/create-chart.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+}
diff --git a/checksetup.pl b/checksetup.pl
new file mode 100755
index 000000000..0e89447fe
--- /dev/null
+++ b/checksetup.pl
@@ -0,0 +1,516 @@
+#!/usr/bin/perl -w
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is mozilla.org code.
+#
+# The Initial Developer of the Original Code is Holger
+# Schurig. Portions created by Holger Schurig are
+# Copyright (C) 1999 Holger Schurig. All
+# Rights Reserved.
+#
+# Contributor(s): Holger Schurig <holgerschurig@nikocity.de>
+# Terry Weissman <terry@mozilla.org>
+# Dan Mosedale <dmose@mozilla.org>
+# Dave Miller <justdave@syndicomm.com>
+# Zach Lipton <zach@zachlipton.com>
+# Jacob Steenhagen <jake@bugzilla.org>
+# Bradley Baetz <bbaetz@student.usyd.edu.au>
+# Tobias Burnus <burnus@net-b.de>
+# Shane H. W. Travis <travis@sedsystems.ca>
+# Gervase Markham <gerv@gerv.net>
+# Erik Stambaugh <erik@dasbistro.com>
+# Dave Lawrence <dkl@redhat.com>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+# Joel Peshkin <bugreport@peshkin.net>
+# Lance Larsh <lance.larsh@oracle.com>
+# A. Karl Kornel <karl@kornel.name>
+# Marc Schumann <wurblzap@gmail.com>
+
+# This file has detailed POD docs, do "perldoc checksetup.pl" to see them.
+
+######################################################################
+# Initialization
+######################################################################
+
+use strict;
+use 5.008001;
+use File::Basename;
+use Getopt::Long qw(:config bundling);
+use Pod::Usage;
+use Safe;
+
+BEGIN { chdir dirname($0); }
+use lib qw(. lib);
+use Bugzilla::Constants;
+use Bugzilla::Install::Requirements;
+use Bugzilla::Install::Util qw(install_string get_version_and_os init_console);
+
+######################################################################
+# Live Code
+######################################################################
+
+# When we're running at the command line, we need to pick the right
+# language before ever displaying any string.
+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');
+
+# 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
+# non-interactive mode.
+my $answers_file = $ARGV[0];
+my $silent = $answers_file && !$switch{'verbose'};
+
+print(install_string('header', get_version_and_os()) . "\n") unless $silent;
+exit if $switch{'version'};
+# Check required --MODULES--
+my $module_results = check_requirements(!$silent);
+Bugzilla::Install::Requirements::print_module_instructions(
+ $module_results, !$silent);
+exit if !$module_results->{pass};
+# Break out if checking the modules is all we have been asked to do.
+exit if $switch{'check-modules'};
+
+###########################################################################
+# Load Bugzilla Modules
+###########################################################################
+
+# It's never safe to "use" a Bugzilla module in checksetup. If a module
+# prerequisite is missing, and you "use" a module that requires it,
+# then instead of our nice normal checksetup message, the user would
+# get a cryptic perl error about the missing module.
+
+require Bugzilla;
+require Bugzilla::User;
+
+require Bugzilla::Config;
+import Bugzilla::Config qw(:admin);
+
+require Bugzilla::Install::Localconfig;
+import Bugzilla::Install::Localconfig qw(update_localconfig);
+
+require Bugzilla::Install::Filesystem;
+import Bugzilla::Install::Filesystem qw(update_filesystem create_htaccess
+ fix_all_file_permissions);
+require Bugzilla::Install::DB;
+require Bugzilla::DB;
+require Bugzilla::Template;
+require Bugzilla::Field;
+require Bugzilla::Install;
+
+Bugzilla->installation_mode(INSTALLATION_MODE_NON_INTERACTIVE) if $answers_file;
+Bugzilla->installation_answers($answers_file);
+
+###########################################################################
+# Check and update --LOCAL-- configuration
+###########################################################################
+
+print "Reading " . bz_locations()->{'localconfig'} . "...\n" unless $silent;
+update_localconfig({ output => !$silent });
+my $lc_hash = Bugzilla->localconfig;
+
+###########################################################################
+# Check --DATABASE-- setup
+###########################################################################
+
+# At this point, localconfig is defined and is readable. So we know
+# everything we need to create the DB. We have to create it early,
+# because some data required to populate data/params is stored in the DB.
+
+Bugzilla::DB::bz_check_requirements(!$silent);
+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();
+
+###########################################################################
+# Check --DATA-- directory
+###########################################################################
+
+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,
+# and set the defaults for new ones
+my %old_params = update_params();
+
+###########################################################################
+# Pre-compile --TEMPLATE-- code
+###########################################################################
+
+Bugzilla::Template::precompile_templates(!$silent)
+ unless $switch{'no-templates'};
+
+###########################################################################
+# Set proper rights (--CHMOD--)
+###########################################################################
+
+fix_all_file_permissions(!$silent);
+
+###########################################################################
+# Check GraphViz setup
+###########################################################################
+
+# If we are using a local 'dot' binary, verify the specified binary exists
+# and that the generated images are accessible.
+check_graphviz(!$silent) if Bugzilla->params->{'webdotbase'};
+
+###########################################################################
+# Changes to the fielddefs --TABLE--
+###########################################################################
+
+# Using Bugzilla::Field's create() or update() depends on the
+# fielddefs table having a modern definition. So, we have to make
+# these particular schema changes before we make any other schema changes.
+Bugzilla::Install::DB::update_fielddefs_definition();
+
+Bugzilla::Field::populate_field_definitions();
+
+###########################################################################
+# Update the tables to the current definition --TABLE--
+###########################################################################
+
+Bugzilla::Install::DB::update_table_definitions(\%old_params);
+Bugzilla::Install::init_workflow();
+
+###########################################################################
+# Bugzilla uses --GROUPS-- to assign various rights to its users.
+###########################################################################
+
+Bugzilla::Install::update_system_groups();
+
+# "Log In" as the fake superuser who can do everything.
+Bugzilla->set_user(Bugzilla::User->super_user);
+
+###########################################################################
+# Create --SETTINGS-- users can adjust
+###########################################################################
+
+Bugzilla::Install::update_settings();
+
+###########################################################################
+# Create Administrator --ADMIN--
+###########################################################################
+
+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'};
+
+###########################################################################
+# Create default Product
+###########################################################################
+
+Bugzilla::Install::create_default_product();
+
+Bugzilla::Hook::process('install_before_final_checks', { silent => $silent });
+
+###########################################################################
+# Final checks
+###########################################################################
+
+# Check if the default parameter for urlbase is still set, and if so, give
+# notification that they should go and visit editparams.cgi
+if (Bugzilla->params->{'urlbase'} eq '') {
+ print "\n" . Bugzilla::Install::get_text('install_urlbase_default') . "\n"
+ unless $silent;
+}
+
+__END__
+
+=head1 NAME
+
+checksetup.pl - A do-it-all upgrade and installation script for Bugzilla.
+
+=head1 SYNOPSIS
+
+ ./checksetup.pl [--help|--check-modules|--version]
+ ./checksetup.pl [SCRIPT [--verbose]] [--no-templates|-t]
+ [--make-admin=user@domain.com]
+ [--reset-password=user@domain.com]
+
+=head1 OPTIONS
+
+=over
+
+=item F<SCRIPT>
+
+Name of script to drive non-interactive mode. This script should
+define an C<%answer> hash whose keys are variable names and the
+values answers to all the questions checksetup.pl asks. For details
+on the format of this script, do C<perldoc checksetup.pl> and look for
+the L</"RUNNING CHECKSETUP NON-INTERACTIVELY"> section.
+
+=item B<--help>
+
+Display this help text
+
+=item B<--check-modules>
+
+Only check for correct module dependencies and quit afterward.
+
+=item B<--make-admin>=username@domain.com
+
+Makes the specified user into a Bugzilla administrator. This is
+in case you accidentally lock yourself out of the Bugzilla administrative
+interface.
+
+=item B<--reset-password>=user@domain.com
+
+Resets the specified user's password. checksetup.pl will prompt you to
+enter a new password for the user.
+
+=item B<--no-templates> (B<-t>)
+
+Don't compile the templates at all. Existing compiled templates will
+remain; missing compiled templates will not be created. (Used primarily
+by developers to speed up checksetup.) Use this switch at your own risk.
+
+=item B<--verbose>
+
+Output results of SCRIPT being processed.
+
+=item B<--version>
+
+Display the version of Bugzilla, Perl, and some info about the
+system that Bugzilla is being installed on, and then exit.
+
+=back
+
+=head1 DESCRIPTION
+
+Hey, what's this?
+
+F<checksetup.pl> is a script that is supposed to run during
+installation time and also after every upgrade.
+
+The goal of this script is to make the installation even easier.
+It does this by doing things for you as well as testing for problems
+in advance.
+
+You can run the script whenever you like. You MUST run it after
+you update Bugzilla, because it will then update your SQL table
+definitions to resync them with the code.
+
+You can see all the details of what the script does at
+L</How Checksetup Works>.
+
+=head1 MODIFYING CHECKSETUP
+
+There should be no need for Bugzilla Administrators to modify
+this script; all user-configurable stuff has been moved
+into a local configuration file called F<localconfig>. When that file
+in changed and F<checksetup.pl> is run, then the user's changes
+will be reflected back into the database.
+
+However, developers often need to modify the installation process.
+This section explains how F<checksetup.pl> works, so that you
+know the right part to modify.
+
+=head2 How Checksetup Works
+
+F<checksetup.pl> runs through several stages during installation:
+
+=over
+
+=item 1
+
+Checks if the required and optional perl modules are installed,
+using L<Bugzilla::Install::Requirements/check_requirements>.
+
+=item 2
+
+Creates or updates the F<localconfig> file, using
+L<Bugzilla::Install::Localconfig/update_localconfig>.
+
+=item 3
+
+Checks the DBD and database version, using
+L<Bugzilla::DB/bz_check_requirements>.
+
+=item 4
+
+Creates the Bugzilla database if it doesn't exist, using
+L<Bugzilla::DB/bz_create_database>.
+
+=item 5
+
+Creates all of the tables in the Bugzilla database, using
+L<Bugzilla::DB/bz_setup_database>.
+
+Note that all the table definitions are stored in
+L<Bugzilla::DB::Schema/ABSTRACT_SCHEMA>.
+
+=item 6
+
+Puts the values into the enum tables (like C<resolution>, C<bug_status>,
+etc.) using L<Bugzilla::DB/bz_populate_enum_tables>.
+
+=item 7
+
+Creates any files that Bugzilla needs but doesn't ship with, using
+L<Bugzilla::Install::Filesystem/update_filesystem>.
+
+=item 8
+
+Creates the F<.htaccess> files if you haven't specified not to
+in F<localconfig>. It does this with
+L<Bugzilla::Install::Filesystem/create_htaccess>.
+
+=item 9
+
+Updates the system parameters (stored in F<data/params>), using
+L<Bugzilla::Config/update_params>.
+
+=item 10
+
+Pre-compiles all templates, to improve the speed of Bugzilla.
+It uses L<Bugzilla::Template/precompile_templates> to do this.
+
+=item 11
+
+Fixes all file permissions to be secure. It does this differently depending
+on whether or not you've specified C<$webservergroup> in F<localconfig>.
+
+The function that does this is
+L<Bugzilla::Install::Filesystem/fix_all_file_permissions>.
+
+=item 12
+
+Populates the C<fielddefs> table, using
+L<Bugzilla::Field/populate_field_definitions>.
+
+=item 13
+
+This is the major part of checksetup--updating the table definitions
+from one version of Bugzilla to another.
+
+The code for this is in L<Bugzilla::Install::DB/update_table_definitions>.
+
+This includes creating the default Classification (using
+L<Bugzilla::Install/create_default_classification>) and setting up all
+the foreign keys for all tables, using L<Bugzilla::DB/bz_setup_foreign_keys>.
+
+=item 14
+
+Creates the system groups--the ones like C<editbugs>, C<admin>, and so on.
+This is L<Bugzilla::Install/update_system_groups>.
+
+=item 15
+
+Creates all of the user-adjustable preferences that appear on the
+"General Preferences" screen. This is L<Bugzilla::Install/update_settings>.
+
+=item 16
+
+Creates an administrator, if one doesn't already exist, using
+L<Bugzilla::Install/create_admin>.
+
+We also can make somebody an admin at this step, if the user specified
+the C<--make-admin> switch.
+
+=item 17
+
+Creates the default Product and Component, using
+L<Bugzilla::Install/create_default_product>.
+
+=back
+
+=head2 Modifying the Database
+
+Sometimes you'll want to modify the database. In fact, that's mostly
+what checksetup does, is upgrade old Bugzilla databases to the modern
+format.
+
+If you'd like to know how to make changes to the datbase, see
+the information in the Bugzilla Developer's Guide, at:
+L<http://www.bugzilla.org/docs/developer.html#sql-schema>
+
+Also see L<Bugzilla::DB/"Schema Modification Methods"> and
+L<Bugzilla::DB/"Schema Information Methods">.
+
+=head1 RUNNING CHECKSETUP NON-INTERACTIVELY
+
+To operate checksetup non-interactively, run it with a single argument
+specifying a filename that contains the information usually obtained by
+prompting the user or by editing localconfig.
+
+The format of that file is as follows:
+
+ $answer{'db_host'} = 'localhost';
+ $answer{'db_driver'} = 'mydbdriver';
+ $answer{'db_port'} = 0;
+ $answer{'db_name'} = 'mydbname';
+ $answer{'db_user'} = 'mydbuser';
+ $answer{'db_pass'} = 'mydbpass';
+
+ $answer{'urlbase'} = 'http://bugzilla.mydomain.com/';
+
+ (Any localconfig variable or parameter can be specified as above.)
+
+ $answer{'ADMIN_EMAIL'} = 'myadmin@mydomain.net';
+ $answer{'ADMIN_PASSWORD'} = 'fooey';
+ $answer{'ADMIN_REALNAME'} = 'Joel Peshkin';
+
+ $answer{'SMTP_SERVER'} = 'mail.mydomain.net';
+
+ $answer{'NO_PAUSE'} = 1
+
+C<NO_PAUSE> means "never stop and prompt the user to hit Enter to continue,
+just go ahead and do things, even if they are potentially dangerous."
+Don't set this to 1 unless you know what you are doing.
+
+=head1 SEE ALSO
+
+=over
+
+=item *
+
+L<Bugzilla::Install::Requirements>
+
+=item *
+
+L<Bugzilla::Install::Localconfig>
+
+=item *
+
+L<Bugzilla::Install::Filesystem>
+
+=item *
+
+L<Bugzilla::Install::DB>
+
+=item *
+
+L<Bugzilla::Install>
+
+=item *
+
+L<Bugzilla::Config/update_params>
+
+=item *
+
+L<Bugzilla::DB/CONNECTION>
+
+=back
+
diff --git a/colchange.cgi b/colchange.cgi
new file mode 100755
index 000000000..844f7615c
--- /dev/null
+++ b/colchange.cgi
@@ -0,0 +1,202 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Gervase Markham <gerv@gerv.net>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+# Pascal Held <paheld@gmail.com>
+
+use strict;
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Util;
+use Bugzilla::CGI;
+use Bugzilla::Search::Saved;
+use Bugzilla::Error;
+use Bugzilla::User;
+use Bugzilla::Token;
+
+use Storable qw(dclone);
+
+# Maps parameters that control columns to the names of columns.
+use constant COLUMN_PARAMS => {
+ 'useclassification' => ['classification'],
+ 'usebugaliases' => ['alias'],
+ 'usetargetmilestone' => ['target_milestone'],
+ 'useqacontact' => ['qa_contact', 'qa_contact_realname'],
+ 'usestatuswhiteboard' => ['status_whiteboard'],
+};
+
+# 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',
+};
+
+Bugzilla->login();
+
+my $cgi = Bugzilla->cgi;
+my $template = Bugzilla->template;
+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 $class (keys %{ COLUMN_CLASSES() }) {
+ eval("use $class; 1;") || die $@;
+ my $column = COLUMN_CLASSES->{$class};
+ delete $columns->{$column} if !$class->any_exist;
+}
+
+if (!Bugzilla->user->is_timetracker) {
+ 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');
+ }
+ else {
+ $cgi->remove_cookie('SPLITHEADER');
+ }
+
+ $vars->{'message'} = "change_columns";
+
+ if ($cgi->param('save_columns_for_search')
+ && defined $search && $search->user->id == Bugzilla->user->id)
+ {
+ my $params = new Bugzilla::CGI($search->url);
+ $params->param('columnlist', join(",", @collist));
+ $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;
+}
+
+$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 = Bugzilla->user->queries;
+ my ($search) = grep($_->name eq $cgi->param('query_based_on'), @$searches);
+
+ if ($search) {
+ $vars->{'saved_search'} = $search;
+ }
+}
+
+# Generate and return the UI (HTML page) from the appropriate template.
+print $cgi->header();
+$template->process("list/change-columns.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
diff --git a/collectstats.pl b/collectstats.pl
new file mode 100755
index 000000000..e2af2f02e
--- /dev/null
+++ b/collectstats.pl
@@ -0,0 +1,530 @@
+#!/usr/bin/perl -w
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>,
+# Harrison Page <harrison@netscape.com>
+# Gervase Markham <gerv@gerv.net>
+# Richard Walters <rwalters@qualcomm.com>
+# Jean-Sebastien Guay <jean_seb@hybride.com>
+# Frédéric Buclin <LpSolit@gmail.com>
+
+# Run me out of cron at midnight to collect Bugzilla statistics.
+#
+# To run new charts for a specific date, pass it in on the command line in
+# ISO (2004-08-14) format.
+
+use strict;
+use lib qw(. lib);
+
+use List::Util qw(first);
+use Cwd;
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Util;
+use Bugzilla::Search;
+use Bugzilla::User;
+use Bugzilla::Product;
+use Bugzilla::Field;
+
+# Turn off output buffering (probably needed when displaying output feedback
+# in the regenerate mode).
+$| = 1;
+
+my $datadir = bz_locations()->{'datadir'};
+my $graphsdir = bz_locations()->{'graphsdir'};
+
+# 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);
+}
+
+my $dbh = Bugzilla->switch_to_shadow_db();
+
+
+# To recreate the daily statistics, run "collectstats.pl --regenerate" .
+my $regenerate = 0;
+if ($#ARGV >= 0 && $ARGV[0] eq "--regenerate") {
+ shift(@ARGV);
+ $regenerate = 1;
+}
+
+# As we can now customize statuses and resolutions, looking at the current list
+# of legal values only is not enough as some now removed statuses and resolutions
+# 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
+ FROM bugs_activity
+ INNER JOIN fielddefs
+ ON fielddefs.id = bugs_activity.fieldid
+ LEFT JOIN $field
+ ON $field.value = bugs_activity.added
+ WHERE fielddefs.name = ?
+ AND $field.id IS NULL
+
+ UNION
+
+ SELECT bugs_activity.removed
+ FROM bugs_activity
+ INNER JOIN fielddefs
+ ON fielddefs.id = bugs_activity.fieldid
+ LEFT JOIN $field
+ ON $field.value = bugs_activity.removed
+ WHERE fielddefs.name = ?
+ AND $field.id IS NULL",
+ undef, ($field, $field));
+
+ push(@$values, @$old_values);
+ $fields->{$field} = $values;
+}
+
+my @statuses = @{$fields->{'bug_status'}};
+my @resolutions = @{$fields->{'resolution'}};
+# Exclude "" from the resolution list.
+@resolutions = grep {$_} @resolutions;
+
+# --regenerate was taking an enormous amount of time to query everything
+# per bug, per day. Instead, we now just get all the data out of the DB
+# at once and stuff it into some data structures.
+my (%bug_status, %bug_resolution, %removed);
+if ($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(
+ q{SELECT bugs_activity.bug_id, bugs_activity.removed,}
+ . $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 });
+ }
+ }
+}
+
+my $tstart = time;
+
+my @myproducts = Bugzilla::Product->get_all;
+unshift(@myproducts, "-All-");
+
+foreach (@myproducts) {
+ my $dir = "$datadir/mining";
+
+ &check_data_dir ($dir);
+
+ if ($regenerate) {
+ regenerate_stats($dir, $_, \%bug_resolution, \%bug_status, \%removed);
+ } else {
+ &collect_stats($dir, $_);
+ }
+}
+my $tend = time;
+# Uncomment the following line for performance testing.
+#print "Total time taken " . delta_time($tstart, $tend) . "\n";
+
+CollectSeriesData();
+
+sub check_data_dir {
+ my $dir = shift;
+
+ if (! -d $dir) {
+ mkdir $dir, 0755;
+ chmod 0755, $dir;
+ }
+}
+
+sub collect_stats {
+ my $dir = shift;
+ my $product = shift;
+ my $when = localtime (time);
+ my $dbh = Bugzilla->dbh;
+ my $product_id;
+
+ if (ref $product) {
+ $product_id = $product->id;
+ $product = $product->name;
+ }
+
+ # NB: Need to mangle the product for the filename, but use the real
+ # product name in the query
+ my $file_product = $product;
+ $file_product =~ s/\//-/gs;
+ my $file = join '/', $dir, $file_product;
+ 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});
+ }
+
+ 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 ne '-All-') {
+ $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 ne '-All-');
+ my $count = $dbh->selectrow_array($sth_status, undef, @values);
+ push(@row, $count);
+ }
+ foreach my $resolution (@resolutions) {
+ @values = ($resolution);
+ push (@values, $product_id) if ($product ne '-All-');
+ my $count = $dbh->selectrow_array($sth_reso, undef, @values);
+ push(@row, $count);
+ }
+
+ if (!$exists || scalar(@data)) {
+ my $fields = join('|', ('DATE', @statuses, @resolutions));
+ print DATA <<FIN;
+# Bugzilla Daily Bug Stats
+#
+# Do not edit me! This file is generated.
+#
+# fields: $fields
+# Product: $product
+# 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;
+ chmod 0644, $file;
+}
+
+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;
+ }
+ 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();
+
+ # NB: Need to mangle the product for the filename, but use the real
+ # product name in the query
+ if (ref $product) {
+ $product = $product->name;
+ }
+ my $file_product = $product;
+ $file_product =~ s/\//-/gs;
+ my $file = join '/', $dir, $file_product;
+
+ my $and_product = "";
+ my $from_product = "";
+
+ my @values = ();
+ if ($product ne '-All-') {
+ $and_product = q{ AND products.name = ?};
+ $from_product = q{ INNER JOIN products
+ ON bugs.product_id = products.id};
+ push (@values, $product);
+ }
+
+ # 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 $from_product
+ 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);
+
+ if (!defined $start) {
+ return;
+ }
+
+ if (open DATA, ">$file") {
+ my $fields = join('|', ('DATE', @statuses, @resolutions));
+ print DATA <<FIN;
+# Bugzilla Daily Bug Stats
+#
+# Do not edit me! This file is generated.
+#
+# fields: $fields
+# Product: $product
+# 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 $product \[\%.1f\%\%]", $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 $from_product
+ 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";
+ }
+
+ # Finish up output feedback for this product.
+ my $tend = time;
+ print "\rRegenerating $product \[100.0\%] - " .
+ delta_time($tstart, $tend) . "\n";
+
+ close DATA;
+ chmod 0640, $file;
+ }
+}
+
+# A helper for --regenerate.
+# For each bug that exists on a day, we determine its status/resolution
+# 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
+# bug made on or after that day.
+sub _get_value {
+ my ($removed, $current, $day, $bug) = @_;
+
+ # 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};
+}
+
+sub today {
+ 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;
+}
+
+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;
+}
+
+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);
+}
+
+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 = $ARGV[0] || 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' => $cgi,
+ 'fields' => ["bug_id"],
+ 'user' => $user);
+ my $sql = $search->getSQL();
+ $data = $shadow_dbh->selectall_arrayref($sql);
+ };
+
+ 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);
+ }
+ }
+}
+
diff --git a/config.cgi b/config.cgi
new file mode 100755
index 000000000..2c82fdc59
--- /dev/null
+++ b/config.cgi
@@ -0,0 +1,166 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Myk Melez <myk@mozilla.org>
+# Frank Becker <Frank@Frank-Becker.de>
+
+################################################################################
+# Script Initialization
+################################################################################
+
+# Make it harder for us to do dangerous things in Perl.
+use strict;
+
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Keyword;
+use Bugzilla::Product;
+use Bugzilla::Status;
+use Bugzilla::Field;
+
+use Digest::MD5 qw(md5_base64);
+
+my $user = Bugzilla->login(LOGIN_OPTIONAL);
+my $cgi = Bugzilla->cgi;
+
+# If the 'requirelogin' parameter is on and the user is not
+# authenticated, return empty fields.
+if (Bugzilla->params->{'requirelogin'} && !$user->id) {
+ display_data();
+}
+
+# Get data from the shadow DB as they don't change very often.
+Bugzilla->switch_to_shadow_db;
+
+# 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->{'keyword'} = [map($_->name, 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 ];
+
+# 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);
+ }
+ }
+} 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);
+
+# Allow consumers to specify whether or not they want flag data.
+if (defined $cgi->param('flags')) {
+ $vars->{'show_flags'} = $cgi->param('flags');
+}
+else {
+ # We default to sending flag data.
+ $vars->{'show_flags'} = 1;
+}
+
+# Create separate lists of open versus resolved statuses. This should really
+# be made part of the configuration.
+my @open_status;
+my @closed_status;
+foreach my $status (@{$vars->{'status'}}) {
+ is_open_state($status) ? push(@open_status, $status)
+ : push(@closed_status, $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 (!Bugzilla->user->is_timetracker) {
+ @fields = grep { $_->name !~ /^(estimated_time|remaining_time|work_time|percentage_complete|deadline)$/ } @fields;
+}
+$vars->{'field'} = \@fields;
+
+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);
+
+ # ETag support.
+ my $if_none_match = $cgi->http('If-None-Match') || "";
+ my $found304;
+ my @if_none = split(/[\s,]+/, $if_none_match);
+ foreach my $if_none (@if_none) {
+ # remove quotes from begin and end of the string
+ $if_none =~ s/^\"//g;
+ $if_none =~ s/\"$//g;
+ if ($if_none eq $digest or $if_none eq '*') {
+ # leave the loop after the first match
+ $found304 = $if_none;
+ last;
+ }
+ }
+
+ if ($found304) {
+ print $cgi->header(-type => 'text/html',
+ -ETag => $found304,
+ -status => '304 Not Modified');
+ }
+ else {
+ # Return HTTP headers.
+ print $cgi->header (-ETag => $digest,
+ -type => $format->{'ctype'});
+ print $output;
+ }
+ exit;
+}
diff --git a/contrib/README b/contrib/README
new file mode 100644
index 000000000..32a5b6488
--- /dev/null
+++ b/contrib/README
@@ -0,0 +1,73 @@
+This directory contains contributed software related to Bugzilla.
+Things in here have not necessarily been tested or tried by anyone
+except the original contributor, so tread carefully. But it may still
+be useful to you. Read the files themselves for detailed usage information
+on any specific script.
+
+This file is encoded in UTF8 for purposes of contributor names.
+
+This directory includes:
+
+bugzilla_ldapsync.rb -- Script that can be run via Cron that queries an LDAP
+ server for e-mail addresses to add Bugzilla users
+ for. Will optionally disable Bugzilla users with
+ no matching LDAP record. Contributed by Thomas
+ Stromberg <thomas+bugzilla@stromberg.org>.
+
+ bugzilla-submit/ -- A standalone bug submission program.
+
+ bzdbcopy.pl -- A script to copy data from an installation running
+ on one DB platform to an installation running on
+ another DB platform.
+
+bz_webservice_demo.p -- An example script that demonstrates how to talk to
+ Bugzilla via XMLRPC.
+
+ cmdline/ -- Various commands for querying your Bugzilla
+ installation.
+
+ cvs-update.pl -- Script to keep a record of all CVS updates made
+ from a given directory. The log is useful when
+ changes need to be backed out.
+
+ gnatsparse/ -- A Python script used to import a GNATS database
+ into Bugzilla.
+
+ gnats2bz.pl -- A Perl script to help import bugs from a GNATS
+ database into a Bugzilla database. Contributed by
+ Tom Schutter <tom@platte.com>.
+
+ jb2bz.py -- Script to import bugs from JitterBug to Bugzilla.
+
+ merge-users.pl -- Script to merge two user accounts. The activities
+ from one account are moved to the another. Specify
+ both accounts on the command line. The new account
+ must already exist.
+
+ mysqld-watcher.pl -- This script can be installed as a frequent Cron
+ job to clean up stalled/dead queries.
+
+ recode.pl -- Script to convert a database from one encoding
+ (or multiple encodings) to UTF-8.
+
+ sendbugmail.pl -- This script is a drop-in replacement for the
+ 'processmail' script which used to be shipped
+ with Bugzilla, but was replaced by the
+ Bugzilla/BugMail.pm Perl module. This script can
+ be used if 'processmail' was previously called
+ from other scripts external to
+ Bugzilla. See the comments at the top of
+ the file for usage information. Contributed
+ by Nick Barnes of Ravenbrook Limited.
+
+sendunsentbugmail.pl -- Script to find bugs with un-sent mail and to
+ send all unsent messages.
+
+ syncLDAP.pl -- Script that can be run via Cron that queries an LDAP
+ server for users and e-mail addresses and adds
+ missing users to Bugzilla. Can disable/update
+ non-existing/changed information. Contributed by
+ Andreas Höfler <andreas.hoefler@bearingpoint.com>.
+
+ yp_nomail.sh -- Script that can be run via Cron that regularly updates
+ the nomail file for terminated employees.
diff --git a/contrib/bugzilla-queue.rhel b/contrib/bugzilla-queue.rhel
new file mode 100755
index 000000000..3e00cce24
--- /dev/null
+++ b/contrib/bugzilla-queue.rhel
@@ -0,0 +1,109 @@
+#!/bin/bash
+#
+# bugzilla-queue This starts, stops, and restarts the Bugzilla jobqueue.pl
+# daemon, which manages sending queued mail and possibly
+# other queued tasks in the future.
+#
+# chkconfig: 345 85 15
+# description: Bugzilla queue runner
+#
+### BEGIN INIT INFO
+# Provides: bugzilla-queue
+# Required-Start: $local_fs $syslog MTA mysqld
+# Required-Stop: $local_fs $syslog MTA mysqld
+# Default-Start: 3 5
+# Default-Stop: 0 1 2 6
+# Short-Description: Start and stop the Bugzilla queue runner.
+# Description: The Bugzilla queue runner (jobqueue.pl) sends any mail
+# that Bugzilla has queued to be sent in the background. If you
+# have enabled the use_mailer_queue parameter in Bugzilla, you
+# must run this daemon.
+### END INIT INFO
+
+NAME=`basename $0`
+
+#################
+# Configuration #
+#################
+
+# This should be the path to your Bugzilla
+BUGZILLA=/var/www/html/bugzilla
+# Who owns the Bugzilla directory and files?
+USER=root
+
+# If you want to pass any options to the daemon (like -d for debugging)
+# specify it here.
+OPTIONS=""
+
+# You can also override the configuration by creating a
+# /etc/sysconfig/bugzilla-queue file so that you don't
+# have to edit this script.
+if [ -r /etc/sysconfig/$NAME ]; then
+ . /etc/sysconfig/$NAME
+fi
+
+##########
+# Script #
+##########
+
+RETVAL=0
+BIN=$BUGZILLA/jobqueue.pl
+PIDFILE=/var/run/$NAME.pid
+
+# Source function library.
+. /etc/rc.d/init.d/functions
+
+usage ()
+{
+ echo "Usage: service $NAME {start|stop|status|restart|condrestart}"
+ RETVAL=1
+}
+
+
+start ()
+{
+ if [ -f "$PIDFILE" ]; then
+ checkpid `cat $PIDFILE` && return 0
+ fi
+ echo -n "Starting $NAME: "
+ touch $PIDFILE
+ chown $USER $PIDFILE
+ daemon --user=$USER \
+ "$BIN ${OPTIONS} -p '$PIDFILE' -n $NAME start > /dev/null"
+ ret=$?
+ [ $ret -eq "0" ] && touch /var/lock/subsys/$NAME
+ echo
+ return $ret
+}
+
+stop ()
+{
+ [ -f /var/lock/subsys/$NAME ] || return 0
+ echo -n "Killing $NAME: "
+ killproc $NAME
+ echo
+ rm -f /var/lock/subsys/$NAME
+}
+
+restart ()
+{
+ stop
+ start
+}
+
+condrestart ()
+{
+ [ -e /var/lock/subsys/$NAME ] && restart || return 0
+}
+
+
+case "$1" in
+ start) start; RETVAL=$? ;;
+ stop) stop; RETVAL=$? ;;
+ status) $BIN -p $PIDFILE -n $NAME check; RETVAL=$?;;
+ restart) restart; RETVAL=$? ;;
+ condrestart) condrestart; RETVAL=$? ;;
+ *) usage ; RETVAL=2 ;;
+esac
+
+exit $RETVAL
diff --git a/contrib/bugzilla-queue.suse b/contrib/bugzilla-queue.suse
new file mode 100755
index 000000000..356302058
--- /dev/null
+++ b/contrib/bugzilla-queue.suse
@@ -0,0 +1,174 @@
+#!/bin/bash
+#
+# bugzilla-queue This starts, stops, and restarts the Bugzilla jobqueue.pl
+# daemon, which manages sending queued mail and possibly
+# other queued tasks in the future.
+#
+# chkconfig: 345 85 15
+# description: Bugzilla queue runner
+#
+### BEGIN INIT INFO
+# Provides: bugzilla-queue
+# Required-Start: $local_fs $syslog
+# Required-Stop: $local_fs $syslog
+# Default-Start: 3 5
+# Default-Stop: 0 1 2 6
+# Short-Description: Start and stop the Bugzilla queue runner.
+# Description: The Bugzilla queue runner (jobqueue.pl) sends any mail
+# that Bugzilla has queued to be sent in the background. If you
+# have enabled the use_mailer_queue parameter in Bugzilla, you
+# must run this daemon.
+### END INIT INFO
+
+NAME=`basename $0`
+
+#################
+# Configuration #
+#################
+
+# This should be the path to your Bugzilla
+BUGZILLA=/var/www/html/bugzilla
+# Who owns the Bugzilla directory and files?
+USER=root
+
+# If you want to pass any options to the daemon (like -d for debugging)
+# specify it here.
+OPTIONS=""
+
+# You can also override the configuration by creating a
+# /etc/sysconfig/bugzilla-queue file so that you don't
+# have to edit this script.
+if [ -r /etc/sysconfig/$NAME ]; then
+ . /etc/sysconfig/$NAME
+fi
+
+##########
+# Script #
+##########
+
+BIN=$BUGZILLA/jobqueue.pl
+if [ ! -x $BIN ]; then
+ echo "$BIN not installed"
+ if [ "$1" = "stop" ]; then
+ exit 0
+ else
+ exit 5
+ fi
+fi
+
+# Source LSB function library.
+. /lib/lsb/init-functions
+
+# Reset status of this service.
+rc_reset
+
+# Return values for all commands but status:
+# 0 - success
+# 1 - generic or unspecified error
+# 2 - invalid or excess argument(s)
+# 3 - unimplemented feature (e.g. "reload")
+# 4 - user had insufficient privileges
+# 5 - program is not installed
+# 6 - program is not configured
+# 7 - program is not running
+# 8--199 - reserved (8--99 LSB, 100--149 distrib, 150--199 appl)
+#
+# Note that starting an already running service, stopping
+# or restarting a not-running service as well as the restart
+# with force-reload (in case signaling is not supported) are
+# considered a success.
+
+case "$1" in
+ start)
+ echo -n "Starting $NAME "
+ # Start daemon with startproc(8). If this fails the return value
+ # is set appropriately by startproc.
+ start_daemon -u $USER $BIN ${OPTIONS} start
+
+ # Remember status and be verbose
+ rc_status -v
+ ;;
+
+ stop)
+ echo -n "Shutting down $NAME "
+ # Stop daemon with killproc(8) and if this fails killproc sets the
+ # return value according to LSB.
+ killproc -TERM $BIN
+
+ # Remember status and be verbose
+ rc_status -v
+ ;;
+
+ status)
+ echo -n "Checking for service $NAME "
+ # Check status with checkproc(8), if process is running checkproc
+ # will return with exit status 0.
+
+ # Return value is slightly different for the status command:
+ # 0 - service up and running
+ # 1 - service dead, but /var/run/ pid file exists
+ # 2 - service dead, but /var/lock/ lock file exists
+ # 3 - service not running (unused)
+ # 4 - service status unknown :-(
+ # 5--199 reserved (5--99 LSB, 100--149 distro, 150--199 appl.)
+
+ # NOTE: checkproc returns LSB compliant status values.
+ checkproc $BIN
+
+ # NOTE: rc_status knows that we called this init script with
+ # "status" option and adapts its messages accordingly.
+ rc_status -v
+
+ # Run jobqueue's own check function too.
+ $BIN check
+ ;;
+
+ restart)
+ # Stop the service and regardless of whether it was running or not,
+ # start it again.
+ $0 stop
+ $0 start
+
+ # Remember status and be quiet.
+ rc_status
+ ;;
+
+ try-restart|condrestart)
+ # Do a restart only if the service was active before.
+ # NOTE: try-restart is now part of LSB (as of 1.9). RH has a
+ # similar command named condrestart.
+ if [ "$1" = "condrestart" ]; then
+ echo "${attn} Use try-restart ${done}(LSB)${attn} rather than condrestart ${warn}(RH)${norm}"
+ fi
+ $0 status
+ if [ $? -eq 0 ]; then
+ $0 restart
+ else
+ rc_reset # Not running is not a failure.
+ fi
+
+ # Remember status and be quiet
+ rc_status
+ ;;
+
+ force-reload)
+ # The jobqueue.pl daemon does not support SIGHUP for reload. Just
+ # restart the service if it is running.
+ echo -n "Reload service $NAME "
+
+ $0 try-restart
+ rc_status
+ ;;
+
+ reload)
+ # The jobqueue.pl daemon does not support SIGHUP for reload.
+ rc_failed 3
+ rc_status -v
+ ;;
+
+ *)
+ echo "Usage: $0 {start|stop|status|try-restart|restart|force-reload|reload}"
+ exit 1
+esac
+
+rc_exit
diff --git a/contrib/bugzilla-submit/README b/contrib/bugzilla-submit/README
new file mode 100644
index 000000000..f9e74b9d4
--- /dev/null
+++ b/contrib/bugzilla-submit/README
@@ -0,0 +1,46 @@
+bugzilla-submit
+===============
+
+Authors: Christian Reis <kiko@async.com.br>
+ Eric Raymond <esr@thyrsus.com>
+
+bugzilla-submit is a simple Python program that creates bugs in a Bugzilla
+instance. It takes as input text resembling message headers (RFC-822
+formatted) via standard input, or optionally a number of commandline
+parameters. It communicates using HTTP, which allows it to work over a
+network.
+
+Requirements
+------------
+
+Its only requirement is Python 2.3 or higher; you should have the
+"python" executable in your path.
+
+Usage Notes
+-----------
+
+* Please constrain testing to your own installation of Bugzilla, or use
+* http://landfill.bugzilla.org/ for testing purposes -- opening test
+* bugs on production instances of Bugzilla is definitely not a good idea
+
+Run "bugzilla-submit --help" for a description of the possible options.
+
+An example input file, named bugdata.txt, is provided. You can pipe it
+in as standard input to bugzilla-submit, providing a Bugzilla URI through
+the command-line.
+
+Note that you must create a ~/.netrc entry to authenticate against the
+Bugzilla instance. The entry's machine field is a *quoted* Bugzilla URI,
+the login field is your ID on that host, and the password field is the
+your password password. An example entry follows:
+
+ machine "http://bugzilla.mozilla.org/"
+ login foo@bar.loo
+ password snarf
+
+Documentation
+-------------
+
+Documentation for bugzilla-submit is provided in Docbook format; see
+bugzilla-submit.xml.
+
diff --git a/contrib/bugzilla-submit/bugdata.txt b/contrib/bugzilla-submit/bugdata.txt
new file mode 100755
index 000000000..fc880165c
--- /dev/null
+++ b/contrib/bugzilla-submit/bugdata.txt
@@ -0,0 +1,13 @@
+Product: FoodReplicator
+Component: Salt
+Version: 1.0
+Priority: P2
+Hardware: PC
+OS: Linux
+Severity: critical
+Summary: Impending electron shortage
+Depends-on: 8
+URL: http://www.google.com/
+
+We need an emergency supply of electrons.
+
diff --git a/contrib/bugzilla-submit/bugzilla-submit b/contrib/bugzilla-submit/bugzilla-submit
new file mode 100755
index 000000000..d98e7de8d
--- /dev/null
+++ b/contrib/bugzilla-submit/bugzilla-submit
@@ -0,0 +1,301 @@
+#!/usr/bin/env python
+#
+# bugzilla-submit: a command-line script to post bugs to a Bugzilla instance
+#
+# Authors: Christian Reis <kiko@async.com.br>
+# Eric S. Raymond <esr@thyrsus.com>
+#
+# This is version 0.5.
+#
+# For a usage hint run bugzilla-submit --help
+#
+# TODO: use RDF output to pick up valid options, as in
+# http://www.async.com.br/~kiko/mybugzilla/config.cgi?ctype=rdf
+
+import sys, string
+
+def error(m):
+ sys.stderr.write("bugzilla-submit: %s\n" % m)
+ sys.stderr.flush()
+ sys.exit(1)
+
+version = string.split(string.split(sys.version)[0], ".")[:2]
+if map(int, version) < [2, 3]:
+ error("you must upgrade to Python 2.3 or higher to use this script.")
+
+import urllib, re, os, netrc, email.Parser, optparse
+
+class ErrorURLopener(urllib.URLopener):
+ """URLopener that handles HTTP 404s"""
+ def http_error_404(self, url, fp, errcode, errmsg, headers, *extra):
+ raise ValueError, errmsg # 'File Not Found'
+
+# Set up some aliases -- partly to hide the less friendly fieldnames
+# behind the names actually used for them in the stock web page presentation,
+# and partly to provide a place for mappings if the Bugzilla fieldnames
+# ever change.
+field_aliases = (('hardware', 'rep_platform'),
+ ('os', 'op_sys'),
+ ('summary', 'short_desc'),
+ ('description', 'comment'),
+ ('depends_on', 'dependson'),
+ ('status', 'bug_status'),
+ ('severity', 'bug_severity'),
+ ('url', 'bug_file_loc'),)
+
+def header_to_field(hdr):
+ hdr = hdr.lower().replace("-", "_")
+ for (alias, name) in field_aliases:
+ if hdr == alias:
+ hdr = name
+ break
+ return hdr
+
+def field_to_header(hdr):
+ hdr = "-".join(map(lambda x: x.capitalize(), hdr.split("_")))
+ for (alias, name) in field_aliases:
+ if hdr == name:
+ hdr = alias
+ break
+ return hdr
+
+def setup_parser():
+ # Take override values from the command line
+ parser = optparse.OptionParser(usage="usage: %prog [options] bugzilla-url")
+ parser.add_option('-b', '--status', dest='bug_status',
+ help='Set the Status field.')
+ parser.add_option('-u', '--url', dest='bug_file_loc',
+ help='Set the URL field.')
+ parser.add_option('-p', '--product', dest='product',
+ help='Set the Product field.')
+ parser.add_option('-v', '--version', dest='version',
+ help='Set the Version field.')
+ parser.add_option('-c', '--component', dest='component',
+ help='Set the Component field.')
+ parser.add_option('-s', '--summary', dest='short_desc',
+ help='Set the Summary field.')
+ parser.add_option('-H', '--hardware', dest='rep_platform',
+ help='Set the Hardware field.')
+ parser.add_option('-o', '--os', dest='op_sys',
+ help='Set the Operating System field.')
+ parser.add_option('-r', '--priority', dest='priority',
+ help='Set the Priority field.')
+ parser.add_option('-x', '--severity', dest='bug_severity',
+ help='Set the Severity field.')
+ parser.add_option('-d', '--description', dest='comment',
+ help='Set the Description field.')
+ parser.add_option('-a', '--assigned-to', dest='assigned_to',
+ help='Set the Assigned-To field.')
+ parser.add_option('-C', '--cc', dest='cc',
+ help='Set the Cc field.')
+ parser.add_option('-k', '--keywords', dest='keywords',
+ help='Set the Keywords field.')
+ parser.add_option('-D', '--depends-on', dest='dependson',
+ help='Set the Depends-On field.')
+ parser.add_option('-B', '--blocked', dest='blocked',
+ help='Set the Blocked field.')
+ parser.add_option('-n', '--no-stdin', dest='read',
+ default=True, action='store_false',
+ help='Suppress reading fields from stdin.')
+ return parser
+
+# Fetch user's credential for access to this Bugzilla instance.
+def get_credentials(bugzilla):
+ # Work around a quirk in the Python implementation.
+ # The URL has to be quoted, otherwise the parser barfs on the colon.
+ # But the parser doesn't strip the quotes.
+ authenticate_on = '"' + bugzilla + '"'
+ try:
+ credentials = netrc.netrc()
+ except netrc.NetrcParseError, e:
+ error("ill-formed .netrc: %s:%s %s" % (e.filename, e.lineno, e.msg))
+ except IOError, e:
+ error("missing .netrc file %s" % str(e).split()[-1])
+ ret = credentials.authenticators(authenticate_on)
+ if not ret:
+ # Okay, the literal string passed in failed. Just to make sure,
+ # try adding/removing a slash after the address and looking
+ # again. We don't know what format was used in .netrc, which is
+ # why this rather hackish approach is necessary.
+ if bugzilla[-1] == "/":
+ authenticate_on = '"' + bugzilla[:-1] + '"'
+ else:
+ authenticate_on = '"' + bugzilla + '/"'
+ ret = credentials.authenticators(authenticate_on)
+ if not ret:
+ # Apparently, an invalid machine URL will cause credentials == None
+ error("no credentials for Bugzilla instance at %s" % bugzilla)
+ return ret
+
+def process_options(options):
+ data = {}
+ # Initialize bug report fields from message on standard input
+ if options.read:
+ message_parser = email.Parser.Parser()
+ message = message_parser.parse(sys.stdin)
+ for (key, value) in message.items():
+ data.update({header_to_field(key) : value})
+ if not 'comment' in data:
+ data['comment'] = message.get_payload()
+
+ # Merge in options from the command line; they override what's on stdin.
+ for (key, value) in options.__dict__.items():
+ if key != 'read' and value != None:
+ data[key] = value
+ return data
+
+def ensure_defaults(data):
+ # Provide some defaults if the user did not supply them.
+ if 'op_sys' not in data:
+ if sys.platform.startswith('linux'):
+ data['op_sys'] = 'Linux'
+ if 'rep_platform' not in data:
+ data['rep_platform'] = 'PC'
+ if 'bug_status' not in data:
+ data['bug_status'] = 'CONFIRMED'
+ if 'bug_severity' not in data:
+ data['bug_severity'] = 'normal'
+ if 'bug_file_loc' not in data:
+ data['bug_file_loc'] = 'http://' # Yes, Bugzilla needs this
+ if 'priority' not in data:
+ data['priority'] = 'Normal'
+
+def validate_fields(data):
+ # Fields for validation
+ required_fields = (
+ "bug_status", "bug_file_loc", "product", "version", "component",
+ "short_desc", "rep_platform", "op_sys", "priority", "bug_severity",
+ "comment",
+ )
+ legal_fields = required_fields + (
+ "assigned_to", "cc", "keywords", "dependson", "blocked",
+ )
+ my_fields = data.keys()
+ for field in my_fields:
+ if field not in legal_fields:
+ error("invalid field: %s" % field_to_header(field))
+ for field in required_fields:
+ if field not in my_fields:
+ error("required field missing: %s" % field_to_header(field))
+
+ if not data['short_desc']:
+ error("summary for bug submission must not be empty")
+
+ if not data['comment']:
+ error("comment for bug submission must not be empty")
+
+#
+# POST-specific functions
+#
+
+def submit_bug_POST(bugzilla, data):
+ # Move the request over the wire
+ postdata = urllib.urlencode(data)
+ try:
+ url = ErrorURLopener().open("%s/post_bug.cgi" % bugzilla, postdata)
+ except ValueError:
+ error("Bugzilla site at %s not found (HTTP returned 404)" % bugzilla)
+ ret = url.read()
+ check_result_POST(ret, data)
+
+def check_result_POST(ret, data):
+ # XXX We can move pre-validation out of here as soon as we pick up
+ # the valid options from config.cgi -- it will become a simple
+ # assertion and ID-grabbing step.
+ #
+ # XXX: We use get() here which may return None, but since the user
+ # might not have provided these options, we don't want to die on
+ # them.
+ version = data.get('version')
+ product = data.get('product')
+ component = data.get('component')
+ priority = data.get('priority')
+ severity = data.get('bug_severity')
+ status = data.get('bug_status')
+ assignee = data.get('assigned_to')
+ platform = data.get('rep_platform')
+ opsys = data.get('op_sys')
+ keywords = data.get('keywords')
+ deps = data.get('dependson', '') + " " + data.get('blocked', '')
+ deps = deps.replace(" ", ", ")
+ # XXX: we should really not be using plain find() here, as it can
+ # match the bug content inadvertedly
+ if ret.find("A legal Version was not") != -1:
+ error("version %r does not exist for component %s:%s" %
+ (version, product, component))
+ if ret.find("A legal Priority was not") != -1:
+ error("priority %r does not exist in "
+ "this Bugzilla instance" % priority)
+ if ret.find("A legal Severity was not") != -1:
+ error("severity %r does not exist in "
+ "this Bugzilla instance" % severity)
+ if ret.find("A legal Status was not") != -1:
+ error("status %r is not a valid creation status in "
+ "this Bugzilla instance" % status)
+ if ret.find("A legal Platform was not") != -1:
+ error("platform %r is not a valid platform in "
+ "this Bugzilla instance" % platform)
+ if ret.find("A legal OS/Version was not") != -1:
+ error("%r is not a valid OS in "
+ "this Bugzilla instance" % opsys)
+ if ret.find("Invalid Username") != -1:
+ error("invalid credentials submitted")
+ if ret.find("Component Needed") != -1:
+ error("the component %r does not exist in "
+ "this Bugzilla instance" % component)
+ if ret.find("Unknown Keyword") != -1:
+ error("keyword(s) %r not registered in "
+ "this Bugzilla instance" % keywords)
+ if ret.find("The product name") != -1:
+ error("product %r does not exist in this "
+ "Bugzilla instance" % product)
+ # XXX: this should be smarter
+ if ret.find("does not exist") != -1:
+ error("could not mark dependencies for bugs %s: one or "
+ "more bugs didn't exist in this Bugzilla instance" % deps)
+ if ret.find("Match Failed") != -1:
+ # XXX: invalid CC hits on this error too
+ error("the bug assignee %r isn't registered in "
+ "this Bugzilla instance" % assignee)
+ # If all is well, return bug number posted
+ if ret.find("process_bug.cgi") == -1:
+ error("could not post bug to %s: are you sure this "
+ "is Bugzilla instance's top-level directory?" % bugzilla)
+ m = re.search("Bug ([0-9]+) Submitted", ret)
+ if not m:
+ print ret
+ error("Internal error: bug id not found; please report a bug")
+ id = m.group(1)
+ print "Bug %s posted." % id
+
+#
+#
+#
+
+if __name__ == "__main__":
+ parser = setup_parser()
+
+ # Parser will print help and exit here if we specified --help
+ (options, args) = parser.parse_args()
+
+ if len(args) != 1:
+ parser.error("missing Bugzilla host URL")
+
+ bugzilla = args[0]
+ data = process_options(options)
+
+ login, account, password = get_credentials(bugzilla)
+ if "@" not in login: # no use even trying to submit
+ error("login %r is invalid (it should be an email address)" % login)
+
+ ensure_defaults(data)
+ validate_fields(data)
+
+ # Attach authentication information
+ data.update({'Bugzilla_login' : login,
+ 'Bugzilla_password' : password,
+ 'GoAheadAndLogIn' : 1,
+ 'form_name' : 'enter_bug'})
+
+ submit_bug_POST(bugzilla, data)
+
diff --git a/contrib/bugzilla-submit/bugzilla-submit.xml b/contrib/bugzilla-submit/bugzilla-submit.xml
new file mode 100755
index 000000000..5c818e268
--- /dev/null
+++ b/contrib/bugzilla-submit/bugzilla-submit.xml
@@ -0,0 +1,221 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!DOCTYPE refentry PUBLIC
+ "-//OASIS//DTD DocBook XML V4.1.2//EN"
+ "docbook/docbookx.dtd">
+<refentry id='bugzilla-submit.1'>
+<refmeta>
+<refentrytitle>bugzilla-submit</refentrytitle>
+<manvolnum>1</manvolnum>
+<refmiscinfo class='date'>Oct 30, 2003</refmiscinfo>
+</refmeta>
+<refnamediv id='name'>
+<refname>bugzilla-submit</refname>
+<refpurpose>post bugs to a Bugzilla instance</refpurpose>
+</refnamediv>
+<refsynopsisdiv id='synopsis'>
+
+<cmdsynopsis>
+ <command>bugzilla-submit</command>
+ <arg choice='opt'>--status <replaceable>bug_status</replaceable></arg>
+ <arg choice='opt'>--url <replaceable>bug_file_loc</replaceable></arg>
+ <arg choice='opt'>--product <replaceable>product</replaceable></arg>
+ <arg choice='opt'>--version <replaceable>version</replaceable></arg>
+ <arg choice='opt'>--component <replaceable>component</replaceable></arg>
+ <arg choice='opt'>--summary <replaceable>short_desc</replaceable></arg>
+ <arg choice='opt'>--hardware <replaceable>rep_platform</replaceable></arg>
+ <arg choice='opt'>--os <replaceable>op_sys</replaceable></arg>
+ <arg choice='opt'>--priority <replaceable>priority</replaceable></arg>
+ <arg choice='opt'>--severity <replaceable>bug_severity</replaceable></arg>
+ <arg choice='opt'>--assigned-to <replaceable>assigned-to</replaceable></arg>
+ <arg choice='opt'>--cc <replaceable>cc</replaceable></arg>
+ <arg choice='opt'>--keywords <replaceable>keywords</replaceable></arg>
+ <arg choice='opt'>--depends-on <replaceable>dependson</replaceable></arg>
+ <arg choice='opt'>--blocked <replaceable>blocked</replaceable></arg>
+ <arg choice='opt'>--description <replaceable>comment</replaceable></arg>
+ <arg choice='opt'>--no-stdin </arg>
+ <arg choice='plain'><replaceable>bugzilla-url</replaceable></arg>
+</cmdsynopsis>
+
+</refsynopsisdiv>
+
+<refsect1 id='description'><title>DESCRIPTION</title>
+
+<para><application>bugzilla-submit</application> is a command-line tool
+for posting bug reports to any instance of Bugzilla. It accepts on
+standard input text resembling an RFC-822 message. The headers of
+that message, and its body, are used to set error-report field values.
+More field values are merged in from command-line options. If required
+fields have not been set, <application>bugzilla-submit</application>
+tries to compute them. Finally, the resulting error report is
+validated. If all required fields are present, and there are no
+illegal fields or values, the report is shipped off to the Mozilla
+instance specified by the single positional argument. Login/password
+credentials are read from the calling user's <filename>~/.netrc</filename>
+file.</para>
+
+<para>bugzilla-submit accepts a single argument:
+<replaceable>bugzilla-url</replaceable>. Its value must match the
+relevant Bugzilla instance's base URL (technically, its
+<replaceable>urlbase</replaceable> param). The program also accepts the
+following options to set or override fields:</para>
+<variablelist>
+<varlistentry>
+<term>-b, --status</term>
+<listitem>
+<para>Set the bug_status field, overriding the Status header from
+standard input if present. (The stock Bugzilla web presentation
+identifies this field as <quote>Status</quote>.)</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>-u, --url</term>
+<listitem>
+<para>Set the bug_file_loc field, overriding the URL header from
+standard input if present. (The stock Bugzilla web presentation
+identifies this field as <quote>URL</quote>.)</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>-p, --product</term>
+<listitem>
+<para>Set the product field, overriding the Product header from
+standard input if necessary.</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>-v, --version</term>
+<listitem><para>Set the version field, overriding the Version header
+from standard input if necessary.</para></listitem>
+</varlistentry>
+<varlistentry>
+<term>-c, --component</term>
+<listitem><para>Set the component field, overriding the Component header
+from standard input if necessary.</para></listitem>
+</varlistentry>
+<varlistentry>
+<term>-s, --summary</term>
+<listitem><para>Set the short_desc field, overriding the Summary header
+from standard input if necessary. (The stock Bugzilla web presentation
+identifies this field as <quote>Summary</quote>.)</para></listitem>
+</varlistentry>
+<varlistentry>
+<term>-H, --hardware</term>
+<listitem><para>Set the rep_platform field, overriding the Hardware header
+from standard input if necessary. (The stock Bugzilla web presentation
+identifies this field as <quote>Hardware</quote>.)</para></listitem>
+</varlistentry>
+<varlistentry>
+<term>-o, --os</term>
+<listitem><para>Set the op_sys field, overriding the OS (Operating
+System) header from standard input if necessary. (The stock Bugzilla web
+presentation also identifies this field as
+<quote>OS</quote>.)</para></listitem>
+</varlistentry>
+<varlistentry>
+<term>-r, --priority</term>
+<listitem><para>Set the priority field, overriding the Priority header
+from standard input if necessary.</para></listitem>
+</varlistentry>
+<varlistentry>
+<term>-x, --severity</term>
+<listitem><para>Set the severity field, overriding the Severity header
+from standard input if necessary.</para></listitem>
+</varlistentry>
+<varlistentry>
+<term>-d, --description</term>
+<listitem><para>Set the comment field, overriding the Description header
+from standard input if necessary. (The stock Bugzilla web presentation
+identifies this field as <quote>Description</quote>.) If there is a
+message body and no Description field and this option is not
+specified, the message body is used as a description.
+</para></listitem>
+</varlistentry>
+<varlistentry>
+<term>-a, --assigned-to</term>
+<listitem>
+<para>Set the optional assigned_to field, overriding the Assigned-To
+header from standard input if necessary.</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>-C, --cc</term>
+<listitem>
+<para>Set the optional cc field, overriding the Cc
+header from standard input if necessary.</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>-k, --keywords</term>
+<listitem>
+<para>Set the optional keywords field, overriding the Keywords
+header from standard input if necessary.</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>-D, --depends-on</term>
+<listitem>
+<para>Set the optional dependson field, overriding the Depends-On
+header from standard input if necessary.</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>-B, --assigned-to</term>
+<listitem>
+<para>Set the optional blocked field, overriding the Blocked
+header from standard input if necessary.</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>-n, --no-stdin</term>
+<listitem><para>Suppress reading fields from standard input.</para></listitem>
+</varlistentry>
+<varlistentry>
+<term>-h, --help</term>
+<listitem><para>Print usage help and exit.</para></listitem>
+</varlistentry>
+</variablelist>
+
+<para>This program will try to deduce OS and Hardware if those are not
+specified. If it fails, validation will fail before shipping the
+report.</para>
+
+<para>There is expected to be a single positional argument following
+any options. It should be the URL of the Bugzilla instance to which
+the bug is to be submitted.</para>
+
+</refsect1>
+<refsect1 id='files'><title>FILES</title>
+<variablelist>
+<varlistentry>
+<term><filename>~/.netrc</filename></term>
+<listitem><para>Must contain an entry in which the machine field is
+the Bugzilla instance URL, the login field is your ID on that host, and the
+password field is the right password. The URL in the machine field
+must be enclosed in double quotes.</para>
+
+<para>For example, if your Bugzilla instance is at
+"http://landfill.bugzilla.org/bztest/", and your login and password
+there are "john@doe.com" and "foo", respectively, your
+<filename>.netrc</filename> entry should look something like:</para>
+
+<screen>
+ machine "http://landfill.bugzilla.org/bztest/"
+ login john@doe.com
+ password foo
+
+</screen>
+
+<para>Note that the machine entry should match exactly the instance URL
+specified to <application>bugzilla-submit</application>.</para>
+
+</listitem>
+</varlistentry>
+</variablelist>
+
+</refsect1>
+<refsect1 id='author'><title>AUTHORS</title>
+<para>Christian Reis &lt;kiko@async.com.br&gt;, Eric S. Raymond
+&lt;esr@thyrsus.com&gt;.</para>
+</refsect1>
+</refentry>
+
diff --git a/contrib/bugzilla_ldapsync.rb b/contrib/bugzilla_ldapsync.rb
new file mode 100755
index 000000000..579f56fc5
--- /dev/null
+++ b/contrib/bugzilla_ldapsync.rb
@@ -0,0 +1,183 @@
+#!/usr/local/bin/ruby
+
+# Queries an LDAP server for all email addresses (tested against Exchange 5.5),
+# and makes nice bugzilla user entries out of them. Also disables Bugzilla users
+# that are not found in LDAP.
+
+require 'ldap'
+require 'dbi'
+require 'getoptlong'
+
+opts = GetoptLong.new(
+ ['--dbname', '-d', GetoptLong::OPTIONAL_ARGUMENT],
+ ['--dbpassword', '-p', GetoptLong::OPTIONAL_ARGUMENT],
+ ['--dbuser', '-u', GetoptLong::OPTIONAL_ARGUMENT],
+ ['--dbpassfile', '-P', GetoptLong::OPTIONAL_ARGUMENT],
+ ['--ldaphost', '-h', GetoptLong::REQUIRED_ARGUMENT],
+ ['--ldapbase', '-b', GetoptLong::OPTIONAL_ARGUMENT],
+ ['--ldapquery', '-q', GetoptLong::OPTIONAL_ARGUMENT],
+ ['--maildomain', '-m', GetoptLong::OPTIONAL_ARGUMENT],
+ ['--noremove', '-n', GetoptLong::OPTIONAL_ARGUMENT],
+ ['--defaultpass', '-D', GetoptLong::OPTIONAL_ARGUMENT],
+ ['--checkmode', '-c', GetoptLong::OPTIONAL_ARGUMENT]
+)
+
+
+# in hash to make it easy
+optHash = Hash.new
+opts.each do |opt, arg|
+ optHash[opt]=arg
+end
+
+# grab password from file if it's an option
+if optHash['--dbpassfile']
+ dbPassword=File.open(optHash['--dbpassfile'], 'r').readlines[0].chomp!
+else
+ dbPassword=optHash['--dbpassword'] || nil
+end
+
+# make bad assumptions.
+dbName = optHash['--dbname'] || 'bugzilla'
+dbUser = optHash['--dbuser'] || 'bugzilla'
+ldapHost = optHash['--ldaphost'] || 'ldap'
+ldapBase = optHash['--ldapbase'] || ''
+mailDomain = optHash['--maildomain'] || `domainname`.chomp!
+ldapQuery = optHash['--ldapquery'] || "(&(objectclass=person)(rfc822Mailbox=*@#{mailDomain}))"
+checkMode = optHash['--checkmode'] || nil
+noRemove = optHash['--noremove'] || nil
+defaultPass = optHash['--defaultpass'] || 'bugzilla'
+
+if (! dbPassword)
+ puts "bugzilla_ldapsync v1.3 (c) 2003 Thomas Stromberg <thomas+bugzilla@stromberg.org>"
+ puts ""
+ puts " -d | --dbname name of MySQL database [#{dbName}]"
+ puts " -u | --dbuser username for MySQL database [#{dbUser}]"
+ puts " -p | --dbpassword password for MySQL user [#{dbPassword}]"
+ puts " -P | --dbpassfile filename containing password for MySQL user"
+ puts " -h | --ldaphost hostname for LDAP server [#{ldapHost}]"
+ puts " -b | --ldapbase Base of LDAP query, for instance, o=Bugzilla.com"
+ puts " -q | --ldapquery LDAP query, uses maildomain [#{ldapQuery}]"
+ puts " -m | --maildomain e-mail domain to use records from"
+ puts " -n | --noremove do not remove Bugzilla users that are not in LDAP"
+ puts " -c | --checkmode checkmode, does not perform any SQL changes"
+ puts " -D | --defaultpass default password for new users [#{defaultPass}]"
+ puts
+ puts "example:"
+ puts
+ puts " bugzilla_ldapsync.rb -c -u taskzilla -P /tmp/test -d taskzilla -h bhncmail -m \"bowebellhowell.com\""
+ exit
+end
+
+
+if (checkMode)
+ puts '(checkmode enabled, no SQL writes will actually happen)'
+ puts "ldapquery is #{ldapQuery}"
+ puts
+end
+
+
+bugzillaUsers = Hash.new
+ldapUsers = Hash.new
+encPassword = defaultPass.crypt('xx')
+sqlNewUser = "INSERT INTO profiles VALUES ('', ?, '#{encPassword}', ?, '', 1, NULL, '0000-00-00 00:00:00');"
+
+# presumes that the MySQL database is local.
+dbh = DBI.connect("DBI:Mysql:#{dbName}", dbUser, dbPassword)
+
+# select all e-mail addresses where there is no disabledtext defined. Only valid users, please!
+dbh.select_all('select login_name, realname, disabledtext from profiles') { |row|
+ login = row[0].downcase
+ bugzillaUsers[login] = Hash.new
+ bugzillaUsers[login]['desc'] = row[1]
+ bugzillaUsers[login]['disabled'] = row[2]
+ #puts "bugzilla has #{login} - \"#{bugzillaUsers[login]['desc']}\" (#{bugzillaUsers[login]['disabled']})"
+}
+
+
+LDAP::Conn.new(ldapHost, 389).bind{|conn|
+ sub = nil
+ # perform the query, but only get the e-mail address, location, and name returned to us.
+ conn.search(ldapBase, LDAP::LDAP_SCOPE_SUBTREE, ldapQuery,
+ ['rfc822Mailbox', 'physicalDeliveryOfficeName', 'cn']) { |entry|
+
+ # Get the users first (primary) e-mail address, but I only want what's before the @ sign.
+ entry.vals("rfc822Mailbox")[0] =~ /([\w\.-]+)\@/
+ email = $1
+
+ # We put the officename in the users description, and nothing otherwise.
+ if entry.vals("physicalDeliveryOfficeName")
+ location = entry.vals("physicalDeliveryOfficeName")[0]
+ else
+ location = ''
+ end
+
+ # for whatever reason, we get blank entries. Do some double checking here.
+ if (email && (email.length > 4) && (location !~ /Generic/) && (entry.vals("cn")))
+ if (location.length > 2)
+ desc = entry.vals("cn")[0] + " (" + location + ")"
+ else
+ desc = entry.vals("cn")[0]
+ end
+
+ # take care of the whitespace.
+ desc.sub!("\s+$", "")
+ desc.sub!("^\s+", "")
+
+ # dumb hack. should be properly escaped, and apostrophes should never ever ever be in email.
+ email.sub!("\'", "%")
+ email.sub!('%', "\'")
+ email=email.downcase
+ ldapUsers[email.downcase] = Hash.new
+ ldapUsers[email.downcase]['desc'] = desc
+ ldapUsers[email.downcase]['disabled'] = nil
+ #puts "ldap has #{email} - #{ldapUsers[email.downcase]['desc']}"
+ end
+ }
+}
+
+# This is the loop that takes the users that we found in Bugzilla originally, and
+# checks to see if they are still in the LDAP server. If they are not, away they go!
+
+ldapUsers.each_key { |user|
+ # user does not exist at all.
+ #puts "checking ldap user #{user}"
+ if (! bugzillaUsers[user])
+ puts "+ Adding #{user} - #{ldapUsers[user]['desc']}"
+
+ if (! checkMode)
+ dbh.do(sqlNewUser, user, ldapUsers[user]['desc'])
+ end
+
+ # short-circuit now.
+ next
+ end
+
+ if (bugzillaUsers[user]['desc'] != ldapUsers[user]['desc'])
+ puts "* Changing #{user} from \"#{bugzillaUsers[user]['desc']}\" to \"#{ldapUsers[user]['desc']}\""
+ if (! checkMode)
+ # not efficient.
+ dbh.do("UPDATE profiles SET realname = ? WHERE login_name = ?", ldapUsers[user]['desc'], user)
+ end
+ end
+
+ if (bugzillaUsers[user]['disabled'].length > 0)
+ puts "+ Enabling #{user} (was \"#{bugzillaUsers[user]['disabled']}\")"
+ if (! checkMode)
+ dbh.do("UPDATE profiles SET disabledtext = NULL WHERE login_name=\"#{user}\"")
+ end
+ end
+}
+
+if (! noRemove)
+ bugzillaUsers.each_key { |user|
+ if ((bugzillaUsers[user]['disabled'].length < 1) && (! ldapUsers[user]))
+ puts "- Disabling #{user} (#{bugzillaUsers[user]['disabled']})"
+
+ if (! checkMode)
+ dbh.do("UPDATE profiles SET disabledtext = \'auto-disabled by ldap sync\' WHERE login_name=\"#{user}\"")
+ end
+ end
+ }
+end
+dbh.disconnect
+
diff --git a/contrib/bz_webservice_demo.pl b/contrib/bz_webservice_demo.pl
new file mode 100755
index 000000000..104151d85
--- /dev/null
+++ b/contrib/bz_webservice_demo.pl
@@ -0,0 +1,420 @@
+#!/usr/bin/perl -w
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the “License”); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an “AS
+# IS” basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s): Marc Schumann <wurblzap@gmail.com>
+# Mads Bondo Dydensborg <mbd@dbc.dk>
+
+=head1 NAME
+
+bz_webservice_demo.pl - Show how to talk to Bugzilla via XMLRPC
+
+=head1 SYNOPSIS
+
+C<bz_webservice_demo.pl [options]>
+
+C<bz_webservice_demo.pl --help> for detailed help
+
+=cut
+
+use strict;
+use lib qw(lib);
+use Getopt::Long;
+use Pod::Usage;
+use File::Basename qw(dirname);
+use File::Spec;
+use HTTP::Cookies;
+use XMLRPC::Lite;
+
+# If you want, say “use Bugzilla::WebService::Constants” here to get access
+# to Bugzilla's web service error code constants.
+# If you do this, remember to issue a “use lib” pointing to your Bugzilla
+# installation directory, too.
+
+my $help;
+my $Bugzilla_uri;
+my $Bugzilla_login;
+my $Bugzilla_password;
+my $Bugzilla_remember;
+my $bug_id;
+my $product_name;
+my $create_file_name;
+my $legal_field_values;
+my $add_comment;
+my $private;
+my $work_time;
+my $fetch_extension_info = 0;
+
+GetOptions('help|h|?' => \$help,
+ 'uri=s' => \$Bugzilla_uri,
+ 'login:s' => \$Bugzilla_login,
+ 'password=s' => \$Bugzilla_password,
+ 'rememberlogin!' => \$Bugzilla_remember,
+ '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
+ ) or pod2usage({'-verbose' => 0, '-exitval' => 1});
+
+=head1 OPTIONS
+
+=over
+
+=item --help, -h, -?
+
+Print a short help message and exit.
+
+=item --uri
+
+URI to Bugzilla's C<xmlrpc.cgi> script, along the lines of
+C<http://your.bugzilla.installation/path/to/bugzilla/xmlrpc.cgi>.
+
+=item --login
+
+Bugzilla login name. Specify this together with B<--password> in order to log in.
+
+Specify this without a value in order to log out.
+
+=item --password
+
+Bugzilla password. Specify this together with B<--login> in order to log in.
+
+=item --rememberlogin
+
+Gives access to Bugzilla's "Bugzilla_remember" option.
+Specify this option while logging in to do the same thing as ticking the
+C<Bugzilla_remember> box on Bugilla's log in form.
+Don't specify this option to do the same thing as unchecking the box.
+
+See Bugzilla's rememberlogin parameter for details.
+
+=item --bug_id
+
+Pass a bug ID to have C<bz_webservice_demo.pl> do some bug-related test calls.
+
+=item --product_name
+
+Pass a product name to have C<bz_webservice_demo.pl> do some product-related
+test calls.
+
+=item --create
+
+Specify a file that contains settings for the creating of a new bug.
+
+=item --field
+
+Pass a field name to get legal values for this field. It must be either a
+global select field (such as bug_status, resolution, rep_platform, op_sys,
+priority, bug_severity) or a custom select field.
+
+=item --comment
+
+A comment to add to a bug identified by B<--bug_id>. You must also pass a B<--login>
+and B<--password> to log in to Bugzilla.
+
+=item --private
+
+An optional non-zero value to specify B<--comment> as private.
+
+=item --worktime
+
+An optional double precision number specifying the work time for B<--comment>.
+
+=item --extension_info
+
+If specified on the command line, the script returns the information about the
+extensions that are installed.
+
+=back
+
+=head1 DESCRIPTION
+
+=cut
+
+pod2usage({'-verbose' => 1, '-exitval' => 0}) if $help;
+_syntaxhelp('URI unspecified') unless $Bugzilla_uri;
+
+# We will use this variable for SOAP call results.
+my $soapresult;
+
+# We will use this variable for function call results.
+my $result;
+
+# Open our cookie jar. We save it into a file so that we may re-use cookies
+# to avoid the need of logging in every time. You're encouraged, but not
+# required, to do this in your applications, too.
+# Cookies are only saved if Bugzilla's rememberlogin parameter is set to one of
+# - on
+# - defaulton (and you didn't pass 0 as third parameter to User.login)
+# - defaultoff (and you passed 1 as third parameter to User.login)
+my $cookie_jar =
+ new HTTP::Cookies('file' => File::Spec->catdir(dirname($0), 'cookies.txt'),
+ 'autosave' => 1);
+
+=head2 Initialization
+
+Using the XMLRPC::Lite class, you set up a proxy, as shown in this script.
+Bugzilla's XMLRPC URI ends in C<xmlrpc.cgi>, so your URI looks along the lines
+of C<http://your.bugzilla.installation/path/to/bugzilla/xmlrpc.cgi>.
+
+=cut
+
+my $proxy = XMLRPC::Lite->proxy($Bugzilla_uri,
+ 'cookie_jar' => $cookie_jar);
+
+=head2 Checking Bugzilla's version
+
+To make sure the Bugzilla you're connecting to supports the methods you wish to
+call, you may want to compare the result of C<Bugzilla.version> to the
+minimum required version your application needs.
+
+=cut
+
+$soapresult = $proxy->call('Bugzilla.version');
+_die_on_fault($soapresult);
+print 'Connecting to a Bugzilla of version ' . $soapresult->result()->{version} . ".\n";
+
+=head2 Checking Bugzilla's timezone
+
+To make sure that you understand the dates and times that Bugzilla returns to you, you may want to call C<Bugzilla.timezone>.
+
+=cut
+
+$soapresult = $proxy->call('Bugzilla.timezone');
+_die_on_fault($soapresult);
+print 'Bugzilla\'s timezone is ' . $soapresult->result()->{timezone} . ".\n";
+
+=head2 Getting Extension Information
+
+Returns all the information any extensions have decided to provide to the webservice.
+
+=cut
+
+if ($fetch_extension_info) {
+ $soapresult = $proxy->call('Bugzilla.extensions');
+ _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 Logging In and Out
+
+=head3 Using Bugzilla's Environment Authentication
+
+Use a
+C<http://login:password@your.bugzilla.installation/path/to/bugzilla/xmlrpc.cgi>
+style URI.
+You don't log out if you're using this kind of authentication.
+
+=head3 Using Bugzilla's CGI Variable Authentication
+
+Use the C<User.login> and C<User.logout> calls to log in and out, as shown
+in this script.
+
+The C<Bugzilla_remember> parameter is optional.
+If omitted, Bugzilla's defaults apply (as specified by its C<rememberlogin>
+parameter).
+
+Bugzilla hands back cookies you'll need to pass along during your work calls.
+
+=cut
+
+if (defined($Bugzilla_login)) {
+ if ($Bugzilla_login ne '') {
+ # Log in.
+ $soapresult = $proxy->call('User.login',
+ { login => $Bugzilla_login,
+ password => $Bugzilla_password,
+ remember => $Bugzilla_remember } );
+ _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 Retrieving Bug Information
+
+Call C<Bug.get> with the ID of the bug you want to know more of.
+The call will return a C<Bugzilla::Bug> object.
+
+Note: You can also use "Bug.get_bugs" for compatibility with Bugzilla 3.0 API.
+
+=cut
+
+if ($bug_id) {
+ $soapresult = $proxy->call('Bug.get', { ids => [$bug_id] });
+ _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
+
+Call C<Product.get_product> with the name of the product you want to know more
+of.
+The call will return a C<Bugzilla::Product> object.
+
+=cut
+
+if ($product_name) {
+ $soapresult = $proxy->call('Product.get_product', $product_name);
+ _die_on_fault($soapresult);
+ $result = $soapresult->result;
+
+ if (ref($result) eq 'HASH') {
+ foreach (keys(%$result)) {
+ print "$_: $$result{$_}\n";
+ }
+ }
+ else {
+ print "$result\n";
+ }
+}
+
+=head2 Creating A Bug
+
+Call C<Bug.create> with the settings read from the file indicated on
+the command line. The file must contain a valid anonymous hash to use
+as argument for the call to C<Bug.create>.
+The call will return a hash with a bug id for the newly created bug.
+
+=cut
+
+if ($create_file_name) {
+ $soapresult = $proxy->call('Bug.create', do "$create_file_name" );
+ _die_on_fault($soapresult);
+ $result = $soapresult->result;
+
+ if (ref($result) eq 'HASH') {
+ foreach (keys(%$result)) {
+ print "$_: $$result{$_}\n";
+ }
+ }
+ else {
+ print "$result\n";
+ }
+
+}
+
+=head2 Getting Legal Field Values
+
+Call C<Bug.legal_values> with the name of the field (including custom
+select fields). The call will return a reference to an array with the
+list of legal values for this field.
+
+=cut
+
+if ($legal_field_values) {
+ $soapresult = $proxy->call('Bug.legal_values', {field => $legal_field_values} );
+ _die_on_fault($soapresult);
+ $result = $soapresult->result;
+
+ print join("\n", @{$result->{values}}) . "\n";
+}
+
+=head2 Adding a comment to a bug
+
+Call C<Bug.add_comment> with the bug id, the comment text, and optionally the number
+of hours you worked on the bug, and a boolean indicating if the comment is private
+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});
+ _die_on_fault($soapresult);
+ print "Comment added.\n";
+ }
+ else {
+ print "A --bug_id must be supplied to add a comment.";
+ }
+}
+
+=head1 NOTES
+
+=head2 Character Set Encoding
+
+Make sure that your application either uses the same character set
+encoding as Bugzilla does, or that it converts correspondingly when using the
+web service API.
+By default, Bugzilla uses UTF-8 as its character set encoding.
+
+=head2 Format For Create File
+
+The create format file is a piece of Perl code, that should look something like
+this:
+
+ {
+ product => "TestProduct",
+ component => "TestComponent",
+ summary => "TestBug - created from bz_webservice_demo.pl",
+ version => "unspecified",
+ description => "This is a description of the bug... hohoho",
+ op_sys => "All",
+ platform => "All",
+ priority => "P4",
+ severity => "normal"
+ };
+
+=head1 SEE ALSO
+
+There are code comments in C<bz_webservice_demo.pl> which might be of further
+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";
+ }
+}
+
+sub _syntaxhelp {
+ my $msg = shift;
+
+ print "Error: $msg\n";
+ pod2usage({'-verbose' => 0, '-exitval' => 1});
+}
diff --git a/contrib/bzdbcopy.pl b/contrib/bzdbcopy.pl
new file mode 100755
index 000000000..8491238b5
--- /dev/null
+++ b/contrib/bzdbcopy.pl
@@ -0,0 +1,254 @@
+#!/usr/bin/perl -w
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved.
+# Portions created by Everything Solved are Copyright (C) 2006
+# Everything Solved. All Rights Reserved.
+#
+# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+
+use strict;
+use lib qw(. lib);
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::DB;
+use Bugzilla::Install::Util qw(indicate_progress);
+use Bugzilla::Util;
+
+#####################################################################
+# User-Configurable Settings
+#####################################################################
+
+# 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_PASSWORD => '';
+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_PASSWORD => '';
+use constant TARGET_DB_HOST => 'localhost';
+
+#####################################################################
+# MAIN SCRIPT
+#####################################################################
+
+print "Connecting to the '" . SOURCE_DB_NAME . "' source database on "
+ . SOURCE_DB_TYPE . "...\n";
+my $source_db = Bugzilla::DB::_connect(SOURCE_DB_TYPE, SOURCE_DB_HOST,
+ SOURCE_DB_NAME, undef, undef, SOURCE_DB_USER, SOURCE_DB_PASSWORD);
+# Don't read entire tables into memory.
+if (SOURCE_DB_TYPE eq 'Mysql') {
+ $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;
+}
+
+print "Connecting to the '" . TARGET_DB_NAME . "' target database on "
+ . TARGET_DB_TYPE . "...\n";
+my $target_db = Bugzilla::DB::_connect(TARGET_DB_TYPE, TARGET_DB_HOST,
+ TARGET_DB_NAME, undef, undef, TARGET_DB_USER, TARGET_DB_PASSWORD);
+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,
+# and it may otherwise fail in that situation (that is, the tables
+# may not exist in the target DB).
+#
+# We don't want to copy over the bz_schema table's contents, though.
+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");
+ }
+ }
+ }
+
+ 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 });
+ }
+
+ # 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 "Committing changes to the target database...\n";
+$target_db->bz_commit_transaction();
+$target_db->bz_setup_foreign_keys();
+
+print "All done! Make sure to run checksetup.pl on the new DB.\n";
+$source_db->disconnect;
+$target_db->disconnect;
+
+1;
+
+__END__
+
+=head1 NAME
+
+bzdbcopy.pl - Copies data from one Bugzilla database to another.
+
+=head1 DESCRIPTION
+
+The intended use of this script is to copy data from an installation
+running on one DB platform to an installation running on another
+DB platform.
+
+It must be run from the directory containing your Bugzilla installation.
+That means if this script is in the contrib/ directory, you should
+be running it as: C<./contrib/bzdbcopy.pl>
+
+Note: Both schemas must already exist and be B<IDENTICAL>. (That is,
+they must have both been created/updated by the same version of
+checksetup.pl.) This script will B<DESTROY ALL CURRENT DATA> in the
+target database.
+
+Both Schemas must be at least from Bugzilla 2.19.3, but if you're
+running a Bugzilla from before 2.20rc2, you'll need the patch at:
+L<http://bugzilla.mozilla.org/show_bug.cgi?id=300311> in order to
+be able to run this script.
+
+Before you using it, you have to correctly set all the variables
+in the "User-Configurable Settings" section at the top of the script.
+The C<SOURCE> settings are for the database you're copying from, and
+the C<TARGET> settings are for the database you're copying to. The
+C<DB_TYPE> is the name of a DB driver from the F<Bugzilla/DB/> directory.
+
diff --git a/contrib/cmdline/bugcount b/contrib/cmdline/bugcount
new file mode 100755
index 000000000..b41412a12
--- /dev/null
+++ b/contrib/cmdline/bugcount
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+thisdir=`dirname "$0"`
+bugids=`$thisdir/bugids "$@"`
+if test "$?" != "0"; then echo "$bugids" 1>&2; exit 1; fi
+
+echo "$bugids" | wc -w
diff --git a/contrib/cmdline/bugids b/contrib/cmdline/bugids
new file mode 100755
index 000000000..6bb54213a
--- /dev/null
+++ b/contrib/cmdline/bugids
@@ -0,0 +1,32 @@
+#!/bin/sh
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is
+# Andreas Franke <afranke@mathweb.org>.
+# Corporation. Portions created by Andreas Franke are
+# Copyright (C) 2001,2005 Andreas Franke. All
+# Rights Reserved.
+#
+# Contributor(s):
+
+thisdir=`dirname "$0"`
+buglist="$thisdir/buglist"
+csvfile="$thisdir/buglist.csv"
+
+$thisdir/buglist "$@" 2>&1 1>${csvfile}
+if test "$?" != "0"; then cat "$csvfile" 1>&2; exit 1; fi
+
+# 1. use 'awk' to select the first column (bug_id)
+# 2. use 'grep -v' to remove the first line with the column headers
+# 3. use backquotes & 'echo' to get all values in one line, space separated
+echo `cat ${csvfile} | awk -F, '{printf $1 "\n"}' | grep -v bug_id`
diff --git a/contrib/cmdline/buglist b/contrib/cmdline/buglist
new file mode 100755
index 000000000..4bd5f20b5
--- /dev/null
+++ b/contrib/cmdline/buglist
@@ -0,0 +1,31 @@
+#!/bin/sh
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is
+# Andreas Franke <afranke@mathweb.org>.
+# Corporation. Portions created by Andreas Franke are
+# Copyright (C) 2001,2005 Andreas Franke. All
+# Rights Reserved.
+#
+# Contributor(s):
+
+defaultcolumnlist="severity priority platform status resolution target_milestone status_whiteboard keywords summaryfull"
+
+thisdir=`dirname "$0"`
+query=`$thisdir/makequery "$@"`
+if test "$?" != "0"; then exit 1; fi
+
+outputfile="/dev/stdout"
+#outputfile="buglist.html"
+#\rm -f ${outputfile}
+wget -q -O ${outputfile} --header="Cookie: COLUMNLIST=${COLUMNLIST-${defaultcolumnlist}}" "${query}"
diff --git a/contrib/cmdline/bugs b/contrib/cmdline/bugs
new file mode 100755
index 000000000..2e8655876
--- /dev/null
+++ b/contrib/cmdline/bugs
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+thisdir=`dirname "$0"`
+bugids=`$thisdir/bugids "$@"` || echo "$bugids" 1>&2 && exit 1
+
+echo "$bugids" | sed -e 's/ /\,/g'
diff --git a/contrib/cmdline/bugslink b/contrib/cmdline/bugslink
new file mode 100755
index 000000000..ee7d5c588
--- /dev/null
+++ b/contrib/cmdline/bugslink
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+thisdir=`dirname "$0"`
+bugids=`$thisdir/bugids "$@"` || echo "$bugids" 1>&2 && exit 1
+
+bugids=`echo "$bugids" | sed -e 's/ /\,/g'`
+echo "https://bugzilla.mozilla.org/buglist.cgi?ctype=html&bug_id=$bugids"
+
diff --git a/contrib/cmdline/makequery b/contrib/cmdline/makequery
new file mode 100755
index 000000000..b34efb841
--- /dev/null
+++ b/contrib/cmdline/makequery
@@ -0,0 +1,108 @@
+#!/bin/sh
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is
+# Andreas Franke <afranke@mathweb.org>.
+# Corporation. Portions created by Andreas Franke are
+# Copyright (C) 2001,2005 Andreas Franke. All
+# Rights Reserved.
+#
+# Contributor(s):
+
+conf="`dirname $0`/query.conf"
+
+query="https://bugzilla.mozilla.org/buglist.cgi?ctype=csv"
+
+chart=0
+and=0
+while test "X$1" != "X"; do
+ arg="$1"
+ shift
+ if test 0 != `expr "X$arg" : 'X--[^=]*\$'`; then
+ # long option: --name val (without '=')
+ name=`expr "X$arg" : 'X--\(.*\)'`
+ val="$1"
+ shift
+ elif test 0 != `expr "X$arg" : 'X--[^=][^=]*='`; then
+ # long option: --name=val
+ name=`expr "X$arg" : 'X--\([^=]*\)'`
+ val=`expr "X$arg" : 'X--[^=]*=\(.*\)'`
+ elif test 0 != `expr "X$arg" : 'X-[a-zA-Z]\$'`; then
+ # short option like -X foo (with space in between)
+ name=`expr "X$arg" : 'X-\(.\)'`
+ val="$1"
+ shift
+ elif test 0 != `expr "X$arg" : 'X-[a-zA-Z]='`; then
+ # reject things like -X=foo
+ echo "Unrecognized option $arg" 1>&2
+ echo "Use -Xfoo or -X foo instead of -X=foo" 1>&2
+ exit 1
+ elif test 0 != `expr "X$arg" : 'X-[a-zA-Z]'`; then
+ # short option like -Xfoo (without space)
+ name=`expr "X$arg" : 'X-\(.\)'`
+ val=`expr "X$arg" : 'X-.\(.*\)'`
+ else
+ name="default"
+ val="$arg"
+ #echo "Unrecognized option $arg" 1>&2
+ #exit 1
+ fi
+
+ # plausibility check: val must not be empty, nor start with '-'
+ if test "X$val" = "X"; then
+ echo "No value found for '$name'!" 1>&2
+ exit 1
+ elif test 0 != `expr "X$val" : "X-"` && \
+ test 0 = `expr "X$val" : "X---"`; then
+ echo "Suspicious value for '$name': '$val' looks like an option!" 1>&2
+ exit 1
+ fi
+
+ # find field and comparison type for option ${name}
+ field=`grep "\"$name\"" "$conf" | awk '{printf $1}'`
+ type=`grep "\"$name\"" "$conf" | awk '{printf $2}'`
+ if test "X$field" = "X" || test "X$type" = "X"; then
+ if test "X$name" = "Xdefault"; then
+ echo 1>&2 "Error: unexpected argument '$arg'"
+ cat 1>&2 <<EOF
+Use short options like -P1 or long options like --priority=1 ,
+or enable the 'default' behaviour in the 'query.conf' file.
+EOF
+ else
+ echo "Unknown field name '$name'." 1>&2
+ fi
+ exit 1
+ fi
+
+ # split val into comma-separated alternative values
+ or=0
+ while test "X$val" != "X"; do
+ # val1 gets everything before the first comma; val gets the rest
+ if test 0 != `expr "X$val" : 'X[^,]*,'`; then
+ val1=`expr "X$val" : 'X\([^,]*\),'`
+ val=`expr "X$val" : 'X[^,]*,\(.*\)'`
+ else
+ val1="$val"
+ val=""
+ fi
+ # append to query
+ query="${query}&field${chart}-${and}-${or}=${field}"
+ query="${query}&type${chart}-${and}-${or}=${type}"
+ query="${query}&value${chart}-${and}-${or}=${val1}"
+ #echo "----- ${name} : ${field} : ${type} : ${val1} -----" 1>&2
+ or=`expr ${or} + 1`
+ done
+ chart=`expr ${chart} + 1`
+done
+
+echo "${query}"
diff --git a/contrib/cmdline/query.conf b/contrib/cmdline/query.conf
new file mode 100755
index 000000000..87390cd3f
--- /dev/null
+++ b/contrib/cmdline/query.conf
@@ -0,0 +1,49 @@
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is
+# Andreas Franke <afranke@mathweb.org>.
+# Corporation. Portions created by Andreas Franke are
+# Copyright (C) 2001 Andreas Franke. All
+# Rights Reserved.
+#
+# Contributor(s):
+
+#
+# This is `query.conf', the config file for `buglist'.
+#
+# Columns: 1: field_name, 2: comparison_type, 3: cmd-line options
+#
+bug_status substring "s","status"
+resolution substring "r","resolution"
+rep_platform substring "p","platform"
+op_sys substring "o","os","opsys"
+priority substring "P","priority"
+bug_severity substring "S","severity"
+assigned_to substring "A","O","owner","assignedto"
+reporter substring "R","reporter"
+qa_contact substring "Q","qa","qacontact"
+cc substring "C","cc"
+product substring "product"
+version substring "V","version"
+component substring "c","component"
+target_milestone substring "M","milestone"
+short_desc substring "summary","defaultREMOVEME"
+longdesc substring "d","description","longdesc"
+bug_file_loc substring "u","url"
+status_whiteboard substring "w","whiteboard"
+keywords substring "k","K","keywords"
+attachments.description substring "attachdesc"
+attach_data.thedata substring "attachdata"
+attachments.mimetype substring "attachmime"
+dependson substring # bug 30823
+blocked substring # bug 30823
diff --git a/contrib/console.pl b/contrib/console.pl
new file mode 100755
index 000000000..408fdef61
--- /dev/null
+++ b/contrib/console.pl
@@ -0,0 +1,186 @@
+#!/usr/bin/perl -w
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is the National Aeronautics
+# and Space Administration of the United States Government.
+# Portions created by the Initial Developer are Copyright (C) 2010
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s): Jesse Clark <jjclark1982@gmail.com>
+
+use File::Basename;
+BEGIN { chdir dirname($0) . "/.."; }
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Util;
+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::Maxdepth = 1;
+$Data::Dumper::Deparse = 0;
+
+my $sysname = get_text('term', {term => 'Bugzilla'});
+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);
+ }
+}
+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 ();
+}
+
+# p: print as a single string (normal behavior puts list items on separate lines)
+sub p {
+ local $^W=0; # 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;
+ }
+}
+
+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;
+ }
+}
+
+sub read_history {
+ 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;
+ }
+}
+
+__END__
+
+=head1 NAME
+
+B<console.pl> - command-line interface to Bugzilla API
+
+=head1 SYNOPSIS
+
+$ B<contrib/console.pl>
+
+Bugzilla> B<b(5)-E<gt>short_desc>
+
+=over 8
+
+"Misplaced Widget"
+
+=back
+
+Bugzilla> B<$f = f "cf_subsystem"; scalar @{$f-E<gt>legal_values}>
+
+=over 8
+
+177
+
+=back
+
+Bugzilla> B<p filter html_light, "1 E<lt> 2 E<lt>bE<gt>3E<lt>/bE<gt>">
+
+=over 8
+
+1 &lt; 2 E<lt>bE<gt>3E<lt>/bE<gt>
+
+=back
+
+Bugzilla> B<$u = u 5; $u-E<gt>groups; d $u>
+
+=head1 DESCRIPTION
+
+Loads Bugzilla packages and prints expressions with Data::Dumper.
+Useful for checking results of Bugzilla API calls without opening
+a debug file from a cgi.
diff --git a/contrib/convert-workflow.pl b/contrib/convert-workflow.pl
new file mode 100755
index 000000000..60029f67a
--- /dev/null
+++ b/contrib/convert-workflow.pl
@@ -0,0 +1,135 @@
+#!/usr/bin/perl -w
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by the Initial Developer are Copyright (C) 2009 the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+use strict;
+use warnings;
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Config qw(:admin);
+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' });
+
+if ($confirmed and $in_progress) {
+ print "You are already using the new workflow.\n";
+ exit 1;
+}
+my $enable_unconfirmed = 0;
+my $result = GetOptions("enable-unconfirmed" => \$enable_unconfirmed);
+
+print <<END;
+WARNING: This will convert the status of all bugs using the following
+system:
+
+ "NEW" will become "CONFIRMED"
+ "ASSIGNED" will become "IN_PROGRESS"
+ "REOPENED" will become "CONFIRMED" (and the "REOPENED" status will be removed)
+ "CLOSED" will become "VERIFIED" (and the "CLOSED" status will be removed)
+
+This change will be immediate. The history of each bug will also be changed
+so that it appears that these statuses were always in existence.
+
+Emails will not be sent for the change.
+
+END
+if ($enable_unconfirmed) {
+ 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
+}
+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'],
+);
+
+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";
+ $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.
+ if (my $existing = new Bugzilla::Status({ name => $to })) {
+ $dbh->do('DELETE FROM bug_status WHERE value = ?', undef, $from);
+ }
+ # Otherwise, rename the old status to the new one.
+ else {
+ $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');
+}
+$dbh->bz_commit_transaction();
+
+print <<END;
+Done. There are some things you may want to fix, now:
+
+ * You may want to run ./collectstats.pl --regenerate to regenerate
+ data for the Old Charts system.
+ * You may have to fix the Status Workflow using the Status Workflow
+ panel in "Administration".
+ * You will probably want to update the "mybugstemplate" and "defaultquery"
+ parameters using the Parameters panel in "Administration". (Just
+ resetting them to the default will work.)
+END
diff --git a/contrib/cvs-update.pl b/contrib/cvs-update.pl
new file mode 100755
index 000000000..32cb5861b
--- /dev/null
+++ b/contrib/cvs-update.pl
@@ -0,0 +1,51 @@
+#!/usr/bin/perl -w
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Dawn Endico <endico@mozilla.org>
+# Jacob Steenhagen <jake@bugzilla.org>
+# Jouni Heikniemi <jouni@heikniemi.net>
+
+# Keep a record of all cvs updates made from a given directory.
+#
+# Later, if changes need to be backed out, look at the log file
+# and run the cvs command with the date that you want to back
+# out to. (Probably the second to last entry).
+
+# Because this script lives in contrib, you may want to
+# ln -s contrib/cvs-update.pl cvs-update.pl
+# from your bugzilla install directory so you can run
+# the script easily from there (./cvs-update.pl)
+
+#DATE=`date +%e/%m/%Y\ %k:%M:%S\ %Z`
+
+my ($second, $minute, $hour, $day, $month, $year) = gmtime;
+my $date = sprintf("%04d-%02d-%02d %d:%02d:%02dZ",
+ $year+1900, $month+1, $day, $hour, $minute, $second);
+my $cmd = "cvs -q update -dP";
+open LOG, ">>cvs-update.log" or die("Couldn't open cvs update log!");
+print LOG "$cmd -D \"$date\"\n";
+close LOG;
+system("$cmd -A");
+
+# sample log file
+#cvs update -P -D "11/04/2000 20:22:08 PDT"
+#cvs update -P -D "11/05/2000 20:22:22 PDT"
+#cvs update -P -D "11/07/2000 20:26:29 PDT"
+#cvs update -P -D "11/08/2000 20:27:10 PDT"
diff --git a/contrib/extension-convert.pl b/contrib/extension-convert.pl
new file mode 100755
index 000000000..88718cf83
--- /dev/null
+++ b/contrib/extension-convert.pl
@@ -0,0 +1,303 @@
+#!/usr/bin/perl -w
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by the Initial Developer are Copyright (C) 2009 the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+use strict;
+use warnings;
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Util qw(trim);
+
+use File::Basename;
+use File::Copy qw(move);
+use File::Find;
+use File::Path qw(mkpath rmtree);
+
+my $from = $ARGV[0]
+ or die <<END;
+You must specify the name of the extension you are converting from,
+as the first argument.
+END
+my $extension_name = ucfirst($from);
+
+my $extdir = bz_locations()->{'extensionsdir'};
+
+my $from_dir = "$extdir/$from";
+if (!-d $from_dir) {
+ 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";
+}
+
+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: $!";
+}
+else {
+ 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
+# in this script.
+undef $from_dir;
+
+if (!-d $to_dir) {
+ die "$to_dir was not created.\n";
+}
+
+my $version = get_version($to_dir);
+move_template_hooks($to_dir);
+rename_module_packages($to_dir, $extension_name);
+my $install_requirements = get_install_requirements($to_dir);
+my ($modules, $subs) = code_files_to_subroutines($to_dir);
+
+my $config_pm = <<END;
+package Bugzilla::Extension::$extension_name;
+use strict;
+use constant NAME => '$extension_name';
+$install_requirements
+__PACKAGE__->NAME;
+END
+
+my $extension_pm = <<END;
+package Bugzilla::Extension::$extension_name;
+use strict;
+use base qw(Bugzilla::Extension);
+
+$modules
+
+our \$VERSION = '$version';
+
+$subs
+
+__PACKAGE__->NAME;
+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")
+ || die "$to_dir/Extension.pm: $!";
+print $extension_fh $extension_pm;
+close($extension_fh);
+
+rmtree("$to_dir/code");
+unlink("$to_dir/info.pl");
+
+###############
+# Subroutines #
+###############
+
+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 @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]);
+}
+
+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);
+ }
+}
+
+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};
+}
+
+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;
+}
+
+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);
+ }
+ }
+ 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;
+sub $name {
+ my (\$self, \$args) = \@_;
+$code_string
+}
+END
+ 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);
+}
+
+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: $!";
+ }
+ }
+}
+
+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;
+}
+
+__END__
+
+=head1 NAME
+
+extension-convert.pl - Convert extensions from the pre-3.6 format to the
+3.6 format.
+
+=head1 SYNOPSIS
+
+ contrib/extension-convert.pl name
+
+ Converts an extension in the F<extensions/> directory into the new
+ extension layout for Bugzilla 3.6.
diff --git a/contrib/fixperms.pl b/contrib/fixperms.pl
new file mode 100755
index 000000000..406c149cb
--- /dev/null
+++ b/contrib/fixperms.pl
@@ -0,0 +1,28 @@
+#!/usr/bin/perl -w
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by the Initial Developer are Copyright (C) 2010 the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+use strict;
+use warnings;
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Install::Filesystem qw(fix_all_file_permissions);
+fix_all_file_permissions(1);
diff --git a/contrib/jb2bz.py b/contrib/jb2bz.py
new file mode 100755
index 000000000..55cb056b5
--- /dev/null
+++ b/contrib/jb2bz.py
@@ -0,0 +1,308 @@
+#!/usr/local/bin/python
+# -*- mode: python -*-
+
+"""
+jb2bz.py - a nonce script to import bugs from JitterBug to Bugzilla
+Written by Tom Emerson, tree@basistech.com
+
+This script is provided in the hopes that it will be useful. No
+rights reserved. No guarantees expressed or implied. Use at your own
+risk. May be dangerous if swallowed. If it doesn't work for you, don't
+blame me. It did what I needed it to do.
+
+This code requires a recent version of Andy Dustman's MySQLdb interface,
+
+ http://sourceforge.net/projects/mysql-python
+
+Share and enjoy.
+"""
+
+import rfc822, mimetools, multifile, mimetypes
+import sys, re, glob, StringIO, os, stat, time
+import MySQLdb, getopt
+
+# mimetypes doesn't include everything we might encounter, yet.
+if not mimetypes.types_map.has_key('.doc'):
+ mimetypes.types_map['.doc'] = 'application/msword'
+
+if not mimetypes.encodings_map.has_key('.bz2'):
+ mimetypes.encodings_map['.bz2'] = "bzip2"
+
+bug_status='CONFIRMED'
+component="default"
+version=""
+product="" # this is required, the rest of these are defaulted as above
+
+"""
+Each bug in JitterBug is stored as a text file named by the bug number.
+Additions to the bug are indicated by suffixes to this:
+
+<bug>
+<bug>.followup.*
+<bug>.reply.*
+<bug>.notes
+
+The dates on the files represent the respective dates they were created/added.
+
+All <bug>s and <bug>.reply.*s include RFC 822 mail headers. These could include
+MIME file attachments as well that would need to be extracted.
+
+There are other additions to the file names, such as
+
+<bug>.notify
+
+which are ignored.
+
+Bugs in JitterBug are organized into directories. At Basis we used the following
+naming conventions:
+
+<product>-bugs Open bugs
+<product>-requests Open Feature Requests
+<product>-resolved Bugs/Features marked fixed by engineering, but not verified
+<product>-verified Resolved defects that have been verified by QA
+
+where <product> is either:
+
+<product-name>
+
+or
+
+<product-name>-<version>
+"""
+
+def process_notes_file(current, fname):
+ try:
+ new_note = {}
+ notes = open(fname, "r")
+ s = os.fstat(notes.fileno())
+
+ new_note['text'] = notes.read()
+ new_note['timestamp'] = time.gmtime(s[stat.ST_MTIME])
+
+ notes.close()
+
+ current['notes'].append(new_note)
+
+ except IOError:
+ pass
+
+def process_reply_file(current, fname):
+ new_note = {}
+ reply = open(fname, "r")
+ msg = rfc822.Message(reply)
+ new_note['text'] = "%s\n%s" % (msg['From'], msg.fp.read())
+ new_note['timestamp'] = rfc822.parsedate_tz(msg['Date'])
+ current["notes"].append(new_note)
+
+def add_notes(current):
+ """Add any notes that have been recorded for the current bug."""
+ process_notes_file(current, "%d.notes" % current['number'])
+
+ for f in glob.glob("%d.reply.*" % current['number']):
+ process_reply_file(current, f)
+
+ for f in glob.glob("%d.followup.*" % current['number']):
+ process_reply_file(current, f)
+
+def maybe_add_attachment(current, file, submsg):
+ """Adds the attachment to the current record"""
+ cd = submsg["Content-Disposition"]
+ m = re.search(r'filename="([^"]+)"', cd)
+ if m == None:
+ return
+ attachment_filename = m.group(1)
+ if (submsg.gettype() == 'application/octet-stream'):
+ # try get a more specific content-type for this attachment
+ type, encoding = mimetypes.guess_type(m.group(1))
+ if type == None:
+ type = submsg.gettype()
+ else:
+ type = submsg.gettype()
+
+ try:
+ data = StringIO.StringIO()
+ mimetools.decode(file, data, submsg.getencoding())
+ except:
+ return
+
+ current['attachments'].append( ( attachment_filename, type, data.getvalue() ) )
+
+def process_mime_body(current, file, submsg):
+ data = StringIO.StringIO()
+ mimetools.decode(file, data, submsg.getencoding())
+ current['description'] = data.getvalue()
+
+
+
+def process_text_plain(msg, current):
+ print "Processing: %d" % current['number']
+ current['description'] = msg.fp.read()
+
+def process_multi_part(file, msg, current):
+ print "Processing: %d" % current['number']
+ mf = multifile.MultiFile(file)
+ mf.push(msg.getparam("boundary"))
+ while mf.next():
+ submsg = mimetools.Message(file)
+ if submsg.has_key("Content-Disposition"):
+ maybe_add_attachment(current, mf, submsg)
+ else:
+ # This is the message body itself (always?), so process
+ # accordingly
+ process_mime_body(current, mf, submsg)
+
+def process_jitterbug(filename):
+ current = {}
+ current['number'] = int(filename)
+ current['notes'] = []
+ current['attachments'] = []
+ current['description'] = ''
+ current['date-reported'] = ()
+ current['short-description'] = ''
+
+ file = open(filename, "r")
+ msg = mimetools.Message(file)
+
+ msgtype = msg.gettype()
+
+ add_notes(current)
+ current['date-reported'] = rfc822.parsedate_tz(msg['Date'])
+ current['short-description'] = msg['Subject']
+
+ if msgtype[:5] == 'text/':
+ process_text_plain(msg, current)
+ elif msgtype[:10] == "multipart/":
+ process_multi_part(file, msg, current)
+ else:
+ # Huh? This should never happen.
+ print "Unknown content-type: %s" % msgtype
+ sys.exit(1)
+
+ # At this point we have processed the message: we have all of the notes and
+ # attachments stored, so it's time to add things to the database.
+ # The schema for JitterBug 2.14 can be found at:
+ #
+ # http://www.trilobyte.net/barnsons/html/dbschema.html
+ #
+ # The following fields need to be provided by the user:
+ #
+ # bug_status
+ # product
+ # version
+ # reporter
+ # component
+ # resolution
+
+ # change this to the user_id of the Bugzilla user who is blessed with the
+ # imported defects
+ reporter=6
+
+ # the resolution will need to be set manually
+ resolution=""
+
+ db = MySQLdb.connect(db='bugs',user='root',host='localhost')
+ cursor = db.cursor()
+
+ cursor.execute( "INSERT INTO bugs SET " \
+ "bug_id=%s," \
+ "bug_severity='normal'," \
+ "bug_status=%s," \
+ "creation_ts=%s," \
+ "delta_ts=%s," \
+ "short_desc=%s," \
+ "product=%s," \
+ "rep_platform='All'," \
+ "assigned_to=%s,"
+ "reporter=%s," \
+ "version=%s," \
+ "component=%s," \
+ "resolution=%s",
+ [ current['number'],
+ bug_status,
+ time.strftime("%Y-%m-%d %H:%M:%S", current['date-reported'][:9]),
+ time.strftime("%Y-%m-%d %H:%M:%S", current['date-reported'][:9]),
+ current['short-description'],
+ product,
+ reporter,
+ reporter,
+ version,
+ component,
+ resolution] )
+
+ # This is the initial long description associated with the bug report
+ cursor.execute( "INSERT INTO longdescs VALUES (%s,%s,%s,%s)",
+ [ current['number'],
+ reporter,
+ time.strftime("%Y-%m-%d %H:%M:%S", current['date-reported'][:9]),
+ current['description'] ] )
+
+ # Add whatever notes are associated with this defect
+ for n in current['notes']:
+ cursor.execute( "INSERT INTO longdescs VALUES (%s,%s,%s,%s)",
+ [current['number'],
+ reporter,
+ time.strftime("%Y-%m-%d %H:%M:%S", n['timestamp'][:9]),
+ n['text']])
+
+ # add attachments associated with this defect
+ for a in current['attachments']:
+ cursor.execute( "INSERT INTO attachments SET " \
+ "bug_id=%s, creation_ts=%s, description='', mimetype=%s," \
+ "filename=%s, submitter_id=%s",
+ [ current['number'],
+ time.strftime("%Y-%m-%d %H:%M:%S", current['date-reported'][:9]),
+ a[1], a[0], reporter ])
+ cursor.execute( "INSERT INTO attach_data SET " \
+ "id=LAST_INSERT_ID(), thedata=%s",
+ [ a[2] ])
+
+ cursor.close()
+ db.close()
+
+def usage():
+ print """Usage: jb2bz.py [OPTIONS] Product
+
+Where OPTIONS are one or more of the following:
+
+ -h This help information.
+ -s STATUS One of UNCONFIRMED, CONFIRMED, IN_PROGRESS, RESOLVED, VERIFIED
+ (default is CONFIRMED)
+ -c COMPONENT The component to attach to each bug as it is important. This should be
+ valid component for the Product.
+ -v VERSION Version to assign to these defects.
+
+Product is the Product to assign these defects to.
+
+All of the JitterBugs in the current directory are imported, including replies, notes,
+attachments, and similar noise.
+"""
+ sys.exit(1)
+
+
+def main():
+ global bug_status, component, version, product
+ opts, args = getopt.getopt(sys.argv[1:], "hs:c:v:")
+
+ for o,a in opts:
+ if o == "-s":
+ if a in ('UNCONFIRMED','CONFIRMED','IN_PROGRESS','RESOLVED','VERIFIED'):
+ bug_status = a
+ elif o == '-c':
+ component = a
+ elif o == '-v':
+ version = a
+ elif o == '-h':
+ usage()
+
+ if len(args) != 1:
+ sys.stderr.write("Must specify the Product.\n")
+ sys.exit(1)
+
+ product = args[0]
+
+ for bug in filter(lambda x: re.match(r"\d+$", x), glob.glob("*")):
+ process_jitterbug(bug)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/contrib/merge-users.pl b/contrib/merge-users.pl
new file mode 100755
index 000000000..ee6ec8628
--- /dev/null
+++ b/contrib/merge-users.pl
@@ -0,0 +1,240 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Myk Melez <myk@mozilla.org>
+# Frédéric Buclin <LpSolit@gmail.com>
+
+use strict;
+
+=head1 NAME
+
+merge-users.pl - Merge two user accounts.
+
+=head1 SYNOPSIS
+
+ This script moves activity from one user account to another.
+ Specify the two accounts on the command line, e.g.:
+
+ ./merge-users.pl old_account@foo.com new_account@bar.com
+ or:
+ ./merge-users.pl id:old_userid id:new_userid
+ or:
+ ./merge-users.pl id:old_userid new_account@bar.com
+
+ Notes: - the new account must already exist.
+ - the id:old_userid syntax permits you to migrate
+ activity from a deleted account to an existing one.
+
+=cut
+
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Util;
+use Bugzilla::User;
+
+use Getopt::Long;
+use Pod::Usage;
+
+my $dbh = Bugzilla->dbh;
+
+# Display the help if called with --help or -?.
+my $help = 0;
+my $result = GetOptions("help|?" => \$help);
+pod2usage(0) if $help;
+
+
+# Make sure accounts were specified on the command line and exist.
+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;
+}
+else {
+ 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";
+}
+else {
+ 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);
+}
+else {
+ 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";
+}
+else {
+ 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";
+}
+
+
+# A list of tables and columns to be changed:
+# - keys of the hash are table names to be locked/altered;
+# - values of the hash contain column names to be updated
+# as well as the columns they depend on:
+# = each array is of the form:
+# ['foo1 bar11 bar12 bar13', 'foo2 bar21 bar22', 'foo3 bar31 bar32']
+# where fooN is the column to update, and barN1, barN2, ... are
+# the columns to take into account to avoid duplicated entries.
+# Note that the barNM columns are optional.
+#
+# 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'],
+);
+
+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);
+}
+
+# Delete all old records for these tables; no migration.
+foreach my $table (qw(logincookies tokens profiles)) {
+ $changes{$table} = [];
+}
+
+# Start the transaction
+$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);
+
+# 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
+ SET $col_to_update = ?
+ WHERE $cols_to_check");
+
+ # 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
+ FROM $table
+ 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(*)
+ 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);
+ }
+ }
+ }
+ # 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";
+
+# Delete the old record from the profiles table.
+$dbh->do('DELETE FROM profiles WHERE userid = ?', undef, $old_id);
+
+# rederive regexp-based group memberships, because we merged all memberships
+# from all of the accounts, and since the email address isn't the same on
+# them, some of them may no longer match the regexps.
+my $user = new Bugzilla::User($new_id);
+$user->derive_regexp_groups();
+
+# Commit the transaction
+$dbh->bz_commit_transaction();
+
+print "Done.\n";
diff --git a/contrib/mysqld-watcher.pl b/contrib/mysqld-watcher.pl
new file mode 100755
index 000000000..a70e2250a
--- /dev/null
+++ b/contrib/mysqld-watcher.pl
@@ -0,0 +1,117 @@
+#!/usr/bin/perl -w
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 2000 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Dan Mosedale <dmose@mozilla.org>
+#
+
+# mysqld-watcher.pl - a script that watches the running instance of
+# mysqld and kills off any long-running SELECTs against the shadow_db
+#
+use strict;
+
+# some configurables:
+
+# length of time before a thread is eligible to be killed, in seconds
+#
+my $long_query_time = 180;
+#
+# the From header for any messages sent out
+#
+my $mail_from = "root\@mothra.mozilla.org";
+#
+# the To header for any messages sent out
+#
+my $mail_to = "root";
+#
+# mail transfer agent. this should probably really be converted to a Param().
+#
+my $mta_program = "/usr/lib/sendmail -t -ODeliveryMode=deferred";
+
+# The array of long-running queries
+#
+my $long = {};
+
+# Run mysqladmin processlist twice, the first time getting complete queries
+# and the second time getting just abbreviated queries. We want complete
+# 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")
+{
+ close(STDIN);
+ open(STDIN, "$command |");
+
+ # 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 ( $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
+# whole; should also do more error-checking
+#
+sub sendEmail($$$$) {
+ ($#_ == 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);
+
+}
+
+# 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";
+ }
+
+ # 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/new-yui.sh b/contrib/new-yui.sh
new file mode 100755
index 000000000..6199c2a46
--- /dev/null
+++ b/contrib/new-yui.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+#
+# Updates the version of YUI used by Bugzilla. Just pass the path to
+# an unzipped yui release directory, like:
+#
+# contrib/new-yui.sh /path/to/yui-2.8.1/
+#
+rsync -av --delete $1/build/ js/yui/
+cd js/yui
+rm -rf editor/ yuiloader-dom-event/
+find -name '*.js' -not -name '*-min.js' -not -name '*-dom-event.js' \
+ -exec rm -f {} \;
+find -name '*-skin.css' -exec rm -f {} \;
+find -depth -path '*/assets' -not -path './assets' -exec rm -rf {} \;
+rm assets/skins/sam/sprite.psd
+rm assets/skins/sam/skin.css
+rmdir utilities
diff --git a/contrib/recode.pl b/contrib/recode.pl
new file mode 100755
index 000000000..f7ba034ac
--- /dev/null
+++ b/contrib/recode.pl
@@ -0,0 +1,380 @@
+#!/usr/bin/perl -w
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved.
+# Portions created by Everything Solved are Copyright (C) 2006
+# Everything Solved. All Rights Reserved.
+#
+# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+
+use strict;
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Constants;
+
+use Digest::MD5 qw(md5_base64);
+use Encode qw(encode decode resolve_alias is_utf8);
+use Encode::Guess;
+use Getopt::Long;
+use Pod::Usage;
+
+#############
+# Constants #
+#############
+
+use constant IGNORE_ENCODINGS => qw(utf8 ascii iso-8859-1);
+
+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 => 'bug_id,bug_when,fieldid',
+ profile_setting => 'user_id,setting_name',
+ 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',
+};
+
+###############
+# Subroutines #
+###############
+
+# "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;
+}
+
+sub do_guess {
+ my ($data) = @_;
+
+ 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.)
+ my @utf8_accidental = qw(shiftjis big5-eten euc-kr euc-jp);
+ 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,
+ # but Encode::Guess can usually tell which one it is.
+ if ($encoding && $encoding eq 'iso-8859-8') {
+ my $decoded_as = guess_iso($data, 'iso-8859-8',
+ # These are ordered this way because it gives the most
+ # accurate results.
+ qw(iso-8859-7 iso-8859-2));
+ $encoding = $decoded_as if $decoded_as;
+ }
+
+ return $encoding;
+}
+
+# A helper for do_guess.
+sub guess_iso {
+ my ($data, $versus, @isos) = @_;
+
+ 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;
+}
+
+sub is_valid_utf8 {
+ my ($str) = @_;
+ Encode::_utf8_on($str);
+ return is_utf8($str, 1);
+}
+
+###############
+# Main Script #
+###############
+
+my %switch;
+GetOptions(\%switch, 'dry-run', 'guess', 'charset=s', 'show-failures',
+ 'overrides=s', 'help|h');
+
+pod2usage({ -verbose => 1 }) if $switch{'help'};
+
+# You have to specify at least one of these switches.
+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.";
+}
+
+if ($switch{'guess'}) {
+ # Encode::Detect::Detector doesn't seem to return a true value.
+ # So we have to check if we can run detect.
+ 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;
+ }
+
+ import Encode::Detect::Detector qw(detect);
+}
+
+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 $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')
+}
+
+$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);
+ }
+ }
+
+ # 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 $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
+ 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 = do_guess($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;
+
+__END__
+
+=head1 NAME
+
+recode.pl - Converts a database from one encoding (or multiple encodings)
+to UTF-8.
+
+=head1 SYNOPSIS
+
+ contrib/recode.pl [--guess [--show-failures]] [--charset=iso-8859-2]
+ [--overrides=file_name]
+
+ --dry-run Don't modify the database.
+
+ --charset Primary charset your data is currently in. This can be
+ optionally omitted if you do --guess.
+
+ --guess Try to guess the charset of the data.
+
+ --show-failures If we fail to guess, show where we failed.
+
+ --overrides Specify a file containing overrides. See --help
+ for more info.
+
+ --help Display detailed help.
+
+ If you aren't sure what to do, try:
+
+ contrib/recode.pl --guess --charset=cp1252
+
+=head1 OPTIONS
+
+=over
+
+=item --dry-run
+
+Don't modify the database, just print out what the conversions will be.
+
+recode.pl will print out a Key for each item. You can use this in the
+overrides file, described below.
+
+=item --guess
+
+If your database is in multiple different encodings, specify this switch
+and recode.pl will do its best to determine the original charset of the data.
+The detection is usually very reliable.
+
+If recode.pl cannot guess the charset, it will leave the data alone, unless
+you've specified --charset.
+
+=item --charset=charset-name
+
+If you do not specify --guess, then your database is converted
+from this character set into the UTF-8.
+
+If you have specified --guess, recode.pl will use this charset as
+a fallback--when it cannot guess the charset of a particular piece
+of data, it will guess that the data is in this charset and convert
+it from this charset to UTF-8.
+
+charset-name must be a charset that is known to perl's Encode
+module. To see a list of available charsets, do:
+
+C<perl -MEncode -e 'print join("\n", Encode-E<gt>encodings(":all"))'>
+
+=item --show-failures
+
+If --guess fails to guess a charset, print out the data it failed on.
+
+=item --overrides=file_name
+
+This is a way of specifying certain encodings to override the encodings of
+--guess. The file is a series of lines. The line should start with the Key
+from --dry-run, and then a space, and then the encoding you'd like to use.
+
+=back
diff --git a/contrib/sendbugmail.pl b/contrib/sendbugmail.pl
new file mode 100755
index 000000000..51de9407d
--- /dev/null
+++ b/contrib/sendbugmail.pl
@@ -0,0 +1,110 @@
+#!/usr/bin/perl -w
+#
+# sendbugmail.pl
+#
+# Nick Barnes, Ravenbrook Limited, 2004-04-01.
+#
+# Bugzilla email script for Bugzilla 2.17.4 and later. Invoke this to send
+# bugmail for a bug which has been changed directly in the database.
+# This uses Bugzilla's own BugMail facility, and will email the
+# users associated with the bug. Replaces the old "processmail"
+# script.
+#
+# Usage: perl -T contrib/sendbugmail.pl bug_id user_email
+
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Util;
+use Bugzilla::BugMail;
+use Bugzilla::User;
+
+my $dbh = Bugzilla->dbh;
+
+sub usage {
+ print STDERR "Usage: $0 bug_id user_email\n";
+ exit;
+}
+
+if (($#ARGV < 1) || ($#ARGV > 2)) {
+ usage();
+}
+
+# Get the arguments.
+my $bugnum = $ARGV[0];
+my $changer = $ARGV[1];
+
+# Validate the bug number.
+if (!($bugnum =~ /^(\d+)$/)) {
+ print STDERR "Bug number \"$bugnum\" not numeric.\n";
+ usage();
+}
+
+detaint_natural($bugnum);
+
+my ($id) = $dbh->selectrow_array("SELECT bug_id FROM bugs WHERE bug_id = ?",
+ undef, $bugnum);
+
+if (!$id) {
+ print STDERR "Bug number $bugnum does not exist.\n";
+ usage();
+}
+
+# Validate the changer address.
+my $match = Bugzilla->params->{'emailregexp'};
+if ($changer !~ /$match/) {
+ print STDERR "Changer \"$changer\" doesn't match email regular expression.\n";
+ usage();
+}
+my $changer_user = new Bugzilla::User({ name => $changer });
+unless ($changer_user) {
+ print STDERR "\"$changer\" is not a valid user.\n";
+ usage();
+}
+
+# Send the email.
+my $outputref = Bugzilla::BugMail::Send($bugnum, {'changer' => $changer_user });
+
+# Report the results.
+my $sent = scalar(@{$outputref->{sent}});
+my $excluded = scalar(@{$outputref->{excluded}});
+
+if ($sent) {
+ print "email sent to $sent recipients:\n";
+} else {
+ print "No email sent.\n";
+}
+
+foreach my $sent (@{$outputref->{sent}}) {
+ print " $sent\n";
+}
+
+if ($excluded) {
+ print "$excluded recipients excluded:\n";
+} else {
+ print "No recipients excluded.\n";
+}
+
+foreach my $excluded (@{$outputref->{excluded}}) {
+ print " $excluded\n";
+}
+
+# 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
+# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# DOCUMENT, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/contrib/sendunsentbugmail.pl b/contrib/sendunsentbugmail.pl
new file mode 100755
index 000000000..ec92a97a0
--- /dev/null
+++ b/contrib/sendunsentbugmail.pl
@@ -0,0 +1,66 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Dave Miller <justdave@bugzilla.org>
+# Myk Melez <myk@mozilla.org>
+
+use strict;
+
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::BugMail;
+
+my $dbh = Bugzilla->dbh;
+
+my $list = $dbh->selectcol_arrayref(
+ 'SELECT bug_id FROM bugs
+ WHERE lastdiffed IS NULL
+ OR lastdiffed < delta_ts
+ AND delta_ts < NOW() - ' . $dbh->sql_interval(30, 'MINUTE') .
+ ' ORDER BY bug_id');
+
+if (scalar(@$list) > 0) {
+ print "OK, now attempting to send unsent mail\n";
+ print scalar(@$list) . " bugs found with possibly unsent mail.\n\n";
+ foreach my $bugid (@$list) {
+ my $start_time = time;
+ print "Sending mail for bug $bugid...\n";
+ my $outputref = Bugzilla::BugMail::Send($bugid);
+ if ($ARGV[0] && $ARGV[0] eq "--report") {
+ print "Mail sent to:\n";
+ foreach (sort @{$outputref->{sent}}) {
+ print $_ . "\n";
+ }
+
+ print "Excluded:\n";
+ foreach (sort @{$outputref->{excluded}}) {
+ print $_ . "\n";
+ }
+ }
+ else {
+ my ($sent, $excluded) = (scalar(@{$outputref->{sent}}),scalar(@{$outputref->{excluded}}));
+ print "$sent mails sent, $excluded people excluded.\n";
+ print "Took " . (time - $start_time) . " seconds.\n\n";
+ }
+ }
+ print "Unsent mail has been sent.\n";
+}
diff --git a/contrib/syncLDAP.pl b/contrib/syncLDAP.pl
new file mode 100755
index 000000000..3da25a656
--- /dev/null
+++ b/contrib/syncLDAP.pl
@@ -0,0 +1,298 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the LDAP to Bugzilla User Sync Tool.
+#
+# The Initial Developer of the Original Code is Andreas Höfler.
+# Portions created by Andreas Höfler are Copyright (C) 2003
+# Andreas Höfler. All Rights Reserved.
+#
+# Contributor(s): Andreas Höfler <andreas.hoefler@bearingpoint.com>
+#
+
+use strict;
+
+use lib qw(. lib);
+
+use Net::LDAP;
+use Bugzilla;
+use Bugzilla::User;
+
+my $cgi = Bugzilla->cgi;
+my $dbh = Bugzilla->dbh;
+
+my $readonly = 0;
+my $nodisable = 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 users, which 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;
+
+###
+# Get current bugzilla users
+###
+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;
+}
+
+###
+# Get current LDAP users
+###
+my $LDAPserver = Bugzilla->params->{"LDAPserver"};
+if ($LDAPserver eq "") {
+ 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(!$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);
+}
+else {
+ $mesg = $LDAPconn->bind();
+}
+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;
+}
+
+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;
+
+###
+# Sort the users into disable/update/create-Lists and display everything
+###
+my %disable_users;
+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;
+ }
+ }
+}
+
+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}){
+ $create_users{$key} = $value;
+ }
+ else {
+ my $bugzilla_user_value = $bugzilla_users{$key};
+ 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};
+ }
+ }
+}
+
+if($quiet == 0) {
+ print "\nUsers to disable: \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 not-existing users... " unless $quiet;
+
+ my $sth_disable = $dbh->prepare(
+ 'UPDATE profiles
+ SET disabledtext = ?
+ 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;
+
+ my $sth_update_login = $dbh->prepare(
+ 'UPDATE profiles
+ SET login_name = ?
+ WHERE ' . $dbh->sql_istrcmp('login_name', '?'));
+ my $sth_update_realname = $dbh->prepare(
+ 'UPDATE profiles
+ SET realname = ?
+ WHERE ' . $dbh->sql_istrcmp('login_name', '?'));
+
+ if($noupdate == 0) {
+ while( my ($key, $value) = each(%update_users) ) {
+ if(defined $value->{'new_login_name'}) {
+ $sth_update_login->execute($value->{'new_login_name'}, $key);
+ } else {
+ $sth_update_realname->execute($value->{'realname'}, $key);
+ }
+ }
+ 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;
+}
+
diff --git a/contrib/yp_nomail.sh b/contrib/yp_nomail.sh
new file mode 100755
index 000000000..9d23d5e33
--- /dev/null
+++ b/contrib/yp_nomail.sh
@@ -0,0 +1,77 @@
+#!/bin/sh
+# -*- Mode: ksh -*-
+##############################################################################
+# yp_nomail
+#
+# Our mail admins got annoyed when bugzilla kept sending email
+# to people who'd had bugzilla entries and left the company. They
+# were no longer in the list of valid email users so it'd bounce.
+# Maintaining the 'data/nomail' file was a pain. Luckily, our UNIX
+# admins list all the users that ever were, but the people who've left
+# have a distinct marker in their password file. For example:
+#
+# fired:*LK*:2053:1010:You're Fired Dude:/home/loser:/bin/false
+#
+# This script takes advantage of the "*LK*" convention seen via
+# ypcat passwd and dumps those people into the nomail file. Any
+# manual additions are kept in a "nomail.(domainname)" file and
+# appended to the list of yp lockouts every night via Cron
+#
+# 58 23 * * * /export/bugzilla/contrib/yp_nomail.sh > /dev/null 2>&1
+#
+# Tak ( Mark Takacs ) 08/2000
+#
+# XXX: Maybe should crosscheck w/bugzilla users?
+##############################################################################
+
+####
+# Configure this section to suite yer installation
+####
+
+DOMAIN=`domainname`
+MOZILLA_HOME="/export/mozilla"
+BUGZILLA_HOME="${MOZILLA_HOME}/bugzilla"
+NOMAIL_DIR="${BUGZILLA_HOME}/data"
+NOMAIL="${NOMAIL_DIR}/nomail"
+NOMAIL_ETIME="${NOMAIL}.${DOMAIN}"
+NOMAIL_YP="${NOMAIL}.yp"
+FIRED_FLAG="\*LK\*"
+
+YPCAT="/usr/bin/ypcat"
+GREP="/usr/bin/grep"
+SORT="/usr/bin/sort"
+
+########################## no more config needed #################
+
+# This dir comes w/Bugzilla. WAY too paranoid
+if [ ! -d ${NOMAIL_DIR} ] ; then
+ echo "Creating $date_dir"
+ mkdir -p ${NOMAIL_DIR}
+fi
+
+#
+# Do some (more) paranoid checking
+#
+touch ${NOMAIL}
+if [ ! -w ${NOMAIL} ] ; then
+ echo "Can't write nomail file: ${NOMAIL} -- exiting"
+ exit
+fi
+if [ ! -r ${NOMAIL_ETIME} ] ; then
+ echo "Can't access custom nomail file: ${NOMAIL_ETIME} -- skipping"
+ NOMAIL_ETIME=""
+fi
+
+#
+# add all the people with '*LK*' password to the nomail list
+# XXX: maybe I should customize the *LK* string. Doh.
+#
+
+LOCKOUT=`$YPCAT passwd | $GREP "${FIRED_FLAG}" | cut -d: -f1 | sort > ${NOMAIL_YP}`
+`cat ${NOMAIL_YP} ${NOMAIL_ETIME} > ${NOMAIL}`
+
+exit
+
+
+# end
+
diff --git a/createaccount.cgi b/createaccount.cgi
new file mode 100755
index 000000000..c2941bc4c
--- /dev/null
+++ b/createaccount.cgi
@@ -0,0 +1,82 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# David Gardiner <david.gardiner@unisa.edu.au>
+# Joe Robins <jmrobins@tgix.com>
+# Christopher Aillon <christopher@aillon.com>
+# Gervase Markham <gerv@gerv.net>
+
+use strict;
+
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::User;
+use Bugzilla::BugMail;
+use Bugzilla::Util;
+
+# 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.
+Bugzilla->login(LOGIN_OPTIONAL);
+
+my $dbh = Bugzilla->dbh;
+my $cgi = Bugzilla->cgi;
+my $template = Bugzilla->template;
+my $vars = {};
+
+$vars->{'doc_section'} = 'myaccount.html';
+
+print $cgi->header();
+
+# If we're using LDAP for login, then we can't create a new account here.
+unless (Bugzilla->user->authorizer->user_can_create_account) {
+ ThrowUserError("auth_cant_create_account");
+}
+
+my $createexp = Bugzilla->params->{'createemailregexp'};
+unless ($createexp) {
+ ThrowUserError("account_creation_disabled");
+}
+
+my $login = $cgi->param('login');
+
+if (defined($login)) {
+ $login = Bugzilla::User->check_login_name_for_creation($login);
+ $vars->{'login'} = $login;
+
+ if ($login !~ /$createexp/) {
+ ThrowUserError("account_creation_restricted");
+ }
+
+ # Create and send a token for this new account.
+ Bugzilla::Token::issue_new_user_account_token($login);
+
+ $template->process("account/created.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
+}
+
+# Show the standard "would you like to create an account?" form.
+$template->process("account/create.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
diff --git a/describecomponents.cgi b/describecomponents.cgi
new file mode 100755
index 000000000..744501bbd
--- /dev/null
+++ b/describecomponents.cgi
@@ -0,0 +1,88 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Bradley Baetz <bbaetz@student.usyd.edu.au>
+# Frédéric Buclin <LpSolit@gmail.com>
+
+use strict;
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Util;
+use Bugzilla::Error;
+use Bugzilla::Product;
+
+my $user = Bugzilla->login();
+my $cgi = Bugzilla->cgi;
+my $template = Bugzilla->template;
+my $vars = {};
+
+print $cgi->header();
+
+# This script does nothing but displaying mostly static data.
+Bugzilla->switch_to_shadow_db;
+
+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_enterable_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 he didn't request.
+ elsif (scalar(@products) > 1 || $product_name) {
+ $vars->{'classifications'} = [{object => undef, products => \@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;
+ }
+
+ # If there is only one product available and the user didn't specify
+ # any product name, we show this product.
+ $product = $products[0];
+}
+
+######################################################################
+# End Data/Security Validation
+######################################################################
+
+$vars->{'product'} = $product;
+
+$template->process("reports/components.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
diff --git a/describekeywords.cgi b/describekeywords.cgi
new file mode 100755
index 000000000..9796b77d5
--- /dev/null
+++ b/describekeywords.cgi
@@ -0,0 +1,46 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Terry Weissman.
+# Portions created by Terry Weissman are
+# Copyright (C) 2000 Terry Weissman. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Contributor(s): Gervase Markham <gerv@gerv.net>
+
+use strict;
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Error;
+use Bugzilla::User;
+use Bugzilla::Keyword;
+
+Bugzilla->login();
+
+my $cgi = Bugzilla->cgi;
+my $template = Bugzilla->template;
+my $vars = {};
+
+# Run queries against the shadow DB.
+Bugzilla->switch_to_shadow_db;
+
+$vars->{'keywords'} = Bugzilla::Keyword->get_all_with_bug_count();
+$vars->{'caneditkeywords'} = Bugzilla->user->in_group("editkeywords");
+
+print Bugzilla->cgi->header();
+$template->process("reports/keywords.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
diff --git a/docs/bugzilla.ent b/docs/bugzilla.ent
new file mode 100644
index 000000000..2bba8fd26
--- /dev/null
+++ b/docs/bugzilla.ent
@@ -0,0 +1,47 @@
+<?xml version="1.0"?>
+
+<!-- Module Versions -->
+<!ENTITY min-cgi-ver "3.51">
+<!ENTITY min-digest-sha-ver "any">
+<!ENTITY min-date-format-ver "2.21">
+<!ENTITY min-datetime-ver "0.28">
+<!ENTITY min-datetime-timezone-ver "0.71">
+<!ENTITY min-dbi-ver "1.41">
+<!ENTITY min-template-ver "2.22">
+<!ENTITY min-email-send-ver "2.00">
+<!ENTITY min-email-mime-ver "1.904">
+<!ENTITY min-uri-ver "any">
+<!ENTITY min-list-moreutils-ver "0.22">
+<!ENTITY min-gd-ver "1.20">
+<!ENTITY min-chart-lines-ver "2.1">
+<!ENTITY min-template-plugin-gd-image-ver "any">
+<!ENTITY min-gd-text-ver "any">
+<!ENTITY min-gd-graph-ver "any">
+<!ENTITY min-xml-twig-ver "any">
+<!ENTITY min-mime-parser-ver "5.406">
+<!ENTITY min-lwp-useragent-ver "any">
+<!ENTITY min-patchreader-ver "0.9.4">
+<!ENTITY min-net-ldap-ver "any">
+<!ENTITY min-authen-sasl-ver "any">
+<!ENTITY min-authen-radius-ver "any">
+<!ENTITY min-soap-lite-ver "0.712">
+<!ENTITY min-json-rpc-ver "any">
+<!ENTITY min-json-xs-ver "2.0">
+<!ENTITY min-test-taint-ver "any">
+<!ENTITY min-html-parser-ver "3.40">
+<!ENTITY min-html-scrubber-ver "any">
+<!ENTITY min-email-mime-attachment-stripper-ver "any">
+<!ENTITY min-email-reply-ver "any">
+<!ENTITY min-theschwartz-ver "any">
+<!ENTITY min-daemon-generic-ver "any">
+<!ENTITY min-mod_perl2-ver "1.999022">
+<!ENTITY min-apache2-sizelimit-ver "0.93">
+<!ENTITY min-math-random-secure-ver "0.05">
+
+ <!-- Database Versions -->
+<!ENTITY min-dbd-pg-ver "1.45">
+<!ENTITY min-pg-ver "8.00.0000">
+<!ENTITY min-dbd-mysql-ver "4.00">
+<!ENTITY min-mysql-ver "4.1.2">
+<!ENTITY min-dbd-oracle-ver "1.19">
+<!ENTITY min-oracle-ver "10.02.0">
diff --git a/docs/en/README.docs b/docs/en/README.docs
new file mode 100644
index 000000000..5fdeb8570
--- /dev/null
+++ b/docs/en/README.docs
@@ -0,0 +1,155 @@
+Welcome to the Bugzilla documentation project!
+You'll find these directories and files here:
+
+README.docs # This README file
+html/ # The compiled HTML docs from XML sources (do not edit)
+txt/ # The compiled text docs from XML sources (do not edit)
+xml/ # The original XML doc sources (edit these)
+
+A note about the XML:
+ The documentation is written in DocBook 4.1.2, and attempts to adhere
+to the LinuxDoc standards where applicable (http://www.tldp.org).
+Please consult "The LDP Author Guide" at tldp.org for details on how
+to set up your personal environment for compiling XML files.
+ If you need to make corrections to typographical errors, or other minor
+editing duties, feel free to use any text editor to make the changes. XML
+is not rocket science -- simply make sure your text appears between
+appropriate tags (like <para>This is a paragraph</para>) and we'll be fine.
+If you are making more extensive changes, please ensure you at least validate
+your XML before checking it in with something like:
+ nsgmls -s $JADE_PUB/xml.dcl Bugzilla-Guide.xml
+
+ When you validate, please validate the master document (Bugzilla-Guide.xml)
+as well as the document you edited to ensure there are no critical errors.
+The following errors are considered "normal" when validating with nsgmls:
+
+ DTDDECL catalog entries are not supported
+ "DOCTYPE" declaration not allowed in instance
+
+ The reason these occur is that free sgml/xml validators do not yet support
+the DTDDECL catalog entries, and I've included DOCTYPE declarations in
+entities referenced from Bugzilla-Guide.xml so these entities can compile
+individually, if necessary. I suppose I ought to comment them out at some
+point, but for now they are convenient and don't hurt anything.
+
+ Thanks for taking the time to read these notes and consulting the
+documentation. Please address comments and questions to the newsgroup:
+news://news.mozilla.org/netscape/public/mozilla/webtools .
+
+==========
+HOW TO SET UP YOUR OWN XML EDITING ENVIRONMENT:
+==========
+
+Trying to set up an XML Docbook editing environment the
+first time can be a daunting task.
+I use Linux-Mandrake, in part, because it has a fully-functional
+XML Docbook editing environment included as part of the
+distribution CD's. If you have easier instructions for how to
+do this for a particular Linux distribution or platform, please
+let the team know at the mailing list: mozilla-webtools@mozilla.org.
+
+The following text is taken nearly verbatim from
+http://bugzilla.mozilla.org/show_bug.cgi?id=95970, where I gave
+these instructions to someone who wanted the greater manageability
+maintaining a document in Docbook brings:
+
+This is just off the top of my head, but here goes. Note some of these may
+NOT be necessary, but I don't think they hurt anything by being installed.
+
+rpms:
+
+openjade
+jadetex
+docbook-dtds
+docbook-style-dsssl
+docbook-style-dsssl-doc
+docbook-utils
+xemacs
+psgml
+sgml-tools
+sgml-common
+
+
+If you're getting these from RedHat, make sure you get the ones in the
+rawhide area. The ones in the 7.2 distribution are too old and don't
+include the XML stuff. The packages distrubuted with RedHat 8.0 and 9
+and known to work.
+
+Download "ldp.dsl" from the Resources page on tldp.org. This is the
+stylesheet I use to get the HTML and text output. It works well, and has a
+nice, consistent look with the rest of the linuxdoc documents. You'll have to
+adjust the paths in ldp.dsl at the top of the file to reflect the actual
+locations of your docbook catalog files. I created a directory,
+/usr/share/sgml/docbook/ldp, and put the ldp.dsl file there. I then edited
+ldp.dsl and changed two lines near the top:
+<!ENTITY docbook.dsl SYSTEM "../dsssl-stylesheets/html/docbook.dsl" CDATA
+dsssl>
+...and...
+<!ENTITY docbook.dsl SYSTEM "../dsssl-stylesheets/print/docbook.dsl" CDATA
+dsssl>
+
+Note the difference is the top one points to the HTML docbook stylesheet,
+and the next one points to the PRINT docbook stylesheet.
+
+Also note that modifying ldp.dsl doesn't seem to be needed on RedHat 9.
+
+ You know, this sure looks awful involved. Anyway, once you have this in
+place, add to your .bashrc:
+export SGML_CATALOG_FILES=/etc/sgml/catalog
+export LDP_HOME=/usr/share/sgml/docbook/ldp
+export JADE_PUB=/usr/share/doc/openjade-1.3.1/pubtext
+
+or in .tcshrc:
+setenv SGML_CATALOG_FILES /etc/sgml/catalog
+setenv LDP_HOME /usr/share/sgml/docbook/ldp
+setenv JADE_PUB /usr/share/doc/openjade-1.3.1/pubtext
+
+ If you have root access and want to set this up for anyone on your box,
+you can add those lines to /etc/profile for bash users and /etc/csh.login
+for tcsh users.
+
+ Make sure you edit the paths in the above environment variables if those
+folders are anywhere else on your system (for example, the openjade version
+might change if you get a new version at some point).
+
+ I suggest xemacs for editing your XML Docbook documents. The darn
+thing just works, and generally includes PSGML mode by default. Not to
+mention you can validate the SGML from right within it without having to
+remember the command-line syntax for nsgml (not that it's that hard
+anyway). If not, you can download psgml at
+http://www.sourceforge.net/projects/psgml.
+
+ Another good editor is the latest releases of vim and gvim. Vim will
+recognize DocBook tags and give them a different color than unreconized tags.
+
+==========
+NOTES:
+==========
+
+ Here are the commands I use to maintain this documentation.
+ You MUST have DocBook 4.1.2 set up correctly in order for this to work.
+
+ These commands can be run all at once using the ./makedocs.pl script.
+
+To create HTML documentation:
+bash$ cd html
+bash$ jade -t sgml -i html -d $LDP_HOME/ldp.dsl\#html \
+$JADE_PUB/xml.dcl ../xml/Bugzilla-Guide.xml
+
+To create HTML documentation as a single big HTML file:
+bash$ cd html
+bash$ jade -V nochunks -t sgml -i html -d $LDP_HOME/ldp.dsl\#html \
+$JADE_PUB/xml.dcl ../xml/Bugzilla-Guide.xml >Bugzilla-Guide.html
+
+To create TXT documentation as a single big TXT file:
+bash$ cd txt
+bash$ lynx -dump -nolist ../html/Bugzilla-Guide.html >Bugzilla-Guide.txt
+
+
+Sincerely,
+ Matthew P. Barnson
+ The Bugzilla "Doc Knight"
+ mbarnson@sisna.com
+
+ with major edits by Dave Miller <justdave@syndicomm.com> based on
+ experience setting this up on the Landfill test server.
diff --git a/docs/en/html/Bugzilla-Guide.html b/docs/en/html/Bugzilla-Guide.html
new file mode 100644
index 000000000..0cd0ae530
--- /dev/null
+++ b/docs/en/html/Bugzilla-Guide.html
@@ -0,0 +1,18111 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>The Bugzilla Guide - 4.0
+ Release</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><META
+NAME="KEYWORD"
+CONTENT="Bugzilla"><META
+NAME="KEYWORD"
+CONTENT="Guide"><META
+NAME="KEYWORD"
+CONTENT="installation"><META
+NAME="KEYWORD"
+CONTENT="FAQ"><META
+NAME="KEYWORD"
+CONTENT="administration"><META
+NAME="KEYWORD"
+CONTENT="integration"><META
+NAME="KEYWORD"
+CONTENT="MySQL"><META
+NAME="KEYWORD"
+CONTENT="Mozilla"><META
+NAME="KEYWORD"
+CONTENT="webtools"></HEAD
+><BODY
+CLASS="book"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="BOOK"
+><A
+NAME="index"
+></A
+><DIV
+CLASS="TITLEPAGE"
+><H1
+CLASS="title"
+><A
+NAME="AEN2"
+>The Bugzilla Guide - 4.0
+ Release</A
+></H1
+><H3
+CLASS="corpauthor"
+>The Bugzilla Team</H3
+><P
+CLASS="pubdate"
+>2011-02-15<BR></P
+><DIV
+><DIV
+CLASS="abstract"
+><P
+></P
+><A
+NAME="AEN7"
+></A
+><P
+>&#13; This is the documentation for Bugzilla, a
+ bug-tracking system from mozilla.org.
+ Bugzilla is an enterprise-class piece of software
+ that tracks millions of bugs and issues for hundreds of
+ organizations around the world.
+ </P
+><P
+>&#13; The most current version of this document can always be found on the
+ <A
+HREF="http://www.bugzilla.org/docs/"
+TARGET="_top"
+>Bugzilla
+ Documentation Page</A
+>.
+ </P
+><P
+></P
+></DIV
+></DIV
+><HR></DIV
+><DIV
+CLASS="TOC"
+><DL
+><DT
+><B
+>Table of Contents</B
+></DT
+><DT
+>1. <A
+HREF="#about"
+>About This Guide</A
+></DT
+><DD
+><DL
+><DT
+>1.1. <A
+HREF="#copyright"
+>Copyright Information</A
+></DT
+><DT
+>1.2. <A
+HREF="#disclaimer"
+>Disclaimer</A
+></DT
+><DT
+>1.3. <A
+HREF="#newversions"
+>New Versions</A
+></DT
+><DT
+>1.4. <A
+HREF="#credits"
+>Credits</A
+></DT
+><DT
+>1.5. <A
+HREF="#conventions"
+>Document Conventions</A
+></DT
+></DL
+></DD
+><DT
+>2. <A
+HREF="#installing-bugzilla"
+>Installing Bugzilla</A
+></DT
+><DD
+><DL
+><DT
+>2.1. <A
+HREF="#installation"
+>Installation</A
+></DT
+><DT
+>2.2. <A
+HREF="#configuration"
+>Configuration</A
+></DT
+><DT
+>2.3. <A
+HREF="#extraconfig"
+>Optional Additional Configuration</A
+></DT
+><DT
+>2.4. <A
+HREF="#multiple-bz-dbs"
+>Multiple Bugzilla databases with a single installation</A
+></DT
+><DT
+>2.5. <A
+HREF="#os-specific"
+>OS-Specific Installation Notes</A
+></DT
+><DT
+>2.6. <A
+HREF="#nonroot"
+>UNIX (non-root) Installation Notes</A
+></DT
+><DT
+>2.7. <A
+HREF="#upgrade"
+>Upgrading to New Releases</A
+></DT
+></DL
+></DD
+><DT
+>3. <A
+HREF="#administration"
+>Administering Bugzilla</A
+></DT
+><DD
+><DL
+><DT
+>3.1. <A
+HREF="#parameters"
+>Bugzilla Configuration</A
+></DT
+><DT
+>3.2. <A
+HREF="#useradmin"
+>User Administration</A
+></DT
+><DT
+>3.3. <A
+HREF="#classifications"
+>Classifications</A
+></DT
+><DT
+>3.4. <A
+HREF="#products"
+>Products</A
+></DT
+><DT
+>3.5. <A
+HREF="#components"
+>Components</A
+></DT
+><DT
+>3.6. <A
+HREF="#versions"
+>Versions</A
+></DT
+><DT
+>3.7. <A
+HREF="#milestones"
+>Milestones</A
+></DT
+><DT
+>3.8. <A
+HREF="#flags-overview"
+>Flags</A
+></DT
+><DT
+>3.9. <A
+HREF="#keywords"
+>Keywords</A
+></DT
+><DT
+>3.10. <A
+HREF="#custom-fields"
+>Custom Fields</A
+></DT
+><DT
+>3.11. <A
+HREF="#edit-values"
+>Legal Values</A
+></DT
+><DT
+>3.12. <A
+HREF="#bug_status_workflow"
+>Bug Status Workflow</A
+></DT
+><DT
+>3.13. <A
+HREF="#voting"
+>Voting</A
+></DT
+><DT
+>3.14. <A
+HREF="#quips"
+>Quips</A
+></DT
+><DT
+>3.15. <A
+HREF="#groups"
+>Groups and Group Security</A
+></DT
+><DT
+>3.16. <A
+HREF="#sanitycheck"
+>Checking and Maintaining Database Integrity</A
+></DT
+></DL
+></DD
+><DT
+>4. <A
+HREF="#security"
+>Bugzilla Security</A
+></DT
+><DD
+><DL
+><DT
+>4.1. <A
+HREF="#security-os"
+>Operating System</A
+></DT
+><DT
+>4.2. <A
+HREF="#security-webserver"
+>Web server</A
+></DT
+><DT
+>4.3. <A
+HREF="#security-bugzilla"
+>Bugzilla</A
+></DT
+></DL
+></DD
+><DT
+>5. <A
+HREF="#using"
+>Using Bugzilla</A
+></DT
+><DD
+><DL
+><DT
+>5.1. <A
+HREF="#using-intro"
+>Introduction</A
+></DT
+><DT
+>5.2. <A
+HREF="#myaccount"
+>Create a Bugzilla Account</A
+></DT
+><DT
+>5.3. <A
+HREF="#bug_page"
+>Anatomy of a Bug</A
+></DT
+><DT
+>5.4. <A
+HREF="#lifecycle"
+>Life Cycle of a Bug</A
+></DT
+><DT
+>5.5. <A
+HREF="#query"
+>Searching for Bugs</A
+></DT
+><DT
+>5.6. <A
+HREF="#bugreports"
+>Filing Bugs</A
+></DT
+><DT
+>5.7. <A
+HREF="#attachments"
+>Attachments</A
+></DT
+><DT
+>5.8. <A
+HREF="#hintsandtips"
+>Hints and Tips</A
+></DT
+><DT
+>5.9. <A
+HREF="#timetracking"
+>Time Tracking Information</A
+></DT
+><DT
+>5.10. <A
+HREF="#userpreferences"
+>User Preferences</A
+></DT
+><DT
+>5.11. <A
+HREF="#reporting"
+>Reports and Charts</A
+></DT
+><DT
+>5.12. <A
+HREF="#flags"
+>Flags</A
+></DT
+><DT
+>5.13. <A
+HREF="#whining"
+>Whining</A
+></DT
+></DL
+></DD
+><DT
+>6. <A
+HREF="#customization"
+>Customizing Bugzilla</A
+></DT
+><DD
+><DL
+><DT
+>6.1. <A
+HREF="#extensions"
+>Bugzilla Extensions</A
+></DT
+><DT
+>6.2. <A
+HREF="#cust-skins"
+>Custom Skins</A
+></DT
+><DT
+>6.3. <A
+HREF="#cust-templates"
+>Template Customization</A
+></DT
+><DT
+>6.4. <A
+HREF="#cust-change-permissions"
+>Customizing Who Can Change What</A
+></DT
+><DT
+>6.5. <A
+HREF="#integration"
+>Integrating Bugzilla with Third-Party Tools</A
+></DT
+></DL
+></DD
+><DT
+>A. <A
+HREF="#troubleshooting"
+>Troubleshooting</A
+></DT
+><DD
+><DL
+><DT
+>A.1. <A
+HREF="#general-advice"
+>General Advice</A
+></DT
+><DT
+>A.2. <A
+HREF="#trbl-testserver"
+>The Apache web server is not serving Bugzilla pages</A
+></DT
+><DT
+>A.3. <A
+HREF="#trbl-perlmodule"
+>I installed a Perl module, but
+ <TT
+CLASS="filename"
+>checksetup.pl</TT
+> claims it's not installed!</A
+></DT
+><DT
+>A.4. <A
+HREF="#trbl-dbdSponge"
+>DBD::Sponge::db prepare failed</A
+></DT
+><DT
+>A.5. <A
+HREF="#paranoid-security"
+>cannot chdir(/var/spool/mqueue)</A
+></DT
+><DT
+>A.6. <A
+HREF="#trbl-relogin-everyone"
+>Everybody is constantly being forced to relogin</A
+></DT
+><DT
+>A.7. <A
+HREF="#trbl-index"
+><TT
+CLASS="filename"
+>index.cgi</TT
+> doesn't show up unless specified in the URL</A
+></DT
+><DT
+>A.8. <A
+HREF="#trbl-passwd-encryption"
+>checksetup.pl reports "Client does not support authentication protocol
+ requested by server..."</A
+></DT
+></DL
+></DD
+><DT
+>B. <A
+HREF="#patches"
+>Contrib</A
+></DT
+><DD
+><DL
+><DT
+>B.1. <A
+HREF="#cmdline"
+>Command-line Search Interface</A
+></DT
+><DT
+>B.2. <A
+HREF="#cmdline-bugmail"
+>Command-line 'Send Unsent Bug-mail' tool</A
+></DT
+></DL
+></DD
+><DT
+>C. <A
+HREF="#install-perlmodules-manual"
+>Manual Installation of Perl Modules</A
+></DT
+><DD
+><DL
+><DT
+>C.1. <A
+HREF="#modules-manual-instructions"
+>Instructions</A
+></DT
+><DT
+>C.2. <A
+HREF="#modules-manual-download"
+>Download Locations</A
+></DT
+><DT
+>C.3. <A
+HREF="#modules-manual-optional"
+>Optional Modules</A
+></DT
+></DL
+></DD
+><DT
+>D. <A
+HREF="#gfdl"
+>GNU Free Documentation License</A
+></DT
+><DD
+><DL
+><DT
+>0. <A
+HREF="#gfdl-0"
+>Preamble</A
+></DT
+><DT
+>1. <A
+HREF="#gfdl-1"
+>Applicability and Definition</A
+></DT
+><DT
+>2. <A
+HREF="#gfdl-2"
+>Verbatim Copying</A
+></DT
+><DT
+>3. <A
+HREF="#gfdl-3"
+>Copying in Quantity</A
+></DT
+><DT
+>4. <A
+HREF="#gfdl-4"
+>Modifications</A
+></DT
+><DT
+>5. <A
+HREF="#gfdl-5"
+>Combining Documents</A
+></DT
+><DT
+>6. <A
+HREF="#gfdl-6"
+>Collections of Documents</A
+></DT
+><DT
+>7. <A
+HREF="#gfdl-7"
+>Aggregation with Independent Works</A
+></DT
+><DT
+>8. <A
+HREF="#gfdl-8"
+>Translation</A
+></DT
+><DT
+>9. <A
+HREF="#gfdl-9"
+>Termination</A
+></DT
+><DT
+>10. <A
+HREF="#gfdl-10"
+>Future Revisions of this License</A
+></DT
+><DT
+><A
+HREF="#gfdl-howto"
+>How to use this License for your documents</A
+></DT
+></DL
+></DD
+><DT
+><A
+HREF="#glossary"
+>Glossary</A
+></DT
+></DL
+></DIV
+><DIV
+CLASS="LOT"
+><DL
+CLASS="LOT"
+><DT
+><B
+>List of Figures</B
+></DT
+><DT
+>5-1. <A
+HREF="#lifecycle-image"
+>Lifecycle of a Bugzilla Bug</A
+></DT
+></DL
+></DIV
+><DIV
+CLASS="LOT"
+><DL
+CLASS="LOT"
+><DT
+><B
+>List of Examples</B
+></DT
+><DT
+>A-1. <A
+HREF="#trbl-relogin-everyone-share"
+>Examples of urlbase/cookiepath pairs for sharing login cookies</A
+></DT
+><DT
+>A-2. <A
+HREF="#trbl-relogin-everyone-restrict"
+>Examples of urlbase/cookiepath pairs to restrict the login cookie</A
+></DT
+></DL
+></DIV
+><DIV
+CLASS="chapter"
+><HR><H1
+><A
+NAME="about"
+></A
+>Chapter 1. About This Guide</H1
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="copyright"
+>1.1. Copyright Information</A
+></H2
+><P
+>This document is copyright (c) 2000-2011 by the various
+ Bugzilla contributors who wrote it.</P
+><A
+NAME="AEN26"
+></A
+><BLOCKQUOTE
+CLASS="BLOCKQUOTE"
+><P
+>&#13; Permission is granted to copy, distribute and/or modify this
+ document under the terms of the GNU Free Documentation
+ License, Version 1.1 or any later version published by the
+ Free Software Foundation; with no Invariant Sections, no
+ Front-Cover Texts, and with no Back-Cover Texts. A copy of
+ the license is included in <A
+HREF="#gfdl"
+>Appendix D</A
+>.
+ </P
+></BLOCKQUOTE
+><P
+>&#13; If you have any questions regarding this document, its
+ copyright, or publishing this document in non-electronic form,
+ please contact the Bugzilla Team.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="disclaimer"
+>1.2. Disclaimer</A
+></H2
+><P
+>&#13; No liability for the contents of this document can be accepted.
+ Follow the instructions herein at your own risk.
+ This document may contain errors
+ and inaccuracies that may damage your system, cause your partner
+ to leave you, your boss to fire you, your cats to
+ pee on your furniture and clothing, and global thermonuclear
+ war. Proceed with caution.
+ </P
+><P
+>&#13; Naming of particular products or brands should not be seen as
+ endorsements, with the exception of the term "GNU/Linux". We
+ wholeheartedly endorse the use of GNU/Linux; it is an extremely
+ versatile, stable,
+ and robust operating system that offers an ideal operating
+ environment for Bugzilla.
+ </P
+><P
+>&#13; Although the Bugzilla development team has taken great care to
+ ensure that all exploitable bugs have been fixed, security holes surely
+ exist in any piece of code. Great care should be taken both in
+ the installation and usage of this software. The Bugzilla development
+ team members assume no liability for your use of Bugzilla. You have
+ the source code, and are responsible for auditing it yourself to ensure
+ your security needs are met.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="newversions"
+>1.3. New Versions</A
+></H2
+><P
+>&#13; This is the 4.0 version of The Bugzilla Guide. It is so named
+ to match the current version of Bugzilla.
+ </P
+><P
+>&#13; The latest version of this guide can always be found at <A
+HREF="http://www.bugzilla.org/docs/"
+TARGET="_top"
+>http://www.bugzilla.org/docs/</A
+>. However, you should read
+ the version which came with the Bugzilla release you are using.
+ </P
+><P
+>
+ In addition, there are Bugzilla template localization projects in
+ <A
+HREF="http://www.bugzilla.org/download/#localizations"
+TARGET="_top"
+>several languages</A
+>.
+ They may have translated documentation available. If you would like to
+ volunteer to translate the Guide into additional languages, please visit the
+ <A
+HREF="https://wiki.mozilla.org/Bugzilla:L10n"
+TARGET="_top"
+>Bugzilla L10n team</A
+>
+ page.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="credits"
+>1.4. Credits</A
+></H2
+><P
+>&#13; The people listed below have made enormous contributions to the
+ creation of this Guide, through their writing, dedicated hacking efforts,
+ numerous e-mail and IRC support sessions, and overall excellent
+ contribution to the Bugzilla community:
+ </P
+><P
+></P
+><DIV
+CLASS="variablelist"
+><DL
+><DT
+>Matthew P. Barnson <CODE
+CLASS="email"
+>&#60;<A
+HREF="mailto:mbarnson@sisna.com"
+>mbarnson@sisna.com</A
+>&#62;</CODE
+></DT
+><DD
+><P
+>for the Herculean task of pulling together the Bugzilla Guide
+ and shepherding it to 2.14.
+ </P
+></DD
+><DT
+>Terry Weissman <CODE
+CLASS="email"
+>&#60;<A
+HREF="mailto:terry@mozilla.org"
+>terry@mozilla.org</A
+>&#62;</CODE
+></DT
+><DD
+><P
+>for initially writing Bugzilla and creating the README upon
+ which the UNIX installation documentation is largely based.
+ </P
+></DD
+><DT
+>Tara Hernandez <CODE
+CLASS="email"
+>&#60;<A
+HREF="mailto:tara@tequilarists.org"
+>tara@tequilarists.org</A
+>&#62;</CODE
+></DT
+><DD
+><P
+>for keeping Bugzilla development going strong after Terry left
+ mozilla.org and for running landfill.
+ </P
+></DD
+><DT
+>Dave Lawrence <CODE
+CLASS="email"
+>&#60;<A
+HREF="mailto:dkl@redhat.com"
+>dkl@redhat.com</A
+>&#62;</CODE
+></DT
+><DD
+><P
+>for providing insight into the key differences between Red
+ Hat's customized Bugzilla.
+ </P
+></DD
+><DT
+>Dawn Endico <CODE
+CLASS="email"
+>&#60;<A
+HREF="mailto:endico@mozilla.org"
+>endico@mozilla.org</A
+>&#62;</CODE
+></DT
+><DD
+><P
+>for being a hacker extraordinaire and putting up with Matthew's
+ incessant questions and arguments on irc.mozilla.org in #mozwebtools
+ </P
+></DD
+><DT
+>Jacob Steenhagen <CODE
+CLASS="email"
+>&#60;<A
+HREF="mailto:jake@bugzilla.org"
+>jake@bugzilla.org</A
+>&#62;</CODE
+></DT
+><DD
+><P
+>for taking over documentation during the 2.17 development
+ period.
+ </P
+></DD
+><DT
+>Dave Miller <CODE
+CLASS="email"
+>&#60;<A
+HREF="mailto:justdave@bugzilla.org"
+>justdave@bugzilla.org</A
+>&#62;</CODE
+></DT
+><DD
+><P
+>for taking over as project lead when Tara stepped down and
+ continually pushing for the documentation to be the best it can be.
+ </P
+></DD
+></DL
+></DIV
+><P
+>&#13; Thanks also go to the following people for significant contributions
+ to this documentation:
+ Kevin Brannen, Vlad Dascalu, Ben FrantzDale, Eric Hanson, Zach Lipton, Gervase Markham, Andrew Pearson, Joe Robins, Spencer Smith, Ron Teitelbaum, Shane Travis, Martin Wulffeld.
+ </P
+><P
+>&#13; Also, thanks are due to the members of the
+ <A
+HREF="news://news.mozilla.org/mozilla.support.bugzilla"
+TARGET="_top"
+>&#13; mozilla.support.bugzilla</A
+>
+ newsgroup (and its predecessor, netscape.public.mozilla.webtools).
+ Without your discussions, insight, suggestions, and patches,
+ this could never have happened.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="conventions"
+>1.5. Document Conventions</A
+></H2
+><P
+>This document uses the following conventions:</P
+><DIV
+CLASS="informaltable"
+><P
+></P
+><A
+NAME="AEN103"
+></A
+><TABLE
+BORDER="0"
+FRAME="void"
+CLASS="CALSTABLE"
+><COL><COL><THEAD
+><TR
+><TH
+>Descriptions</TH
+><TH
+>Appearance</TH
+></TR
+></THEAD
+><TBODY
+><TR
+><TD
+>Caution</TD
+><TD
+>&#13; <DIV
+CLASS="caution"
+><P
+></P
+><TABLE
+CLASS="caution"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/caution.gif"
+HSPACE="5"
+ALT="Caution"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>Don't run with scissors!</P
+></TD
+></TR
+></TABLE
+></DIV
+>
+ </TD
+></TR
+><TR
+><TD
+>Hint or Tip</TD
+><TD
+>&#13; <DIV
+CLASS="tip"
+><P
+></P
+><TABLE
+CLASS="tip"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/tip.gif"
+HSPACE="5"
+ALT="Tip"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>For best results... </P
+></TD
+></TR
+></TABLE
+></DIV
+>
+ </TD
+></TR
+><TR
+><TD
+>Note</TD
+><TD
+>&#13; <DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>Dear John...</P
+></TD
+></TR
+></TABLE
+></DIV
+>
+ </TD
+></TR
+><TR
+><TD
+>Warning</TD
+><TD
+>&#13; <DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>Read this or the cat gets it.</P
+></TD
+></TR
+></TABLE
+></DIV
+>
+ </TD
+></TR
+><TR
+><TD
+>File or directory name</TD
+><TD
+>&#13; <TT
+CLASS="filename"
+>filename</TT
+>
+ </TD
+></TR
+><TR
+><TD
+>Command to be typed</TD
+><TD
+>&#13; <B
+CLASS="command"
+>command</B
+>
+ </TD
+></TR
+><TR
+><TD
+>Application name</TD
+><TD
+>&#13; <SPAN
+CLASS="application"
+>application</SPAN
+>
+ </TD
+></TR
+><TR
+><TD
+>&#13; Normal user's prompt under bash shell</TD
+><TD
+>bash$</TD
+></TR
+><TR
+><TD
+>&#13; Root user's prompt under bash shell</TD
+><TD
+>bash#</TD
+></TR
+><TR
+><TD
+>&#13; Normal user's prompt under tcsh shell</TD
+><TD
+>tcsh$</TD
+></TR
+><TR
+><TD
+>Environment variables</TD
+><TD
+>&#13; <CODE
+CLASS="envar"
+>VARIABLE</CODE
+>
+ </TD
+></TR
+><TR
+><TD
+>Term found in the glossary</TD
+><TD
+>&#13; <A
+HREF="#gloss-bugzilla"
+><I
+CLASS="glossterm"
+>Bugzilla</I
+></A
+>
+ </TD
+></TR
+><TR
+><TD
+>Code example</TD
+><TD
+>&#13; <TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+><CODE
+CLASS="sgmltag"
+>&#60;para&#62;</CODE
+>
+Beginning and end of paragraph
+<CODE
+CLASS="sgmltag"
+>&#60;/para&#62;</CODE
+></PRE
+></FONT
+></TD
+></TR
+></TABLE
+>
+ </TD
+></TR
+></TBODY
+></TABLE
+><P
+></P
+></DIV
+><P
+>
+ This documentation is maintained in DocBook 4.1.2 XML format.
+ Changes are best submitted as plain text or XML diffs, attached
+ to a bug filed in the <A
+HREF="https://bugzilla.mozilla.org/enter_bug.cgi?product=Bugzilla&#38;component=Documentation"
+TARGET="_top"
+>Bugzilla Documentation</A
+> component.
+ </P
+></DIV
+></DIV
+><DIV
+CLASS="chapter"
+><HR><H1
+><A
+NAME="installing-bugzilla"
+></A
+>Chapter 2. Installing Bugzilla</H1
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="installation"
+>2.1. Installation</A
+></H2
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>If you just want to <EM
+>use</EM
+> Bugzilla,
+ you do not need to install it. None of this chapter is relevant to
+ you. Ask your Bugzilla administrator for the URL to access it from
+ your web browser.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>The Bugzilla server software is usually installed on Linux or
+ Solaris.
+ If you are installing on another OS, check <A
+HREF="#os-specific"
+>Section 2.5</A
+>
+ before you start your installation to see if there are any special
+ instructions.
+ </P
+><P
+>This guide assumes that you have administrative access to the
+ Bugzilla machine. It not possible to
+ install and run Bugzilla itself without administrative access except
+ in the very unlikely event that every single prerequisite is
+ already installed.
+ </P
+><DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>The installation process may make your machine insecure for
+ short periods of time. Make sure there is a firewall between you
+ and the Internet.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; You are strongly recommended to make a backup of your system
+ before installing Bugzilla (and at regular intervals thereafter :-).
+ </P
+><P
+>In outline, the installation proceeds as follows:
+ </P
+><DIV
+CLASS="procedure"
+><OL
+TYPE="1"
+><LI
+CLASS="step"
+><P
+><A
+HREF="#install-perl"
+>Install Perl</A
+>
+ (5.8.1 or above)
+ </P
+></LI
+><LI
+CLASS="step"
+><P
+><A
+HREF="#install-database"
+>Install a Database Engine</A
+>
+ </P
+></LI
+><LI
+CLASS="step"
+><P
+><A
+HREF="#install-webserver"
+>Install a Webserver</A
+>
+ </P
+></LI
+><LI
+CLASS="step"
+><P
+><A
+HREF="#install-bzfiles"
+>Install Bugzilla</A
+>
+ </P
+></LI
+><LI
+CLASS="step"
+><P
+><A
+HREF="#install-perlmodules"
+>Install Perl modules</A
+>
+ </P
+></LI
+><LI
+CLASS="step"
+><P
+>&#13; <A
+HREF="#install-MTA"
+>Install a Mail Transfer Agent</A
+>
+ (Sendmail 8.7 or above, or an MTA that is Sendmail-compatible with at least this version)
+ </P
+></LI
+><LI
+CLASS="step"
+><P
+>Configure all of the above.
+ </P
+></LI
+></OL
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="install-perl"
+>2.1.1. Perl</A
+></H3
+><P
+>Installed Version Test: <TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>perl -v</PRE
+></FONT
+></TD
+></TR
+></TABLE
+></P
+><P
+>Any machine that doesn't have Perl on it is a sad machine indeed.
+ If you don't have it and your OS doesn't provide official packages,
+ visit <A
+HREF="http://www.perl.org"
+TARGET="_top"
+>http://www.perl.org</A
+>.
+ Although Bugzilla runs with Perl 5.8.1,
+ it's a good idea to be using the latest stable version.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="install-database"
+>2.1.2. Database Engine</A
+></H3
+><P
+>&#13; Bugzilla supports MySQL, PostgreSQL and Oracle as database servers.
+ You only require one of these systems to make use of Bugzilla.
+ </P
+><DIV
+CLASS="section"
+><HR><H4
+CLASS="section"
+><A
+NAME="install-mysql"
+>2.1.2.1. MySQL</A
+></H4
+><P
+>Installed Version Test: <TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>mysql -V</PRE
+></FONT
+></TD
+></TR
+></TABLE
+></P
+><P
+>&#13; If you don't have it and your OS doesn't provide official packages,
+ visit <A
+HREF="http://www.mysql.com"
+TARGET="_top"
+>http://www.mysql.com</A
+>. You need MySQL version
+ 4.1.2 or higher.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+> Many of the binary
+ versions of MySQL store their data files in
+ <TT
+CLASS="filename"
+>/var</TT
+>.
+ On some Unix systems, this is part of a smaller root partition,
+ and may not have room for your bug database. To change the data
+ directory, you have to build MySQL from source yourself, and
+ set it as an option to <TT
+CLASS="filename"
+>configure</TT
+>.</P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>If you install from something other than a packaging/installation
+ system, such as .rpm (Redhat Package), .deb (Debian Package), .exe
+ (Windows Executable), or .msi (Microsoft Installer), make sure the MySQL
+ server is started when the machine boots.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H4
+CLASS="section"
+><A
+NAME="install-pg"
+>2.1.2.2. PostgreSQL</A
+></H4
+><P
+>Installed Version Test: <TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>psql -V</PRE
+></FONT
+></TD
+></TR
+></TABLE
+></P
+><P
+>&#13; If you don't have it and your OS doesn't provide official packages,
+ visit <A
+HREF="http://www.postgresql.org/"
+TARGET="_top"
+>http://www.postgresql.org/</A
+>. You need PostgreSQL
+ version 8.00.0000 or higher.
+ </P
+><P
+>If you install from something other than a packaging/installation
+ system, such as .rpm (Redhat Package), .deb (Debian Package), .exe
+ (Windows Executable), or .msi (Microsoft Installer), make sure the
+ PostgreSQL server is started when the machine boots.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H4
+CLASS="section"
+><A
+NAME="install-oracle"
+>2.1.2.3. Oracle</A
+></H4
+><P
+>&#13; Installed Version Test: <TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>select * from v$version</PRE
+></FONT
+></TD
+></TR
+></TABLE
+>
+ (you first have to log in into your DB)
+ </P
+><P
+>&#13; If you don't have it and your OS doesn't provide official packages,
+ visit <A
+HREF="http://www.oracle.com/"
+TARGET="_top"
+>http://www.oracle.com/</A
+>. You need Oracle
+ version 10.02.0 or higher.
+ </P
+><P
+>&#13; If you install from something other than a packaging/installation
+ system, such as .rpm (Redhat Package), .deb (Debian Package), .exe
+ (Windows Executable), or .msi (Microsoft Installer), make sure the
+ Oracle server is started when the machine boots.
+ </P
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="install-webserver"
+>2.1.3. Web Server</A
+></H3
+><P
+>Installed Version Test: view the default welcome page at
+ http://&#60;your-machine&#62;/</P
+><P
+>You have freedom of choice here, pretty much any web server that
+ is capable of running <A
+HREF="#gloss-cgi"
+><I
+CLASS="glossterm"
+>CGI</I
+></A
+>
+ scripts will work.
+ However, we strongly recommend using the Apache web server
+ (either 1.3.x or 2.x), and
+ the installation instructions usually assume you are
+ using it. If you have got Bugzilla working using another web server,
+ please share your experiences with us by filing a bug in <A
+HREF="https://bugzilla.mozilla.org/enter_bug.cgi?product=Bugzilla&#38;component=Documentation"
+TARGET="_top"
+>Bugzilla Documentation</A
+>.
+ </P
+><P
+>&#13; If you don't have Apache and your OS doesn't provide official packages,
+ visit <A
+HREF="http://httpd.apache.org/"
+TARGET="_top"
+>http://httpd.apache.org/</A
+>.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="install-bzfiles"
+>2.1.4. Bugzilla</A
+></H3
+><P
+>&#13; <A
+HREF="http://www.bugzilla.org/download/"
+TARGET="_top"
+>Download a Bugzilla tarball</A
+>
+ (or check it out from CVS) and place
+ it in a suitable directory, accessible by the default web server user
+ (probably <SPAN
+CLASS="QUOTE"
+>"apache"</SPAN
+> or <SPAN
+CLASS="QUOTE"
+>"www"</SPAN
+>).
+ Good locations are either directly in the web server's document directories or
+ in <TT
+CLASS="filename"
+>/usr/local</TT
+> with a symbolic link to the web server's
+ document directories or an alias in the web server's configuration.
+ </P
+><DIV
+CLASS="caution"
+><P
+></P
+><TABLE
+CLASS="caution"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/caution.gif"
+HSPACE="5"
+ALT="Caution"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>The default Bugzilla distribution is NOT designed to be placed
+ in a <TT
+CLASS="filename"
+>cgi-bin</TT
+> directory. This
+ includes any directory which is configured using the
+ <CODE
+CLASS="option"
+>ScriptAlias</CODE
+> directive of Apache.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>Once all the files are in a web accessible directory, make that
+ directory writable by your web server's user. This is a temporary step
+ until you run the
+ <TT
+CLASS="filename"
+>checksetup.pl</TT
+>
+ script, which locks down your installation.</P
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="install-perlmodules"
+>2.1.5. Perl Modules</A
+></H3
+><P
+>Bugzilla's installation process is based
+ on a script called <TT
+CLASS="filename"
+>checksetup.pl</TT
+>.
+ The first thing it checks is whether you have appropriate
+ versions of all the required
+ Perl modules. The aim of this section is to pass this check.
+ When it passes, proceed to <A
+HREF="#configuration"
+>Section 2.2</A
+>.
+ </P
+><P
+>&#13; At this point, you need to <TT
+CLASS="filename"
+>su</TT
+> to root. You should
+ remain as root until the end of the install. To check you have the
+ required modules, run:
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="screen"
+><SAMP
+CLASS="prompt"
+>bash#</SAMP
+> ./checksetup.pl --check-modules</PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>&#13; <TT
+CLASS="filename"
+>checksetup.pl</TT
+> will print out a list of the
+ required and optional Perl modules, together with the versions
+ (if any) installed on your machine.
+ The list of required modules is reasonably long; however, you
+ may already have several of them installed.
+ </P
+><P
+>&#13; The preferred way to install missing Perl modules is to use the package
+ manager provided by your operating system (e.g <SPAN
+CLASS="QUOTE"
+>"rpm"</SPAN
+> or
+ <SPAN
+CLASS="QUOTE"
+>"yum"</SPAN
+> on Linux distros, or <SPAN
+CLASS="QUOTE"
+>"ppm"</SPAN
+> on Windows
+ if using ActivePerl, see <A
+HREF="#win32-perl-modules"
+>Section 2.5.1.2</A
+>).
+ If some Perl modules are still missing or are too old, then we recommend
+ using the <TT
+CLASS="filename"
+>install-module.pl</TT
+> script (doesn't work
+ with ActivePerl on Windows). If for some reason you really need to
+ install the Perl modules manually, see
+ <A
+HREF="#install-perlmodules-manual"
+>Appendix C</A
+>. For instance, on Unix,
+ you invoke <TT
+CLASS="filename"
+>install-module.pl</TT
+> as follows:
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="screen"
+><SAMP
+CLASS="prompt"
+>bash#</SAMP
+> perl install-module.pl &#60;modulename&#62;</PRE
+></FONT
+></TD
+></TR
+></TABLE
+><DIV
+CLASS="tip"
+><P
+></P
+><TABLE
+CLASS="tip"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/tip.gif"
+HSPACE="5"
+ALT="Tip"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>Many people complain that Perl modules will not install for
+ them. Most times, the error messages complain that they are missing a
+ file in
+ <SPAN
+CLASS="QUOTE"
+>"@INC"</SPAN
+>.
+ Virtually every time, this error is due to permissions being set too
+ restrictively for you to compile Perl modules or not having the
+ necessary Perl development libraries installed on your system.
+ Consult your local UNIX systems administrator for help solving these
+ permissions issues; if you
+ <EM
+>are</EM
+>
+ the local UNIX sysadmin, please consult the newsgroup/mailing list
+ for further assistance or hire someone to help you out.</P
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>If you are using a package-based system, and attempting to install the
+ Perl modules from CPAN, you may need to install the "development" packages for
+ MySQL and GD before attempting to install the related Perl modules. The names of
+ these packages will vary depending on the specific distribution you are using,
+ but are often called <TT
+CLASS="filename"
+>&#60;packagename&#62;-devel</TT
+>.</P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; Here is a complete list of modules and their minimum versions.
+ Some modules have special installation notes, which follow.
+ </P
+><P
+>Required Perl modules:
+ <P
+></P
+><OL
+TYPE="1"
+><LI
+><P
+>&#13; CGI (3.51)
+ </P
+></LI
+><LI
+><P
+>&#13; Date::Format (2.21)
+ </P
+></LI
+><LI
+><P
+>&#13; DateTime (0.28)
+ </P
+></LI
+><LI
+><P
+>&#13; DateTime::TimeZone (0.71)
+ </P
+></LI
+><LI
+><P
+>&#13; DBI (1.41)
+ </P
+></LI
+><LI
+><P
+>&#13; DBD::mysql (4.00) if using MySQL
+ </P
+></LI
+><LI
+><P
+>&#13; DBD::Pg (1.45) if using PostgreSQL
+ </P
+></LI
+><LI
+><P
+>&#13; DBD::Oracle (1.19) if using Oracle
+ </P
+></LI
+><LI
+><P
+>&#13; Digest::SHA (any)
+ </P
+></LI
+><LI
+><P
+>&#13; Email::Send (2.00)
+ </P
+></LI
+><LI
+><P
+>&#13; Email::MIME (1.904)
+ </P
+></LI
+><LI
+><P
+>&#13; Template (2.22)
+ </P
+></LI
+><LI
+><P
+>&#13; URI (any)
+ </P
+></LI
+></OL
+>
+
+ Optional Perl modules:
+ <P
+></P
+><OL
+TYPE="1"
+><LI
+><P
+>&#13; GD (1.20) for bug charting
+ </P
+></LI
+><LI
+><P
+>&#13; Template::Plugin::GD::Image
+ (any) for Graphical Reports
+ </P
+></LI
+><LI
+><P
+>&#13; Chart::Lines (2.1) for bug charting
+ </P
+></LI
+><LI
+><P
+>&#13; GD::Graph (any) for bug charting
+ </P
+></LI
+><LI
+><P
+>&#13; GD::Text (any) for bug charting
+ </P
+></LI
+><LI
+><P
+>&#13; XML::Twig (any) for bug import/export
+ </P
+></LI
+><LI
+><P
+>&#13; MIME::Parser (5.406) for bug import/export
+ </P
+></LI
+><LI
+><P
+>&#13; LWP::UserAgent
+ (any) for Automatic Update Notifications
+ </P
+></LI
+><LI
+><P
+>&#13; PatchReader (0.9.4) for pretty HTML view of patches
+ </P
+></LI
+><LI
+><P
+>&#13; Net::LDAP
+ (any) for LDAP Authentication
+ </P
+></LI
+><LI
+><P
+>&#13; Authen::SASL
+ (any) for SASL Authentication
+ </P
+></LI
+><LI
+><P
+>&#13; Authen::Radius
+ (any) for RADIUS Authentication
+ </P
+></LI
+><LI
+><P
+>&#13; SOAP::Lite (0.712) for the web service interface
+ </P
+></LI
+><LI
+><P
+>&#13; JSON::RPC
+ (any) for the JSON-RPC interface
+ </P
+></LI
+><LI
+><P
+>&#13; Test::Taint
+ (any) for the web service interface
+ </P
+></LI
+><LI
+><P
+>&#13; HTML::Parser
+ (3.40) for More HTML in Product/Group Descriptions
+ </P
+></LI
+><LI
+><P
+>&#13; HTML::Scrubber
+ (any) for More HTML in Product/Group Descriptions
+ </P
+></LI
+><LI
+><P
+>&#13; Email::MIME::Attachment::Stripper
+ (any) for Inbound Email
+ </P
+></LI
+><LI
+><P
+>&#13; Email::Reply
+ (any) for Inbound Email
+ </P
+></LI
+><LI
+><P
+>&#13; TheSchwartz
+ (any) for Mail Queueing
+ </P
+></LI
+><LI
+><P
+>&#13; Daemon::Generic
+ (any) for Mail Queueing
+ </P
+></LI
+><LI
+><P
+>&#13; mod_perl2
+ (1.999022) for mod_perl
+ </P
+></LI
+></OL
+>
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="install-MTA"
+>2.1.6. Mail Transfer Agent (MTA)</A
+></H3
+><P
+>&#13; Bugzilla is dependent on the availability of an e-mail system for its
+ user authentication and for other tasks.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; This is not entirely true. It is possible to completely disable
+ email sending, or to have Bugzilla store email messages in a
+ file instead of sending them. However, this is mainly intended
+ for testing, as disabling or diverting email on a production
+ machine would mean that users could miss important events (such
+ as bug changes or the creation of new accounts).
+ </P
+><P
+>&#13; For more information, see the <SPAN
+CLASS="QUOTE"
+>"mail_delivery_method"</SPAN
+> parameter
+ in <A
+HREF="#parameters"
+>Section 3.1</A
+>.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; On Linux, any Sendmail-compatible MTA (Mail Transfer Agent) will
+ suffice. Sendmail, Postfix, qmail and Exim are examples of common
+ MTAs. Sendmail is the original Unix MTA, but the others are easier to
+ configure, and therefore many people replace Sendmail with Postfix or
+ Exim. They are drop-in replacements, so Bugzilla will not
+ distinguish between them.
+ </P
+><P
+>&#13; If you are using Sendmail, version 8.7 or higher is required.
+ If you are using a Sendmail-compatible MTA, it must be congruent with
+ at least version 8.7 of Sendmail.
+ </P
+><P
+>&#13; Consult the manual for the specific MTA you choose for detailed
+ installation instructions. Each of these programs will have their own
+ configuration files where you must configure certain parameters to
+ ensure that the mail is delivered properly. They are implemented
+ as services, and you should ensure that the MTA is in the auto-start
+ list of services for the machine.
+ </P
+><P
+>&#13; If a simple mail sent with the command-line 'mail' program
+ succeeds, then Bugzilla should also be fine.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="using-mod_perl-with-bugzilla"
+>2.1.7. Installing Bugzilla on mod_perl</A
+></H3
+><P
+>It is now possible to run the Bugzilla software under <TT
+CLASS="literal"
+>mod_perl</TT
+> on
+ Apache. <TT
+CLASS="literal"
+>mod_perl</TT
+> has some additional requirements to that of running
+ Bugzilla under <TT
+CLASS="literal"
+>mod_cgi</TT
+> (the standard and previous way).</P
+><P
+>Bugzilla requires <TT
+CLASS="literal"
+>mod_perl</TT
+> to be installed, which can be
+ obtained from <A
+HREF="http://perl.apache.org"
+TARGET="_top"
+>http://perl.apache.org</A
+> - Bugzilla requires
+ version 1.999022 (AKA 2.0.0-RC5) to be installed.</P
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="configuration"
+>2.2. Configuration</A
+></H2
+><DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Poorly-configured MySQL and Bugzilla installations have
+ given attackers full access to systems in the past. Please take the
+ security parts of these guidelines seriously, even for Bugzilla
+ machines hidden away behind your firewall. Be certain to read
+ <A
+HREF="#security"
+>Chapter 4</A
+> for some important security tips.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="localconfig"
+>2.2.1. localconfig</A
+></H3
+><P
+>&#13; You should now run <TT
+CLASS="filename"
+>checksetup.pl</TT
+> again, this time
+ without the <TT
+CLASS="literal"
+>--check-modules</TT
+> switch.
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="screen"
+><SAMP
+CLASS="prompt"
+>bash#</SAMP
+> ./checksetup.pl</PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>&#13; This time, <TT
+CLASS="filename"
+>checksetup.pl</TT
+> should tell you that all
+ the correct modules are installed and will display a message about, and
+ write out a file called, <TT
+CLASS="filename"
+>localconfig</TT
+>. This file
+ contains the default settings for a number of Bugzilla parameters.
+ </P
+><P
+>&#13; Load this file in your editor. The only two values you
+ <EM
+>need</EM
+> to change are $db_driver and $db_pass,
+ respectively the type of the database and the password for
+ the user you will create for your database. Pick a strong
+ password (for simplicity, it should not contain single quote
+ characters) and put it here. $db_driver can be either 'mysql',
+ 'Pg' or 'oracle'.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; In Oracle, <TT
+CLASS="literal"
+>$db_name</TT
+> should actually be
+ the SID name of your database (e.g. "XE" if you are using Oracle XE).
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; You may need to change the value of
+ <EM
+>webservergroup</EM
+> if your web server does not
+ run in the "apache" group. On Debian, for example, Apache runs in
+ the "www-data" group. If you are going to run Bugzilla on a
+ machine where you do not have root access (such as on a shared web
+ hosting account), you will need to leave
+ <EM
+>webservergroup</EM
+> empty, ignoring the warnings
+ that <TT
+CLASS="filename"
+>checksetup.pl</TT
+> will subsequently display
+ every time it is run.
+ </P
+><DIV
+CLASS="caution"
+><P
+></P
+><TABLE
+CLASS="caution"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/caution.gif"
+HSPACE="5"
+ALT="Caution"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; If you are using suexec, you should use your own primary group
+ for <EM
+>webservergroup</EM
+> rather than leaving it
+ empty, and see the additional directions in the suexec section
+ <A
+HREF="#suexec"
+>Section 2.6.6.1</A
+>.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; The other options in the <TT
+CLASS="filename"
+>localconfig</TT
+> file
+ are documented by their accompanying comments. If you have a slightly
+ non-standard database setup, you may wish to change one or more of
+ the other "$db_*" parameters.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="database-engine"
+>2.2.2. Database Server</A
+></H3
+><P
+>&#13; This section deals with configuring your database server for use
+ with Bugzilla. Currently, MySQL (<A
+HREF="#mysql"
+>Section 2.2.2.2</A
+>),
+ PostgreSQL (<A
+HREF="#postgresql"
+>Section 2.2.2.3</A
+>) and Oracle (<A
+HREF="#oracle"
+>Section 2.2.2.4</A
+>)
+ are available.
+ </P
+><DIV
+CLASS="section"
+><HR><H4
+CLASS="section"
+><A
+NAME="database-schema"
+>2.2.2.1. Bugzilla Database Schema</A
+></H4
+><P
+>&#13; The Bugzilla database schema is available at
+ <A
+HREF="http://www.ravenbrook.com/project/p4dti/tool/cgi/bugzilla-schema/"
+TARGET="_top"
+>Ravenbrook</A
+>.
+ This very valuable tool can generate a written description of
+ the Bugzilla database schema for any version of Bugzilla. It
+ can also generate a diff between two versions to help someone
+ see what has changed.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H4
+CLASS="section"
+><A
+NAME="mysql"
+>2.2.2.2. MySQL</A
+></H4
+><DIV
+CLASS="caution"
+><P
+></P
+><TABLE
+CLASS="caution"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/caution.gif"
+HSPACE="5"
+ALT="Caution"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; MySQL's default configuration is insecure.
+ We highly recommend to run <TT
+CLASS="filename"
+>mysql_secure_installation</TT
+>
+ on Linux or the MySQL installer on Windows, and follow the instructions.
+ Important points to note are:
+ <P
+></P
+><OL
+TYPE="1"
+><LI
+><P
+>Be sure that the root account has a secure password set.</P
+></LI
+><LI
+><P
+>Do not create an anonymous account, and if it exists, say "yes"
+ to remove it.</P
+></LI
+><LI
+><P
+>If your web server and MySQL server are on the same machine,
+ you should disable the network access.</P
+></LI
+></OL
+>
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="section"
+><HR><H5
+CLASS="section"
+><A
+NAME="mysql-max-allowed-packet"
+>2.2.2.2.1. Allow large attachments and many comments</A
+></H5
+><P
+>By default, MySQL will only allow you to insert things
+ into the database that are smaller than 1MB. Attachments
+ may be larger than this. Also, Bugzilla combines all comments
+ on a single bug into one field for full-text searching, and the
+ combination of all comments on a single bug could in some cases
+ be larger than 1MB.</P
+><P
+>To change MySQL's default, you need to edit your MySQL
+ configuration file, which is usually <TT
+CLASS="filename"
+>/etc/my.cnf</TT
+>
+ on Linux. We recommend that you allow at least 4MB packets by
+ adding the "max_allowed_packet" parameter to your MySQL
+ configuration in the "[mysqld]" section, like this:</P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="screen"
+>[mysqld]
+# Allow packets up to 4MB
+max_allowed_packet=4M
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="section"
+><HR><H5
+CLASS="section"
+><A
+NAME="AEN438"
+>2.2.2.2.2. Allow small words in full-text indexes</A
+></H5
+><P
+>By default, words must be at least four characters in length
+ in order to be indexed by MySQL's full-text indexes. This causes
+ a lot of Bugzilla specific words to be missed, including "cc",
+ "ftp" and "uri".</P
+><P
+>MySQL can be configured to index those words by setting the
+ ft_min_word_len param to the minimum size of the words to index.
+ This can be done by modifying the <TT
+CLASS="filename"
+>/etc/my.cnf</TT
+>
+ according to the example below:</P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="screen"
+> [mysqld]
+ # Allow small words in full-text indexes
+ ft_min_word_len=2</PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>Rebuilding the indexes can be done based on documentation found at
+ <A
+HREF="http://www.mysql.com/doc/en/Fulltext_Fine-tuning.html"
+TARGET="_top"
+>http://www.mysql.com/doc/en/Fulltext_Fine-tuning.html</A
+>.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H5
+CLASS="section"
+><A
+NAME="install-setupdatabase-adduser"
+>2.2.2.2.3. Add a user to MySQL</A
+></H5
+><P
+>&#13; You need to add a new MySQL user for Bugzilla to use.
+ (It's not safe to have Bugzilla use the MySQL root account.)
+ The following instructions assume the defaults in
+ <TT
+CLASS="filename"
+>localconfig</TT
+>; if you changed those,
+ you need to modify the SQL command appropriately. You will
+ need the <TT
+CLASS="replaceable"
+><I
+>$db_pass</I
+></TT
+> password you
+ set in <TT
+CLASS="filename"
+>localconfig</TT
+> in
+ <A
+HREF="#localconfig"
+>Section 2.2.1</A
+>.
+ </P
+><P
+>&#13; We use an SQL <B
+CLASS="command"
+>GRANT</B
+> command to create
+ a <SPAN
+CLASS="QUOTE"
+>"bugs"</SPAN
+> user. This also restricts the
+ <SPAN
+CLASS="QUOTE"
+>"bugs"</SPAN
+>user to operations within a database
+ called <SPAN
+CLASS="QUOTE"
+>"bugs"</SPAN
+>, and only allows the account
+ to connect from <SPAN
+CLASS="QUOTE"
+>"localhost"</SPAN
+>. Modify it to
+ reflect your setup if you will be connecting from another
+ machine or as a different user.
+ </P
+><P
+>&#13; Run the <TT
+CLASS="filename"
+>mysql</TT
+> command-line client and enter:
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="screen"
+>&#13; <SAMP
+CLASS="prompt"
+>mysql&#62;</SAMP
+> GRANT SELECT, INSERT,
+ UPDATE, DELETE, INDEX, ALTER, CREATE, LOCK TABLES,
+ CREATE TEMPORARY TABLES, DROP, REFERENCES ON bugs.*
+ TO bugs@localhost IDENTIFIED BY '<TT
+CLASS="replaceable"
+><I
+>$db_pass</I
+></TT
+>';
+ <SAMP
+CLASS="prompt"
+>mysql&#62;</SAMP
+> FLUSH PRIVILEGES;
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="section"
+><HR><H5
+CLASS="section"
+><A
+NAME="AEN465"
+>2.2.2.2.4. Permit attachments table to grow beyond 4GB</A
+></H5
+><P
+>&#13; By default, MySQL will limit the size of a table to 4GB.
+ This limit is present even if the underlying filesystem
+ has no such limit. To set a higher limit, follow these
+ instructions.
+ </P
+><P
+>&#13; After you have completed the rest of the installation (or at least the
+ database setup parts), you should run the <TT
+CLASS="filename"
+>MySQL</TT
+>
+ command-line client and enter the following, replacing <TT
+CLASS="literal"
+>$bugs_db</TT
+>
+ with your Bugzilla database name (<EM
+>bugs</EM
+> by default):
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="screen"
+>&#13; <SAMP
+CLASS="prompt"
+>mysql&#62;</SAMP
+> use <TT
+CLASS="replaceable"
+><I
+>$bugs_db</I
+></TT
+>
+ <SAMP
+CLASS="prompt"
+>mysql&#62;</SAMP
+> ALTER TABLE attachments
+ AVG_ROW_LENGTH=1000000, MAX_ROWS=20000;
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>&#13; The above command will change the limit to 20GB. Mysql will have
+ to make a temporary copy of your entire table to do this. Ideally,
+ you should do this when your attachments table is still small.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; This does not affect Big Files, attachments that are stored directly
+ on disk instead of in the database.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H4
+CLASS="section"
+><A
+NAME="postgresql"
+>2.2.2.3. PostgreSQL</A
+></H4
+><DIV
+CLASS="section"
+><H5
+CLASS="section"
+><A
+NAME="AEN481"
+>2.2.2.3.1. Add a User to PostgreSQL</A
+></H5
+><P
+>You need to add a new user to PostgreSQL for the Bugzilla
+ application to use when accessing the database. The following instructions
+ assume the defaults in <TT
+CLASS="filename"
+>localconfig</TT
+>; if you
+ changed those, you need to modify the commands appropriately. You will
+ need the <TT
+CLASS="replaceable"
+><I
+>$db_pass</I
+></TT
+> password you
+ set in <TT
+CLASS="filename"
+>localconfig</TT
+> in
+ <A
+HREF="#localconfig"
+>Section 2.2.1</A
+>.</P
+><P
+>On most systems, to create the user in PostgreSQL, you will need to
+ login as the root user, and then</P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="screen"
+> <SAMP
+CLASS="prompt"
+>bash#</SAMP
+> su - postgres</PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>As the postgres user, you then need to create a new user: </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="screen"
+> <SAMP
+CLASS="prompt"
+>bash$</SAMP
+> createuser -U postgres -dRSP bugs</PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>When asked for a password, provide the password which will be set as
+ <TT
+CLASS="replaceable"
+><I
+>$db_pass</I
+></TT
+> in <TT
+CLASS="filename"
+>localconfig</TT
+>.
+ The created user will not be a superuser (-S) and will not be able to create
+ new users (-R). He will only have the ability to create databases (-d).</P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>If your are running PostgreSQL 8.0, you must replace -dRSP by -dAP.</P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H5
+CLASS="section"
+><A
+NAME="AEN499"
+>2.2.2.3.2. Configure PostgreSQL</A
+></H5
+><P
+>Now, you will need to edit <TT
+CLASS="filename"
+>pg_hba.conf</TT
+> which is
+ usually located in <TT
+CLASS="filename"
+>/var/lib/pgsql/data/</TT
+>. In this file,
+ you will need to add a new line to it as follows:</P
+><P
+>&#13; <SAMP
+CLASS="computeroutput"
+>host all bugs 127.0.0.1 255.255.255.255 md5</SAMP
+>
+ </P
+><P
+>This means that for TCP/IP (host) connections, allow connections from
+ '127.0.0.1' to 'all' databases on this server from the 'bugs' user, and use
+ password authentication (md5) for that user.</P
+><P
+>Now, you will need to restart PostgreSQL, but you will need to fully
+ stop and start the server rather than just restarting due to the possibility
+ of a change to <TT
+CLASS="filename"
+>postgresql.conf</TT
+>. After the server has
+ restarted, you will need to edit <TT
+CLASS="filename"
+>localconfig</TT
+>, finding
+ the <TT
+CLASS="literal"
+>$db_driver</TT
+> variable and setting it to
+ <TT
+CLASS="literal"
+>Pg</TT
+> and changing the password in <TT
+CLASS="literal"
+>$db_pass</TT
+>
+ to the one you picked previously, while setting up the account.</P
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H4
+CLASS="section"
+><A
+NAME="oracle"
+>2.2.2.4. Oracle</A
+></H4
+><DIV
+CLASS="section"
+><H5
+CLASS="section"
+><A
+NAME="AEN515"
+>2.2.2.4.1. Create a New Tablespace</A
+></H5
+><P
+>&#13; You can use the existing tablespace or create a new one for Bugzilla.
+ To create a new tablespace, run the following command:
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#13; CREATE TABLESPACE bugs
+ DATAFILE '<TT
+CLASS="replaceable"
+><I
+>$path_to_datafile</I
+></TT
+>' SIZE 500M
+ AUTOEXTEND ON NEXT 30M MAXSIZE UNLIMITED
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>&#13; Here, the name of the tablespace is 'bugs', but you can
+ choose another name. <TT
+CLASS="replaceable"
+><I
+>$path_to_datafile</I
+></TT
+> is
+ the path to the file containing your database, for instance
+ <TT
+CLASS="filename"
+>/u01/oradata/bugzilla.dbf</TT
+>.
+ The initial size of the database file is set in this example to 500 Mb,
+ with an increment of 30 Mb everytime we reach the size limit of the file.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H5
+CLASS="section"
+><A
+NAME="AEN523"
+>2.2.2.4.2. Add a User to Oracle</A
+></H5
+><P
+>&#13; The user name and password must match what you set in
+ <TT
+CLASS="filename"
+>localconfig</TT
+> (<TT
+CLASS="literal"
+>$db_user</TT
+>
+ and <TT
+CLASS="literal"
+>$db_pass</TT
+>, respectively). Here, we assume that
+ the user name is 'bugs' and the tablespace name is the same
+ as above.
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#13; CREATE USER bugs
+ IDENTIFIED BY "<TT
+CLASS="replaceable"
+><I
+>$db_pass</I
+></TT
+>"
+ DEFAULT TABLESPACE bugs
+ TEMPORARY TABLESPACE TEMP
+ PROFILE DEFAULT;
+ -- GRANT/REVOKE ROLE PRIVILEGES
+ GRANT CONNECT TO bugs;
+ GRANT RESOURCE TO bugs;
+ -- GRANT/REVOKE SYSTEM PRIVILEGES
+ GRANT UNLIMITED TABLESPACE TO bugs;
+ GRANT EXECUTE ON CTXSYS.CTX_DDL TO bugs;
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="section"
+><HR><H5
+CLASS="section"
+><A
+NAME="AEN531"
+>2.2.2.4.3. Configure the Web Server</A
+></H5
+><P
+>&#13; If you use Apache, append these lines to <TT
+CLASS="filename"
+>httpd.conf</TT
+>
+ to set ORACLE_HOME and LD_LIBRARY_PATH. For instance:
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#13; SetEnv ORACLE_HOME /u01/app/oracle/product/10.2.0/
+ SetEnv LD_LIBRARY_PATH /u01/app/oracle/product/10.2.0/lib/
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>&#13; When this is done, restart your web server.
+ </P
+></DIV
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="AEN537"
+>2.2.3. checksetup.pl</A
+></H3
+><P
+>&#13; Next, rerun <TT
+CLASS="filename"
+>checksetup.pl</TT
+>. It reconfirms
+ that all the modules are present, and notices the altered
+ localconfig file, which it assumes you have edited to your
+ satisfaction. It compiles the UI templates,
+ connects to the database using the 'bugs'
+ user you created and the password you defined, and creates the
+ 'bugs' database and the tables therein.
+ </P
+><P
+>&#13; After that, it asks for details of an administrator account. Bugzilla
+ can have multiple administrators - you can create more later - but
+ it needs one to start off with.
+ Enter the email address of an administrator, his or her full name,
+ and a suitable Bugzilla password.
+ </P
+><P
+>&#13; <TT
+CLASS="filename"
+>checksetup.pl</TT
+> will then finish. You may rerun
+ <TT
+CLASS="filename"
+>checksetup.pl</TT
+> at any time if you wish.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="http"
+>2.2.4. Web server</A
+></H3
+><P
+>&#13; Configure your web server according to the instructions in the
+ appropriate section. (If it makes a difference in your choice,
+ the Bugzilla Team recommends Apache.) To check whether your web server
+ is correctly configured, try to access <TT
+CLASS="filename"
+>testagent.cgi</TT
+>
+ from your web server. If "OK" is displayed, then your configuration
+ is successful. Regardless of which web server
+ you are using, however, ensure that sensitive information is
+ not remotely available by properly applying the access controls in
+ <A
+HREF="#security-webserver-access"
+>Section 4.2.1</A
+>. You can run
+ <TT
+CLASS="filename"
+>testserver.pl</TT
+> to check if your web server serves
+ Bugzilla files as expected.
+ </P
+><DIV
+CLASS="section"
+><HR><H4
+CLASS="section"
+><A
+NAME="http-apache"
+>2.2.4.1. Bugzilla using Apache</A
+></H4
+><P
+>You have two options for running Bugzilla under Apache -
+ <A
+HREF="#http-apache-mod_cgi"
+>mod_cgi</A
+> (the default) and
+ <A
+HREF="#http-apache-mod_perl"
+>mod_perl</A
+> (new in Bugzilla
+ 2.23)
+ </P
+><DIV
+CLASS="section"
+><HR><H5
+CLASS="section"
+><A
+NAME="http-apache-mod_cgi"
+>2.2.4.1.1. Apache <SPAN
+CLASS="productname"
+>httpd</SPAN
+> with mod_cgi</A
+></H5
+><P
+>&#13; To configure your Apache web server to work with Bugzilla while using
+ mod_cgi, do the following:
+ </P
+><DIV
+CLASS="procedure"
+><OL
+TYPE="1"
+><LI
+CLASS="step"
+><P
+>&#13; Load <TT
+CLASS="filename"
+>httpd.conf</TT
+> in your editor.
+ In Fedora and Red Hat Linux, this file is found in
+ <TT
+CLASS="filename"
+>/etc/httpd/conf</TT
+>.
+ </P
+></LI
+><LI
+CLASS="step"
+><P
+>&#13; Apache uses <SAMP
+CLASS="computeroutput"
+>&#60;Directory&#62;</SAMP
+>
+ directives to permit fine-grained permission setting. Add the
+ following lines to a directive that applies to the location
+ of your Bugzilla installation. (If such a section does not
+ exist, you'll want to add one.) In this example, Bugzilla has
+ been installed at
+ <TT
+CLASS="filename"
+>/var/www/html/bugzilla</TT
+>.
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#13; &#60;Directory /var/www/html/bugzilla&#62;
+ AddHandler cgi-script .cgi
+ Options +Indexes +ExecCGI
+ DirectoryIndex index.cgi
+ AllowOverride Limit FileInfo Indexes
+ &#60;/Directory&#62;
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>&#13; These instructions: allow apache to run .cgi files found
+ within the bugzilla directory; instructs the server to look
+ for a file called <TT
+CLASS="filename"
+>index.cgi</TT
+> if someone
+ only types the directory name into the browser; and allows
+ Bugzilla's <TT
+CLASS="filename"
+>.htaccess</TT
+> files to override
+ global permissions.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; It is possible to make these changes globally, or to the
+ directive controlling Bugzilla's parent directory (e.g.
+ <SAMP
+CLASS="computeroutput"
+>&#60;Directory /var/www/html/&#62;</SAMP
+>).
+ Such changes would also apply to the Bugzilla directory...
+ but they would also apply to many other places where they
+ may or may not be appropriate. In most cases, including
+ this one, it is better to be as restrictive as possible
+ when granting extra access.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; On Windows, you may have to also add the
+ <SAMP
+CLASS="computeroutput"
+>ScriptInterpreterSource Registry-Strict</SAMP
+>
+ line, see <A
+HREF="#win32-http"
+>Windows specific notes</A
+>.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></LI
+><LI
+CLASS="step"
+><P
+>&#13; <TT
+CLASS="filename"
+>checksetup.pl</TT
+> can set tighter permissions
+ on Bugzilla's files and directories if it knows what group the
+ web server runs as. Find the <SAMP
+CLASS="computeroutput"
+>Group</SAMP
+>
+ line in <TT
+CLASS="filename"
+>httpd.conf</TT
+>, place the value found
+ there in the <TT
+CLASS="replaceable"
+><I
+>$webservergroup</I
+></TT
+> variable
+ in <TT
+CLASS="filename"
+>localconfig</TT
+>, then rerun
+ <TT
+CLASS="filename"
+>checksetup.pl</TT
+>.
+ </P
+></LI
+><LI
+CLASS="step"
+><P
+>&#13; Optional: If Bugzilla does not actually reside in the webspace
+ directory, but instead has been symbolically linked there, you
+ will need to add the following to the
+ <SAMP
+CLASS="computeroutput"
+>Options</SAMP
+> line of the Bugzilla
+ <SAMP
+CLASS="computeroutput"
+>&#60;Directory&#62;</SAMP
+> directive
+ (the same one as in the step above):
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#13; +FollowSymLinks
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>&#13; Without this directive, Apache will not follow symbolic links
+ to places outside its own directory structure, and you will be
+ unable to run Bugzilla.
+ </P
+></LI
+></OL
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H5
+CLASS="section"
+><A
+NAME="http-apache-mod_perl"
+>2.2.4.1.2. Apache <SPAN
+CLASS="productname"
+>httpd</SPAN
+> with mod_perl</A
+></H5
+><P
+>Some configuration is required to make Bugzilla work with Apache
+ and mod_perl</P
+><DIV
+CLASS="procedure"
+><OL
+TYPE="1"
+><LI
+CLASS="step"
+><P
+>&#13; Load <TT
+CLASS="filename"
+>httpd.conf</TT
+> in your editor.
+ In Fedora and Red Hat Linux, this file is found in
+ <TT
+CLASS="filename"
+>/etc/httpd/conf</TT
+>.
+ </P
+></LI
+><LI
+CLASS="step"
+><P
+>Add the following information to your httpd.conf file, substituting
+ where appropriate with your own local paths.</P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>This should be used instead of the &#60;Directory&#62; block
+ shown above. This should also be above any other <TT
+CLASS="literal"
+>mod_perl</TT
+>
+ directives within the <TT
+CLASS="filename"
+>httpd.conf</TT
+> and must be specified
+ in the order as below.</P
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>You should also ensure that you have disabled <TT
+CLASS="literal"
+>KeepAlive</TT
+>
+ support in your Apache install when utilizing Bugzilla under mod_perl</P
+></TD
+></TR
+></TABLE
+></DIV
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#13; PerlSwitches -w -T
+ PerlConfigRequire /var/www/html/bugzilla/mod_perl.pl
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+></LI
+><LI
+CLASS="step"
+><P
+>&#13; <TT
+CLASS="filename"
+>checksetup.pl</TT
+> can set tighter permissions
+ on Bugzilla's files and directories if it knows what group the
+ web server runs as. Find the <SAMP
+CLASS="computeroutput"
+>Group</SAMP
+>
+ line in <TT
+CLASS="filename"
+>httpd.conf</TT
+>, place the value found
+ there in the <TT
+CLASS="replaceable"
+><I
+>$webservergroup</I
+></TT
+> variable
+ in <TT
+CLASS="filename"
+>localconfig</TT
+>, then rerun
+ <TT
+CLASS="filename"
+>checksetup.pl</TT
+>.
+ </P
+></LI
+></OL
+></DIV
+><P
+>On restarting Apache, Bugzilla should now be running within the
+ mod_perl environment. Please ensure you have run checksetup.pl to set
+ permissions before you restart Apache.</P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>Please bear the following points in mind when looking at using
+ Bugzilla under mod_perl:
+ <P
+></P
+><UL
+><LI
+><P
+>&#13; mod_perl support in Bugzilla can take up a HUGE amount of RAM. You could be
+ looking at 30MB per httpd child, easily. Basically, you just need a lot of RAM.
+ The more RAM you can get, the better. mod_perl is basically trading RAM for
+ speed. At least 2GB total system RAM is recommended for running Bugzilla under
+ mod_perl.
+ </P
+></LI
+><LI
+><P
+>&#13; Under mod_perl, you have to restart Apache if you make any manual change to
+ any Bugzilla file. You can't just reload--you have to actually
+ <EM
+>restart</EM
+> the server (as in make sure it stops and starts
+ again). You <EM
+>can</EM
+> change localconfig and the params file
+ manually, if you want, because those are re-read every time you load a page.
+ </P
+></LI
+><LI
+><P
+>&#13; You must run in Apache's Prefork MPM (this is the default). The Worker MPM
+ may not work--we haven't tested Bugzilla's mod_perl support under threads.
+ (And, in fact, we're fairly sure it <EM
+>won't</EM
+> work.)
+ </P
+></LI
+><LI
+><P
+>&#13; Bugzilla generally expects to be the only mod_perl application running on
+ your entire server. It may or may not work if there are other applications also
+ running under mod_perl. It does try its best to play nice with other mod_perl
+ applications, but it still may have conflicts.
+ </P
+></LI
+><LI
+><P
+>&#13; It is recommended that you have one Bugzilla instance running under mod_perl
+ on your server. Bugzilla has not been tested with more than one instance running.
+ </P
+></LI
+></UL
+>
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H4
+CLASS="section"
+><A
+NAME="http-iis"
+>2.2.4.2. Microsoft <SPAN
+CLASS="productname"
+>Internet Information Services</SPAN
+></A
+></H4
+><P
+>&#13; If you are running Bugzilla on Windows and choose to use
+ Microsoft's <SPAN
+CLASS="productname"
+>Internet Information Services</SPAN
+>
+ or <SPAN
+CLASS="productname"
+>Personal Web Server</SPAN
+> you will need
+ to perform a number of other configuration steps as explained below.
+ You may also want to refer to the following Microsoft Knowledge
+ Base articles:
+ <A
+HREF="http://support.microsoft.com/default.aspx?scid=kb;en-us;245225"
+TARGET="_top"
+>245225</A
+>
+ <SPAN
+CLASS="QUOTE"
+>"HOW TO: Configure and Test a PERL Script with IIS 4.0,
+ 5.0, and 5.1"</SPAN
+> (for <SPAN
+CLASS="productname"
+>Internet Information
+ Services</SPAN
+>) and
+ <A
+HREF="http://support.microsoft.com/default.aspx?scid=kb;en-us;231998"
+TARGET="_top"
+>231998</A
+>
+ <SPAN
+CLASS="QUOTE"
+>"HOW TO: FP2000: How to Use Perl with Microsoft Personal Web
+ Server on Windows 95/98"</SPAN
+> (for <SPAN
+CLASS="productname"
+>Personal Web
+ Server</SPAN
+>).
+ </P
+><P
+>&#13; You will need to create a virtual directory for the Bugzilla
+ install. Put the Bugzilla files in a directory that is named
+ something <EM
+>other</EM
+> than what you want your
+ end-users accessing. That is, if you want your users to access
+ your Bugzilla installation through
+ <SPAN
+CLASS="QUOTE"
+>"http://&#60;yourdomainname&#62;/Bugzilla"</SPAN
+>, then do
+ <EM
+>not</EM
+> put your Bugzilla files in a directory
+ named <SPAN
+CLASS="QUOTE"
+>"Bugzilla"</SPAN
+>. Instead, place them in a different
+ location, and then use the IIS Administration tool to create a
+ Virtual Directory named "Bugzilla" that acts as an alias for the
+ actual location of the files. When creating that virtual directory,
+ make sure you add the <SPAN
+CLASS="QUOTE"
+>"Execute (such as ISAPI applications or
+ CGI)"</SPAN
+> access permission.
+ </P
+><P
+>&#13; You will also need to tell IIS how to handle Bugzilla's
+ .cgi files. Using the IIS Administration tool again, open up
+ the properties for the new virtual directory and select the
+ Configuration option to access the Script Mappings. Create an
+ entry mapping .cgi to:
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#13;&#60;full path to perl.exe &#62;\perl.exe -x&#60;full path to Bugzilla&#62; -wT "%s" %s
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>&#13; For example:
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#13;c:\perl\bin\perl.exe -xc:\bugzilla -wT "%s" %s
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; The ActiveState install may have already created an entry for
+ .pl files that is limited to <SPAN
+CLASS="QUOTE"
+>"GET,HEAD,POST"</SPAN
+>. If
+ so, this mapping should be <EM
+>removed</EM
+> as
+ Bugzilla's .pl files are not designed to be run via a web server.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; IIS will also need to know that the index.cgi should be treated
+ as a default document. On the Documents tab page of the virtual
+ directory properties, you need to add index.cgi as a default
+ document type. If you wish, you may remove the other default
+ document types for this particular virtual directory, since Bugzilla
+ doesn't use any of them.
+ </P
+><P
+>&#13; Also, and this can't be stressed enough, make sure that files
+ such as <TT
+CLASS="filename"
+>localconfig</TT
+> and your
+ <TT
+CLASS="filename"
+>data</TT
+> directory are
+ secured as described in <A
+HREF="#security-webserver-access"
+>Section 4.2.1</A
+>.
+ </P
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="install-config-bugzilla"
+>2.2.5. Bugzilla</A
+></H3
+><P
+>&#13; Your Bugzilla should now be working. Access
+ <TT
+CLASS="filename"
+>http://&#60;your-bugzilla-server&#62;/</TT
+> -
+ you should see the Bugzilla
+ front page. If not, consult the Troubleshooting section,
+ <A
+HREF="#troubleshooting"
+>Appendix A</A
+>.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; The URL above may be incorrect if you installed Bugzilla into a
+ subdirectory or used a symbolic link from your web site root to
+ the Bugzilla directory.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; Log in with the administrator account you defined in the last
+ <TT
+CLASS="filename"
+>checksetup.pl</TT
+> run. You should go through
+ the Parameters page and see if there are any you wish to change.
+ They key parameters are documented in <A
+HREF="#parameters"
+>Section 3.1</A
+>;
+ you should certainly alter
+ <B
+CLASS="command"
+>maintainer</B
+> and <B
+CLASS="command"
+>urlbase</B
+>;
+ you may also want to alter
+ <B
+CLASS="command"
+>cookiepath</B
+> or <B
+CLASS="command"
+>requirelogin</B
+>.
+ </P
+><P
+>&#13; Bugzilla has several optional features which require extra
+ configuration. You can read about those in
+ <A
+HREF="#extraconfig"
+>Section 2.3</A
+>.
+ </P
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="extraconfig"
+>2.3. Optional Additional Configuration</A
+></H2
+><P
+>&#13; Bugzilla has a number of optional features. This section describes how
+ to configure or enable them.
+ </P
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="AEN688"
+>2.3.1. Bug Graphs</A
+></H3
+><P
+>If you have installed the necessary Perl modules you
+ can start collecting statistics for the nifty Bugzilla
+ graphs.</P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="screen"
+><SAMP
+CLASS="prompt"
+>bash#</SAMP
+> <B
+CLASS="command"
+>crontab -e</B
+></PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>&#13; This should bring up the crontab file in your editor.
+ Add a cron entry like this to run
+ <TT
+CLASS="filename"
+>collectstats.pl</TT
+>
+ daily at 5 after midnight:
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>5 0 * * * cd &#60;your-bugzilla-directory&#62; ; ./collectstats.pl</PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>&#13; After two days have passed you'll be able to view bug graphs from
+ the Reports page.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Windows does not have 'cron', but it does have the Task
+ Scheduler, which performs the same duties. There are also
+ third-party tools that can be used to implement cron, such as
+ <A
+HREF="http://www.nncron.ru/"
+TARGET="_top"
+>nncron</A
+>.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="installation-whining-cron"
+>2.3.2. The Whining Cron</A
+></H3
+><P
+>What good are
+ bugs if they're not annoying? To help make them more so you
+ can set up Bugzilla's automatic whining system to complain at engineers
+ which leave their bugs in the CONFIRMED state without triaging them.
+ </P
+><P
+>&#13; This can be done by adding the following command as a daily
+ crontab entry, in the same manner as explained above for bug
+ graphs. This example runs it at 12.55am.
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>55 0 * * * cd &#60;your-bugzilla-directory&#62; ; ./whineatnews.pl</PRE
+></FONT
+></TD
+></TR
+></TABLE
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Windows does not have 'cron', but it does have the Task
+ Scheduler, which performs the same duties. There are also
+ third-party tools that can be used to implement cron, such as
+ <A
+HREF="http://www.nncron.ru/"
+TARGET="_top"
+>nncron</A
+>.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="installation-whining"
+>2.3.3. Whining</A
+></H3
+><P
+>&#13; As of Bugzilla 2.20, users can configure Bugzilla to regularly annoy
+ them at regular intervals, by having Bugzilla execute saved searches
+ at certain times and emailing the results to the user. This is known
+ as "Whining". The process of configuring Whining is described
+ in <A
+HREF="#whining"
+>Section 5.13</A
+>, but for it to work a Perl script must be
+ executed at regular intervals.
+ </P
+><P
+>&#13; This can be done by adding the following command as a daily
+ crontab entry, in the same manner as explained above for bug
+ graphs. This example runs it every 15 minutes.
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>*/15 * * * * cd &#60;your-bugzilla-directory&#62; ; ./whine.pl</PRE
+></FONT
+></TD
+></TR
+></TABLE
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Whines can be executed as often as every 15 minutes, so if you specify
+ longer intervals between executions of whine.pl, some users may not
+ be whined at as often as they would expect. Depending on the person,
+ this can either be a very Good Thing or a very Bad Thing.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Windows does not have 'cron', but it does have the Task
+ Scheduler, which performs the same duties. There are also
+ third-party tools that can be used to implement cron, such as
+ <A
+HREF="http://www.nncron.ru/"
+TARGET="_top"
+>nncron</A
+>.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="apache-addtype"
+>2.3.4. Serving Alternate Formats with the right MIME type</A
+></H3
+><P
+>&#13; Some Bugzilla pages have alternate formats, other than just plain
+ <ACRONYM
+CLASS="acronym"
+>HTML</ACRONYM
+>. In particular, a few Bugzilla pages can
+ output their contents as either <ACRONYM
+CLASS="acronym"
+>XUL</ACRONYM
+> (a special
+ Mozilla format, that looks like a program <ACRONYM
+CLASS="acronym"
+>GUI</ACRONYM
+>)
+ or <ACRONYM
+CLASS="acronym"
+>RDF</ACRONYM
+> (a type of structured <ACRONYM
+CLASS="acronym"
+>XML</ACRONYM
+>
+ that can be read by various programs).
+ </P
+><P
+>&#13; In order for your users to see these pages correctly, Apache must
+ send them with the right <ACRONYM
+CLASS="acronym"
+>MIME</ACRONYM
+> type. To do this,
+ add the following lines to your Apache configuration, either in the
+ <SAMP
+CLASS="computeroutput"
+>&#60;VirtualHost&#62;</SAMP
+> section for your
+ Bugzilla, or in the <SAMP
+CLASS="computeroutput"
+>&#60;Directory&#62;</SAMP
+>
+ section for your Bugzilla:
+ </P
+><P
+>&#13; <TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="screen"
+>AddType application/vnd.mozilla.xul+xml .xul
+AddType application/rdf+xml .rdf</PRE
+></FONT
+></TD
+></TR
+></TABLE
+>
+ </P
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="multiple-bz-dbs"
+>2.4. Multiple Bugzilla databases with a single installation</A
+></H2
+><P
+>The previous instructions referred to a standard installation, with
+ one unique Bugzilla database. However, you may want to host several
+ distinct installations, without having several copies of the code. This is
+ possible by using the PROJECT environment variable. When accessed,
+ Bugzilla checks for the existence of this variable, and if present, uses
+ its value to check for an alternative configuration file named
+ <TT
+CLASS="filename"
+>localconfig.&#60;PROJECT&#62;</TT
+> in the same location as
+ the default one (<TT
+CLASS="filename"
+>localconfig</TT
+>). It also checks for
+ customized templates in a directory named
+ <TT
+CLASS="filename"
+>&#60;PROJECT&#62;</TT
+> in the same location as the
+ default one (<TT
+CLASS="filename"
+>template/&#60;langcode&#62;</TT
+>). By default
+ this is <TT
+CLASS="filename"
+>template/en/default</TT
+> so PROJECT's templates
+ would be located at <TT
+CLASS="filename"
+>template/en/PROJECT</TT
+>.</P
+><P
+>To set up an alternate installation, just export PROJECT=foo before
+ running <B
+CLASS="command"
+>checksetup.pl</B
+> for the first time. It will
+ result in a file called <TT
+CLASS="filename"
+>localconfig.foo</TT
+> instead of
+ <TT
+CLASS="filename"
+>localconfig</TT
+>. Edit this file as described above, with
+ reference to a new database, and re-run <B
+CLASS="command"
+>checksetup.pl</B
+>
+ to populate it. That's all.</P
+><P
+>Now you have to configure the web server to pass this environment
+ variable when accessed via an alternate URL, such as virtual host for
+ instance. The following is an example of how you could do it in Apache,
+ other Webservers may differ.
+<TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#13;&#60;VirtualHost 212.85.153.228:80&#62;
+ ServerName foo.bar.baz
+ SetEnv PROJECT foo
+ Alias /bugzilla /var/www/bugzilla
+&#60;/VirtualHost&#62;
+</PRE
+></FONT
+></TD
+></TR
+></TABLE
+>
+ </P
+><P
+>Don't forget to also export this variable before accessing Bugzilla
+ by other means, such as cron tasks for instance.</P
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="os-specific"
+>2.5. OS-Specific Installation Notes</A
+></H2
+><P
+>Many aspects of the Bugzilla installation can be affected by the
+ operating system you choose to install it on. Sometimes it can be made
+ easier and others more difficult. This section will attempt to help you
+ understand both the difficulties of running on specific operating systems
+ and the utilities available to make it easier.
+ </P
+><P
+>If you have anything to add or notes for an operating system not
+ covered, please file a bug in <A
+HREF="https://bugzilla.mozilla.org/enter_bug.cgi?product=Bugzilla&#38;component=Documentation"
+TARGET="_top"
+>Bugzilla Documentation</A
+>.
+ </P
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="os-win32"
+>2.5.1. Microsoft Windows</A
+></H3
+><P
+>&#13; Making Bugzilla work on Windows is more difficult than making it
+ work on Unix. For that reason, we still recommend doing so on a Unix
+ based system such as GNU/Linux. That said, if you do want to get
+ Bugzilla running on Windows, you will need to make the following
+ adjustments. A detailed step-by-step
+ <A
+HREF="https://wiki.mozilla.org/Bugzilla:Win32Install"
+TARGET="_top"
+>&#13; installation guide for Windows</A
+> is also available
+ if you need more help with your installation.
+ </P
+><DIV
+CLASS="section"
+><HR><H4
+CLASS="section"
+><A
+NAME="win32-perl"
+>2.5.1.1. Win32 Perl</A
+></H4
+><P
+>&#13; Perl for Windows can be obtained from
+ <A
+HREF="http://www.activestate.com/"
+TARGET="_top"
+>ActiveState</A
+>.
+ You should be able to find a compiled binary at <A
+HREF="http://aspn.activestate.com/ASPN/Downloads/ActivePerl/"
+TARGET="_top"
+>http://aspn.activestate.com/ASPN/Downloads/ActivePerl/</A
+>.
+ The following instructions assume that you are using version
+ 5.8.1 of ActiveState.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; These instructions are for 32-bit versions of Windows. If you are
+ using a 64-bit version of Windows, you will need to install 32-bit
+ Perl in order to install the 32-bit modules as described below.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H4
+CLASS="section"
+><A
+NAME="win32-perl-modules"
+>2.5.1.2. Perl Modules on Win32</A
+></H4
+><P
+>&#13; Bugzilla on Windows requires the same perl modules found in
+ <A
+HREF="#install-perlmodules"
+>Section 2.1.5</A
+>. The main difference is that
+ windows uses <A
+HREF="#gloss-ppm"
+><I
+CLASS="glossterm"
+>PPM</I
+></A
+> instead
+ of CPAN. ActiveState provides a GUI to manage Perl modules. We highly
+ recommend that you use it. If you prefer to use ppm from the
+ command-line, type:
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#13;C:\perl&#62; <B
+CLASS="command"
+>ppm install &#60;module name&#62;</B
+>
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>&#13; The best source for the Windows PPM modules needed for Bugzilla
+ is probably the theory58S website, which you can add to your list
+ of repositories as follows (for Perl 5.8.x):
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#13;<B
+CLASS="command"
+>ppm repo add theory58S http://theoryx5.uwinnipeg.ca/ppms/</B
+>
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>&#13; If you are using Perl 5.10.x, you cannot use the same PPM modules as Perl
+ 5.8.x as they are incompatible. In this case, you should add the following
+ repository:
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#13;<B
+CLASS="command"
+>ppm repo add theory58S http://cpan.uwinnipeg.ca/PPMPackages/10xx/</B
+>
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; In versions prior to 5.8.8 build 819 of PPM the command is
+ <TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#13;<B
+CLASS="command"
+>ppm repository add theory58S http://theoryx5.uwinnipeg.ca/ppms/</B
+>
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+>
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; The PPM repository stores modules in 'packages' that may have
+ a slightly different name than the module. If retrieving these
+ modules from there, you will need to pay attention to the information
+ provided when you run <B
+CLASS="command"
+>checksetup.pl</B
+> as it will
+ tell you what package you'll need to install.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="tip"
+><P
+></P
+><TABLE
+CLASS="tip"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/tip.gif"
+HSPACE="5"
+ALT="Tip"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; If you are behind a corporate firewall, you will need to let the
+ ActiveState PPM utility know how to get through it to access
+ the repositories by setting the HTTP_proxy system environmental
+ variable. For more information on setting that variable, see
+ the ActiveState documentation.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H4
+CLASS="section"
+><A
+NAME="win32-http"
+>2.5.1.3. Serving the web pages</A
+></H4
+><P
+>&#13; As is the case on Unix based systems, any web server should
+ be able to handle Bugzilla; however, the Bugzilla Team still
+ recommends Apache whenever asked. No matter what web server
+ you choose, be sure to pay attention to the security notes
+ in <A
+HREF="#security-webserver-access"
+>Section 4.2.1</A
+>. More
+ information on configuring specific web servers can be found
+ in <A
+HREF="#http"
+>Section 2.2.4</A
+>.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; The web server looks at <TT
+CLASS="filename"
+>/usr/bin/perl</TT
+> to
+ call Perl. If you are using Apache on windows, you can set the
+ <A
+HREF="http://httpd.apache.org/docs-2.0/mod/core.html#scriptinterpretersource"
+TARGET="_top"
+>ScriptInterpreterSource</A
+>
+ directive in your Apache config file to make it look at the
+ right place: insert the line
+ <TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>ScriptInterpreterSource Registry-Strict</PRE
+></FONT
+></TD
+></TR
+></TABLE
+>
+ into your <TT
+CLASS="filename"
+>httpd.conf</TT
+> file, and create the key
+ <TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>HKEY_CLASSES_ROOT\.cgi\Shell\ExecCGI\Command</PRE
+></FONT
+></TD
+></TR
+></TABLE
+>
+ with <CODE
+CLASS="option"
+>C:\Perl\bin\perl.exe -T</CODE
+> as value (adapt to your
+ path if needed) in the registry. When this is done, restart Apache.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H4
+CLASS="section"
+><A
+NAME="win32-email"
+>2.5.1.4. Sending Email</A
+></H4
+><P
+>&#13; To enable Bugzilla to send email on Windows, the server running the
+ Bugzilla code must be able to connect to, or act as, an SMTP server.
+ </P
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="os-macosx"
+>2.5.2. <SPAN
+CLASS="productname"
+>Mac OS X</SPAN
+></A
+></H3
+><P
+>Making Bugzilla work on Mac OS X requires the following
+ adjustments.</P
+><DIV
+CLASS="section"
+><HR><H4
+CLASS="section"
+><A
+NAME="macosx-sendmail"
+>2.5.2.1. Sendmail</A
+></H4
+><P
+>In Mac OS X 10.3 and later,
+ <A
+HREF="http://www.postfix.org/"
+TARGET="_top"
+>Postfix</A
+>
+ is used as the built-in email server. Postfix provides an executable
+ that mimics sendmail enough to fool Bugzilla, as long as Bugzilla can
+ find it. Bugzilla is able to find the fake sendmail executable without
+ any assistance.</P
+></DIV
+><DIV
+CLASS="section"
+><HR><H4
+CLASS="section"
+><A
+NAME="macosx-libraries"
+>2.5.2.2. Libraries &#38; Perl Modules on Mac OS X</A
+></H4
+><P
+>Apple does not include the GD library with Mac OS X. Bugzilla
+ needs this for bug graphs.</P
+><P
+>You can use DarwinPorts (<A
+HREF="http://darwinports.com/"
+TARGET="_top"
+>http://darwinports.com/</A
+>)
+ or Fink (<A
+HREF="http://sourceforge.net/projects/fink/"
+TARGET="_top"
+>http://sourceforge.net/projects/fink/</A
+>), both
+ of which are similar in nature to the CPAN installer, but install
+ common unix programs.</P
+><P
+>Follow the instructions for setting up DarwinPorts or Fink.
+ Once you have one installed, you'll want to use it to install the
+ <TT
+CLASS="filename"
+>gd2</TT
+> package.
+ </P
+><P
+>Fink will prompt you for a number of dependencies, type 'y' and hit
+ enter to install all of the dependencies and then watch it work. You will
+ then be able to use <A
+HREF="#gloss-cpan"
+><I
+CLASS="glossterm"
+>CPAN</I
+></A
+> to
+ install the GD Perl module.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>To prevent creating conflicts with the software that Apple
+ installs by default, Fink creates its own directory tree at
+ <TT
+CLASS="filename"
+>/sw</TT
+> where it installs most of
+ the software that it installs. This means your libraries and headers
+ will be at <TT
+CLASS="filename"
+>/sw/lib</TT
+> and
+ <TT
+CLASS="filename"
+>/sw/include</TT
+> instead of
+ <TT
+CLASS="filename"
+>/usr/lib</TT
+> and
+ <TT
+CLASS="filename"
+>/usr/include</TT
+>. When the
+ Perl module config script asks where your <TT
+CLASS="filename"
+>libgd</TT
+>
+ is, be sure to tell it
+ <TT
+CLASS="filename"
+>/sw/lib</TT
+>.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>Also available via DarwinPorts and Fink is
+ <TT
+CLASS="filename"
+>expat</TT
+>. After installing the expat package, you
+ will be able to install XML::Parser using CPAN. If you use fink, there
+ is one caveat. Unlike recent versions of
+ the GD module, XML::Parser doesn't prompt for the location of the
+ required libraries. When using CPAN, you will need to use the following
+ command sequence:
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="screen"
+>&#13;# perl -MCPAN -e'look XML::Parser' <A
+NAME="macosx-look"
+><IMG
+SRC="../images/callouts/1.gif"
+HSPACE="0"
+VSPACE="0"
+BORDER="0"
+ALT="(1)"></A
+>
+# perl Makefile.PL EXPATLIBPATH=/sw/lib EXPATINCPATH=/sw/include
+# make; make test; make install <A
+NAME="macosx-make"
+><IMG
+SRC="../images/callouts/2.gif"
+HSPACE="0"
+VSPACE="0"
+BORDER="0"
+ALT="(2)"></A
+>
+# exit <A
+NAME="macosx-exit"
+><IMG
+SRC="../images/callouts/3.gif"
+HSPACE="0"
+VSPACE="0"
+BORDER="0"
+ALT="(3)"></A
+>
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><DIV
+CLASS="calloutlist"
+><DL
+COMPACT="COMPACT"
+><DT
+><A
+HREF="#macosx-look"
+><IMG
+SRC="../images/callouts/1.gif"
+HSPACE="0"
+VSPACE="0"
+BORDER="0"
+ALT="(1)"></A
+><A
+HREF="#macosx-exit"
+><IMG
+SRC="../images/callouts/3.gif"
+HSPACE="0"
+VSPACE="0"
+BORDER="0"
+ALT="(3)"></A
+></DT
+><DD
+>The look command will download the module and spawn a
+ new shell with the extracted files as the current working directory.
+ The exit command will return you to your original shell.
+ </DD
+><DT
+><A
+HREF="#macosx-make"
+><IMG
+SRC="../images/callouts/2.gif"
+HSPACE="0"
+VSPACE="0"
+BORDER="0"
+ALT="(2)"></A
+></DT
+><DD
+>You should watch the output from these make commands,
+ especially <SPAN
+CLASS="QUOTE"
+>"make test"</SPAN
+> as errors may prevent
+ XML::Parser from functioning correctly with Bugzilla.
+ </DD
+></DL
+></DIV
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="os-linux"
+>2.5.3. Linux Distributions</A
+></H3
+><P
+>Many Linux distributions include Bugzilla and its
+ dependencies in their native package management systems.
+ Installing Bugzilla with root access on any Linux system
+ should be as simple as finding the Bugzilla package in the
+ package management application and installing it using the
+ normal command syntax. Several distributions also perform
+ the proper web server configuration automatically on installation.
+ </P
+><P
+>Please consult the documentation of your Linux
+ distribution for instructions on how to install packages,
+ or for specific instructions on installing Bugzilla with
+ native package management tools. There is also a
+ <A
+HREF="http://wiki.mozilla.org/Bugzilla:Linux_Distro_Installation"
+TARGET="_top"
+>&#13; Bugzilla Wiki Page</A
+> for distro-specific installation
+ notes.
+ </P
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="nonroot"
+>2.6. UNIX (non-root) Installation Notes</A
+></H2
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="AEN851"
+>2.6.1. Introduction</A
+></H3
+><P
+>If you are running a *NIX OS as non-root, either due
+ to lack of access (web hosts, for example) or for security
+ reasons, this will detail how to install Bugzilla on such
+ a setup. It is recommended that you read through the
+ <A
+HREF="#installation"
+>Section 2.1</A
+>
+ first to get an idea on the installation steps required.
+ (These notes will reference to steps in that guide.)</P
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="AEN855"
+>2.6.2. MySQL</A
+></H3
+><P
+>You may have MySQL installed as root. If you're
+ setting up an account with a web host, a MySQL account
+ needs to be set up for you. From there, you can create
+ the bugs account, or use the account given to you.</P
+><DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>You may have problems trying to set up
+ <B
+CLASS="command"
+>GRANT</B
+> permissions to the database.
+ If you're using a web host, chances are that you have a
+ separate database which is already locked down (or one big
+ database with limited/no access to the other areas), but you
+ may want to ask your system administrator what the security
+ settings are set to, and/or run the <B
+CLASS="command"
+>GRANT</B
+>
+ command for you.</P
+><P
+>Also, you will probably not be able to change the MySQL
+ root user password (for obvious reasons), so skip that
+ step.</P
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="section"
+><HR><H4
+CLASS="section"
+><A
+NAME="AEN863"
+>2.6.2.1. Running MySQL as Non-Root</A
+></H4
+><DIV
+CLASS="section"
+><H5
+CLASS="section"
+><A
+NAME="AEN865"
+>2.6.2.1.1. The Custom Configuration Method</A
+></H5
+><P
+>Create a file .my.cnf in your
+ home directory (using /home/foo in this example)
+ as follows....</P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#13;[mysqld]
+datadir=/home/foo/mymysql
+socket=/home/foo/mymysql/thesock
+port=8081
+
+[mysql]
+socket=/home/foo/mymysql/thesock
+port=8081
+
+[mysql.server]
+user=mysql
+basedir=/var/lib
+
+[safe_mysqld]
+err-log=/home/foo/mymysql/the.log
+pid-file=/home/foo/mymysql/the.pid
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="section"
+><HR><H5
+CLASS="section"
+><A
+NAME="AEN869"
+>2.6.2.1.2. The Custom Built Method</A
+></H5
+><P
+>You can install MySQL as a not-root, if you really need to.
+ Build it with PREFIX set to <TT
+CLASS="filename"
+>/home/foo/mysql</TT
+>,
+ or use pre-installed executables, specifying that you want
+ to put all of the data files in <TT
+CLASS="filename"
+>/home/foo/mysql/data</TT
+>.
+ If there is another MySQL server running on the system that you
+ do not own, use the -P option to specify a TCP port that is not
+ in use.</P
+></DIV
+><DIV
+CLASS="section"
+><HR><H5
+CLASS="section"
+><A
+NAME="AEN874"
+>2.6.2.1.3. Starting the Server</A
+></H5
+><P
+>After your mysqld program is built and any .my.cnf file is
+ in place, you must initialize the databases (ONCE).</P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="screen"
+>&#13; <SAMP
+CLASS="prompt"
+>bash$</SAMP
+>
+ <B
+CLASS="command"
+>mysql_install_db</B
+>
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>Then start the daemon with</P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="screen"
+>&#13; <SAMP
+CLASS="prompt"
+>bash$</SAMP
+>
+ <B
+CLASS="command"
+>safe_mysql &#38;</B
+>
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>After you start mysqld the first time, you then connect to
+ it as "root" and <B
+CLASS="command"
+>GRANT</B
+> permissions to other
+ users. (Again, the MySQL root account has nothing to do with
+ the *NIX root account.)</P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>You will need to start the daemons yourself. You can either
+ ask your system administrator to add them to system startup files, or
+ add a crontab entry that runs a script to check on these daemons
+ and restart them if needed.</P
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>Do NOT run daemons or other services on a server without first
+ consulting your system administrator! Daemons use up system resources
+ and running one may be in violation of your terms of service for any
+ machine on which you are a user!</P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="AEN890"
+>2.6.3. Perl</A
+></H3
+><P
+>&#13; On the extremely rare chance that you don't have Perl on
+ the machine, you will have to build the sources
+ yourself. The following commands should get your system
+ installed with your own personal version of Perl:
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="screen"
+>&#13; <SAMP
+CLASS="prompt"
+>bash$</SAMP
+>
+ <B
+CLASS="command"
+>wget http://perl.org/CPAN/src/stable.tar.gz</B
+>
+ <SAMP
+CLASS="prompt"
+>bash$</SAMP
+>
+ <B
+CLASS="command"
+>tar zvxf stable.tar.gz</B
+>
+ <SAMP
+CLASS="prompt"
+>bash$</SAMP
+>
+ <B
+CLASS="command"
+>cd perl-5.8.1</B
+> (or whatever the version of Perl is called)
+ <SAMP
+CLASS="prompt"
+>bash$</SAMP
+>
+ <B
+CLASS="command"
+>sh Configure -de -Dprefix=/home/foo/perl</B
+>
+ <SAMP
+CLASS="prompt"
+>bash$</SAMP
+>
+ <B
+CLASS="command"
+>make &#38;&#38; make test &#38;&#38; make install</B
+>
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>&#13; Once you have Perl installed into a directory (probably
+ in <TT
+CLASS="filename"
+>~/perl/bin</TT
+>), you will need to
+ install the Perl Modules, described below.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="install-perlmodules-nonroot"
+>2.6.4. Perl Modules</A
+></H3
+><P
+>&#13; Installing the Perl modules as a non-root user is accomplished by
+ running the <TT
+CLASS="filename"
+>install-module.pl</TT
+>
+ script. For more details on this script, see
+ <A
+HREF="api/install-module.html"
+TARGET="_top"
+><TT
+CLASS="filename"
+>install-module.pl</TT
+>
+ documentation</A
+>
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="AEN912"
+>2.6.5. HTTP Server</A
+></H3
+><P
+>Ideally, this also needs to be installed as root and
+ run under a special web server account. As long as
+ the web server will allow the running of *.cgi files outside of a
+ cgi-bin, and a way of denying web access to certain files (such as a
+ .htaccess file), you should be good in this department.</P
+><DIV
+CLASS="section"
+><HR><H4
+CLASS="section"
+><A
+NAME="AEN915"
+>2.6.5.1. Running Apache as Non-Root</A
+></H4
+><P
+>You can run Apache as a non-root user, but the port will need
+ to be set to one above 1024. If you type <B
+CLASS="command"
+>httpd -V</B
+>,
+ you will get a list of the variables that your system copy of httpd
+ uses. One of those, namely HTTPD_ROOT, tells you where that
+ installation looks for its config information.</P
+><P
+>From there, you can copy the config files to your own home
+ directory to start editing. When you edit those and then use the -d
+ option to override the HTTPD_ROOT compiled into the web server, you
+ get control of your own customized web server.</P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>You will need to start the daemons yourself. You can either
+ ask your system administrator to add them to system startup files, or
+ add a crontab entry that runs a script to check on these daemons
+ and restart them if needed.</P
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>Do NOT run daemons or other services on a server without first
+ consulting your system administrator! Daemons use up system resources
+ and running one may be in violation of your terms of service for any
+ machine on which you are a user!</P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="AEN924"
+>2.6.6. Bugzilla</A
+></H3
+><P
+>&#13; When you run <B
+CLASS="command"
+>./checksetup.pl</B
+> to create
+ the <TT
+CLASS="filename"
+>localconfig</TT
+> file, it will list the Perl
+ modules it finds. If one is missing, go back and double-check the
+ module installation from <A
+HREF="#install-perlmodules-nonroot"
+>Section 2.6.4</A
+>,
+ then delete the <TT
+CLASS="filename"
+>localconfig</TT
+> file and try again.
+ </P
+><DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>One option in <TT
+CLASS="filename"
+>localconfig</TT
+> you
+ might have problems with is the web server group. If you can't
+ successfully browse to the <TT
+CLASS="filename"
+>index.cgi</TT
+> (like
+ a Forbidden error), you may have to relax your permissions,
+ and blank out the web server group. Of course, this may pose
+ as a security risk. Having a properly jailed shell and/or
+ limited access to shell accounts may lessen the security risk,
+ but use at your own risk.</P
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="section"
+><HR><H4
+CLASS="section"
+><A
+NAME="suexec"
+>2.6.6.1. suexec or shared hosting</A
+></H4
+><P
+>If you are running on a system that uses suexec (most shared
+ hosting environments do this), you will need to set the
+ <EM
+>webservergroup</EM
+> value in <TT
+CLASS="filename"
+>localconfig</TT
+>
+ to match <EM
+>your</EM
+> primary group, rather than the one
+ the web server runs under. You will need to run the following
+ shell commands after running <B
+CLASS="command"
+>./checksetup.pl</B
+>,
+ every time you run it (or modify <TT
+CLASS="filename"
+>checksetup.pl</TT
+>
+ to do them for you via the system() command).
+ <TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+> for i in docs graphs images js skins; do find $i -type d -exec chmod o+rx {} \; ; done
+ for i in jpg gif css js png html rdf xul; do find . -name \*.$i -exec chmod o+r {} \; ; done
+ find . -name .htaccess -exec chmod o+r {} \;
+ chmod o+x . data data/webdot</PRE
+></FONT
+></TD
+></TR
+></TABLE
+>
+ Pay particular attention to the number of semicolons and dots.
+ They are all important. A future version of Bugzilla will
+ hopefully be able to do this for you out of the box.</P
+></DIV
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="upgrade"
+>2.7. Upgrading to New Releases</A
+></H2
+><P
+>Upgrading to new Bugzilla releases is very simple. There is
+ a script included with Bugzilla that will automatically
+ do all of the database migration for you.</P
+><P
+>The following sections explain how to upgrade from one
+ version of Bugzilla to another. Whether you are upgrading
+ from one bug-fix version to another (such as 3.0.1 to 3.0.2)
+ or from one major version to another (such as from 3.0 to 3.2),
+ the instructions are always the same.</P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Any examples in the following sections are written as though the
+ user were updating to version 2.22.1, but the procedures are the
+ same no matter what version you're updating to. Also, in the
+ examples, the user's Bugzilla installation is found at
+ <TT
+CLASS="filename"
+>/var/www/html/bugzilla</TT
+>. If that is not the
+ same as the location of your Bugzilla installation, simply
+ substitute the proper paths where appropriate.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="upgrade-before"
+>2.7.1. Before You Upgrade</A
+></H3
+><P
+>Before you start your upgrade, there are a few important
+ steps to take:</P
+><P
+></P
+><OL
+TYPE="1"
+><LI
+><P
+>&#13; Read the <A
+HREF="http://www.bugzilla.org/releases/"
+TARGET="_top"
+>Release
+ Notes</A
+> of the version you're upgrading to,
+ particularly the "Notes for Upgraders" section.
+ </P
+></LI
+><LI
+><P
+>&#13; View the Sanity Check (<A
+HREF="#sanitycheck"
+>Section 3.16</A
+>) page
+ on your installation before upgrading. Attempt to fix all warnings
+ that the page produces before you go any further, or you may
+ experience problems during your upgrade.
+ </P
+></LI
+><LI
+><P
+>&#13; Shut down your Bugzilla installation by putting some HTML or
+ text in the shutdownhtml parameter
+ (see <A
+HREF="#parameters"
+>Section 3.1</A
+>).
+ </P
+></LI
+><LI
+><P
+>&#13; Make a backup of the Bugzilla database.
+ <EM
+>THIS IS VERY IMPORTANT</EM
+>. If
+ anything goes wrong during the upgrade, your installation
+ can be corrupted beyond recovery. Having a backup keeps you safe.
+ </P
+><DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Upgrading is a one-way process. You cannot "downgrade" an
+ upgraded Bugzilla. If you wish to revert to the old Bugzilla
+ version for any reason, you will have to restore your database
+ from this backup.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>Here are some sample commands you could use to backup
+ your database, depending on what database system you're
+ using. You may have to modify these commands for your
+ particular setup.</P
+><P
+></P
+><DIV
+CLASS="variablelist"
+><DL
+><DT
+>MySQL:</DT
+><DD
+><P
+>&#13; <B
+CLASS="command"
+>mysqldump --opt -u bugs -p bugs &#62; bugs.sql</B
+>
+ </P
+></DD
+><DT
+>PostgreSQL:</DT
+><DD
+><P
+>&#13; <B
+CLASS="command"
+>pg_dump --no-privileges --no-owner -h localhost -U bugs
+ &#62; bugs.sql</B
+>
+ </P
+></DD
+></DL
+></DIV
+></LI
+></OL
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="upgrade-files"
+>2.7.2. Getting The New Bugzilla</A
+></H3
+><P
+>There are three ways to get the new version of Bugzilla.
+ We'll list them here briefly and then explain them
+ more later.</P
+><P
+></P
+><DIV
+CLASS="variablelist"
+><DL
+><DT
+>CVS (<A
+HREF="#upgrade-cvs"
+>Section 2.7.2.2</A
+>)</DT
+><DD
+><P
+>&#13; If have <B
+CLASS="command"
+>cvs</B
+> installed on your machine
+ and you have Internet access, this is the easiest way to
+ upgrade, particularly if you have made modifications
+ to the code or templates of Bugzilla.
+ </P
+></DD
+><DT
+>Download the tarball (<A
+HREF="#upgrade-tarball"
+>Section 2.7.2.3</A
+>)</DT
+><DD
+><P
+>&#13; This is a very simple way to upgrade, and good if you
+ haven't made many (or any) modifications to the code or
+ templates of your Bugzilla.
+ </P
+></DD
+><DT
+>Patches (<A
+HREF="#upgrade-patches"
+>Section 2.7.2.4</A
+>)</DT
+><DD
+><P
+>&#13; If you have made modifications to your Bugzilla, and
+ you don't have Internet access or you don't want to use
+ cvs, then this is the best way to upgrade.
+ </P
+><P
+>&#13; You can only do minor upgrades (such as 3.0 to 3.0.1 or
+ 3.0.1 to 3.0.2) with patches.
+ </P
+></DD
+></DL
+></DIV
+><DIV
+CLASS="section"
+><HR><H4
+CLASS="section"
+><A
+NAME="upgrade-modified"
+>2.7.2.1. If you have modified your Bugzilla</A
+></H4
+><P
+>&#13; If you have modified the code or templates of your Bugzilla,
+ then upgrading requires a bit more thought and effort.
+ A discussion of the various methods of updating compared with
+ degree and methods of local customization can be found in
+ <A
+HREF="#template-method"
+>Section 6.3.2</A
+>.
+ </P
+><P
+>&#13; The larger the jump you are trying to make, the more difficult it
+ is going to be to upgrade if you have made local customizations.
+ Upgrading from 3.0 to 3.0.1 should be fairly painless even if
+ you are heavily customized, but going from 2.18 to 3.0 is going
+ to mean a fair bit of work re-writing your local changes to use
+ the new files, logic, templates, etc. If you have done no local
+ changes at all, however, then upgrading should be approximately
+ the same amount of work regardless of how long it has been since
+ your version was released.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H4
+CLASS="section"
+><A
+NAME="upgrade-cvs"
+>2.7.2.2. Upgrading using CVS</A
+></H4
+><P
+>&#13; This requires that you have cvs installed (most Unix machines do),
+ and requires that you are able to access cvs-mirror.mozilla.org
+ on port 2401, which may not be an option if you are behind a
+ highly restrictive firewall or don't have Internet access.
+ </P
+><P
+>&#13; The following shows the sequence of commands needed to update a
+ Bugzilla installation via CVS, and a typical series of results.
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#13;bash$ <B
+CLASS="command"
+>cd /var/www/html/bugzilla</B
+>
+bash$ <B
+CLASS="command"
+>cvs login</B
+>
+Logging in to :pserver:anonymous@cvs-mirror.mozilla.org:2401/cvsroot
+CVS password: <EM
+>('anonymous', or just leave it blank)</EM
+>
+bash$ <B
+CLASS="command"
+>cvs -q update -r BUGZILLA-2_22_1 -dP</B
+>
+P checksetup.pl
+P collectstats.pl
+P docs/rel_notes.txt
+P template/en/default/list/quips.html.tmpl
+<EM
+>(etc.)</EM
+>
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><DIV
+CLASS="caution"
+><P
+></P
+><TABLE
+CLASS="caution"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/caution.gif"
+HSPACE="5"
+ALT="Caution"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; If a line in the output from <B
+CLASS="command"
+>cvs update</B
+> begins
+ with a <SAMP
+CLASS="computeroutput"
+>C</SAMP
+>, then that represents a
+ file with local changes that CVS was unable to properly merge. You
+ need to resolve these conflicts manually before Bugzilla (or at
+ least the portion using that file) will be usable.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H4
+CLASS="section"
+><A
+NAME="upgrade-tarball"
+>2.7.2.3. Upgrading using the tarball</A
+></H4
+><P
+>&#13; If you are unable (or unwilling) to use CVS, another option that's
+ always available is to obtain the latest tarball from the <A
+HREF="http://www.bugzilla.org/download/"
+TARGET="_top"
+>Download Page</A
+> and
+ create a new Bugzilla installation from that.
+ </P
+><P
+>&#13; This sequence of commands shows how to get the tarball from the
+ command-line; it is also possible to download it from the site
+ directly in a web browser. If you go that route, save the file
+ to the <TT
+CLASS="filename"
+>/var/www/html</TT
+>
+ directory (or its equivalent, if you use something else) and
+ omit the first three lines of the example.
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#13;bash$ <B
+CLASS="command"
+>cd /var/www/html</B
+>
+bash$ <B
+CLASS="command"
+>wget http://ftp.mozilla.org/pub/mozilla.org/webtools/bugzilla-2.22.1.tar.gz</B
+>
+<EM
+>(Output omitted)</EM
+>
+bash$ <B
+CLASS="command"
+>tar xzvf bugzilla-2.22.1.tar.gz</B
+>
+bugzilla-2.22.1/
+bugzilla-2.22.1/.cvsignore
+<EM
+>(Output truncated)</EM
+>
+bash$ <B
+CLASS="command"
+>cd bugzilla-2.22.1</B
+>
+bash$ <B
+CLASS="command"
+>cp ../bugzilla/localconfig* .</B
+>
+bash$ <B
+CLASS="command"
+>cp -r ../bugzilla/data .</B
+>
+bash$ <B
+CLASS="command"
+>cd ..</B
+>
+bash$ <B
+CLASS="command"
+>mv bugzilla bugzilla.old</B
+>
+bash$ <B
+CLASS="command"
+>mv bugzilla-2.22.1 bugzilla</B
+>
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; The <B
+CLASS="command"
+>cp</B
+> commands both end with periods which
+ is a very important detail--it means that the destination
+ directory is the current working directory.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; This upgrade method will give you a clean install of Bugzilla.
+ That's fine if you don't have any local customizations that you
+ want to maintain. If you do have customizations, then you will
+ need to reapply them by hand to the appropriate files.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H4
+CLASS="section"
+><A
+NAME="upgrade-patches"
+>2.7.2.4. Upgrading using patches</A
+></H4
+><P
+>&#13; A patch is a collection of all the bug fixes that have been made
+ since the last bug-fix release.
+ </P
+><P
+>&#13; If you are doing a bug-fix upgrade&#8212;that is, one where only the
+ last number of the revision changes, such as from 2.22 to
+ 2.22.1&#8212;then you have the option of obtaining and applying a
+ patch file from the <A
+HREF="http://www.bugzilla.org/download/"
+TARGET="_top"
+>Download Page</A
+>.
+ </P
+><P
+>&#13; As above, this example starts with obtaining the file via the
+ command line. If you have already downloaded it, you can omit the
+ first two commands.
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#13;bash$ <B
+CLASS="command"
+>cd /var/www/html/bugzilla</B
+>
+bash$ <B
+CLASS="command"
+>wget http://ftp.mozilla.org/pub/mozilla.org/webtools/bugzilla-2.22-to-2.22.1.diff.gz</B
+>
+<EM
+>(Output omitted)</EM
+>
+bash$ <B
+CLASS="command"
+>gunzip bugzilla-2.22-to-2.22.1.diff.gz</B
+>
+bash$ <B
+CLASS="command"
+>patch -p1 &#60; bugzilla-2.22-to-2.22.1.diff</B
+>
+patching file checksetup.pl
+patching file collectstats.pl
+<EM
+>(etc.)</EM
+>
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Be aware that upgrading from a patch file does not change the
+ entries in your <TT
+CLASS="filename"
+>CVS</TT
+> directory.
+ This could make it more difficult to upgrade using CVS
+ (<A
+HREF="#upgrade-cvs"
+>Section 2.7.2.2</A
+>) in the future.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="upgrade-completion"
+>2.7.3. Completing Your Upgrade</A
+></H3
+><P
+>&#13; Now that you have the new Bugzilla code, there are a few final
+ steps to complete your upgrade.
+ </P
+><P
+></P
+><OL
+TYPE="1"
+><LI
+><P
+>&#13; If your new Bugzilla installation is in a different
+ directory or on a different machine than your old Bugzilla
+ installation, make sure that you have copied the
+ <TT
+CLASS="filename"
+>data</TT
+> directory and the
+ <TT
+CLASS="filename"
+>localconfig</TT
+> file from your old Bugzilla
+ installation. (If you followed the tarball instructions
+ above, this has already happened.)
+ </P
+></LI
+><LI
+><P
+>&#13; If this is a major update, check that the configuration
+ (<A
+HREF="#configuration"
+>Section 2.2</A
+>) for your new Bugzilla is
+ up-to-date. Sometimes the configuration requirements change
+ between major versions.
+ </P
+></LI
+><LI
+><P
+>&#13; If you didn't do it as part of the above configuration step,
+ now you need to run <B
+CLASS="command"
+>checksetup.pl</B
+>, which
+ will do everything required to convert your existing database
+ and settings for the new version:
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#13;bash$ <B
+CLASS="command"
+>cd /var/www/html/bugzilla</B
+>
+bash$ <B
+CLASS="command"
+>./checksetup.pl</B
+>
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; The period at the beginning of the command
+ <B
+CLASS="command"
+>./checksetup.pl</B
+> is important and can not
+ be omitted.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="caution"
+><P
+></P
+><TABLE
+CLASS="caution"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/caution.gif"
+HSPACE="5"
+ALT="Caution"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; If this is a major upgrade (say, 2.22 to 3.0 or similar),
+ running <B
+CLASS="command"
+>checksetup.pl</B
+> on a large
+ installation (75,000 or more bugs) can take a long time,
+ possibly several hours.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></LI
+><LI
+><P
+>&#13; Clear any HTML or text that you put into the shutdownhtml
+ parameter, to re-activate Bugzilla.
+ </P
+></LI
+><LI
+><P
+>&#13; View the Sanity Check (<A
+HREF="#sanitycheck"
+>Section 3.16</A
+>) page in your
+ upgraded Bugzilla.
+ </P
+><P
+>&#13; It is recommended that, if possible, you fix any problems
+ you see, immediately. Failure to do this may mean that Bugzilla
+ will not work correctly. Be aware that if the sanity check page
+ contains more errors after an upgrade, it doesn't necessarily
+ mean there are more errors in your database than there were
+ before, as additional tests are added to the sanity check over
+ time, and it is possible that those errors weren't being
+ checked for in the old version.
+ </P
+></LI
+></OL
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="upgrade-notifications"
+>2.7.4. Automatic Notifications of New Releases</A
+></H3
+><P
+>&#13; Bugzilla 3.0 introduced the ability to automatically notify
+ administrators when new releases are available, based on the
+ <TT
+CLASS="literal"
+>upgrade_notification</TT
+> parameter, see
+ <A
+HREF="#parameters"
+>Section 3.1</A
+>. Administrators will see these
+ notifications when they access the <TT
+CLASS="filename"
+>index.cgi</TT
+>
+ page, i.e. generally when logging in. Bugzilla will check once per
+ day for new releases, unless the parameter is set to
+ <SPAN
+CLASS="QUOTE"
+>"disabled"</SPAN
+>. If you are behind a proxy, you may have to set
+ the <TT
+CLASS="literal"
+>proxy_url</TT
+> parameter accordingly. If the proxy
+ requires authentication, use the
+ <TT
+CLASS="literal"
+>http://user:pass@proxy_url/</TT
+> syntax.
+ </P
+></DIV
+></DIV
+></DIV
+><DIV
+CLASS="chapter"
+><HR><H1
+><A
+NAME="administration"
+></A
+>Chapter 3. Administering Bugzilla</H1
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="parameters"
+>3.1. Bugzilla Configuration</A
+></H2
+><P
+>&#13; Bugzilla is configured by changing various parameters, accessed
+ from the "Parameters" link in the Administration page (the
+ Administration page can be found by clicking the "Administration"
+ link in the footer). The parameters are divided into several categories,
+ accessed via the menu on the left. Following is a description of the
+ different categories and important parameters within those categories.
+ </P
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="param-requiredsettings"
+>3.1.1. Required Settings</A
+></H3
+><P
+>&#13; The core required parameters for any Bugzilla installation are set
+ here. These parameters must be set before a new Bugzilla installation
+ can be used. Administrators should review this list before
+ deploying a new Bugzilla installation.
+ </P
+><P
+></P
+><DIV
+CLASS="variablelist"
+><DL
+><DT
+>maintainer</DT
+><DD
+><P
+>
+ Email address of the person
+ responsible for maintaining this Bugzilla installation.
+ The address need not be that of a valid Bugzilla account.
+ </P
+></DD
+><DT
+>urlbase</DT
+><DD
+><P
+>&#13; Defines the fully qualified domain name and web
+ server path to this Bugzilla installation.
+ </P
+><P
+>&#13; For example, if the Bugzilla query page is
+ <TT
+CLASS="filename"
+>http://www.foo.com/bugzilla/query.cgi</TT
+>,
+ the <SPAN
+CLASS="QUOTE"
+>"urlbase"</SPAN
+> should be set
+ to <TT
+CLASS="filename"
+>http://www.foo.com/bugzilla/</TT
+>.
+ </P
+></DD
+><DT
+>docs_urlbase</DT
+><DD
+><P
+>&#13; Defines path to the Bugzilla documentation. This can be a fully
+ qualified domain name, or a path relative to "urlbase".
+ </P
+><P
+>&#13; For example, if the "Bugzilla Configuration" page
+ of the documentation is
+ <TT
+CLASS="filename"
+>http://www.foo.com/bugzilla/docs/html/parameters.html</TT
+>,
+ set the <SPAN
+CLASS="QUOTE"
+>"docs_urlbase"</SPAN
+>
+ to <TT
+CLASS="filename"
+>http://www.foo.com/bugzilla/docs/html/</TT
+>.
+ </P
+></DD
+><DT
+>sslbase</DT
+><DD
+><P
+>&#13; Defines the fully qualified domain name and web
+ server path for HTTPS (SSL) connections to this Bugzilla installation.
+ </P
+><P
+>&#13; For example, if the Bugzilla main page is
+ <TT
+CLASS="filename"
+>https://www.foo.com/bugzilla/index.cgi</TT
+>,
+ the <SPAN
+CLASS="QUOTE"
+>"sslbase"</SPAN
+> should be set
+ to <TT
+CLASS="filename"
+>https://www.foo.com/bugzilla/</TT
+>.
+ </P
+></DD
+><DT
+>ssl_redirect</DT
+><DD
+><P
+>&#13; If enabled, Bugzilla will force HTTPS (SSL) connections, by
+ automatically redirecting any users who try to use a non-SSL
+ connection.
+ </P
+></DD
+><DT
+>cookiedomain</DT
+><DD
+><P
+>&#13; Defines the domain for Bugzilla cookies. This is typically left blank.
+ If there are multiple hostnames that point to the same webserver, which
+ require the same cookie, then this parameter can be utilized. For
+ example, If your website is at
+ <TT
+CLASS="filename"
+>https://www.foo.com/</TT
+>, setting this to
+ <TT
+CLASS="filename"
+>.foo.com/</TT
+> will also allow
+ <TT
+CLASS="filename"
+>bar.foo.com/</TT
+> to access Bugzilla cookies.
+ </P
+></DD
+><DT
+>cookiepath</DT
+><DD
+><P
+>&#13; Defines a path, relative to the web server root, that Bugzilla
+ cookies will be restricted to. For example, if the
+ <B
+CLASS="command"
+>urlbase</B
+> is set to
+ <TT
+CLASS="filename"
+>http://www.foo.com/bugzilla/</TT
+>, the
+ <B
+CLASS="command"
+>cookiepath</B
+> should be set to
+ <TT
+CLASS="filename"
+>/bugzilla/</TT
+>. Setting it to "/" will allow all sites
+ served by this web server or virtual host to read Bugzilla cookies.
+ </P
+></DD
+><DT
+>utf8</DT
+><DD
+><P
+>&#13; Determines whether to use UTF-8 (Unicode) encoding for all text in
+ Bugzilla. New installations should set this to true to avoid character
+ encoding problems. Existing databases should set this to true only
+ after the data has been converted from existing legacy character
+ encoding to UTF-8, using the
+ <TT
+CLASS="filename"
+>contrib/recode.pl</TT
+> script.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; If you turn this parameter from "off" to "on", you must re-run
+ <TT
+CLASS="filename"
+>checksetup.pl</TT
+> immediately afterward.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DD
+><DT
+>shutdownhtml</DT
+><DD
+><P
+>&#13; If there is any text in this field, this Bugzilla installation will
+ be completely disabled and this text will appear instead of all
+ Bugzilla pages for all users, including Admins. Used in the event
+ of site maintenance or outage situations.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Although regular log-in capability is disabled while
+ <B
+CLASS="command"
+>shutdownhtml</B
+>
+ is enabled, safeguards are in place to protect the unfortunate
+ admin who loses connection to Bugzilla. Should this happen to you,
+ go directly to the <TT
+CLASS="filename"
+>editparams.cgi</TT
+> (by typing
+ the URL in manually, if necessary). Doing this will prompt you to
+ log in, and your name/password will be accepted here (but nowhere
+ else).
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DD
+><DT
+>announcehtml</DT
+><DD
+><P
+>&#13; Any text in this field will be displayed at the top of every HTML
+ page in this Bugzilla installation. The text is not wrapped in any
+ tags. For best results, wrap the text in a <SPAN
+CLASS="QUOTE"
+>"&#60;div&#62;"</SPAN
+>
+ tag. Any style attributes from the CSS can be applied. For example,
+ to make the text green inside of a red box, add <SPAN
+CLASS="QUOTE"
+>"id=message"</SPAN
+>
+ to the <SPAN
+CLASS="QUOTE"
+>"&#60;div&#62;"</SPAN
+> tag.
+ </P
+></DD
+><DT
+>proxy_url</DT
+><DD
+><P
+>&#13; If this Bugzilla installation is behind a proxy, enter the proxy
+ information here to enable Bugzilla to access the Internet. Bugzilla
+ requires Internet access to utilize the
+ <B
+CLASS="command"
+>upgrade_notification</B
+> parameter (below). If the
+ proxy requires authentication, use the syntax:
+ <TT
+CLASS="filename"
+>http://user:pass@proxy_url/</TT
+>.
+ </P
+></DD
+><DT
+>upgrade_notification</DT
+><DD
+><P
+>&#13; Enable or disable a notification on the homepage of this Bugzilla
+ installation when a newer version of Bugzilla is available. This
+ notification is only visible to administrators. Choose "disabled",
+ to turn off the notification. Otherwise, choose which version of
+ Bugzilla you want to be notified about: "development_snapshot" is the
+ latest release on the trunk; "latest_stable_release" is the most
+ recent release available on the most recent stable branch;
+ "stable_branch_release" the most recent release on the branch
+ this installation is based on.
+ </P
+></DD
+></DL
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="param-admin-policies"
+>3.1.2. Administrative Policies</A
+></H3
+><P
+>&#13; This page contains parameters for basic administrative functions.
+ Options include whether to allow the deletion of bugs and users,
+ and whether to allow users to change their email address.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="param-user-authentication"
+>3.1.3. User Authentication</A
+></H3
+><P
+>&#13; This page contains the settings that control how this Bugzilla
+ installation will do its authentication. Choose what authentication
+ mechanism to use (the Bugzilla database, or an external source such
+ as LDAP), and set basic behavioral parameters. For example, choose
+ whether to require users to login to browse bugs, the management
+ of authentication cookies, and the regular expression used to
+ validate email addresses. Some parameters are highlighted below.
+ </P
+><P
+></P
+><DIV
+CLASS="variablelist"
+><DL
+><DT
+>emailregexp</DT
+><DD
+><P
+>&#13; Defines the regular expression used to validate email addresses
+ used for login names. The default attempts to match fully
+ qualified email addresses (i.e. 'user@example.com'). Some
+ Bugzilla installations allow only local user names (i.e 'user'
+ instead of 'user@example.com'). In that case, the
+ <B
+CLASS="command"
+>emailsuffix</B
+> parameter should be used to define
+ the email domain.
+ </P
+></DD
+><DT
+>emailsuffix</DT
+><DD
+><P
+>&#13; This string is appended to login names when actually sending
+ email to a user. For example,
+ If <B
+CLASS="command"
+>emailregexp</B
+> has been set to allow
+ local usernames,
+ then this parameter would contain the email domain for all users
+ (i.e. '@example.com').
+ </P
+></DD
+></DL
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="param-attachments"
+>3.1.4. Attachments</A
+></H3
+><P
+>&#13; This page allows for setting restrictions and other parameters
+ regarding attachments to bugs. For example, control size limitations
+ and whether to allow pointing to external files via a URI.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="param-bug-change-policies"
+>3.1.5. Bug Change Policies</A
+></H3
+><P
+>&#13; Set policy on default behavior for bug change events. For example,
+ choose which status to set a bug to when it is marked as a duplicate,
+ and choose whether to allow bug reporters to set the priority or
+ target milestone. Also allows for configuration of what changes
+ should require the user to make a comment, described below.
+ </P
+><P
+></P
+><DIV
+CLASS="variablelist"
+><DL
+><DT
+>commenton*</DT
+><DD
+><P
+>&#13; All these fields allow you to dictate what changes can pass
+ without comment, and which must have a comment from the
+ person who changed them. Often, administrators will allow
+ users to add themselves to the CC list, accept bugs, or
+ change the Status Whiteboard without adding a comment as to
+ their reasons for the change, yet require that most other
+ changes come with an explanation.
+ </P
+><P
+>&#13; Set the "commenton" options according to your site policy. It
+ is a wise idea to require comments when users resolve, reassign, or
+ reopen bugs at the very least.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; It is generally far better to require a developer comment
+ when resolving bugs than not. Few things are more annoying to bug
+ database users than having a developer mark a bug "fixed" without
+ any comment as to what the fix was (or even that it was truly
+ fixed!)
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DD
+><DT
+>noresolveonopenblockers</DT
+><DD
+><P
+>&#13; This option will prevent users from resolving bugs as FIXED if
+ they have unresolved dependencies. Only the FIXED resolution
+ is affected. Users will be still able to resolve bugs to
+ resolutions other than FIXED if they have unresolved dependent
+ bugs.
+ </P
+></DD
+></DL
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="param-bugfields"
+>3.1.6. Bug Fields</A
+></H3
+><P
+>&#13; The parameters in this section determine the default settings of
+ several Bugzilla fields for new bugs, and also control whether
+ certain fields are used. For example, choose whether to use the
+ "target milestone" field or the "status whiteboard" field.
+ </P
+><P
+></P
+><DIV
+CLASS="variablelist"
+><DL
+><DT
+>useqacontact</DT
+><DD
+><P
+>&#13; This allows you to define an email address for each component,
+ in addition to that of the default assignee, who will be sent
+ carbon copies of incoming bugs.
+ </P
+></DD
+><DT
+>usestatuswhiteboard</DT
+><DD
+><P
+>&#13; This defines whether you wish to have a free-form, overwritable field
+ associated with each bug. The advantage of the Status Whiteboard is
+ that it can be deleted or modified with ease, and provides an
+ easily-searchable field for indexing some bugs that have some trait
+ in common.
+ </P
+></DD
+></DL
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="param-bugmoving"
+>3.1.7. Bug Moving</A
+></H3
+><P
+>&#13; This page controls whether this Bugzilla installation allows certain
+ users to move bugs to an external database. If bug moving is enabled,
+ there are a number of parameters that control bug moving behaviors.
+ For example, choose which users are allowed to move bugs, the location
+ of the external database, and the default product and component that
+ bugs moved <EM
+>from</EM
+> other bug databases to this
+ Bugzilla installation are assigned to.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="param-dependency-graphs"
+>3.1.8. Dependency Graphs</A
+></H3
+><P
+>&#13; This page has one parameter that sets the location of a Web Dot
+ server, or of the Web Dot binary on the local system, that is used
+ to generate dependency graphs. Web Dot is a CGI program that creates
+ images from <TT
+CLASS="filename"
+>.dot</TT
+> graphic description files. If
+ no Web Dot server or binary is specified, then dependency graphs will
+ be disabled.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="param-group-security"
+>3.1.9. Group Security</A
+></H3
+><P
+>&#13; Bugzilla allows for the creation of different groups, with the
+ ability to restrict the visibility of bugs in a group to a set of
+ specific users. Specific products can also be associated with
+ groups, and users restricted to only see products in their groups.
+ Several parameters are described in more detail below. Most of the
+ configuration of groups and their relationship to products is done
+ on the "Groups" and "Product" pages of the "Administration" area.
+ The options on this page control global default behavior.
+ For more information on Groups and Group Security, see
+ <A
+HREF="#groups"
+>Section 3.15</A
+>
+ </P
+><P
+></P
+><DIV
+CLASS="variablelist"
+><DL
+><DT
+>makeproductgroups</DT
+><DD
+><P
+>&#13; Determines whether or not to automatically create groups
+ when new products are created. If this is on, the groups will be
+ used for querying bugs.
+ </P
+></DD
+><DT
+>usevisibilitygroups</DT
+><DD
+><P
+>&#13; If selected, user visibility will be restricted to members of
+ groups, as selected in the group configuration settings.
+ Each user-defined group can be allowed to see members of selected
+ other groups.
+ For details on configuring groups (including the visibility
+ restrictions) see <A
+HREF="#edit-groups"
+>Section 3.15.2</A
+>.
+ </P
+></DD
+><DT
+>querysharegroup</DT
+><DD
+><P
+>&#13; The name of the group of users who are allowed to share saved
+ searches with one another. For more information on using
+ saved searches, see <A
+HREF="#savedsearches"
+>Saved Searches</A
+>.
+ </P
+></DD
+></DL
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="bzldap"
+>3.1.10. LDAP Authentication</A
+></H3
+><P
+>LDAP authentication is a module for Bugzilla's plugin
+ authentication architecture. This page contains all the parameters
+ necessary to configure Bugzilla for use with LDAP authentication.
+ </P
+><P
+>&#13; The existing authentication
+ scheme for Bugzilla uses email addresses as the primary user ID, and a
+ password to authenticate that user. All places within Bugzilla that
+ require a user ID (e.g assigning a bug) use the email
+ address. The LDAP authentication builds on top of this scheme, rather
+ than replacing it. The initial log-in is done with a username and
+ password for the LDAP directory. Bugzilla tries to bind to LDAP using
+ those credentials and, if successful, tries to map this account to a
+ Bugzilla account. If an LDAP mail attribute is defined, the value of this
+ attribute is used, otherwise the "emailsuffix" parameter is appended to LDAP
+ username to form a full email address. If an account for this address
+ already exists in the Bugzilla installation, it will log in to that account.
+ If no account for that email address exists, one is created at the time
+ of login. (In this case, Bugzilla will attempt to use the "displayName"
+ or "cn" attribute to determine the user's full name.) After
+ authentication, all other user-related tasks are still handled by email
+ address, not LDAP username. For example, bugs are still assigned by
+ email address and users are still queried by email address.
+ </P
+><DIV
+CLASS="caution"
+><P
+></P
+><TABLE
+CLASS="caution"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/caution.gif"
+HSPACE="5"
+ALT="Caution"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>Because the Bugzilla account is not created until the first time
+ a user logs in, a user who has not yet logged is unknown to Bugzilla.
+ This means they cannot be used as an assignee or QA contact (default or
+ otherwise), added to any CC list, or any other such operation. One
+ possible workaround is the <TT
+CLASS="filename"
+>bugzilla_ldapsync.rb</TT
+>
+ script in the
+ <A
+HREF="#gloss-contrib"
+><I
+CLASS="glossterm"
+>&#13; <TT
+CLASS="filename"
+>contrib</TT
+></I
+></A
+>
+ directory. Another possible solution is fixing
+ <A
+HREF="https://bugzilla.mozilla.org/show_bug.cgi?id=201069"
+TARGET="_top"
+>bug
+ 201069</A
+>.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>Parameters required to use LDAP Authentication:</P
+><P
+></P
+><DIV
+CLASS="variablelist"
+><DL
+><DT
+><A
+NAME="param-user_verify_class_for_ldap"
+></A
+>user_verify_class</DT
+><DD
+><P
+>If you want to list <SPAN
+CLASS="QUOTE"
+>"LDAP"</SPAN
+> here,
+ make sure to have set up the other parameters listed below.
+ Unless you have other (working) authentication methods listed as
+ well, you may otherwise not be able to log back in to Bugzilla once
+ you log out.
+ If this happens to you, you will need to manually edit
+ <TT
+CLASS="filename"
+>data/params</TT
+> and set user_verify_class to
+ <SPAN
+CLASS="QUOTE"
+>"DB"</SPAN
+>.
+ </P
+></DD
+><DT
+><A
+NAME="param-LDAPserver"
+></A
+>LDAPserver</DT
+><DD
+><P
+>This parameter should be set to the name (and optionally the
+ port) of your LDAP server. If no port is specified, it assumes
+ the default LDAP port of 389.
+ </P
+><P
+>For example: <SPAN
+CLASS="QUOTE"
+>"ldap.company.com"</SPAN
+>
+ or <SPAN
+CLASS="QUOTE"
+>"ldap.company.com:3268"</SPAN
+>
+ </P
+><P
+>You can also specify a LDAP URI, so as to use other
+ protocols, such as LDAPS or LDAPI. If port was not specified in
+ the URI, the default is either 389 or 636 for 'LDAP' and 'LDAPS'
+ schemes respectively.
+ </P
+><DIV
+CLASS="tip"
+><P
+></P
+><TABLE
+CLASS="tip"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/tip.gif"
+HSPACE="5"
+ALT="Tip"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; In order to use SSL with LDAP, specify a URI with "ldaps://".
+ This will force the use of SSL over port 636.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>For example, normal LDAP:
+ <SPAN
+CLASS="QUOTE"
+>"ldap://ldap.company.com"</SPAN
+>, LDAP over SSL:
+ <SPAN
+CLASS="QUOTE"
+>"ldaps://ldap.company.com"</SPAN
+> or LDAP over a UNIX
+ domain socket <SPAN
+CLASS="QUOTE"
+>"ldapi://%2fvar%2flib%2fldap_sock"</SPAN
+>.
+ </P
+></DD
+><DT
+><A
+NAME="param-LDAPbinddn"
+></A
+>LDAPbinddn [Optional]</DT
+><DD
+><P
+>Some LDAP servers will not allow an anonymous bind to search
+ the directory. If this is the case with your configuration you
+ should set the LDAPbinddn parameter to the user account Bugzilla
+ should use instead of the anonymous bind.
+ </P
+><P
+>Ex. <SPAN
+CLASS="QUOTE"
+>"cn=default,cn=user:password"</SPAN
+></P
+></DD
+><DT
+><A
+NAME="param-LDAPBaseDN"
+></A
+>LDAPBaseDN</DT
+><DD
+><P
+>The LDAPBaseDN parameter should be set to the location in
+ your LDAP tree that you would like to search for email addresses.
+ Your uids should be unique under the DN specified here.
+ </P
+><P
+>Ex. <SPAN
+CLASS="QUOTE"
+>"ou=People,o=Company"</SPAN
+></P
+></DD
+><DT
+><A
+NAME="param-LDAPuidattribute"
+></A
+>LDAPuidattribute</DT
+><DD
+><P
+>The LDAPuidattribute parameter should be set to the attribute
+ which contains the unique UID of your users. The value retrieved
+ from this attribute will be used when attempting to bind as the
+ user to confirm their password.
+ </P
+><P
+>Ex. <SPAN
+CLASS="QUOTE"
+>"uid"</SPAN
+></P
+></DD
+><DT
+><A
+NAME="param-LDAPmailattribute"
+></A
+>LDAPmailattribute</DT
+><DD
+><P
+>The LDAPmailattribute parameter should be the name of the
+ attribute which contains the email address your users will enter
+ into the Bugzilla login boxes.
+ </P
+><P
+>Ex. <SPAN
+CLASS="QUOTE"
+>"mail"</SPAN
+></P
+></DD
+></DL
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="bzradius"
+>3.1.11. RADIUS Authentication</A
+></H3
+><P
+>&#13; RADIUS authentication is a module for Bugzilla's plugin
+ authentication architecture. This page contains all the parameters
+ necessary for configuring Bugzilla to use RADIUS authentication.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Most caveats that apply to LDAP authentication apply to RADIUS
+ authentication as well. See <A
+HREF="#bzldap"
+>Section 3.1.10</A
+> for details.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>Parameters required to use RADIUS Authentication:</P
+><P
+></P
+><DIV
+CLASS="variablelist"
+><DL
+><DT
+><A
+NAME="param-user_verify_class_for_radius"
+></A
+>user_verify_class</DT
+><DD
+><P
+>If you want to list <SPAN
+CLASS="QUOTE"
+>"RADIUS"</SPAN
+> here,
+ make sure to have set up the other parameters listed below.
+ Unless you have other (working) authentication methods listed as
+ well, you may otherwise not be able to log back in to Bugzilla once
+ you log out.
+ If this happens to you, you will need to manually edit
+ <TT
+CLASS="filename"
+>data/params</TT
+> and set user_verify_class to
+ <SPAN
+CLASS="QUOTE"
+>"DB"</SPAN
+>.
+ </P
+></DD
+><DT
+><A
+NAME="param-RADIUS_server"
+></A
+>RADIUS_server</DT
+><DD
+><P
+>This parameter should be set to the name (and optionally the
+ port) of your RADIUS server.
+ </P
+></DD
+><DT
+><A
+NAME="param-RADIUS_secret"
+></A
+>RADIUS_secret</DT
+><DD
+><P
+>This parameter should be set to the RADIUS server's secret.
+ </P
+></DD
+><DT
+><A
+NAME="param-RADIUS_email_suffix"
+></A
+>RADIUS_email_suffix</DT
+><DD
+><P
+>Bugzilla needs an e-mail address for each user account.
+ Therefore, it needs to determine the e-mail address corresponding
+ to a RADIUS user.
+ Bugzilla offers only a simple way to do this: it can concatenate
+ a suffix to the RADIUS user name to convert it into an e-mail
+ address.
+ You can specify this suffix in the RADIUS_email_suffix parameter.
+ </P
+><P
+>If this simple solution does not work for you, you'll
+ probably need to modify
+ <TT
+CLASS="filename"
+>Bugzilla/Auth/Verify/RADIUS.pm</TT
+> to match your
+ requirements.
+ </P
+></DD
+></DL
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="param-email"
+>3.1.12. Email</A
+></H3
+><P
+>&#13; This page contains all of the parameters for configuring how
+ Bugzilla deals with the email notifications it sends. See below
+ for a summary of important options.
+ </P
+><P
+></P
+><DIV
+CLASS="variablelist"
+><DL
+><DT
+>mail_delivery_method</DT
+><DD
+><P
+>&#13; This is used to specify how email is sent, or if it is sent at
+ all. There are several options included for different MTAs,
+ along with two additional options that disable email sending.
+ "Test" does not send mail, but instead saves it in
+ <TT
+CLASS="filename"
+>data/mailer.testfile</TT
+> for later review.
+ "None" disables email sending entirely.
+ </P
+></DD
+><DT
+>mailfrom</DT
+><DD
+><P
+>&#13; This is the email address that will appear in the "From" field
+ of all emails sent by this Bugzilla installation. Some email
+ servers require mail to be from a valid email address, therefore
+ it is recommended to choose a valid email address here.
+ </P
+></DD
+><DT
+>whinedays</DT
+><DD
+><P
+>&#13; Set this to the number of days you want to let bugs go
+ in the CONFIRMED state before notifying people they have
+ untouched new bugs. If you do not plan to use this feature, simply
+ do not set up the whining cron job described in the installation
+ instructions, or set this value to "0" (never whine).
+ </P
+></DD
+><DT
+>globalwatcher</DT
+><DD
+><P
+>&#13; This allows you to define specific users who will
+ receive notification each time a new bug in entered, or when
+ an existing bug changes, according to the normal groupset
+ permissions. It may be useful for sending notifications to a
+ mailing-list, for instance.
+ </P
+></DD
+></DL
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="param-patchviewer"
+>3.1.13. Patch Viewer</A
+></H3
+><P
+>&#13; This page contains configuration parameters for the CVS server,
+ Bonsai server and LXR server that Bugzilla will use to enable the
+ features of the Patch Viewer. Bonsai is a tool that enables queries
+ to a CVS tree. LXR is a tool that can cross reference and index source
+ code.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="param-querydefaults"
+>3.1.14. Query Defaults</A
+></H3
+><P
+>&#13; This page controls the default behavior of Bugzilla in regards to
+ several aspects of querying bugs. Options include what the default
+ query options are, what the "My Bugs" page returns, whether users
+ can freely add bugs to the quip list, and how many duplicate bugs are
+ needed to add a bug to the "most frequently reported" list.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="param-shadowdatabase"
+>3.1.15. Shadow Database</A
+></H3
+><P
+>&#13; This page controls whether a shadow database is used, and all the
+ parameters associated with the shadow database. Versions of Bugzilla
+ prior to 3.2 used the MyISAM table type, which supports
+ only table-level write locking. With MyISAM, any time someone is making a change to
+ a bug, the entire table is locked until the write operation is complete.
+ Locking for write also blocks reads until the write is complete.
+ </P
+><P
+>&#13; The <SPAN
+CLASS="QUOTE"
+>"shadowdb"</SPAN
+> parameter was designed to get around
+ this limitation. While only a single user is allowed to write to
+ a table at a time, reads can continue unimpeded on a read-only
+ shadow copy of the database.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; As of version 3.2, Bugzilla no longer uses the MyISAM table type.
+ Instead, InnoDB is used, which can do transaction-based locking.
+ Therefore, the limitations the Shadow Database feature was designed
+ to workaround no longer exist.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="admin-usermatching"
+>3.1.16. User Matching</A
+></H3
+><P
+>&#13; The settings on this page control how users are selected and queried
+ when adding a user to a bug. For example, users need to be selected
+ when choosing who the bug is assigned to, adding to the CC list or
+ selecting a QA contact. With the "usemenuforusers" parameter, it is
+ possible to configure Bugzilla to
+ display a list of users in the fields instead of an empty text field.
+ This should only be used in Bugzilla installations with a small number
+ of users. If users are selected via a text box, this page also
+ contains parameters for how user names can be queried and matched
+ when entered.
+ </P
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="useradmin"
+>3.2. User Administration</A
+></H2
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="defaultuser"
+>3.2.1. Creating the Default User</A
+></H3
+><P
+>When you first run checksetup.pl after installing Bugzilla, it
+ will prompt you for the administrative username (email address) and
+ password for this "super user". If for some reason you delete
+ the "super user" account, re-running checksetup.pl will again prompt
+ you for this username and password.</P
+><DIV
+CLASS="tip"
+><P
+></P
+><TABLE
+CLASS="tip"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/tip.gif"
+HSPACE="5"
+ALT="Tip"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>If you wish to add more administrative users, add them to
+ the "admin" group and, optionally, edit the tweakparams, editusers,
+ creategroups, editcomponents, and editkeywords groups to add the
+ entire admin group to those groups (which is the case by default).
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="manageusers"
+>3.2.2. Managing Other Users</A
+></H3
+><DIV
+CLASS="section"
+><H4
+CLASS="section"
+><A
+NAME="user-account-search"
+>3.2.2.1. Searching for existing users</A
+></H4
+><P
+>&#13; If you have <SPAN
+CLASS="QUOTE"
+>"editusers"</SPAN
+> privileges or if you are allowed
+ to grant privileges for some groups, the <SPAN
+CLASS="QUOTE"
+>"Users"</SPAN
+> link
+ will appear in the Administration page.
+ </P
+><P
+>&#13; The first screen is a search form to search for existing user
+ accounts. You can run searches based either on the user ID, real
+ name or login name (i.e. the email address, or just the first part
+ of the email address if the "emailsuffix" parameter is set).
+ The search can be conducted
+ in different ways using the listbox to the right of the text entry
+ box. You can match by case-insensitive substring (the default),
+ regular expression, a <EM
+>reverse</EM
+> regular expression
+ match (which finds every user name which does NOT match the regular
+ expression), or the exact string if you know exactly who you are
+ looking for. The search can be restricted to users who are in a
+ specific group. By default, the restriction is turned off.
+ </P
+><P
+>&#13; The search returns a list of
+ users matching your criteria. User properties can be edited by clicking
+ the login name. The Account History of a user can be viewed by clicking
+ the "View" link in the Account History column. The Account History
+ displays changes that have been made to the user account, the time of
+ the change and the user who made the change. For example, the Account
+ History page will display details of when a user was added or removed
+ from a group.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H4
+CLASS="section"
+><A
+NAME="createnewusers"
+>3.2.2.2. Creating new users</A
+></H4
+><DIV
+CLASS="section"
+><H5
+CLASS="section"
+><A
+NAME="self-registration"
+>3.2.2.2.1. Self-registration</A
+></H5
+><P
+>&#13; By default, users can create their own user accounts by clicking the
+ <SPAN
+CLASS="QUOTE"
+>"New Account"</SPAN
+> link at the bottom of each page (assuming
+ they aren't logged in as someone else already). If you want to disable
+ this self-registration, or if you want to restrict who can create his
+ own user account, you have to edit the <SPAN
+CLASS="QUOTE"
+>"createemailregexp"</SPAN
+>
+ parameter in the <SPAN
+CLASS="QUOTE"
+>"Configuration"</SPAN
+> page, see
+ <A
+HREF="#parameters"
+>Section 3.1</A
+>.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H5
+CLASS="section"
+><A
+NAME="user-account-creation"
+>3.2.2.2.2. Accounts created by an administrator</A
+></H5
+><P
+>&#13; Users with <SPAN
+CLASS="QUOTE"
+>"editusers"</SPAN
+> privileges, such as administrators,
+ can create user accounts for other users:
+ </P
+><P
+></P
+><OL
+TYPE="1"
+><LI
+><P
+>After logging in, click the "Users" link at the footer of
+ the query page, and then click "Add a new user".</P
+></LI
+><LI
+><P
+>Fill out the form presented. This page is self-explanatory.
+ When done, click "Submit".</P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>Adding a user this way will <EM
+>not</EM
+>
+ send an email informing them of their username and password.
+ While useful for creating dummy accounts (watchers which
+ shuttle mail to another system, for instance, or email
+ addresses which are a mailing list), in general it is
+ preferable to log out and use the <SPAN
+CLASS="QUOTE"
+>"New Account"</SPAN
+>
+ button to create users, as it will pre-populate all the
+ required fields and also notify the user of her account name
+ and password.</P
+></TD
+></TR
+></TABLE
+></DIV
+></LI
+></OL
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H4
+CLASS="section"
+><A
+NAME="modifyusers"
+>3.2.2.3. Modifying Users</A
+></H4
+><P
+>Once you have found your user, you can change the following
+ fields:</P
+><P
+></P
+><UL
+><LI
+><P
+>&#13; <EM
+>Login Name</EM
+>:
+ This is generally the user's full email address. However, if you
+ have are using the <SPAN
+CLASS="QUOTE"
+>"emailsuffix"</SPAN
+> parameter, this may
+ just be the user's login name. Note that users can now change their
+ login names themselves (to any valid email address).
+ </P
+></LI
+><LI
+><P
+>&#13; <EM
+>Real Name</EM
+>: The user's real name. Note that
+ Bugzilla does not require this to create an account.</P
+></LI
+><LI
+><P
+>&#13; <EM
+>Password</EM
+>:
+ You can change the user's password here. Users can automatically
+ request a new password, so you shouldn't need to do this often.
+ If you want to disable an account, see Disable Text below.
+ </P
+></LI
+><LI
+><P
+>&#13; <EM
+>Bugmail Disabled</EM
+>:
+ Mark this checkbox to disable bugmail and whinemail completely
+ for this account. This checkbox replaces the data/nomail file
+ which existed in older versions of Bugzilla.
+ </P
+></LI
+><LI
+><P
+>&#13; <EM
+>Disable Text</EM
+>:
+ If you type anything in this box, including just a space, the
+ user is prevented from logging in, or making any changes to
+ bugs via the web interface.
+ The HTML you type in this box is presented to the user when
+ they attempt to perform these actions, and should explain
+ why the account was disabled.
+ </P
+><P
+>&#13; Users with disabled accounts will continue to receive
+ mail from Bugzilla; furthermore, they will not be able
+ to log in themselves to change their own preferences and
+ stop it. If you want an account (disabled or active) to
+ stop receiving mail, simply check the
+ <SPAN
+CLASS="QUOTE"
+>"Bugmail Disabled"</SPAN
+> checkbox above.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Even users whose accounts have been disabled can still
+ submit bugs via the e-mail gateway, if one exists.
+ The e-mail gateway should <EM
+>not</EM
+> be
+ enabled for secure installations of Bugzilla.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Don't disable all the administrator accounts!
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></LI
+><LI
+><P
+>&#13; <EM
+>&#60;groupname&#62;</EM
+>:
+ If you have created some groups, e.g. "securitysensitive", then
+ checkboxes will appear here to allow you to add users to, or
+ remove them from, these groups. The first checkbox gives the
+ user the ability to add and remove other users as members of
+ this group. The second checkbox adds the user himself as a member
+ of the group.
+ </P
+></LI
+><LI
+><P
+>&#13; <EM
+>canconfirm</EM
+>:
+ This field is only used if you have enabled the "unconfirmed"
+ status. If you enable this for a user,
+ that user can then move bugs from "Unconfirmed" to a "Confirmed"
+ status (e.g.: "New" status).</P
+></LI
+><LI
+><P
+>&#13; <EM
+>creategroups</EM
+>:
+ This option will allow a user to create and destroy groups in
+ Bugzilla.</P
+></LI
+><LI
+><P
+>&#13; <EM
+>editbugs</EM
+>:
+ Unless a user has this bit set, they can only edit those bugs
+ for which they are the assignee or the reporter. Even if this
+ option is unchecked, users can still add comments to bugs.
+ </P
+></LI
+><LI
+><P
+>&#13; <EM
+>editcomponents</EM
+>:
+ This flag allows a user to create new products and components,
+ as well as modify and destroy those that have no bugs associated
+ with them. If a product or component has bugs associated with it,
+ those bugs must be moved to a different product or component
+ before Bugzilla will allow them to be destroyed.
+ </P
+></LI
+><LI
+><P
+>&#13; <EM
+>editkeywords</EM
+>:
+ If you use Bugzilla's keyword functionality, enabling this
+ feature allows a user to create and destroy keywords. As always,
+ the keywords for existing bugs containing the keyword the user
+ wishes to destroy must be changed before Bugzilla will allow it
+ to die.</P
+></LI
+><LI
+><P
+>&#13; <EM
+>editusers</EM
+>:
+ This flag allows a user to do what you're doing right now: edit
+ other users. This will allow those with the right to do so to
+ remove administrator privileges from other users or grant them to
+ themselves. Enable with care.</P
+></LI
+><LI
+><P
+>&#13; <EM
+>tweakparams</EM
+>:
+ This flag allows a user to change Bugzilla's Params
+ (using <TT
+CLASS="filename"
+>editparams.cgi</TT
+>.)</P
+></LI
+><LI
+><P
+>
+ <EM
+>&#60;productname&#62;</EM
+>:
+ This allows an administrator to specify the products
+ in which a user can see bugs. If you turn on the
+ <SPAN
+CLASS="QUOTE"
+>"makeproductgroups"</SPAN
+> parameter in
+ the Group Security Panel in the Parameters page,
+ then Bugzilla creates one group per product (at the time you create
+ the product), and this group has exactly the same name as the
+ product itself. Note that for products that already exist when
+ the parameter is turned on, the corresponding group will not be
+ created. The user must still have the <SPAN
+CLASS="QUOTE"
+>"editbugs"</SPAN
+>
+ privilege to edit bugs in these products.</P
+></LI
+></UL
+></DIV
+><DIV
+CLASS="section"
+><HR><H4
+CLASS="section"
+><A
+NAME="user-account-deletion"
+>3.2.2.4. Deleting Users</A
+></H4
+><P
+>&#13; If the <SPAN
+CLASS="QUOTE"
+>"allowuserdeletion"</SPAN
+> parameter is turned on, see
+ <A
+HREF="#parameters"
+>Section 3.1</A
+>, then you can also delete user accounts.
+ Note that this is most of the time not the best thing to do. If only
+ a warning in a yellow box is displayed, then the deletion is safe.
+ If a warning is also displayed in a red box, then you should NOT try
+ to delete the user account, else you will get referential integrity
+ problems in your database, which can lead to unexpected behavior,
+ such as bugs not appearing in bug lists anymore, or data displaying
+ incorrectly. You have been warned!
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H4
+CLASS="section"
+><A
+NAME="impersonatingusers"
+>3.2.2.5. Impersonating Users</A
+></H4
+><P
+>&#13; There may be times when an administrator would like to do something as
+ another user. The <B
+CLASS="command"
+>sudo</B
+> feature may be used to do
+ this.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; To use the sudo feature, you must be in the
+ <EM
+>bz_sudoers</EM
+> group. By default, all
+ administrators are in this group.</P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; If you have access to this feature, you may start a session by
+ going to the Edit Users page, Searching for a user and clicking on
+ their login. You should see a link below their login name titled
+ "Impersonate this user". Click on the link. This will take you
+ to a page where you will see a description of the feature and
+ instructions for using it. After reading the text, simply
+ enter the login of the user you would like to impersonate, provide
+ a short message explaining why you are doing this, and press the
+ button.</P
+><P
+>&#13; As long as you are using this feature, everything you do will be done
+ as if you were logged in as the user you are impersonating.</P
+><DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; The user you are impersonating will not be told about what you are
+ doing. If you do anything that results in mail being sent, that
+ mail will appear to be from the user you are impersonating. You
+ should be extremely careful while using this feature.</P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="classifications"
+>3.3. Classifications</A
+></H2
+><P
+>Classifications tend to be used in order to group several related
+ products into one distinct entity.</P
+><P
+>The classifications layer is disabled by default; it can be turned
+ on or off using the useclassification parameter,
+ in the <EM
+>Bug Fields</EM
+> section of the edit parameters screen.</P
+><P
+>Access to the administration of classifications is controlled using
+ the <EM
+>editclassifications</EM
+> system group, which defines
+ a privilege for creating, destroying, and editing classifications.</P
+><P
+>When activated, classifications will introduce an additional
+ step when filling bugs (dedicated to classification selection), and they
+ will also appear in the advanced search form.</P
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="products"
+>3.4. Products</A
+></H2
+><P
+>&#13; <A
+HREF="#gloss-product"
+><I
+CLASS="glossterm"
+>&#13; Products</I
+></A
+> typically represent real-world
+ shipping products. Products can be given
+ <A
+HREF="#classifications"
+>Classifications</A
+>.
+ For example, if a company makes computer games,
+ they could have a classification of "Games", and a separate
+ product for each game. This company might also have a
+ <SPAN
+CLASS="QUOTE"
+>"Common"</SPAN
+> product for units of technology used
+ in multiple games, and perhaps a few special products that
+ represent items that are not actually shipping products
+ (for example, "Website", or "Administration").
+ </P
+><P
+>&#13; Many of Bugzilla's settings are configurable on a per-product
+ basis. The number of <SPAN
+CLASS="QUOTE"
+>"votes"</SPAN
+> available to
+ users is set per-product, as is the number of votes
+ required to move a bug automatically from the UNCONFIRMED
+ status to the CONFIRMED status.
+ </P
+><P
+>&#13; When creating or editing products the following options are
+ available:
+ </P
+><P
+></P
+><DIV
+CLASS="variablelist"
+><DL
+><DT
+>Product</DT
+><DD
+><P
+>
+ The name of the product
+ </P
+></DD
+><DT
+>Description</DT
+><DD
+><P
+>
+ A brief description of the product
+ </P
+></DD
+><DT
+>Default milestone</DT
+><DD
+><P
+>
+ Select the default milestone for this product.
+ </P
+></DD
+><DT
+>Closed for bug entry</DT
+><DD
+><P
+>
+ Select this box to prevent new bugs from being
+ entered against this product.
+ </P
+></DD
+><DT
+>Maximum votes per person</DT
+><DD
+><P
+>
+ Maximum votes a user is allowed to give for this
+ product
+ </P
+></DD
+><DT
+>Maximum votes a person can put on a single bug</DT
+><DD
+><P
+>
+ Maximum votes a user is allowed to give for this
+ product in a single bug
+ </P
+></DD
+><DT
+>Confirmation threshold</DT
+><DD
+><P
+>
+ Number of votes needed to automatically remove any
+ bug against this product from the UNCONFIRMED state
+ </P
+></DD
+><DT
+>Version</DT
+><DD
+><P
+>
+ Specify which version of the product bugs will be
+ entered against.
+ </P
+></DD
+><DT
+>Create chart datasets for this product</DT
+><DD
+><P
+>
+ Select to make chart datasets available for this product.
+ </P
+></DD
+></DL
+></DIV
+><P
+>&#13; When editing a product there is also a link to edit Group Access Controls,
+ see <A
+HREF="#product-group-controls"
+>Section 3.4.4</A
+>.
+ </P
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="create-product"
+>3.4.1. Creating New Products</A
+></H3
+><P
+>&#13; To create a new product:
+ </P
+><P
+></P
+><OL
+TYPE="1"
+><LI
+><P
+>
+ Select <SPAN
+CLASS="QUOTE"
+>"Administration"</SPAN
+> from the footer and then
+ choose <SPAN
+CLASS="QUOTE"
+>"Products"</SPAN
+> from the main administration page.
+ </P
+></LI
+><LI
+><P
+>&#13; Select the <SPAN
+CLASS="QUOTE"
+>"Add"</SPAN
+> link in the bottom right.
+ </P
+></LI
+><LI
+><P
+>&#13; Enter the name of the product and a description. The
+ Description field may contain HTML.
+ </P
+></LI
+><LI
+><P
+>&#13; When the product is created, Bugzilla will give a message
+ stating that a component must be created before any bugs can
+ be entered against the new product. Follow the link to create
+ a new component. See <A
+HREF="#components"
+>Components</A
+> for more
+ information.
+ </P
+></LI
+></OL
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="edit-products"
+>3.4.2. Editing Products</A
+></H3
+><P
+>&#13; To edit an existing product, click the "Products" link from the
+ "Administration" page. If the 'useclassification' parameter is
+ turned on, a table of existing classifications is displayed,
+ including an "Unclassified" category. The table indicates how many products
+ are in each classification. Click on the classification name to see its
+ products. If the 'useclassification' parameter is not in use, the table
+ lists all products directly. The product table summarizes the information
+ about the product defined
+ when the product was created. Click on the product name to edit these
+ properties, and to access links to other product attributes such as the
+ product's components, versions, milestones, and group access controls.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="comps-vers-miles-products"
+>3.4.3. Adding or Editing Components, Versions and Target Milestones</A
+></H3
+><P
+>&#13; To edit existing, or add new, Components, Versions or Target Milestones
+ to a Product, select the "Edit Components", "Edit Versions" or "Edit
+ Milestones" links from the "Edit Product" page. A table of existing
+ Components, Versions or Milestones is displayed. Click on a item name
+ to edit the properties of that item. Below the table is a link to add
+ a new Component, Version or Milestone.
+ </P
+><P
+>&#13; For more information on components, see <A
+HREF="#components"
+>Components</A
+>.
+ </P
+><P
+>&#13; For more information on versions, see <A
+HREF="#versions"
+>Section 3.6</A
+>.
+ </P
+><P
+>&#13; For more information on milestones, see <A
+HREF="#milestones"
+>Section 3.7</A
+>.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="product-group-controls"
+>3.4.4. Assigning Group Controls to Products</A
+></H3
+><P
+>
+ On the <SPAN
+CLASS="QUOTE"
+>"Edit Product"</SPAN
+> page, there is a link called
+ <SPAN
+CLASS="QUOTE"
+>"Edit Group Access Controls"</SPAN
+>. The settings on this page
+ control the relationship of the groups to the product being edited.
+ </P
+><P
+>&#13; Group Access Controls are an important aspect of using groups for
+ isolating products and restricting access to bugs filed against those
+ products. For more information on groups, including how to create, edit
+ add users to, and alter permission of, see <A
+HREF="#groups"
+>Section 3.15</A
+>.
+ </P
+><P
+>&#13; After selecting the "Edit Group Access Controls" link from the "Edit
+ Product" page, a table containing all user-defined groups for this
+ Bugzilla installation is displayed. The system groups that are created
+ when Bugzilla is installed are not applicable to Group Access Controls.
+ Below is description of what each of these fields means.
+ </P
+><P
+>&#13; Groups may be applicable (e.g bugs in this product can be associated
+ with this group) , default (e.g. bugs in this product are in this group
+ by default), and mandatory (e.g. bugs in this product must be associated
+ with this group) for each product. Groups can also control access
+ to bugs for a given product, or be used to make bugs for a product
+ totally read-only unless the group restrictions are met. The best way to
+ understand these relationships is by example. See
+ <A
+HREF="#group-control-examples"
+>Section 3.4.4.1</A
+> for examples of
+ product and group relationships.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Products and Groups are not limited to a one-to-one relationship.
+ Multiple groups can be associated with the same product, and groups
+ can be associated with more than one product.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; If any group has <EM
+>Entry</EM
+> selected, then the
+ product will restrict bug entry to only those users
+ who are members of <EM
+>all</EM
+> the groups with
+ <EM
+>Entry</EM
+> selected.
+ </P
+><P
+>&#13; If any group has <EM
+>Canedit</EM
+> selected,
+ then the product will be read-only for any users
+ who are not members of <EM
+>all</EM
+> of the groups with
+ <EM
+>Canedit</EM
+> selected. <EM
+>Only</EM
+> users who
+ are members of all the <EM
+>Canedit</EM
+> groups
+ will be able to edit bugs for this product. This is an additional
+ restriction that enables finer-grained control over products rather
+ than just all-or-nothing access levels.
+ </P
+><P
+>&#13; The following settings let you
+ choose privileges on a <EM
+>per-product basis</EM
+>.
+ This is a convenient way to give privileges to
+ some users for some products only, without having
+ to give them global privileges which would affect
+ all products.
+ </P
+><P
+>&#13; Any group having <EM
+>editcomponents</EM
+>
+ selected allows users who are in this group to edit all
+ aspects of this product, including components, milestones
+ and versions.
+ </P
+><P
+>&#13; Any group having <EM
+>canconfirm</EM
+> selected
+ allows users who are in this group to confirm bugs
+ in this product.
+ </P
+><P
+>&#13; Any group having <EM
+>editbugs</EM
+> selected allows
+ users who are in this group to edit all fields of
+ bugs in this product.
+ </P
+><P
+>&#13; The <EM
+>MemberControl</EM
+> and
+ <EM
+>OtherControl</EM
+> are used in tandem to determine which
+ bugs will be placed in this group. The only allowable combinations of
+ these two parameters are listed in a table on the "Edit Group Access Controls"
+ page. Consult this table for details on how these fields can be used.
+ Examples of different uses are described below.
+ </P
+><DIV
+CLASS="section"
+><HR><H4
+CLASS="section"
+><A
+NAME="group-control-examples"
+>3.4.4.1. Common Applications of Group Controls</A
+></H4
+><P
+>&#13; The use of groups is best explained by providing examples that illustrate
+ configurations for common use cases. The examples follow a common syntax:
+ <EM
+>Group: Entry, MemberControl, OtherControl, CanEdit,
+ EditComponents, CanConfirm, EditBugs</EM
+>. Where "Group" is the name
+ of the group being edited for this product. The other fields all
+ correspond to the table on the "Edit Group Access Controls" page. If any
+ of these options are not listed, it means they are not checked.
+ </P
+><P
+>&#13; Basic Product/Group Restriction
+ </P
+><P
+>&#13; Suppose there is a product called "Bar". The
+ "Bar" product can only have bugs entered against it by users in the
+ group "Foo". Additionally, bugs filed against product "Bar" must stay
+ restricted to users to "Foo" at all times. Furthermore, only members
+ of group "Foo" can edit bugs filed against product "Bar", even if other
+ users could see the bug. This arrangement would achieved by the
+ following:
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#13;Product Bar:
+foo: ENTRY, MANDATORY/MANDATORY, CANEDIT
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>&#13; Perhaps such strict restrictions are not needed for product "Bar". A
+ more lenient way to configure product "Bar" and group "Foo" would be:
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#13;Product Bar:
+foo: ENTRY, SHOWN/SHOWN, EDITCOMPONENTS, CANCONFIRM, EDITBUGS
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>&#13; The above indicates that for product "Bar", members of group "Foo" can
+ enter bugs. Any one with permission to edit a bug against product "Bar"
+ can put the bug
+ in group "Foo", even if they themselves are not in "Foo". Anyone in group
+ "Foo" can edit all aspects of the components of product "Bar", can confirm
+ bugs against product "Bar", and can edit all fields of any bug against
+ product "Bar".
+ </P
+><P
+>&#13; General User Access With Security Group
+ </P
+><P
+>&#13; To permit any user to file bugs against "Product A",
+ and to permit any user to submit those bugs into a
+ group called "Security":
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>
+Product A:
+security: SHOWN/SHOWN
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>&#13; General User Access With A Security Product
+ </P
+><P
+>&#13; To permit any user to file bugs against product called "Security"
+ while keeping those bugs from becoming visible to anyone
+ outside the group "SecurityWorkers" (unless a member of the
+ "SecurityWorkers" group removes that restriction):
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>
+Product Security:
+securityworkers: DEFAULT/MANDATORY
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>&#13; Product Isolation With a Common Group
+ </P
+><P
+>&#13; To permit users of "Product A" to access the bugs for
+ "Product A", users of "Product B" to access the bugs for
+ "Product B", and support staff, who are members of the "Support
+ Group" to access both, three groups are needed:
+ </P
+><P
+></P
+><OL
+TYPE="1"
+><LI
+><P
+>Support Group: Contains members of the support staff.</P
+></LI
+><LI
+><P
+>AccessA Group: Contains users of product A and the Support group.</P
+></LI
+><LI
+><P
+>AccessB Group: Contains users of product B and the Support group.</P
+></LI
+></OL
+><P
+>&#13; Once these three groups are defined, the product group controls
+ can be set to:
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#13;Product A:
+AccessA: ENTRY, MANDATORY/MANDATORY
+Product B:
+AccessB: ENTRY, MANDATORY/MANDATORY
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>&#13; Perhaps the "Support Group" wants more control. For example,
+ the "Support Group" could be permitted to make bugs inaccessible to
+ users of both groups "AccessA" and "AccessB".
+ Then, the "Support Group" could be permitted to publish
+ bugs relevant to all users in a third product (let's call it
+ "Product Common") that is read-only
+ to anyone outside the "Support Group". In this way the "Support Group"
+ could control bugs that should be seen by both groups.
+ That configuration would be:
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#13;Product A:
+AccessA: ENTRY, MANDATORY/MANDATORY
+Support: SHOWN/NA
+Product B:
+AccessB: ENTRY, MANDATORY/MANDATORY
+Support: SHOWN/NA
+Product Common:
+Support: ENTRY, DEFAULT/MANDATORY, CANEDIT
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>&#13; Make a Product Read Only
+ </P
+><P
+>&#13; Sometimes a product is retired and should no longer have
+ new bugs filed against it (for example, an older version of a software
+ product that is no longer supported). A product can be made read-only
+ by creating a group called "readonly" and adding products to the
+ group as needed:
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#13;Product A:
+ReadOnly: ENTRY, NA/NA, CANEDIT
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; For more information on Groups outside of how they relate to products
+ see <A
+HREF="#groups"
+>Section 3.15</A
+>.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="components"
+>3.5. Components</A
+></H2
+><P
+>Components are subsections of a Product. E.g. the computer game
+ you are designing may have a "UI"
+ component, an "API" component, a "Sound System" component, and a
+ "Plugins" component, each overseen by a different programmer. It
+ often makes sense to divide Components in Bugzilla according to the
+ natural divisions of responsibility within your Product or
+ company.</P
+><P
+>&#13; Each component has a default assignee and (if you turned it on in the parameters),
+ a QA Contact. The default assignee should be the primary person who fixes bugs in
+ that component. The QA Contact should be the person who will ensure
+ these bugs are completely fixed. The Assignee, QA Contact, and Reporter
+ will get email when new bugs are created in this Component and when
+ these bugs change. Default Assignee and Default QA Contact fields only
+ dictate the
+ <EM
+>default assignments</EM
+>;
+ these can be changed on bug submission, or at any later point in
+ a bug's life.</P
+><P
+>To create a new Component:</P
+><P
+></P
+><OL
+TYPE="1"
+><LI
+><P
+>Select the <SPAN
+CLASS="QUOTE"
+>"Edit components"</SPAN
+> link
+ from the <SPAN
+CLASS="QUOTE"
+>"Edit product"</SPAN
+> page</P
+></LI
+><LI
+><P
+>Select the <SPAN
+CLASS="QUOTE"
+>"Add"</SPAN
+> link in the bottom right.</P
+></LI
+><LI
+><P
+>Fill out the <SPAN
+CLASS="QUOTE"
+>"Component"</SPAN
+> field, a
+ short <SPAN
+CLASS="QUOTE"
+>"Description"</SPAN
+>, the
+ <SPAN
+CLASS="QUOTE"
+>"Default Assignee"</SPAN
+>, <SPAN
+CLASS="QUOTE"
+>"Default CC List"</SPAN
+>
+ and <SPAN
+CLASS="QUOTE"
+>"Default QA Contact"</SPAN
+> (if enabled).
+ The <SPAN
+CLASS="QUOTE"
+>"Component Description"</SPAN
+> field may contain a
+ limited subset of HTML tags. The <SPAN
+CLASS="QUOTE"
+>"Default Assignee"</SPAN
+>
+ field must be a login name already existing in the Bugzilla database.
+ </P
+></LI
+></OL
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="versions"
+>3.6. Versions</A
+></H2
+><P
+>Versions are the revisions of the product, such as "Flinders
+ 3.1", "Flinders 95", and "Flinders 2000". Version is not a multi-select
+ field; the usual practice is to select the earliest version known to have
+ the bug.
+ </P
+><P
+>To create and edit Versions:</P
+><P
+></P
+><OL
+TYPE="1"
+><LI
+><P
+>From the "Edit product" screen, select "Edit Versions"</P
+></LI
+><LI
+><P
+>You will notice that the product already has the default
+ version "undefined". Click the "Add" link in the bottom right.</P
+></LI
+><LI
+><P
+>Enter the name of the Version. This field takes text only.
+ Then click the "Add" button.</P
+></LI
+></OL
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="milestones"
+>3.7. Milestones</A
+></H2
+><P
+>Milestones are "targets" that you plan to get a bug fixed by. For
+ example, you have a bug that you plan to fix for your 3.0 release, it
+ would be assigned the milestone of 3.0.</P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>Milestone options will only appear for a Product if you turned
+ on the "usetargetmilestone" parameter in the "Bug Fields" tab of the
+ "Parameters" page.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>To create new Milestones, and set Default Milestones:</P
+><P
+></P
+><OL
+TYPE="1"
+><LI
+><P
+>Select "Edit milestones" from the "Edit product" page.</P
+></LI
+><LI
+><P
+>Select "Add" in the bottom right corner.</P
+></LI
+><LI
+><P
+>Enter the name of the Milestone in the "Milestone" field. You
+ can optionally set the "sortkey", which is a positive or negative
+ number (-32768 to 32767) that defines where in the list this particular
+ milestone appears. This is because milestones often do not
+ occur in alphanumeric order For example, "Future" might be
+ after "Release 1.2". Select "Add".</P
+></LI
+></OL
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="flags-overview"
+>3.8. Flags</A
+></H2
+><P
+>&#13; Flags are a way to attach a specific status to a bug or attachment,
+ either <SPAN
+CLASS="QUOTE"
+>"+"</SPAN
+> or <SPAN
+CLASS="QUOTE"
+>"-"</SPAN
+>. The meaning of these symbols depends on the text
+ the flag itself, but contextually they could mean pass/fail,
+ accept/reject, approved/denied, or even a simple yes/no. If your site
+ allows requestable flags, then users may set a flag to <SPAN
+CLASS="QUOTE"
+>"?"</SPAN
+> as a
+ request to another user that they look at the bug/attachment, and set
+ the flag to its correct status.
+ </P
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="flags-simpleexample"
+>3.8.1. A Simple Example</A
+></H3
+><P
+>&#13; A developer might want to ask their manager,
+ <SPAN
+CLASS="QUOTE"
+>"Should we fix this bug before we release version 2.0?"</SPAN
+>
+ They might want to do this for a <EM
+>lot</EM
+> of bugs,
+ so it would be nice to streamline the process...
+ </P
+><P
+>&#13; In Bugzilla, it would work this way:
+ <P
+></P
+><OL
+TYPE="1"
+><LI
+><P
+>&#13; The Bugzilla administrator creates a flag type called
+ <SPAN
+CLASS="QUOTE"
+>"blocking2.0"</SPAN
+> that shows up on all bugs in
+ your product.
+ </P
+><P
+>&#13; It shows up on the <SPAN
+CLASS="QUOTE"
+>"Show Bug"</SPAN
+> screen
+ as the text <SPAN
+CLASS="QUOTE"
+>"blocking2.0"</SPAN
+> with a drop-down box next
+ to it. The drop-down box contains four values: an empty space,
+ <SPAN
+CLASS="QUOTE"
+>"?"</SPAN
+>, <SPAN
+CLASS="QUOTE"
+>"-"</SPAN
+>, and <SPAN
+CLASS="QUOTE"
+>"+"</SPAN
+>.
+ </P
+></LI
+><LI
+><P
+>The developer sets the flag to <SPAN
+CLASS="QUOTE"
+>"?"</SPAN
+>.</P
+></LI
+><LI
+><P
+>&#13; The manager sees the <SAMP
+CLASS="computeroutput"
+>blocking2.0</SAMP
+>
+ flag with a <SPAN
+CLASS="QUOTE"
+>"?"</SPAN
+> value.
+ </P
+></LI
+><LI
+><P
+>&#13; If the manager thinks the feature should go into the product
+ before version 2.0 can be released, he sets the flag to
+ <SPAN
+CLASS="QUOTE"
+>"+"</SPAN
+>. Otherwise, he sets it to <SPAN
+CLASS="QUOTE"
+>"-"</SPAN
+>.
+ </P
+></LI
+><LI
+><P
+>&#13; Now, every Bugzilla user who looks at the bug knows whether or
+ not the bug needs to be fixed before release of version 2.0.
+ </P
+></LI
+></OL
+>
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="flags-about"
+>3.8.2. About Flags</A
+></H3
+><DIV
+CLASS="section"
+><H4
+CLASS="section"
+><A
+NAME="flag-values"
+>3.8.2.1. Values</A
+></H4
+><P
+>&#13; Flags can have three values:
+ <P
+></P
+><DIV
+CLASS="variablelist"
+><DL
+><DT
+><SAMP
+CLASS="computeroutput"
+>?</SAMP
+></DT
+><DD
+><P
+>&#13; A user is requesting that a status be set. (Think of it as 'A question is being asked'.)
+ </P
+></DD
+><DT
+><SAMP
+CLASS="computeroutput"
+>-</SAMP
+></DT
+><DD
+><P
+>&#13; The status has been set negatively. (The question has been answered <SPAN
+CLASS="QUOTE"
+>"no"</SPAN
+>.)
+ </P
+></DD
+><DT
+><SAMP
+CLASS="computeroutput"
+>+</SAMP
+></DT
+><DD
+><P
+>&#13; The status has been set positively.
+ (The question has been answered <SPAN
+CLASS="QUOTE"
+>"yes"</SPAN
+>.)
+ </P
+></DD
+></DL
+></DIV
+>
+ </P
+><P
+>&#13; Actually, there's a fourth value a flag can have --
+ <SPAN
+CLASS="QUOTE"
+>"unset"</SPAN
+> -- which shows up as a blank space. This
+ just means that nobody has expressed an opinion (or asked
+ someone else to express an opinion) about this bug or attachment.
+ </P
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="flag-askto"
+>3.8.3. Using flag requests</A
+></H3
+><P
+>&#13; If a flag has been defined as 'requestable', and a user has enough privileges
+ to request it (see below), the user can set the flag's status to <SPAN
+CLASS="QUOTE"
+>"?"</SPAN
+>.
+ This status indicates that someone (a.k.a. <SPAN
+CLASS="QUOTE"
+>"the requester"</SPAN
+>) is asking
+ someone else to set the flag to either <SPAN
+CLASS="QUOTE"
+>"+"</SPAN
+> or <SPAN
+CLASS="QUOTE"
+>"-"</SPAN
+>.
+ </P
+><P
+>&#13; If a flag has been defined as 'specifically requestable',
+ a text box will appear next to the flag into which the requester may
+ enter a Bugzilla username. That named person (a.k.a. <SPAN
+CLASS="QUOTE"
+>"the requestee"</SPAN
+>)
+ will receive an email notifying them of the request, and pointing them
+ to the bug/attachment in question.
+ </P
+><P
+>&#13; If a flag has <EM
+>not</EM
+> been defined as 'specifically requestable',
+ then no such text-box will appear. A request to set this flag cannot be made of
+ any specific individual, but must be asked <SPAN
+CLASS="QUOTE"
+>"to the wind"</SPAN
+>.
+ A requester may <SPAN
+CLASS="QUOTE"
+>"ask the wind"</SPAN
+> on any flag simply by leaving the text-box blank.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="flag-types"
+>3.8.4. Two Types of Flags</A
+></H3
+><P
+>&#13; Flags can go in two places: on an attachment, or on a bug.
+ </P
+><DIV
+CLASS="section"
+><HR><H4
+CLASS="section"
+><A
+NAME="flag-type-attachment"
+>3.8.4.1. Attachment Flags</A
+></H4
+><P
+>&#13; Attachment flags are used to ask a question about a specific
+ attachment on a bug.
+ </P
+><P
+>&#13; Many Bugzilla installations use this to
+ request that one developer <SPAN
+CLASS="QUOTE"
+>"review"</SPAN
+> another
+ developer's code before they check it in. They attach the code to
+ a bug report, and then set a flag on that attachment called
+ <SPAN
+CLASS="QUOTE"
+>"review"</SPAN
+> to
+ <SAMP
+CLASS="computeroutput"
+>review?boss@domain.com</SAMP
+>.
+ boss@domain.com is then notified by email that
+ he has to check out that attachment and approve it or deny it.
+ </P
+><P
+>&#13; For a Bugzilla user, attachment flags show up in three places:
+ <P
+></P
+><OL
+TYPE="1"
+><LI
+><P
+>&#13; On the list of attachments in the <SPAN
+CLASS="QUOTE"
+>"Show Bug"</SPAN
+>
+ screen, you can see the current state of any flags that
+ have been set to ?, +, or -. You can see who asked about
+ the flag (the requester), and who is being asked (the
+ requestee).
+ </P
+></LI
+><LI
+><P
+>&#13; When you <SPAN
+CLASS="QUOTE"
+>"Edit"</SPAN
+> an attachment, you can
+ see any settable flag, along with any flags that have
+ already been set. This <SPAN
+CLASS="QUOTE"
+>"Edit Attachment"</SPAN
+>
+ screen is where you set flags to ?, -, +, or unset them.
+ </P
+></LI
+><LI
+><P
+>&#13; Requests are listed in the <SPAN
+CLASS="QUOTE"
+>"Request Queue"</SPAN
+>, which
+ is accessible from the <SPAN
+CLASS="QUOTE"
+>"My Requests"</SPAN
+> link (if you are
+ logged in) or <SPAN
+CLASS="QUOTE"
+>"Requests"</SPAN
+> link (if you are logged out)
+ visible in the footer of all pages.
+ </P
+></LI
+></OL
+>
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H4
+CLASS="section"
+><A
+NAME="flag-type-bug"
+>3.8.4.2. Bug Flags</A
+></H4
+><P
+>&#13; Bug flags are used to set a status on the bug itself. You can
+ see Bug Flags in the <SPAN
+CLASS="QUOTE"
+>"Show Bug"</SPAN
+> and <SPAN
+CLASS="QUOTE"
+>"Requests"</SPAN
+>
+ screens, as described above.
+ </P
+><P
+>&#13; Only users with enough privileges (see below) may set flags on bugs.
+ This doesn't necessarily include the assignee, reporter, or users with the
+ <SAMP
+CLASS="computeroutput"
+>editbugs</SAMP
+> permission.
+ </P
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="flags-admin"
+>3.8.5. Administering Flags</A
+></H3
+><P
+>&#13; If you have the <SPAN
+CLASS="QUOTE"
+>"editcomponents"</SPAN
+> permission, you can
+ edit Flag Types from the main administration page. Clicking the
+ <SPAN
+CLASS="QUOTE"
+>"Flags"</SPAN
+> link will bring you to the <SPAN
+CLASS="QUOTE"
+>"Administer
+ Flag Types"</SPAN
+> page. Here, you can select whether you want
+ to create (or edit) a Bug flag, or an Attachment flag.
+ </P
+><P
+>&#13; No matter which you choose, the interface is the same, so we'll
+ just go over it once.
+ </P
+><DIV
+CLASS="section"
+><HR><H4
+CLASS="section"
+><A
+NAME="flags-edit"
+>3.8.5.1. Editing a Flag</A
+></H4
+><P
+>&#13; To edit a flag's properties, just click the flag's name.
+ That will take you to the same
+ form as described below (<A
+HREF="#flags-create"
+>Section 3.8.5.2</A
+>).
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H4
+CLASS="section"
+><A
+NAME="flags-create"
+>3.8.5.2. Creating a Flag</A
+></H4
+><P
+>&#13; When you click on the <SPAN
+CLASS="QUOTE"
+>"Create a Flag Type for..."</SPAN
+>
+ link, you will be presented with a form. Here is what the fields in
+ the form mean:
+ </P
+><DIV
+CLASS="section"
+><HR><H5
+CLASS="section"
+><A
+NAME="flags-create-field-name"
+>3.8.5.2.1. Name</A
+></H5
+><P
+>&#13; This is the name of the flag. This will be displayed
+ to Bugzilla users who are looking at or setting the flag.
+ The name may contain any valid Unicode characters except commas
+ and spaces.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H5
+CLASS="section"
+><A
+NAME="flags-create-field-description"
+>3.8.5.2.2. Description</A
+></H5
+><P
+>&#13; The description describes the flag in more detail. It is visible
+ in a tooltip when hovering over a flag either in the <SPAN
+CLASS="QUOTE"
+>"Show Bug"</SPAN
+>
+ or <SPAN
+CLASS="QUOTE"
+>"Edit Attachment"</SPAN
+> pages. This field can be as
+ long as you like, and can contain any character you want.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H5
+CLASS="section"
+><A
+NAME="flags-create-field-category"
+>3.8.5.2.3. Category</A
+></H5
+><P
+>&#13; Default behaviour for a newly-created flag is to appear on
+ products and all components, which is why <SPAN
+CLASS="QUOTE"
+>"__Any__:__Any__"</SPAN
+>
+ is already entered in the <SPAN
+CLASS="QUOTE"
+>"Inclusions"</SPAN
+> box.
+ If this is not your desired behaviour, you must either set some
+ exclusions (for products on which you don't want the flag to appear),
+ or you must remove <SPAN
+CLASS="QUOTE"
+>"__Any__:__Any__"</SPAN
+> from the Inclusions box
+ and define products/components specifically for this flag.
+ </P
+><P
+>&#13; To create an Inclusion, select a Product from the top drop-down box.
+ You may also select a specific component from the bottom drop-down box.
+ (Setting <SPAN
+CLASS="QUOTE"
+>"__Any__"</SPAN
+> for Product translates to,
+ <SPAN
+CLASS="QUOTE"
+>"all the products in this Bugzilla"</SPAN
+>.
+ Selecting <SPAN
+CLASS="QUOTE"
+>"__Any__"</SPAN
+> in the Component field means
+ <SPAN
+CLASS="QUOTE"
+>"all components in the selected product."</SPAN
+>)
+ Selections made, press <SPAN
+CLASS="QUOTE"
+>"Include"</SPAN
+>, and your
+ Product/Component pairing will show up in the <SPAN
+CLASS="QUOTE"
+>"Inclusions"</SPAN
+> box on the right.
+ </P
+><P
+>&#13; To create an Exclusion, the process is the same; select a Product from the
+ top drop-down box, select a specific component if you want one, and press
+ <SPAN
+CLASS="QUOTE"
+>"Exclude"</SPAN
+>. The Product/Component pairing will show up in the
+ <SPAN
+CLASS="QUOTE"
+>"Exclusions"</SPAN
+> box on the right.
+ </P
+><P
+>&#13; This flag <EM
+>will</EM
+> and <EM
+>can</EM
+> be set for any
+ products/components that appearing in the <SPAN
+CLASS="QUOTE"
+>"Inclusions"</SPAN
+> box
+ (or which fall under the appropriate <SPAN
+CLASS="QUOTE"
+>"__Any__"</SPAN
+>).
+ This flag <EM
+>will not</EM
+> appear (and therefore cannot be set) on
+ any products appearing in the <SPAN
+CLASS="QUOTE"
+>"Exclusions"</SPAN
+> box.
+ <EM
+> IMPORTANT: Exclusions override inclusions.</EM
+>
+ </P
+><P
+>&#13; You may select a Product without selecting a specific Component,
+ but you can't select a Component without a Product, or to select a
+ Component that does not belong to the named Product. If you do so,
+ Bugzilla will display an error message, even if all your products
+ have a component by that name.
+ </P
+><P
+><EM
+>Example:</EM
+> Let's say you have a product called
+ <SPAN
+CLASS="QUOTE"
+>"Jet Plane"</SPAN
+> that has thousands of components. You want
+ to be able to ask if a problem should be fixed in the next model of
+ plane you release. We'll call the flag <SPAN
+CLASS="QUOTE"
+>"fixInNext"</SPAN
+>.
+ But, there's one component in <SPAN
+CLASS="QUOTE"
+>"Jet Plane,"</SPAN
+>
+ called <SPAN
+CLASS="QUOTE"
+>"Pilot."</SPAN
+> It doesn't make sense to release a
+ new pilot, so you don't want to have the flag show up in that component.
+ So, you include <SPAN
+CLASS="QUOTE"
+>"Jet Plane:__Any__"</SPAN
+> and you exclude
+ <SPAN
+CLASS="QUOTE"
+>"Jet Plane:Pilot"</SPAN
+>.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H5
+CLASS="section"
+><A
+NAME="flags-create-field-sortkey"
+>3.8.5.2.4. Sort Key</A
+></H5
+><P
+>&#13; Flags normally show up in alphabetical order. If you want them to
+ show up in a different order, you can use this key set the order on each flag.
+ Flags with a lower sort key will appear before flags with a higher
+ sort key. Flags that have the same sort key will be sorted alphabetically,
+ but they will still be after flags with a lower sort key, and before flags
+ with a higher sort key.
+ </P
+><P
+>&#13; <EM
+>Example:</EM
+> I have AFlag (Sort Key 100), BFlag (Sort Key 10),
+ CFlag (Sort Key 10), and DFlag (Sort Key 1). These show up in
+ the order: DFlag, BFlag, CFlag, AFlag.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H5
+CLASS="section"
+><A
+NAME="flags-create-field-active"
+>3.8.5.2.5. Active</A
+></H5
+><P
+>&#13; Sometimes, you might want to keep old flag information in the
+ Bugzilla database, but stop users from setting any new flags of this type.
+ To do this, uncheck <SPAN
+CLASS="QUOTE"
+>"active"</SPAN
+>. Deactivated
+ flags will still show up in the UI if they are ?, +, or -, but they
+ may only be cleared (unset), and cannot be changed to a new value.
+ Once a deactivated flag is cleared, it will completely disappear from a
+ bug/attachment, and cannot be set again.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H5
+CLASS="section"
+><A
+NAME="flags-create-field-requestable"
+>3.8.5.2.6. Requestable</A
+></H5
+><P
+>&#13; New flags are, by default, <SPAN
+CLASS="QUOTE"
+>"requestable"</SPAN
+>, meaning that they
+ offer users the <SPAN
+CLASS="QUOTE"
+>"?"</SPAN
+> option, as well as <SPAN
+CLASS="QUOTE"
+>"+"</SPAN
+>
+ and <SPAN
+CLASS="QUOTE"
+>"-"</SPAN
+>.
+ To remove the ? option, uncheck <SPAN
+CLASS="QUOTE"
+>"requestable"</SPAN
+>.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H5
+CLASS="section"
+><A
+NAME="flags-create-field-specific"
+>3.8.5.2.7. Specifically Requestable</A
+></H5
+><P
+>&#13; By default this box is checked for new flags, meaning that users may make
+ flag requests of specific individuals. Unchecking this box will remove the
+ text box next to a flag; if it is still requestable, then requests may
+ only be made <SPAN
+CLASS="QUOTE"
+>"to the wind."</SPAN
+> Removing this after specific
+ requests have been made will not remove those requests; that data will
+ stay in the database (though it will no longer appear to the user).
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H5
+CLASS="section"
+><A
+NAME="flags-create-field-multiplicable"
+>3.8.5.2.8. Multiplicable</A
+></H5
+><P
+>&#13; Any flag with <SPAN
+CLASS="QUOTE"
+>"Multiplicable"</SPAN
+> set (default for new flags is 'on')
+ may be set more than once. After being set once, an unset flag
+ of the same type will appear below it with <SPAN
+CLASS="QUOTE"
+>"addl."</SPAN
+> (short for
+ <SPAN
+CLASS="QUOTE"
+>"additional"</SPAN
+>) before the name. There is no limit to the number of
+ times a Multiplicable flags may be set on the same bug/attachment.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H5
+CLASS="section"
+><A
+NAME="flags-create-field-cclist"
+>3.8.5.2.9. CC List</A
+></H5
+><P
+>&#13; If you want certain users to be notified every time this flag is
+ set to ?, -, +, or unset, add them here. This is a comma-separated
+ list of email addresses that need not be restricted to Bugzilla usernames.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H5
+CLASS="section"
+><A
+NAME="flags-create-grant-group"
+>3.8.5.2.10. Grant Group</A
+></H5
+><P
+>&#13; When this field is set to some given group, only users in the group
+ can set the flag to <SPAN
+CLASS="QUOTE"
+>"+"</SPAN
+> and <SPAN
+CLASS="QUOTE"
+>"-"</SPAN
+>. This
+ field does not affect who can request or cancel the flag. For that,
+ see the <SPAN
+CLASS="QUOTE"
+>"Request Group"</SPAN
+> field below. If this field
+ is left blank, all users can set or delete this flag. This field is
+ useful for restricting which users can approve or reject requests.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H5
+CLASS="section"
+><A
+NAME="flags-create-request-group"
+>3.8.5.2.11. Request Group</A
+></H5
+><P
+>&#13; When this field is set to some given group, only users in the group
+ can request or cancel this flag. Note that this field has no effect
+ if the <SPAN
+CLASS="QUOTE"
+>"grant group"</SPAN
+> field is empty. You can set the
+ value of this field to a different group, but both fields have to be
+ set to a group for this field to have an effect.
+ </P
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H4
+CLASS="section"
+><A
+NAME="flags-delete"
+>3.8.5.3. Deleting a Flag</A
+></H4
+><P
+>&#13; When you are at the <SPAN
+CLASS="QUOTE"
+>"Administer Flag Types"</SPAN
+> screen,
+ you will be presented with a list of Bug flags and a list of Attachment
+ Flags.
+ </P
+><P
+>&#13; To delete a flag, click on the <SPAN
+CLASS="QUOTE"
+>"Delete"</SPAN
+> link next to
+ the flag description.
+ </P
+><DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Once you delete a flag, it is <EM
+>gone</EM
+> from
+ your Bugzilla. All the data for that flag will be deleted.
+ Everywhere that flag was set, it will disappear,
+ and you cannot get that data back. If you want to keep flag data,
+ but don't want anybody to set any new flags or change current flags,
+ unset <SPAN
+CLASS="QUOTE"
+>"active"</SPAN
+> in the flag Edit form.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="keywords"
+>3.9. Keywords</A
+></H2
+><P
+>&#13; The administrator can define keywords which can be used to tag and
+ categorise bugs. For example, the keyword "regression" is commonly used.
+ A company might have a policy stating all regressions
+ must be fixed by the next release - this keyword can make tracking those
+ bugs much easier.
+ </P
+><P
+>&#13; Keywords are global, rather than per-product. If the administrator changes
+ a keyword currently applied to any bugs, the keyword cache must be rebuilt
+ using the <A
+HREF="#sanitycheck"
+>Section 3.16</A
+> script. Currently keywords can not
+ be marked obsolete to prevent future usage.
+ </P
+><P
+>&#13; Keywords can be created, edited or deleted by clicking the "Keywords"
+ link in the admin page. There are two fields for each keyword - the keyword
+ itself and a brief description. Once created, keywords can be selected
+ and applied to individual bugs in that bug's "Details" section.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="custom-fields"
+>3.10. Custom Fields</A
+></H2
+><P
+>&#13; The release of Bugzilla 3.0 added the ability to create Custom Fields.
+ Custom Fields are treated like any other field - they can be set in bugs
+ and used for search queries. Administrators should keep in mind that
+ adding too many fields can make the user interface more complicated and
+ harder to use. Custom Fields should be added only when necessary and with
+ careful consideration.
+ </P
+><DIV
+CLASS="tip"
+><P
+></P
+><TABLE
+CLASS="tip"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/tip.gif"
+HSPACE="5"
+ALT="Tip"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Before adding a Custom Field, make sure that Bugzilla can not already
+ do the desired behavior. Many Bugzilla options are not enabled by
+ default, and many times Administrators find that simply enabling
+ certain options that already exist is sufficient.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; Administrators can manage Custom Fields using the
+ <SPAN
+CLASS="QUOTE"
+>"Custom Fields"</SPAN
+> link on the Administration page. The Custom
+ Fields administration page displays a list of Custom Fields, if any exist,
+ and a link to "Add a new custom field".
+ </P
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="add-custom-fields"
+>3.10.1. Adding Custom Fields</A
+></H3
+><P
+>&#13; To add a new Custom Field, click the "Add a new custom field" link. This
+ page displays several options for the new field, described below.
+ </P
+><P
+>&#13; The following attributes must be set for each new custom field:
+ <P
+></P
+><UL
+><LI
+><P
+>&#13; <EM
+>Name:</EM
+>
+ The name of the field in the database, used internally. This name
+ MUST begin with <SPAN
+CLASS="QUOTE"
+>"cf_"</SPAN
+> to prevent confusion with
+ standard fields. If this string is omitted, it will
+ be automatically added to the name entered.
+ </P
+></LI
+><LI
+><P
+>&#13; <EM
+>Description:</EM
+>
+ A brief string which is used as the label for this Custom Field.
+ That is the string that users will see, and should be
+ short and explicit.
+ </P
+></LI
+><LI
+><P
+>&#13; <EM
+>Type:</EM
+>
+ The type of field to create. There are
+ several types available:
+ <P
+></P
+><TABLE
+BORDER="0"
+><TBODY
+><TR
+><TD
+>&#13; Large Text Box: A multiple line box for entering free text.
+ </TD
+></TR
+><TR
+><TD
+>&#13; Free Text: A single line box for entering free text.
+ </TD
+></TR
+><TR
+><TD
+>&#13; Multiple-Selection Box: A list box where multiple options
+ can be selected. After creating this field, it must be edited
+ to add the selection options. See
+ <A
+HREF="#edit-values-list"
+>Section 3.11.1</A
+> for information about
+ editing legal values.
+ </TD
+></TR
+><TR
+><TD
+>&#13; Drop Down: A list box where only one option can be selected.
+ After creating this field, it must be edited to add the
+ selection options. See
+ <A
+HREF="#edit-values-list"
+>Section 3.11.1</A
+> for information about
+ editing legal values.
+ </TD
+></TR
+><TR
+><TD
+>&#13; Date/Time: A date field. This field appears with a
+ calendar widget for choosing the date.
+ </TD
+></TR
+></TBODY
+></TABLE
+><P
+></P
+>
+ </P
+></LI
+><LI
+><P
+>&#13; <EM
+>Sortkey:</EM
+>
+ Integer that determines in which order Custom Fields are
+ displayed in the User Interface, especially when viewing a bug.
+ Fields with lower values are displayed first.
+ </P
+></LI
+><LI
+><P
+>&#13; <EM
+>Can be set on bug creation:</EM
+>
+ Boolean that determines whether this field can be set on
+ bug creation. If not selected, then a bug must be created
+ before this field can be set. See <A
+HREF="#bugreports"
+>Section 5.6</A
+>
+ for information about filing bugs.
+ </P
+></LI
+><LI
+><P
+>&#13; <EM
+>Displayed in bugmail for new bugs:</EM
+>
+ Boolean that determines whether the value set on this field
+ should appear in bugmail when the bug is filed. This attribute
+ has no effect if the field cannot be set on bug creation.
+ </P
+></LI
+><LI
+><P
+>&#13; <EM
+>Is obsolete:</EM
+>
+ Boolean that determines whether this field should
+ be displayed at all. Obsolete Custom Fields are hidden.
+ </P
+></LI
+></UL
+>
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="edit-custom-fields"
+>3.10.2. Editing Custom Fields</A
+></H3
+><P
+>&#13; As soon as a Custom Field is created, its name and type cannot be
+ changed. If this field is a drop down menu, its legal values can
+ be set as described in <A
+HREF="#edit-values-list"
+>Section 3.11.1</A
+>. All
+ other attributes can be edited as described above.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="delete-custom-fields"
+>3.10.3. Deleting Custom Fields</A
+></H3
+><P
+>&#13; Only custom fields which are marked as obsolete, and which never
+ have been used, can be deleted completely (else the integrity
+ of the bug history would be compromised). For custom fields marked
+ as obsolete, a "Delete" link will appear in the <SPAN
+CLASS="QUOTE"
+>"Action"</SPAN
+>
+ column. If the custom field has been used in the past, the deletion
+ will be rejected. But marking the field as obsolete is sufficient
+ to hide it from the user interface entirely.
+ </P
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="edit-values"
+>3.11. Legal Values</A
+></H2
+><P
+>&#13; Since Bugzilla 2.20 RC1, legal values for Operating Systems, platforms,
+ bug priorities and severities can be edited from the User Interface
+ directly. This means that it is no longer required to manually edit
+ <TT
+CLASS="filename"
+>localconfig</TT
+>. Starting with Bugzilla 2.23.3,
+ the list of valid resolutions can be customized from the same interface.
+ Since Bugzilla 3.1.1 the list of valid bug statuses can be customized
+ as well.
+ </P
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="edit-values-list"
+>3.11.1. Viewing/Editing legal values</A
+></H3
+><P
+>&#13; Editing legal values requires <SPAN
+CLASS="QUOTE"
+>"admin"</SPAN
+> privileges.
+ Select "Legal Values" from the Administration page. A list of all
+ fields, both system fields and Custom Fields, for which legal values
+ can be edited appears. Click a field name to edit its legal values.
+ </P
+><P
+>&#13; There is no limit to how many values a field can have, but each value
+ must be unique to that field. The sortkey is important to display these
+ values in the desired order.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="edit-values-delete"
+>3.11.2. Deleting legal values</A
+></H3
+><P
+>&#13; Legal values from Custom Fields can be deleted, but only if the
+ following two conditions are respected:
+ </P
+><P
+></P
+><OL
+TYPE="1"
+><LI
+><P
+>The value is not used by default for the field.</P
+></LI
+><LI
+><P
+>No bug is currently using this value.</P
+></LI
+></OL
+><P
+>&#13; If any of these conditions is not respected, the value cannot be deleted.
+ The only way to delete these values is to reassign bugs to another value
+ and to set another value as default for the field.
+ </P
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="bug_status_workflow"
+>3.12. Bug Status Workflow</A
+></H2
+><P
+>&#13; The bug status workflow is no longer hardcoded but can be freely customized
+ from the web interface. Only one bug status cannot be renamed nor deleted,
+ UNCONFIRMED, but the workflow involving it is free. The configuration
+ page displays all existing bug statuses twice, first on the left for bug
+ statuses we come from and on the top for bug statuses we move to.
+ If the checkbox is checked, then the transition between the two bug statuses
+ is legal, else it's forbidden independently of your privileges. The bug status
+ used for the "duplicate_or_move_bug_status" parameter must be part of the
+ workflow as that is the bug status which will be used when duplicating or
+ moving a bug, so it must be available from each bug status.
+ </P
+><P
+>&#13; When the workflow is set, the "View Current Triggers" link below the table
+ lets you set which transitions require a comment from the user.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="voting"
+>3.13. Voting</A
+></H2
+><P
+>Voting allows users to be given a pot of votes which they can allocate
+ to bugs, to indicate that they'd like them fixed.
+ This allows developers to gauge
+ user need for a particular enhancement or bugfix. By allowing bugs with
+ a certain number of votes to automatically move from "UNCONFIRMED" to
+ "CONFIRMED", users of the bug system can help high-priority bugs garner
+ attention so they don't sit for a long time awaiting triage.</P
+><P
+>To modify Voting settings:</P
+><P
+></P
+><OL
+TYPE="1"
+><LI
+><P
+>Navigate to the "Edit product" screen for the Product you
+ wish to modify</P
+></LI
+><LI
+><P
+><EM
+>Maximum Votes per person</EM
+>:
+ Setting this field to "0" disables voting.</P
+></LI
+><LI
+><P
+><EM
+>Maximum Votes a person can put on a single
+ bug</EM
+>:
+ It should probably be some number lower than the
+ "Maximum votes per person". Don't set this field to "0" if
+ "Maximum votes per person" is non-zero; that doesn't make
+ any sense.</P
+></LI
+><LI
+><P
+><EM
+>Number of votes a bug in this product needs to
+ automatically get out of the UNCONFIRMED state</EM
+>:
+ Setting this field to "0" disables the automatic move of
+ bugs from UNCONFIRMED to CONFIRMED.
+ </P
+></LI
+><LI
+><P
+>Once you have adjusted the values to your preference, click
+ "Update".</P
+></LI
+></OL
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="quips"
+>3.14. Quips</A
+></H2
+><P
+>&#13; Quips are small text messages that can be configured to appear
+ next to search results. A Bugzilla installation can have its own specific
+ quips. Whenever a quip needs to be displayed, a random selection
+ is made from the pool of already existing quips.
+ </P
+><P
+>&#13; Quips are controlled by the <EM
+>enablequips</EM
+> parameter.
+ It has several possible values: on, approved, frozen or off.
+ In order to enable quips approval you need to set this parameter
+ to "approved". In this way, users are free to submit quips for
+ addition but an administrator must explicitly approve them before
+ they are actually used.
+ </P
+><P
+>&#13; In order to see the user interface for the quips, it is enough to click
+ on a quip when it is displayed together with the search results. Or
+ it can be seen directly in the browser by visiting the quips.cgi URL
+ (prefixed with the usual web location of the Bugzilla installation).
+ Once the quip interface is displayed, it is enough to click the
+ "view and edit the whole quip list" in order to see the administration
+ page. A page with all the quips available in the database will
+ be displayed.
+ </P
+><P
+>&#13; Next to each tip there is a checkbox, under the
+ "Approved" column. Quips who have this checkbox checked are
+ already approved and will appear next to the search results.
+ The ones that have it unchecked are still preserved in the
+ database but they will not appear on search results pages.
+ User submitted quips have initially the checkbox unchecked.
+ </P
+><P
+>&#13; Also, there is a delete link next to each quip,
+ which can be used in order to permanently delete a quip.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="groups"
+>3.15. Groups and Group Security</A
+></H2
+><P
+>&#13; Groups allow for separating bugs into logical divisions.
+ Groups are typically used
+ to isolate bugs that should only be seen by certain people. For
+ example, a company might create a different group for each one of its customers
+ or partners. Group permissions could be set so that each partner or customer would
+ only have access to their own bugs. Or, groups might be used to create
+ variable access controls for different departments within an organization.
+ Another common use of groups is to associate groups with products,
+ creating isolation and access control on a per-product basis.
+ </P
+><P
+>&#13; Groups and group behaviors are controlled in several places:
+ </P
+><P
+></P
+><OL
+TYPE="1"
+><LI
+><P
+>&#13; The group configuration page. To view or edit existing groups, or to
+ create new groups, access the "Groups" link from the "Administration"
+ page. This section of the manual deals primarily with the aspect of
+ group controls accessed on this page.
+ </P
+></LI
+><LI
+><P
+>&#13; Global configuration parameters. Bugzilla has several parameters
+ that control the overall default group behavior and restriction
+ levels. For more information on the parameters that control
+ group behavior globally, see <A
+HREF="#param-group-security"
+>Section 3.1.9</A
+>.
+ </P
+></LI
+><LI
+><P
+>&#13; Product association with groups. Most of the functionality of groups
+ and group security is controlled at the product level. Some aspects
+ of group access controls for products are discussed in this section,
+ but for more detail see <A
+HREF="#product-group-controls"
+>Section 3.4.4</A
+>.
+ </P
+></LI
+><LI
+><P
+>&#13; Group access for users. See <A
+HREF="#users-and-groups"
+>Section 3.15.3</A
+> for
+ details on how users are assigned group access.
+ </P
+></LI
+></OL
+><P
+>&#13; Group permissions are such that if a bug belongs to a group, only members
+ of that group can see the bug. If a bug is in more than one group, only
+ members of <EM
+>all</EM
+> the groups that the bug is in can see
+ the bug. For information on granting read-only access to certain people and
+ full edit access to others, see <A
+HREF="#product-group-controls"
+>Section 3.4.4</A
+>.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; By default, bugs can also be seen by the Assignee, the Reporter, and
+ by everyone on the CC List, regardless of whether or not the bug would
+ typically be viewable by them. Visibility to the Reporter and CC List can
+ be overridden (on a per-bug basis) by bringing up the bug, finding the
+ section that starts with <SPAN
+CLASS="QUOTE"
+>"Users in the roles selected below..."</SPAN
+>
+ and un-checking the box next to either 'Reporter' or 'CC List' (or both).
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="create-groups"
+>3.15.1. Creating Groups</A
+></H3
+><P
+>&#13; To create a new group, follow the steps below:
+ </P
+><P
+></P
+><OL
+TYPE="1"
+><LI
+><P
+>&#13; Select the <SPAN
+CLASS="QUOTE"
+>"Administration"</SPAN
+> link in the page footer,
+ and then select the <SPAN
+CLASS="QUOTE"
+>"Groups"</SPAN
+> link from the
+ Administration page.
+ </P
+></LI
+><LI
+><P
+>&#13; A table of all the existing groups is displayed. Below the table is a
+ description of all the fields. To create a new group, select the
+ <SPAN
+CLASS="QUOTE"
+>"Add Group"</SPAN
+> link under the table of existing groups.
+ </P
+></LI
+><LI
+><P
+>&#13; There are five fields to fill out. These fields are documented below
+ the form. Choose a name and description for the group. Decide whether
+ this group should be used for bugs (in all likelihood this should be
+ selected). Optionally, choose a regular expression that will
+ automatically add any matching users to the group, and choose an
+ icon that will help identify user comments for the group. The regular
+ expression can be useful, for example, to automatically put all users
+ from the same company into one group (if the group is for a specific
+ customer or partner).
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; If <SPAN
+CLASS="QUOTE"
+>"User RegExp"</SPAN
+> is filled out, users whose email
+ addresses match the regular expression will automatically be
+ members of the group as long as their email addresses continue
+ to match the regular expression. If their email address changes
+ and no longer matches the regular expression, they will be removed
+ from the group. Versions 2.16 and older of Bugzilla did not automatically
+ remove users who's email addresses no longer matched the RegExp.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; If specifying a domain in the regular expression, end
+ the regexp with a "$". Otherwise, when granting access to
+ "@mycompany\.com", access will also be granted to
+ 'badperson@mycompany.com.cracker.net'. Use the syntax,
+ '@mycompany\.com$' for the regular expression.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></LI
+><LI
+><P
+>&#13; After the new group is created, it can be edited for additional options.
+ The "Edit Group" page allows for specifying other groups that should be included
+ in this group and which groups should be permitted to add and delete
+ users from this group. For more details, see <A
+HREF="#edit-groups"
+>Section 3.15.2</A
+>.
+ </P
+></LI
+></OL
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="edit-groups"
+>3.15.2. Editing Groups and Assigning Group Permissions</A
+></H3
+><P
+>&#13; To access the "Edit Groups" page, select the
+ <SPAN
+CLASS="QUOTE"
+>"Administration"</SPAN
+> link in the page footer,
+ and then select the <SPAN
+CLASS="QUOTE"
+>"Groups"</SPAN
+> link from the Administration page.
+ A table of all the existing groups is displayed. Click on a group name
+ you wish to edit or control permissions for.
+ </P
+><P
+>&#13; The "Edit Groups" page contains the same five fields present when
+ creating a new group. Below that are two additional sections, "Group
+ Permissions," and "Mass Remove". The "Mass Remove" option simply removes
+ all users from the group who match the regular expression entered. The
+ "Group Permissions" section requires further explanation.
+ </P
+><P
+>&#13; The "Group Permissions" section on the "Edit Groups" page contains four sets
+ of permissions that control the relationship of this group to other
+ groups. If the 'usevisibilitygroups' parameter is in use (see
+ <A
+HREF="#parameters"
+>Section 3.1</A
+>) two additional sets of permissions are displayed.
+ Each set consists of two select boxes. On the left, a select box
+ with a list of all existing groups. On the right, a select box listing
+ all groups currently selected for this permission setting (this box will
+ be empty for new groups). The way these controls allow groups to relate
+ to one another is called <EM
+>inheritance</EM
+>.
+ Each of the six permissions is described below.
+ </P
+><P
+></P
+><DIV
+CLASS="variablelist"
+><DL
+><DT
+><EM
+>Groups That Are a Member of This Group</EM
+></DT
+><DD
+><P
+>
+ Members of any groups selected here will automatically have
+ membership in this group. In other words, members of any selected
+ group will inherit membership in this group.
+ </P
+></DD
+><DT
+><EM
+>Groups That This Group Is a Member Of</EM
+></DT
+><DD
+><P
+>&#13; Members of this group will inherit membership to any group
+ selected here. For example, suppose the group being edited is
+ an Admin group. If there are two products (Product1 and Product2)
+ and each product has its
+ own group (Group1 and Group2), and the Admin group
+ should have access to both products,
+ simply select both Group1 and Group2 here.
+ </P
+></DD
+><DT
+><EM
+>Groups That Can Grant Membership in This Group</EM
+></DT
+><DD
+><P
+>&#13; The members of any group selected here will be able add users
+ to this group, even if they themselves are not in this group.
+ </P
+></DD
+><DT
+><EM
+>Groups That This Group Can Grant Membership In</EM
+></DT
+><DD
+><P
+>&#13; Members of this group can add users to any group selected here,
+ even if they themselves are not in the selected groups.
+ </P
+></DD
+><DT
+><EM
+>Groups That Can See This Group</EM
+></DT
+><DD
+><P
+>&#13; Members of any selected group can see the users in this group.
+ This setting is only visible if the 'usevisibilitygroups' parameter
+ is enabled on the Bugzilla Configuration page. See
+ <A
+HREF="#parameters"
+>Section 3.1</A
+> for information on configuring Bugzilla.
+ </P
+></DD
+><DT
+><EM
+>Groups That This Group Can See</EM
+></DT
+><DD
+><P
+>&#13; Members of this group can see members in any of the selected groups.
+ This setting is only visible if the 'usevisibilitygroups' parameter
+ is enabled on the the Bugzilla Configuration page. See
+ <A
+HREF="#parameters"
+>Section 3.1</A
+> for information on configuring Bugzilla.
+ </P
+></DD
+></DL
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="users-and-groups"
+>3.15.3. Assigning Users to Groups</A
+></H3
+><P
+>&#13; A User can become a member of a group in several ways:
+ </P
+><P
+></P
+><OL
+TYPE="1"
+><LI
+><P
+>&#13; The user can be explicitly placed in the group by editing
+ the user's profile. This can be done by accessing the "Users" page
+ from the "Administration" page. Use the search form to find the user
+ you want to edit group membership for, and click on their email
+ address in the search results to edit their profile. The profile
+ page lists all the groups, and indicates if the user is a member of
+ the group either directly or indirectly. More information on indirect
+ group membership is below. For more details on User administration,
+ see <A
+HREF="#useradmin"
+>Section 3.2</A
+>.
+ </P
+></LI
+><LI
+><P
+>&#13; The group can include another group of which the user is
+ a member. This is indicated by square brackets around the checkbox
+ next to the group name in the user's profile.
+ See <A
+HREF="#edit-groups"
+>Section 3.15.2</A
+> for details on group inheritance.
+ </P
+></LI
+><LI
+><P
+>&#13; The user's email address can match the regular expression
+ that has been specified to automatically grant membership to
+ the group. This is indicated by "*" around the check box by the
+ group name in the user's profile.
+ See <A
+HREF="#create-groups"
+>Section 3.15.1</A
+> for details on
+ the regular expression option when creating groups.
+ </P
+></LI
+></OL
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="AEN2110"
+>3.15.4. Assigning Group Controls to Products</A
+></H3
+><P
+>&#13; The primary functionality of groups is derived from the relationship of
+ groups to products. The concepts around segregating access to bugs with
+ product group controls can be confusing. For details and examples on this
+ topic, see <A
+HREF="#product-group-controls"
+>Section 3.4.4</A
+>.
+ </P
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="sanitycheck"
+>3.16. Checking and Maintaining Database Integrity</A
+></H2
+><P
+>&#13; Over time it is possible for the Bugzilla database to become corrupt
+ or to have anomalies.
+ This could happen through normal usage of Bugzilla, manual database
+ administration outside of the Bugzilla user interface, or from some
+ other unexpected event. Bugzilla includes a "Sanity Check" script that
+ can perform several basic database checks, and repair certain problems or
+ inconsistencies.
+ </P
+><P
+>&#13; To run the "Sanity Check" script, log in as an Administrator and click the
+ "Sanity Check" link in the admin page. Any problems that are found will be
+ displayed in red letters. If the script is capable of fixing a problem,
+ it will present a link to initiate the fix. If the script can not
+ fix the problem it will require manual database administration or recovery.
+ </P
+><P
+>&#13; The "Sanity Check" script can also be run from the command line via the perl
+ script <TT
+CLASS="filename"
+>sanitycheck.pl</TT
+>. The script can also be run as
+ a <B
+CLASS="command"
+>cron</B
+> job. Results will be delivered by email.
+ </P
+><P
+>&#13; The "Sanity Check" script should be run on a regular basis as a matter of
+ best practice.
+ </P
+><DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; The "Sanity Check" script is no substitute for a competent database
+ administrator. It is only designed to check and repair basic database
+ problems.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+></DIV
+><DIV
+CLASS="chapter"
+><HR><H1
+><A
+NAME="security"
+></A
+>Chapter 4. Bugzilla Security</H1
+><P
+>While some of the items in this chapter are related to the operating
+ system Bugzilla is running on or some of the support software required to
+ run Bugzilla, it is all related to protecting your data. This is not
+ intended to be a comprehensive guide to securing Linux, Apache, MySQL, or
+ any other piece of software mentioned. There is no substitute for active
+ administration and monitoring of a machine. The key to good security is
+ actually right in the middle of the word: <EM
+>U R It</EM
+>.
+ </P
+><P
+>While programmers in general always strive to write secure code,
+ accidents can and do happen. The best approach to security is to always
+ assume that the program you are working with isn't 100% secure and restrict
+ its access to other parts of your machine as much as possible.
+ </P
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="security-os"
+>4.1. Operating System</A
+></H2
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="security-os-ports"
+>4.1.1. TCP/IP Ports</A
+></H3
+><P
+>The TCP/IP standard defines more than 65,000 ports for sending
+ and receiving traffic. Of those, Bugzilla needs exactly one to operate
+ (different configurations and options may require up to 3). You should
+ audit your server and make sure that you aren't listening on any ports
+ you don't need to be. It's also highly recommended that the server
+ Bugzilla resides on, along with any other machines you administer, be
+ placed behind some kind of firewall.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="security-os-accounts"
+>4.1.2. System User Accounts</A
+></H3
+><P
+>Many <A
+HREF="#gloss-daemon"
+><I
+CLASS="glossterm"
+>daemons</I
+></A
+>, such
+ as Apache's <TT
+CLASS="filename"
+>httpd</TT
+> or MySQL's
+ <TT
+CLASS="filename"
+>mysqld</TT
+>, run as either <SPAN
+CLASS="QUOTE"
+>"root"</SPAN
+> or
+ <SPAN
+CLASS="QUOTE"
+>"nobody"</SPAN
+>. This is even worse on Windows machines where the
+ majority of <A
+HREF="#gloss-service"
+><I
+CLASS="glossterm"
+>services</I
+></A
+>
+ run as <SPAN
+CLASS="QUOTE"
+>"SYSTEM"</SPAN
+>. While running as <SPAN
+CLASS="QUOTE"
+>"root"</SPAN
+> or
+ <SPAN
+CLASS="QUOTE"
+>"SYSTEM"</SPAN
+> introduces obvious security concerns, the
+ problems introduced by running everything as <SPAN
+CLASS="QUOTE"
+>"nobody"</SPAN
+> may
+ not be so obvious. Basically, if you run every daemon as
+ <SPAN
+CLASS="QUOTE"
+>"nobody"</SPAN
+> and one of them gets compromised it can
+ compromise every other daemon running as <SPAN
+CLASS="QUOTE"
+>"nobody"</SPAN
+> on your
+ machine. For this reason, it is recommended that you create a user
+ account for each daemon.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>You will need to set the <CODE
+CLASS="option"
+>webservergroup</CODE
+> option
+ in <TT
+CLASS="filename"
+>localconfig</TT
+> to the group your web server runs
+ as. This will allow <TT
+CLASS="filename"
+>./checksetup.pl</TT
+> to set file
+ permissions on Unix systems so that nothing is world-writable.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="security-os-chroot"
+>4.1.3. The <TT
+CLASS="filename"
+>chroot</TT
+> Jail</A
+></H3
+><P
+>&#13; If your system supports it, you may wish to consider running
+ Bugzilla inside of a <TT
+CLASS="filename"
+>chroot</TT
+> jail. This option
+ provides unprecedented security by restricting anything running
+ inside the jail from accessing any information outside of it. If you
+ wish to use this option, please consult the documentation that came
+ with your system.
+ </P
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="security-webserver"
+>4.2. Web server</A
+></H2
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="security-webserver-access"
+>4.2.1. Disabling Remote Access to Bugzilla Configuration Files</A
+></H3
+><P
+>&#13; There are many files that are placed in the Bugzilla directory
+ area that should not be accessible from the web server. Because of the way
+ Bugzilla is currently layed out, the list of what should and should not
+ be accessible is rather complicated. A quick way is to run
+ <TT
+CLASS="filename"
+>testserver.pl</TT
+> to check if your web server serves
+ Bugzilla files as expected. If not, you may want to follow the few
+ steps below.
+ </P
+><DIV
+CLASS="tip"
+><P
+></P
+><TABLE
+CLASS="tip"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/tip.gif"
+HSPACE="5"
+ALT="Tip"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>Bugzilla ships with the ability to create
+ <A
+HREF="#gloss-htaccess"
+><I
+CLASS="glossterm"
+><TT
+CLASS="filename"
+>.htaccess</TT
+></I
+></A
+>
+ files that enforce these rules. Instructions for enabling these
+ directives in Apache can be found in <A
+HREF="#http-apache"
+>Section 2.2.4.1</A
+>
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+></P
+><UL
+COMPACT="COMPACT"
+><LI
+><P
+>In the main Bugzilla directory, you should:</P
+><P
+></P
+><UL
+COMPACT="COMPACT"
+><LI
+><P
+>Block:
+ <TT
+CLASS="filename"
+>*.pl</TT
+>, <TT
+CLASS="filename"
+>*localconfig*</TT
+>
+ </P
+></LI
+></UL
+></LI
+><LI
+><P
+>In <TT
+CLASS="filename"
+>data</TT
+>:</P
+><P
+></P
+><UL
+COMPACT="COMPACT"
+><LI
+><P
+>Block everything</P
+></LI
+></UL
+></LI
+><LI
+><P
+>In <TT
+CLASS="filename"
+>data/webdot</TT
+>:</P
+><P
+></P
+><UL
+COMPACT="COMPACT"
+><LI
+><P
+>If you use a remote webdot server:</P
+><P
+></P
+><UL
+COMPACT="COMPACT"
+><LI
+><P
+>Block everything</P
+></LI
+><LI
+><P
+>But allow
+ <TT
+CLASS="filename"
+>*.dot</TT
+>
+ only for the remote webdot server</P
+></LI
+></UL
+></LI
+><LI
+><P
+>Otherwise, if you use a local GraphViz:</P
+><P
+></P
+><UL
+COMPACT="COMPACT"
+><LI
+><P
+>Block everything</P
+></LI
+><LI
+><P
+>But allow:
+ <TT
+CLASS="filename"
+>*.png</TT
+>, <TT
+CLASS="filename"
+>*.gif</TT
+>, <TT
+CLASS="filename"
+>*.jpg</TT
+>, <TT
+CLASS="filename"
+>*.map</TT
+>
+ </P
+></LI
+></UL
+></LI
+><LI
+><P
+>And if you don't use any dot:</P
+><P
+></P
+><UL
+COMPACT="COMPACT"
+><LI
+><P
+>Block everything</P
+></LI
+></UL
+></LI
+></UL
+></LI
+><LI
+><P
+>In <TT
+CLASS="filename"
+>Bugzilla</TT
+>:</P
+><P
+></P
+><UL
+COMPACT="COMPACT"
+><LI
+><P
+>Block everything</P
+></LI
+></UL
+></LI
+><LI
+><P
+>In <TT
+CLASS="filename"
+>template</TT
+>:</P
+><P
+></P
+><UL
+COMPACT="COMPACT"
+><LI
+><P
+>Block everything</P
+></LI
+></UL
+></LI
+></UL
+><P
+>Be sure to test that data that should not be accessed remotely is
+ properly blocked. Of particular interest is the localconfig file which
+ contains your database password. Also, be aware that many editors
+ create temporary and backup files in the working directory and that
+ those should also not be accessible. For more information, see
+ <A
+HREF="http://bugzilla.mozilla.org/show_bug.cgi?id=186383"
+TARGET="_top"
+>bug 186383</A
+>
+ or
+ <A
+HREF="http://online.securityfocus.com/bid/6501"
+TARGET="_top"
+>Bugtraq ID 6501</A
+>.
+ To test, simply run <TT
+CLASS="filename"
+>testserver.pl</TT
+>, as said above.
+ </P
+><DIV
+CLASS="tip"
+><P
+></P
+><TABLE
+CLASS="tip"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/tip.gif"
+HSPACE="5"
+ALT="Tip"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>Be sure to check <A
+HREF="#http"
+>Section 2.2.4</A
+> for instructions
+ specific to the web server you use.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="security-bugzilla"
+>4.3. Bugzilla</A
+></H2
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="security-bugzilla-charset"
+>4.3.1. Prevent users injecting malicious Javascript</A
+></H3
+><P
+>If you installed Bugzilla version 2.22 or later from scratch,
+ then the <EM
+>utf8</EM
+> parameter is switched on by default.
+ This makes Bugzilla explicitly set the character encoding, following
+ <A
+HREF="http://www.cert.org/tech_tips/malicious_code_mitigation.html#3"
+TARGET="_top"
+>a
+ CERT advisory</A
+> recommending exactly this.
+ The following therefore does not apply to you; just keep
+ <EM
+>utf8</EM
+> turned on.
+ </P
+><P
+>If you've upgraded from an older version, then it may be possible
+ for a Bugzilla user to take advantage of character set encoding
+ ambiguities to inject HTML into Bugzilla comments.
+ This could include malicious scripts.
+ This is because due to internationalization concerns, we are unable to
+ turn the <EM
+>utf8</EM
+> parameter on by default for upgraded
+ installations.
+ Turning it on manually will prevent this problem.
+ </P
+></DIV
+></DIV
+></DIV
+><DIV
+CLASS="chapter"
+><HR><H1
+><A
+NAME="using"
+></A
+>Chapter 5. Using Bugzilla</H1
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="using-intro"
+>5.1. Introduction</A
+></H2
+><P
+>This section contains information for end-users of Bugzilla. There
+ is a Bugzilla test installation, called
+ <A
+HREF="http://landfill.bugzilla.org/"
+TARGET="_top"
+>Landfill</A
+>, which you are
+ welcome to play with (if it's up). However, not all of the Bugzilla
+ installations there will necessarily have all Bugzilla features enabled,
+ and different installations run different versions, so some things may not
+ quite work as this document describes.</P
+><P
+>&#13; Frequently Asked Questions (FAQ) are available and answered on
+ <A
+HREF="http://wiki.mozilla.org/Bugzilla:FAQ"
+TARGET="_top"
+>wiki.mozilla.org</A
+>.
+ They may cover some questions you have which are left unanswered.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="myaccount"
+>5.2. Create a Bugzilla Account</A
+></H2
+><P
+>If you want to use Bugzilla, first you need to create an account.
+ Consult with the administrator responsible for your installation of
+ Bugzilla for the URL you should use to access it. If you're
+ test-driving Bugzilla, use this URL:
+ <A
+HREF="http://landfill.bugzilla.org/bugzilla-4.0-branch/"
+TARGET="_top"
+>http://landfill.bugzilla.org/bugzilla-4.0-branch/</A
+>.
+ </P
+><P
+></P
+><OL
+TYPE="1"
+><LI
+><P
+>&#13; On the home page <TT
+CLASS="filename"
+>index.cgi</TT
+>, click the
+ <SPAN
+CLASS="QUOTE"
+>"Open a new Bugzilla account"</SPAN
+> link, or the
+ <SPAN
+CLASS="QUOTE"
+>"New Account"</SPAN
+> link available in the footer of pages.
+ Now enter your email address, then click the <SPAN
+CLASS="QUOTE"
+>"Send"</SPAN
+>
+ button.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; If none of these links is available, this means that the
+ administrator of the installation has disabled self-registration.
+ This means that only an administrator can create accounts
+ for other users. One reason could be that this installation is
+ private.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Also, if only some users are allowed to create an account on
+ the installation, you may see these links but your registration
+ may fail if your email address doesn't match the ones accepted
+ by the installation. This is another way to restrict who can
+ access and edit bugs in this installation.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></LI
+><LI
+><P
+>&#13; Within moments, and if your registration is accepted, you should
+ receive an email to the address you provided, which contains your
+ login name (generally the same as the email address), and two URLs
+ with a token (a random string generated by the installation) to
+ confirm, respectively cancel, your registration. This is a way to
+ prevent users from abusing the generation of user accounts, for
+ instance by entering inexistent email addresses, or email addresses
+ which do not belong to them.
+ </P
+></LI
+><LI
+><P
+>&#13; By default, you have 3 days to confirm your registration. Past this
+ timeframe, the token is invalidated and the registration is
+ automatically canceled. You can also cancel this registration sooner
+ by using the appropriate URL in the email you got.
+ </P
+></LI
+><LI
+><P
+>&#13; If you confirm your registration, Bugzilla will ask you your real name
+ (optional, but recommended) and your password, which must be between
+ 3 and 16 characters long.
+ </P
+></LI
+><LI
+><P
+>&#13; Now all you need to do is to click the <SPAN
+CLASS="QUOTE"
+>"Log In"</SPAN
+>
+ link in the footer at the bottom of the page in your browser,
+ enter your email address and password you just chose into the
+ login form, and click the <SPAN
+CLASS="QUOTE"
+>"Log in"</SPAN
+> button.
+ </P
+></LI
+></OL
+><P
+>&#13; You are now logged in. Bugzilla uses cookies to remember you are
+ logged in so, unless you have cookies disabled or your IP address changes,
+ you should not have to log in again during your session.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="bug_page"
+>5.3. Anatomy of a Bug</A
+></H2
+><P
+>The core of Bugzilla is the screen which displays a particular
+ bug. It's a good place to explain some Bugzilla concepts.
+ <A
+HREF="http://landfill.bugzilla.org/bugzilla-4.0-branch/show_bug.cgi?id=1"
+TARGET="_top"
+>&#13; Bug 1 on Landfill</A
+>
+
+ is a good example. Note that the labels for most fields are hyperlinks;
+ clicking them will take you to context-sensitive help on that
+ particular field. Fields marked * may not be present on every
+ installation of Bugzilla.</P
+><P
+></P
+><OL
+TYPE="1"
+><LI
+><P
+>&#13; <EM
+>Product and Component</EM
+>:
+ Bugs are divided up by Product and Component, with a Product
+ having one or more Components in it. For example,
+ bugzilla.mozilla.org's "Bugzilla" Product is composed of several
+ Components:
+ <P
+></P
+><TABLE
+BORDER="0"
+><TBODY
+><TR
+><TD
+>&#13; <EM
+>Administration:</EM
+>
+ Administration of a Bugzilla installation.</TD
+></TR
+><TR
+><TD
+>&#13; <EM
+>Bugzilla-General:</EM
+>
+ Anything that doesn't fit in the other components, or spans
+ multiple components.</TD
+></TR
+><TR
+><TD
+>&#13; <EM
+>Creating/Changing Bugs:</EM
+>
+ Creating, changing, and viewing bugs.</TD
+></TR
+><TR
+><TD
+>&#13; <EM
+>Documentation:</EM
+>
+ The Bugzilla documentation, including The Bugzilla Guide.</TD
+></TR
+><TR
+><TD
+>&#13; <EM
+>Email:</EM
+>
+ Anything to do with email sent by Bugzilla.</TD
+></TR
+><TR
+><TD
+>&#13; <EM
+>Installation:</EM
+>
+ The installation process of Bugzilla.</TD
+></TR
+><TR
+><TD
+>&#13; <EM
+>Query/Buglist:</EM
+>
+ Anything to do with searching for bugs and viewing the
+ buglists.</TD
+></TR
+><TR
+><TD
+>&#13; <EM
+>Reporting/Charting:</EM
+>
+ Getting reports from Bugzilla.</TD
+></TR
+><TR
+><TD
+>&#13; <EM
+>User Accounts:</EM
+>
+ Anything about managing a user account from the user's perspective.
+ Saved queries, creating accounts, changing passwords, logging in,
+ etc.</TD
+></TR
+><TR
+><TD
+>&#13; <EM
+>User Interface:</EM
+>
+ General issues having to do with the user interface cosmetics (not
+ functionality) including cosmetic issues, HTML templates,
+ etc.</TD
+></TR
+></TBODY
+></TABLE
+><P
+></P
+>
+ </P
+></LI
+><LI
+><P
+>&#13; <EM
+>Status and Resolution:</EM
+>
+
+ These define exactly what state the bug is in - from not even
+ being confirmed as a bug, through to being fixed and the fix
+ confirmed by Quality Assurance. The different possible values for
+ Status and Resolution on your installation should be documented in the
+ context-sensitive help for those items.</P
+></LI
+><LI
+><P
+>&#13; <EM
+>Assigned To:</EM
+>
+ The person responsible for fixing the bug.</P
+></LI
+><LI
+><P
+>&#13; <EM
+>*QA Contact:</EM
+>
+ The person responsible for quality assurance on this bug.</P
+></LI
+><LI
+><P
+>&#13; <EM
+>*URL:</EM
+>
+ A URL associated with the bug, if any.</P
+></LI
+><LI
+><P
+>&#13; <EM
+>Summary:</EM
+>
+ A one-sentence summary of the problem.</P
+></LI
+><LI
+><P
+>&#13; <EM
+>*Status Whiteboard:</EM
+>
+ (a.k.a. Whiteboard) A free-form text area for adding short notes
+ and tags to a bug.</P
+></LI
+><LI
+><P
+>&#13; <EM
+>*Keywords:</EM
+>
+ The administrator can define keywords which you can use to tag and
+ categorise bugs - e.g. The Mozilla Project has keywords like crash
+ and regression.</P
+></LI
+><LI
+><P
+>&#13; <EM
+>Platform and OS:</EM
+>
+ These indicate the computing environment where the bug was
+ found.</P
+></LI
+><LI
+><P
+>&#13; <EM
+>Version:</EM
+>
+ The "Version" field is usually used for versions of a product which
+ have been released, and is set to indicate which versions of a
+ Component have the particular problem the bug report is
+ about.</P
+></LI
+><LI
+><P
+>&#13; <EM
+>Priority:</EM
+>
+ The bug assignee uses this field to prioritize his or her bugs.
+ It's a good idea not to change this on other people's bugs.</P
+></LI
+><LI
+><P
+>&#13; <EM
+>Severity:</EM
+>
+ This indicates how severe the problem is - from blocker
+ ("application unusable") to trivial ("minor cosmetic issue"). You
+ can also use this field to indicate whether a bug is an enhancement
+ request.</P
+></LI
+><LI
+><P
+>&#13; <EM
+>*Target:</EM
+>
+ (a.k.a. Target Milestone) A future version by which the bug is to
+ be fixed. e.g. The Bugzilla Project's milestones for future
+ Bugzilla versions are 2.18, 2.20, 3.0, etc. Milestones are not
+ restricted to numbers, thought - you can use any text strings, such
+ as dates.</P
+></LI
+><LI
+><P
+>&#13; <EM
+>Reporter:</EM
+>
+ The person who filed the bug.</P
+></LI
+><LI
+><P
+>&#13; <EM
+>CC list:</EM
+>
+ A list of people who get mail when the bug changes.</P
+></LI
+><LI
+><P
+>&#13; <EM
+>*Time Tracking:</EM
+>
+ This form can be used for time tracking.
+ To use this feature, you have to be blessed group membership
+ specified by the <SPAN
+CLASS="QUOTE"
+>"timetrackinggroup"</SPAN
+> parameter.
+ <P
+></P
+><TABLE
+BORDER="0"
+><TBODY
+><TR
+><TD
+>&#13; <EM
+>Orig. Est.:</EM
+>
+ This field shows the original estimated time.</TD
+></TR
+><TR
+><TD
+>&#13; <EM
+>Current Est.:</EM
+>
+ This field shows the current estimated time.
+ This number is calculated from <SPAN
+CLASS="QUOTE"
+>"Hours Worked"</SPAN
+>
+ and <SPAN
+CLASS="QUOTE"
+>"Hours Left"</SPAN
+>.</TD
+></TR
+><TR
+><TD
+>&#13; <EM
+>Hours Worked:</EM
+>
+ This field shows the number of hours worked.</TD
+></TR
+><TR
+><TD
+>&#13; <EM
+>Hours Left:</EM
+>
+ This field shows the <SPAN
+CLASS="QUOTE"
+>"Current Est."</SPAN
+> -
+ <SPAN
+CLASS="QUOTE"
+>"Hours Worked"</SPAN
+>.
+ This value + <SPAN
+CLASS="QUOTE"
+>"Hours Worked"</SPAN
+> will become the
+ new Current Est.</TD
+></TR
+><TR
+><TD
+>&#13; <EM
+>%Complete:</EM
+>
+ This field shows what percentage of the task is complete.</TD
+></TR
+><TR
+><TD
+>&#13; <EM
+>Gain:</EM
+>
+ This field shows the number of hours that the bug is ahead of the
+ <SPAN
+CLASS="QUOTE"
+>"Orig. Est."</SPAN
+>.</TD
+></TR
+><TR
+><TD
+>&#13; <EM
+>Deadline:</EM
+>
+ This field shows the deadline for this bug.</TD
+></TR
+></TBODY
+></TABLE
+><P
+></P
+>
+ </P
+></LI
+><LI
+><P
+>&#13; <EM
+>Attachments:</EM
+>
+ You can attach files (e.g. testcases or patches) to bugs. If there
+ are any attachments, they are listed in this section. Attachments are
+ normally stored in the Bugzilla database, unless they are marked as
+ Big Files, which are stored directly on disk.
+ </P
+></LI
+><LI
+><P
+>&#13; <EM
+>*Dependencies:</EM
+>
+ If this bug cannot be fixed unless other bugs are fixed (depends
+ on), or this bug stops other bugs being fixed (blocks), their
+ numbers are recorded here.</P
+></LI
+><LI
+><P
+>&#13; <EM
+>*Votes:</EM
+>
+ Whether this bug has any votes.</P
+></LI
+><LI
+><P
+>&#13; <EM
+>Additional Comments:</EM
+>
+ You can add your two cents to the bug discussion here, if you have
+ something worthwhile to say.</P
+></LI
+></OL
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="lifecycle"
+>5.4. Life Cycle of a Bug</A
+></H2
+><P
+>&#13; The life cycle of a bug, also known as workflow, is customizable to match
+ the needs of your organization, see <A
+HREF="#bug_status_workflow"
+>Section 3.12</A
+>.
+ <A
+HREF="#lifecycle-image"
+>Figure 5-1</A
+> contains a graphical representation of
+ the default workflow using the default bug statuses. If you wish to
+ customize this image for your site, the
+ <A
+HREF="../images/bzLifecycle.xml"
+TARGET="_top"
+>diagram file</A
+>
+ is available in <A
+HREF="http://www.gnome.org/projects/dia"
+TARGET="_top"
+>Dia's</A
+>
+ native XML format.
+ </P
+><DIV
+CLASS="figure"
+><A
+NAME="lifecycle-image"
+></A
+><P
+><B
+>Figure 5-1. Lifecycle of a Bugzilla Bug</B
+></P
+><DIV
+CLASS="mediaobject"
+><P
+><IMG
+SRC="../images/bzLifecycle.png"></P
+></DIV
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="query"
+>5.5. Searching for Bugs</A
+></H2
+><P
+>The Bugzilla Search page is the interface where you can find
+ any bug report, comment, or patch currently in the Bugzilla system. You
+ can play with it here:
+ <A
+HREF="http://landfill.bugzilla.org/bugzilla-4.0-branch/query.cgi"
+TARGET="_top"
+>http://landfill.bugzilla.org/bugzilla-4.0-branch/query.cgi</A
+>.</P
+><P
+>The Search page has controls for selecting different possible
+ values for all of the fields in a bug, as described above. For some
+ fields, multiple values can be selected. In those cases, Bugzilla
+ returns bugs where the content of the field matches any one of the selected
+ values. If none is selected, then the field can take any value.</P
+><P
+>&#13; After a search is run, you can save it as a Saved Search, which
+ will appear in the page footer. If you are in the group defined
+ by the "querysharegroup" parameter, you may share your queries
+ with other users, see <A
+HREF="#savedsearches"
+>Saved Searches</A
+> for more details.
+ </P
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="boolean"
+>5.5.1. Boolean Charts</A
+></H3
+><P
+>&#13; Highly advanced querying is done using Boolean Charts.
+ </P
+><P
+>&#13; The boolean charts further restrict the set of results
+ returned by a query. It is possible to search for bugs
+ based on elaborate combinations of criteria.
+ </P
+><P
+>&#13; The simplest boolean searches have only one term. These searches
+ permit the selected left <EM
+>field</EM
+>
+ to be compared using a
+ selectable <EM
+>operator</EM
+> to a
+ specified <EM
+>value.</EM
+>
+ Using the "And," "Or," and "Add Another Boolean Chart" buttons,
+ additional terms can be included in the query, further
+ altering the list of bugs returned by the query.
+ </P
+><P
+>&#13; There are three fields in each row of a boolean search.
+ </P
+><P
+></P
+><UL
+><LI
+><P
+>&#13; <EM
+>Field:</EM
+>
+ the items being searched
+ </P
+></LI
+><LI
+><P
+>&#13; <EM
+>Operator:</EM
+>
+ the comparison operator
+ </P
+></LI
+><LI
+><P
+>&#13; <EM
+>Value:</EM
+>
+ the value to which the field is being compared
+ </P
+></LI
+></UL
+><DIV
+CLASS="section"
+><HR><H4
+CLASS="section"
+><A
+NAME="pronouns"
+>5.5.1.1. Pronoun Substitution</A
+></H4
+><P
+>&#13; Sometimes, a query needs to compare a user-related field
+ (such as ReportedBy) with a role-specific user (such as the
+ user running the query or the user to whom each bug is assigned).
+ When the operator is either "equals" or "notequals", the value
+ can be "%reporter%", "%assignee%", "%qacontact%", or "%user%".
+ The user pronoun
+ refers to the user who is executing the query or, in the case
+ of whining reports, the user who will be the recipient
+ of the report. The reporter, assignee, and qacontact
+ pronouns refer to the corresponding fields in the bug.
+ </P
+><P
+>&#13; Boolean charts also let you type a group name in any user-related
+ field if the operator is either "equals", "notequals" or "anyexact".
+ This will let you query for any member belonging (or not) to the
+ specified group. The group name must be entered following the
+ "%group.foo%" syntax, where "foo" is the group name.
+ So if you are looking for bugs reported by any user being in the
+ "editbugs" group, then you can type "%group.editbugs%".
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H4
+CLASS="section"
+><A
+NAME="negation"
+>5.5.1.2. Negation</A
+></H4
+><P
+>&#13; At first glance, negation seems redundant. Rather than
+ searching for
+ <A
+NAME="AEN2438"
+></A
+><BLOCKQUOTE
+CLASS="BLOCKQUOTE"
+><P
+>&#13; NOT("summary" "contains the string" "foo"),
+ </P
+></BLOCKQUOTE
+>
+ one could search for
+ <A
+NAME="AEN2440"
+></A
+><BLOCKQUOTE
+CLASS="BLOCKQUOTE"
+><P
+>&#13; ("summary" "does not contain the string" "foo").
+ </P
+></BLOCKQUOTE
+>
+ However, the search
+ <A
+NAME="AEN2442"
+></A
+><BLOCKQUOTE
+CLASS="BLOCKQUOTE"
+><P
+>&#13; ("CC" "does not contain the string" "@mozilla.org")
+ </P
+></BLOCKQUOTE
+>
+ would find every bug where anyone on the CC list did not contain
+ "@mozilla.org" while
+ <A
+NAME="AEN2444"
+></A
+><BLOCKQUOTE
+CLASS="BLOCKQUOTE"
+><P
+>&#13; NOT("CC" "contains the string" "@mozilla.org")
+ </P
+></BLOCKQUOTE
+>
+ would find every bug where there was nobody on the CC list who
+ did contain the string. Similarly, the use of negation also permits
+ complex expressions to be built using terms OR'd together and then
+ negated. Negation permits queries such as
+ <A
+NAME="AEN2446"
+></A
+><BLOCKQUOTE
+CLASS="BLOCKQUOTE"
+><P
+>&#13; NOT(("product" "equals" "update") OR
+ ("component" "equals" "Documentation"))
+ </P
+></BLOCKQUOTE
+>
+ to find bugs that are neither
+ in the update product or in the documentation component or
+ <A
+NAME="AEN2448"
+></A
+><BLOCKQUOTE
+CLASS="BLOCKQUOTE"
+><P
+>&#13; NOT(("commenter" "equals" "%assignee%") OR
+ ("component" "equals" "Documentation"))
+ </P
+></BLOCKQUOTE
+>
+ to find non-documentation
+ bugs on which the assignee has never commented.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H4
+CLASS="section"
+><A
+NAME="multiplecharts"
+>5.5.1.3. Multiple Charts</A
+></H4
+><P
+>&#13; The terms within a single row of a boolean chart are all
+ constraints on a single piece of data. If you are looking for
+ a bug that has two different people cc'd on it, then you need
+ to use two boolean charts. A search for
+ <A
+NAME="AEN2453"
+></A
+><BLOCKQUOTE
+CLASS="BLOCKQUOTE"
+><P
+>&#13; ("cc" "contains the string" "foo@") AND
+ ("cc" "contains the string" "@mozilla.org")
+ </P
+></BLOCKQUOTE
+>
+ would return only bugs with "foo@mozilla.org" on the cc list.
+ If you wanted bugs where there is someone on the cc list
+ containing "foo@" and someone else containing "@mozilla.org",
+ then you would need two boolean charts.
+ <A
+NAME="AEN2455"
+></A
+><BLOCKQUOTE
+CLASS="BLOCKQUOTE"
+><P
+>&#13; First chart: ("cc" "contains the string" "foo@")
+ </P
+><P
+>&#13; Second chart: ("cc" "contains the string" "@mozilla.org")
+ </P
+></BLOCKQUOTE
+>
+ The bugs listed will be only the bugs where ALL the charts are true.
+ </P
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="quicksearch"
+>5.5.2. Quicksearch</A
+></H3
+><P
+>&#13; Quicksearch is a single-text-box query tool which uses
+ metacharacters to indicate what is to be searched. For example, typing
+ "<TT
+CLASS="literal"
+>foo|bar</TT
+>"
+ into Quicksearch would search for "foo" or "bar" in the
+ summary and status whiteboard of a bug; adding
+ "<TT
+CLASS="literal"
+>:BazProduct</TT
+>" would
+ search only in that product.
+ You can use it to find a bug by its number or its alias, too.
+ </P
+><P
+>&#13; You'll find the Quicksearch box in Bugzilla's footer area.
+ On Bugzilla's front page, there is an additional
+ <A
+HREF="../../page.cgi?id=quicksearch.html"
+TARGET="_top"
+>Help</A
+>
+ link which details how to use it.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="casesensitivity"
+>5.5.3. Case Sensitivity in Searches</A
+></H3
+><P
+>&#13; Bugzilla queries are case-insensitive and accent-insensitive, when
+ used with either MySQL or Oracle databases. When using Bugzilla with
+ PostgreSQL, however, some queries are case-sensitive. This is due to
+ the way PostgreSQL handles case and accent sensitivity.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="list"
+>5.5.4. Bug Lists</A
+></H3
+><P
+>If you run a search, a list of matching bugs will be returned.
+ </P
+><P
+>The format of the list is configurable. For example, it can be
+ sorted by clicking the column headings. Other useful features can be
+ accessed using the links at the bottom of the list:
+ <P
+></P
+><TABLE
+BORDER="0"
+><TBODY
+><TR
+><TD
+>&#13; <EM
+>Long Format:</EM
+>
+
+ this gives you a large page with a non-editable summary of the fields
+ of each bug.</TD
+></TR
+><TR
+><TD
+>&#13; <EM
+>XML:</EM
+>
+
+ get the buglist in the XML format.</TD
+></TR
+><TR
+><TD
+>&#13; <EM
+>CSV:</EM
+>
+
+ get the buglist as comma-separated values, for import into e.g.
+ a spreadsheet.</TD
+></TR
+><TR
+><TD
+>&#13; <EM
+>Feed:</EM
+>
+
+ get the buglist as an Atom feed. Copy this link into your
+ favorite feed reader. If you are using Firefox, you can also
+ save the list as a live bookmark by clicking the live bookmark
+ icon in the status bar. To limit the number of bugs in the feed,
+ add a limit=n parameter to the URL.</TD
+></TR
+><TR
+><TD
+>&#13; <EM
+>iCalendar:</EM
+>
+
+ Get the buglist as an iCalendar file. Each bug is represented as a
+ to-do item in the imported calendar.</TD
+></TR
+><TR
+><TD
+>&#13; <EM
+>Change Columns:</EM
+>
+
+ change the bug attributes which appear in the list.</TD
+></TR
+><TR
+><TD
+>&#13; <EM
+>Change several bugs at once:</EM
+>
+
+ If your account is sufficiently empowered, and more than one bug
+ appear in the bug list, this link is displayed which lets you make
+ the same change to all the bugs in the list - for example, changing
+ their assignee.</TD
+></TR
+><TR
+><TD
+>&#13; <EM
+>Send mail to bug assignees:</EM
+>
+
+ If more than one bug appear in the bug list and there are at least
+ two distinct bug assignees, this links is displayed which lets you
+ easily send a mail to the assignees of all bugs on the list.</TD
+></TR
+><TR
+><TD
+>&#13; <EM
+>Edit Search:</EM
+>
+
+ If you didn't get exactly the results you were looking for, you can
+ return to the Query page through this link and make small revisions
+ to the query you just made so you get more accurate results.</TD
+></TR
+><TR
+><TD
+>&#13; <EM
+>Remember Search As:</EM
+>
+
+ You can give a search a name and remember it; a link will appear
+ in your page footer giving you quick access to run it again later.
+ </TD
+></TR
+></TBODY
+></TABLE
+><P
+></P
+>
+ </P
+><P
+>&#13; If you would like to access the bug list from another program
+ it is often useful to have the list returned in something other
+ than HTML. By adding the ctype=type parameter into the bug list URL
+ you can specify several alternate formats. Besides the types described
+ above, the following formats are also supported: ECMAScript, also known
+ as JavaScript (ctype=js), and Resource Description Framework RDF/XML
+ (ctype=rdf).
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="individual-buglists"
+>5.5.5. Adding/removing tags to/from bugs</A
+></H3
+><P
+>&#13; You can add and remove tags from individual bugs, which let you find and
+ manage them more easily. Creating a new tag automatically generates a saved
+ search - whose name is the name of the tag - which lists bugs with this tag.
+ This saved search will be displayed in the footer of pages by default, as
+ all other saved searches. The main difference between tags and normal saved
+ searches is that saved searches, as described in the previous section, are
+ stored in the form of a list of matching criteria, while the saved search
+ generated by tags is a list of bug numbers. Consequently, you can easily
+ edit this list by either adding or removing tags from bugs. To enable this
+ feature, you have to turn on the <SPAN
+CLASS="QUOTE"
+>"Enable tags for bugs"</SPAN
+> user
+ preference, see <A
+HREF="#userpreferences"
+>Section 5.10</A
+>. This feature is disabled
+ by default.
+ </P
+><P
+>&#13; This feature is useful when you want to keep track of several bugs, but
+ for different reasons. Instead of adding yourself to the CC list of all
+ these bugs and mixing all these reasons, you can now store these bugs in
+ separate lists, e.g. <SPAN
+CLASS="QUOTE"
+>"Keep in mind"</SPAN
+>, <SPAN
+CLASS="QUOTE"
+>"Interesting bugs"</SPAN
+>,
+ or <SPAN
+CLASS="QUOTE"
+>"Triage"</SPAN
+>. One big advantage of this way to manage bugs
+ is that you can easily add or remove bugs one by one, which is not
+ possible to do with saved searches without having to edit the search
+ criteria again.
+ </P
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="bugreports"
+>5.6. Filing Bugs</A
+></H2
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="fillingbugs"
+>5.6.1. Reporting a New Bug</A
+></H3
+><P
+>Years of bug writing experience has been distilled for your
+ reading pleasure into the
+ <A
+HREF="http://landfill.bugzilla.org/bugzilla-4.0-branch/page.cgi?id=bug-writing.html"
+TARGET="_top"
+>&#13; Bug Writing Guidelines</A
+>.
+ While some of the advice is Mozilla-specific, the basic principles of
+ reporting Reproducible, Specific bugs, isolating the Product you are
+ using, the Version of the Product, the Component which failed, the
+ Hardware Platform, and Operating System you were using at the time of
+ the failure go a long way toward ensuring accurate, responsible fixes
+ for the bug that bit you.</P
+><P
+>The procedure for filing a bug is as follows:</P
+><P
+></P
+><OL
+TYPE="1"
+><LI
+><P
+>&#13; Click the <SPAN
+CLASS="QUOTE"
+>"New"</SPAN
+> link available in the footer
+ of pages, or the <SPAN
+CLASS="QUOTE"
+>"Enter a new bug report"</SPAN
+> link
+ displayed on the home page of the Bugzilla installation.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; If you want to file a test bug to see how Bugzilla works,
+ you can do it on one of our test installations on
+ <A
+HREF="http://landfill.bugzilla.org/bugzilla-4.0-branch/"
+TARGET="_top"
+>Landfill</A
+>.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></LI
+><LI
+><P
+>&#13; You first have to select the product in which you found a bug.
+ </P
+></LI
+><LI
+><P
+>&#13; You now see a form where you can specify the component (part of
+ the product which is affected by the bug you discovered; if you have
+ no idea, just select <SPAN
+CLASS="QUOTE"
+>"General"</SPAN
+> if such a component exists),
+ the version of the program you were using, the Operating System and
+ platform your program is running on and the severity of the bug (if the
+ bug you found crashes the program, it's probably a major or a critical
+ bug; if it's a typo somewhere, that's something pretty minor; if it's
+ something you would like to see implemented, then that's an enhancement).
+ </P
+></LI
+><LI
+><P
+>&#13; You now have to give a short but descriptive summary of the bug you found.
+ <SPAN
+CLASS="QUOTE"
+>"My program is crashing all the time"</SPAN
+> is a very poor summary
+ and doesn't help developers at all. Try something more meaningful or
+ your bug will probably be ignored due to a lack of precision.
+ The next step is to give a very detailed list of steps to reproduce
+ the problem you encountered. Try to limit these steps to a minimum set
+ required to reproduce the problem. This will make the life of
+ developers easier, and the probability that they consider your bug in
+ a reasonable timeframe will be much higher.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Try to make sure that everything in the summary is also in the first
+ comment. Summaries are often updated and this will ensure your original
+ information is easily accessible.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></LI
+><LI
+><P
+>&#13; As you file the bug, you can also attach a document (testcase, patch,
+ or screenshot of the problem).
+ </P
+></LI
+><LI
+><P
+>&#13; Depending on the Bugzilla installation you are using and the product in
+ which you are filing the bug, you can also request developers to consider
+ your bug in different ways (such as requesting review for the patch you
+ just attached, requesting your bug to block the next release of the
+ product, and many other product specific requests).
+ </P
+></LI
+><LI
+><P
+>&#13; Now is a good time to read your bug report again. Remove all misspellings,
+ otherwise your bug may not be found by developers running queries for some
+ specific words, and so your bug would not get any attention.
+ Also make sure you didn't forget any important information developers
+ should know in order to reproduce the problem, and make sure your
+ description of the problem is explicit and clear enough.
+ When you think your bug report is ready to go, the last step is to
+ click the <SPAN
+CLASS="QUOTE"
+>"Commit"</SPAN
+> button to add your report into the database.
+ </P
+></LI
+></OL
+><P
+>&#13; You do not need to put "any" or similar strings in the URL field.
+ If there is no specific URL associated with the bug, leave this
+ field blank.
+ </P
+><P
+>If you feel a bug you filed was incorrectly marked as a
+ DUPLICATE of another, please question it in your bug, not
+ the bug it was duped to. Feel free to CC the person who duped it
+ if they are not already CCed.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="cloningbugs"
+>5.6.2. Clone an Existing Bug</A
+></H3
+><P
+>&#13; Starting with version 2.20, Bugzilla has a feature that allows you
+ to clone an existing bug. The newly created bug will inherit
+ most settings from the old bug. This allows you to track more
+ easily similar concerns in a new bug. To use this, go to the bug
+ that you want to clone, then click the <SPAN
+CLASS="QUOTE"
+>"Clone This Bug"</SPAN
+>
+ link on the bug page. This will take you to the <SPAN
+CLASS="QUOTE"
+>"Enter Bug"</SPAN
+>
+ page that is filled with the values that the old bug has.
+ You can change those values and/or texts if needed.
+ </P
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="attachments"
+>5.7. Attachments</A
+></H2
+><P
+>&#13; You should use attachments, rather than comments, for large chunks of ASCII
+ data, such as trace, debugging output files, or log files. That way, it
+ doesn't bloat the bug for everyone who wants to read it, and cause people to
+ receive fat, useless mails.
+ </P
+><P
+>You should make sure to trim screenshots. There's no need to show the
+ whole screen if you are pointing out a single-pixel problem.
+ </P
+><P
+>Bugzilla stores and uses a Content-Type for each attachment
+ (e.g. text/html). To download an attachment as a different
+ Content-Type (e.g. application/xhtml+xml), you can override this
+ using a 'content_type' parameter on the URL, e.g.
+ <TT
+CLASS="filename"
+>&#38;content_type=text/plain</TT
+>.
+ </P
+><P
+>&#13; If you have a really large attachment, something that does not need to
+ be recorded forever (as most attachments are), or something that is too
+ big for your database, you can mark your attachment as a
+ <SPAN
+CLASS="QUOTE"
+>"Big File"</SPAN
+>, assuming the administrator of the installation
+ has enabled this feature. Big Files are stored directly on disk instead
+ of in the database. The maximum size of a <SPAN
+CLASS="QUOTE"
+>"Big File"</SPAN
+> is
+ normally larger than the maximum size of a regular attachment. Independently
+ of the storage system used, an administrator can delete these attachments
+ at any time. Nevertheless, if these files are stored in the database, the
+ <SPAN
+CLASS="QUOTE"
+>"allow_attachment_deletion"</SPAN
+> parameter (which is turned off
+ by default) must be enabled in order to delete them.
+ </P
+><P
+>&#13; Also, if the administrator turned on the <SPAN
+CLASS="QUOTE"
+>"allow_attach_url"</SPAN
+>
+ parameter, you can enter the URL pointing to the attachment instead of
+ uploading the attachment itself. For example, this is useful if you want to
+ point to an external application, a website or a very large file. Note that
+ there is no guarantee that the source file will always be available, nor
+ that its content will remain unchanged.
+ </P
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="patchviewer"
+>5.7.1. Patch Viewer</A
+></H3
+><P
+>Viewing and reviewing patches in Bugzilla is often difficult due to
+ lack of context, improper format and the inherent readability issues that
+ raw patches present. Patch Viewer is an enhancement to Bugzilla designed
+ to fix that by offering increased context, linking to sections, and
+ integrating with Bonsai, LXR and CVS.</P
+><P
+>Patch viewer allows you to:</P
+><P
+></P
+><TABLE
+BORDER="0"
+><TBODY
+><TR
+><TD
+>View patches in color, with side-by-side view rather than trying
+ to interpret the contents of the patch.</TD
+></TR
+><TR
+><TD
+>See the difference between two patches.</TD
+></TR
+><TR
+><TD
+>Get more context in a patch.</TD
+></TR
+><TR
+><TD
+>Collapse and expand sections of a patch for easy
+ reading.</TD
+></TR
+><TR
+><TD
+>Link to a particular section of a patch for discussion or
+ review</TD
+></TR
+><TR
+><TD
+>Go to Bonsai or LXR to see more context, blame, and
+ cross-references for the part of the patch you are looking at</TD
+></TR
+><TR
+><TD
+>Create a rawtext unified format diff out of any patch, no
+ matter what format it came from</TD
+></TR
+></TBODY
+></TABLE
+><P
+></P
+><DIV
+CLASS="section"
+><HR><H4
+CLASS="section"
+><A
+NAME="patchviewer_view"
+>5.7.1.1. Viewing Patches in Patch Viewer</A
+></H4
+><P
+>The main way to view a patch in patch viewer is to click on the
+ "Diff" link next to a patch in the Attachments list on a bug. You may
+ also do this within the edit window by clicking the "View Attachment As
+ Diff" button in the Edit Attachment screen.</P
+></DIV
+><DIV
+CLASS="section"
+><HR><H4
+CLASS="section"
+><A
+NAME="patchviewer_diff"
+>5.7.1.2. Seeing the Difference Between Two Patches</A
+></H4
+><P
+>To see the difference between two patches, you must first view the
+ newer patch in Patch Viewer. Then select the older patch from the
+ dropdown at the top of the page ("Differences between [dropdown] and
+ this patch") and click the "Diff" button. This will show you what
+ is new or changed in the newer patch.</P
+></DIV
+><DIV
+CLASS="section"
+><HR><H4
+CLASS="section"
+><A
+NAME="patchviewer_context"
+>5.7.1.3. Getting More Context in a Patch</A
+></H4
+><P
+>To get more context in a patch, you put a number in the textbox at
+ the top of Patch Viewer ("Patch / File / [textbox]") and hit enter.
+ This will give you that many lines of context before and after each
+ change. Alternatively, you can click on the "File" link there and it
+ will show each change in the full context of the file. This feature only
+ works against files that were diffed using "cvs diff".</P
+></DIV
+><DIV
+CLASS="section"
+><HR><H4
+CLASS="section"
+><A
+NAME="patchviewer_collapse"
+>5.7.1.4. Collapsing and Expanding Sections of a Patch</A
+></H4
+><P
+>To view only a certain set of files in a patch (for example, if a
+ patch is absolutely huge and you want to only review part of it at a
+ time), you can click the "(+)" and "(-)" links next to each file (to
+ expand it or collapse it). If you want to collapse all files or expand
+ all files, you can click the "Collapse All" and "Expand All" links at the
+ top of the page.</P
+></DIV
+><DIV
+CLASS="section"
+><HR><H4
+CLASS="section"
+><A
+NAME="patchviewer_link"
+>5.7.1.5. Linking to a Section of a Patch</A
+></H4
+><P
+>To link to a section of a patch (for example, if you want to be
+ able to give someone a URL to show them which part you are talking
+ about) you simply click the "Link Here" link on the section header. The
+ resulting URL can be copied and used in discussion.</P
+></DIV
+><DIV
+CLASS="section"
+><HR><H4
+CLASS="section"
+><A
+NAME="patchviewer_bonsai_lxr"
+>5.7.1.6. Going to Bonsai and LXR</A
+></H4
+><P
+>To go to Bonsai to get blame for the lines you are interested in,
+ you can click the "Lines XX-YY" link on the section header you are
+ interested in. This works even if the patch is against an old
+ version of the file, since Bonsai stores all versions of the file.</P
+><P
+>To go to LXR, you click on the filename on the file header
+ (unfortunately, since LXR only does the most recent version, line
+ numbers are likely to rot).</P
+></DIV
+><DIV
+CLASS="section"
+><HR><H4
+CLASS="section"
+><A
+NAME="patchviewer_unified_diff"
+>5.7.1.7. Creating a Unified Diff</A
+></H4
+><P
+>If the patch is not in a format that you like, you can turn it
+ into a unified diff format by clicking the "Raw Unified" link at the top
+ of the page.</P
+></DIV
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="hintsandtips"
+>5.8. Hints and Tips</A
+></H2
+><P
+>This section distills some Bugzilla tips and best practices
+ that have been developed.</P
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="AEN2591"
+>5.8.1. Autolinkification</A
+></H3
+><P
+>Bugzilla comments are plain text - so typing &#60;U&#62; will
+ produce less-than, U, greater-than rather than underlined text.
+ However, Bugzilla will automatically make hyperlinks out of certain
+ sorts of text in comments. For example, the text
+ "http://www.bugzilla.org" will be turned into a link:
+ <A
+HREF="http://www.bugzilla.org"
+TARGET="_top"
+>http://www.bugzilla.org</A
+>.
+ Other strings which get linkified in the obvious manner are:
+ <P
+></P
+><TABLE
+BORDER="0"
+><TBODY
+><TR
+><TD
+>bug 12345</TD
+></TR
+><TR
+><TD
+>comment 7</TD
+></TR
+><TR
+><TD
+>bug 23456, comment 53</TD
+></TR
+><TR
+><TD
+>attachment 4321</TD
+></TR
+><TR
+><TD
+>mailto:george@example.com</TD
+></TR
+><TR
+><TD
+>george@example.com</TD
+></TR
+><TR
+><TD
+>ftp://ftp.mozilla.org</TD
+></TR
+><TR
+><TD
+>Most other sorts of URL</TD
+></TR
+></TBODY
+></TABLE
+><P
+></P
+>
+ </P
+><P
+>A corollary here is that if you type a bug number in a comment,
+ you should put the word "bug" before it, so it gets autolinkified
+ for the convenience of others.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="commenting"
+>5.8.2. Comments</A
+></H3
+><P
+>If you are changing the fields on a bug, only comment if
+ either you have something pertinent to say, or Bugzilla requires it.
+ Otherwise, you may spam people unnecessarily with bug mail.
+ To take an example: a user can set up their account to filter out messages
+ where someone just adds themselves to the CC field of a bug
+ (which happens a lot.) If you come along, add yourself to the CC field,
+ and add a comment saying "Adding self to CC", then that person
+ gets a pointless piece of mail they would otherwise have avoided.
+ </P
+><P
+>&#13; Don't use sigs in comments. Signing your name ("Bill") is acceptable,
+ if you do it out of habit, but full mail/news-style
+ four line ASCII art creations are not.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="comment-wrapping"
+>5.8.3. Server-Side Comment Wrapping</A
+></H3
+><P
+>&#13; Bugzilla stores comments unwrapped and wraps them at display time. This
+ ensures proper wrapping in all browsers. Lines beginning with the "&#62;"
+ character are assumed to be quotes, and are not wrapped.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="dependencytree"
+>5.8.4. Dependency Tree</A
+></H3
+><P
+>&#13; On the <SPAN
+CLASS="QUOTE"
+>"Dependency tree"</SPAN
+> page linked from each bug
+ page, you can see the dependency relationship from the bug as a
+ tree structure.
+ </P
+><P
+>&#13; You can change how much depth to show, and you can hide resolved bugs
+ from this page. You can also collaps/expand dependencies for
+ each bug on the tree view, using the [-]/[+] buttons that appear
+ before its summary. This option is not available for terminal
+ bugs in the tree (that don't have further dependencies).
+ </P
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="timetracking"
+>5.9. Time Tracking Information</A
+></H2
+><P
+>&#13; Users who belong to the group specified by the <SPAN
+CLASS="QUOTE"
+>"timetrackinggroup"</SPAN
+>
+ parameter have access to time-related fields. Developers can see
+ deadlines and estimated times to fix bugs, and can provide time spent
+ on these bugs.
+ </P
+><P
+>&#13; At any time, a summary of the time spent by developers on bugs is
+ accessible either from bug lists when clicking the <SPAN
+CLASS="QUOTE"
+>"Time Summary"</SPAN
+>
+ button or from individual bugs when clicking the <SPAN
+CLASS="QUOTE"
+>"Summarize time"</SPAN
+>
+ link in the time tracking table. The <TT
+CLASS="filename"
+>summarize_time.cgi</TT
+>
+ page lets you view this information either per developer or per bug,
+ and can be split on a month basis to have greater details on how time
+ is spent by developers.
+ </P
+><P
+>&#13; As soon as a bug is marked as RESOLVED, the remaining time expected
+ to fix the bug is set to zero. This lets QA people set it again for
+ their own usage, and it will be set to zero again when the bug will
+ be marked as CLOSED.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="userpreferences"
+>5.10. User Preferences</A
+></H2
+><P
+>&#13; Once logged in, you can customize various aspects of
+ Bugzilla via the "Preferences" link in the page footer.
+ The preferences are split into five tabs:</P
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="generalpreferences"
+>5.10.1. General Preferences</A
+></H3
+><P
+>&#13; This tab allows you to change several default settings of Bugzilla.
+ </P
+><P
+></P
+><UL
+COMPACT="COMPACT"
+><LI
+><P
+>&#13; Bugzilla's general appearance (skin) - select which skin to use.
+ Bugzilla supports adding custom skins.
+ </P
+></LI
+><LI
+><P
+>&#13; Quote the associated comment when you click on its reply link - sets
+ the behavior of the comment "Reply" link. Options include quoting the
+ full comment, just reference the comment number, or turn the link off.
+ </P
+></LI
+><LI
+><P
+>&#13; Language used in email - select which language email will be sent in,
+ from the list of available languages.
+ </P
+></LI
+><LI
+><P
+>&#13; After changing a bug - This controls what page is displayed after
+ changes to a bug are submitted. The options include to show the bug
+ just modified, to show the next bug in your list, or to do nothing.
+ </P
+></LI
+><LI
+><P
+>&#13; Enable tags for bugs - turn bug tagging on or off.
+ </P
+></LI
+><LI
+><P
+>&#13; Zoom textareas large when in use (requires JavaScript) - enable or
+ disable the automatic expanding of text areas when text is being
+ entered into them.
+ </P
+></LI
+><LI
+><P
+>&#13; Field separator character for CSV files -
+ Select between a comma and semi-colon for exported CSV bug lists.
+ </P
+></LI
+><LI
+><P
+>&#13; Automatically add me to the CC list of bugs I change - set default
+ behavior of CC list. Options include "Always", "Never", and "Only
+ if I have no role on them".
+ </P
+></LI
+><LI
+><P
+>&#13; When viewing a bug, show comments in this order -
+ controls the order of comments. Options include "Oldest
+ to Newest", "Newest to Oldest" and "Newest to Oldest, but keep the
+ bug description at the top".
+ </P
+></LI
+><LI
+><P
+>&#13; Show a quip at the top of each bug list - controls
+ whether a quip will be shown on the Bug list page.
+ </P
+></LI
+></UL
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="emailpreferences"
+>5.10.2. Email Preferences</A
+></H3
+><P
+>&#13; This tab allows you to enable or disable email notification on
+ specific events.
+ </P
+><P
+>&#13; In general, users have almost complete control over how much (or
+ how little) email Bugzilla sends them. If you want to receive the
+ maximum amount of email possible, click the <SPAN
+CLASS="QUOTE"
+>"Enable All
+ Mail"</SPAN
+> button. If you don't want to receive any email from
+ Bugzilla at all, click the <SPAN
+CLASS="QUOTE"
+>"Disable All Mail"</SPAN
+> button.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; A Bugzilla administrator can stop a user from receiving
+ bugmail by clicking the <SPAN
+CLASS="QUOTE"
+>"Bugmail Disabled"</SPAN
+> checkbox
+ when editing the user account. This is a drastic step
+ best taken only for disabled accounts, as it overrides
+ the user's individual mail preferences.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; There are two global options -- <SPAN
+CLASS="QUOTE"
+>"Email me when someone
+ asks me to set a flag"</SPAN
+> and <SPAN
+CLASS="QUOTE"
+>"Email me when someone
+ sets a flag I asked for"</SPAN
+>. These define how you want to
+ receive bugmail with regards to flags. Their use is quite
+ straightforward; enable the checkboxes if you want Bugzilla to
+ send you mail under either of the above conditions.
+ </P
+><P
+>&#13; If you'd like to set your bugmail to something besides
+ 'Completely ON' and 'Completely OFF', the
+ <SPAN
+CLASS="QUOTE"
+>"Field/recipient specific options"</SPAN
+> table
+ allows you to do just that. The rows of the table
+ define events that can happen to a bug -- things like
+ attachments being added, new comments being made, the
+ priority changing, etc. The columns in the table define
+ your relationship with the bug:
+ </P
+><P
+></P
+><UL
+COMPACT="COMPACT"
+><LI
+><P
+>&#13; Reporter - Where you are the person who initially
+ reported the bug. Your name/account appears in the
+ <SPAN
+CLASS="QUOTE"
+>"Reporter:"</SPAN
+> field.
+ </P
+></LI
+><LI
+><P
+>&#13; Assignee - Where you are the person who has been
+ designated as the one responsible for the bug. Your
+ name/account appears in the <SPAN
+CLASS="QUOTE"
+>"Assigned To:"</SPAN
+>
+ field of the bug.
+ </P
+></LI
+><LI
+><P
+>&#13; QA Contact - You are one of the designated
+ QA Contacts for the bug. Your account appears in the
+ <SPAN
+CLASS="QUOTE"
+>"QA Contact:"</SPAN
+> text-box of the bug.
+ </P
+></LI
+><LI
+><P
+>&#13; CC - You are on the list CC List for the bug.
+ Your account appears in the <SPAN
+CLASS="QUOTE"
+>"CC:"</SPAN
+> text box
+ of the bug.
+ </P
+></LI
+><LI
+><P
+>&#13; Voter - You have placed one or more votes for the bug.
+ Your account appears only if someone clicks on the
+ <SPAN
+CLASS="QUOTE"
+>"Show votes for this bug"</SPAN
+> link on the bug.
+ </P
+></LI
+></UL
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Some columns may not be visible for your installation, depending
+ on your site's configuration.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; To fine-tune your bugmail, decide the events for which you want
+ to receive bugmail; then decide if you want to receive it all
+ the time (enable the checkbox for every column), or only when
+ you have a certain relationship with a bug (enable the checkbox
+ only for those columns). For example: if you didn't want to
+ receive mail when someone added themselves to the CC list, you
+ could uncheck all the boxes in the <SPAN
+CLASS="QUOTE"
+>"CC Field Changes"</SPAN
+>
+ line. As another example, if you never wanted to receive email
+ on bugs you reported unless the bug was resolved, you would
+ un-check all boxes in the <SPAN
+CLASS="QUOTE"
+>"Reporter"</SPAN
+> column
+ except for the one on the <SPAN
+CLASS="QUOTE"
+>"The bug is resolved or
+ verified"</SPAN
+> row.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Bugzilla adds the <SPAN
+CLASS="QUOTE"
+>"X-Bugzilla-Reason"</SPAN
+> header to
+ all bugmail it sends, describing the recipient's relationship
+ (AssignedTo, Reporter, QAContact, CC, or Voter) to the bug.
+ This header can be used to do further client-side filtering.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; Bugzilla has a feature called <SPAN
+CLASS="QUOTE"
+>"Users Watching"</SPAN
+>.
+ When you enter one or more comma-delineated user accounts (usually email
+ addresses) into the text entry box, you will receive a copy of all the
+ bugmail those users are sent (security settings permitting).
+ This powerful functionality enables seamless transitions as developers
+ change projects or users go on holiday.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; The ability to watch other users may not be available in all
+ Bugzilla installations. If you don't see this feature, and feel
+ that you need it, speak to your administrator.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; Each user listed in the <SPAN
+CLASS="QUOTE"
+>"Users watching you"</SPAN
+> field
+ has you listed in their <SPAN
+CLASS="QUOTE"
+>"Users to watch"</SPAN
+> list
+ and can get bugmail according to your relationship to the bug and
+ their <SPAN
+CLASS="QUOTE"
+>"Field/recipient specific options"</SPAN
+> setting.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="savedsearches"
+>5.10.3. Saved Searches</A
+></H3
+><P
+>&#13; On this tab you can view and run any Saved Searches that you have
+ created, and also any Saved Searches that other members of the group
+ defined in the "querysharegroup" parameter have shared.
+ Saved Searches can be added to the page footer from this screen.
+ If somebody is sharing a Search with a group she or he is allowed to
+ <A
+HREF="#groups"
+>assign users to</A
+>, the sharer may opt to have
+ the Search show up in the footer of the group's direct members by default.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="accountpreferences"
+>5.10.4. Name and Password</A
+></H3
+><P
+>On this tab, you can change your basic account information,
+ including your password, email address and real name. For security
+ reasons, in order to change anything on this page you must type your
+ <EM
+>current</EM
+> password into the <SPAN
+CLASS="QUOTE"
+>"Password"</SPAN
+>
+ field at the top of the page.
+ If you attempt to change your email address, a confirmation
+ email is sent to both the old and new addresses, with a link to use to
+ confirm the change. This helps to prevent account hijacking.</P
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="permissionsettings"
+>5.10.5. Permissions</A
+></H3
+><P
+>&#13; This is a purely informative page which outlines your current
+ permissions on this installation of Bugzilla.
+ </P
+><P
+>&#13; A complete list of permissions is below. Only users with
+ <EM
+>editusers</EM
+> privileges can change the permissions
+ of other users.
+ </P
+><P
+></P
+><DIV
+CLASS="variablelist"
+><DL
+><DT
+>admin</DT
+><DD
+><P
+>
+ Indicates user is an Administrator.
+ </P
+></DD
+><DT
+>bz_canusewhineatothers</DT
+><DD
+><P
+>
+ Indicates user can configure whine reports for other users.
+ </P
+></DD
+><DT
+>bz_canusewhines</DT
+><DD
+><P
+>
+ Indicates user can configure whine reports for self.
+ </P
+></DD
+><DT
+>bz_sudoers</DT
+><DD
+><P
+>
+ Indicates user can perform actions as other users.
+ </P
+></DD
+><DT
+>bz_sudo_protect</DT
+><DD
+><P
+>
+ Indicates user can not be impersonated by other users.
+ </P
+></DD
+><DT
+>canconfirm</DT
+><DD
+><P
+>
+ Indicates user can confirm a bug or mark it a duplicate.
+ </P
+></DD
+><DT
+>creategroups</DT
+><DD
+><P
+>
+ Indicates user can create and destroy groups.
+ </P
+></DD
+><DT
+>editbugs</DT
+><DD
+><P
+>
+ Indicates user can edit all bug fields.
+ </P
+></DD
+><DT
+>editclassifications</DT
+><DD
+><P
+>
+ Indicates user can create, destroy, and edit classifications.
+ </P
+></DD
+><DT
+>editcomponents</DT
+><DD
+><P
+>
+ Indicates user can create, destroy, and edit components.
+ </P
+></DD
+><DT
+>editkeywords</DT
+><DD
+><P
+>
+ Indicates user can create, destroy, and edit keywords.
+ </P
+></DD
+><DT
+>editusers</DT
+><DD
+><P
+>
+ Indicates user can edit or disable users.
+ </P
+></DD
+><DT
+>tweakparams</DT
+><DD
+><P
+>
+ Indicates user can change Parameters.
+ </P
+></DD
+></DL
+></DIV
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; For more information on how permissions work in Bugzilla (i.e. who can
+ change what), see <A
+HREF="#cust-change-permissions"
+>Section 6.4</A
+>.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="reporting"
+>5.11. Reports and Charts</A
+></H2
+><P
+>As well as the standard buglist, Bugzilla has two more ways of
+ viewing sets of bugs. These are the reports (which give different
+ views of the current state of the database) and charts (which plot
+ the changes in particular sets of bugs over time.)</P
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="reports"
+>5.11.1. Reports</A
+></H3
+><P
+>&#13; A report is a view of the current state of the bug database.
+ </P
+><P
+>&#13; You can run either an HTML-table-based report, or a graphical
+ line/pie/bar-chart-based one. The two have different pages to
+ define them, but are close cousins - once you've defined and
+ viewed a report, you can switch between any of the different
+ views of the data at will.
+ </P
+><P
+>&#13; Both report types are based on the idea of defining a set of bugs
+ using the standard search interface, and then choosing some
+ aspect of that set to plot on the horizontal and/or vertical axes.
+ You can also get a form of 3-dimensional report by choosing to have
+ multiple images or tables.
+ </P
+><P
+>&#13; So, for example, you could use the search form to choose "all
+ bugs in the WorldControl product", and then plot their severity
+ against their component to see which component had had the largest
+ number of bad bugs reported against it.
+ </P
+><P
+>&#13; Once you've defined your parameters and hit "Generate Report",
+ you can switch between HTML, CSV, Bar, Line and Pie. (Note: Pie
+ is only available if you didn't define a vertical axis, as pie
+ charts don't have one.) The other controls are fairly self-explanatory;
+ you can change the size of the image if you find text is overwriting
+ other text, or the bars are too thin to see.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="charts"
+>5.11.2. Charts</A
+></H3
+><P
+>&#13; A chart is a view of the state of the bug database over time.
+ </P
+><P
+>&#13; Bugzilla currently has two charting systems - Old Charts and New
+ Charts. Old Charts have been part of Bugzilla for a long time; they
+ chart each status and resolution for each product, and that's all.
+ They are deprecated, and going away soon - we won't say any more
+ about them.
+ New Charts are the future - they allow you to chart anything you
+ can define as a search.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Both charting forms require the administrator to set up the
+ data-gathering script. If you can't see any charts, ask them whether
+ they have done so.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; An individual line on a chart is called a data set.
+ All data sets are organised into categories and subcategories. The
+ data sets that Bugzilla defines automatically use the Product name
+ as a Category and Component names as Subcategories, but there is no
+ need for you to follow that naming scheme with your own charts if
+ you don't want to.
+ </P
+><P
+>&#13; Data sets may be public or private. Everyone sees public data sets in
+ the list, but only their creator sees private data sets. Only
+ administrators can make data sets public.
+ No two data sets, even two private ones, can have the same set of
+ category, subcategory and name. So if you are creating private data
+ sets, one idea is to have the Category be your username.
+ </P
+><DIV
+CLASS="section"
+><HR><H4
+CLASS="section"
+><A
+NAME="AEN2788"
+>5.11.2.1. Creating Charts</A
+></H4
+><P
+>&#13; You create a chart by selecting a number of data sets from the
+ list, and pressing Add To List for each. In the List Of Data Sets
+ To Plot, you can define the label that data set will have in the
+ chart's legend, and also ask Bugzilla to Sum a number of data sets
+ (e.g. you could Sum data sets representing RESOLVED, VERIFIED and
+ CLOSED in a particular product to get a data set representing all
+ the resolved bugs in that product.)
+ </P
+><P
+>&#13; If you've erroneously added a data set to the list, select it
+ using the checkbox and click Remove. Once you add more than one
+ data set, a "Grand Total" line
+ automatically appears at the bottom of the list. If you don't want
+ this, simply remove it as you would remove any other line.
+ </P
+><P
+>&#13; You may also choose to plot only over a certain date range, and
+ to cumulate the results - that is, to plot each one using the
+ previous one as a baseline, so the top line gives a sum of all
+ the data sets. It's easier to try than to explain :-)
+ </P
+><P
+>&#13; Once a data set is in the list, one can also perform certain
+ actions on it. For example, one can edit the
+ data set's parameters (name, frequency etc.) if it's one you
+ created or if you are an administrator.
+ </P
+><P
+>&#13; Once you are happy, click Chart This List to see the chart.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H4
+CLASS="section"
+><A
+NAME="charts-new-series"
+>5.11.2.2. Creating New Data Sets</A
+></H4
+><P
+>&#13; You may also create new data sets of your own. To do this,
+ click the "create a new data set" link on the Create Chart page.
+ This takes you to a search-like interface where you can define
+ the search that Bugzilla will plot. At the bottom of the page,
+ you choose the category, sub-category and name of your new
+ data set.
+ </P
+><P
+>&#13; If you have sufficient permissions, you can make the data set public,
+ and reduce the frequency of data collection to less than the default
+ seven days.
+ </P
+></DIV
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="flags"
+>5.12. Flags</A
+></H2
+><P
+>&#13; A flag is a kind of status that can be set on bugs or attachments
+ to indicate that the bugs/attachments are in a certain state.
+ Each installation can define its own set of flags that can be set
+ on bugs or attachments.
+ </P
+><P
+>&#13; If your installation has defined a flag, you can set or unset that flag,
+ and if your administrator has enabled requesting of flags, you can submit
+ a request for another user to set the flag.
+ </P
+><P
+>&#13; To set a flag, select either "+" or "-" from the drop-down menu next to
+ the name of the flag in the "Flags" list. The meaning of these values are
+ flag-specific and thus cannot be described in this documentation,
+ but by way of example, setting a flag named "review" to "+" may indicate
+ that the bug/attachment has passed review, while setting it to "-"
+ may indicate that the bug/attachment has failed review.
+ </P
+><P
+>&#13; To unset a flag, click its drop-down menu and select the blank value.
+ Note that marking an attachment as obsolete automatically cancels all
+ pending requests for the attachment.
+ </P
+><P
+>&#13; If your administrator has enabled requests for a flag, request a flag
+ by selecting "?" from the drop-down menu and then entering the username
+ of the user you want to set the flag in the text field next to the menu.
+ </P
+><P
+>&#13; A set flag appears in bug reports and on "edit attachment" pages with the
+ abbreviated username of the user who set the flag prepended to the
+ flag name. For example, if Jack sets a "review" flag to "+", it appears
+ as Jack: review [ + ]
+ </P
+><P
+>&#13; A requested flag appears with the user who requested the flag prepended
+ to the flag name and the user who has been requested to set the flag
+ appended to the flag name within parentheses. For example, if Jack
+ asks Jill for review, it appears as Jack: review [ ? ] (Jill).
+ </P
+><P
+>&#13; You can browse through open requests made of you and by you by selecting
+ 'My Requests' from the footer. You can also look at open requests limited
+ by other requesters, requestees, products, components, and flag names from
+ this page. Note that you can use '-' for requestee to specify flags with
+ 'no requestee' set.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="whining"
+>5.13. Whining</A
+></H2
+><P
+>&#13; Whining is a feature in Bugzilla that can regularly annoy users at
+ specified times. Using this feature, users can execute saved searches
+ at specific times (i.e. the 15th of the month at midnight) or at
+ regular intervals (i.e. every 15 minutes on Sundays). The results of the
+ searches are sent to the user, either as a single email or as one email
+ per bug, along with some descriptive text.
+ </P
+><DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Throughout this section it will be assumed that all users are members
+ of the bz_canusewhines group, membership in which is required in order
+ to use the Whining system. You can easily make all users members of
+ the bz_canusewhines group by setting the User RegExp to ".*" (without
+ the quotes).
+ </P
+><P
+>&#13; Also worth noting is the bz_canusewhineatothers group. Members of this
+ group can create whines for any user or group in Bugzilla using a
+ extended form of the whining interface. Features only available to
+ members of the bz_canusewhineatothers group will be noted in the
+ appropriate places.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; For whining to work, a special Perl script must be executed at regular
+ intervals. More information on this is available in
+ <A
+HREF="#installation-whining"
+>Section 2.3.3</A
+>.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; This section does not cover the whineatnews.pl script. See
+ <A
+HREF="#installation-whining-cron"
+>Section 2.3.2</A
+> for more information on
+ The Whining Cron.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="whining-overview"
+>5.13.1. The Event</A
+></H3
+><P
+>&#13; The whining system defines an "Event" as one or more queries being
+ executed at regular intervals, with the results of said queries (if
+ there are any) being emailed to the user. Events are created by
+ clicking on the "Add new event" button.
+ </P
+><P
+>&#13; Once a new event is created, the first thing to set is the "Email
+ subject line". The contents of this field will be used in the subject
+ line of every email generated by this event. In addition to setting a
+ subject, space is provided to enter some descriptive text that will be
+ included at the top of each message (to help you in understanding why
+ you received the email in the first place).
+ </P
+><P
+>&#13; The next step is to specify when the Event is to be run (the Schedule)
+ and what searches are to be performed (the Searches).
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="whining-schedule"
+>5.13.2. Whining Schedule</A
+></H3
+><P
+>&#13; Each whining event is associated with zero or more schedules. A
+ schedule is used to specify when the query (specified below) is to be
+ run. A new event starts out with no schedules (which means it will
+ never run, as it is not scheduled to run). To add a schedule, press
+ the "Add a new schedule" button.
+ </P
+><P
+>&#13; Each schedule includes an interval, which you use to tell Bugzilla
+ when the event should be run. An event can be run on certain days of
+ the week, certain days of the month, during weekdays (defined as
+ Monday through Friday), or every day.
+ </P
+><DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Be careful if you set your event to run on the 29th, 30th, or 31st of
+ the month, as your event may not run exactly when expected. If you
+ want your event to run on the last day of the month, select "Last day
+ of the month" as the interval.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; Once you have specified the day(s) on which the event is to be run, you
+ should now specify the time at which the event is to be run. You can
+ have the event run at a certain hour on the specified day(s), or
+ every hour, half-hour, or quarter-hour on the specified day(s).
+ </P
+><P
+>&#13; If a single schedule does not execute an event as many times as you
+ would want, you can create another schedule for the same event. For
+ example, if you want to run an event on days whose numbers are
+ divisible by seven, you would need to add four schedules to the event,
+ setting the schedules to run on the 7th, 14th, 21st, and 28th (one day
+ per schedule) at whatever time (or times) you choose.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; If you are a member of the bz_canusewhineatothers group, then you
+ will be presented with another option: "Mail to". Using this you
+ can control who will receive the emails generated by this event. You
+ can choose to send the emails to a single user (identified by email
+ address) or a single group (identified by group name). To send to
+ multiple users or groups, create a new schedule for each additional
+ user/group.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="whining-query"
+>5.13.3. Whining Searches</A
+></H3
+><P
+>&#13; Each whining event is associated with zero or more searches. A search
+ is any saved search to be run as part of the specified schedule (see
+ above). You start out without any searches associated with the event
+ (which means that the event will not run, as there will never be any
+ results to return). To add a search, press the "Include search" button.
+ </P
+><P
+>&#13; The first field to examine in your newly added search is the Sort field.
+ Searches are run, and results included, in the order specified by the
+ Sort field. Searches with smaller Sort values will run before searches
+ with bigger Sort values.
+ </P
+><P
+>&#13; The next field to examine is the Search field. This is where you
+ choose the actual search that is to be run. Instead of defining search
+ parameters here, you are asked to choose from the list of saved
+ searches (the same list that appears at the bottom of every Bugzilla
+ page). You are only allowed to choose from searches that you have
+ saved yourself (the default saved search, "My Bugs", is not a valid
+ choice). If you do not have any saved searches, you can take this
+ opportunity to create one (see <A
+HREF="#list"
+>Section 5.5.4</A
+>).
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; When running queries, the whining system acts as if you are the user
+ executing the query. This means that the whining system will ignore
+ bugs that match your query, but that you can not access.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; Once you have chosen the saved search to be executed, give the query a
+ descriptive title. This title will appear in the email, above the
+ results of the query. If you choose "One message per bug", the query
+ title will appear at the top of each email that contains a bug matching
+ your query.
+ </P
+><P
+>&#13; Finally, decide if the results of the query should be sent in a single
+ email, or if each bug should appear in its own email.
+ </P
+><DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Think carefully before checking the "One message per bug" box. If
+ you create a query that matches thousands of bugs, you will receive
+ thousands of emails!
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="AEN2848"
+>5.13.4. Saving Your Changes</A
+></H3
+><P
+>&#13; Once you have defined at least one schedule, and created at least one
+ query, go ahead and "Update/Commit". This will save your Event and make
+ it available for immediate execution.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; If you ever feel like deleting your event, you may do so using the
+ "Remove Event" button in the upper-right corner of each Event. You
+ can also modify an existing event, so long as you "Update/Commit"
+ after completing your modifications.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+></DIV
+></DIV
+><DIV
+CLASS="chapter"
+><HR><H1
+><A
+NAME="customization"
+></A
+>Chapter 6. Customizing Bugzilla</H1
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="extensions"
+>6.1. Bugzilla Extensions</A
+></H2
+><P
+>&#13; One of the best ways to customize Bugzilla is by writing a Bugzilla
+ Extension. Bugzilla Extensions let you modify both the code and
+ UI of Bugzilla in a way that can be distributed to other Bugzilla
+ users and ported forward to future versions of Bugzilla with minimal
+ effort.
+ </P
+><P
+>&#13; See the <A
+HREF="api/Bugzilla/Extension.html"
+TARGET="_top"
+>Bugzilla Extension
+ documentation</A
+> for information on how to write an Extension.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="cust-skins"
+>6.2. Custom Skins</A
+></H2
+><P
+>&#13; Bugzilla allows you to have multiple skins. These are custom CSS and possibly
+ also custom images for Bugzilla. To create a new custom skin, you have two
+ choices:
+ <P
+></P
+><UL
+><LI
+><P
+>&#13; Make a single CSS file, and put it in the
+ <TT
+CLASS="filename"
+>skins/contrib</TT
+> directory.
+ </P
+></LI
+><LI
+><P
+>&#13; Make a directory that contains all the same CSS file
+ names as <TT
+CLASS="filename"
+>skins/standard/</TT
+>, and put
+ your directory in <TT
+CLASS="filename"
+>skins/contrib/</TT
+>.
+ </P
+></LI
+></UL
+>
+ </P
+><P
+>&#13; After you put the file or the directory there, make sure to run checksetup.pl
+ so that it can reset the file permissions correctly.
+ </P
+><P
+>&#13; After you have installed the new skin, it will show up as an option in the
+ user's General Preferences. If you would like to force a particular skin on all
+ users, just select it in the Default Preferences and then uncheck "Enabled" on
+ the preference.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="cust-templates"
+>6.3. Template Customization</A
+></H2
+><P
+>&#13; Administrators can configure the look and feel of Bugzilla without
+ having to edit Perl files or face the nightmare of massive merge
+ conflicts when they upgrade to a newer version in the future.
+ </P
+><P
+>&#13; Templatization also makes localized versions of Bugzilla possible,
+ for the first time. It's possible to have Bugzilla's UI language
+ determined by the user's browser. More information is available in
+ <A
+HREF="#template-http-accept"
+>Section 6.3.6</A
+>.
+ </P
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="template-directory"
+>6.3.1. Template Directory Structure</A
+></H3
+><P
+>&#13; The template directory structure starts with top level directory
+ named <TT
+CLASS="filename"
+>template</TT
+>, which contains a directory
+ for each installed localization. The next level defines the
+ language used in the templates. Bugzilla comes with English
+ templates, so the directory name is <TT
+CLASS="filename"
+>en</TT
+>,
+ and we will discuss <TT
+CLASS="filename"
+>template/en</TT
+> throughout
+ the documentation. Below <TT
+CLASS="filename"
+>template/en</TT
+> is the
+ <TT
+CLASS="filename"
+>default</TT
+> directory, which contains all the
+ standard templates shipped with Bugzilla.
+ </P
+><DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; A directory <TT
+CLASS="filename"
+>data/templates</TT
+> also exists;
+ this is where Template Toolkit puts the compiled versions of
+ the templates from either the default or custom directories.
+ <EM
+>Do not</EM
+> directly edit the files in this
+ directory, or all your changes will be lost the next time
+ Template Toolkit recompiles the templates.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="template-method"
+>6.3.2. Choosing a Customization Method</A
+></H3
+><P
+>&#13; If you want to edit Bugzilla's templates, the first decision
+ you must make is how you want to go about doing so. There are two
+ choices, and which you use depends mainly on the scope of your
+ modifications, and the method you plan to use to upgrade Bugzilla.
+ </P
+><P
+>&#13; The first method of making customizations is to directly edit the
+ templates found in <TT
+CLASS="filename"
+>template/en/default</TT
+>.
+ This is probably the best way to go about it if you are going to
+ be upgrading Bugzilla through CVS, because if you then execute
+ a <B
+CLASS="command"
+>cvs update</B
+>, any changes you have made will
+ be merged automagically with the updated versions.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; If you use this method, and CVS conflicts occur during an
+ update, the conflicted templates (and possibly other parts
+ of your installation) will not work until they are resolved.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; The second method is to copy the templates to be modified
+ into a mirrored directory structure under
+ <TT
+CLASS="filename"
+>template/en/custom</TT
+>. Templates in this
+ directory structure automatically override any identically-named
+ and identically-located templates in the
+ <TT
+CLASS="filename"
+>default</TT
+> directory.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; The <TT
+CLASS="filename"
+>custom</TT
+> directory does not exist
+ at first and must be created if you want to use it.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; The second method of customization should be used if you
+ use the overwriting method of upgrade, because otherwise
+ your changes will be lost. This method may also be better if
+ you are using the CVS method of upgrading and are going to make major
+ changes, because it is guaranteed that the contents of this directory
+ will not be touched during an upgrade, and you can then decide whether
+ to continue using your own templates, or make the effort to merge your
+ changes into the new versions by hand.
+ </P
+><P
+>&#13; Using this method, your installation may break if incompatible
+ changes are made to the template interface. Such changes should
+ be documented in the release notes, provided you are using a
+ stable release of Bugzilla. If you use using unstable code, you will
+ need to deal with this one yourself, although if possible the changes
+ will be mentioned before they occur in the deprecations section of the
+ previous stable release's release notes.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Regardless of which method you choose, it is recommended that
+ you run <B
+CLASS="command"
+>./checksetup.pl</B
+> after
+ editing any templates in the <TT
+CLASS="filename"
+>template/en/default</TT
+>
+ directory, and after creating or editing any templates in the
+ <TT
+CLASS="filename"
+>custom</TT
+> directory.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; It is <EM
+>required</EM
+> that you run
+ <B
+CLASS="command"
+>./checksetup.pl</B
+> after creating a new
+ template in the <TT
+CLASS="filename"
+>custom</TT
+> directory. Failure
+ to do so will raise an incomprehensible error message.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="template-edit"
+>6.3.3. How To Edit Templates</A
+></H3
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; If you are making template changes that you intend on submitting back
+ for inclusion in standard Bugzilla, you should read the relevant
+ sections of the
+ <A
+HREF="http://www.bugzilla.org/docs/developer.html"
+TARGET="_top"
+>Developers'
+ Guide</A
+>.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; The syntax of the Template Toolkit language is beyond the scope of
+ this guide. It's reasonably easy to pick up by looking at the current
+ templates; or, you can read the manual, available on the
+ <A
+HREF="http://www.template-toolkit.org"
+TARGET="_top"
+>Template Toolkit home
+ page</A
+>.
+ </P
+><P
+>&#13; One thing you should take particular care about is the need
+ to properly HTML filter data that has been passed into the template.
+ This means that if the data can possibly contain special HTML characters
+ such as &#60;, and the data was not intended to be HTML, they need to be
+ converted to entity form, i.e. &#38;lt;. You use the 'html' filter in the
+ Template Toolkit to do this. If you forget, you may open up
+ your installation to cross-site scripting attacks.
+ </P
+><P
+>&#13; Also note that Bugzilla adds a few filters of its own, that are not
+ in standard Template Toolkit. In particular, the 'url_quote' filter
+ can convert characters that are illegal or have special meaning in URLs,
+ such as &#38;, to the encoded form, i.e. %26. This actually encodes most
+ characters (but not the common ones such as letters and numbers and so
+ on), including the HTML-special characters, so there's never a need to
+ HTML filter afterwards.
+ </P
+><P
+>&#13; Editing templates is a good way of doing a <SPAN
+CLASS="QUOTE"
+>"poor man's custom
+ fields"</SPAN
+>.
+ For example, if you don't use the Status Whiteboard, but want to have
+ a free-form text entry box for <SPAN
+CLASS="QUOTE"
+>"Build Identifier"</SPAN
+>,
+ then you can just
+ edit the templates to change the field labels. It's still be called
+ status_whiteboard internally, but your users don't need to know that.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="template-formats"
+>6.3.4. Template Formats and Types</A
+></H3
+><P
+>&#13; Some CGI's have the ability to use more than one template. For example,
+ <TT
+CLASS="filename"
+>buglist.cgi</TT
+> can output itself as RDF, or as two
+ formats of HTML (complex and simple). The mechanism that provides this
+ feature is extensible.
+ </P
+><P
+>&#13; Bugzilla can support different types of output, which again can have
+ multiple formats. In order to request a certain type, you can append
+ the &#38;ctype=&#60;contenttype&#62; (such as rdf or html) to the
+ <TT
+CLASS="filename"
+>&#60;cginame&#62;.cgi</TT
+> URL. If you would like to
+ retrieve a certain format, you can use the &#38;format=&#60;format&#62;
+ (such as simple or complex) in the URL.
+ </P
+><P
+>&#13; To see if a CGI supports multiple output formats and types, grep the
+ CGI for <SPAN
+CLASS="QUOTE"
+>"get_format"</SPAN
+>. If it's not present, adding
+ multiple format/type support isn't too hard - see how it's done in
+ other CGIs, e.g. config.cgi.
+ </P
+><P
+>&#13; To make a new format template for a CGI which supports this,
+ open a current template for
+ that CGI and take note of the INTERFACE comment (if present.) This
+ comment defines what variables are passed into this template. If
+ there isn't one, I'm afraid you'll have to read the template and
+ the code to find out what information you get.
+ </P
+><P
+>&#13; Write your template in whatever markup or text style is appropriate.
+ </P
+><P
+>&#13; You now need to decide what content type you want your template
+ served as. The content types are defined in the
+ <TT
+CLASS="filename"
+>Bugzilla/Constants.pm</TT
+> file in the
+ <TT
+CLASS="filename"
+>contenttypes</TT
+>
+ constant. If your content type is not there, add it. Remember
+ the three- or four-letter tag assigned to your content type.
+ This tag will be part of the template filename.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; After adding or changing a content type, it's suitable to edit
+ <TT
+CLASS="filename"
+>Bugzilla/Constants.pm</TT
+> in order to reflect
+ the changes. Also, the file should be kept up to date after an
+ upgrade if content types have been customized in the past.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; Save the template as <TT
+CLASS="filename"
+>&#60;stubname&#62;-&#60;formatname&#62;.&#60;contenttypetag&#62;.tmpl</TT
+>.
+ Try out the template by calling the CGI as
+ <TT
+CLASS="filename"
+>&#60;cginame&#62;.cgi?format=&#60;formatname&#62;&#38;ctype=&#60;type&#62;</TT
+> .
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="template-specific"
+>6.3.5. Particular Templates</A
+></H3
+><P
+>&#13; There are a few templates you may be particularly interested in
+ customizing for your installation.
+ </P
+><P
+>&#13; <B
+CLASS="command"
+>index.html.tmpl</B
+>:
+ This is the Bugzilla front page.
+ </P
+><P
+>&#13; <B
+CLASS="command"
+>global/header.html.tmpl</B
+>:
+ This defines the header that goes on all Bugzilla pages.
+ The header includes the banner, which is what appears to users
+ and is probably what you want to edit instead. However the
+ header also includes the HTML HEAD section, so you could for
+ example add a stylesheet or META tag by editing the header.
+ </P
+><P
+>&#13; <B
+CLASS="command"
+>global/banner.html.tmpl</B
+>:
+ This contains the <SPAN
+CLASS="QUOTE"
+>"banner"</SPAN
+>, the part of the header
+ that appears
+ at the top of all Bugzilla pages. The default banner is reasonably
+ barren, so you'll probably want to customize this to give your
+ installation a distinctive look and feel. It is recommended you
+ preserve the Bugzilla version number in some form so the version
+ you are running can be determined, and users know what docs to read.
+ </P
+><P
+>&#13; <B
+CLASS="command"
+>global/footer.html.tmpl</B
+>:
+ This defines the footer that goes on all Bugzilla pages. Editing
+ this is another way to quickly get a distinctive look and feel for
+ your Bugzilla installation.
+ </P
+><P
+>&#13; <B
+CLASS="command"
+>global/variables.none.tmpl</B
+>:
+ This defines a list of terms that may be changed in order to
+ <SPAN
+CLASS="QUOTE"
+>"brand"</SPAN
+> the Bugzilla instance In this way, terms
+ like <SPAN
+CLASS="QUOTE"
+>"bugs"</SPAN
+> can be replaced with <SPAN
+CLASS="QUOTE"
+>"issues"</SPAN
+>
+ across the whole Bugzilla installation. The name
+ <SPAN
+CLASS="QUOTE"
+>"Bugzilla"</SPAN
+> and other words can be customized as well.
+ </P
+><P
+>&#13; <B
+CLASS="command"
+>list/table.html.tmpl</B
+>:
+ This template controls the appearance of the bug lists created
+ by Bugzilla. Editing this template allows per-column control of
+ the width and title of a column, the maximum display length of
+ each entry, and the wrap behaviour of long entries.
+ For long bug lists, Bugzilla inserts a 'break' every 100 bugs by
+ default; this behaviour is also controlled by this template, and
+ that value can be modified here.
+ </P
+><P
+>&#13; <B
+CLASS="command"
+>bug/create/user-message.html.tmpl</B
+>:
+ This is a message that appears near the top of the bug reporting page.
+ By modifying this, you can tell your users how they should report
+ bugs.
+ </P
+><P
+>&#13; <B
+CLASS="command"
+>bug/process/midair.html.tmpl</B
+>:
+ This is the page used if two people submit simultaneous changes to the
+ same bug. The second person to submit their changes will get this page
+ to tell them what the first person did, and ask if they wish to
+ overwrite those changes or go back and revisit the bug. The default
+ title and header on this page read "Mid-air collision detected!" If
+ you work in the aviation industry, or other environment where this
+ might be found offensive (yes, we have true stories of this happening)
+ you'll want to change this to something more appropriate for your
+ environment.
+ </P
+><P
+>&#13; <B
+CLASS="command"
+>bug/create/create.html.tmpl</B
+> and
+ <B
+CLASS="command"
+>bug/create/comment.txt.tmpl</B
+>:
+ You may not wish to go to the effort of creating custom fields in
+ Bugzilla, yet you want to make sure that each bug report contains
+ a number of pieces of important information for which there is not
+ a special field. The bug entry system has been designed in an
+ extensible fashion to enable you to add arbitrary HTML widgets,
+ such as drop-down lists or textboxes, to the bug entry page
+ and have their values appear formatted in the initial comment.
+ A hidden field that indicates the format should be added inside
+ the form in order to make the template functional. Its value should
+ be the suffix of the template filename. For example, if the file
+ is called <TT
+CLASS="filename"
+>create-cust.html.tmpl</TT
+>, then
+ <TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#60;input type="hidden" name="format" value="cust"&#62;</PRE
+></FONT
+></TD
+></TR
+></TABLE
+>
+ should be used inside the form.
+ </P
+><P
+>
+ An example of this is the mozilla.org
+ <A
+HREF="http://landfill.bugzilla.org/bugzilla-tip/enter_bug.cgi?product=WorldControl&#38;format=guided"
+TARGET="_top"
+>guided
+ bug submission form</A
+>. The code for this comes with the Bugzilla
+ distribution as an example for you to copy. It can be found in the
+ files
+ <TT
+CLASS="filename"
+>create-guided.html.tmpl</TT
+> and
+ <TT
+CLASS="filename"
+>comment-guided.html.tmpl</TT
+>.
+ </P
+><P
+>&#13; So to use this feature, create a custom template for
+ <TT
+CLASS="filename"
+>enter_bug.cgi</TT
+>. The default template, on which you
+ could base it, is
+ <TT
+CLASS="filename"
+>custom/bug/create/create.html.tmpl</TT
+>.
+ Call it <TT
+CLASS="filename"
+>create-&#60;formatname&#62;.html.tmpl</TT
+>, and
+ in it, add widgets for each piece of information you'd like
+ collected - such as a build number, or set of steps to reproduce.
+ </P
+><P
+>&#13; Then, create a template like
+ <TT
+CLASS="filename"
+>custom/bug/create/comment.txt.tmpl</TT
+>, and call it
+ <TT
+CLASS="filename"
+>comment-&#60;formatname&#62;.txt.tmpl</TT
+>. This
+ template should reference the form fields you have created using
+ the syntax <TT
+CLASS="filename"
+>[% form.&#60;fieldname&#62; %]</TT
+>. When a
+ bug report is
+ submitted, the initial comment attached to the bug report will be
+ formatted according to the layout of this template.
+ </P
+><P
+>&#13; For example, if your custom enter_bug template had a field
+ <TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#60;input type="text" name="buildid" size="30"&#62;</PRE
+></FONT
+></TD
+></TR
+></TABLE
+>
+ and then your comment.txt.tmpl had
+ <TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>BuildID: [% form.buildid %]</PRE
+></FONT
+></TD
+></TR
+></TABLE
+>
+ then something like
+ <TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>BuildID: 20020303</PRE
+></FONT
+></TD
+></TR
+></TABLE
+>
+ would appear in the initial comment.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H3
+CLASS="section"
+><A
+NAME="template-http-accept"
+>6.3.6. Configuring Bugzilla to Detect the User's Language</A
+></H3
+><P
+>Bugzilla honours the user's Accept: HTTP header. You can install
+ templates in other languages, and Bugzilla will pick the most appropriate
+ according to a priority order defined by you. Many
+ language templates can be obtained from <A
+HREF="http://www.bugzilla.org/download.html#localizations"
+TARGET="_top"
+>http://www.bugzilla.org/download.html#localizations</A
+>. Instructions
+ for submitting new languages are also available from that location.
+ </P
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="cust-change-permissions"
+>6.4. Customizing Who Can Change What</A
+></H2
+><DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; This feature should be considered experimental; the Bugzilla code you
+ will be changing is not stable, and could change or move between
+ versions. Be aware that if you make modifications as outlined here,
+ you may have
+ to re-make them or port them if Bugzilla changes internally between
+ versions, and you upgrade.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; Companies often have rules about which employees, or classes of employees,
+ are allowed to change certain things in the bug system. For example,
+ only the bug's designated QA Contact may be allowed to VERIFY the bug.
+ Bugzilla has been
+ designed to make it easy for you to write your own custom rules to define
+ who is allowed to make what sorts of value transition.
+ </P
+><P
+>&#13; By default, assignees, QA owners and users
+ with <EM
+>editbugs</EM
+> privileges can edit all fields of bugs,
+ except group restrictions (unless they are members of the groups they
+ are trying to change). Bug reporters also have the ability to edit some
+ fields, but in a more restrictive manner. Other users, without
+ <EM
+>editbugs</EM
+> privileges, can not edit
+ bugs, except to comment and add themselves to the CC list.
+ </P
+><P
+>&#13; For maximum flexibility, customizing this means editing Bugzilla's Perl
+ code. This gives the administrator complete control over exactly who is
+ allowed to do what. The relevant method is called
+ <TT
+CLASS="filename"
+>check_can_change_field()</TT
+>,
+ and is found in <TT
+CLASS="filename"
+>Bug.pm</TT
+> in your
+ Bugzilla/ directory. If you open that file and search for
+ <SPAN
+CLASS="QUOTE"
+>"sub check_can_change_field"</SPAN
+>, you'll find it.
+ </P
+><P
+>&#13; This function has been carefully commented to allow you to see exactly
+ how it works, and give you an idea of how to make changes to it.
+ Certain marked sections should not be changed - these are
+ the <SPAN
+CLASS="QUOTE"
+>"plumbing"</SPAN
+> which makes the rest of the function work.
+ In between those sections, you'll find snippets of code like:
+ <TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+> # Allow the assignee to change anything.
+ if ($ownerid eq $whoid) {
+ return 1;
+ }</PRE
+></FONT
+></TD
+></TR
+></TABLE
+>
+ It's fairly obvious what this piece of code does.
+ </P
+><P
+>&#13; So, how does one go about changing this function? Well, simple changes
+ can be made just by removing pieces - for example, if you wanted to
+ prevent any user adding a comment to a bug, just remove the lines marked
+ <SPAN
+CLASS="QUOTE"
+>"Allow anyone to change comments."</SPAN
+> If you don't want the
+ Reporter to have any special rights on bugs they have filed, just
+ remove the entire section that deals with the Reporter.
+ </P
+><P
+>&#13; More complex customizations are not much harder. Basically, you add
+ a check in the right place in the function, i.e. after all the variables
+ you are using have been set up. So, don't look at $ownerid before
+ $ownerid has been obtained from the database. You can either add a
+ positive check, which returns 1 (allow) if certain conditions are true,
+ or a negative check, which returns 0 (deny.) E.g.:
+ <TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+> if ($field eq "qacontact") {
+ if (Bugzilla-&#62;user-&#62;in_group("quality_assurance")) {
+ return 1;
+ }
+ else {
+ return 0;
+ }
+ }</PRE
+></FONT
+></TD
+></TR
+></TABLE
+>
+ This says that only users in the group "quality_assurance" can change
+ the QA Contact field of a bug.
+ </P
+><P
+>&#13; Getting more weird:
+ <TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+> if (($field eq "priority") &#38;&#38;
+ (Bugzilla-&#62;user-&#62;email =~ /.*\@example\.com$/))
+ {
+ if ($oldvalue eq "P1") {
+ return 1;
+ }
+ else {
+ return 0;
+ }
+ }</PRE
+></FONT
+></TD
+></TR
+></TABLE
+>
+ This says that if the user is trying to change the priority field,
+ and their email address is @example.com, they can only do so if the
+ old value of the field was "P1". Not very useful, but illustrative.
+ </P
+><DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; If you are modifying <TT
+CLASS="filename"
+>process_bug.cgi</TT
+> in any
+ way, do not change the code that is bounded by DO_NOT_CHANGE blocks.
+ Doing so could compromise security, or cause your installation to
+ stop working entirely.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; For a list of possible field names, look at the bugs table in the
+ database. If you need help writing custom rules for your organization,
+ ask in the newsgroup.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="integration"
+>6.5. Integrating Bugzilla with Third-Party Tools</A
+></H2
+><P
+>&#13; Many utilities and applications can integrate with Bugzilla,
+ either on the client- or server-side. None of them are maintained
+ by the Bugzilla community, nor are they tested during our
+ QA tests, so use them at your own risk. They are listed at
+ <A
+HREF="https://wiki.mozilla.org/Bugzilla:Addons"
+TARGET="_top"
+>https://wiki.mozilla.org/Bugzilla:Addons</A
+>.
+ </P
+></DIV
+></DIV
+><DIV
+CLASS="appendix"
+><HR><H1
+><A
+NAME="troubleshooting"
+></A
+>Appendix A. Troubleshooting</H1
+><P
+>This section gives solutions to common Bugzilla installation
+ problems. If none of the section headings seems to match your
+ problem, read the general advice.
+ </P
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="general-advice"
+>A.1. General Advice</A
+></H2
+><P
+>If you can't get <TT
+CLASS="filename"
+>checksetup.pl</TT
+> to run to
+ completion, it normally explains what's wrong and how to fix it.
+ If you can't work it out, or if it's being uncommunicative, post
+ the errors in the
+ <A
+HREF="news://news.mozilla.org/mozilla.support.bugzilla"
+TARGET="_top"
+>mozilla.support.bugzilla</A
+>
+ newsgroup.
+ </P
+><P
+>If you have made it all the way through
+ <A
+HREF="#installation"
+>Section 2.1</A
+> (Installation) and
+ <A
+HREF="#configuration"
+>Section 2.2</A
+> (Configuration) but accessing the Bugzilla
+ URL doesn't work, the first thing to do is to check your web server error
+ log. For Apache, this is often located at
+ <TT
+CLASS="filename"
+>/etc/logs/httpd/error_log</TT
+>. The error messages
+ you see may be self-explanatory enough to enable you to diagnose and
+ fix the problem. If not, see below for some commonly-encountered
+ errors. If that doesn't help, post the errors to the newsgroup.
+ </P
+><P
+>&#13; Bugzilla can also log all user-based errors (and many code-based errors)
+ that occur, without polluting the web server's error log. To enable
+ Bugzilla error logging, create a file that Bugzilla can write to, named
+ <TT
+CLASS="filename"
+>errorlog</TT
+>, in the Bugzilla <TT
+CLASS="filename"
+>data</TT
+>
+ directory. Errors will be logged as they occur, and will include the type
+ of the error, the IP address and username (if available) of the user who
+ triggered the error, and the values of all environment variables; if a
+ form was being submitted, the data in the form will also be included.
+ To disable error logging, delete or rename the
+ <TT
+CLASS="filename"
+>errorlog</TT
+> file.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="trbl-testserver"
+>A.2. The Apache web server is not serving Bugzilla pages</A
+></H2
+><P
+>After you have run <B
+CLASS="command"
+>checksetup.pl</B
+> twice,
+ run <B
+CLASS="command"
+>testserver.pl http://yoursite.yourdomain/yoururl</B
+>
+ to confirm that your web server is configured properly for
+ Bugzilla.
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#13;<SAMP
+CLASS="prompt"
+>bash$</SAMP
+> ./testserver.pl http://landfill.bugzilla.org/bugzilla-tip
+TEST-OK Webserver is running under group id in $webservergroup.
+TEST-OK Got ant picture.
+TEST-OK Webserver is executing CGIs.
+TEST-OK Webserver is preventing fetch of http://landfill.bugzilla.org/bugzilla-tip/localconfig.
+</PRE
+></FONT
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="trbl-perlmodule"
+>A.3. I installed a Perl module, but
+ <TT
+CLASS="filename"
+>checksetup.pl</TT
+> claims it's not installed!</A
+></H2
+><P
+>This could be caused by one of two things:</P
+><P
+></P
+><OL
+TYPE="1"
+><LI
+><P
+>You have two versions of Perl on your machine. You are installing
+ modules into one, and Bugzilla is using the other. Rerun the CPAN
+ commands (or manual compile) using the full path to Perl from the
+ top of <TT
+CLASS="filename"
+>checksetup.pl</TT
+>. This will make sure you
+ are installing the modules in the right place.
+ </P
+></LI
+><LI
+><P
+>The permissions on your library directories are set incorrectly.
+ They must, at the very least, be readable by the web server user or
+ group. It is recommended that they be world readable.
+ </P
+></LI
+></OL
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="trbl-dbdSponge"
+>A.4. DBD::Sponge::db prepare failed</A
+></H2
+><P
+>The following error message may appear due to a bug in DBD::mysql
+ (over which the Bugzilla team have no control):
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+> DBD::Sponge::db prepare failed: Cannot determine NUM_OF_FIELDS at D:/Perl/site/lib/DBD/mysql.pm line 248.
+ SV = NULL(0x0) at 0x20fc444
+ REFCNT = 1
+ FLAGS = (PADBUSY,PADMY)
+</PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>To fix this, go to
+ <TT
+CLASS="filename"
+>&#60;path-to-perl&#62;/lib/DBD/sponge.pm</TT
+>
+ in your Perl installation and replace
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+> my $numFields;
+ if ($attribs-&#62;{'NUM_OF_FIELDS'}) {
+ $numFields = $attribs-&#62;{'NUM_OF_FIELDS'};
+ } elsif ($attribs-&#62;{'NAME'}) {
+ $numFields = @{$attribs-&#62;{NAME}};
+</PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>with</P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+> my $numFields;
+ if ($attribs-&#62;{'NUM_OF_FIELDS'}) {
+ $numFields = $attribs-&#62;{'NUM_OF_FIELDS'};
+ } elsif ($attribs-&#62;{'NAMES'}) {
+ $numFields = @{$attribs-&#62;{NAMES}};
+</PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>(note the S added to NAME.)</P
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="paranoid-security"
+>A.5. cannot chdir(/var/spool/mqueue)</A
+></H2
+><P
+>If you are installing Bugzilla on SuSE Linux, or some other
+ distributions with <SPAN
+CLASS="QUOTE"
+>"paranoid"</SPAN
+> security options, it is
+ possible that the checksetup.pl script may fail with the error:
+<TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>cannot chdir(/var/spool/mqueue): Permission denied
+</PRE
+></FONT
+></TD
+></TR
+></TABLE
+>
+ </P
+><P
+>This is because your <TT
+CLASS="filename"
+>/var/spool/mqueue</TT
+>
+ directory has a mode of <SAMP
+CLASS="computeroutput"
+>drwx------</SAMP
+>.
+ Type <B
+CLASS="command"
+>chmod 755 <TT
+CLASS="filename"
+>/var/spool/mqueue</TT
+></B
+>
+ as root to fix this problem. This will allow any process running on your
+ machine the ability to <EM
+>read</EM
+> the
+ <TT
+CLASS="filename"
+>/var/spool/mqueue</TT
+> directory.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="trbl-relogin-everyone"
+>A.6. Everybody is constantly being forced to relogin</A
+></H2
+><P
+>The most-likely cause is that the <SPAN
+CLASS="QUOTE"
+>"cookiepath"</SPAN
+> parameter
+ is not set correctly in the Bugzilla configuration. You can change this (if
+ you're a Bugzilla administrator) from the editparams.cgi page via the web interface.
+ </P
+><P
+>The value of the cookiepath parameter should be the actual directory
+ containing your Bugzilla installation, <EM
+>as seen by the end-user's
+ web browser</EM
+>. Leading and trailing slashes are mandatory. You can
+ also set the cookiepath to any directory which is a parent of the Bugzilla
+ directory (such as '/', the root directory). But you can't put something
+ that isn't at least a partial match or it won't work. What you're actually
+ doing is restricting the end-user's browser to sending the cookies back only
+ to that directory.
+ </P
+><P
+>How do you know if you want your specific Bugzilla directory or the
+ whole site?
+ </P
+><P
+>If you have only one Bugzilla running on the server, and you don't
+ mind having other applications on the same server with it being able to see
+ the cookies (you might be doing this on purpose if you have other things on
+ your site that share authentication with Bugzilla), then you'll want to have
+ the cookiepath set to "/", or to a sufficiently-high enough directory that
+ all of the involved apps can see the cookies.
+ </P
+><DIV
+CLASS="example"
+><A
+NAME="trbl-relogin-everyone-share"
+></A
+><P
+><B
+>Example A-1. Examples of urlbase/cookiepath pairs for sharing login cookies</B
+></P
+><A
+NAME="AEN3090"
+></A
+><BLOCKQUOTE
+CLASS="BLOCKQUOTE"
+><P
+CLASS="literallayout"
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;urlbase&nbsp;is&nbsp;<A
+HREF="http://bugzilla.mozilla.org/"
+TARGET="_top"
+>http://bugzilla.mozilla.org/</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cookiepath&nbsp;is&nbsp;/<br>
+<br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;urlbase&nbsp;is&nbsp;<A
+HREF="http://tools.mysite.tld/bugzilla/"
+TARGET="_top"
+>http://tools.mysite.tld/bugzilla/</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;but&nbsp;you&nbsp;have&nbsp;http://tools.mysite.tld/someotherapp/&nbsp;which&nbsp;shares<br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;authentication&nbsp;with&nbsp;your&nbsp;Bugzilla<br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cookiepath&nbsp;is&nbsp;/<br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</P
+></BLOCKQUOTE
+></DIV
+><P
+>On the other hand, if you have more than one Bugzilla running on the
+ server (some people do - we do on landfill) then you need to have the
+ cookiepath restricted enough so that the different Bugzillas don't
+ confuse their cookies with one another.
+ </P
+><DIV
+CLASS="example"
+><A
+NAME="trbl-relogin-everyone-restrict"
+></A
+><P
+><B
+>Example A-2. Examples of urlbase/cookiepath pairs to restrict the login cookie</B
+></P
+><A
+NAME="AEN3097"
+></A
+><BLOCKQUOTE
+CLASS="BLOCKQUOTE"
+><P
+CLASS="literallayout"
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;urlbase&nbsp;is&nbsp;<A
+HREF="http://landfill.bugzilla.org/bugzilla-tip/"
+TARGET="_top"
+>http://landfill.bugzilla.org/bugzilla-tip/</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cookiepath&nbsp;is&nbsp;/bugzilla-tip/<br>
+<br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;urlbase&nbsp;is&nbsp;<A
+HREF="http://landfill.bugzilla.org/bugzilla-2.16-branch/"
+TARGET="_top"
+>http://landfill.bugzilla.org/bugzilla-2.16-branch/</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cookiepath&nbsp;is&nbsp;/bugzilla-2.16-branch/<br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</P
+></BLOCKQUOTE
+></DIV
+><P
+>If you had cookiepath set to <SPAN
+CLASS="QUOTE"
+>"/"</SPAN
+> at any point in the
+ past and need to set it to something more restrictive
+ (i.e. <SPAN
+CLASS="QUOTE"
+>"/bugzilla/"</SPAN
+>), you can safely do this without
+ requiring users to delete their Bugzilla-related cookies in their
+ browser (this is true starting with Bugzilla 2.18 and Bugzilla 2.16.5).
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="trbl-index"
+>A.7. <TT
+CLASS="filename"
+>index.cgi</TT
+> doesn't show up unless specified in the URL</A
+></H2
+><P
+>&#13; You probably need to set up your web server in such a way that it
+ will serve the index.cgi page as an index page.
+ </P
+><P
+>&#13; If you are using Apache, you can do this by adding
+ <TT
+CLASS="filename"
+>index.cgi</TT
+> to the end of the
+ <SAMP
+CLASS="computeroutput"
+>DirectoryIndex</SAMP
+> line
+ as mentioned in <A
+HREF="#http-apache"
+>Section 2.2.4.1</A
+>.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="trbl-passwd-encryption"
+>A.8. checksetup.pl reports "Client does not support authentication protocol
+ requested by server..."</A
+></H2
+><P
+>&#13; This error is occurring because you are using the new password
+ encryption that comes with MySQL 4.1, while your
+ <TT
+CLASS="filename"
+>DBD::mysql</TT
+> module was compiled against an
+ older version of MySQL. If you recompile <TT
+CLASS="filename"
+>DBD::mysql</TT
+>
+ against the current MySQL libraries (or just obtain a newer version
+ of this module) then the error may go away.
+ </P
+><P
+>&#13; If that does not fix the problem, or if you cannot recompile the
+ existing module (e.g. you're running Windows) and/or don't want to
+ replace it (e.g. you want to keep using a packaged version), then a
+ workaround is available from the MySQL docs:
+ <A
+HREF="http://dev.mysql.com/doc/mysql/en/Old_client.html"
+TARGET="_top"
+>http://dev.mysql.com/doc/mysql/en/Old_client.html</A
+>
+ </P
+></DIV
+></DIV
+><DIV
+CLASS="appendix"
+><HR><H1
+><A
+NAME="patches"
+></A
+>Appendix B. Contrib</H1
+><P
+>&#13; There are a number of unofficial Bugzilla add-ons in the
+ <TT
+CLASS="filename"
+>$BUGZILLA_ROOT/contrib/</TT
+>
+ directory. This section documents them.
+ </P
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="cmdline"
+>B.1. Command-line Search Interface</A
+></H2
+><P
+>&#13; There are a suite of Unix utilities for searching Bugzilla from the
+ command line. They live in the
+ <TT
+CLASS="filename"
+>contrib/cmdline</TT
+> directory.
+ There are three files - <TT
+CLASS="filename"
+>query.conf</TT
+>,
+ <TT
+CLASS="filename"
+>buglist</TT
+> and <TT
+CLASS="filename"
+>bugs</TT
+>.
+ </P
+><DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; These files pre-date the templatization work done as part of the
+ 2.16 release, and have not been updated.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; <TT
+CLASS="filename"
+>query.conf</TT
+> contains the mapping from
+ options to field names and comparison types. Quoted option names
+ are <SPAN
+CLASS="QUOTE"
+>"grepped"</SPAN
+> for, so it should be easy to edit this
+ file. Comments (#) have no effect; you must make sure these lines
+ do not contain any quoted <SPAN
+CLASS="QUOTE"
+>"option"</SPAN
+>.
+ </P
+><P
+>&#13; <TT
+CLASS="filename"
+>buglist</TT
+> is a shell script that submits a
+ Bugzilla query and writes the resulting HTML page to stdout.
+ It supports both short options, (such as <SPAN
+CLASS="QUOTE"
+>"-Afoo"</SPAN
+>
+ or <SPAN
+CLASS="QUOTE"
+>"-Rbar"</SPAN
+>) and long options (such
+ as <SPAN
+CLASS="QUOTE"
+>"--assignedto=foo"</SPAN
+> or <SPAN
+CLASS="QUOTE"
+>"--reporter=bar"</SPAN
+>).
+ If the first character of an option is not <SPAN
+CLASS="QUOTE"
+>"-"</SPAN
+>, it is
+ treated as if it were prefixed with <SPAN
+CLASS="QUOTE"
+>"--default="</SPAN
+>.
+ </P
+><P
+>&#13; The column list is taken from the COLUMNLIST environment variable.
+ This is equivalent to the <SPAN
+CLASS="QUOTE"
+>"Change Columns"</SPAN
+> option
+ that is available when you list bugs in buglist.cgi. If you have
+ already used Bugzilla, grep for COLUMNLIST in your cookies file
+ to see your current COLUMNLIST setting.
+ </P
+><P
+>&#13; <TT
+CLASS="filename"
+>bugs</TT
+> is a simple shell script which calls
+ <TT
+CLASS="filename"
+>buglist</TT
+> and extracts the
+ bug numbers from the output. Adding the prefix
+ <SPAN
+CLASS="QUOTE"
+>"http://bugzilla.mozilla.org/buglist.cgi?bug_id="</SPAN
+>
+ turns the bug list into a working link if any bugs are found.
+ Counting bugs is easy. Pipe the results through
+ <B
+CLASS="command"
+>sed -e 's/,/ /g' | wc | awk '{printf $2 "\n"}'</B
+>
+ </P
+><P
+>&#13; Akkana Peck says she has good results piping
+ <TT
+CLASS="filename"
+>buglist</TT
+> output through
+ <B
+CLASS="command"
+>w3m -T text/html -dump</B
+>
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="cmdline-bugmail"
+>B.2. Command-line 'Send Unsent Bug-mail' tool</A
+></H2
+><P
+>&#13; Within the <TT
+CLASS="filename"
+>contrib</TT
+> directory
+ exists a utility with the descriptive (if compact) name
+ of <TT
+CLASS="filename"
+>sendunsentbugmail.pl</TT
+>. The purpose of this
+ script is, simply, to send out any bug-related mail that should
+ have been sent by now, but for one reason or another has not.
+ </P
+><P
+>&#13; To accomplish this task, <TT
+CLASS="filename"
+>sendunsentbugmail.pl</TT
+> uses
+ the same mechanism as the <TT
+CLASS="filename"
+>sanitycheck.cgi</TT
+> script;
+ it scans through the entire database looking for bugs with changes that
+ were made more than 30 minutes ago, but where there is no record of
+ anyone related to that bug having been sent mail. Having compiled a list,
+ it then uses the standard rules to determine who gets mail, and sends it
+ out.
+ </P
+><P
+>&#13; As the script runs, it indicates the bug for which it is currently
+ sending mail; when it has finished, it gives a numerical count of how
+ many mails were sent and how many people were excluded. (Individual
+ user names are not recorded or displayed.) If the script produces
+ no output, that means no unsent mail was detected.
+ </P
+><P
+>&#13; <EM
+>Usage</EM
+>: move the sendunsentbugmail.pl script
+ up into the main directory, ensure it has execute permission, and run it
+ from the command line (or from a cron job) with no parameters.
+ </P
+></DIV
+></DIV
+><DIV
+CLASS="appendix"
+><HR><H1
+><A
+NAME="install-perlmodules-manual"
+></A
+>Appendix C. Manual Installation of Perl Modules</H1
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="modules-manual-instructions"
+>C.1. Instructions</A
+></H2
+><P
+>&#13; If you need to install Perl modules manually, here's how it's done.
+ Download the module using the link given in the next section, and then
+ apply this magic incantation, as root:
+ </P
+><P
+>
+ <TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="screen"
+><SAMP
+CLASS="prompt"
+>bash#</SAMP
+> tar -xzvf &#60;module&#62;.tar.gz
+<SAMP
+CLASS="prompt"
+>bash#</SAMP
+> cd &#60;module&#62;
+<SAMP
+CLASS="prompt"
+>bash#</SAMP
+> perl Makefile.PL
+<SAMP
+CLASS="prompt"
+>bash#</SAMP
+> make
+<SAMP
+CLASS="prompt"
+>bash#</SAMP
+> make test
+<SAMP
+CLASS="prompt"
+>bash#</SAMP
+> make install</PRE
+></FONT
+></TD
+></TR
+></TABLE
+>
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; In order to compile source code under Windows you will need to obtain
+ a 'make' utility. The <B
+CLASS="command"
+>nmake</B
+> utility provided with
+ Microsoft Visual C++ may be used. As an alternative, there is a
+ utility called <B
+CLASS="command"
+>dmake</B
+> available from CPAN which is
+ written entirely in Perl.
+ </P
+><P
+>&#13; As described in <A
+HREF="#modules-manual-download"
+>Section C.2</A
+>, however, most
+ packages already exist and are available from ActiveState or theory58S.
+ We highly recommend that you install them using the ppm GUI available with
+ ActiveState and to add the theory58S repository to your list of repositories.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="modules-manual-download"
+>C.2. Download Locations</A
+></H2
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Running Bugzilla on Windows requires the use of ActiveState
+ Perl 5.8.1 or higher. Many modules already exist in the core
+ distribution of ActiveState Perl. Additional modules can be downloaded
+ from <A
+HREF="http://theoryx5.uwinnipeg.ca/ppms/"
+TARGET="_top"
+>http://theoryx5.uwinnipeg.ca/ppms/</A
+> if you use
+ Perl 5.8.x or from <A
+HREF="http://cpan.uwinnipeg.ca/PPMPackages/10xx/"
+TARGET="_top"
+>http://cpan.uwinnipeg.ca/PPMPackages/10xx/</A
+>
+ if you use Perl 5.10.x.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; CGI:
+ <P
+CLASS="literallayout"
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CPAN&nbsp;Download&nbsp;Page:&nbsp;<A
+HREF="http://search.cpan.org/dist/CGI.pm/"
+TARGET="_top"
+>http://search.cpan.org/dist/CGI.pm/</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Documentation:&nbsp;<A
+HREF="http://perldoc.perl.org/CGI.html"
+TARGET="_top"
+>http://perldoc.perl.org/CGI.html</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</P
+>
+ </P
+><P
+>&#13; Data-Dumper:
+ <P
+CLASS="literallayout"
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CPAN&nbsp;Download&nbsp;Page:&nbsp;<A
+HREF="http://search.cpan.org/dist/Data-Dumper/"
+TARGET="_top"
+>http://search.cpan.org/dist/Data-Dumper/</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Documentation:&nbsp;<A
+HREF="http://search.cpan.org/dist/Data-Dumper/Dumper.pm"
+TARGET="_top"
+>http://search.cpan.org/dist/Data-Dumper/Dumper.pm</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</P
+>
+ </P
+><P
+>&#13; Date::Format (part of TimeDate):
+ <P
+CLASS="literallayout"
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CPAN&nbsp;Download&nbsp;Page:&nbsp;<A
+HREF="http://search.cpan.org/dist/TimeDate/"
+TARGET="_top"
+>http://search.cpan.org/dist/TimeDate/</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Documentation:&nbsp;<A
+HREF="http://search.cpan.org/dist/TimeDate/lib/Date/Format.pm"
+TARGET="_top"
+>http://search.cpan.org/dist/TimeDate/lib/Date/Format.pm</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</P
+>
+ </P
+><P
+>&#13; DBI:
+ <P
+CLASS="literallayout"
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CPAN&nbsp;Download&nbsp;Page:&nbsp;<A
+HREF="http://search.cpan.org/dist/DBI/"
+TARGET="_top"
+>http://search.cpan.org/dist/DBI/</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Documentation:&nbsp;<A
+HREF="http://dbi.perl.org/docs/"
+TARGET="_top"
+>http://dbi.perl.org/docs/</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</P
+>
+ </P
+><P
+>&#13; DBD::mysql:
+ <P
+CLASS="literallayout"
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CPAN&nbsp;Download&nbsp;Page:&nbsp;<A
+HREF="http://search.cpan.org/dist/DBD-mysql/"
+TARGET="_top"
+>http://search.cpan.org/dist/DBD-mysql/</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Documentation:&nbsp;<A
+HREF="http://search.cpan.org/dist/DBD-mysql/lib/DBD/mysql.pm"
+TARGET="_top"
+>http://search.cpan.org/dist/DBD-mysql/lib/DBD/mysql.pm</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</P
+>
+ </P
+><P
+>&#13; DBD::Pg:
+ <P
+CLASS="literallayout"
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CPAN&nbsp;Download&nbsp;Page:&nbsp;<A
+HREF="http://search.cpan.org/dist/DBD-Pg/"
+TARGET="_top"
+>http://search.cpan.org/dist/DBD-Pg/</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Documentation:&nbsp;<A
+HREF="http://search.cpan.org/dist/DBD-Pg/Pg.pm"
+TARGET="_top"
+>http://search.cpan.org/dist/DBD-Pg/Pg.pm</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</P
+>
+ </P
+><P
+>&#13; Template-Toolkit:
+ <P
+CLASS="literallayout"
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CPAN&nbsp;Download&nbsp;Page:&nbsp;<A
+HREF="http://search.cpan.org/dist/Template-Toolkit/"
+TARGET="_top"
+>http://search.cpan.org/dist/Template-Toolkit/</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Documentation:&nbsp;<A
+HREF="http://www.template-toolkit.org/docs.html"
+TARGET="_top"
+>http://www.template-toolkit.org/docs.html</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</P
+>
+ </P
+><P
+>&#13; GD:
+ <P
+CLASS="literallayout"
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CPAN&nbsp;Download&nbsp;Page:&nbsp;<A
+HREF="http://search.cpan.org/dist/GD/"
+TARGET="_top"
+>http://search.cpan.org/dist/GD/</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Documentation:&nbsp;<A
+HREF="http://search.cpan.org/dist/GD/GD.pm"
+TARGET="_top"
+>http://search.cpan.org/dist/GD/GD.pm</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</P
+>
+ </P
+><P
+>&#13; Template::Plugin::GD:
+ <P
+CLASS="literallayout"
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CPAN&nbsp;Download&nbsp;Page:&nbsp;<A
+HREF="http://search.cpan.org/dist/Template-GD/"
+TARGET="_top"
+>http://search.cpan.org/dist/Template-GD/</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Documentation:&nbsp;<A
+HREF="http://www.template-toolkit.org/docs/aqua/Modules/index.html"
+TARGET="_top"
+>http://www.template-toolkit.org/docs/aqua/Modules/index.html</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</P
+>
+ </P
+><P
+>&#13; MIME::Parser (part of MIME-tools):
+ <P
+CLASS="literallayout"
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CPAN&nbsp;Download&nbsp;Page:&nbsp;<A
+HREF="http://search.cpan.org/dist/MIME-tools/"
+TARGET="_top"
+>http://search.cpan.org/dist/MIME-tools/</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Documentation:&nbsp;<A
+HREF="http://search.cpan.org/dist/MIME-tools/lib/MIME/Parser.pm"
+TARGET="_top"
+>http://search.cpan.org/dist/MIME-tools/lib/MIME/Parser.pm</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</P
+>
+ </P
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="modules-manual-optional"
+>C.3. Optional Modules</A
+></H2
+><P
+>&#13; Chart::Lines:
+ <P
+CLASS="literallayout"
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CPAN&nbsp;Download&nbsp;Page:&nbsp;<A
+HREF="http://search.cpan.org/dist/Chart/"
+TARGET="_top"
+>http://search.cpan.org/dist/Chart/</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Documentation:&nbsp;<A
+HREF="http://search.cpan.org/dist/Chart/Chart.pod"
+TARGET="_top"
+>http://search.cpan.org/dist/Chart/Chart.pod</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</P
+>
+ </P
+><P
+>&#13; GD::Graph:
+ <P
+CLASS="literallayout"
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CPAN&nbsp;Download&nbsp;Page:&nbsp;<A
+HREF="http://search.cpan.org/dist/GDGraph/"
+TARGET="_top"
+>http://search.cpan.org/dist/GDGraph/</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Documentation:&nbsp;<A
+HREF="http://search.cpan.org/dist/GDGraph/Graph.pm"
+TARGET="_top"
+>http://search.cpan.org/dist/GDGraph/Graph.pm</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</P
+>
+ </P
+><P
+>&#13; GD::Text::Align (part of GD::Text::Util):
+ <P
+CLASS="literallayout"
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CPAN&nbsp;Download&nbsp;Page:&nbsp;<A
+HREF="http://search.cpan.org/dist/GDTextUtil/"
+TARGET="_top"
+>http://search.cpan.org/dist/GDTextUtil/</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Documentation:&nbsp;<A
+HREF="http://search.cpan.org/dist/GDTextUtil/Text/Align.pm"
+TARGET="_top"
+>http://search.cpan.org/dist/GDTextUtil/Text/Align.pm</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</P
+>
+ </P
+><P
+>&#13; XML::Twig:
+ <P
+CLASS="literallayout"
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CPAN&nbsp;Download&nbsp;Page:&nbsp;<A
+HREF="http://search.cpan.org/dist/XML-Twig/"
+TARGET="_top"
+>http://search.cpan.org/dist/XML-Twig/</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Documentation:&nbsp;<A
+HREF="http://standards.ieee.org/resources/spasystem/twig/twig_stable.html"
+TARGET="_top"
+>http://standards.ieee.org/resources/spasystem/twig/twig_stable.html</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</P
+>
+ </P
+><P
+>&#13; PatchReader:
+ <P
+CLASS="literallayout"
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CPAN&nbsp;Download&nbsp;Page:&nbsp;<A
+HREF="http://search.cpan.org/author/JKEISER/PatchReader/"
+TARGET="_top"
+>http://search.cpan.org/author/JKEISER/PatchReader/</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Documentation:&nbsp;<A
+HREF="http://www.johnkeiser.com/mozilla/Patch_Viewer.html"
+TARGET="_top"
+>http://www.johnkeiser.com/mozilla/Patch_Viewer.html</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</P
+>
+ </P
+></DIV
+></DIV
+><DIV
+CLASS="appendix"
+><HR><H1
+><A
+NAME="gfdl"
+></A
+>Appendix D. GNU Free Documentation License</H1
+><P
+>Version 1.1, March 2000</P
+><A
+NAME="AEN3255"
+></A
+><BLOCKQUOTE
+CLASS="BLOCKQUOTE"
+><P
+>Copyright (C) 2000 Free Software Foundation, Inc. 59 Temple Place,
+ Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and
+ distribute verbatim copies of this license document, but changing it is
+ not allowed.</P
+></BLOCKQUOTE
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="gfdl-0"
+>0. Preamble</A
+></H2
+><P
+>The purpose of this License is to make a manual, textbook, or other
+ written document "free" in the sense of freedom: to assure everyone the
+ effective freedom to copy and redistribute it, with or without modifying
+ it, either commercially or noncommercially. Secondarily, this License
+ preserves for the author and publisher a way to get credit for their
+ work, while not being considered responsible for modifications made by
+ others.</P
+><P
+>This License is a kind of "copyleft", which means that derivative
+ works of the document must themselves be free in the same sense. It
+ complements the GNU General Public License, which is a copyleft license
+ designed for free software.</P
+><P
+>We have designed this License in order to use it for manuals for
+ free software, because free software needs free documentation: a free
+ program should come with manuals providing the same freedoms that the
+ software does. But this License is not limited to software manuals; it
+ can be used for any textual work, regardless of subject matter or whether
+ it is published as a printed book. We recommend this License principally
+ for works whose purpose is instruction or reference.</P
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="gfdl-1"
+>1. Applicability and Definition</A
+></H2
+><P
+>This License applies to any manual or other work that contains a
+ notice placed by the copyright holder saying it can be distributed under
+ the terms of this License. The "Document", below, refers to any such
+ manual or work. Any member of the public is a licensee, and is addressed
+ as "you".</P
+><P
+>A "Modified Version" of the Document means any work containing the
+ Document or a portion of it, either copied verbatim, or with
+ modifications and/or translated into another language.</P
+><P
+>A "Secondary Section" is a named appendix or a front-matter section
+ of the Document that deals exclusively with the relationship of the
+ publishers or authors of the Document to the Document's overall subject
+ (or to related matters) and contains nothing that could fall directly
+ within that overall subject. (For example, if the Document is in part a
+ textbook of mathematics, a Secondary Section may not explain any
+ mathematics.) The relationship could be a matter of historical connection
+ with the subject or with related matters, or of legal, commercial,
+ philosophical, ethical or political position regarding them.</P
+><P
+>The "Invariant Sections" are certain Secondary Sections whose
+ titles are designated, as being those of Invariant Sections, in the
+ notice that says that the Document is released under this License.</P
+><P
+>The "Cover Texts" are certain short passages of text that are
+ listed, as Front-Cover Texts or Back-Cover Texts, in the notice that says
+ that the Document is released under this License.</P
+><P
+>A "Transparent" copy of the Document means a machine-readable copy,
+ represented in a format whose specification is available to the general
+ public, whose contents can be viewed and edited directly and
+ straightforwardly with generic text editors or (for images composed of
+ pixels) generic paint programs or (for drawings) some widely available
+ drawing editor, and that is suitable for input to text formatters or for
+ automatic translation to a variety of formats suitable for input to text
+ formatters. A copy made in an otherwise Transparent file format whose
+ markup has been designed to thwart or discourage subsequent modification
+ by readers is not Transparent. A copy that is not "Transparent" is called
+ "Opaque".</P
+><P
+>Examples of suitable formats for Transparent copies include plain
+ ASCII without markup, Texinfo input format, LaTeX input format, SGML or
+ XML using a publicly available DTD, and standard-conforming simple HTML
+ designed for human modification. Opaque formats include PostScript, PDF,
+ proprietary formats that can be read and edited only by proprietary word
+ processors, SGML or XML for which the DTD and/or processing tools are not
+ generally available, and the machine-generated HTML produced by some word
+ processors for output purposes only.</P
+><P
+>The "Title Page" means, for a printed book, the title page itself,
+ plus such following pages as are needed to hold, legibly, the material
+ this License requires to appear in the title page. For works in formats
+ which do not have any title page as such, "Title Page" means the text
+ near the most prominent appearance of the work's title, preceding the
+ beginning of the body of the text.</P
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="gfdl-2"
+>2. Verbatim Copying</A
+></H2
+><P
+>You may copy and distribute the Document in any medium, either
+ commercially or noncommercially, provided that this License, the
+ copyright notices, and the license notice saying this License applies to
+ the Document are reproduced in all copies, and that you add no other
+ conditions whatsoever to those of this License. You may not use technical
+ measures to obstruct or control the reading or further copying of the
+ copies you make or distribute. However, you may accept compensation in
+ exchange for copies. If you distribute a large enough number of copies
+ you must also follow the conditions in section 3.</P
+><P
+>You may also lend copies, under the same conditions stated above,
+ and you may publicly display copies.</P
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="gfdl-3"
+>3. Copying in Quantity</A
+></H2
+><P
+>If you publish printed copies of the Document numbering more than
+ 100, and the Document's license notice requires Cover Texts, you must
+ enclose the copies in covers that carry, clearly and legibly, all these
+ Cover Texts: Front-Cover Texts on the front cover, and Back-Cover Texts
+ on the back cover. Both covers must also clearly and legibly identify you
+ as the publisher of these copies. The front cover must present the full
+ title with all words of the title equally prominent and visible. You may
+ add other material on the covers in addition. Copying with changes
+ limited to the covers, as long as they preserve the title of the Document
+ and satisfy these conditions, can be treated as verbatim copying in other
+ respects.</P
+><P
+>If the required texts for either cover are too voluminous to fit
+ legibly, you should put the first ones listed (as many as fit reasonably)
+ on the actual cover, and continue the rest onto adjacent pages.</P
+><P
+>If you publish or distribute Opaque copies of the Document
+ numbering more than 100, you must either include a machine-readable
+ Transparent copy along with each Opaque copy, or state in or with each
+ Opaque copy a publicly-accessible computer-network location containing a
+ complete Transparent copy of the Document, free of added material, which
+ the general network-using public has access to download anonymously at no
+ charge using public-standard network protocols. If you use the latter
+ option, you must take reasonably prudent steps, when you begin
+ distribution of Opaque copies in quantity, to ensure that this
+ Transparent copy will remain thus accessible at the stated location until
+ at least one year after the last time you distribute an Opaque copy
+ (directly or through your agents or retailers) of that edition to the
+ public.</P
+><P
+>It is requested, but not required, that you contact the authors of
+ the Document well before redistributing any large number of copies, to
+ give them a chance to provide you with an updated version of the
+ Document.</P
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="gfdl-4"
+>4. Modifications</A
+></H2
+><P
+>You may copy and distribute a Modified Version of the Document
+ under the conditions of sections 2 and 3 above, provided that you release
+ the Modified Version under precisely this License, with the Modified
+ Version filling the role of the Document, thus licensing distribution and
+ modification of the Modified Version to whoever possesses a copy of it.
+ In addition, you must do these things in the Modified Version:</P
+><P
+></P
+><OL
+TYPE="A"
+><LI
+><P
+>Use in the Title Page (and on the covers, if any) a title
+ distinct from that of the Document, and from those of previous
+ versions (which should, if there were any, be listed in the History
+ section of the Document). You may use the same title as a previous
+ version if the original publisher of that version gives
+ permission.</P
+></LI
+><LI
+><P
+>List on the Title Page, as authors, one or more persons or
+ entities responsible for authorship of the modifications in the
+ Modified Version, together with at least five of the principal
+ authors of the Document (all of its principal authors, if it has less
+ than five).</P
+></LI
+><LI
+><P
+>State on the Title page the name of the publisher of the
+ Modified Version, as the publisher.</P
+></LI
+><LI
+><P
+>Preserve all the copyright notices of the Document.</P
+></LI
+><LI
+><P
+>Add an appropriate copyright notice for your modifications
+ adjacent to the other copyright notices.</P
+></LI
+><LI
+><P
+>Include, immediately after the copyright notices, a license
+ notice giving the public permission to use the Modified Version under
+ the terms of this License, in the form shown in the Addendum
+ below.</P
+></LI
+><LI
+><P
+>Preserve in that license notice the full lists of Invariant
+ Sections and required Cover Texts given in the Document's license
+ notice.</P
+></LI
+><LI
+><P
+>Include an unaltered copy of this License.</P
+></LI
+><LI
+><P
+>Preserve the section entitled "History", and its title, and add
+ to it an item stating at least the title, year, new authors, and
+ publisher of the Modified Version as given on the Title Page. If
+ there is no section entitled "History" in the Document, create one
+ stating the title, year, authors, and publisher of the Document as
+ given on its Title Page, then add an item describing the Modified
+ Version as stated in the previous sentence.</P
+></LI
+><LI
+><P
+>Preserve the network location, if any, given in the Document
+ for public access to a Transparent copy of the Document, and likewise
+ the network locations given in the Document for previous versions it
+ was based on. These may be placed in the "History" section. You may
+ omit a network location for a work that was published at least four
+ years before the Document itself, or if the original publisher of the
+ version it refers to gives permission.</P
+></LI
+><LI
+><P
+>In any section entitled "Acknowledgements" or "Dedications",
+ preserve the section's title, and preserve in the section all the
+ substance and tone of each of the contributor acknowledgements and/or
+ dedications given therein.</P
+></LI
+><LI
+><P
+>Preserve all the Invariant Sections of the Document, unaltered
+ in their text and in their titles. Section numbers or the equivalent
+ are not considered part of the section titles.</P
+></LI
+><LI
+><P
+>Delete any section entitled "Endorsements". Such a section may
+ not be included in the Modified Version.</P
+></LI
+><LI
+><P
+>Do not retitle any existing section as "Endorsements" or to
+ conflict in title with any Invariant Section.</P
+></LI
+></OL
+><P
+>If the Modified Version includes new front-matter sections or
+ appendices that qualify as Secondary Sections and contain no material
+ copied from the Document, you may at your option designate some or all of
+ these sections as invariant. To do this, add their titles to the list of
+ Invariant Sections in the Modified Version's license notice. These titles
+ must be distinct from any other section titles.</P
+><P
+>You may add a section entitled "Endorsements", provided it contains
+ nothing but endorsements of your Modified Version by various parties--for
+ example, statements of peer review or that the text has been approved by
+ an organization as the authoritative definition of a standard.</P
+><P
+>You may add a passage of up to five words as a Front-Cover Text,
+ and a passage of up to 25 words as a Back-Cover Text, to the end of the
+ list of Cover Texts in the Modified Version. Only one passage of
+ Front-Cover Text and one of Back-Cover Text may be added by (or through
+ arrangements made by) any one entity. If the Document already includes a
+ cover text for the same cover, previously added by you or by arrangement
+ made by the same entity you are acting on behalf of, you may not add
+ another; but you may replace the old one, on explicit permission from the
+ previous publisher that added the old one.</P
+><P
+>The author(s) and publisher(s) of the Document do not by this
+ License give permission to use their names for publicity for or to assert
+ or imply endorsement of any Modified Version.</P
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="gfdl-5"
+>5. Combining Documents</A
+></H2
+><P
+>You may combine the Document with other documents released under
+ this License, under the terms defined in section 4 above for modified
+ versions, provided that you include in the combination all of the
+ Invariant Sections of all of the original documents, unmodified, and list
+ them all as Invariant Sections of your combined work in its license
+ notice.</P
+><P
+>The combined work need only contain one copy of this License, and
+ multiple identical Invariant Sections may be replaced with a single copy.
+ If there are multiple Invariant Sections with the same name but different
+ contents, make the title of each such section unique by adding at the end
+ of it, in parentheses, the name of the original author or publisher of
+ that section if known, or else a unique number. Make the same adjustment
+ to the section titles in the list of Invariant Sections in the license
+ notice of the combined work.</P
+><P
+>In the combination, you must combine any sections entitled
+ "History" in the various original documents, forming one section entitled
+ "History"; likewise combine any sections entitled "Acknowledgements", and
+ any sections entitled "Dedications". You must delete all sections
+ entitled "Endorsements."</P
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="gfdl-6"
+>6. Collections of Documents</A
+></H2
+><P
+>You may make a collection consisting of the Document and other
+ documents released under this License, and replace the individual copies
+ of this License in the various documents with a single copy that is
+ included in the collection, provided that you follow the rules of this
+ License for verbatim copying of each of the documents in all other
+ respects.</P
+><P
+>You may extract a single document from such a collection, and
+ distribute it individually under this License, provided you insert a copy
+ of this License into the extracted document, and follow this License in
+ all other respects regarding verbatim copying of that document.</P
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="gfdl-7"
+>7. Aggregation with Independent Works</A
+></H2
+><P
+>A compilation of the Document or its derivatives with other
+ separate and independent documents or works, in or on a volume of a
+ storage or distribution medium, does not as a whole count as a Modified
+ Version of the Document, provided no compilation copyright is claimed for
+ the compilation. Such a compilation is called an "aggregate", and this
+ License does not apply to the other self-contained works thus compiled
+ with the Document, on account of their being thus compiled, if they are
+ not themselves derivative works of the Document.</P
+><P
+>If the Cover Text requirement of section 3 is applicable to these
+ copies of the Document, then if the Document is less than one quarter of
+ the entire aggregate, the Document's Cover Texts may be placed on covers
+ that surround only the Document within the aggregate. Otherwise they must
+ appear on covers around the whole aggregate.</P
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="gfdl-8"
+>8. Translation</A
+></H2
+><P
+>Translation is considered a kind of modification, so you may
+ distribute translations of the Document under the terms of section 4.
+ Replacing Invariant Sections with translations requires special
+ permission from their copyright holders, but you may include translations
+ of some or all Invariant Sections in addition to the original versions of
+ these Invariant Sections. You may include a translation of this License
+ provided that you also include the original English version of this
+ License. In case of a disagreement between the translation and the
+ original English version of this License, the original English version
+ will prevail.</P
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="gfdl-9"
+>9. Termination</A
+></H2
+><P
+>You may not copy, modify, sublicense, or distribute the Document
+ except as expressly provided for under this License. Any other attempt to
+ copy, modify, sublicense or distribute the Document is void, and will
+ automatically terminate your rights under this License. However, parties
+ who have received copies, or rights, from you under this License will not
+ have their licenses terminated so long as such parties remain in full
+ compliance.</P
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="gfdl-10"
+>10. Future Revisions of this License</A
+></H2
+><P
+>The Free Software Foundation may publish new, revised versions of
+ the GNU Free Documentation License from time to time. Such new versions
+ will be similar in spirit to the present version, but may differ in
+ detail to address new problems or concerns. See
+ <A
+HREF="http://www.gnu.org/copyleft/"
+TARGET="_top"
+>http://www.gnu.org/copyleft/</A
+>.</P
+><P
+>Each version of the License is given a distinguishing version
+ number. If the Document specifies that a particular numbered version of
+ this License "or any later version" applies to it, you have the option of
+ following the terms and conditions either of that specified version or of
+ any later version that has been published (not as a draft) by the Free
+ Software Foundation. If the Document does not specify a version number of
+ this License, you may choose any version ever published (not as a draft)
+ by the Free Software Foundation.</P
+></DIV
+><DIV
+CLASS="section"
+><HR><H2
+CLASS="section"
+><A
+NAME="gfdl-howto"
+>How to use this License for your documents</A
+></H2
+><P
+>To use this License in a document you have written, include a copy
+ of the License in the document and put the following copyright and
+ license notices just after the title page:</P
+><A
+NAME="AEN3345"
+></A
+><BLOCKQUOTE
+CLASS="BLOCKQUOTE"
+><P
+>Copyright (c) YEAR YOUR NAME. Permission is granted to copy,
+ distribute and/or modify this document under the terms of the GNU Free
+ Documentation License, Version 1.1 or any later version published by
+ the Free Software Foundation; with the Invariant Sections being LIST
+ THEIR TITLES, with the Front-Cover Texts being LIST, and with the
+ Back-Cover Texts being LIST. A copy of the license is included in the
+ section entitled "GNU Free Documentation License".</P
+></BLOCKQUOTE
+><P
+>If you have no Invariant Sections, write "with no Invariant
+ Sections" instead of saying which ones are invariant. If you have no
+ Front-Cover Texts, write "no Front-Cover Texts" instead of "Front-Cover
+ Texts being LIST"; likewise for Back-Cover Texts.</P
+><P
+>If your document contains nontrivial examples of program code, we
+ recommend releasing these examples in parallel under your choice of free
+ software license, such as the GNU General Public License, to permit their
+ use in free software.</P
+></DIV
+></DIV
+><DIV
+CLASS="GLOSSARY"
+><H1
+><A
+NAME="glossary"
+></A
+>Glossary</H1
+><DIV
+CLASS="glossdiv"
+><H1
+CLASS="glossdiv"
+><A
+NAME="AEN3350"
+>0-9, high ascii</A
+></H1
+><DL
+><DT
+><A
+NAME="gloss-htaccess"
+></A
+><B
+>.htaccess</B
+></DT
+><DD
+><P
+>Apache web server, and other NCSA-compliant web servers,
+ observe the convention of using files in directories called
+ <TT
+CLASS="filename"
+>.htaccess</TT
+>
+
+ to restrict access to certain files. In Bugzilla, they are used
+ to keep secret files which would otherwise
+ compromise your installation - e.g. the
+ <TT
+CLASS="filename"
+>localconfig</TT
+>
+ file contains the password to your database.
+ curious.</P
+></DD
+></DL
+></DIV
+><DIV
+CLASS="glossdiv"
+><H1
+CLASS="glossdiv"
+><A
+NAME="gloss-a"
+>A</A
+></H1
+><DL
+><DT
+><A
+NAME="gloss-apache"
+></A
+><B
+>Apache</B
+></DT
+><DD
+><P
+>In this context, Apache is the web server most commonly used
+ for serving up Bugzilla
+ pages. Contrary to popular belief, the apache web server has nothing
+ to do with the ancient and noble Native American tribe, but instead
+ derived its name from the fact that it was
+ <SPAN
+CLASS="QUOTE"
+>"a patchy"</SPAN
+>
+ version of the original
+ <ACRONYM
+CLASS="acronym"
+>NCSA</ACRONYM
+>
+ world-wide-web server.</P
+><P
+></P
+><DIV
+CLASS="variablelist"
+><P
+><B
+>Useful Directives when configuring Bugzilla</B
+></P
+><DL
+><DT
+><SAMP
+CLASS="computeroutput"
+><A
+HREF="http://httpd.apache.org/docs-2.0/mod/core.html#addhandler"
+TARGET="_top"
+>AddHandler</A
+></SAMP
+></DT
+><DD
+><P
+>Tell Apache that it's OK to run CGI scripts.</P
+></DD
+><DT
+><SAMP
+CLASS="computeroutput"
+><A
+HREF="http://httpd.apache.org/docs-2.0/mod/core.html#allowoverride"
+TARGET="_top"
+>AllowOverride</A
+></SAMP
+>, <SAMP
+CLASS="computeroutput"
+><A
+HREF="http://httpd.apache.org/docs-2.0/mod/core.html#options"
+TARGET="_top"
+>Options</A
+></SAMP
+></DT
+><DD
+><P
+>These directives are used to tell Apache many things about
+ the directory they apply to. For Bugzilla's purposes, we need
+ them to allow script execution and <TT
+CLASS="filename"
+>.htaccess</TT
+>
+ overrides.
+ </P
+></DD
+><DT
+><SAMP
+CLASS="computeroutput"
+><A
+HREF="http://httpd.apache.org/docs-2.0/mod/mod_dir.html#directoryindex"
+TARGET="_top"
+>DirectoryIndex</A
+></SAMP
+></DT
+><DD
+><P
+>Used to tell Apache what files are indexes. If you can
+ not add <TT
+CLASS="filename"
+>index.cgi</TT
+> to the list of valid files,
+ you'll need to set <SAMP
+CLASS="computeroutput"
+>$index_html</SAMP
+> to
+ 1 in <TT
+CLASS="filename"
+>localconfig</TT
+> so
+ <B
+CLASS="command"
+>./checksetup.pl</B
+> will create an
+ <TT
+CLASS="filename"
+>index.html</TT
+> that redirects to
+ <TT
+CLASS="filename"
+>index.cgi</TT
+>.
+ </P
+></DD
+><DT
+><SAMP
+CLASS="computeroutput"
+><A
+HREF="http://httpd.apache.org/docs-2.0/mod/core.html#scriptinterpretersource"
+TARGET="_top"
+>ScriptInterpreterSource</A
+></SAMP
+></DT
+><DD
+><P
+>Used when running Apache on windows so the shebang line
+ doesn't have to be changed in every Bugzilla script.
+ </P
+></DD
+></DL
+></DIV
+><P
+>For more information about how to configure Apache for Bugzilla,
+ see <A
+HREF="#http-apache"
+>Section 2.2.4.1</A
+>.
+ </P
+></DD
+></DL
+></DIV
+><DIV
+CLASS="glossdiv"
+><H1
+CLASS="glossdiv"
+><A
+NAME="gloss-b"
+>B</A
+></H1
+><DL
+><DT
+><B
+>Bug</B
+></DT
+><DD
+><P
+>A
+ <SPAN
+CLASS="QUOTE"
+>"bug"</SPAN
+>
+
+ in Bugzilla refers to an issue entered into the database which has an
+ associated number, assignments, comments, etc. Some also refer to a
+ <SPAN
+CLASS="QUOTE"
+>"tickets"</SPAN
+>
+ or
+ <SPAN
+CLASS="QUOTE"
+>"issues"</SPAN
+>;
+ in the context of Bugzilla, they are synonymous.</P
+></DD
+><DT
+><B
+>Bug Number</B
+></DT
+><DD
+><P
+>Each Bugzilla bug is assigned a number that uniquely identifies
+ that bug. The bug associated with a bug number can be pulled up via a
+ query, or easily from the very front page by typing the number in the
+ "Find" box.</P
+></DD
+><DT
+><A
+NAME="gloss-bugzilla"
+></A
+><B
+>Bugzilla</B
+></DT
+><DD
+><P
+>Bugzilla is the world-leading free software bug tracking system.
+ </P
+></DD
+></DL
+></DIV
+><DIV
+CLASS="glossdiv"
+><H1
+CLASS="glossdiv"
+><A
+NAME="gloss-c"
+>C</A
+></H1
+><DL
+><DT
+><A
+NAME="gloss-cgi"
+></A
+><B
+>Common Gateway Interface</B
+></DT
+> (CGI)<DD
+><P
+><ACRONYM
+CLASS="acronym"
+>CGI</ACRONYM
+> is an acronym for Common Gateway Interface. This is
+ a standard for interfacing an external application with a web server. Bugzilla
+ is an example of a <ACRONYM
+CLASS="acronym"
+>CGI</ACRONYM
+> application.
+ </P
+></DD
+><DT
+><A
+NAME="gloss-component"
+></A
+><B
+>Component</B
+></DT
+><DD
+><P
+>A Component is a subsection of a Product. It should be a narrow
+ category, tailored to your organization. All Products must contain at
+ least one Component (and, as a matter of fact, creating a Product
+ with no Components will create an error in Bugzilla).</P
+></DD
+><DT
+><A
+NAME="gloss-cpan"
+></A
+><B
+>Comprehensive Perl Archive Network</B
+></DT
+> (CPAN)<DD
+><P
+>&#13; <ACRONYM
+CLASS="acronym"
+>CPAN</ACRONYM
+>
+
+ stands for the
+ <SPAN
+CLASS="QUOTE"
+>"Comprehensive Perl Archive Network"</SPAN
+>.
+ CPAN maintains a large number of extremely useful
+ <I
+CLASS="glossterm"
+>Perl</I
+>
+ modules - encapsulated chunks of code for performing a
+ particular task.</P
+></DD
+><DT
+><A
+NAME="gloss-contrib"
+></A
+><B
+><TT
+CLASS="filename"
+>contrib</TT
+></B
+></DT
+><DD
+><P
+>The <TT
+CLASS="filename"
+>contrib</TT
+> directory is
+ a location to put scripts that have been contributed to Bugzilla but
+ are not a part of the official distribution. These scripts are written
+ by third parties and may be in languages other than perl. For those
+ that are in perl, there may be additional modules or other requirements
+ than those of the official distribution.
+ <DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>Scripts in the <TT
+CLASS="filename"
+>contrib</TT
+>
+ directory are not officially supported by the Bugzilla team and may
+ break in between versions.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+>
+ </P
+></DD
+></DL
+></DIV
+><DIV
+CLASS="glossdiv"
+><H1
+CLASS="glossdiv"
+><A
+NAME="gloss-d"
+>D</A
+></H1
+><DL
+><DT
+><A
+NAME="gloss-daemon"
+></A
+><B
+>daemon</B
+></DT
+><DD
+><P
+>A daemon is a computer program which runs in the background. In
+ general, most daemons are started at boot time via System V init
+ scripts, or through RC scripts on BSD-based systems.
+ <I
+CLASS="glossterm"
+>mysqld</I
+>,
+ the MySQL server, and
+ <I
+CLASS="glossterm"
+>apache</I
+>,
+ a web server, are generally run as daemons.</P
+></DD
+><DT
+><A
+NAME="gloss-dos"
+></A
+><B
+>DOS Attack</B
+></DT
+><DD
+><P
+>A DOS, or Denial of Service attack, is when a user attempts to
+ deny access to a web server by repeatedly accessing a page or sending
+ malformed requests to a webserver. A D-DOS, or
+ Distributed Denial of Service attack, is when these requests come
+ from multiple sources at the same time. Unfortunately, these are much
+ more difficult to defend against.
+ </P
+></DD
+></DL
+></DIV
+><DIV
+CLASS="glossdiv"
+><H1
+CLASS="glossdiv"
+><A
+NAME="gloss-g"
+>G</A
+></H1
+><DL
+><DT
+><A
+NAME="gloss-groups"
+></A
+><B
+>Groups</B
+></DT
+><DD
+><P
+>The word
+ <SPAN
+CLASS="QUOTE"
+>"Groups"</SPAN
+>
+
+ has a very special meaning to Bugzilla. Bugzilla's main security
+ mechanism comes by placing users in groups, and assigning those
+ groups certain privileges to view bugs in particular
+ <I
+CLASS="glossterm"
+>Products</I
+>
+ in the
+ <I
+CLASS="glossterm"
+>Bugzilla</I
+>
+ database.</P
+></DD
+></DL
+></DIV
+><DIV
+CLASS="glossdiv"
+><H1
+CLASS="glossdiv"
+><A
+NAME="gloss-j"
+>J</A
+></H1
+><DL
+><DT
+><A
+NAME="gloss-javascript"
+></A
+><B
+>JavaScript</B
+></DT
+><DD
+><P
+>JavaScript is cool, we should talk about it.
+ </P
+></DD
+></DL
+></DIV
+><DIV
+CLASS="glossdiv"
+><H1
+CLASS="glossdiv"
+><A
+NAME="gloss-m"
+>M</A
+></H1
+><DL
+><DT
+><A
+NAME="gloss-mta"
+></A
+><B
+>Message Transport Agent</B
+></DT
+> (MTA)<DD
+><P
+>A Message Transport Agent is used to control the flow of email on a system.
+ The <A
+HREF="http://search.cpan.org/dist/Email-Send/lib/Email/Send.pm"
+TARGET="_top"
+>Email::Send</A
+>
+ Perl module, which Bugzilla uses to send email, can be configured to
+ use many different underlying implementations for actually sending the
+ mail using the <CODE
+CLASS="option"
+>mail_delivery_method</CODE
+> parameter.
+ </P
+></DD
+><DT
+><A
+NAME="gloss-mysql"
+></A
+><B
+>MySQL</B
+></DT
+><DD
+><P
+>MySQL is currently the required
+ <A
+HREF="#gloss-rdbms"
+><I
+CLASS="glossterm"
+>RDBMS</I
+></A
+> for Bugzilla. MySQL
+ can be downloaded from <A
+HREF="http://www.mysql.com"
+TARGET="_top"
+>http://www.mysql.com</A
+>. While you
+ should familiarize yourself with all of the documentation, some high
+ points are:
+ </P
+><P
+></P
+><DIV
+CLASS="variablelist"
+><DL
+><DT
+><A
+HREF="http://www.mysql.com/doc/en/Backup.html"
+TARGET="_top"
+>Backup</A
+></DT
+><DD
+><P
+>Methods for backing up your Bugzilla database.
+ </P
+></DD
+><DT
+><A
+HREF="http://www.mysql.com/doc/en/Option_files.html"
+TARGET="_top"
+>Option Files</A
+></DT
+><DD
+><P
+>Information about how to configure MySQL using
+ <TT
+CLASS="filename"
+>my.cnf</TT
+>.
+ </P
+></DD
+><DT
+><A
+HREF="http://www.mysql.com/doc/en/Privilege_system.html"
+TARGET="_top"
+>Privilege System</A
+></DT
+><DD
+><P
+>Information about how to protect your MySQL server.
+ </P
+></DD
+></DL
+></DIV
+></DD
+></DL
+></DIV
+><DIV
+CLASS="glossdiv"
+><H1
+CLASS="glossdiv"
+><A
+NAME="gloss-p"
+>P</A
+></H1
+><DL
+><DT
+><A
+NAME="gloss-ppm"
+></A
+><B
+>Perl Package Manager</B
+></DT
+> (PPM)<DD
+><P
+><A
+HREF="http://aspn.activestate.com/ASPN/Downloads/ActivePerl/PPM/"
+TARGET="_top"
+>http://aspn.activestate.com/ASPN/Downloads/ActivePerl/PPM/</A
+>
+ </P
+></DD
+><DT
+><B
+>Product</B
+></DT
+><DD
+><P
+>A Product is a broad category of types of bugs, normally
+ representing a single piece of software or entity. In general,
+ there are several Components to a Product. A Product may define a
+ group (used for security) for all bugs entered into
+ its Components.</P
+></DD
+><DT
+><B
+>Perl</B
+></DT
+><DD
+><P
+>First written by Larry Wall, Perl is a remarkable program
+ language. It has the benefits of the flexibility of an interpreted
+ scripting language (such as shell script), combined with the speed
+ and power of a compiled language, such as C.
+ <I
+CLASS="glossterm"
+>Bugzilla</I
+>
+
+ is maintained in Perl.</P
+></DD
+></DL
+></DIV
+><DIV
+CLASS="glossdiv"
+><H1
+CLASS="glossdiv"
+><A
+NAME="gloss-q"
+>Q</A
+></H1
+><DL
+><DT
+><B
+>QA</B
+></DT
+><DD
+><P
+>&#13; <SPAN
+CLASS="QUOTE"
+>"QA"</SPAN
+>,
+ <SPAN
+CLASS="QUOTE"
+>"Q/A"</SPAN
+>, and
+ <SPAN
+CLASS="QUOTE"
+>"Q.A."</SPAN
+>
+ are short for
+ <SPAN
+CLASS="QUOTE"
+>"Quality Assurance"</SPAN
+>.
+ In most large software development organizations, there is a team
+ devoted to ensuring the product meets minimum standards before
+ shipping. This team will also generally want to track the progress of
+ bugs over their life cycle, thus the need for the
+ <SPAN
+CLASS="QUOTE"
+>"QA Contact"</SPAN
+>
+
+ field in a bug.</P
+></DD
+></DL
+></DIV
+><DIV
+CLASS="glossdiv"
+><H1
+CLASS="glossdiv"
+><A
+NAME="gloss-r"
+>R</A
+></H1
+><DL
+><DT
+><A
+NAME="gloss-rdbms"
+></A
+><B
+>Relational DataBase Management System</B
+></DT
+> (RDBMS)<DD
+><P
+>A relational database management system is a database system
+ that stores information in tables that are related to each other.
+ </P
+></DD
+><DT
+><A
+NAME="gloss-regexp"
+></A
+><B
+>Regular Expression</B
+></DT
+> (regexp)<DD
+><P
+>A regular expression is an expression used for pattern matching.
+ <A
+HREF="http://perldoc.com/perl5.6/pod/perlre.html#Regular-Expressions"
+TARGET="_top"
+>Documentation</A
+>
+ </P
+></DD
+></DL
+></DIV
+><DIV
+CLASS="glossdiv"
+><H1
+CLASS="glossdiv"
+><A
+NAME="gloss-s"
+>S</A
+></H1
+><DL
+><DT
+><A
+NAME="gloss-service"
+></A
+><B
+>Service</B
+></DT
+><DD
+><P
+>In Windows NT environment, a boot-time background application
+ is referred to as a service. These are generally managed through the
+ control panel while logged in as an account with
+ <SPAN
+CLASS="QUOTE"
+>"Administrator"</SPAN
+> level capabilities. For more
+ information, consult your Windows manual or the MSKB.
+ </P
+></DD
+><DT
+><B
+>&#13; <ACRONYM
+CLASS="acronym"
+>SGML</ACRONYM
+>
+ </B
+></DT
+><DD
+><P
+>&#13; <ACRONYM
+CLASS="acronym"
+>SGML</ACRONYM
+>
+
+ stands for
+ <SPAN
+CLASS="QUOTE"
+>"Standard Generalized Markup Language"</SPAN
+>.
+ Created in the 1980's to provide an extensible means to maintain
+ documentation based upon content instead of presentation,
+ <ACRONYM
+CLASS="acronym"
+>SGML</ACRONYM
+>
+
+ has withstood the test of time as a robust, powerful language.
+ <I
+CLASS="glossterm"
+>&#13; <ACRONYM
+CLASS="acronym"
+>XML</ACRONYM
+>
+ </I
+>
+
+ is the
+ <SPAN
+CLASS="QUOTE"
+>"baby brother"</SPAN
+>
+
+ of SGML; any valid
+ <ACRONYM
+CLASS="acronym"
+>XML</ACRONYM
+>
+
+ document it, by definition, a valid
+ <ACRONYM
+CLASS="acronym"
+>SGML</ACRONYM
+>
+
+ document. The document you are reading is written and maintained in
+ <ACRONYM
+CLASS="acronym"
+>SGML</ACRONYM
+>,
+ and is also valid
+ <ACRONYM
+CLASS="acronym"
+>XML</ACRONYM
+>
+
+ if you modify the Document Type Definition.</P
+></DD
+></DL
+></DIV
+><DIV
+CLASS="glossdiv"
+><H1
+CLASS="glossdiv"
+><A
+NAME="gloss-t"
+>T</A
+></H1
+><DL
+><DT
+><A
+NAME="gloss-target-milestone"
+></A
+><B
+>Target Milestone</B
+></DT
+><DD
+><P
+>Target Milestones are Product goals. They are configurable on a
+ per-Product basis. Most software development houses have a concept of
+
+ <SPAN
+CLASS="QUOTE"
+>"milestones"</SPAN
+>
+
+ where the people funding a project expect certain functionality on
+ certain dates. Bugzilla facilitates meeting these milestones by
+ giving you the ability to declare by which milestone a bug will be
+ fixed, or an enhancement will be implemented.</P
+></DD
+><DT
+><A
+NAME="gloss-tcl"
+></A
+><B
+>Tool Command Language</B
+></DT
+> (TCL)<DD
+><P
+>TCL is an open source scripting language available for Windows,
+ Macintosh, and Unix based systems. Bugzilla 1.0 was written in TCL but
+ never released. The first release of Bugzilla was 2.0, which was when
+ it was ported to perl.
+ </P
+></DD
+></DL
+></DIV
+><DIV
+CLASS="glossdiv"
+><H1
+CLASS="glossdiv"
+><A
+NAME="gloss-z"
+>Z</A
+></H1
+><DL
+><DT
+><A
+NAME="gloss-zarro"
+></A
+><B
+>Zarro Boogs Found</B
+></DT
+><DD
+><P
+>This is just a goofy way of saying that there were no bugs
+ found matching your query. When asked to explain this message,
+ Terry had the following to say:
+ </P
+><A
+NAME="AEN3591"
+></A
+><TABLE
+BORDER="0"
+WIDTH="100%"
+CELLSPACING="0"
+CELLPADDING="0"
+CLASS="BLOCKQUOTE"
+><TR
+><TD
+WIDTH="10%"
+VALIGN="TOP"
+>&nbsp;</TD
+><TD
+VALIGN="TOP"
+><P
+>I've been asked to explain this ... way back when, when
+ Netscape released version 4.0 of its browser, we had a release
+ party. Naturally, there had been a big push to try and fix every
+ known bug before the release. Naturally, that hadn't actually
+ happened. (This is not unique to Netscape or to 4.0; the same thing
+ has happened with every software project I've ever seen.) Anyway,
+ at the release party, T-shirts were handed out that said something
+ like "Netscape 4.0: Zarro Boogs". Just like the software, the
+ T-shirt had no known bugs. Uh-huh.
+ </P
+><P
+>So, when you query for a list of bugs, and it gets no results,
+ you can think of this as a friendly reminder. Of *course* there are
+ bugs matching your query, they just aren't in the bugsystem yet...
+ </P
+></TD
+><TD
+WIDTH="10%"
+VALIGN="TOP"
+>&nbsp;</TD
+></TR
+><TR
+><TD
+COLSPAN="2"
+ALIGN="RIGHT"
+VALIGN="TOP"
+>--<SPAN
+CLASS="attribution"
+>Terry Weissman</SPAN
+></TD
+><TD
+WIDTH="10%"
+>&nbsp;</TD
+></TR
+></TABLE
+></DD
+></DL
+></DIV
+></DIV
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/about.html b/docs/en/html/about.html
new file mode 100644
index 000000000..9328f8683
--- /dev/null
+++ b/docs/en/html/about.html
@@ -0,0 +1,174 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>About This Guide</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="PREVIOUS"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="NEXT"
+TITLE="Copyright Information"
+HREF="copyright.html"></HEAD
+><BODY
+CLASS="chapter"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="index.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+></TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="copyright.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="chapter"
+><H1
+><A
+NAME="about"
+></A
+>Chapter 1. About This Guide</H1
+><DIV
+CLASS="TOC"
+><DL
+><DT
+><B
+>Table of Contents</B
+></DT
+><DT
+>1.1. <A
+HREF="copyright.html"
+>Copyright Information</A
+></DT
+><DT
+>1.2. <A
+HREF="disclaimer.html"
+>Disclaimer</A
+></DT
+><DT
+>1.3. <A
+HREF="newversions.html"
+>New Versions</A
+></DT
+><DT
+>1.4. <A
+HREF="credits.html"
+>Credits</A
+></DT
+><DT
+>1.5. <A
+HREF="conventions.html"
+>Document Conventions</A
+></DT
+></DL
+></DIV
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="copyright.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>The Bugzilla Guide - 4.0
+ Release</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+>&nbsp;</TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Copyright Information</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/administration.html b/docs/en/html/administration.html
new file mode 100644
index 000000000..f3789b6e7
--- /dev/null
+++ b/docs/en/html/administration.html
@@ -0,0 +1,435 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Administering Bugzilla</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="PREVIOUS"
+TITLE="Upgrading to New Releases"
+HREF="upgrade.html"><LINK
+REL="NEXT"
+TITLE="Bugzilla Configuration"
+HREF="parameters.html"></HEAD
+><BODY
+CLASS="chapter"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="upgrade.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+></TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="parameters.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="chapter"
+><H1
+><A
+NAME="administration"
+></A
+>Chapter 3. Administering Bugzilla</H1
+><DIV
+CLASS="TOC"
+><DL
+><DT
+><B
+>Table of Contents</B
+></DT
+><DT
+>3.1. <A
+HREF="parameters.html"
+>Bugzilla Configuration</A
+></DT
+><DD
+><DL
+><DT
+>3.1.1. <A
+HREF="parameters.html#param-requiredsettings"
+>Required Settings</A
+></DT
+><DT
+>3.1.2. <A
+HREF="parameters.html#param-admin-policies"
+>Administrative Policies</A
+></DT
+><DT
+>3.1.3. <A
+HREF="parameters.html#param-user-authentication"
+>User Authentication</A
+></DT
+><DT
+>3.1.4. <A
+HREF="parameters.html#param-attachments"
+>Attachments</A
+></DT
+><DT
+>3.1.5. <A
+HREF="parameters.html#param-bug-change-policies"
+>Bug Change Policies</A
+></DT
+><DT
+>3.1.6. <A
+HREF="parameters.html#param-bugfields"
+>Bug Fields</A
+></DT
+><DT
+>3.1.7. <A
+HREF="parameters.html#param-bugmoving"
+>Bug Moving</A
+></DT
+><DT
+>3.1.8. <A
+HREF="parameters.html#param-dependency-graphs"
+>Dependency Graphs</A
+></DT
+><DT
+>3.1.9. <A
+HREF="parameters.html#param-group-security"
+>Group Security</A
+></DT
+><DT
+>3.1.10. <A
+HREF="parameters.html#bzldap"
+>LDAP Authentication</A
+></DT
+><DT
+>3.1.11. <A
+HREF="parameters.html#bzradius"
+>RADIUS Authentication</A
+></DT
+><DT
+>3.1.12. <A
+HREF="parameters.html#param-email"
+>Email</A
+></DT
+><DT
+>3.1.13. <A
+HREF="parameters.html#param-patchviewer"
+>Patch Viewer</A
+></DT
+><DT
+>3.1.14. <A
+HREF="parameters.html#param-querydefaults"
+>Query Defaults</A
+></DT
+><DT
+>3.1.15. <A
+HREF="parameters.html#param-shadowdatabase"
+>Shadow Database</A
+></DT
+><DT
+>3.1.16. <A
+HREF="parameters.html#admin-usermatching"
+>User Matching</A
+></DT
+></DL
+></DD
+><DT
+>3.2. <A
+HREF="useradmin.html"
+>User Administration</A
+></DT
+><DD
+><DL
+><DT
+>3.2.1. <A
+HREF="useradmin.html#defaultuser"
+>Creating the Default User</A
+></DT
+><DT
+>3.2.2. <A
+HREF="useradmin.html#manageusers"
+>Managing Other Users</A
+></DT
+></DL
+></DD
+><DT
+>3.3. <A
+HREF="classifications.html"
+>Classifications</A
+></DT
+><DT
+>3.4. <A
+HREF="products.html"
+>Products</A
+></DT
+><DD
+><DL
+><DT
+>3.4.1. <A
+HREF="products.html#create-product"
+>Creating New Products</A
+></DT
+><DT
+>3.4.2. <A
+HREF="products.html#edit-products"
+>Editing Products</A
+></DT
+><DT
+>3.4.3. <A
+HREF="products.html#comps-vers-miles-products"
+>Adding or Editing Components, Versions and Target Milestones</A
+></DT
+><DT
+>3.4.4. <A
+HREF="products.html#product-group-controls"
+>Assigning Group Controls to Products</A
+></DT
+></DL
+></DD
+><DT
+>3.5. <A
+HREF="components.html"
+>Components</A
+></DT
+><DT
+>3.6. <A
+HREF="versions.html"
+>Versions</A
+></DT
+><DT
+>3.7. <A
+HREF="milestones.html"
+>Milestones</A
+></DT
+><DT
+>3.8. <A
+HREF="flags-overview.html"
+>Flags</A
+></DT
+><DD
+><DL
+><DT
+>3.8.1. <A
+HREF="flags-overview.html#flags-simpleexample"
+>A Simple Example</A
+></DT
+><DT
+>3.8.2. <A
+HREF="flags-overview.html#flags-about"
+>About Flags</A
+></DT
+><DT
+>3.8.3. <A
+HREF="flags-overview.html#flag-askto"
+>Using flag requests</A
+></DT
+><DT
+>3.8.4. <A
+HREF="flags-overview.html#flag-types"
+>Two Types of Flags</A
+></DT
+><DT
+>3.8.5. <A
+HREF="flags-overview.html#flags-admin"
+>Administering Flags</A
+></DT
+></DL
+></DD
+><DT
+>3.9. <A
+HREF="keywords.html"
+>Keywords</A
+></DT
+><DT
+>3.10. <A
+HREF="custom-fields.html"
+>Custom Fields</A
+></DT
+><DD
+><DL
+><DT
+>3.10.1. <A
+HREF="custom-fields.html#add-custom-fields"
+>Adding Custom Fields</A
+></DT
+><DT
+>3.10.2. <A
+HREF="custom-fields.html#edit-custom-fields"
+>Editing Custom Fields</A
+></DT
+><DT
+>3.10.3. <A
+HREF="custom-fields.html#delete-custom-fields"
+>Deleting Custom Fields</A
+></DT
+></DL
+></DD
+><DT
+>3.11. <A
+HREF="edit-values.html"
+>Legal Values</A
+></DT
+><DD
+><DL
+><DT
+>3.11.1. <A
+HREF="edit-values.html#edit-values-list"
+>Viewing/Editing legal values</A
+></DT
+><DT
+>3.11.2. <A
+HREF="edit-values.html#edit-values-delete"
+>Deleting legal values</A
+></DT
+></DL
+></DD
+><DT
+>3.12. <A
+HREF="bug_status_workflow.html"
+>Bug Status Workflow</A
+></DT
+><DT
+>3.13. <A
+HREF="voting.html"
+>Voting</A
+></DT
+><DT
+>3.14. <A
+HREF="quips.html"
+>Quips</A
+></DT
+><DT
+>3.15. <A
+HREF="groups.html"
+>Groups and Group Security</A
+></DT
+><DD
+><DL
+><DT
+>3.15.1. <A
+HREF="groups.html#create-groups"
+>Creating Groups</A
+></DT
+><DT
+>3.15.2. <A
+HREF="groups.html#edit-groups"
+>Editing Groups and Assigning Group Permissions</A
+></DT
+><DT
+>3.15.3. <A
+HREF="groups.html#users-and-groups"
+>Assigning Users to Groups</A
+></DT
+><DT
+>3.15.4. <A
+HREF="groups.html#AEN2110"
+>Assigning Group Controls to Products</A
+></DT
+></DL
+></DD
+><DT
+>3.16. <A
+HREF="sanitycheck.html"
+>Checking and Maintaining Database Integrity</A
+></DT
+></DL
+></DIV
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="upgrade.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="parameters.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Upgrading to New Releases</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+>&nbsp;</TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Bugzilla Configuration</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/api/Bugzilla.html b/docs/en/html/api/Bugzilla.html
new file mode 100644
index 000000000..952af3d8e
--- /dev/null
+++ b/docs/en/html/api/Bugzilla.html
@@ -0,0 +1,262 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href="./../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#METHODS'>METHODS</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla - Semi-persistent collection of various objects used by scripts and modules</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<pre class="code"> use Bugzilla;
+
+ sub someModulesSub {
+ Bugzilla-&#62;dbh-&#62;prepare(...);
+ Bugzilla-&#62;template-&#62;process(...);
+ }</pre>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>Several Bugzilla &#39;things&#39; are used by a variety of modules and scripts. This includes database handles, template objects, and so on.</p>
+
+<p>This module is a singleton intended as a central place to store these objects. This approach has several advantages:</p>
+
+<ul>
+<li>They&#39;re not global variables, so we don&#39;t have issues with them staying around with mod_perl</li>
+
+<li>Everything is in one central place, so it&#39;s easy to access, modify, and maintain</li>
+
+<li>Code in modules can get access to these objects without having to have them all passed from the caller, and the caller&#39;s caller, and....</li>
+
+<li>We can reuse objects across requests using mod_perl where appropriate (eg templates), whilst destroying those which are only valid for a single request (such as the current user)</li>
+</ul>
+
+<p>Note that items accessible via this object are demand-loaded when requested.</p>
+
+<p>For something to be added to this object, it should either be able to benefit from persistence when run under mod_perl (such as the a <code class="code">template</code> object), or should be something which is globally required by a large ammount of code (such as the current <code class="code">user</code> object).</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="METHODS"
+>METHODS</a></h1>
+
+<p>Note that all <code class="code">Bugzilla</code> functionality is method based; use <code class="code">Bugzilla-&#62;dbh</code> rather than <code class="code">Bugzilla::dbh</code>. Nothing cares about this now, but don&#39;t rely on that.</p>
+
+<dl>
+<dt><a name="template"
+><code class="code">template</code></a></dt>
+
+<dd>
+<p>The current <code class="code">Template</code> object, to be used for output</p>
+
+<dt><a name="template_inner"
+><code class="code">template_inner</code></a></dt>
+
+<dd>
+<p>If you ever need a <a href="./Bugzilla/Template.html" class="podlinkpod"
+>Bugzilla::Template</a> object while you&#39;re already processing a template, use this. Also use it if you want to specify the language to use. If no argument is passed, it uses the last language set. If the argument is &#34;&#34; (empty string), the language is reset to the current one (the one used by Bugzilla-&#62;template).</p>
+
+<dt><a name="cgi"
+><code class="code">cgi</code></a></dt>
+
+<dd>
+<p>The current <code class="code">cgi</code> object. Note that modules should <b>not</b> be using this in general. Not all Bugzilla actions are cgi requests. Its useful as a convenience method for those scripts/templates which are only use via CGI, though.</p>
+
+<dt><a name="input_params"
+><code class="code">input_params</code></a></dt>
+
+<dd>
+<p>When running under the WebService, this is a hashref containing the arguments passed to the WebService method that was called. When running in a normal script, this is a hashref containing the contents of the CGI parameters.</p>
+
+<p>Modifying this hashref will modify the CGI parameters or the WebService arguments (depending on what <code class="code">input_params</code> currently represents).</p>
+
+<p>This should be used instead of <a href="#cgi" class="podlinkpod"
+>&#34;cgi&#34;</a> in situations where your code could be being called by either a normal CGI script or a WebService method, such as during a code hook.</p>
+
+<p><b>Note:</b> When <code class="code">input_params</code> represents the CGI parameters, any parameter specified more than once (like <code class="code">foo=bar&#38;foo=baz</code>) will appear as an arrayref in the hash, but any value specified only once will appear as a scalar. This means that even if a value <i>can</i> appear multiple times, if it only <i>does</i> appear once, then it will be a scalar in <code class="code">input_params</code>, not an arrayref.</p>
+
+<dt><a name="user"
+><code class="code">user</code></a></dt>
+
+<dd>
+<p><code class="code">undef</code> if there is no currently logged in user or if the login code has not yet been run. If an sudo session is in progress, the <code class="code">Bugzilla::User</code> corresponding to the person who is being impersonated. If no session is in progress, the current <code class="code">Bugzilla::User</code>.</p>
+
+<dt><a name="set_user"
+><code class="code">set_user</code></a></dt>
+
+<dd>
+<p>Allows you to directly set what <a href="#user" class="podlinkpod"
+>&#34;user&#34;</a> will return. You can use this if you want to bypass <a href="#login" class="podlinkpod"
+>&#34;login&#34;</a> for some reason and directly &#34;log in&#34; a specific <a href="./Bugzilla/User.html" class="podlinkpod"
+>Bugzilla::User</a>. Be careful with it, though!</p>
+
+<dt><a name="sudoer"
+><code class="code">sudoer</code></a></dt>
+
+<dd>
+<p><code class="code">undef</code> if there is no currently logged in user, the currently logged in user is not in the <i>sudoer</i> group, or there is no session in progress. If an sudo session is in progress, returns the <code class="code">Bugzilla::User</code> object corresponding to the person who logged in and initiated the session. If no session is in progress, returns the <code class="code">Bugzilla::User</code> object corresponding to the currently logged in user.</p>
+
+<dt><a
+><code class="code">sudo_request</code> This begins an sudo session for the current request. It is meant to be used when a session has just started. For normal use, sudo access should normally be set at login time.</a></dt>
+
+<dd>
+<dt><a name="login"
+><code class="code">login</code></a></dt>
+
+<dd>
+<p>Logs in a user, returning a <code class="code">Bugzilla::User</code> object, or <code class="code">undef</code> if there is no logged in user. See <a href="./Bugzilla/Auth.html" class="podlinkpod"
+>Bugzilla::Auth</a>, and <a href="./Bugzilla/User.html" class="podlinkpod"
+>Bugzilla::User</a>.</p>
+
+<dt><a name="page_requires_login"
+><code class="code">page_requires_login</code></a></dt>
+
+<dd>
+<p>If the current page always requires the user to log in (for example, <code class="code">enter_bug.cgi</code> or any page called with <code class="code">?GoAheadAndLogIn=1</code>) then this will return something true. Otherwise it will return false. (This is set when you call <a href="#login" class="podlinkpod"
+>&#34;login&#34;</a>.)</p>
+
+<dt><a name="logout($option)"
+><code class="code">logout($option)</code></a></dt>
+
+<dd>
+<p>Logs out the current user, which involves invalidating user sessions and cookies. Three options are available from <a href="./Bugzilla/Constants.html" class="podlinkpod"
+>Bugzilla::Constants</a>: LOGOUT_CURRENT (the default), LOGOUT_ALL or LOGOUT_KEEP_CURRENT.</p>
+
+<dt><a name="logout_user($user)"
+><code class="code">logout_user($user)</code></a></dt>
+
+<dd>
+<p>Logs out the specified user (invalidating all his sessions), taking a Bugzilla::User instance.</p>
+
+<dt><a name="logout_by_id($id)"
+><code class="code">logout_by_id($id)</code></a></dt>
+
+<dd>
+<p>Logs out the user with the id specified. This is a compatibility function to be used in callsites where there is only a userid and no Bugzilla::User instance.</p>
+
+<dt><a name="logout_request"
+><code class="code">logout_request</code></a></dt>
+
+<dd>
+<p>Essentially, causes calls to <code class="code">Bugzilla-&#62;user</code> to return <code class="code">undef</code>. This has the effect of logging out a user for the current request only; cookies and database sessions are left intact.</p>
+
+<dt><a name="error_mode"
+><code class="code">error_mode</code></a></dt>
+
+<dd>
+<p>Call either <code class="code">Bugzilla-</code>error_mode(Bugzilla::Constants::ERROR_MODE_DIE)&#62; or <code class="code">Bugzilla-</code>error_mode(Bugzilla::Constants::ERROR_MODE_DIE_SOAP_FAULT)&#62; to change this flag&#39;s default of <code class="code">Bugzilla::Constants::ERROR_MODE_WEBPAGE</code> and to indicate that errors should be passed to error mode specific error handlers rather than being sent to a browser and finished with an exit().</p>
+
+<p>This is useful, for example, to keep <code class="code">eval</code> blocks from producing wild HTML on errors, making it easier for you to catch them. (Remember to reset the error mode to its previous value afterwards, though.)</p>
+
+<p><code class="code">Bugzilla-</code>error_mode&#62; will return the current state of this flag.</p>
+
+<p>Note that <code class="code">Bugzilla-</code>error_mode&#62; is being called by <code class="code">Bugzilla-</code>usage_mode&#62; on usage mode changes.</p>
+
+<dt><a name="usage_mode"
+><code class="code">usage_mode</code></a></dt>
+
+<dd>
+<p>Call either <code class="code">Bugzilla-</code>usage_mode(Bugzilla::Constants::USAGE_MODE_CMDLINE)&#62; or <code class="code">Bugzilla-</code>usage_mode(Bugzilla::Constants::USAGE_MODE_XMLRPC)&#62; near the beginning of your script to change this flag&#39;s default of <code class="code">Bugzilla::Constants::USAGE_MODE_BROWSER</code> and to indicate that Bugzilla is being called in a non-interactive manner.</p>
+
+<p>This influences error handling because on usage mode changes, <code class="code">usage_mode</code> calls <code class="code">Bugzilla-</code>error_mode&#62; to set an error mode which makes sense for the usage mode.</p>
+
+<p><code class="code">Bugzilla-</code>usage_mode&#62; will return the current state of this flag.</p>
+
+<dt><a name="installation_mode"
+><code class="code">installation_mode</code></a></dt>
+
+<dd>
+<p>Determines whether or not installation should be silent. See <a href="./Bugzilla/Constants.html" class="podlinkpod"
+>Bugzilla::Constants</a> for the <code class="code">INSTALLATION_MODE</code> constants.</p>
+
+<dt><a name="installation_answers"
+><code class="code">installation_answers</code></a></dt>
+
+<dd>
+<p>Returns a hashref representing any &#34;answers&#34; file passed to <em class="code">checksetup.pl</em>, used to automatically answer or skip prompts.</p>
+
+<dt><a name="dbh"
+><code class="code">dbh</code></a></dt>
+
+<dd>
+<p>The current database handle. See <a href="./DBI.html" class="podlinkpod"
+>DBI</a>.</p>
+
+<dt><a name="dbh_main"
+><code class="code">dbh_main</code></a></dt>
+
+<dd>
+<p>The main database handle. See <a href="./DBI.html" class="podlinkpod"
+>DBI</a>.</p>
+
+<dt><a name="languages"
+><code class="code">languages</code></a></dt>
+
+<dd>
+<p>Currently installed languages. Returns a reference to a list of RFC 1766 language tags of installed languages.</p>
+
+<dt><a name="switch_to_shadow_db"
+><code class="code">switch_to_shadow_db</code></a></dt>
+
+<dd>
+<p>Switch from using the main database to using the shadow database.</p>
+
+<dt><a name="switch_to_main_db"
+><code class="code">switch_to_main_db</code></a></dt>
+
+<dd>
+<p>Change the database object to refer to the main database.</p>
+
+<dt><a name="params"
+><code class="code">params</code></a></dt>
+
+<dd>
+<p>The current Parameters of Bugzilla, as a hashref. If <code class="code">data/params</code> does not exist, then we return an empty hashref. If <code class="code">data/params</code> is unreadable or is not valid perl, we <code class="code">die</code>.</p>
+
+<dt><a name="local_timezone"
+><code class="code">local_timezone</code></a></dt>
+
+<dd>
+<p>Returns the local timezone of the Bugzilla installation, as a DateTime::TimeZone object. This detection is very time consuming, so we cache this information for future references.</p>
+
+<dt><a name="job_queue"
+><code class="code">job_queue</code></a></dt>
+
+<dd>
+<p>Returns a <a href="./Bugzilla/JobQueue.html" class="podlinkpod"
+>Bugzilla::JobQueue</a> that you can use for queueing jobs. Will throw an error if job queueing is not correctly configured on this Bugzilla installation.</p>
+
+<dt><a name="feature"
+><code class="code">feature</code></a></dt>
+
+<dd>
+<p>Tells you whether or not a specific feature is enabled. For names of features, see <code class="code">OPTIONAL_MODULES</code> in <code class="code">Bugzilla::Install::Requirements</code>.</p>
+</dd>
+</dl>
+<p class="backlinkbottom"><b><a name="___bottom" href="index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/Attachment.html b/docs/en/html/api/Bugzilla/Attachment.html
new file mode 100644
index 000000000..6f47f0c4f
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/Attachment.html
@@ -0,0 +1,270 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::Attachment</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href=".././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::Attachment</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#Instance_Properties'>Instance Properties</a>
+ <li class='indexItem indexItem2'><a href='#Class_Methods'>Class Methods</a>
+ </ul>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::Attachment - Bugzilla attachment class.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<pre class="code"> use Bugzilla::Attachment;
+
+ # Get the attachment with the given ID.
+ my $attachment = new Bugzilla::Attachment($attach_id);
+
+ # Get the attachments with the given IDs.
+ my $attachments = Bugzilla::Attachment-&#62;new_from_list($attach_ids);</pre>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>Attachment.pm represents an attachment object. It is an implementation of <a href="../Bugzilla/Object.html" class="podlinkpod"
+>Bugzilla::Object</a>, and thus provides all methods that <a href="../Bugzilla/Object.html" class="podlinkpod"
+>Bugzilla::Object</a> provides.</p>
+
+<p>The methods that are specific to <code class="code">Bugzilla::Attachment</code> are listed below.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Instance_Properties"
+>Instance Properties</a></h2>
+
+<dl>
+<dt><a name="bug_id"
+><code class="code">bug_id</code></a></dt>
+
+<dd>
+<p>the ID of the bug to which the attachment is attached</p>
+</dd>
+</dl>
+
+<dl>
+<dt><a name="bug"
+><code class="code">bug</code></a></dt>
+
+<dd>
+<p>the bug object to which the attachment is attached</p>
+</dd>
+</dl>
+
+<dl>
+<dt><a name="description"
+><code class="code">description</code></a></dt>
+
+<dd>
+<p>user-provided text describing the attachment</p>
+</dd>
+</dl>
+
+<dl>
+<dt><a name="contenttype"
+><code class="code">contenttype</code></a></dt>
+
+<dd>
+<p>the attachment&#39;s MIME media type</p>
+</dd>
+</dl>
+
+<dl>
+<dt><a name="attacher"
+><code class="code">attacher</code></a></dt>
+
+<dd>
+<p>the user who attached the attachment</p>
+</dd>
+</dl>
+
+<dl>
+<dt><a name="attached"
+><code class="code">attached</code></a></dt>
+
+<dd>
+<p>the date and time on which the attacher attached the attachment</p>
+</dd>
+</dl>
+
+<dl>
+<dt><a name="modification_time"
+><code class="code">modification_time</code></a></dt>
+
+<dd>
+<p>the date and time on which the attachment was last modified.</p>
+</dd>
+</dl>
+
+<dl>
+<dt><a name="filename"
+><code class="code">filename</code></a></dt>
+
+<dd>
+<p>the name of the file the attacher attached</p>
+</dd>
+</dl>
+
+<dl>
+<dt><a name="ispatch"
+><code class="code">ispatch</code></a></dt>
+
+<dd>
+<p>whether or not the attachment is a patch</p>
+</dd>
+</dl>
+
+<dl>
+<dt><a name="isurl"
+><code class="code">isurl</code></a></dt>
+
+<dd>
+<p>whether or not the attachment is a URL</p>
+</dd>
+</dl>
+
+<dl>
+<dt><a name="isobsolete"
+><code class="code">isobsolete</code></a></dt>
+
+<dd>
+<p>whether or not the attachment is obsolete</p>
+</dd>
+</dl>
+
+<dl>
+<dt><a name="isprivate"
+><code class="code">isprivate</code></a></dt>
+
+<dd>
+<p>whether or not the attachment is private</p>
+</dd>
+</dl>
+
+<dl>
+<dt><a name="is_viewable"
+><code class="code">is_viewable</code></a></dt>
+
+<dd>
+<p>Returns 1 if the attachment has a content-type viewable in this browser. Note that we don&#39;t use $cgi-&#62;Accept()&#39;s ability to check if a content-type matches, because this will return a value even if it&#39;s matched by the generic */* which most browsers add to the end of their Accept: headers.</p>
+</dd>
+</dl>
+
+<dl>
+<dt><a name="data"
+><code class="code">data</code></a></dt>
+
+<dd>
+<p>the content of the attachment</p>
+</dd>
+</dl>
+
+<dl>
+<dt><a name="datasize"
+><code class="code">datasize</code></a></dt>
+
+<dd>
+<p>the length (in characters) of the attachment content</p>
+</dd>
+</dl>
+
+<dl>
+<dt><a name="flags"
+><code class="code">flags</code></a></dt>
+
+<dd>
+<p>flags that have been set on the attachment</p>
+</dd>
+</dl>
+
+<dl>
+<dt><a name="flag_types"
+><code class="code">flag_types</code></a></dt>
+
+<dd>
+<p>Return all flag types available for this attachment as well as flags already set, grouped by flag type.</p>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Class_Methods"
+>Class Methods</a></h2>
+
+<dl>
+<dt><a name="get_attachments_by_bug($bug_id)"
+><code class="code">get_attachments_by_bug($bug_id)</code></a></dt>
+
+<dd>
+<p>Description: retrieves and returns the attachments the currently logged in user can view for the given bug.</p>
+
+<p>Params: <code class="code">$bug_id</code> - integer - the ID of the bug for which to retrieve and return attachments.</p>
+
+<p>Returns: a reference to an array of attachment objects.</p>
+
+<dt><a name="validate_can_edit($attachment,_$product_id)"
+><code class="code">validate_can_edit($attachment, $product_id)</code></a></dt>
+
+<dd>
+<p>Description: validates if the user is allowed to view and edit the attachment. Only the submitter or someone with editbugs privs can edit it. Only the submitter and users in the insider group can view private attachments.</p>
+
+<p>Params: $attachment - the attachment object being edited. $product_id - the product ID the attachment belongs to.</p>
+
+<p>Returns: 1 on success, 0 otherwise.</p>
+
+<dt><a name="validate_obsolete($bug,_$attach_ids)"
+><code class="code">validate_obsolete($bug, $attach_ids)</code></a></dt>
+
+<dd>
+<p>Description: validates if attachments the user wants to mark as obsolete really belong to the given bug and are not already obsolete. Moreover, a user cannot mark an attachment as obsolete if he cannot view it (due to restrictions on it).</p>
+
+<p>Params: $bug - The bug object obsolete attachments should belong to. $attach_ids - The list of attachments to mark as obsolete.</p>
+
+<p>Returns: The list of attachment objects to mark as obsolete. Else an error is thrown.</p>
+
+<dt><a name="create"
+><code class="code">create</code></a></dt>
+
+<dd>
+<p>Description: inserts an attachment into the given bug.</p>
+
+<p>Params: takes a hashref with the following keys: <code class="code">bug</code> - Bugzilla::Bug object - the bug for which to insert the attachment. <code class="code">data</code> - Either a filehandle pointing to the content of the attachment, or the content of the attachment itself. <code class="code">description</code> - string - describe what the attachment is about. <code class="code">filename</code> - string - the name of the attachment (used by the browser when downloading it). If the attachment is a URL, this parameter has no effect. <code class="code">mimetype</code> - string - a valid MIME type. <code class="code">creation_ts</code> - string (optional) - timestamp of the insert as returned by SELECT LOCALTIMESTAMP(0). <code class="code">ispatch</code> - boolean (optional, default false) - true if the attachment is a patch. <code class="code">isprivate</code> - boolean (optional, default false) - true if the attachment is private. <code class="code">isurl</code> - boolean (optional, default false) - true if the attachment is a URL pointing to some external ressource. <code class="code">store_in_file</code> - boolean (optional, default false) - true if the attachment must be stored in data/attachments/ instead of in the DB.</p>
+
+<p>Returns: The new attachment object.</p>
+
+<dt><a name="remove_from_db()"
+><code class="code">remove_from_db()</code></a></dt>
+
+<dd>
+<p>Description: removes an attachment from the DB.</p>
+
+<p>Params: none</p>
+
+<p>Returns: nothing</p>
+</dd>
+</dl>
+<p class="backlinkbottom"><b><a name="___bottom" href="../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/Auth.html b/docs/en/html/api/Bugzilla/Auth.html
new file mode 100644
index 000000000..fdc5fcb9b
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/Auth.html
@@ -0,0 +1,411 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::Auth</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href=".././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::Auth</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#AUTHENTICATION_ERROR_CODES'>AUTHENTICATION ERROR CODES</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#AUTH_NODATA'>AUTH_NODATA</a>
+ <li class='indexItem indexItem2'><a href='#AUTH_ERROR'>AUTH_ERROR</a>
+ <li class='indexItem indexItem2'><a href='#AUTH_LOGINFAILED'>AUTH_LOGINFAILED</a>
+ <li class='indexItem indexItem2'><a href='#AUTH_NO_SUCH_USER'>AUTH_NO_SUCH_USER</a>
+ <li class='indexItem indexItem2'><a href='#AUTH_DISABLED'>AUTH_DISABLED</a>
+ <li class='indexItem indexItem2'><a href='#AUTH_LOCKOUT'>AUTH_LOCKOUT</a>
+ </ul>
+ <li class='indexItem indexItem1'><a href='#LOGIN_TYPES'>LOGIN TYPES</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#LOGIN_OPTIONAL'>LOGIN_OPTIONAL</a>
+ <li class='indexItem indexItem2'><a href='#LOGIN_NORMAL'>LOGIN_NORMAL</a>
+ <li class='indexItem indexItem2'><a href='#LOGIN_REQUIRED'>LOGIN_REQUIRED</a>
+ </ul>
+ <li class='indexItem indexItem1'><a href='#METHODS'>METHODS</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#Login'>Login</a>
+ <li class='indexItem indexItem2'><a href='#Info_Methods'>Info Methods</a>
+ </ul>
+ <li class='indexItem indexItem1'><a href='#STRUCTURE'>STRUCTURE</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#The_Info_Getter'>The Info Getter</a>
+ <li class='indexItem indexItem2'><a href='#The_Verifier'>The Verifier</a>
+ <li class='indexItem indexItem2'><a href='#The_Persistence_Mechanism'>The Persistence Mechanism</a>
+ <li class='indexItem indexItem2'><a href='#Other_Things_We_Do'>Other Things We Do</a>
+ <li class='indexItem indexItem2'><a href='#The_%24login_data_Hash'>The $login_data Hash</a>
+ </ul>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::Auth - An object that authenticates the login credentials for a user.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>Handles authentication for Bugzilla users.</p>
+
+<p>Authentication from Bugzilla involves two sets of modules.
+One set is used to obtain the username/password (from CGI,
+email,
+etc),
+and the other set uses this data to authenticate against the datasource (the Bugzilla DB,
+LDAP,
+PAM,
+etc.).</p>
+
+<p>Modules for obtaining the username/password are subclasses of <a href="../Bugzilla/Auth/Login.html" class="podlinkpod"
+>Bugzilla::Auth::Login</a>,
+and modules for authenticating are subclasses of <a href="../Bugzilla/Auth/Verify.html" class="podlinkpod"
+>Bugzilla::Auth::Verify</a>.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="AUTHENTICATION_ERROR_CODES"
+>AUTHENTICATION ERROR CODES</a></h1>
+
+<p>Whenever a method in the <code class="code">Bugzilla::Auth</code> family fails in some way,
+it will return a hashref containing at least a single key called <code class="code">failure</code>.
+<code class="code">failure</code> will point to an integer error code,
+and depending on the error code the hashref may contain more data.</p>
+
+<p>The error codes are explained here below.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="AUTH_NODATA"
+><code class="code">AUTH_NODATA</code></a></h2>
+
+<p>Insufficient login data was provided by the user.
+This may happen in several cases,
+such as cookie authentication when the cookie is not present.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="AUTH_ERROR"
+><code class="code">AUTH_ERROR</code></a></h2>
+
+<p>An error occurred when trying to use the login mechanism.</p>
+
+<p>The hashref will also contain an <code class="code">error</code> element,
+which is the name of an error from <code class="code">template/en/default/global/code-error.html</code> -- the same type of error that would be thrown by <a href="../Bugzilla/Error/ThrowCodeError.html" class="podlinkpod"
+>Bugzilla::Error::ThrowCodeError</a>.</p>
+
+<p>The hashref *may* contain an element called <code class="code">details</code>,
+which is a hashref that should be passed to <a href="../Bugzilla/Error/ThrowCodeError.html" class="podlinkpod"
+>Bugzilla::Error::ThrowCodeError</a> as the various fields to be used in the error message.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="AUTH_LOGINFAILED"
+><code class="code">AUTH_LOGINFAILED</code></a></h2>
+
+<p>An incorrect username or password was given.</p>
+
+<p>The hashref may also contain a <code class="code">failure_count</code> element,
+which specifies how many times the account has failed to log in within the lockout period (see <a href="#AUTH_LOCKOUT" class="podlinkpod"
+>&#34;AUTH_LOCKOUT&#34;</a>).
+This is used to warn the user when he is getting close to being locked out.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="AUTH_NO_SUCH_USER"
+><code class="code">AUTH_NO_SUCH_USER</code></a></h2>
+
+<p>This is an optional more-specific version of <code class="code">AUTH_LOGINFAILED</code>.
+Modules should throw this error when they discover that the requested user account actually does not exist,
+according to them.</p>
+
+<p>That is,
+for example,
+<a href="../Bugzilla/Auth/Verify/LDAP.html" class="podlinkpod"
+>Bugzilla::Auth::Verify::LDAP</a> would throw this if the user didn&#39;t exist in LDAP.</p>
+
+<p>The difference between <code class="code">AUTH_NO_SUCH_USER</code> and <code class="code">AUTH_LOGINFAILED</code> should never be communicated to the user,
+for security reasons.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="AUTH_DISABLED"
+><code class="code">AUTH_DISABLED</code></a></h2>
+
+<p>The user successfully logged in,
+but their account has been disabled.
+Usually this is throw only by <code class="code">Bugzilla::Auth::login</code>.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="AUTH_LOCKOUT"
+><code class="code">AUTH_LOCKOUT</code></a></h2>
+
+<p>The user&#39;s account is locked out after having failed to log in too many times within a certain period of time (as specified by <a href="../Bugzilla/Constants.html#LOGIN_LOCKOUT_INTERVAL" class="podlinkpod"
+>&#34;LOGIN_LOCKOUT_INTERVAL&#34; in Bugzilla::Constants</a>).</p>
+
+<p>The hashref will also contain a <code class="code">user</code> element,
+representing the <a href="../Bugzilla/User.html" class="podlinkpod"
+>Bugzilla::User</a> whose account is locked out.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="LOGIN_TYPES"
+>LOGIN TYPES</a></h1>
+
+<p>The <code class="code">login</code> function (below) can do different types of login,
+depending on what constant you pass into it:</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="LOGIN_OPTIONAL"
+><code class="code">LOGIN_OPTIONAL</code></a></h2>
+
+<p>A login is never required to access this data.
+Attempting to login is still useful,
+because this allows the page to be personalised.
+Note that an incorrect login will still trigger an error,
+even though the lack of a login will be OK.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="LOGIN_NORMAL"
+><code class="code">LOGIN_NORMAL</code></a></h2>
+
+<p>A login may or may not be required,
+depending on the setting of the <i>requirelogin</i> parameter.
+This is the default if you don&#39;t specify a type.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="LOGIN_REQUIRED"
+><code class="code">LOGIN_REQUIRED</code></a></h2>
+
+<p>A login is always required to access this data.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="METHODS"
+>METHODS</a></h1>
+
+<p>These are methods that can be called on a <code class="code">Bugzilla::Auth</code> object itself.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Login"
+>Login</a></h2>
+
+<dl>
+<dt><a name="login($type)"
+><code class="code">login($type)</code></a></dt>
+
+<dd>
+<p>Description: Logs a user in.
+For more details on how this works internally,
+see the section entitled &#34;STRUCTURE.&#34; Params: $type - One of the Login Types from above.
+Returns: An authenticated <code class="code">Bugzilla::User</code>.
+Or,
+if the type was not <code class="code">LOGIN_REQUIRED</code>,
+then we return an empty <code class="code">Bugzilla::User</code> if no login data was passed in.</p>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Info_Methods"
+>Info Methods</a></h2>
+
+<p>These are methods that give information about the Bugzilla::Auth object.</p>
+
+<dl>
+<dt><a name="can_change_password"
+><code class="code">can_change_password</code></a></dt>
+
+<dd>
+<p>Description: Tells you whether or not the current login system allows changing passwords.
+Params: None Returns: <code class="code">true</code> if users and administrators should be allowed to change passwords,
+<code class="code">false</code> otherwise.</p>
+
+<dt><a name="can_login"
+><code class="code">can_login</code></a></dt>
+
+<dd>
+<p>Description: Tells you whether or not the current login system allows users to log in through the web interface.
+Params: None Returns: <code class="code">true</code> if users can log in through the web interface,
+<code class="code">false</code> otherwise.</p>
+
+<dt><a name="can_logout"
+><code class="code">can_logout</code></a></dt>
+
+<dd>
+<p>Description: Tells you whether or not the current login system allows users to log themselves out.
+Params: None Returns: <code class="code">true</code> if users can log themselves out,
+<code class="code">false</code> otherwise.
+If a user isn&#39;t logged in,
+we always return <code class="code">false</code>.</p>
+
+<dt><a name="user_can_create_account"
+><code class="code">user_can_create_account</code></a></dt>
+
+<dd>
+<p>Description: Tells you whether or not users are allowed to manually create their own accounts,
+based on the current login system in use.
+Note that this doesn&#39;t check the <code class="code">createemailregexp</code> parameter--you have to do that by yourself in your code.
+Params: None Returns: <code class="code">true</code> if users are allowed to create new Bugzilla accounts,
+<code class="code">false</code> otherwise.</p>
+
+<dt><a name="can_change_email"
+><code class="code">can_change_email</code></a></dt>
+
+<dd>
+<p>Description: Whether or not the current login system allows users to change their own email address.
+Params: None Returns: <code class="code">true</code> if users can change their own email address,
+<code class="code">false</code> otherwise.</p>
+</dd>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="STRUCTURE"
+>STRUCTURE</a></h1>
+
+<p>This section is mostly interesting to developers who want to implement a new authentication type.
+It describes the general structure of the Bugzilla::Auth family,
+and how the <code class="code">login</code> function works.</p>
+
+<p>A <code class="code">Bugzilla::Auth</code> object is essentially a collection of a few other objects: the &#34;Info Getter,&#34; the &#34;Verifier,&#34; and the &#34;Persistence Mechanism.&#34;</p>
+
+<p>They are used inside the <code class="code">login</code> function in the following order:</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="The_Info_Getter"
+>The Info Getter</a></h2>
+
+<p>This is a <code class="code">Bugzilla::Auth::Login</code> object.
+Basically,
+it gets the username and password from the user,
+somehow.
+Or,
+it just gets enough information to uniquely identify a user,
+and passes that on down the line.
+(For example,
+a <code class="code">user_id</code> is enough to uniquely identify a user,
+even without a username and password.)</p>
+
+<p>Some Info Getters don&#39;t require any verification.
+For example,
+if we got the <code class="code">user_id</code> from a Cookie,
+we don&#39;t need to check the username and password.</p>
+
+<p>If an Info Getter returns only a <code class="code">user_id</code> and no username/password,
+then it MUST NOT require verification.
+If an Info Getter requires verfication,
+then it MUST return at least a <code class="code">username</code>.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="The_Verifier"
+>The Verifier</a></h2>
+
+<p>This verifies that the username and password are valid.</p>
+
+<p>It&#39;s possible that some methods of verification don&#39;t require a password.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="The_Persistence_Mechanism"
+>The Persistence Mechanism</a></h2>
+
+<p>This makes it so that the user doesn&#39;t have to log in on every page.
+Normally this object just sends a cookie to the user&#39;s web browser,
+as that&#39;s the most common method of &#34;login persistence.&#34;</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Other_Things_We_Do"
+>Other Things We Do</a></h2>
+
+<p>After we verify the username and password,
+sometimes we automatically create an account in the Bugzilla database,
+for certain authentication types.
+We use the &#34;Account Source&#34; to get data about the user,
+and create them in the database.
+(Or,
+if their data has changed since the last time they logged in,
+their data gets updated.)</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="The_$login_data_Hash"
+>The <code class="code">$login_data</code> Hash</a></h2>
+
+<p>All of the <code class="code">Bugzilla::Auth::Login</code> and <code class="code">Bugzilla::Auth::Verify</code> methods take an argument called <code class="code">$login_data</code>.
+This is basically a hash that becomes more and more populated as we go through the <code class="code">login</code> function.</p>
+
+<p>All <code class="code">Bugzilla::Auth::Login</code> and <code class="code">Bugzilla::Auth::Verify</code> methods also *return* the <code class="code">$login_data</code> structure,
+when they succeed.
+They may have added new data to it.</p>
+
+<p>For all <code class="code">Bugzilla::Auth::Login</code> and <code class="code">Bugzilla::Auth::Verify</code> methods,
+the rule is &#34;you must return the same hashref you were passed in.&#34; You can modify the hashref all you want,
+but you can&#39;t create a new one.
+The only time you can return a new one is if you&#39;re returning some error code instead of the <code class="code">$login_data</code> structure.</p>
+
+<p>Each <code class="code">Bugzilla::Auth::Login</code> or <code class="code">Bugzilla::Auth::Verify</code> method explains in its documentation which <code class="code">$login_data</code> elements are required by it,
+and which are set by it.</p>
+
+<p>Here are all of the elements that *may* be in <code class="code">$login_data</code>:</p>
+
+<dl>
+<dt><a name="user_id"
+><code class="code">user_id</code></a></dt>
+
+<dd>
+<p>A Bugzilla <code class="code">user_id</code> that uniquely identifies a user.</p>
+
+<dt><a name="username"
+><code class="code">username</code></a></dt>
+
+<dd>
+<p>The username that was provided by the user.</p>
+
+<dt><a name="bz_username"
+><code class="code">bz_username</code></a></dt>
+
+<dd>
+<p>The username of this user inside of Bugzilla.
+Sometimes this differs from <code class="code">username</code>.</p>
+
+<dt><a name="password"
+><code class="code">password</code></a></dt>
+
+<dd>
+<p>The password provided by the user.</p>
+
+<dt><a name="realname"
+><code class="code">realname</code></a></dt>
+
+<dd>
+<p>The real name of the user.</p>
+
+<dt><a name="extern_id"
+><code class="code">extern_id</code></a></dt>
+
+<dd>
+<p>Some string that uniquely identifies the user in an external account source.
+If this <code class="code">extern_id</code> already exists in the database with a different username,
+the username will be *changed* to be the username specified in this <code class="code">$login_data</code>.</p>
+
+<p>That is,
+let&#39;s my extern_id is <code class="code">mkanat</code>.
+I already have an account in Bugzilla with the username of <code class="code">mkanat@foo.com</code>.
+But this time,
+when I log in,
+I have an extern_id of <code class="code">mkanat</code> and a <code class="code">username</code> of <code class="code">mkanat@bar.org</code>.
+So now,
+Bugzilla will automatically change my username to <code class="code">mkanat@bar.org</code> instead of <code class="code">mkanat@foo.com</code>.</p>
+
+<dt><a name="user"
+><code class="code">user</code></a></dt>
+
+<dd>
+<p>A <a href="../Bugzilla/User.html" class="podlinkpod"
+>Bugzilla::User</a> object representing the authenticated user.
+Note that <code class="code">Bugzilla::Auth::login</code> may modify this object at various points.</p>
+</dd>
+</dl>
+<p class="backlinkbottom"><b><a name="___bottom" href="../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/Auth/Login.html b/docs/en/html/api/Bugzilla/Auth/Login.html
new file mode 100644
index 000000000..c6436f001
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/Auth/Login.html
@@ -0,0 +1,126 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::Auth::Login</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href="../.././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::Auth::Login</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#LOGIN_METHODS'>LOGIN METHODS</a>
+ <li class='indexItem indexItem1'><a href='#INFO_METHODS'>INFO METHODS</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::Auth::Login - Gets username/password data from the user.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>Bugzilla::Auth::Login is used to get information that uniquely identifies a user and allows us to authorize their Bugzilla access.</p>
+
+<p>It is mostly an abstract class,
+requiring subclasses to implement most methods.</p>
+
+<p>Note that callers outside of the <code class="code">Bugzilla::Auth</code> package should never create this object directly.
+Just create a <code class="code">Bugzilla::Auth</code> object and call <code class="code">login</code> on it.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="LOGIN_METHODS"
+>LOGIN METHODS</a></h1>
+
+<p>These are methods that have to do with getting the actual login data from the user or handling a login somehow.</p>
+
+<p>These methods are abstract -- they MUST be implemented by a subclass.</p>
+
+<dl>
+<dt><a name="get_login_info()"
+><code class="code">get_login_info()</code></a></dt>
+
+<dd>
+<p>Description: Gets a username/password from the user,
+or some other information that uniquely identifies them.
+Params: None Returns: A <code class="code">$login_data</code> hashref.
+(See <a href="../../Bugzilla/Auth.html" class="podlinkpod"
+>Bugzilla::Auth</a> for details.) The hashref MUST contain: <code class="code">user_id</code> *or* <code class="code">username</code> If this is a login method that requires verification,
+the hashref MUST contain <code class="code">password</code>.
+The hashref MAY contain <code class="code">realname</code> and <code class="code">extern_id</code>.</p>
+
+<dt><a name="fail_nodata()"
+><code class="code">fail_nodata()</code></a></dt>
+
+<dd>
+<p>Description: This function is called when Bugzilla doesn&#39;t get a username/password and the login type is <code class="code">LOGIN_REQUIRED</code> (See <a href="../../Bugzilla/Auth.html" class="podlinkpod"
+>Bugzilla::Auth</a> for a description of <code class="code">LOGIN_REQUIRED</code>).
+That is,
+this handles <code class="code">AUTH_NODATA</code> in that situation.</p>
+
+<pre class="code"> This function MUST stop CGI execution when it is complete.
+ That is, it must call C&#60;exit&#62; or C&#60;ThrowUserError&#62; or some
+ such thing.
+Params: None
+Returns: Never Returns.</pre>
+</dd>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="INFO_METHODS"
+>INFO METHODS</a></h1>
+
+<p>These are methods that describe the capabilities of this <code class="code">Bugzilla::Auth::Login</code> object. These are all no-parameter methods that return either <code class="code">true</code> or <code class="code">false</code>.</p>
+
+<dl>
+<dt><a name="can_logout"
+><code class="code">can_logout</code></a></dt>
+
+<dd>
+<p>Whether or not users can log out if they logged in using this object. Defaults to <code class="code">true</code>.</p>
+
+<dt><a name="can_login"
+><code class="code">can_login</code></a></dt>
+
+<dd>
+<p>Whether or not users can log in through the web interface using this object. Defaults to <code class="code">true</code>.</p>
+
+<dt><a name="requires_persistence"
+><code class="code">requires_persistence</code></a></dt>
+
+<dd>
+<p>Whether or not we should send the user a cookie if they logged in with this method. Defaults to <code class="code">true</code>.</p>
+
+<dt><a name="requires_verification"
+><code class="code">requires_verification</code></a></dt>
+
+<dd>
+<p>Whether or not we should check the username/password that we got from this login method. Defaults to <code class="code">true</code>.</p>
+
+<dt><a name="user_can_create_account"
+><code class="code">user_can_create_account</code></a></dt>
+
+<dd>
+<p>Whether or not users can create accounts, if this login method is currently being used by the system. Defaults to <code class="code">false</code>.</p>
+
+<dt><a name="is_automatic"
+><code class="code">is_automatic</code></a></dt>
+
+<dd>
+<p>True if this login method requires no interaction from the user within Bugzilla. (For example, <code class="code">Env</code> auth is &#34;automatic&#34; because the webserver just passes us an environment variable on most page requests, and does not ask the user for authentication information directly in Bugzilla.) Defaults to <code class="code">false</code>.</p>
+</dd>
+</dl>
+<p class="backlinkbottom"><b><a name="___bottom" href="../../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/Auth/Verify.html b/docs/en/html/api/Bugzilla/Auth/Verify.html
new file mode 100644
index 000000000..42f543ac7
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/Auth/Verify.html
@@ -0,0 +1,142 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::Auth::Verify</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href="../.././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::Auth::Verify</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#VERIFICATION_METHODS'>VERIFICATION METHODS</a>
+ <li class='indexItem indexItem1'><a href='#MODIFICATION_METHODS'>MODIFICATION METHODS</a>
+ <li class='indexItem indexItem1'><a href='#INFO_METHODS'>INFO METHODS</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::Auth::Verify - An object that verifies usernames and passwords.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>Bugzilla::Auth::Verify provides the &#34;Verifier&#34; part of the Bugzilla login process.
+(For details,
+see the &#34;STRUCTURE&#34; section of <a href="../../Bugzilla/Auth.html" class="podlinkpod"
+>Bugzilla::Auth</a>.)</p>
+
+<p>It is mostly an abstract class,
+requiring subclasses to implement most methods.</p>
+
+<p>Note that callers outside of the <code class="code">Bugzilla::Auth</code> package should never create this object directly.
+Just create a <code class="code">Bugzilla::Auth</code> object and call <code class="code">login</code> on it.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="VERIFICATION_METHODS"
+>VERIFICATION METHODS</a></h1>
+
+<p>These are the methods that have to do with the actual verification.</p>
+
+<p>Subclasses MUST implement these methods.</p>
+
+<dl>
+<dt><a name="check_credentials($login_data)"
+><code class="code">check_credentials($login_data)</code></a></dt>
+
+<dd>
+<p>Description: Checks whether or not a username is valid.
+Params: $login_data - A <code class="code">$login_data</code> hashref,
+as described in <a href="../../Bugzilla/Auth.html" class="podlinkpod"
+>Bugzilla::Auth</a>.
+This <code class="code">$login_data</code> hashref MUST contain <code class="code">username</code>,
+and SHOULD also contain <code class="code">password</code>.
+Returns: A <code class="code">$login_data</code> hashref with <code class="code">bz_username</code> set.
+This method may also set <code class="code">realname</code>.
+It must avoid changing anything that is already set.</p>
+</dd>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="MODIFICATION_METHODS"
+>MODIFICATION METHODS</a></h1>
+
+<p>These are methods that change data in the actual authentication backend.</p>
+
+<p>These methods are optional,
+they do not have to be implemented by subclasses.</p>
+
+<dl>
+<dt><a name="create_or_update_user($login_data)"
+><code class="code">create_or_update_user($login_data)</code></a></dt>
+
+<dd>
+<p>Description: Automatically creates a user account in the database if it doesn&#39;t already exist,
+or updates the account data if <code class="code">$login_data</code> contains newer information.</p>
+
+<p>Params: $login_data - A <code class="code">$login_data</code> hashref,
+as described in <a href="../../Bugzilla/Auth.html" class="podlinkpod"
+>Bugzilla::Auth</a>.
+This <code class="code">$login_data</code> hashref MUST contain either <code class="code">user_id</code>,
+<code class="code">bz_username</code>,
+or <code class="code">username</code>.
+If both <code class="code">username</code> and <code class="code">bz_username</code> are specified,
+<code class="code">bz_username</code> is used as the login name of the user to create in the database.
+It MAY also contain <code class="code">extern_id</code>,
+in which case it still MUST contain <code class="code">bz_username</code> or <code class="code">username</code>.
+It MAY contain <code class="code">password</code> and <code class="code">realname</code>.</p>
+
+<p>Returns: A hashref with one element,
+<code class="code">user</code>,
+which is a <a href="../../Bugzilla/User.html" class="podlinkpod"
+>Bugzilla::User</a> object.
+May also return a login error as described in <a href="../../Bugzilla/Auth.html" class="podlinkpod"
+>Bugzilla::Auth</a>.</p>
+
+<p>Note: This method is not abstract,
+it is actually implemented and creates accounts in the Bugzilla database.
+Subclasses should probably all call the <code class="code">Bugzilla::Auth::Verify</code> version of this function at the end of their own <code class="code">create_or_update_user</code>.</p>
+
+<dt><a name="change_password($user,_$password)"
+><code class="code">change_password($user,
+$password)</code></a></dt>
+
+<dd>
+<p>Description: Modifies the user&#39;s password in the authentication backend.
+Params: $user - A <a href="../../Bugzilla/User.html" class="podlinkpod"
+>Bugzilla::User</a> object representing the user whose password we want to change.
+$password - The user&#39;s new password.
+Returns: Nothing.</p>
+</dd>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="INFO_METHODS"
+>INFO METHODS</a></h1>
+
+<p>These are methods that describe the capabilities of this object.
+These are all no-parameter methods that return either <code class="code">true</code> or <code class="code">false</code>.</p>
+
+<dl>
+<dt><a name="user_can_create_account"
+><code class="code">user_can_create_account</code></a></dt>
+
+<dd>
+<p>Whether or not users can manually create accounts in this type of account source.
+Defaults to <code class="code">true</code>.</p>
+</dd>
+</dl>
+<p class="backlinkbottom"><b><a name="___bottom" href="../../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/CGI.html b/docs/en/html/api/Bugzilla/CGI.html
new file mode 100644
index 000000000..7371280f4
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/CGI.html
@@ -0,0 +1,119 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::CGI</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href=".././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::CGI</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#CHANGES_FROM_CGI.PM'>CHANGES FROM CGI.PM</a>
+ <li class='indexItem indexItem1'><a href='#ADDITIONAL_FUNCTIONS'>ADDITIONAL FUNCTIONS</a>
+ <li class='indexItem indexItem1'><a href='#SEE_ALSO'>SEE ALSO</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::CGI - CGI handling for Bugzilla</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<pre class="code"> use Bugzilla::CGI;
+
+ my $cgi = new Bugzilla::CGI();</pre>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>This package inherits from the standard CGI module, to provide additional Bugzilla-specific functionality. In general, see <a href="../CGI.html" class="podlinkpod"
+>the CGI.pm docs</a> for documention.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="CHANGES_FROM_CGI.PM"
+>CHANGES FROM <a href="../CGI.html" class="podlinkpod"
+>CGI.PM</a></a></h1>
+
+<p>Bugzilla::CGI has some differences from <a href="../CGI.html" class="podlinkpod"
+>CGI.pm</a>.</p>
+
+<dl>
+<dt><a name="cgi_error_is_automatically_checked"
+><code class="code">cgi_error</code> is automatically checked</a></dt>
+
+<dd>
+<p>After creating the CGI object, <code class="code">Bugzilla::CGI</code> automatically checks <i>cgi_error</i>, and throws a CodeError if a problem is detected.</p>
+</dd>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="ADDITIONAL_FUNCTIONS"
+>ADDITIONAL FUNCTIONS</a></h1>
+
+<p><i>Bugzilla::CGI</i> also includes additional functions.</p>
+
+<dl>
+<dt><a name="canonicalise_query(@exclude)"
+><code class="code">canonicalise_query(@exclude)</code></a></dt>
+
+<dd>
+<p>This returns a sorted string of the parameters, suitable for use in a url. Values in <code class="code">@exclude</code> are not included in the result.</p>
+
+<dt><a name="send_cookie"
+><code class="code">send_cookie</code></a></dt>
+
+<dd>
+<p>This routine is identical to the cookie generation part of CGI.pm&#39;s <code class="code">cookie</code> routine, except that it knows about Bugzilla&#39;s cookie_path and cookie_domain parameters and takes them into account if necessary. This should be used by all Bugzilla code (instead of <code class="code">cookie</code> or the <code class="code">-cookie</code> argument to <code class="code">header</code>), so that under mod_perl the headers can be sent correctly, using <code class="code">print</code> or the mod_perl APIs as appropriate.</p>
+
+<p>To remove (expire) a cookie, use <code class="code">remove_cookie</code>.</p>
+
+<dt><a name="remove_cookie"
+><code class="code">remove_cookie</code></a></dt>
+
+<dd>
+<p>This is a wrapper around send_cookie, setting an expiry date in the past, effectively removing the cookie.</p>
+
+<p>As its only argument, it takes the name of the cookie to expire.</p>
+
+<dt><a name="redirect_to_https"
+><code class="code">redirect_to_https</code></a></dt>
+
+<dd>
+<p>This routine redirects the client to the https version of the page that they&#39;re looking at, using the <code class="code">sslbase</code> parameter for the redirection.</p>
+
+<p>Generally you should use <a href="../Bugzilla/Util.html#do_ssl_redirect_if_required" class="podlinkpod"
+>&#34;do_ssl_redirect_if_required&#34; in Bugzilla::Util</a> instead of calling this directly.</p>
+
+<dt><a name="redirect_to_urlbase"
+><code class="code">redirect_to_urlbase</code></a></dt>
+
+<dd>
+<p>Redirects from the current URL to one prefixed by the urlbase parameter.</p>
+</dd>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SEE_ALSO"
+>SEE ALSO</a></h1>
+
+<p><a href="../CGI.html" class="podlinkpod"
+>CGI</a>, <a href="../CGI/Cookie.html" class="podlinkpod"
+>CGI::Cookie</a></p>
+<p class="backlinkbottom"><b><a name="___bottom" href="../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/Classification.html b/docs/en/html/api/Bugzilla/Classification.html
new file mode 100644
index 000000000..539417381
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/Classification.html
@@ -0,0 +1,87 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::Classification</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href=".././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::Classification</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#METHODS'>METHODS</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::Classification - Bugzilla classification class.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<pre class="code"> use Bugzilla::Classification;
+
+ my $classification = new Bugzilla::Classification(1);
+ my $classification = new Bugzilla::Classification({name =&#62; &#39;Acme&#39;});
+
+ my $id = $classification-&#62;id;
+ my $name = $classification-&#62;name;
+ my $description = $classification-&#62;description;
+ my $sortkey = $classification-&#62;sortkey;
+ my $product_count = $classification-&#62;product_count;
+ my $products = $classification-&#62;products;</pre>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>Classification.pm represents a classification object. It is an implementation of <a href="../Bugzilla/Object.html" class="podlinkpod"
+>Bugzilla::Object</a>, and thus provides all methods that <a href="../Bugzilla/Object.html" class="podlinkpod"
+>Bugzilla::Object</a> provides.</p>
+
+<p>The methods that are specific to <code class="code">Bugzilla::Classification</code> are listed below.</p>
+
+<p>A Classification is a higher-level grouping of Products.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="METHODS"
+>METHODS</a></h1>
+
+<dl>
+<dt><a name="product_count()"
+><code class="code">product_count()</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Returns the total number of products that belong to
+ the classification.
+
+ Params: none.
+
+ Returns: Integer - The total of products inside the classification.</pre>
+
+<dt><a name="products"
+><code class="code">products</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Returns all products of the classification.
+
+ Params: none.
+
+ Returns: A reference to an array of Bugzilla::Product objects.</pre>
+</dd>
+</dl>
+<p class="backlinkbottom"><b><a name="___bottom" href="../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/Comment.html b/docs/en/html/api/Bugzilla/Comment.html
new file mode 100644
index 000000000..51c289e3b
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/Comment.html
@@ -0,0 +1,145 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::Comment</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href=".././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::Comment</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#Accessors'>Accessors</a>
+ </ul>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::Comment - A Comment for a given bug</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<pre class="code"> use Bugzilla::Comment;
+
+ my $comment = Bugzilla::Comment-&#62;new($comment_id);
+ my $comments = Bugzilla::Comment-&#62;new_from_list($comment_ids);</pre>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>Bugzilla::Comment represents a comment attached to a bug.</p>
+
+<p>This implements all standard <code class="code">Bugzilla::Object</code> methods. See <a href="../Bugzilla/Object.html" class="podlinkpod"
+>Bugzilla::Object</a> for more details.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Accessors"
+>Accessors</a></h2>
+
+<dl>
+<dt><a name="bug_id"
+><code class="code">bug_id</code></a></dt>
+
+<dd>
+<p><code class="code">int</code> The ID of the bug to which the comment belongs.</p>
+
+<dt><a name="creation_ts"
+><code class="code">creation_ts</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The comment creation timestamp.</p>
+
+<dt><a name="body"
+><code class="code">body</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The body without any special additional text.</p>
+
+<dt><a name="work_time"
+><code class="code">work_time</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> Time spent as related to this comment.</p>
+
+<dt><a name="is_private"
+><code class="code">is_private</code></a></dt>
+
+<dd>
+<p><code class="code">boolean</code> Comment is marked as private</p>
+
+<dt><a name="already_wrapped"
+><code class="code">already_wrapped</code></a></dt>
+
+<dd>
+<p>If this comment is stored in the database word-wrapped, this will be <code class="code">1</code>. <code class="code">0</code> otherwise.</p>
+
+<dt><a name="author"
+><code class="code">author</code></a></dt>
+
+<dd>
+<p><a href="../Bugzilla/User.html" class="podlinkpod"
+>Bugzilla::User</a> who created the comment.</p>
+
+<dt><a name="count"
+><code class="code">count</code></a></dt>
+
+<dd>
+<p><code class="code">int</code> The position this comment is located in the full list of comments for a bug starting from 0.</p>
+
+<dt><a name="body_full"
+><code class="code">body_full</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p><code class="code">string</code> Body of the comment, including any special text (such as &#34;this bug was marked as a duplicate of...&#34;).</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="is_bugmail"
+><code class="code">is_bugmail</code></a></dt>
+
+<dd>
+<p><code class="code">boolean</code>. <code class="code">1</code> if this comment should be formatted specifically for bugmail.</p>
+
+<dt><a name="wrap"
+><code class="code">wrap</code></a></dt>
+
+<dd>
+<p><code class="code">boolean</code>. <code class="code">1</code> if the comment should be returned word-wrapped.</p>
+</dd>
+</dl>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>A string, the full text of the comment as it would be displayed to an end-user.</p>
+</dd>
+</dl>
+</dd>
+</dl>
+<p class="backlinkbottom"><b><a name="___bottom" href="../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/Component.html b/docs/en/html/api/Bugzilla/Component.html
new file mode 100644
index 000000000..3792cce90
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/Component.html
@@ -0,0 +1,281 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::Component</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href=".././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::Component</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#METHODS'>METHODS</a>
+ <li class='indexItem indexItem1'><a href='#CLASS_METHODS'>CLASS METHODS</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::Component - Bugzilla product component class.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<pre class="code"> use Bugzilla::Component;
+
+ my $component = new Bugzilla::Component($comp_id);
+ my $component = new Bugzilla::Component({ product =&#62; $product, name =&#62; $name });
+
+ my $bug_count = $component-&#62;bug_count();
+ my $bug_ids = $component-&#62;bug_ids();
+ my $id = $component-&#62;id;
+ my $name = $component-&#62;name;
+ my $description = $component-&#62;description;
+ my $product_id = $component-&#62;product_id;
+ my $default_assignee = $component-&#62;default_assignee;
+ my $default_qa_contact = $component-&#62;default_qa_contact;
+ my $initial_cc = $component-&#62;initial_cc;
+ my $product = $component-&#62;product;
+ my $bug_flag_types = $component-&#62;flag_types-&#62;{&#39;bug&#39;};
+ my $attach_flag_types = $component-&#62;flag_types-&#62;{&#39;attachment&#39;};
+
+ my $component = Bugzilla::Component-&#62;check({ product =&#62; $product, name =&#62; $name });
+
+ my $component =
+ Bugzilla::Component-&#62;create({ name =&#62; $name,
+ product =&#62; $product,
+ initialowner =&#62; $user_login1,
+ initialqacontact =&#62; $user_login2,
+ description =&#62; $description});
+
+ $component-&#62;set_name($new_name);
+ $component-&#62;set_description($new_description);
+ $component-&#62;set_default_assignee($new_login_name);
+ $component-&#62;set_default_qa_contact($new_login_name);
+ $component-&#62;set_cc_list(\@new_login_names);
+ $component-&#62;update();
+
+ $component-&#62;remove_from_db;</pre>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>Component.pm represents a Product Component object.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="METHODS"
+>METHODS</a></h1>
+
+<dl>
+<dt><a name="new($param)"
+><code class="code">new($param)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: The constructor is used to load an existing component
+ by passing a component ID or a hash with the product
+ object the component belongs to and the component name.
+
+ Params: $param - If you pass an integer, the integer is the
+ component ID from the database that we want to
+ read in. If you pass in a hash with the &#39;name&#39;
+ and &#39;product&#39; keys, then the value of the name
+ key is the name of a component being in the given
+ product.
+
+ Returns: A Bugzilla::Component object.</pre>
+
+<dt><a name="bug_count()"
+><code class="code">bug_count()</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Returns the total of bugs that belong to the component.
+
+ Params: none.
+
+ Returns: Integer with the number of bugs.</pre>
+
+<dt><a name="bugs_ids()"
+><code class="code">bugs_ids()</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Returns all bug IDs that belong to the component.
+
+ Params: none.
+
+ Returns: A reference to an array of bug IDs.</pre>
+
+<dt><a name="default_assignee()"
+><code class="code">default_assignee()</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Returns a user object that represents the default assignee for
+ the component.
+
+ Params: none.
+
+ Returns: A Bugzilla::User object.</pre>
+
+<dt><a name="default_qa_contact()"
+><code class="code">default_qa_contact()</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Returns a user object that represents the default QA contact for
+ the component.
+
+ Params: none.
+
+ Returns: A Bugzilla::User object.</pre>
+
+<dt><a name="initial_cc"
+><code class="code">initial_cc</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Returns a list of user objects representing users being
+ in the initial CC list.
+
+ Params: none.
+
+ Returns: An arrayref of L&#60;Bugzilla::User&#62; objects.</pre>
+
+<dt><a name="flag_types()"
+><code class="code">flag_types()</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Returns all bug and attachment flagtypes available for
+ the component.
+
+ Params: none.
+
+ Returns: Two references to an array of flagtype objects.</pre>
+
+<dt><a name="product()"
+><code class="code">product()</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Returns the product the component belongs to.
+
+ Params: none.
+
+ Returns: A Bugzilla::Product object.</pre>
+
+<dt><a name="set_name($new_name)"
+><code class="code">set_name($new_name)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Changes the name of the component.
+
+ Params: $new_name - new name of the component (string). This name
+ must be unique within the product.
+
+ Returns: Nothing.</pre>
+
+<dt><a name="set_description($new_desc)"
+><code class="code">set_description($new_desc)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Changes the description of the component.
+
+ Params: $new_desc - new description of the component (string).
+
+ Returns: Nothing.</pre>
+
+<dt><a name="set_default_assignee($new_assignee)"
+><code class="code">set_default_assignee($new_assignee)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Changes the default assignee of the component.
+
+ Params: $new_owner - login name of the new default assignee of
+ the component (string). This user account
+ must already exist.
+
+ Returns: Nothing.</pre>
+
+<dt><a name="set_default_qa_contact($new_qa_contact)"
+><code class="code">set_default_qa_contact($new_qa_contact)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Changes the default QA contact of the component.
+
+ Params: $new_qa_contact - login name of the new QA contact of
+ the component (string). This user
+ account must already exist.
+
+ Returns: Nothing.</pre>
+
+<dt><a name="set_cc_list(\@cc_list)"
+><code class="code">set_cc_list(\@cc_list)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Changes the list of users being in the CC list by default.
+
+ Params: \@cc_list - list of login names (string). All the user
+ accounts must already exist.
+
+ Returns: Nothing.</pre>
+
+<dt><a name="update()"
+><code class="code">update()</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Write changes made to the component into the DB.
+
+ Params: none.
+
+ Returns: A hashref with changes made to the component object.</pre>
+
+<dt><a name="remove_from_db()"
+><code class="code">remove_from_db()</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Deletes the current component from the DB. The object itself
+ is not destroyed.
+
+ Params: none.
+
+ Returns: Nothing.</pre>
+</dd>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="CLASS_METHODS"
+>CLASS METHODS</a></h1>
+
+<dl>
+<dt><a name="create(\%params)"
+><code class="code">create(\%params)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Create a new component for the given product.
+
+ Params: The hashref must have the following keys:
+ name - name of the new component (string). This name
+ must be unique within the product.
+ product - a Bugzilla::Product object to which
+ the Component is being added.
+ description - description of the new component (string).
+ initialowner - login name of the default assignee (string).
+ The following keys are optional:
+ initiaqacontact - login name of the default QA contact (string),
+ or an empty string to clear it.
+ initial_cc - an arrayref of login names to add to the
+ CC list by default.
+
+ Returns: A Bugzilla::Component object.</pre>
+</dd>
+</dl>
+<p class="backlinkbottom"><b><a name="___bottom" href="../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/Config.html b/docs/en/html/api/Bugzilla/Config.html
new file mode 100644
index 000000000..0314b9151
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/Config.html
@@ -0,0 +1,100 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::Config</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href=".././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::Config</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#FUNCTIONS'>FUNCTIONS</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#Parameters'>Parameters</a>
+ </ul>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::Config - Configuration parameters for Bugzilla</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<pre class="code"> # Administration functions
+ use Bugzilla::Config qw(:admin);
+
+ update_params();
+ SetParam($param, $value);
+ write_params();</pre>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>This package contains ways to access Bugzilla configuration parameters.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="FUNCTIONS"
+>FUNCTIONS</a></h1>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Parameters"
+>Parameters</a></h2>
+
+<p>Parameters can be set, retrieved, and updated.</p>
+
+<dl>
+<dt><a name="SetParam($name,_$value)"
+><code class="code">SetParam($name, $value)</code></a></dt>
+
+<dd>
+<p>Sets the param named $name to $value. Values are checked using the checker function for the given param if one exists.</p>
+
+<dt><a name="update_params()"
+><code class="code">update_params()</code></a></dt>
+
+<dd>
+<p>Updates the parameters, by transitioning old params to new formats, setting defaults for new params, and removing obsolete ones. Used by <em class="code">checksetup.pl</em> in the process of an installation or upgrade.</p>
+
+<p>Prints out information about what it&#39;s doing, if it makes any changes.</p>
+
+<p>May prompt the user for input, if certain required parameters are not specified.</p>
+
+<dt><a name="write_params($params)"
+><code class="code">write_params($params)</code></a></dt>
+
+<dd>
+<p>Description: Writes the parameters to disk.</p>
+
+<p>Params: <code class="code">$params</code> (optional) - A hashref to write to the disk instead of <code class="code">Bugzilla-</code>params&#62;. Used only by <code class="code">update_params</code>.</p>
+
+<p>Returns: nothing</p>
+
+<dt><a name="read_param_file()"
+><code class="code">read_param_file()</code></a></dt>
+
+<dd>
+<p>Description: Most callers should never need this. This is used by <code class="code">Bugzilla-</code>params&#62; to directly read <code class="code">$datadir/params</code> and load it into memory. Use <code class="code">Bugzilla-</code>params&#62; instead.</p>
+
+<p>Params: none</p>
+
+<p>Returns: A hashref containing the current params in <code class="code">$datadir/params</code>.</p>
+</dd>
+</dl>
+<p class="backlinkbottom"><b><a name="___bottom" href="../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/Config/Common.html b/docs/en/html/api/Bugzilla/Config/Common.html
new file mode 100644
index 000000000..243299e03
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/Config/Common.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::Config::Common</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href="../.././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::Config::Common</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#Functions'>Functions</a>
+ </ul>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::Config::Common - Parameter checking functions</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>All parameter checking functions are called with two parameters: the value to check,
+and a hash with the details of the param (type,
+default etc.) as defined in the relevant <em class="code">Bugzilla::Config::*</em> package.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Functions"
+>Functions</a></h2>
+
+<dl>
+<dt><a name="check_multi"
+><code class="code">check_multi</code></a></dt>
+
+<dd>
+<p>Checks that a multi-valued parameter (ie types <code class="code">s</code>,
+<code class="code">o</code> or <code class="code">m</code>) satisfies its contraints.</p>
+
+<dt><a name="check_numeric"
+><code class="code">check_numeric</code></a></dt>
+
+<dd>
+<p>Checks that the value is a valid number</p>
+
+<dt><a name="check_regexp"
+><code class="code">check_regexp</code></a></dt>
+
+<dd>
+<p>Checks that the value is a valid regexp</p>
+</dd>
+</dl>
+<p class="backlinkbottom"><b><a name="___bottom" href="../../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/DB.html b/docs/en/html/api/Bugzilla/DB.html
new file mode 100644
index 000000000..78c09087d
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/DB.html
@@ -0,0 +1,1422 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::DB</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href=".././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::DB</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#CONSTANTS'>CONSTANTS</a>
+ <li class='indexItem indexItem1'><a href='#CONNECTION'>CONNECTION</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#Functions'>Functions</a>
+ </ul>
+ <li class='indexItem indexItem1'><a href='#ABSTRACT_METHODS'>ABSTRACT METHODS</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#Constructor'>Constructor</a>
+ <li class='indexItem indexItem2'><a href='#SQL_Generation'>SQL Generation</a>
+ </ul>
+ <li class='indexItem indexItem1'><a href='#IMPLEMENTED_METHODS'>IMPLEMENTED METHODS</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#General_Information_Methods'>General Information Methods</a>
+ <li class='indexItem indexItem2'><a href='#Database_Setup_Methods'>Database Setup Methods</a>
+ <li class='indexItem indexItem2'><a href='#Schema_Modification_Methods'>Schema Modification Methods</a>
+ <li class='indexItem indexItem2'><a href='#Schema_Information_Methods'>Schema Information Methods</a>
+ <li class='indexItem indexItem2'><a href='#Transaction_Methods'>Transaction Methods</a>
+ </ul>
+ <li class='indexItem indexItem1'><a href='#SUBCLASS_HELPERS'>SUBCLASS HELPERS</a>
+ <li class='indexItem indexItem1'><a href='#SEE_ALSO'>SEE ALSO</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::DB - Database access routines,
+using <a href="../DBI.html" class="podlinkpod"
+>DBI</a></p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<pre class="code"> # Obtain db handle
+ use Bugzilla::DB;
+ my $dbh = Bugzilla-&#62;dbh;
+
+ # prepare a query using DB methods
+ my $sth = $dbh-&#62;prepare(&#34;SELECT &#34; .
+ $dbh-&#62;sql_date_format(&#34;creation_ts&#34;, &#34;%Y%m%d&#34;) .
+ &#34; FROM bugs WHERE bug_status != &#39;RESOLVED&#39; &#34; .
+ $dbh-&#62;sql_limit(1));
+
+ # Execute the query
+ $sth-&#62;execute;
+
+ # Get the results
+ my @result = $sth-&#62;fetchrow_array;
+
+ # Schema Modification
+ $dbh-&#62;bz_add_column($table, $name, \%definition, $init_value);
+ $dbh-&#62;bz_add_index($table, $name, $definition);
+ $dbh-&#62;bz_add_table($name);
+ $dbh-&#62;bz_drop_index($table, $name);
+ $dbh-&#62;bz_drop_table($name);
+ $dbh-&#62;bz_alter_column($table, $name, \%new_def, $set_nulls_to);
+ $dbh-&#62;bz_drop_column($table, $column);
+ $dbh-&#62;bz_rename_column($table, $old_name, $new_name);
+
+ # Schema Information
+ my $column = $dbh-&#62;bz_column_info($table, $column);
+ my $index = $dbh-&#62;bz_index_info($table, $index);</pre>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>Functions in this module allows creation of a database handle to connect to the Bugzilla database. This should never be done directly; all users should use the <a href="../Bugzilla.html" class="podlinkpod"
+>Bugzilla</a> module to access the current <code class="code">dbh</code> instead.</p>
+
+<p>This module also contains methods extending the returned handle with functionality which is different between databases allowing for easy customization for particular database via inheritance. These methods should be always preffered over hard-coding SQL commands.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="CONSTANTS"
+>CONSTANTS</a></h1>
+
+<p>Subclasses of Bugzilla::DB are required to define certain constants. These constants are required to be subroutines or &#34;use constant&#34; variables.</p>
+
+<dl>
+<dt><a name="BLOB_TYPE"
+><code class="code">BLOB_TYPE</code></a></dt>
+
+<dd>
+<p>The <code class="code">\%attr</code> argument that must be passed to bind_param in order to correctly escape a <code class="code">LONGBLOB</code> type.</p>
+
+<dt><a name="ISOLATION_LEVEL"
+><code class="code">ISOLATION_LEVEL</code></a></dt>
+
+<dd>
+<p>The argument that this database should send to <code class="code">SET TRANSACTION ISOLATION LEVEL</code> when starting a transaction. If you override this in a subclass, the isolation level you choose should be as strict as or more strict than the default isolation level defined in <a href="../Bugzilla/DB.html" class="podlinkpod"
+>Bugzilla::DB</a>.</p>
+</dd>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="CONNECTION"
+>CONNECTION</a></h1>
+
+<p>A new database handle to the required database can be created using this module. This is normally done by the <a href="../Bugzilla.html" class="podlinkpod"
+>Bugzilla</a> module, and so these routines should not be called from anywhere else.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Functions"
+>Functions</a></h2>
+
+<dl>
+<dt><a name="connect_main"
+><code class="code">connect_main</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Function to connect to the main database, returning a new database handle.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a
+><code class="code">$no_db_name</code> (optional) - If true, connect to the database server, but don&#39;t connect to a specific database. This is only used when creating a database. After you create the database, you should re-create a new Bugzilla::DB object without using this parameter.</a></dt>
+</dl>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>New instance of the DB class</p>
+</dd>
+</dl>
+
+<dt><a name="connect_shadow"
+><code class="code">connect_shadow</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Function to connect to the shadow database, returning a new database handle. This routine <code class="code">die</code>s if no shadow database is configured.</p>
+
+<dt><a name="Params_(none)"
+><b>Params</b> (none)</a></dt>
+
+<dd>
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>A new instance of the DB class</p>
+</dd>
+</dl>
+
+<dt><a name="bz_check_requirements"
+><code class="code">bz_check_requirements</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Checks to make sure that you have the correct DBD and database version installed for the database that Bugzilla will be using. Prints a message and exits if you don&#39;t pass the requirements.</p>
+
+<p>If <code class="code">$db_check</code> is false (from <em class="code">localconfig</em>), we won&#39;t check the database version.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="$output_-_true_if_the_function_should_display_informational_output_about_what_it&#39;s_doing,_such_as_versions_found."
+><code class="code">$output</code> - <code class="code">true</code> if the function should display informational output about what it&#39;s doing, such as versions found.</a></dt>
+</dl>
+
+<dt><a name="Returns_(nothing)"
+><b>Returns</b> (nothing)</a></dt>
+</dl>
+
+<dt><a name="bz_create_database"
+><code class="code">bz_create_database</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Creates an empty database with the name <code class="code">$db_name</code>, if that database doesn&#39;t already exist. Prints an error message and exits if we can&#39;t create the database.</p>
+
+<dt><a name="Params_(none)"
+><b>Params</b> (none)</a></dt>
+
+<dd>
+<dt><a name="Returns_(nothing)"
+><b>Returns</b> (nothing)</a></dt>
+</dl>
+
+<dt><a name="_connect"
+><code class="code">_connect</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Internal function, creates and returns a new, connected instance of the correct DB class. This routine <code class="code">die</code>s if no driver is specified.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="$driver_-_name_of_the_database_driver_to_use"
+><code class="code">$driver</code> - name of the database driver to use</a></dt>
+
+<dd>
+<dt><a name="$host_-_host_running_the_database_we_are_connecting_to"
+><code class="code">$host</code> - host running the database we are connecting to</a></dt>
+
+<dd>
+<dt><a name="$dbname_-_name_of_the_database_to_connect_to"
+><code class="code">$dbname</code> - name of the database to connect to</a></dt>
+
+<dd>
+<dt><a name="$port_-_port_the_database_is_listening_on"
+><code class="code">$port</code> - port the database is listening on</a></dt>
+
+<dd>
+<dt><a name="$sock_-_socket_the_database_is_listening_on"
+><code class="code">$sock</code> - socket the database is listening on</a></dt>
+
+<dd>
+<dt><a name="$user_-_username_used_to_log_in_to_the_database"
+><code class="code">$user</code> - username used to log in to the database</a></dt>
+
+<dd>
+<dt><a name="$pass_-_password_used_to_log_in_to_the_database"
+><code class="code">$pass</code> - password used to log in to the database</a></dt>
+</dl>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>A new instance of the DB class</p>
+</dd>
+</dl>
+
+<dt><a name="_handle_error"
+><code class="code">_handle_error</code></a></dt>
+
+<dd>
+<p>Function passed to the DBI::connect call for error handling. It shortens the error for printing.</p>
+
+<dt><a name="import"
+><code class="code">import</code></a></dt>
+
+<dd>
+<p>Overrides the standard import method to check that derived class implements all required abstract methods. Also calls original implementation in its super class.</p>
+</dd>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="ABSTRACT_METHODS"
+>ABSTRACT METHODS</a></h1>
+
+<p>Note: Methods which can be implemented generically for all DBs are implemented in this module. If needed, they can be overridden with DB specific code. Methods which do not have standard implementation are abstract and must be implemented for all supported databases separately. To avoid confusion with standard DBI methods, all methods returning string with formatted SQL command have prefix <code class="code">sql_</code>. All other methods have prefix <code class="code">bz_</code>.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Constructor"
+>Constructor</a></h2>
+
+<dl>
+<dt><a name="new"
+><code class="code">new</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Constructor. Abstract method, should be overridden by database specific code.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="$user_-_username_used_to_log_in_to_the_database"
+><code class="code">$user</code> - username used to log in to the database</a></dt>
+
+<dd>
+<dt><a name="$pass_-_password_used_to_log_in_to_the_database"
+><code class="code">$pass</code> - password used to log in to the database</a></dt>
+
+<dd>
+<dt><a name="$host_-_host_running_the_database_we_are_connecting_to"
+><code class="code">$host</code> - host running the database we are connecting to</a></dt>
+
+<dd>
+<dt><a name="$dbname_-_name_of_the_database_to_connect_to"
+><code class="code">$dbname</code> - name of the database to connect to</a></dt>
+
+<dd>
+<dt><a name="$port_-_port_the_database_is_listening_on"
+><code class="code">$port</code> - port the database is listening on</a></dt>
+
+<dd>
+<dt><a name="$sock_-_socket_the_database_is_listening_on"
+><code class="code">$sock</code> - socket the database is listening on</a></dt>
+</dl>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>A new instance of the DB class</p>
+
+<dt><a name="Note"
+><b>Note</b></a></dt>
+
+<dd>
+<p>The constructor should create a DSN from the parameters provided and then call <code class="code">db_new()</code> method of its super class to create a new class instance. See <a href="../db_new.html" class="podlinkpod"
+>db_new</a> description in this module. As per DBI documentation, all class variables must be prefixed with &#34;private_&#34;. See <a href="../DBI.html" class="podlinkpod"
+>DBI</a>.</p>
+</dd>
+</dl>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="SQL_Generation"
+>SQL Generation</a></h2>
+
+<dl>
+<dt><a name="sql_regexp"
+><code class="code">sql_regexp</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Outputs SQL regular expression operator for POSIX regex searches (case insensitive) in format suitable for a given database.</p>
+
+<p>Abstract method, should be overridden by database specific code.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="$expr_-_SQL_expression_for_the_text_to_be_searched_(scalar)"
+><code class="code">$expr</code> - SQL expression for the text to be searched (scalar)</a></dt>
+
+<dd>
+<dt><a name="$pattern_-_the_regular_expression_to_search_for_(scalar)"
+><code class="code">$pattern</code> - the regular expression to search for (scalar)</a></dt>
+
+<dd>
+<dt><a name="$nocheck_-_true_if_the_pattern_should_not_be_tested;_false_otherwise_(boolean)"
+><code class="code">$nocheck</code> - true if the pattern should not be tested; false otherwise (boolean)</a></dt>
+
+<dd>
+<dt><a name="$real_pattern_-_the_real_regular_expression_to_search_for._This_argument_is_used_when_$pattern_is_a_placeholder_(&#39;?&#39;)."
+><code class="code">$real_pattern</code> - the real regular expression to search for. This argument is used when <code class="code">$pattern</code> is a placeholder (&#39;?&#39;).</a></dt>
+</dl>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>Formatted SQL for regular expression search (e.g. REGEXP) (scalar)</p>
+</dd>
+</dl>
+
+<dt><a name="sql_not_regexp"
+><code class="code">sql_not_regexp</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Outputs SQL regular expression operator for negative POSIX regex searches (case insensitive) in format suitable for a given database.</p>
+
+<p>Abstract method, should be overridden by database specific code.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<p>Same as <a href="#sql_regexp" class="podlinkpod"
+>&#34;sql_regexp&#34;</a>.</p>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>Formatted SQL for negative regular expression search (e.g. NOT REGEXP) (scalar)</p>
+</dd>
+</dl>
+
+<dt><a name="sql_limit"
+><code class="code">sql_limit</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Returns SQL syntax for limiting results to some number of rows with optional offset if not starting from the begining.</p>
+
+<p>Abstract method, should be overridden by database specific code.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="$limit_-_number_of_rows_to_return_from_query_(scalar)"
+><code class="code">$limit</code> - number of rows to return from query (scalar)</a></dt>
+
+<dd>
+<dt><a name="$offset_-_number_of_rows_to_skip_before_counting_(scalar)"
+><code class="code">$offset</code> - number of rows to skip before counting (scalar)</a></dt>
+</dl>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>Formatted SQL for limiting number of rows returned from query with optional offset (e.g. LIMIT 1, 1) (scalar)</p>
+</dd>
+</dl>
+
+<dt><a name="sql_from_days"
+><code class="code">sql_from_days</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Outputs SQL syntax for converting Julian days to date.</p>
+
+<p>Abstract method, should be overridden by database specific code.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="$days_-_days_to_convert_to_date"
+><code class="code">$days</code> - days to convert to date</a></dt>
+</dl>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>Formatted SQL for returning Julian days in dates. (scalar)</p>
+</dd>
+</dl>
+
+<dt><a name="sql_to_days"
+><code class="code">sql_to_days</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Outputs SQL syntax for converting date to Julian days.</p>
+
+<p>Abstract method, should be overridden by database specific code.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="$date_-_date_to_convert_to_days"
+><code class="code">$date</code> - date to convert to days</a></dt>
+</dl>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>Formatted SQL for returning date fields in Julian days. (scalar)</p>
+</dd>
+</dl>
+
+<dt><a name="sql_date_format"
+><code class="code">sql_date_format</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Outputs SQL syntax for formatting dates.</p>
+
+<p>Abstract method, should be overridden by database specific code.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="$date_-_date_or_name_of_date_type_column_(scalar)"
+><code class="code">$date</code> - date or name of date type column (scalar)</a></dt>
+
+<dd>
+<dt><a
+><code class="code">$format</code> - format string for date output (scalar) (<code class="code">%Y</code> = year, four digits, <code class="code">%y</code> = year, two digits, <code class="code">%m</code> = month, <code class="code">%d</code> = day, <code class="code">%a</code> = weekday name, 3 letters, <code class="code">%H</code> = hour 00-23, <code class="code">%i</code> = minute, <code class="code">%s</code> = second)</a></dt>
+</dl>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>Formatted SQL for date formatting (scalar)</p>
+</dd>
+</dl>
+
+<dt><a name="sql_interval"
+><code class="code">sql_interval</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Outputs proper SQL syntax for a time interval function.</p>
+
+<p>Abstract method, should be overridden by database specific code.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="$interval_-_the_time_interval_requested_(e.g._&#39;30&#39;)_(integer)"
+><code class="code">$interval</code> - the time interval requested (e.g. &#39;30&#39;) (integer)</a></dt>
+
+<dd>
+<dt><a name="$units_-_the_units_the_interval_is_in_(e.g._&#39;MINUTE&#39;)_(string)"
+><code class="code">$units</code> - the units the interval is in (e.g. &#39;MINUTE&#39;) (string)</a></dt>
+</dl>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>Formatted SQL for interval function (scalar)</p>
+</dd>
+</dl>
+
+<dt><a name="sql_position"
+><code class="code">sql_position</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Outputs proper SQL syntax determining position of a substring (fragment) withing a string (text). Note: if the substring or text are string constants, they must be properly quoted (e.g. &#34;&#39;pattern&#39;&#34;).</p>
+
+<p>It searches for the string in a case-sensitive manner. If you want to do a case-insensitive search, use <a href="#sql_iposition" class="podlinkpod"
+>&#34;sql_iposition&#34;</a>.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="$fragment_-_the_string_fragment_we_are_searching_for_(scalar)"
+><code class="code">$fragment</code> - the string fragment we are searching for (scalar)</a></dt>
+
+<dd>
+<dt><a name="$text_-_the_text_to_search_(scalar)"
+><code class="code">$text</code> - the text to search (scalar)</a></dt>
+</dl>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>Formatted SQL for substring search (scalar)</p>
+</dd>
+</dl>
+
+<dt><a name="sql_iposition"
+><code class="code">sql_iposition</code></a></dt>
+
+<dd>
+<p>Just like <a href="#sql_position" class="podlinkpod"
+>&#34;sql_position&#34;</a>, but case-insensitive.</p>
+
+<dt><a name="sql_group_by"
+><code class="code">sql_group_by</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Outputs proper SQL syntax for grouping the result of a query.</p>
+
+<p>For ANSI SQL databases, we need to group by all columns we are querying for (except for columns used in aggregate functions). Some databases require (or even allow) to specify only one or few columns if the result is uniquely defined. For those databases, the default implementation needs to be overloaded.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="$needed_columns_-_string_with_comma_separated_list_of_columns_we_need_to_group_by_to_get_expected_result_(scalar)"
+><code class="code">$needed_columns</code> - string with comma separated list of columns we need to group by to get expected result (scalar)</a></dt>
+
+<dd>
+<dt><a
+><code class="code">$optional_columns</code> - string with comma separated list of all other columns we are querying for, but which are not in the required list.</a></dt>
+</dl>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>Formatted SQL for row grouping (scalar)</p>
+</dd>
+</dl>
+
+<dt><a name="sql_string_concat"
+><code class="code">sql_string_concat</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Returns SQL syntax for concatenating multiple strings (constants or values from table columns) together.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="@params_-_array_of_column_names_or_strings_to_concatenate"
+><code class="code">@params</code> - array of column names or strings to concatenate</a></dt>
+</dl>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>Formatted SQL for concatenating specified strings</p>
+</dd>
+</dl>
+
+<dt><a name="sql_string_until"
+><code class="code">sql_string_until</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Returns SQL for truncating a string at the first occurrence of a certain substring.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<p>Note that both parameters need to be sql-quoted.</p>
+
+<dt><a name="$string_The_string_we&#39;re_truncating"
+><code class="code">$string</code> The string we&#39;re truncating</a></dt>
+
+<dd>
+<dt><a name="$substring_The_substring_we&#39;re_truncating_at."
+><code class="code">$substring</code> The substring we&#39;re truncating at.</a></dt>
+</dl>
+
+<dt><a name="sql_fulltext_search"
+><code class="code">sql_fulltext_search</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Returns SQL syntax for performing a full text search for specified text on a given column.</p>
+
+<p>There is a ANSI SQL version of this method implemented using LIKE operator, but it&#39;s not a real full text search. DB specific modules should override this, as this generic implementation will be always much slower. This generic implementation returns &#39;relevance&#39; as 0 for no match, or 1 for a match.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="$column_-_name_of_column_to_search_(scalar)"
+><code class="code">$column</code> - name of column to search (scalar)</a></dt>
+
+<dd>
+<dt><a name="$text_-_text_to_search_for_(scalar)"
+><code class="code">$text</code> - text to search for (scalar)</a></dt>
+</dl>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>Formatted SQL for full text search</p>
+</dd>
+</dl>
+
+<dt><a name="sql_istrcmp"
+><code class="code">sql_istrcmp</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Returns SQL for a case-insensitive string comparison.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="$left_-_What_should_be_on_the_left-hand-side_of_the_operation."
+><code class="code">$left</code> - What should be on the left-hand-side of the operation.</a></dt>
+
+<dd>
+<dt><a name="$right_-_What_should_be_on_the_right-hand-side_of_the_operation."
+><code class="code">$right</code> - What should be on the right-hand-side of the operation.</a></dt>
+
+<dd>
+<dt><a
+><code class="code">$op</code> (optional) - What the operation is. Should be a valid ANSI SQL comparison operator, such as <code class="code">=</code>, <code class="code">&#60;</code>, <code class="code">LIKE</code>, etc. Defaults to <code class="code">=</code> if not specified.</a></dt>
+</dl>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>A SQL statement that will run the comparison in a case-insensitive fashion.</p>
+
+<dt><a name="Note"
+><b>Note</b></a></dt>
+
+<dd>
+<p>Uses <a href="#sql_istring" class="podlinkpod"
+>&#34;sql_istring&#34;</a>, so it has the same performance concerns. Try to avoid using this function unless absolutely necessary.</p>
+
+<p>Subclass Implementors: Override sql_istring instead of this function, most of the time (this function uses sql_istring).</p>
+</dd>
+</dl>
+
+<dt><a name="sql_istring"
+><code class="code">sql_istring</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Returns SQL syntax &#34;preparing&#34; a string or text column for case-insensitive comparison.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="$string_-_string_to_convert_(scalar)"
+><code class="code">$string</code> - string to convert (scalar)</a></dt>
+</dl>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>Formatted SQL making the string case insensitive.</p>
+
+<dt><a name="Note"
+><b>Note</b></a></dt>
+
+<dd>
+<p>The default implementation simply calls LOWER on the parameter. If this is used to search on a text column with index, the index will not be usually used unless it was created as LOWER(column).</p>
+</dd>
+</dl>
+
+<dt><a name="sql_in"
+><code class="code">sql_in</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Returns SQL syntax for the <code class="code">IN ()</code> operator.</p>
+
+<p>Only necessary where an <code class="code">IN</code> clause can have more than 1000 items.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="$column_name_-_Column_name_(e.g._bug_id)"
+><code class="code">$column_name</code> - Column name (e.g. <code class="code">bug_id</code>)</a></dt>
+
+<dd>
+<dt><a name="$in_list_ref_-_an_arrayref_containing_values_for_IN_()"
+><code class="code">$in_list_ref</code> - an arrayref containing values for <code class="code">IN ()</code></a></dt>
+</dl>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>Formatted SQL for the <code class="code">IN</code> operator.</p>
+</dd>
+</dl>
+</dd>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="IMPLEMENTED_METHODS"
+>IMPLEMENTED METHODS</a></h1>
+
+<p>These methods are implemented in Bugzilla::DB, and only need to be implemented in subclasses if you need to override them for database-compatibility reasons.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="General_Information_Methods"
+>General Information Methods</a></h2>
+
+<p>These methods return information about data in the database.</p>
+
+<dl>
+<dt><a name="bz_last_key"
+><code class="code">bz_last_key</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Returns the last serial number, usually from a previous INSERT.</p>
+
+<p>Must be executed directly following the relevant INSERT. This base implementation uses <a href="../DBI.html#last_insert_id" class="podlinkpod"
+>&#34;last_insert_id&#34; in DBI</a>. If the DBD supports it, it is the preffered way to obtain the last serial index. If it is not supported, the DB-specific code needs to override this function.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="$table_-_name_of_table_containing_serial_column_(scalar)"
+><code class="code">$table</code> - name of table containing serial column (scalar)</a></dt>
+
+<dd>
+<dt><a name="$column_-_name_of_column_containing_serial_data_type_(scalar)"
+><code class="code">$column</code> - name of column containing serial data type (scalar)</a></dt>
+</dl>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>Last inserted ID (scalar)</p>
+</dd>
+</dl>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Database_Setup_Methods"
+>Database Setup Methods</a></h2>
+
+<p>These methods are used by the Bugzilla installation programs to set up the database.</p>
+
+<dl>
+<dt><a name="bz_populate_enum_tables"
+><code class="code">bz_populate_enum_tables</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>For an upgrade or an initial installation, populates the tables that hold the legal values for the old &#34;enum&#34; fields: <code class="code">bug_severity</code>, <code class="code">resolution</code>, etc. Prints out information if it inserts anything into the DB.</p>
+
+<dt><a name="Params_(none)"
+><b>Params</b> (none)</a></dt>
+
+<dd>
+<dt><a name="Returns_(nothing)"
+><b>Returns</b> (nothing)</a></dt>
+</dl>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Schema_Modification_Methods"
+>Schema Modification Methods</a></h2>
+
+<p>These methods modify the current Bugzilla Schema.</p>
+
+<p>Where a parameter says &#34;Abstract index/column definition&#34;, it returns/takes information in the formats defined for indexes and columns in <code class="code">Bugzilla::DB::Schema::ABSTRACT_SCHEMA</code>.</p>
+
+<dl>
+<dt><a name="bz_add_column"
+><code class="code">bz_add_column</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Adds a new column to a table in the database. Prints out a brief statement that it did so, to stdout. Note that you cannot add a NOT NULL column that has no default -- the database won&#39;t know what to set all the NULL values to.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="$table_-_the_table_where_the_column_is_being_added"
+><code class="code">$table</code> - the table where the column is being added</a></dt>
+
+<dd>
+<dt><a name="$name_-_the_name_of_the_new_column"
+><code class="code">$name</code> - the name of the new column</a></dt>
+
+<dd>
+<dt><a name="\%definition_-_Abstract_column_definition_for_the_new_column"
+><code class="code">\%definition</code> - Abstract column definition for the new column</a></dt>
+
+<dd>
+<dt><a
+><code class="code">$init_value</code> (optional) - An initial value to set the column to. Required if your column is NOT NULL and has no DEFAULT set.</a></dt>
+</dl>
+
+<dt><a name="Returns_(nothing)"
+><b>Returns</b> (nothing)</a></dt>
+</dl>
+
+<dt><a name="bz_add_index"
+><code class="code">bz_add_index</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Adds a new index to a table in the database. Prints out a brief statement that it did so, to stdout. If the index already exists, we will do nothing.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="$table_-_The_table_the_new_index_is_on."
+><code class="code">$table</code> - The table the new index is on.</a></dt>
+
+<dd>
+<dt><a name="$name_-_A_name_for_the_new_index."
+><code class="code">$name</code> - A name for the new index.</a></dt>
+
+<dd>
+<dt><a name="$definition_-_An_abstract_index_definition._Either_a_hashref_or_an_arrayref."
+><code class="code">$definition</code> - An abstract index definition. Either a hashref or an arrayref.</a></dt>
+</dl>
+
+<dt><a name="Returns_(nothing)"
+><b>Returns</b> (nothing)</a></dt>
+</dl>
+
+<dt><a name="bz_add_table"
+><code class="code">bz_add_table</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Creates a new table in the database, based on the definition for that table in the abstract schema.</p>
+
+<p>Note that unlike the other &#39;add&#39; functions, this does not take a definition, but always creates the table as it exists in <a href="../Bugzilla/DB/Schema.html#ABSTRACT_SCHEMA" class="podlinkpod"
+>&#34;ABSTRACT_SCHEMA&#34; in Bugzilla::DB::Schema</a>.</p>
+
+<p>If a table with that name already exists, then this function returns silently.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="$name_-_The_name_of_the_table_you_want_to_create."
+><code class="code">$name</code> - The name of the table you want to create.</a></dt>
+</dl>
+
+<dt><a name="Returns_(nothing)"
+><b>Returns</b> (nothing)</a></dt>
+</dl>
+
+<dt><a name="bz_drop_index"
+><code class="code">bz_drop_index</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Removes an index from the database. Prints out a brief statement that it did so, to stdout. If the index doesn&#39;t exist, we do nothing.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="$table_-_The_table_that_the_index_is_on."
+><code class="code">$table</code> - The table that the index is on.</a></dt>
+
+<dd>
+<dt><a name="$name_-_The_name_of_the_index_that_you_want_to_drop."
+><code class="code">$name</code> - The name of the index that you want to drop.</a></dt>
+</dl>
+
+<dt><a name="Returns_(nothing)"
+><b>Returns</b> (nothing)</a></dt>
+</dl>
+
+<dt><a name="bz_drop_table"
+><code class="code">bz_drop_table</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Drops a table from the database. If the table doesn&#39;t exist, we just return silently.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="$name_-_The_name_of_the_table_to_drop."
+><code class="code">$name</code> - The name of the table to drop.</a></dt>
+</dl>
+
+<dt><a name="Returns_(nothing)"
+><b>Returns</b> (nothing)</a></dt>
+</dl>
+
+<dt><a name="bz_alter_column"
+><code class="code">bz_alter_column</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Changes the data type of a column in a table. Prints out the changes being made to stdout. If the new type is the same as the old type, the function returns without changing anything.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="$table_-_the_table_where_the_column_is"
+><code class="code">$table</code> - the table where the column is</a></dt>
+
+<dd>
+<dt><a name="$name_-_the_name_of_the_column_you_want_to_change"
+><code class="code">$name</code> - the name of the column you want to change</a></dt>
+
+<dd>
+<dt><a name="\%new_def_-_An_abstract_column_definition_for_the_new_data_type_of_the_columm"
+><code class="code">\%new_def</code> - An abstract column definition for the new data type of the columm</a></dt>
+
+<dd>
+<dt><a
+><code class="code">$set_nulls_to</code> (Optional) - If you are changing the column to be NOT NULL, you probably also want to set any existing NULL columns to a particular value. Specify that value here. <b>NOTE</b>: The value should not already be SQL-quoted.</a></dt>
+</dl>
+
+<dt><a name="Returns_(nothing)"
+><b>Returns</b> (nothing)</a></dt>
+</dl>
+
+<dt><a name="bz_drop_column"
+><code class="code">bz_drop_column</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Removes a column from a database table. If the column doesn&#39;t exist, we return without doing anything. If we do anything, we print a short message to <code class="code">stdout</code> about the change.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="$table_-_The_table_where_the_column_is"
+><code class="code">$table</code> - The table where the column is</a></dt>
+
+<dd>
+<dt><a name="$column_-_The_name_of_the_column_you_want_to_drop"
+><code class="code">$column</code> - The name of the column you want to drop</a></dt>
+</dl>
+
+<dt><a name="Returns_(nothing)"
+><b>Returns</b> (nothing)</a></dt>
+</dl>
+
+<dt><a name="bz_rename_column"
+><code class="code">bz_rename_column</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Renames a column in a database table. If the <code class="code">$old_name</code> column doesn&#39;t exist, we return without doing anything. If <code class="code">$old_name</code> and <code class="code">$new_name</code> both already exist in the table specified, we fail.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="$table_-_The_name_of_the_table_containing_the_column_that_you_want_to_rename"
+><code class="code">$table</code> - The name of the table containing the column that you want to rename</a></dt>
+
+<dd>
+<dt><a name="$old_name_-_The_current_name_of_the_column_that_you_want_to_rename"
+><code class="code">$old_name</code> - The current name of the column that you want to rename</a></dt>
+
+<dd>
+<dt><a name="$new_name_-_The_new_name_of_the_column"
+><code class="code">$new_name</code> - The new name of the column</a></dt>
+</dl>
+
+<dt><a name="Returns_(nothing)"
+><b>Returns</b> (nothing)</a></dt>
+</dl>
+
+<dt><a name="bz_rename_table"
+><code class="code">bz_rename_table</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Renames a table in the database. Does nothing if the table doesn&#39;t exist.</p>
+
+<p>Throws an error if the old table exists and there is already a table with the new name.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="$old_name_-_The_current_name_of_the_table."
+><code class="code">$old_name</code> - The current name of the table.</a></dt>
+
+<dd>
+<dt><a name="$new_name_-_What_you&#39;re_renaming_the_table_to."
+><code class="code">$new_name</code> - What you&#39;re renaming the table to.</a></dt>
+</dl>
+
+<dt><a name="Returns_(nothing)"
+><b>Returns</b> (nothing)</a></dt>
+</dl>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Schema_Information_Methods"
+>Schema Information Methods</a></h2>
+
+<p>These methods return information about the current Bugzilla database schema, as it currently exists on the disk.</p>
+
+<p>Where a parameter says &#34;Abstract index/column definition&#34;, it returns/takes information in the formats defined for indexes and columns for <a href="../Bugzilla/DB/Schema.html#ABSTRACT_SCHEMA" class="podlinkpod"
+>&#34;ABSTRACT_SCHEMA&#34; in Bugzilla::DB::Schema</a>.</p>
+
+<dl>
+<dt><a name="bz_column_info"
+><code class="code">bz_column_info</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Get abstract column definition.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="$table_-_The_name_of_the_table_the_column_is_in."
+><code class="code">$table</code> - The name of the table the column is in.</a></dt>
+
+<dd>
+<dt><a name="$column_-_The_name_of_the_column."
+><code class="code">$column</code> - The name of the column.</a></dt>
+</dl>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>An abstract column definition for that column. If the table or column does not exist, we return <code class="code">undef</code>.</p>
+</dd>
+</dl>
+
+<dt><a name="bz_index_info"
+><code class="code">bz_index_info</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Get abstract index definition.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="$table_-_The_table_the_index_is_on."
+><code class="code">$table</code> - The table the index is on.</a></dt>
+
+<dd>
+<dt><a name="$index_-_The_name_of_the_index."
+><code class="code">$index</code> - The name of the index.</a></dt>
+</dl>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>An abstract index definition for that index, always in hashref format. The hashref will always contain the <code class="code">TYPE</code> element, but it will be an empty string if it&#39;s just a normal index.</p>
+
+<p>If the index does not exist, we return <code class="code">undef</code>.</p>
+</dd>
+</dl>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Transaction_Methods"
+>Transaction Methods</a></h2>
+
+<p>These methods deal with the starting and stopping of transactions in the database.</p>
+
+<dl>
+<dt><a name="bz_in_transaction"
+><code class="code">bz_in_transaction</code></a></dt>
+
+<dd>
+<p>Returns <code class="code">1</code> if we are currently in the middle of an uncommitted transaction, <code class="code">0</code> otherwise.</p>
+
+<dt><a name="bz_start_transaction"
+><code class="code">bz_start_transaction</code></a></dt>
+
+<dd>
+<p>Starts a transaction.</p>
+
+<p>It is OK to call <code class="code">bz_start_transaction</code> when you are already inside of a transaction. However, you must call <a href="#bz_commit_transaction" class="podlinkpod"
+>&#34;bz_commit_transaction&#34;</a> as many times as you called <code class="code">bz_start_transaction</code>, in order for your transaction to actually commit.</p>
+
+<p>Bugzilla uses <code class="code">REPEATABLE READ</code> transactions.</p>
+
+<p>Returns nothing and takes no parameters.</p>
+
+<dt><a name="bz_commit_transaction"
+><code class="code">bz_commit_transaction</code></a></dt>
+
+<dd>
+<p>Ends a transaction, commiting all changes. Returns nothing and takes no parameters.</p>
+
+<dt><a name="bz_rollback_transaction"
+><code class="code">bz_rollback_transaction</code></a></dt>
+
+<dd>
+<p>Ends a transaction, rolling back all changes. Returns nothing and takes no parameters.</p>
+</dd>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SUBCLASS_HELPERS"
+>SUBCLASS HELPERS</a></h1>
+
+<p>Methods in this class are intended to be used by subclasses to help them with their functions.</p>
+
+<dl>
+<dt><a name="db_new"
+><code class="code">db_new</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Constructor</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="$dsn_-_database_connection_string"
+><code class="code">$dsn</code> - database connection string</a></dt>
+
+<dd>
+<dt><a name="$user_-_username_used_to_log_in_to_the_database"
+><code class="code">$user</code> - username used to log in to the database</a></dt>
+
+<dd>
+<dt><a name="$pass_-_password_used_to_log_in_to_the_database"
+><code class="code">$pass</code> - password used to log in to the database</a></dt>
+
+<dd>
+<dt><a
+><code class="code">\%override_attrs</code> - set of attributes for DB connection (optional). You only have to set attributes that you want to be different from the default attributes set inside of <code class="code">db_new</code>.</a></dt>
+</dl>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>A new instance of the DB class</p>
+
+<dt><a name="Note"
+><b>Note</b></a></dt>
+
+<dd>
+<p>The name of this constructor is not <code class="code">new</code>, as that would make our check for implementation of <code class="code">new</code> by derived class useless.</p>
+</dd>
+</dl>
+</dd>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SEE_ALSO"
+>SEE ALSO</a></h1>
+
+<p><a href="../DBI.html" class="podlinkpod"
+>DBI</a></p>
+
+<p><a href="../Bugzilla/Constants.html#DB_MODULE" class="podlinkpod"
+>&#34;DB_MODULE&#34; in Bugzilla::Constants</a></p>
+<p class="backlinkbottom"><b><a name="___bottom" href="../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/DB/Mysql.html b/docs/en/html/api/Bugzilla/DB/Mysql.html
new file mode 100644
index 000000000..18b5127ff
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/DB/Mysql.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::DB::Mysql</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href="../.././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::DB::Mysql</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::DB::Mysql - Bugzilla database compatibility layer for MySQL</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>This module overrides methods of the Bugzilla::DB module with MySQL specific implementation.
+It is instantiated by the Bugzilla::DB module and should never be used directly.</p>
+
+<p>For interface details see <a href="../../Bugzilla/DB.html" class="podlinkpod"
+>Bugzilla::DB</a> and <a href="../../DBI.html" class="podlinkpod"
+>DBI</a>.</p>
+<p class="backlinkbottom"><b><a name="___bottom" href="../../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/DB/Oracle.html b/docs/en/html/api/Bugzilla/DB/Oracle.html
new file mode 100644
index 000000000..946a11a08
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/DB/Oracle.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::DB::Oracle</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href="../.././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::DB::Oracle</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::DB::Oracle - Bugzilla database compatibility layer for Oracle</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>This module overrides methods of the Bugzilla::DB module with Oracle specific implementation.
+It is instantiated by the Bugzilla::DB module and should never be used directly.</p>
+
+<p>For interface details see <a href="../../Bugzilla/DB.html" class="podlinkpod"
+>Bugzilla::DB</a> and <a href="../../DBI.html" class="podlinkpod"
+>DBI</a>.</p>
+<p class="backlinkbottom"><b><a name="___bottom" href="../../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/DB/Pg.html b/docs/en/html/api/Bugzilla/DB/Pg.html
new file mode 100644
index 000000000..0b1d03e23
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/DB/Pg.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::DB::Pg</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href="../.././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::DB::Pg</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::DB::Pg - Bugzilla database compatibility layer for PostgreSQL</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>This module overrides methods of the Bugzilla::DB module with PostgreSQL specific implementation.
+It is instantiated by the Bugzilla::DB module and should never be used directly.</p>
+
+<p>For interface details see <a href="../../Bugzilla/DB.html" class="podlinkpod"
+>Bugzilla::DB</a> and <a href="../../DBI.html" class="podlinkpod"
+>DBI</a>.</p>
+<p class="backlinkbottom"><b><a name="___bottom" href="../../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/DB/Schema.html b/docs/en/html/api/Bugzilla/DB/Schema.html
new file mode 100644
index 000000000..61cada880
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/DB/Schema.html
@@ -0,0 +1,767 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::DB::Schema</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href="../.././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::DB::Schema</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#NEW_TO_SCHEMA.PM%3F'>NEW TO SCHEMA.PM?</a>
+ <li class='indexItem indexItem1'><a href='#CONSTANTS'>CONSTANTS</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#Referential_Integrity'>Referential Integrity</a>
+ </ul>
+ <li class='indexItem indexItem1'><a href='#METHODS'>METHODS</a>
+ <li class='indexItem indexItem1'><a href='#SERIALIZATION%2FDESERIALIZATION'>SERIALIZATION/DESERIALIZATION</a>
+ <li class='indexItem indexItem1'><a href='#CLASS_METHODS'>CLASS METHODS</a>
+ <li class='indexItem indexItem1'><a href='#ABSTRACT_DATA_TYPES'>ABSTRACT DATA TYPES</a>
+ <li class='indexItem indexItem1'><a href='#SEE_ALSO'>SEE ALSO</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::DB::Schema - Abstract database schema for Bugzilla</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<pre class="code"> # Obtain MySQL database schema.
+ # Do not do this. Use Bugzilla::DB instead.
+ use Bugzilla::DB::Schema;
+ my $mysql_schema = new Bugzilla::DB::Schema(&#39;Mysql&#39;);
+
+ # Recommended way to obtain database schema.
+ use Bugzilla::DB;
+ my $dbh = Bugzilla-&#62;dbh;
+ my $schema = $dbh-&#62;_bz_schema();
+
+ # Get the list of tables in the Bugzilla database.
+ my @tables = $schema-&#62;get_table_list();
+
+ # Get the SQL statements need to create the bugs table.
+ my @statements = $schema-&#62;get_table_ddl(&#39;bugs&#39;);
+
+ # Get the database-specific SQL data type used to implement
+ # the abstract data type INT1.
+ my $db_specific_type = $schema-&#62;sql_type(&#39;INT1&#39;);</pre>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>This module implements an object-oriented, abstract database schema. It should be considered package-private to the Bugzilla::DB module. That means that CGI scripts should never call any function in this module directly, but should instead rely on methods provided by Bugzilla::DB.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NEW_TO_SCHEMA.PM?"
+>NEW TO SCHEMA.PM?</a></h1>
+
+<p>If this is your first time looking at Schema.pm, especially if you are making changes to the database, please take a look at <a href="http://www.bugzilla.org/docs/developer.html#sql-schema" class="podlinkurl"
+>http://www.bugzilla.org/docs/developer.html#sql-schema</a> to learn more about how this integrates into the rest of Bugzilla.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="CONSTANTS"
+>CONSTANTS</a></h1>
+
+<dl>
+<dt><a name="SCHEMA_VERSION"
+><code class="code">SCHEMA_VERSION</code></a></dt>
+
+<dd>
+<p>The &#39;version&#39; of the internal schema structure. This version number is incremented every time the the fundamental structure of Schema internals changes.</p>
+
+<p>This is NOT changed every time a table or a column is added. This number is incremented only if the internal structures of this Schema would be incompatible with the internal structures of a previous Schema version.</p>
+
+<p>In general, unless you are messing around with serialization and deserialization of the schema, you don&#39;t need to worry about this constant.</p>
+
+<dt><a name="ABSTRACT_SCHEMA"
+><code class="code">ABSTRACT_SCHEMA</code></a></dt>
+
+<dd>
+<p>The abstract database schema structure consists of a hash reference in which each key is the name of a table in the Bugzilla database.</p>
+
+<p>The value for each key is a hash reference containing the keys <code class="code">FIELDS</code> and <code class="code">INDEXES</code> which in turn point to array references containing information on the table&#39;s fields and indexes.</p>
+
+<p>A field hash reference should must contain the key <code class="code">TYPE</code>. Optional field keys include <code class="code">PRIMARYKEY</code>, <code class="code">NOTNULL</code>, and <code class="code">DEFAULT</code>.</p>
+
+<p>The <code class="code">INDEXES</code> array reference contains index names and information regarding the index. If the index name points to an array reference, then the index is a regular index and the array contains the indexed columns. If the index name points to a hash reference, then the hash must contain the key <code class="code">FIELDS</code>. It may also contain the key <code class="code">TYPE</code>, which can be used to specify the type of index such as UNIQUE or FULLTEXT.</p>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Referential_Integrity"
+>Referential Integrity</a></h2>
+
+<p>Bugzilla::DB::Schema supports &#34;foreign keys&#34;, a way of saying that &#34;Column X may only contain values from Column Y in Table Z&#34;. For example, in Bugzilla, bugs.resolution should only contain values from the resolution.values field.</p>
+
+<p>It does this by adding an additional item to a column, called <code class="code">REFERENCES</code>. This is a hash with the following members:</p>
+
+<dl>
+<dt><a name="TABLE"
+><code class="code">TABLE</code></a></dt>
+
+<dd>
+<p>The table the foreign key points at</p>
+
+<dt><a name="COLUMN"
+><code class="code">COLUMN</code></a></dt>
+
+<dd>
+<p>The column pointed at in that table.</p>
+
+<dt><a name="DELETE"
+><code class="code">DELETE</code></a></dt>
+
+<dd>
+<p>What to do if the row in the parent table is deleted. Choices are <code class="code">RESTRICT</code>, <code class="code">CASCADE</code>, or <code class="code">SET NULL</code>.</p>
+
+<p><code class="code">RESTRICT</code> means the deletion of the row in the parent table will be forbidden by the database if there is a row in <i>this</i> table that still refers to it. This is the default, if you don&#39;t specify <code class="code">DELETE</code>.</p>
+
+<p><code class="code">CASCADE</code> means that this row will be deleted along with that row.</p>
+
+<p><code class="code">SET NULL</code> means that the column will be set to <code class="code">NULL</code> when the parent row is deleted. Note that this is only valid if the column can actually be set to <code class="code">NULL</code>. (That is, the column isn&#39;t <code class="code">NOT NULL</code>.)</p>
+
+<dt><a name="UPDATE"
+><code class="code">UPDATE</code></a></dt>
+
+<dd>
+<p>What to do if the value in the parent table is updated. You can set this to <code class="code">CASCADE</code> or <code class="code">RESTRICT</code>, which mean the same thing as they do for <a href="#DELETE" class="podlinkpod"
+>&#34;DELETE&#34;</a>. This variable defaults to <code class="code">CASCADE</code>, which means &#34;also update this column in this table.&#34;</p>
+</dd>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="METHODS"
+>METHODS</a></h1>
+
+<p>Note: Methods which can be implemented generically for all DBs are implemented in this module. If needed, they can be overridden with DB-specific code in a subclass. Methods which are prefixed with <code class="code">_</code> are considered protected. Subclasses may override these methods, but other modules should not invoke these methods directly.</p>
+
+<dl>
+<dt><a name="new"
+><code class="code">new</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Public constructor method used to instantiate objects of this
+ class. However, it also can be used as a factory method to
+ instantiate database-specific subclasses when an optional
+ driver argument is supplied.
+ Parameters: $driver (optional) - Used to specify the type of database.
+ This routine C&#60;die&#62;s if no subclass is found for the specified
+ driver.
+ $schema (optional) - A reference to a hash. Callers external
+ to this package should never use this parameter.
+ Returns: new instance of the Schema class or a database-specific subclass</pre>
+
+<dt><a name="_initialize"
+><code class="code">_initialize</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Protected method that initializes an object after
+ instantiation with the abstract schema. All subclasses should
+ override this method. The typical subclass implementation
+ should first call the C&#60;_initialize&#62; method of the superclass,
+ then do any database-specific initialization (especially
+ define the database-specific implementation of the all
+ abstract data types), and then call the C&#60;_adjust_schema&#62;
+ method.
+ Parameters: $abstract_schema (optional) - A reference to a hash. If
+ provided, this hash will be used as the internal
+ representation of the abstract schema instead of our
+ default abstract schema. This is intended for internal
+ use only by deserialize_abstract.
+ Returns: the instance of the Schema class</pre>
+
+<dt><a name="_adjust_schema"
+><code class="code">_adjust_schema</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Protected method that alters the abstract schema at
+ instantiation-time to be database-specific. It is a generic
+ enough routine that it can be defined here in the base class.
+ It takes the abstract schema and replaces the abstract data
+ types with database-specific data types.
+ Parameters: none
+ Returns: the instance of the Schema class</pre>
+
+<dt><a name="get_type_ddl"
+><code class="code">get_type_ddl</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Public method to convert abstract (database-generic) field specifiers to database-specific data types suitable for use in a <code class="code">CREATE TABLE</code> or <code class="code">ALTER TABLE</code> SQL statment. If no database-specific field type has been defined for the given field type, then it will just return the same field type.</p>
+
+<dt><a name="Parameters"
+><b>Parameters</b></a></dt>
+
+<dd>
+<dl>
+<dt><a
+><code class="code">$def</code> - A reference to a hash of a field containing the following keys: <code class="code">TYPE</code> (required), <code class="code">NOTNULL</code> (optional), <code class="code">DEFAULT</code> (optional), <code class="code">PRIMARYKEY</code> (optional), <code class="code">REFERENCES</code> (optional)</a></dt>
+</dl>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>A DDL string suitable for describing a field in a <code class="code">CREATE TABLE</code> or <code class="code">ALTER TABLE</code> SQL statement</p>
+</dd>
+</dl>
+
+<dt><a name="_get_fk_ddl"
+><code class="code">_get_fk_ddl</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Protected method. Translates the <code class="code">REFERENCES</code> item of a column into SQL.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a
+><code class="code">$table</code> - The name of the table the reference is from. =item <code class="code">$column</code> - The name of the column the reference is from =item <code class="code">$references</code> - The <code class="code">REFERENCES</code> hashref from a column.</a></dt>
+</dl>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>SQL for to define the foreign key, or an empty string if <code class="code">$references</code> is undefined.</p>
+</dd>
+</dl>
+
+<dt><a name="convert_type"
+><code class="code">convert_type</code></a></dt>
+
+<dd>
+<p>Converts a TYPE from the <a href="#ABSTRACT_SCHEMA" class="podlinkpod"
+>&#34;ABSTRACT_SCHEMA&#34;</a> format into the real SQL type.</p>
+
+<dt><a name="get_column($table,_$column)"
+><code class="code">get_column($table, $column)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Public method to get the abstract definition of a column.
+ Parameters: $table - the table name
+ $column - a column in the table
+ Returns: a hashref containing information about the column, including its
+ type (C&#60;TYPE&#62;), whether or not it can be null (C&#60;NOTNULL&#62;),
+ its default value if it has one (C&#60;DEFAULT), etc.
+ Returns undef if the table or column does not exist.</pre>
+
+<dt><a name="get_table_list"
+><code class="code">get_table_list</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Public method for discovering what tables should exist in the
+ Bugzilla database.
+
+ Parameters: none
+
+ Returns: An array of table names, in alphabetical order.</pre>
+
+<dt><a name="get_table_columns"
+><code class="code">get_table_columns</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Public method for discovering what columns are in a given
+ table in the Bugzilla database.
+ Parameters: $table - the table name
+ Returns: array of column names</pre>
+
+<dt><a name="get_table_ddl"
+><code class="code">get_table_ddl</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Public method to generate the SQL statements needed to create
+ the a given table and its indexes in the Bugzilla database.
+ Subclasses may override or extend this method, if needed, but
+ subclasses probably should override C&#60;_get_create_table_ddl&#62;
+ or C&#60;_get_create_index_ddl&#62; instead.
+ Parameters: $table - the table name
+ Returns: an array of strings containing SQL statements</pre>
+
+<dt><a name="_get_create_table_ddl"
+><code class="code">_get_create_table_ddl</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Protected method to generate the &#34;create table&#34; SQL statement
+ for a given table.
+ Parameters: $table - the table name
+ Returns: a string containing the DDL statement for the specified table</pre>
+
+<dt><a name="_get_create_index_ddl"
+><code class="code">_get_create_index_ddl</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Protected method to generate a &#34;create index&#34; SQL statement
+ for a given table and index.
+ Parameters: $table_name - the name of the table
+ $index_name - the name of the index
+ $index_fields - a reference to an array of field names
+ $index_type (optional) - specify type of index (e.g., UNIQUE)
+ Returns: a string containing the DDL statement</pre>
+
+<dt><a name="get_add_column_ddl($table,_$column,_\%definition,_$init_value)"
+><code class="code">get_add_column_ddl($table, $column, \%definition, $init_value)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Generate SQL to add a column to a table.
+ Params: $table - The table containing the column.
+ $column - The name of the column being added.
+ \%definition - The new definition for the column,
+ in standard C&#60;ABSTRACT_SCHEMA&#62; format.
+ $init_value - (optional) An initial value to set
+ the column to. Should already be SQL-quoted
+ if necessary.
+ Returns: An array of SQL statements.</pre>
+
+<dt><a name="get_add_index_ddl"
+><code class="code">get_add_index_ddl</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Gets SQL for creating an index.
+ NOTE: Subclasses should not override this function. Instead,
+ if they need to specify a custom CREATE INDEX statement,
+ they should override C&#60;_get_create_index_ddl&#62;
+ Params: $table - The name of the table the index will be on.
+ $name - The name of the new index.
+ $definition - An index definition. Either a hashref
+ with FIELDS and TYPE or an arrayref
+ containing a list of columns.
+ Returns: An array of SQL statements that will create the
+ requested index.</pre>
+
+<dt><a name="get_alter_column_ddl($table,_$column,_\%definition)"
+><code class="code">get_alter_column_ddl($table, $column, \%definition)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Generate SQL to alter a column in a table.
+ The column that you are altering must exist,
+ and the table that it lives in must exist.
+ Params: $table - The table containing the column.
+ $column - The name of the column being changed.
+ \%definition - The new definition for the column,
+ in standard C&#60;ABSTRACT_SCHEMA&#62; format.
+ $set_nulls_to - A value to set NULL values to, if
+ your new definition is NOT NULL and contains
+ no DEFAULT, and when there is a possibility
+ that the column could contain NULLs. $set_nulls_to
+ should be already SQL-quoted if necessary.
+ Returns: An array of SQL statements.</pre>
+
+<dt><a name="get_drop_index_ddl($table,_$name)"
+><code class="code">get_drop_index_ddl($table, $name)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Generates SQL statements to drop an index.
+ Params: $table - The table the index is on.
+ $name - The name of the index being dropped.
+ Returns: An array of SQL statements.</pre>
+
+<dt><a name="get_drop_column_ddl($table,_$column)"
+><code class="code">get_drop_column_ddl($table, $column)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Generate SQL to drop a column from a table.
+ Params: $table - The table containing the column.
+ $column - The name of the column being dropped.
+ Returns: An array of SQL statements.</pre>
+
+<dt><a name="get_drop_table_ddl($table)"
+><code class="code">get_drop_table_ddl($table)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Generate SQL to drop a table from the database.
+ Params: $table - The name of the table to drop.
+ Returns: An array of SQL statements.</pre>
+
+<dt><a name="get_rename_column_ddl($table,_$old_name,_$new_name)"
+><code class="code">get_rename_column_ddl($table, $old_name, $new_name)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Generate SQL to change the name of a column in a table.
+ NOTE: ANSI SQL contains no simple way to rename a column,
+ so this function is ABSTRACT and must be implemented
+ by subclasses.
+ Params: $table - The table containing the column to be renamed.
+ $old_name - The name of the column being renamed.
+ $new_name - The name the column is changing to.
+ Returns: An array of SQL statements.</pre>
+
+<dt><a name="get_rename_table_sql"
+><code class="code">get_rename_table_sql</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Gets SQL to rename a table in the database.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="$old_name_-_The_current_name_of_the_table."
+><code class="code">$old_name</code> - The current name of the table.</a></dt>
+
+<dd>
+<dt><a name="$new_name_-_The_new_name_of_the_table."
+><code class="code">$new_name</code> - The new name of the table.</a></dt>
+</dl>
+
+<dt><a name="Returns:_An_array_of_SQL_statements_to_rename_a_table."
+><b>Returns</b>: An array of SQL statements to rename a table.</a></dt>
+</dl>
+
+<dt><a name="delete_table($name)"
+><code class="code">delete_table($name)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Deletes a table from this Schema object.
+ Dies if you try to delete a table that doesn&#39;t exist.
+ Params: $name - The name of the table to delete.
+ Returns: nothing</pre>
+
+<dt><a name="get_column_abstract($table,_$column)"
+><code class="code">get_column_abstract($table, $column)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: A column definition from the abstract internal schema.
+ cross-database format.
+ Params: $table - The name of the table
+ $column - The name of the column that you want
+ Returns: A hash reference. For the format, see the docs for
+ C&#60;ABSTRACT_SCHEMA&#62;.
+ Returns undef if the column or table does not exist.</pre>
+
+<dt><a name="get_indexes_on_column_abstract($table,_$column)"
+><code class="code">get_indexes_on_column_abstract($table, $column)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Gets a list of indexes that are on a given column.
+ Params: $table - The table the column is on.
+ $column - The name of the column.
+ Returns: Indexes in the standard format of an INDEX
+ entry on a table. That is, key-value pairs
+ where the key is the index name and the value
+ is the index definition.
+ If there are no indexes on that column, we return
+ undef.</pre>
+
+<dt><a name="get_index_abstract($table,_$index)"
+><code class="code">get_index_abstract($table, $index)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Returns an index definition from the internal abstract schema.
+ Params: $table - The table the index is on.
+ $index - The name of the index.
+ Returns: A hash reference representing an index definition.
+ See the C&#60;ABSTRACT_SCHEMA&#62; docs for details.
+ Returns undef if the index does not exist.</pre>
+
+<dt><a name="get_table_abstract($table)"
+><code class="code">get_table_abstract($table)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Gets the abstract definition for a table in this Schema
+ object.
+ Params: $table - The name of the table you want a definition for.
+ Returns: An abstract table definition, or undef if the table doesn&#39;t
+ exist.</pre>
+
+<dt><a name="add_table($name,_\%definition)"
+><code class="code">add_table($name, \%definition)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Creates a new table in this Schema object.
+ If you do not specify a definition, we will
+ simply create an empty table.
+ Params: $name - The name for the new table.
+ \%definition (optional) - An abstract definition for
+ the new table.
+ Returns: nothing</pre>
+
+<dt><a name="rename_table"
+><code class="code">rename_table</code></a></dt>
+
+<dd>
+<p>Renames a table from <code class="code">$old_name</code> to <code class="code">$new_name</code> in this Schema object.</p>
+
+<dt><a name="delete_column($table,_$column)"
+><code class="code">delete_column($table, $column)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Deletes a column from this Schema object.
+ Params: $table - Name of the table that the column is in.
+ The table must exist, or we will fail.
+ $column - Name of the column to delete.
+ Returns: nothing</pre>
+
+<dt><a name="rename_column($table,_$old_name,_$new_name)"
+><code class="code">rename_column($table, $old_name, $new_name)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Renames a column on a table in the Schema object.
+ The column that you are renaming must exist.
+ Params: $table - The table the column is on.
+ $old_name - The current name of the column.
+ $new_name - The new name of hte column.
+ Returns: nothing</pre>
+
+<dt><a name="set_column($table,_$column,_\%new_def)"
+><code class="code">set_column($table, $column, \%new_def)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Changes the definition of a column in this Schema object.
+ If the column doesn&#39;t exist, it will be added.
+ The table that you specify must already exist in the Schema.
+ NOTE: This does not affect the database on the disk.
+ Use the C&#60;Bugzilla::DB&#62; &#34;Schema Modification Methods&#34;
+ if you want to do that.
+ Params: $table - The name of the table that the column is on.
+ $column - The name of the column.
+ \%new_def - The new definition for the column, in
+ C&#60;ABSTRACT_SCHEMA&#62; format.
+ Returns: nothing</pre>
+
+<dt><a name="set_index($table,_$name,_$definition)"
+><code class="code">set_index($table, $name, $definition)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Changes the definition of an index in this Schema object.
+ If the index doesn&#39;t exist, it will be added.
+ The table that you specify must already exist in the Schema.
+ NOTE: This does not affect the database on the disk.
+ Use the C&#60;Bugzilla::DB&#62; &#34;Schema Modification Methods&#34;
+ if you want to do that.
+ Params: $table - The table the index is on.
+ $name - The name of the index.
+ $definition - A hashref or an arrayref. An index
+ definition in C&#60;ABSTRACT_SCHEMA&#62; format.
+ Returns: nothing</pre>
+
+<dt><a name="delete_index($table,_$name)"
+><code class="code">delete_index($table, $name)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Removes an index definition from this Schema object.
+ If the index doesn&#39;t exist, we will fail.
+ The table that you specify must exist in the Schema.
+ NOTE: This does not affect the database on the disk.
+ Use the C&#60;Bugzilla::DB&#62; &#34;Schema Modification Methods&#34;
+ if you want to do that.
+ Params: $table - The table the index is on.
+ $name - The name of the index that we&#39;re removing.
+ Returns: nothing</pre>
+
+<dt><a name="columns_equal($col_one,_$col_two)"
+><code class="code">columns_equal($col_one, $col_two)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Tells you if two columns have entirely identical definitions.
+ The TYPE field&#39;s value will be compared case-insensitive.
+ However, all other fields will be case-sensitive.
+ Params: $col_one, $col_two - The columns to compare. Hash
+ references, in C&#60;ABSTRACT_SCHEMA&#62; format.
+ Returns: C&#60;1&#62; if the columns are identical, C&#60;0&#62; if they are not.</pre>
+</dd>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SERIALIZATION/DESERIALIZATION"
+>SERIALIZATION/DESERIALIZATION</a></h1>
+
+<dl>
+<dt><a name="serialize_abstract()"
+><code class="code">serialize_abstract()</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Serializes the &#34;abstract&#34; schema into a format
+ that deserialize_abstract() can read in. This is
+ a method, called on a Schema instance.
+ Parameters: none
+ Returns: A scalar containing the serialized, abstract schema.
+ Do not attempt to manipulate this data directly,
+ as the format may change at any time in the future.
+ The only thing you should do with the returned value
+ is either store it somewhere (coupled with appropriate
+ SCHEMA_VERSION) or deserialize it.</pre>
+
+<dt><a name="deserialize_abstract($serialized,_$version)"
+><code class="code">deserialize_abstract($serialized, $version)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Used for when you&#39;ve read a serialized Schema off the disk,
+ and you want a Schema object that represents that data.
+ Params: $serialized - scalar. The serialized data.
+ $version - A number in the format X.YZ. The &#34;version&#34;
+ of the Schema that did the serialization.
+ See the docs for C&#60;SCHEMA_VERSION&#62; for more details.
+ Returns: A Schema object. It will have the methods of (and work
+ in the same fashion as) the current version of Schema.
+ However, it will represent the serialized data instead of
+ ABSTRACT_SCHEMA.</pre>
+</dd>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="CLASS_METHODS"
+>CLASS METHODS</a></h1>
+
+<p>These methods are generally called on the class instead of on a specific object.</p>
+
+<dl>
+<dt><a name="get_empty_schema()"
+><code class="code">get_empty_schema()</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Returns a Schema that has no tables. In effect, this
+ Schema is totally &#34;empty.&#34;
+ Params: none
+ Returns: A &#34;empty&#34; Schema object.</pre>
+</dd>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="ABSTRACT_DATA_TYPES"
+>ABSTRACT DATA TYPES</a></h1>
+
+<p>The size and range data provided here is only intended as a guide. See your database&#39;s Bugzilla module (in this directory) for the most up-to-date values for these data types. The following abstract data types are used:</p>
+
+<dl>
+<dt><a name="BOOLEAN"
+><code class="code">BOOLEAN</code></a></dt>
+
+<dd>
+<p>Logical value 0 or 1 where 1 is true, 0 is false.</p>
+
+<dt><a name="INT1"
+><code class="code">INT1</code></a></dt>
+
+<dd>
+<p>Integer values (-128 - 127 or 0 - 255 unsigned).</p>
+
+<dt><a name="INT2"
+><code class="code">INT2</code></a></dt>
+
+<dd>
+<p>Integer values (-32,768 - 32767 or 0 - 65,535 unsigned).</p>
+
+<dt><a name="INT3"
+><code class="code">INT3</code></a></dt>
+
+<dd>
+<p>Integer values (-8,388,608 - 8,388,607 or 0 - 16,777,215 unsigned)</p>
+
+<dt><a name="INT4"
+><code class="code">INT4</code></a></dt>
+
+<dd>
+<p>Integer values (-2,147,483,648 - 2,147,483,647 or 0 - 4,294,967,295 unsigned)</p>
+
+<dt><a name="SMALLSERIAL"
+><code class="code">SMALLSERIAL</code></a></dt>
+
+<dd>
+<p>An auto-increment <a href="#INT1" class="podlinkpod"
+>&#34;INT1&#34;</a></p>
+
+<dt><a name="MEDIUMSERIAL"
+><code class="code">MEDIUMSERIAL</code></a></dt>
+
+<dd>
+<p>An auto-increment <a href="#INT3" class="podlinkpod"
+>&#34;INT3&#34;</a></p>
+
+<dt><a name="INTSERIAL"
+><code class="code">INTSERIAL</code></a></dt>
+
+<dd>
+<p>An auto-increment <a href="#INT4" class="podlinkpod"
+>&#34;INT4&#34;</a></p>
+
+<dt><a name="TINYTEXT"
+><code class="code">TINYTEXT</code></a></dt>
+
+<dd>
+<p>Variable length string of characters up to 255 (2^8 - 1) characters wide.</p>
+
+<dt><a name="MEDIUMTEXT"
+><code class="code">MEDIUMTEXT</code></a></dt>
+
+<dd>
+<p>Variable length string of characters up to 4000 characters wide. May be longer on some databases.</p>
+
+<dt><a name="LONGTEXT"
+><code class="code">LONGTEXT</code></a></dt>
+
+<dd>
+<p>Variable length string of characters up to 16M (2^24 - 1) characters wide.</p>
+
+<dt><a name="LONGBLOB"
+><code class="code">LONGBLOB</code></a></dt>
+
+<dd>
+<p>Variable length string of binary data up to 4M (2^32 - 1) bytes wide</p>
+
+<dt><a name="DATETIME"
+><code class="code">DATETIME</code></a></dt>
+
+<dd>
+<p>DATETIME support varies from database to database, however, it&#39;s generally safe to say that DATETIME entries support all date/time combinations greater than 1900-01-01 00:00:00. Note that the format used is <code class="code">YYYY-MM-DD hh:mm:ss</code> to be safe, though it&#39;s possible that your database may not require leading zeros. For greatest compatibility, however, please make sure dates are formatted as above for queries to guarantee consistent results.</p>
+</dd>
+</dl>
+
+<p>Database-specific subclasses should define the implementation for these data types as a hash reference stored internally in the schema object as <code class="code">db_specific</code>. This is typically done in overridden <a href="../../_initialize.html" class="podlinkpod"
+>_initialize</a> method.</p>
+
+<p>The following abstract boolean values should also be defined on a database-specific basis:</p>
+
+<dl>
+<dt><a name="TRUE"
+><code class="code">TRUE</code></a></dt>
+
+<dd>
+<dt><a name="FALSE"
+><code class="code">FALSE</code></a></dt>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SEE_ALSO"
+>SEE ALSO</a></h1>
+
+<p><a href="../../Bugzilla/DB.html" class="podlinkpod"
+>Bugzilla::DB</a></p>
+
+<p><a href="http://www.bugzilla.org/docs/developer.html#sql-schema" class="podlinkurl"
+>http://www.bugzilla.org/docs/developer.html#sql-schema</a></p>
+<p class="backlinkbottom"><b><a name="___bottom" href="../../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/Error.html b/docs/en/html/api/Bugzilla/Error.html
new file mode 100644
index 000000000..162a289b6
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/Error.html
@@ -0,0 +1,83 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::Error</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href=".././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::Error</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#FUNCTIONS'>FUNCTIONS</a>
+ <li class='indexItem indexItem1'><a href='#SEE_ALSO'>SEE ALSO</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::Error - Error handling utilities for Bugzilla</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<pre class="code"> use Bugzilla::Error;
+
+ ThrowUserError(&#34;error_tag&#34;,
+ { foo =&#62; &#39;bar&#39; });</pre>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>Various places throughout the Bugzilla codebase need to report errors to the user. The <code class="code">Throw*Error</code> family of functions allow this to be done in a generic and localizable manner.</p>
+
+<p>These functions automatically unlock the database tables, if there were any locked. They will also roll back the transaction, if it is supported by the underlying DB.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="FUNCTIONS"
+>FUNCTIONS</a></h1>
+
+<dl>
+<dt><a name="ThrowUserError"
+><code class="code">ThrowUserError</code></a></dt>
+
+<dd>
+<p>This function takes an error tag as the first argument, and an optional hashref of variables as a second argument. These are used by the <i>global/user-error.html.tmpl</i> template to format the error, using the passed in variables as required.</p>
+
+<dt><a name="ThrowCodeError"
+><code class="code">ThrowCodeError</code></a></dt>
+
+<dd>
+<p>This function is used when an internal check detects an error of some sort. This usually indicates a bug in Bugzilla, although it can occur if the user manually constructs urls without correct parameters.</p>
+
+<p>This function&#39;s behaviour is similar to <code class="code">ThrowUserError</code>, except that the template used to display errors is <i>global/code-error.html.tmpl</i>. In addition if the hashref used as the optional second argument contains a key <i>variables</i> then the contents of the hashref (which is expected to be another hashref) will be displayed after the error message, as a debugging aid.</p>
+
+<dt><a name="ThrowTemplateError"
+><code class="code">ThrowTemplateError</code></a></dt>
+
+<dd>
+<p>This function should only be called if a <code class="code">template-&#60;gt</code>process()&#62; fails. It tries another template first, because often one template being broken or missing doesn&#39;t mean that they all are. But it falls back to a print statement as a last-ditch error.</p>
+</dd>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SEE_ALSO"
+>SEE ALSO</a></h1>
+
+<p><a href="../Bugzilla.html" class="podlinkpod"
+>Bugzilla</a></p>
+<p class="backlinkbottom"><b><a name="___bottom" href="../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/Extension.html b/docs/en/html/api/Bugzilla/Extension.html
new file mode 100644
index 000000000..a7f66f044
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/Extension.html
@@ -0,0 +1,546 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::Extension</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href=".././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::Extension</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#WRITING_EXTENSIONS'>WRITING EXTENSIONS</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#Using_extensions%2Fcreate.pl'>Using extensions/create.pl</a>
+ <li class='indexItem indexItem2'><a href='#Example_Extension'>Example Extension</a>
+ <li class='indexItem indexItem2'><a href='#Where_Extension_Code_Goes'>Where Extension Code Goes</a>
+ <li class='indexItem indexItem2'><a href='#The_Extension_NAME.'>The Extension NAME.</a>
+ <li class='indexItem indexItem2'><a href='#Hooks'>Hooks</a>
+ <ul class='indexList indexList3'>
+ <li class='indexItem indexItem3'><a href='#Adding_New_Hooks_To_Bugzilla'>Adding New Hooks To Bugzilla</a>
+ </ul>
+ <li class='indexItem indexItem2'><a href='#If_Your_Extension_Requires_Certain_Perl_Modules'>If Your Extension Requires Certain Perl Modules</a>
+ <ul class='indexList indexList3'>
+ <li class='indexItem indexItem3'><a href='#If_Your_Extension_Needs_Certain_Modules_In_Order_To_Compile'>If Your Extension Needs Certain Modules In Order To Compile</a>
+ </ul>
+ <li class='indexItem indexItem2'><a href='#Libraries'>Libraries</a>
+ <li class='indexItem indexItem2'><a href='#Templates'>Templates</a>
+ <li class='indexItem indexItem2'><a href='#Template_Hooks'>Template Hooks</a>
+ <ul class='indexList indexList3'>
+ <li class='indexItem indexItem3'><a href='#Which_Templates_Can_Be_Hooked'>Which Templates Can Be Hooked</a>
+ <li class='indexItem indexItem3'><a href='#Where_Template_Hooks_Go'>Where Template Hooks Go</a>
+ <li class='indexItem indexItem3'><a href='#Adding_New_Template_Hooks_to_Bugzilla'>Adding New Template Hooks to Bugzilla</a>
+ </ul>
+ <li class='indexItem indexItem2'><a href='#Overriding_Existing_Templates'>Overriding Existing Templates</a>
+ <ul class='indexList indexList3'>
+ <li class='indexItem indexItem3'><a href='#A_Warning_About_Extensions_That_You_Want_To_Distribute'>A Warning About Extensions That You Want To Distribute</a>
+ </ul>
+ <li class='indexItem indexItem2'><a href='#CSS%2C_JavaScript%2C_and_Images'>CSS, JavaScript, and Images</a>
+ <li class='indexItem indexItem2'><a href='#Disabling_Your_Extension'>Disabling Your Extension</a>
+ </ul>
+ <li class='indexItem indexItem1'><a href='#DISTRIBUTING_EXTENSIONS'>DISTRIBUTING EXTENSIONS</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#Distributing_on_CPAN'>Distributing on CPAN</a>
+ <ul class='indexList indexList3'>
+ <li class='indexItem indexItem3'><a href='#Templates_in_extensions_distributed_on_CPAN'>Templates in extensions distributed on CPAN</a>
+ <li class='indexItem indexItem3'><a href='#Using_an_extension_distributed_on_CPAN'>Using an extension distributed on CPAN</a>
+ </ul>
+ </ul>
+ <li class='indexItem indexItem1'><a href='#GETTING_HELP_WITH_WRITING_EXTENSIONS'>GETTING HELP WITH WRITING EXTENSIONS</a>
+ <li class='indexItem indexItem1'><a href='#ADDITIONAL_CONSTANTS'>ADDITIONAL CONSTANTS</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#%24VERSION'>$VERSION</a>
+ </ul>
+ <li class='indexItem indexItem1'><a href='#SUBCLASS_METHODS'>SUBCLASS METHODS</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#Class_Methods'>Class Methods</a>
+ <ul class='indexList indexList3'>
+ <li class='indexItem indexItem3'><a href='#new'>new</a>
+ </ul>
+ <li class='indexItem indexItem2'><a href='#Instance_Methods'>Instance Methods</a>
+ <ul class='indexList indexList3'>
+ <li class='indexItem indexItem3'><a href='#enabled'>enabled</a>
+ <li class='indexItem indexItem3'><a href='#package_dir'>package_dir</a>
+ <li class='indexItem indexItem3'><a href='#template_dir'>template_dir</a>
+ <li class='indexItem indexItem3'><a href='#lib_dir'>lib_dir</a>
+ </ul>
+ </ul>
+ <li class='indexItem indexItem1'><a href='#BUGZILLA%3A%3AEXTENSION_CLASS_METHODS'>BUGZILLA::EXTENSION CLASS METHODS</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#load'>load</a>
+ <li class='indexItem indexItem2'><a href='#load_all'>load_all</a>
+ </ul>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::Extension - Base class for Bugzilla Extensions.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<p>The following would be in <em class="code">extensions/Foo/Extension.pm</em> or <em class="code">extensions/Foo.pm</em>:</p>
+
+<pre class="code"> package Bugzilla::Extension::Foo
+ use strict;
+ use base qw(Bugzilla::Extension);
+
+ our $VERSION = &#39;0.02&#39;;
+ use constant NAME =&#62; &#39;Foo&#39;;
+
+ sub some_hook_name { ... }
+
+ __PACKAGE__-&#62;NAME;</pre>
+
+<p>Custom templates would go into <em class="code">extensions/Foo/template/en/default/</em>. <a href="#Template_Hooks" class="podlinkpod"
+>Template hooks</a> would go into <em class="code">extensions/Foo/template/en/default/hook/</em>.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>This is the base class for all Bugzilla extensions.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="WRITING_EXTENSIONS"
+>WRITING EXTENSIONS</a></h1>
+
+<p>The <a href="#SYNOPSIS" class="podlinkpod"
+>&#34;SYNOPSIS&#34;</a> above gives a pretty good overview of what&#39;s basically required to write an extension. This section gives more information on exactly how extensions work and how you write them. There is also a <a href="https://wiki.mozilla.org/Bugzilla:Extension_Notes" class="podlinkurl"
+>wiki page</a> with additional HOWTOs, tips and tricks.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Using_extensions/create.pl"
+>Using <em class="code">extensions/create.pl</em></a></h2>
+
+<p>There is a script, <a href="../extensions/create.html" class="podlinkpod"
+>extensions::create</a>, that will set up the framework of a new extension for you. To use it, pick a name for your extension and, in the base bugzilla directory, do:</p>
+
+<p><code class="code">extensions/create.pl NAME</code></p>
+
+<p>But replace <code class="code">NAME</code> with the name you picked for your extension. That will create a new directory in the <em class="code">extensions/</em> directory with the name of your extension. The directory will contain a full framework for a new extension, with helpful comments in each file describing things about them.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Example_Extension"
+>Example Extension</a></h2>
+
+<p>There is a sample extension in <em class="code">extensions/Example/</em> that demonstrates most of the things described in this document, so if you find the documentation confusing, try just reading the code instead.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Where_Extension_Code_Goes"
+>Where Extension Code Goes</a></h2>
+
+<p>Extension code lives under the <em class="code">extensions/</em> directory in Bugzilla.</p>
+
+<p>There are two ways to write extensions:</p>
+
+<ol>
+<li>If your extension will have only code and no templates or other files, you can create a simple <code class="code">.pm</code> file in the <em class="code">extensions/</em> directory.
+<p>For example, if you wanted to create an extension called &#34;Foo&#34; using this method, you would put your code into a file called <em class="code">extensions/Foo.pm</em>.</p>
+</li>
+
+<li>If you plan for your extension to have templates and other files, you can create a whole directory for your extension, and the main extension code would go into a file called <em class="code">Extension.pm</em> in that directory.
+<p>For example, if you wanted to create an extension called &#34;Foo&#34; using this method, you would put your code into a file called <em class="code">extensions/Foo/Extension.pm</em>.</p>
+</li>
+</ol>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="The_Extension_NAME."
+>The Extension <code class="code">NAME</code>.</a></h2>
+
+<p>The &#34;name&#34; of an extension shows up in several places:</p>
+
+<ol>
+<li>The name of the package:
+<p><code class="code">package Bugzilla::Extension::Foo;</code></p>
+</li>
+
+<li>In a <code class="code">NAME</code> constant that <b>must</b> be defined for every extension:
+<p><code class="code">use constant NAME =&#62; &#39;Foo&#39;;</code></p>
+</li>
+
+<li>At the very end of the file:
+<p><code class="code">__PACKAGE__-&#62;NAME;</code></p>
+
+<p>You&#39;ll notice that though most Perl packages end with <code class="code">1;</code>, Bugzilla Extensions must <b>always</b> end with <code class="code">__PACKAGE__-&#62;NAME;</code>.</p>
+</li>
+</ol>
+
+<p>The name must be identical in all of those locations.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Hooks"
+>Hooks</a></h2>
+
+<p>In <a href="../Bugzilla/Hook.html" class="podlinkpod"
+>Bugzilla::Hook</a>, there is a <a href="../Bugzilla/Hook.html#HOOKS" class="podlinkpod"
+>list of hooks</a>. These are the various areas of Bugzilla that an extension can &#34;hook&#34; into, which allow your extension to perform code during that point in Bugzilla&#39;s execution.</p>
+
+<p>If your extension wants to implement a hook, all you have to do is write a subroutine in your hook package that has the same name as the hook. The subroutine will be called as a method on your extension, and it will get the arguments specified in the hook&#39;s documentation as named parameters in a hashref.</p>
+
+<p>For example, here&#39;s an implementation of a hook named <code class="code">foo_start</code> that gets an argument named <code class="code">bar</code>:</p>
+
+<pre class="code"> sub foo_start {
+ my ($self, $args) = @_;
+ my $bar = $args-&#62;{bar};
+ print &#34;I got $bar!\n&#34;;
+ }</pre>
+
+<p>And that would go into your extension&#39;s code file--the file that was described in the <a href="#Where_Extension_Code_Goes" class="podlinkpod"
+>&#34;Where Extension Code Goes&#34;</a> section above.</p>
+
+<p>During your subroutine, you may want to know what values were passed as CGI arguments to the current script, or what arguments were passed to the current WebService method. You can get that data via <a href="../Bugzilla.html#input_params" class="podlinkpod"
+>&#34;input_params&#34; in Bugzilla</a>.</p>
+
+<h3><a class='u' href='#___top' title='click to go to top of document'
+name="Adding_New_Hooks_To_Bugzilla"
+>Adding New Hooks To Bugzilla</a></h3>
+
+<p>If you need a new hook for your extension and you want that hook to be added to Bugzilla itself, see our development process at <a href="http://wiki.mozilla.org/Bugzilla:Developers" class="podlinkurl"
+>http://wiki.mozilla.org/Bugzilla:Developers</a>.</p>
+
+<p>In order for a new hook to be accepted into Bugzilla, it has to work, it must have documentation in <a href="../Bugzilla/Hook.html" class="podlinkpod"
+>Bugzilla::Hook</a>, and it must have example code in <em class="code">extensions/Example/Extension.pm</em>.</p>
+
+<p>One question that is often asked about new hooks is, &#34;Is this the most flexible way to implement this hook?&#34; That is, the more power extension authors get from a hook, the more likely it is to be accepted into Bugzilla. Hooks that only hook a very specific part of Bugzilla will not be accepted if their functionality can be accomplished equally well with a more generic hook.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="If_Your_Extension_Requires_Certain_Perl_Modules"
+>If Your Extension Requires Certain Perl Modules</a></h2>
+
+<p>If there are certain Perl modules that your extension requires in order to run, there is a way you can tell Bugzilla this, and then <a href="../checksetup.html" class="podlinkpod"
+>checksetup</a> will make sure that those modules are installed, when you run <a href="../checksetup.html" class="podlinkpod"
+>checksetup</a>.</p>
+
+<p>To do this, you need to specify a constant called <code class="code">REQUIRED_MODULES</code> in your extension. This constant has the same format as <a href="../Bugzilla/Install/Requirements.html#REQUIRED_MODULES" class="podlinkpod"
+>&#34;REQUIRED_MODULES&#34; in Bugzilla::Install::Requirements</a>.</p>
+
+<p>If there are optional modules that add additional functionality to your application, you can specify them in a constant called OPTIONAL_MODULES, which has the same format as <a href="../Bugzilla/Install/Requirements.html#OPTIONAL_MODULES" class="podlinkpod"
+>&#34;OPTIONAL_MODULES&#34; in Bugzilla::Install::Requirements</a>.</p>
+
+<h3><a class='u' href='#___top' title='click to go to top of document'
+name="If_Your_Extension_Needs_Certain_Modules_In_Order_To_Compile"
+>If Your Extension Needs Certain Modules In Order To Compile</a></h3>
+
+<p>If your extension needs a particular Perl module in order to <i>compile</i>, then you have a &#34;chicken and egg&#34; problem--in order to read <code class="code">REQUIRED_MODULES</code>, we have to compile your extension. In order to compile your extension, we need to already have the modules in <code class="code">REQUIRED_MODULES</code>!</p>
+
+<p>To get around this problem, Bugzilla allows you to have an additional file, besides <em class="code">Extension.pm</em>, called <em class="code">Config.pm</em>, that contains just <code class="code">REQUIRED_MODULES</code>. If you have a <em class="code">Config.pm</em>, it must also contain the <code class="code">NAME</code> constant, instead of your main <em class="code">Extension.pm</em> containing the <code class="code">NAME</code> constant.</p>
+
+<p>The contents of the file would look something like this for an extension named <code class="code">Foo</code>:</p>
+
+<pre class="code"> package Bugzilla::Extension::Foo;
+ use strict;
+ use constant NAME =&#62; &#39;Foo&#39;;
+ use constant REQUIRED_MODULES =&#62; [
+ {
+ package =&#62; &#39;Some-Package&#39;,
+ module =&#62; &#39;Some::Module&#39;,
+ version =&#62; 0,
+ }
+ ];
+ __PACKAGE__-&#62;NAME;</pre>
+
+<p>Note that it is <i>not</i> a subclass of <code class="code">Bugzilla::Extension</code>, because at the time that module requirements are being checked in <a href="../checksetup.html" class="podlinkpod"
+>checksetup</a>, <code class="code">Bugzilla::Extension</code> cannot be loaded. Also, just like <em class="code">Extension.pm</em>, it ends with <code class="code">__PACKAGE__-&#62;NAME;</code>. Note also that it has the <b>exact same</b> <code class="code">package</code> name as <em class="code">Extension.pm</em>.</p>
+
+<p>This file may not use any Perl modules other than <a href="../Bugzilla/Constants.html" class="podlinkpod"
+>Bugzilla::Constants</a>, <a href="../Bugzilla/Install/Util.html" class="podlinkpod"
+>Bugzilla::Install::Util</a>, <a href="../Bugzilla/Install/Requirements.html" class="podlinkpod"
+>Bugzilla::Install::Requirements</a>, and modules that ship with Perl itself.</p>
+
+<p>If you want to define both <code class="code">REQUIRED_MODULES</code> and <code class="code">OPTIONAL_MODULES</code>, they must both be in <em class="code">Config.pm</em> or both in <em class="code">Extension.pm</em>.</p>
+
+<p>Every time your extension is loaded by Bugzilla, <em class="code">Config.pm</em> will be read and then <em class="code">Extension.pm</em> will be read, so your methods in <em class="code">Extension.pm</em> will have access to everything in <em class="code">Config.pm</em>. Don&#39;t define anything with an identical name in both files, or Perl may throw a warning that you are redefining things.</p>
+
+<p>This method of setting <code class="code">REQUIRED_MODULES</code> is of course not available if your extension is a single file named <code class="code">Foo.pm</code>.</p>
+
+<p>If any of this is confusing, just look at the code of the Example extension. It uses this method to specify requirements.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Libraries"
+>Libraries</a></h2>
+
+<p>Extensions often want to have their own Perl modules. Your extension can load any Perl module in its <em class="code">lib/</em> directory. (So, if your extension is <em class="code">extensions/Foo/</em>, then your Perl modules go into <em class="code">extensions/Foo/lib/</em>.)</p>
+
+<p>However, the <code class="code">package</code> name of your libraries will not work quite like normal Perl modules do. <em class="code">extensions/Foo/lib/Bar.pm</em> is loaded as <code class="code">Bugzilla::Extension::Foo::Bar</code>. Or, to say it another way, <code class="code">use Bugzilla::Extension::Foo::Bar;</code> loads <em class="code">extensions/Foo/lib/Bar.pm</em>, which should have <code class="code">package Bugzilla::Extension::Foo::Bar;</code> as its package name.</p>
+
+<p>This allows any place in Bugzilla to load your modules, which is important for some hooks. It even allows other extensions to load your modules, and allows you to install your modules into the global Perl install as <em class="code">Bugzilla/Extension/Foo/Bar.pm</em>, if you&#39;d like, which helps allow CPAN distribution of Bugzilla extensions.</p>
+
+<p><b>Note:</b> If you want to <code class="code">use</code> or <code class="code">require</code> a module that&#39;s in <em class="code">extensions/Foo/lib/</em> at the top level of your <em class="code">Extension.pm</em>, you must have a <em class="code">Config.pm</em> (see above) with at least the <code class="code">NAME</code> constant defined in it.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Templates"
+>Templates</a></h2>
+
+<p>Extensions store templates in a <code class="code">template</code> subdirectory of the extension. (Obviously, this isn&#39;t available for extensions that aren&#39;t a directory.)</p>
+
+<p>The format of this directory is exactly like the normal layout of Bugzilla&#39;s <code class="code">template</code> directory--in fact, your extension&#39;s <code class="code">template</code> directory becomes part of Bugzilla&#39;s template &#34;search path&#34; as described in <a href="../Bugzilla/Install/Util.html#template_include_path" class="podlinkpod"
+>&#34;template_include_path&#34; in Bugzilla::Install::Util</a>.</p>
+
+<p>You can actually include templates in your extension without having any <code class="code">.pm</code> files in your extension at all, if you want. (That is, it&#39;s entirely valid to have an extension that&#39;s just template files and no code files.)</p>
+
+<p>Bugzilla&#39;s templates are written in a language called Template Toolkit. You can find out more about Template Toolkit at <a href="http://template-toolkit.org" class="podlinkurl"
+>http://template-toolkit.org</a>.</p>
+
+<p>There are two ways to extend or modify Bugzilla&#39;s templates: you can use template hooks (described below) or you can override existing templates entirely (described further down).</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Template_Hooks"
+>Template Hooks</a></h2>
+
+<p>Templates can be extended using a system of &#34;hooks&#34; that add new UI elements to a particular area of Bugzilla without modifying the code of the existing templates. This is the recommended way for extensions to modify the user interface of Bugzilla.</p>
+
+<h3><a class='u' href='#___top' title='click to go to top of document'
+name="Which_Templates_Can_Be_Hooked"
+>Which Templates Can Be Hooked</a></h3>
+
+<p>There is no list of template hooks like there is for standard code hooks. To find what places in the user interface can be hooked, search for the string <code class="code">Hook.process</code> in Bugzilla&#39;s templates (in the <em class="code">template/en/default/</em> directory). That will also give you the name of the hooks--the first argument to <code class="code">Hook.process</code> is the name of the hook. (A later section in this document explains how to use that name).</p>
+
+<p>For example, if you see <code class="code">Hook.process(&#34;additional_header&#34;)</code>, that means the name of the hook is <code class="code">additional_header</code>.</p>
+
+<h3><a class='u' href='#___top' title='click to go to top of document'
+name="Where_Template_Hooks_Go"
+>Where Template Hooks Go</a></h3>
+
+<p>To extend templates in your extension using template hooks, you put files into the <em class="code">template/en/default/hook</em> directory of your extension. So, if you had an extension called &#34;Foo&#34;, your template extensions would go into <em class="code">extensions/Foo/template/en/default/hook/</em>.</p>
+
+<p>(Note that the base <em class="code">template/en/default/hook</em> directory in Bugzilla itself also works, although you would never use that for an extension that you intended to distribute.)</p>
+
+<p>The files that go into this directory have a certain name, based on the name of the template that is being hooked, and the name of the hook. For example, let&#39;s imagine that you have an extension named &#34;Foo&#34;, and you want to use the <code class="code">additional_header</code> hook in <em class="code">template/en/default/global/header.html.tmpl</em>. Your code would go into <em class="code">extensions/Foo/template/en/default/hook/global/header-additional_header.html.tmpl</em>. Any code you put into that file will happen at the point that <code class="code">Hook.process(&#34;additional_header&#34;)</code> is called in <em class="code">template/en/default/global/header.html.tmpl</em>.</p>
+
+<p>As you can see, template extension file names follow a pattern. The pattern looks like:</p>
+
+<pre class="code"> &#60;templates&#62;/hook/&#60;template path&#62;/&#60;template name&#62;-&#60;hook name&#62;.&#60;template type&#62;.tmpl</pre>
+
+<dl>
+<dt><a name="&#60;templates&#62;"
+>&#60;templates&#62;</a></dt>
+
+<dd>
+<p>This is the full path to the template directory, like <em class="code">extensions/Foo/template/en/default</em>. This works much like normal templates do, in the sense that template extensions in <code class="code">custom</code> override template extensions in <code class="code">default</code> for your extension, templates for different languages can be supplied, etc. Template extensions are searched for and run in the order described in <a href="../Bugzilla/Install/Util.html#template_include_path" class="podlinkpod"
+>&#34;template_include_path&#34; in Bugzilla::Install::Util</a>.</p>
+
+<p>The difference between normal templates and template hooks is that hooks will be run for <i>every</i> extension, whereas for normal templates, Bugzilla just takes the first one it finds and stops searching. So while a template extension in the <code class="code">custom</code> directory may override the same-named template extension in the <code class="code">default</code> directory <i>within your Bugzilla extension</i>, it will not override the same-named template extension in the <code class="code">default</code> directory of another Bugzilla extension.</p>
+
+<dt><a name="&#60;template_path&#62;"
+>&#60;template path&#62;</a></dt>
+
+<dd>
+<p>This is the part of the path (excluding the filename) that comes after <em class="code">template/en/default/</em> in a template&#39;s path. So, for <em class="code">template/en/default/global/header.html.tmpl</em>, this would simply be <code class="code">global</code>.</p>
+
+<dt><a name="&#60;template_name&#62;"
+>&#60;template name&#62;</a></dt>
+
+<dd>
+<p>This is the file name of the template, before the <code class="code">.html.tmpl</code> part. So, for <em class="code">template/en/default/global/header.html.tmpl</em>, this would be <code class="code">header</code>.</p>
+
+<dt><a name="&#60;hook_name&#62;"
+>&#60;hook name&#62;</a></dt>
+
+<dd>
+<p>This is the name of the hook--what you saw in <code class="code">Hook.process</code> inside of the template you want to hook. In our example, this is <code class="code">additional_header</code>.</p>
+
+<dt><a name="&#60;template_type&#62;"
+>&#60;template type&#62;</a></dt>
+
+<dd>
+<p>This is what comes after the template name but before <code class="code">.tmpl</code> in the template&#39;s path. In most cases this is <code class="code">html</code>, but sometimes it&#39;s <code class="code">none</code>, <code class="code">txt</code>, <code class="code">js</code>, or various other formats, indicating what type of output the template has.</p>
+</dd>
+</dl>
+
+<h3><a class='u' href='#___top' title='click to go to top of document'
+name="Adding_New_Template_Hooks_to_Bugzilla"
+>Adding New Template Hooks to Bugzilla</a></h3>
+
+<p>Adding new template hooks is just like adding code hooks (see <a href="#Adding_New_Hooks_To_Bugzilla" class="podlinkpod"
+>&#34;Adding New Hooks To Bugzilla&#34;</a>) except that you don&#39;t have to document them, and including example code is optional.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Overriding_Existing_Templates"
+>Overriding Existing Templates</a></h2>
+
+<p>Sometimes you don&#39;t want to extend a template, you just want to replace it entirely with your extension&#39;s template, or you want to add an entirely new template to Bugzilla for your extension to use.</p>
+
+<p>To replace the <em class="code">template/en/default/global/banner.html.tmpl</em> template in an extension named &#34;Foo&#34;, create a file called <em class="code">extensions/Foo/template/en/default/global/banner.html.tmpl</em>. Note that this is very similar to the path for a template hook, except that it excludes <em class="code">hook/</em>, and the template is named <i>exactly</i> like the standard Bugzilla template.</p>
+
+<p>You can also use this method to add entirely new templates. If you have an extension named &#34;Foo&#34;, and you add a file named <em class="code">extensions/Foo/template/en/default/foo/bar.html.tmpl</em>, you can load that in your code using <code class="code">$template-&#62;process(&#39;foo/bar.html.tmpl&#39;)</code>.</p>
+
+<h3><a class='u' href='#___top' title='click to go to top of document'
+name="A_Warning_About_Extensions_That_You_Want_To_Distribute"
+>A Warning About Extensions That You Want To Distribute</a></h3>
+
+<p>You should never override an existing Bugzilla template in an extension that you plan to distribute to others, because only one extension can override any given template, and which extension will &#34;win&#34; that war if there are multiple extensions installed is totally undefined.</p>
+
+<p>However, adding new templates in an extension that you want to distribute is fine, though you have to be careful about how you name them, because any templates with an identical path and name (say, both called <em class="code">global/stuff.html.tmpl</em>) will conflict. The usual way to work around this is to put all your custom templates into a template path that&#39;s named after your extension (since the name of your extension has to be unique anyway). So if your extension was named Foo, your custom templates would go into <em class="code">extensions/Foo/template/en/default/foo/</em>. The only time that doesn&#39;t work is with the <code class="code">page_before_template</code> extension, in which case your templates should probably be in a directory like <em class="code">extensions/Foo/template/en/default/page/foo/</em> so as not to conflict with other pages that other extensions might add.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="CSS,_JavaScript,_and_Images"
+>CSS, JavaScript, and Images</a></h2>
+
+<p>If you include CSS, JavaScript, and images in your extension that are served directly to the user (that is, they&#39;re not read by a script and then printed--they&#39;re just linked directly in your HTML), they should go into the <em class="code">web/</em> subdirectory of your extension.</p>
+
+<p>So, for example, if you had a CSS file called <em class="code">style.css</em> and your extension was called <em class="code">Foo</em>, your file would go into <em class="code">extensions/Foo/web/style.css</em>.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Disabling_Your_Extension"
+>Disabling Your Extension</a></h2>
+
+<p>If you want your extension to be totally ignored by Bugzilla (it will not be compiled or seen to exist at all), then create a file called <code class="code">disabled</code> in your extension&#39;s directory. (If your extension is just a file, like <em class="code">extensions/Foo.pm</em>, you cannot use this method to disable your extension, and will just have to remove it from the directory if you want to totally disable it.) Note that if you are running under mod_perl, you may have to restart your web server for this to take effect.</p>
+
+<p>If you want your extension to be compiled and have <a href="../checksetup.html" class="podlinkpod"
+>checksetup</a> check for its module pre-requisites, but you don&#39;t want the module to be used by Bugzilla, then you should make your extension&#39;s <a href="#enabled" class="podlinkpod"
+>&#34;enabled&#34;</a> method return <code class="code">0</code> or some false value.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DISTRIBUTING_EXTENSIONS"
+>DISTRIBUTING EXTENSIONS</a></h1>
+
+<p>If you&#39;ve made an extension and you want to publish it, the first thing you&#39;ll want to do is package up your extension&#39;s code and then put a link to it in the appropriate section of <a href="http://wiki.mozilla.org/Bugzilla:Addons" class="podlinkurl"
+>http://wiki.mozilla.org/Bugzilla:Addons</a>.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Distributing_on_CPAN"
+>Distributing on CPAN</a></h2>
+
+<p>If you want a centralized distribution point that makes it easy for Bugzilla users to install your extension, it is possible to distribute your Bugzilla Extension through CPAN.</p>
+
+<p>The details of making a standard CPAN module are too much to go into here, but a lot of it is covered in <a href="../perlmodlib.html" class="podlinkpod"
+>perlmodlib</a> and on <a href="http://www.cpan.org/" class="podlinkurl"
+>http://www.cpan.org/</a> among other places.</p>
+
+<p>When you distribute your extension via CPAN, your <em class="code">Extension.pm</em> should simply install itself as <em class="code">Bugzilla/Extension/Foo.pm</em>, where <code class="code">Foo</code> is the name of your module. You do not need a separate <em class="code">Config.pm</em> file, because CPAN itself will handle installing the prerequisites of your module, so Bugzilla doesn&#39;t have to worry about it.</p>
+
+<h3><a class='u' href='#___top' title='click to go to top of document'
+name="Templates_in_extensions_distributed_on_CPAN"
+>Templates in extensions distributed on CPAN</a></h3>
+
+<p>If your extension is <em class="code">/usr/lib/perl5/Bugzilla/Extension/Foo.pm</em>, then Bugzilla will look for templates in the directory <em class="code">/usr/lib/perl5/Bugzilla/Extension/Foo/template/</em>.</p>
+
+<p>You can change this behavior by overriding the <a href="#template_dir" class="podlinkpod"
+>&#34;template_dir&#34;</a> or <a href="#package_dir" class="podlinkpod"
+>&#34;package_dir&#34;</a> methods described lower down in this document.</p>
+
+<h3><a class='u' href='#___top' title='click to go to top of document'
+name="Using_an_extension_distributed_on_CPAN"
+>Using an extension distributed on CPAN</a></h3>
+
+<p>There is a file named <em class="code">data/extensions/additional</em> in Bugzilla. This is a plain-text file. Each line is the name of a module, like <code class="code">Bugzilla::Extension::Foo</code>. In addition to the extensions in the <em class="code">extensions/</em> directory, each module listed in this file will be loaded as a Bugzilla Extension whenever Bugzilla loads or uses extensions.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="GETTING_HELP_WITH_WRITING_EXTENSIONS"
+>GETTING HELP WITH WRITING EXTENSIONS</a></h1>
+
+<p>If you are an extension author and you&#39;d like some assistance from other extension authors or the Bugzilla development team, you can use the normal support channels described at <a href="http://www.bugzilla.org/support/" class="podlinkurl"
+>http://www.bugzilla.org/support/</a>.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="ADDITIONAL_CONSTANTS"
+>ADDITIONAL CONSTANTS</a></h1>
+
+<p>In addition to <code class="code">NAME</code>, there are some other constants you might want to define:</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="$VERSION"
+><code class="code">$VERSION</code></a></h2>
+
+<p>This should be a string that describes what version of your extension this is. Something like <code class="code">1.0</code>, <code class="code">1.3.4</code> or a similar string.</p>
+
+<p>There are no particular restrictions on the format of version numbers, but you should probably keep them to just numbers and periods, in the interest of other software that parses version numbers.</p>
+
+<p>By default, this will be <code class="code">undef</code> if you don&#39;t define it.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SUBCLASS_METHODS"
+>SUBCLASS METHODS</a></h1>
+
+<p>In addition to hooks, there are a few methods that your extension can define to modify its behavior, if you want:</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Class_Methods"
+>Class Methods</a></h2>
+
+<p>These methods are called on your extension&#39;s class. (Like <code class="code">Bugzilla::Extension::Foo-&#62;some_method</code>).</p>
+
+<h3><a class='u' href='#___top' title='click to go to top of document'
+name="new"
+><code class="code">new</code></a></h3>
+
+<p>Once every request, this method is called on your extension in order to create an &#34;instance&#34; of it. (Extensions are treated like objects--they are instantiated once per request in Bugzilla, and then methods are called on the object.)</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Instance_Methods"
+>Instance Methods</a></h2>
+
+<p>These are called on an instantiated Extension object.</p>
+
+<h3><a class='u' href='#___top' title='click to go to top of document'
+name="enabled"
+><code class="code">enabled</code></a></h3>
+
+<p>This should return <code class="code">1</code> if this extension&#39;s hook code should be run by Bugzilla, and <code class="code">0</code> otherwise.</p>
+
+<h3><a class='u' href='#___top' title='click to go to top of document'
+name="package_dir"
+><code class="code">package_dir</code></a></h3>
+
+<p>This returns the directory that your extension is located in.</p>
+
+<p>If this is an extension that was installed via CPAN, the directory will be the path to <em class="code">Bugzilla/Extension/Foo/</em>, if <code class="code">Foo.pm</code> is the name of your extension.</p>
+
+<p>If you want to override this method, and you have a <em class="code">Config.pm</em>, you must override this method in <em class="code">Config.pm</em>.</p>
+
+<h3><a class='u' href='#___top' title='click to go to top of document'
+name="template_dir"
+><code class="code">template_dir</code></a></h3>
+
+<p>The directory that your package&#39;s templates are in.</p>
+
+<p>This defaults to the <code class="code">template</code> subdirectory of the <a href="#package_dir" class="podlinkpod"
+>&#34;package_dir&#34;</a>.</p>
+
+<p>If you want to override this method, and you have a <em class="code">Config.pm</em>, you must override this method in <em class="code">Config.pm</em>.</p>
+
+<h3><a class='u' href='#___top' title='click to go to top of document'
+name="lib_dir"
+><code class="code">lib_dir</code></a></h3>
+
+<p>The directory where your extension&#39;s libraries are.</p>
+
+<p>This defaults to the <code class="code">lib</code> subdirectory of the <a href="#package_dir" class="podlinkpod"
+>&#34;package_dir&#34;</a>.</p>
+
+<p>If you want to override this method, and you have a <em class="code">Config.pm</em>, you must override this method in <em class="code">Config.pm</em>.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="BUGZILLA::EXTENSION_CLASS_METHODS"
+>BUGZILLA::EXTENSION CLASS METHODS</a></h1>
+
+<p>These are used internally by Bugzilla to load and set up extensions. If you are an extension author, you don&#39;t need to care about these.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="load"
+><code class="code">load</code></a></h2>
+
+<p>Takes two arguments, the path to <em class="code">Extension.pm</em> and the path to <em class="code">Config.pm</em>, for an extension. Loads the extension&#39;s code packages into memory using <code class="code">require</code>, does some sanity-checking on the extension, and returns the package name of the loaded extension.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="load_all"
+><code class="code">load_all</code></a></h2>
+
+<p>Calls <a href="#load" class="podlinkpod"
+>&#34;load&#34;</a> for every enabled extension installed into Bugzilla, and returns an arrayref of all the package names that were loaded.</p>
+<p class="backlinkbottom"><b><a name="___bottom" href="../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/Field.html b/docs/en/html/api/Bugzilla/Field.html
new file mode 100644
index 000000000..4eee90cf7
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/Field.html
@@ -0,0 +1,466 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::Field</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href=".././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::Field</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#Instance_Properties'>Instance Properties</a>
+ <li class='indexItem indexItem2'><a href='#Instance_Mutators'>Instance Mutators</a>
+ <li class='indexItem indexItem2'><a href='#Instance_Method'>Instance Method</a>
+ <li class='indexItem indexItem2'><a href='#Class_Methods'>Class Methods</a>
+ <li class='indexItem indexItem2'><a href='#Data_Validation'>Data Validation</a>
+ </ul>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::Field - a particular piece of information about bugs and useful routines for form field manipulation</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<pre class="code"> use Bugzilla;
+ use Data::Dumper;
+
+ # Display information about all fields.
+ print Dumper(Bugzilla-&#62;get_fields());
+
+ # Display information about non-obsolete custom fields.
+ print Dumper(Bugzilla-&#62;active_custom_fields);
+
+ use Bugzilla::Field;
+
+ # Display information about non-obsolete custom fields.
+ # Bugzilla-&#62;get_fields() is a wrapper around Bugzilla::Field-&#62;match(),
+ # so both methods take the same arguments.
+ print Dumper(Bugzilla::Field-&#62;match({ obsolete =&#62; 0, custom =&#62; 1 }));
+
+ # Create or update a custom field or field definition.
+ my $field = Bugzilla::Field-&#62;create(
+ {name =&#62; &#39;cf_silly&#39;, description =&#62; &#39;Silly&#39;, custom =&#62; 1});
+
+ # Instantiate a Field object for an existing field.
+ my $field = new Bugzilla::Field({name =&#62; &#39;qacontact_accessible&#39;});
+ if ($field-&#62;obsolete) {
+ print $field-&#62;description . &#34; is obsolete\n&#34;;
+ }
+
+ # Validation Routines
+ check_field($name, $value, \@legal_values, $no_warn);
+ $fieldid = get_field_id($fieldname);</pre>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>Field.pm defines field objects, which represent the particular pieces of information that Bugzilla stores about bugs.</p>
+
+<p>This package also provides functions for dealing with CGI form fields.</p>
+
+<p><code class="code">Bugzilla::Field</code> is an implementation of <a href="../Bugzilla/Object.html" class="podlinkpod"
+>Bugzilla::Object</a>, and so provides all of the methods available in <a href="../Bugzilla/Object.html" class="podlinkpod"
+>Bugzilla::Object</a>, in addition to what is documented here.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Instance_Properties"
+>Instance Properties</a></h2>
+
+<dl>
+<dt><a name="name"
+><code class="code">name</code></a></dt>
+
+<dd>
+<p>the name of the field in the database; begins with &#34;cf_&#34; if field is a custom field, but test the value of the boolean &#34;custom&#34; property to determine if a given field is a custom field;</p>
+
+<dt><a name="description"
+><code class="code">description</code></a></dt>
+
+<dd>
+<p>a short string describing the field; displayed to Bugzilla users in several places within Bugzilla&#39;s UI, f.e. as the form field label on the &#34;show bug&#34; page;</p>
+</dd>
+</dl>
+
+<dl>
+<dt><a name="type"
+><code class="code">type</code></a></dt>
+
+<dd>
+<p>an integer specifying the kind of field this is; values correspond to the FIELD_TYPE_* constants in Constants.pm</p>
+</dd>
+</dl>
+
+<dl>
+<dt><a name="custom"
+><code class="code">custom</code></a></dt>
+
+<dd>
+<p>a boolean specifying whether or not the field is a custom field; if true, field name should start &#34;cf_&#34;, but use this property to determine which fields are custom fields;</p>
+</dd>
+</dl>
+
+<dl>
+<dt><a name="in_new_bugmail"
+><code class="code">in_new_bugmail</code></a></dt>
+
+<dd>
+<p>a boolean specifying whether or not the field is displayed in bugmail for newly-created bugs;</p>
+</dd>
+</dl>
+
+<dl>
+<dt><a name="sortkey"
+><code class="code">sortkey</code></a></dt>
+
+<dd>
+<p>an integer specifying the sortkey of the field.</p>
+</dd>
+</dl>
+
+<dl>
+<dt><a name="obsolete"
+><code class="code">obsolete</code></a></dt>
+
+<dd>
+<p>a boolean specifying whether or not the field is obsolete;</p>
+</dd>
+</dl>
+
+<dl>
+<dt><a name="enter_bug"
+><code class="code">enter_bug</code></a></dt>
+
+<dd>
+<p>A boolean specifying whether or not this field should appear on enter_bug.cgi</p>
+</dd>
+</dl>
+
+<dl>
+<dt><a name="buglist"
+><code class="code">buglist</code></a></dt>
+
+<dd>
+<p>A boolean specifying whether or not this field is selectable as a display or order column in buglist.cgi</p>
+</dd>
+</dl>
+
+<dl>
+<dt><a name="is_select"
+><code class="code">is_select</code></a></dt>
+
+<dd>
+<p>True if this is a <code class="code">FIELD_TYPE_SINGLE_SELECT</code> or <code class="code">FIELD_TYPE_MULTI_SELECT</code> field. It is only safe to call <a href="#legal_values" class="podlinkpod"
+>&#34;legal_values&#34;</a> if this is true.</p>
+
+<dt><a name="legal_values"
+><code class="code">legal_values</code></a></dt>
+
+<dd>
+<p>Valid values for this field, as an array of <a href="../Bugzilla/Field/Choice.html" class="podlinkpod"
+>Bugzilla::Field::Choice</a> objects.</p>
+</dd>
+</dl>
+
+<dl>
+<dt><a name="is_abnormal"
+><code class="code">is_abnormal</code></a></dt>
+
+<dd>
+<p>Most fields that have a <code class="code">SELECT</code> <a href="#type" class="podlinkpod"
+>&#34;type&#34;</a> have a certain schema for the table that stores their values, the table has the same name as the field, and the field&#39;s legal values can be edited via <em class="code">editvalues.cgi</em>.</p>
+
+<p>However, some fields do not follow that pattern. Those fields are considered &#34;abnormal&#34;.</p>
+
+<p>This method returns <code class="code">1</code> if the field is &#34;abnormal&#34;, <code class="code">0</code> otherwise.</p>
+</dd>
+</dl>
+
+<dl>
+<dt><a name="is_timetracking"
+><code class="code">is_timetracking</code></a></dt>
+
+<dd>
+<p>True if this is a time-tracking field that should only be shown to users in the <code class="code">timetrackinggroup</code>.</p>
+</dd>
+</dl>
+
+<dl>
+<dt><a name="visibility_field"
+><code class="code">visibility_field</code></a></dt>
+
+<dd>
+<p>What field controls this field&#39;s visibility? Returns a <code class="code">Bugzilla::Field</code> object representing the field that controls this field&#39;s visibility.</p>
+
+<p>Returns undef if there is no field that controls this field&#39;s visibility.</p>
+</dd>
+</dl>
+
+<dl>
+<dt><a name="visibility_value"
+><code class="code">visibility_value</code></a></dt>
+
+<dd>
+<p>If we have a <a href="#visibility_field" class="podlinkpod"
+>&#34;visibility_field&#34;</a>, then what value does that field have to be set to in order to show this field? Returns a <a href="../Bugzilla/Field/Choice.html" class="podlinkpod"
+>Bugzilla::Field::Choice</a> or undef if there is no <code class="code">visibility_field</code> set.</p>
+</dd>
+</dl>
+
+<dl>
+<dt><a name="controls_visibility_of"
+><code class="code">controls_visibility_of</code></a></dt>
+
+<dd>
+<p>An arrayref of <code class="code">Bugzilla::Field</code> objects, representing fields that this field controls the visibility of.</p>
+</dd>
+</dl>
+
+<dl>
+<dt><a name="value_field"
+><code class="code">value_field</code></a></dt>
+
+<dd>
+<p>The Bugzilla::Field that controls the list of values for this field.</p>
+
+<p>Returns undef if there is no field that controls this field&#39;s visibility.</p>
+</dd>
+</dl>
+
+<dl>
+<dt><a name="controls_values_of"
+><code class="code">controls_values_of</code></a></dt>
+
+<dd>
+<p>An arrayref of <code class="code">Bugzilla::Field</code> objects, representing fields that this field controls the values of.</p>
+</dd>
+</dl>
+
+<dl>
+<dt><a name="is_visible_on_bug"
+><code class="code">is_visible_on_bug</code></a></dt>
+
+<dd>
+<p>See <a href="../Bugzilla/Field/ChoiceInterface.html" class="podlinkpod"
+>Bugzilla::Field::ChoiceInterface</a>.</p>
+</dd>
+</dl>
+
+<dl>
+<dt><a name="is_relationship"
+><code class="code">is_relationship</code></a></dt>
+
+<dd>
+<p>Applies only to fields of type FIELD_TYPE_BUG_ID. Checks to see if a reverse relationship description has been set. This is the canonical condition to enable reverse link display, dependency tree display, and similar functionality.</p>
+</dd>
+</dl>
+
+<dl>
+<dt><a name="reverse_desc"
+><code class="code">reverse_desc</code></a></dt>
+
+<dd>
+<p>Applies only to fields of type FIELD_TYPE_BUG_ID. Describes the reverse relationship of this field. For example, if a BUG_ID field is called &#34;Is a duplicate of&#34;, the reverse description would be &#34;Duplicates of this bug&#34;.</p>
+</dd>
+</dl>
+
+<dl>
+<dt><a name="is_mandatory"
+><code class="code">is_mandatory</code></a></dt>
+
+<dd>
+<p>a boolean specifying whether or not the field is mandatory;</p>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Instance_Mutators"
+>Instance Mutators</a></h2>
+
+<p>These set the particular field that they are named after.</p>
+
+<p>They take a single value--the new value for that field.</p>
+
+<p>They will throw an error if you try to set the values to something invalid.</p>
+
+<dl>
+<dt><a name="set_description"
+><code class="code">set_description</code></a></dt>
+
+<dd>
+<dt><a name="set_enter_bug"
+><code class="code">set_enter_bug</code></a></dt>
+
+<dd>
+<dt><a name="set_obsolete"
+><code class="code">set_obsolete</code></a></dt>
+
+<dd>
+<dt><a name="set_sortkey"
+><code class="code">set_sortkey</code></a></dt>
+
+<dd>
+<dt><a name="set_in_new_bugmail"
+><code class="code">set_in_new_bugmail</code></a></dt>
+
+<dd>
+<dt><a name="set_buglist"
+><code class="code">set_buglist</code></a></dt>
+
+<dd>
+<dt><a name="set_reverse_desc"
+><code class="code">set_reverse_desc</code></a></dt>
+
+<dd>
+<dt><a name="set_visibility_field"
+><code class="code">set_visibility_field</code></a></dt>
+
+<dd>
+<dt><a name="set_visibility_value"
+><code class="code">set_visibility_value</code></a></dt>
+
+<dd>
+<dt><a name="set_value_field"
+><code class="code">set_value_field</code></a></dt>
+
+<dd>
+<dt><a name="set_is_mandatory"
+><code class="code">set_is_mandatory</code></a></dt>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Instance_Method"
+>Instance Method</a></h2>
+
+<dl>
+<dt><a name="remove_from_db"
+><code class="code">remove_from_db</code></a></dt>
+
+<dd>
+<p>Attempts to remove the passed in field from the database. Deleting a field is only successful if the field is obsolete and there are no values specified (or EVER specified) for the field.</p>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Class_Methods"
+>Class Methods</a></h2>
+
+<dl>
+<dt><a name="create"
+><code class="code">create</code></a></dt>
+
+<dd>
+<p>Just like <a href="../Bugzilla/Object.html#create" class="podlinkpod"
+>&#34;create&#34; in Bugzilla::Object</a>. Takes the following parameters:</p>
+
+<dl>
+<dt><a name="name_Required_-_The_name_of_the_field."
+><code class="code">name</code> <b>Required</b> - The name of the field.</a></dt>
+
+<dd>
+<dt><a name="description_Required_-_The_field_label_to_display_in_the_UI."
+><code class="code">description</code> <b>Required</b> - The field label to display in the UI.</a></dt>
+
+<dd>
+<dt><a name="mailhead_-_boolean_-_Whether_this_field_appears_at_the_top_of_the_bugmail_for_a_newly-filed_bug._Defaults_to_0."
+><code class="code">mailhead</code> - boolean - Whether this field appears at the top of the bugmail for a newly-filed bug. Defaults to 0.</a></dt>
+
+<dd>
+<dt><a
+><code class="code">custom</code> - boolean - True if this is a Custom Field. The field will be added to the <code class="code">bugs</code> table if it does not exist. Defaults to 0.</a></dt>
+
+<dd>
+<dt><a name="sortkey_-_integer_-_The_sortkey_of_the_field._Defaults_to_0."
+><code class="code">sortkey</code> - integer - The sortkey of the field. Defaults to 0.</a></dt>
+
+<dd>
+<dt><a name="enter_bug_-_boolean_-_Whether_this_field_is_editable_on_the_bug_creation_form._Defaults_to_0."
+><code class="code">enter_bug</code> - boolean - Whether this field is editable on the bug creation form. Defaults to 0.</a></dt>
+
+<dd>
+<dt><a name="buglist_-_boolean_-_Whether_this_field_is_selectable_as_a_display_or_order_column_in_bug_lists._Defaults_to_0."
+><code class="code">buglist</code> - boolean - Whether this field is selectable as a display or order column in bug lists. Defaults to 0.</a></dt>
+
+<dd>
+<p><code class="code">obsolete</code> - boolean - Whether this field is obsolete. Defaults to 0.</p>
+
+<p><code class="code">is_mandatory</code> - boolean - Whether this field is mandatory. Defaults to 0.</p>
+</dd>
+</dl>
+</dd>
+</dl>
+
+<dl>
+<dt><a name="get_legal_field_values($field)"
+><code class="code">get_legal_field_values($field)</code></a></dt>
+
+<dd>
+<p>Description: returns all the legal values for a field that has a list of legal values, like rep_platform or resolution. The table where these values are stored must at least have the following columns: value, isactive, sortkey.</p>
+
+<p>Params: <code class="code">$field</code> - Name of the table where valid values are.</p>
+
+<p>Returns: a reference to a list of valid values.</p>
+</dd>
+</dl>
+
+<dl>
+<dt><a name="populate_field_definitions()"
+><code class="code">populate_field_definitions()</code></a></dt>
+
+<dd>
+<p>Description: Populates the fielddefs table during an installation or upgrade.</p>
+
+<p>Params: none</p>
+
+<p>Returns: nothing</p>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Data_Validation"
+>Data Validation</a></h2>
+
+<dl>
+<dt><a name="check_field($name,_$value,_\@legal_values,_$no_warn)"
+><code class="code">check_field($name, $value, \@legal_values, $no_warn)</code></a></dt>
+
+<dd>
+<p>Description: Makes sure the field $name is defined and its $value is non empty. If @legal_values is defined, this routine checks whether its value is one of the legal values associated with this field, else it checks against the default valid values for this field obtained by <code class="code">get_legal_field_values($name)</code>. If the test is successful, the function returns 1. If the test fails, an error is thrown (by default), unless $no_warn is true, in which case the function returns 0.</p>
+
+<p>Params: $name - the field name $value - the field value @legal_values - (optional) list of legal values $no_warn - (optional) do not throw an error if true</p>
+
+<p>Returns: 1 on success; 0 on failure if $no_warn is true (else an error is thrown).</p>
+</dd>
+</dl>
+
+<dl>
+<dt><a name="get_field_id($fieldname)"
+><code class="code">get_field_id($fieldname)</code></a></dt>
+
+<dd>
+<p>Description: Returns the ID of the specified field name and throws an error if this field does not exist.</p>
+
+<p>Params: $name - a field name</p>
+
+<p>Returns: the corresponding field ID or an error if the field name does not exist.</p>
+</dd>
+</dl>
+<p class="backlinkbottom"><b><a name="___bottom" href="../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/Field/Choice.html b/docs/en/html/api/Bugzilla/Field/Choice.html
new file mode 100644
index 000000000..2e69cb764
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/Field/Choice.html
@@ -0,0 +1,93 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::Field::Choice</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href="../.././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::Field::Choice</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#METHODS'>METHODS</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#Class_Factory'>Class Factory</a>
+ <li class='indexItem indexItem2'><a href='#Mutators'>Mutators</a>
+ </ul>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::Field::Choice - A legal value for a &#60;select&#62;-type field.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<pre class="code"> my $field = new Bugzilla::Field({name =&#62; &#39;bug_status&#39;});
+
+ my $choice = new Bugzilla::Field::Choice-&#62;type($field)-&#62;new(1);
+
+ my $choices = Bugzilla::Field::Choice-&#62;type($field)-&#62;new_from_list([1,2,3]);
+ my $choices = Bugzilla::Field::Choice-&#62;type($field)-&#62;get_all();
+ my $choices = Bugzilla::Field::Choice-&#62;type($field-&#62;match({ sortkey =&#62; 10 }); </pre>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>This is an implementation of <a href="../../Bugzilla/Object.html" class="podlinkpod"
+>Bugzilla::Object</a>, but with a twist. You can&#39;t call any class methods (such as <code class="code">new</code>, <code class="code">create</code>, etc.) directly on <code class="code">Bugzilla::Field::Choice</code> itself. Instead, you have to call <code class="code">Bugzilla::Field::Choice-&#62;type($field)</code> to get the class you&#39;re going to instantiate, and then you call the methods on that.</p>
+
+<p>We do that because each field has its own database table for its values, so each value type needs its own class.</p>
+
+<p>See the <a href="#SYNOPSIS" class="podlinkpod"
+>&#34;SYNOPSIS&#34;</a> for examples of how this works.</p>
+
+<p>This class implements <a href="../../Bugzilla/Field/ChoiceInterface.html" class="podlinkpod"
+>Bugzilla::Field::ChoiceInterface</a>, and so all methods of that class are also available here.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="METHODS"
+>METHODS</a></h1>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Class_Factory"
+>Class Factory</a></h2>
+
+<p>In object-oriented design, a &#34;class factory&#34; is a method that picks and returns the right class for you, based on an argument that you pass.</p>
+
+<dl>
+<dt><a name="type"
+><code class="code">type</code></a></dt>
+
+<dd>
+<p>Takes a single argument, which is either the name of a field from the <code class="code">fielddefs</code> table, or a <a href="../../Bugzilla/Field.html" class="podlinkpod"
+>Bugzilla::Field</a> object representing a field.</p>
+
+<p>Returns an appropriate subclass of <code class="code">Bugzilla::Field::Choice</code> that you can now call class methods on (like <code class="code">new</code>, <code class="code">create</code>, <code class="code">match</code>, etc.)</p>
+
+<p><b>NOTE</b>: YOU CANNOT CALL CLASS METHODS ON <code class="code">Bugzilla::Field::Choice</code>. You must call <code class="code">type</code> to get a class you can call methods on.</p>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Mutators"
+>Mutators</a></h2>
+
+<p>This class implements mutators for all of the settable accessors in <a href="../../Bugzilla/Field/ChoiceInterface.html" class="podlinkpod"
+>Bugzilla::Field::ChoiceInterface</a>.</p>
+<p class="backlinkbottom"><b><a name="___bottom" href="../../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/Field/ChoiceInterface.html b/docs/en/html/api/Bugzilla/Field/ChoiceInterface.html
new file mode 100644
index 000000000..8d738e0aa
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/Field/ChoiceInterface.html
@@ -0,0 +1,150 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::Field::ChoiceInterface</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href="../.././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::Field::ChoiceInterface</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#METHODS'>METHODS</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#Accessors'>Accessors</a>
+ </ul>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::Field::ChoiceInterface - Makes an object act like a Bugzilla::Field::Choice.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>This is an &#34;interface&#34;,
+in the Java sense (sometimes called a &#34;Role&#34; or a &#34;Mixin&#34; in other languages).
+<a href="../../Bugzilla/Field/Choice.html" class="podlinkpod"
+>Bugzilla::Field::Choice</a> is the primary implementor of this interface,
+but other classes also implement it if they want to &#34;act like&#34; <a href="../../Bugzilla/Field/Choice.html" class="podlinkpod"
+>Bugzilla::Field::Choice</a>.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="METHODS"
+>METHODS</a></h1>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Accessors"
+>Accessors</a></h2>
+
+<p>These are in addition to the standard <a href="../../Bugzilla/Object.html" class="podlinkpod"
+>Bugzilla::Object</a> accessors.</p>
+
+<dl>
+<dt><a name="sortkey"
+><code class="code">sortkey</code></a></dt>
+
+<dd>
+<p>The key that determines the sort order of this item.</p>
+
+<dt><a name="field"
+><code class="code">field</code></a></dt>
+
+<dd>
+<p>The <a href="../../Bugzilla/Field.html" class="podlinkpod"
+>Bugzilla::Field</a> object that this field value belongs to.</p>
+
+<dt><a name="is_active"
+><code class="code">is_active</code></a></dt>
+
+<dd>
+<p>Whether or not this value should appear as an option on bugs that do not already have it set as the current value.</p>
+
+<dt><a name="is_static"
+><code class="code">is_static</code></a></dt>
+
+<dd>
+<p><code class="code">0</code> if this field value can be renamed or deleted,
+<code class="code">1</code> otherwise.</p>
+
+<dt><a name="is_default"
+><code class="code">is_default</code></a></dt>
+
+<dd>
+<p><code class="code">1</code> if this is the default value for this field,
+<code class="code">0</code> otherwise.</p>
+
+<dt><a name="bug_count"
+><code class="code">bug_count</code></a></dt>
+
+<dd>
+<p>An integer count of the number of bugs that have this value set.</p>
+
+<dt><a name="controls_visibility_of_fields"
+><code class="code">controls_visibility_of_fields</code></a></dt>
+
+<dd>
+<p>Returns an arrayref of <a href="../../Bugzilla/Field.html" class="podlinkpod"
+>Bugzilla::Field</a> objects,
+representing any fields whose visibility are controlled by this field value.</p>
+
+<dt><a name="controlled_values"
+><code class="code">controlled_values</code></a></dt>
+
+<dd>
+<p>Tells you which values in <b>other</b> fields appear (become visible) when this value is set in its field.</p>
+
+<p>Returns a hashref of arrayrefs.
+The hash keys are the names of fields,
+and the values are arrays of objects that implement <code class="code">Bugzilla::Field::ChoiceInterface</code>,
+representing values that this value controls the visibility of,
+for that field.</p>
+
+<dt><a name="visibility_value"
+><code class="code">visibility_value</code></a></dt>
+
+<dd>
+<p>Returns an object that implements <code class="code">Bugzilla::Field::ChoiceInterface</code>,
+which represents the value that needs to be set in order for this value to appear in the UI.</p>
+
+<dt><a name="is_visible_on_bug"
+><code class="code">is_visible_on_bug</code></a></dt>
+
+<dd>
+<p>Returns <code class="code">1</code> if,
+according to the settings of <code class="code">is_active</code> and <code class="code">visibility_value</code>,
+this value should be displayed as an option when viewing a bug.
+Returns <code class="code">0</code> otherwise.</p>
+
+<p>Takes a single argument,
+a <a href="../../Bugzilla/Bug.html" class="podlinkpod"
+>Bugzilla::Bug</a> object or a hash with similar fields to a <a href="../../Bugzilla/Bug.html" class="podlinkpod"
+>Bugzilla::Bug</a> object.</p>
+
+<dt><a name="is_set_on_bug"
+><code class="code">is_set_on_bug</code></a></dt>
+
+<dd>
+<p>Returns <code class="code">1</code> if this value is the current value set for its field on the passed-in <a href="../../Bugzilla/Bug.html" class="podlinkpod"
+>Bugzilla::Bug</a> object (or a hash that looks like a <a href="../../Bugzilla/Bug.html" class="podlinkpod"
+>Bugzilla::Bug</a>).
+For multi-valued fields,
+we return <code class="code">1</code> if <i>any</i> of the currently selected values are this value.</p>
+
+<p>Returns <code class="code">0</code> otherwise.</p>
+</dd>
+</dl>
+<p class="backlinkbottom"><b><a name="___bottom" href="../../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/Flag.html b/docs/en/html/api/Bugzilla/Flag.html
new file mode 100644
index 000000000..25be92fc4
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/Flag.html
@@ -0,0 +1,207 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::Flag</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href=".././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::Flag</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#NOTES'>NOTES</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#METHODS'>METHODS</a>
+ </ul>
+ <li class='indexItem indexItem1'><a href='#SEE_ALSO'>SEE ALSO</a>
+ <li class='indexItem indexItem1'><a href='#CONTRIBUTORS'>CONTRIBUTORS</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::Flag - A module to deal with Bugzilla flag values.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<p>Flag.pm provides an interface to flags as stored in Bugzilla.
+See below for more information.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NOTES"
+>NOTES</a></h1>
+
+<ul>
+<li>Import relevant functions from that script.</li>
+
+<li>Use of private functions / variables outside this module may lead to unexpected results after an upgrade.
+Please avoid using private functions in other files/modules.
+Private functions are functions whose names start with _ or a re specifically noted as being private.</li>
+</ul>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="METHODS"
+>METHODS</a></h2>
+
+<dl>
+<dt><a name="id"
+><code class="code">id</code></a></dt>
+
+<dd>
+<p>Returns the ID of the flag.</p>
+
+<dt><a name="name"
+><code class="code">name</code></a></dt>
+
+<dd>
+<p>Returns the name of the flagtype the flag belongs to.</p>
+
+<dt><a name="bug_id"
+><code class="code">bug_id</code></a></dt>
+
+<dd>
+<p>Returns the ID of the bug this flag belongs to.</p>
+
+<dt><a name="attach_id"
+><code class="code">attach_id</code></a></dt>
+
+<dd>
+<p>Returns the ID of the attachment this flag belongs to,
+if any.</p>
+
+<dt><a name="status"
+><code class="code">status</code></a></dt>
+
+<dd>
+<p>Returns the status &#39;+&#39;,
+&#39;-&#39;,
+&#39;?&#39; of the flag.</p>
+</dd>
+</dl>
+
+<dl>
+<dt><a name="type"
+><code class="code">type</code></a></dt>
+
+<dd>
+<p>Returns the type of the flag,
+as a Bugzilla::FlagType object.</p>
+
+<dt><a name="setter"
+><code class="code">setter</code></a></dt>
+
+<dd>
+<p>Returns the user who set the flag,
+as a Bugzilla::User object.</p>
+
+<dt><a name="requestee"
+><code class="code">requestee</code></a></dt>
+
+<dd>
+<p>Returns the user who has been requested to set the flag,
+as a Bugzilla::User object.</p>
+
+<dt><a name="attachment"
+><code class="code">attachment</code></a></dt>
+
+<dd>
+<p>Returns the attachment object the flag belongs to if the flag is an attachment flag,
+else undefined.</p>
+</dd>
+</dl>
+
+<dl>
+<dt><a name="match($criteria)"
+><code class="code">match($criteria)</code></a></dt>
+
+<dd>
+<p>Queries the database for flags matching the given criteria (specified as a hash of field names and their matching values) and returns an array of matching records.</p>
+</dd>
+</dl>
+
+<dl>
+<dt><a name="count($criteria)"
+><code class="code">count($criteria)</code></a></dt>
+
+<dd>
+<p>Queries the database for flags matching the given criteria (specified as a hash of field names and their matching values) and returns an array of matching records.</p>
+</dd>
+</dl>
+
+<dl>
+<dt><a name="create($flag,_$timestamp)"
+><code class="code">create($flag,
+$timestamp)</code></a></dt>
+
+<dd>
+<p>Creates a flag record in the database.</p>
+</dd>
+</dl>
+
+<dl>
+<dt><a name="extract_flags_from_cgi($bug,_$attachment,_$hr_vars)"
+><code class="code">extract_flags_from_cgi($bug,
+$attachment,
+$hr_vars)</code></a></dt>
+
+<dd>
+<p>Checks whether or not there are new flags to create and returns an array of hashes.
+This array is then passed to Flag::create().</p>
+</dd>
+</dl>
+
+<dl>
+<dt><a name="notify($flag,_$bug,_$attachment)"
+><code class="code">notify($flag,
+$bug,
+$attachment)</code></a></dt>
+
+<dd>
+<p>Sends an email notification about a flag being created,
+fulfilled or deleted.</p>
+</dd>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SEE_ALSO"
+>SEE ALSO</a></h1>
+
+<dl>
+<dt><a name="Bugzilla::FlagType"
+><b>Bugzilla::FlagType</b></a></dt>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="CONTRIBUTORS"
+>CONTRIBUTORS</a></h1>
+
+<dl>
+<dt><a name="Myk_Melez_&#60;myk@mozilla.org&#62;"
+>Myk Melez &#60;myk@mozilla.org&#62;</a></dt>
+
+<dd>
+<dt><a name="Jouni_Heikniemi_&#60;jouni@heikniemi.net&#62;"
+>Jouni Heikniemi &#60;jouni@heikniemi.net&#62;</a></dt>
+
+<dd>
+<dt><a name="Kevin_Benton_&#60;kevin.benton@amd.com&#62;"
+>Kevin Benton &#60;kevin.benton@amd.com&#62;</a></dt>
+
+<dd>
+<dt><a name="Fr&#195;&#169;d&#195;&#169;ric_Buclin_&#60;LpSolit@gmail.com&#62;"
+>Fr&#195;&#169;d&#195;&#169;ric Buclin &#60;LpSolit@gmail.com&#62;</a></dt>
+</dl>
+<p class="backlinkbottom"><b><a name="___bottom" href="../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/FlagType.html b/docs/en/html/api/Bugzilla/FlagType.html
new file mode 100644
index 000000000..3f04698b6
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/FlagType.html
@@ -0,0 +1,223 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::FlagType</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href=".././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::FlagType</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#NOTES'>NOTES</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#METHODS'>METHODS</a>
+ </ul>
+ <li class='indexItem indexItem1'><a href='#PUBLIC_FUNCTIONS%2FMETHODS'>PUBLIC FUNCTIONS/METHODS</a>
+ <li class='indexItem indexItem1'><a href='#SEE_ALSO'>SEE ALSO</a>
+ <li class='indexItem indexItem1'><a href='#CONTRIBUTORS'>CONTRIBUTORS</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::FlagType - A module to deal with Bugzilla flag types.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<p>FlagType.pm provides an interface to flag types as stored in Bugzilla.
+See below for more information.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NOTES"
+>NOTES</a></h1>
+
+<ul>
+<li>Use of private functions/variables outside this module may lead to unexpected results after an upgrade.
+Please avoid using private functions in other files/modules.
+Private functions are functions whose names start with _ or are specifically noted as being private.</li>
+</ul>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="METHODS"
+>METHODS</a></h2>
+
+<dl>
+<dt><a name="id"
+><code class="code">id</code></a></dt>
+
+<dd>
+<p>Returns the ID of the flagtype.</p>
+
+<dt><a name="name"
+><code class="code">name</code></a></dt>
+
+<dd>
+<p>Returns the name of the flagtype.</p>
+
+<dt><a name="description"
+><code class="code">description</code></a></dt>
+
+<dd>
+<p>Returns the description of the flagtype.</p>
+
+<dt><a name="cc_list"
+><code class="code">cc_list</code></a></dt>
+
+<dd>
+<p>Returns the concatenated CC list for the flagtype,
+as a single string.</p>
+
+<dt><a name="target_type"
+><code class="code">target_type</code></a></dt>
+
+<dd>
+<p>Returns whether the flagtype applies to bugs or attachments.</p>
+
+<dt><a name="is_active"
+><code class="code">is_active</code></a></dt>
+
+<dd>
+<p>Returns whether the flagtype is active or disabled.
+Flags being in a disabled flagtype are not deleted.
+It only prevents you from adding new flags to it.</p>
+
+<dt><a name="is_requestable"
+><code class="code">is_requestable</code></a></dt>
+
+<dd>
+<p>Returns whether you can request for the given flagtype (i.e.
+whether the &#39;?&#39; flag is available or not).</p>
+
+<dt><a name="is_requesteeble"
+><code class="code">is_requesteeble</code></a></dt>
+
+<dd>
+<p>Returns whether you can ask someone specifically or not.</p>
+
+<dt><a name="is_multiplicable"
+><code class="code">is_multiplicable</code></a></dt>
+
+<dd>
+<p>Returns whether you can have more than one flag for the given flagtype in a given bug/attachment.</p>
+
+<dt><a name="sortkey"
+><code class="code">sortkey</code></a></dt>
+
+<dd>
+<p>Returns the sortkey of the flagtype.</p>
+</dd>
+</dl>
+
+<dl>
+<dt><a name="grant_list"
+><code class="code">grant_list</code></a></dt>
+
+<dd>
+<p>Returns a reference to an array of users who have permission to grant this flag type.
+The arrays are populated with hashrefs containing the login,
+identity and visibility of users.</p>
+
+<dt><a name="grant_group"
+><code class="code">grant_group</code></a></dt>
+
+<dd>
+<p>Returns the group (as a Bugzilla::Group object) in which a user must be in order to grant or deny a request.</p>
+
+<dt><a name="request_group"
+><code class="code">request_group</code></a></dt>
+
+<dd>
+<p>Returns the group (as a Bugzilla::Group object) in which a user must be in order to request or clear a flag.</p>
+
+<dt><a name="flag_count"
+><code class="code">flag_count</code></a></dt>
+
+<dd>
+<p>Returns the number of flags belonging to the flagtype.</p>
+
+<dt><a name="inclusions"
+><code class="code">inclusions</code></a></dt>
+
+<dd>
+<p>Return a hash of product/component IDs and names explicitly associated with the flagtype.</p>
+
+<dt><a name="exclusions"
+><code class="code">exclusions</code></a></dt>
+
+<dd>
+<p>Return a hash of product/component IDs and names explicitly excluded from the flagtype.</p>
+</dd>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="PUBLIC_FUNCTIONS/METHODS"
+>PUBLIC FUNCTIONS/METHODS</a></h1>
+
+<dl>
+<dt><a name="get_clusions($id,_$type)"
+><code class="code">get_clusions($id,
+$type)</code></a></dt>
+
+<dd>
+<p>Return a hash of product/component IDs and names associated with the flagtype: $clusions{&#39;product_name:component_name&#39;} = &#34;product_ID:component_ID&#34;</p>
+</dd>
+</dl>
+
+<dl>
+<dt><a name="match($criteria)"
+><code class="code">match($criteria)</code></a></dt>
+
+<dd>
+<p>Queries the database for flag types matching the given criteria and returns a list of matching flagtype objects.</p>
+</dd>
+</dl>
+
+<dl>
+<dt><a name="count($criteria)"
+><code class="code">count($criteria)</code></a></dt>
+
+<dd>
+<p>Returns the total number of flag types matching the given criteria.</p>
+</dd>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SEE_ALSO"
+>SEE ALSO</a></h1>
+
+<dl>
+<dt><a name="Bugzilla::Flags"
+><b>Bugzilla::Flags</b></a></dt>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="CONTRIBUTORS"
+>CONTRIBUTORS</a></h1>
+
+<dl>
+<dt><a name="Myk_Melez_&#60;myk@mozilla.org&#62;"
+>Myk Melez &#60;myk@mozilla.org&#62;</a></dt>
+
+<dd>
+<dt><a name="Kevin_Benton_&#60;kevin.benton@amd.com&#62;"
+>Kevin Benton &#60;kevin.benton@amd.com&#62;</a></dt>
+
+<dd>
+<dt><a name="Fr&#195;&#169;d&#195;&#169;ric_Buclin_&#60;LpSolit@gmail.com&#62;"
+>Fr&#195;&#169;d&#195;&#169;ric Buclin &#60;LpSolit@gmail.com&#62;</a></dt>
+</dl>
+<p class="backlinkbottom"><b><a name="___bottom" href="../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/Group.html b/docs/en/html/api/Bugzilla/Group.html
new file mode 100644
index 000000000..43070ea37
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/Group.html
@@ -0,0 +1,160 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::Group</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href=".././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::Group</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#SUBROUTINES'>SUBROUTINES</a>
+ <li class='indexItem indexItem1'><a href='#METHODS'>METHODS</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::Group - Bugzilla group class.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<pre class="code"> use Bugzilla::Group;
+
+ my $group = new Bugzilla::Group(1);
+ my $group = new Bugzilla::Group({name =&#62; &#39;AcmeGroup&#39;});
+
+ my $id = $group-&#62;id;
+ my $name = $group-&#62;name;
+ my $description = $group-&#62;description;
+ my $user_reg_exp = $group-&#62;user_reg_exp;
+ my $is_active = $group-&#62;is_active;
+ my $icon_url = $group-&#62;icon_url;
+ my $is_active_bug_group = $group-&#62;is_active_bug_group;
+
+ my $group_id = Bugzilla::Group::ValidateGroupName(&#39;admin&#39;, @users);
+ my @groups = Bugzilla::Group-&#62;get_all;</pre>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>Group.pm represents a Bugzilla Group object. It is an implementation of <a href="../Bugzilla/Object.html" class="podlinkpod"
+>Bugzilla::Object</a>, and thus has all the methods that <a href="../Bugzilla/Object.html" class="podlinkpod"
+>Bugzilla::Object</a> provides, in addition to any methods documented below.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SUBROUTINES"
+>SUBROUTINES</a></h1>
+
+<dl>
+<dt><a name="create"
+><code class="code">create</code></a></dt>
+
+<dd>
+<p>Note that in addition to what <a href="../Bugzilla/Object.html#create(%24params)" class="podlinkpod"
+>&#34;create($params)&#34; in Bugzilla::Object</a> normally does, this function also makes the new group be inherited by the <code class="code">admin</code> group. That is, the <code class="code">admin</code> group will automatically be a member of this group.</p>
+
+<dt><a name="ValidateGroupName($name,_@users)"
+><code class="code">ValidateGroupName($name, @users)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: ValidateGroupName checks to see if ANY of the users
+ in the provided list of user objects can see the
+ named group.
+
+ Params: $name - String with the group name.
+ @users - An array with Bugzilla::User objects.
+
+ Returns: It returns the group id if successful
+ and undef otherwise.</pre>
+</dd>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="METHODS"
+>METHODS</a></h1>
+
+<dl>
+<dt><a name="check_remove"
+><code class="code">check_remove</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Determines whether it&#39;s OK to remove this group from the database, and throws an error if it&#39;s not OK.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="test_only"
+><code class="code">test_only</code></a></dt>
+
+<dd>
+<p><code class="code">boolean</code> If you want to only check if the group can be deleted <i>at all</i>, under any circumstances, specify <code class="code">test_only</code> to just do the most basic tests (the other parameters will be ignored in this situation, as those tests won&#39;t be run).</p>
+
+<dt><a name="remove_from_users"
+><code class="code">remove_from_users</code></a></dt>
+
+<dd>
+<p><code class="code">boolean</code> True if it would be OK to remove all users who are in this group from this group.</p>
+
+<dt><a name="remove_from_bugs"
+><code class="code">remove_from_bugs</code></a></dt>
+
+<dd>
+<p><code class="code">boolean</code> True if it would be OK to remove all bugs that are in this group from this group.</p>
+
+<dt><a name="remove_from_flags"
+><code class="code">remove_from_flags</code></a></dt>
+
+<dd>
+<p><code class="code">boolean</code> True if it would be OK to stop all flagtypes that reference this group from referencing this group (e.g., as their grantgroup or requestgroup).</p>
+
+<dt><a name="remove_from_products"
+><code class="code">remove_from_products</code></a></dt>
+
+<dd>
+<p><code class="code">boolean</code> True if it would be OK to remove this group from all group controls on products.</p>
+</dd>
+</dl>
+
+<dt><a name="Returns_(nothing)"
+><b>Returns</b> (nothing)</a></dt>
+</dl>
+
+<dt><a name="members_non_inherited"
+><code class="code">members_non_inherited</code></a></dt>
+
+<dd>
+<p>Returns an arrayref of <a href="../Bugzilla/User.html" class="podlinkpod"
+>Bugzilla::User</a> objects representing people who are &#34;directly&#34; in this group, meaning that they&#39;re in it because they match the group regular expression, or they have been actually added to the group manually.</p>
+
+<dt><a name="flatten_group_membership"
+><code class="code">flatten_group_membership</code></a></dt>
+
+<dd>
+<p>Accepts a list of groups and returns a list of all the groups whose members inherit membership in any group on the list. So, we can determine if a user is in any of the groups input to flatten_group_membership by querying the user_group_map for any user with DIRECT or REGEXP membership IN() the list of groups returned.</p>
+</dd>
+</dl>
+<p class="backlinkbottom"><b><a name="___bottom" href="../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/Hook.html b/docs/en/html/api/Bugzilla/Hook.html
new file mode 100644
index 000000000..7574e718d
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/Hook.html
@@ -0,0 +1,1322 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::Hook</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href=".././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::Hook</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#How_Hooks_Work'>How Hooks Work</a>
+ </ul>
+ <li class='indexItem indexItem1'><a href='#SUBROUTINES'>SUBROUTINES</a>
+ <li class='indexItem indexItem1'><a href='#HOOKS'>HOOKS</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#attachment_process_data'>attachment_process_data</a>
+ <li class='indexItem indexItem2'><a href='#auth_login_methods'>auth_login_methods</a>
+ <li class='indexItem indexItem2'><a href='#auth_verify_methods'>auth_verify_methods</a>
+ <li class='indexItem indexItem2'><a href='#bug_columns'>bug_columns</a>
+ <li class='indexItem indexItem2'><a href='#bug_end_of_create'>bug_end_of_create</a>
+ <li class='indexItem indexItem2'><a href='#bug_end_of_create_validators'>bug_end_of_create_validators</a>
+ <li class='indexItem indexItem2'><a href='#bug_end_of_update'>bug_end_of_update</a>
+ <li class='indexItem indexItem2'><a href='#bug_check_can_change_field'>bug_check_can_change_field</a>
+ <li class='indexItem indexItem2'><a href='#bug_fields'>bug_fields</a>
+ <li class='indexItem indexItem2'><a href='#bug_format_comment'>bug_format_comment</a>
+ <li class='indexItem indexItem2'><a href='#buglist_columns'>buglist_columns</a>
+ <li class='indexItem indexItem2'><a href='#search_operator_field_override'>search_operator_field_override</a>
+ <li class='indexItem indexItem2'><a href='#bugmail_recipients'>bugmail_recipients</a>
+ <li class='indexItem indexItem2'><a href='#bugmail_relationships'>bugmail_relationships</a>
+ <li class='indexItem indexItem2'><a href='#config_add_panels'>config_add_panels</a>
+ <li class='indexItem indexItem2'><a href='#config_modify_panels'>config_modify_panels</a>
+ <li class='indexItem indexItem2'><a href='#email_in_before_parse'>email_in_before_parse</a>
+ <li class='indexItem indexItem2'><a href='#email_in_after_parse'>email_in_after_parse</a>
+ <li class='indexItem indexItem2'><a href='#enter_bug_entrydefaultvars'>enter_bug_entrydefaultvars</a>
+ <li class='indexItem indexItem2'><a href='#flag_end_of_update'>flag_end_of_update</a>
+ <li class='indexItem indexItem2'><a href='#group_before_delete'>group_before_delete</a>
+ <li class='indexItem indexItem2'><a href='#group_end_of_create'>group_end_of_create</a>
+ <li class='indexItem indexItem2'><a href='#group_end_of_update'>group_end_of_update</a>
+ <li class='indexItem indexItem2'><a href='#install_before_final_checks'>install_before_final_checks</a>
+ <li class='indexItem indexItem2'><a href='#install_update_db'>install_update_db</a>
+ <li class='indexItem indexItem2'><a href='#db_schema_abstract_schema'>db_schema_abstract_schema</a>
+ <li class='indexItem indexItem2'><a href='#mailer_before_send'>mailer_before_send</a>
+ <li class='indexItem indexItem2'><a href='#object_before_create'>object_before_create</a>
+ <li class='indexItem indexItem2'><a href='#object_before_delete'>object_before_delete</a>
+ <li class='indexItem indexItem2'><a href='#object_before_set'>object_before_set</a>
+ <li class='indexItem indexItem2'><a href='#object_columns'>object_columns</a>
+ <li class='indexItem indexItem2'><a href='#object_end_of_create'>object_end_of_create</a>
+ <li class='indexItem indexItem2'><a href='#object_end_of_create_validators'>object_end_of_create_validators</a>
+ <li class='indexItem indexItem2'><a href='#object_end_of_set'>object_end_of_set</a>
+ <li class='indexItem indexItem2'><a href='#object_end_of_set_all'>object_end_of_set_all</a>
+ <li class='indexItem indexItem2'><a href='#object_end_of_update'>object_end_of_update</a>
+ <li class='indexItem indexItem2'><a href='#object_update_columns'>object_update_columns</a>
+ <li class='indexItem indexItem2'><a href='#object_validators'>object_validators</a>
+ <li class='indexItem indexItem2'><a href='#page_before_template'>page_before_template</a>
+ <li class='indexItem indexItem2'><a href='#product_confirm_delete'>product_confirm_delete</a>
+ <li class='indexItem indexItem2'><a href='#product_end_of_create'>product_end_of_create</a>
+ <li class='indexItem indexItem2'><a href='#quicksearch_map'>quicksearch_map</a>
+ <li class='indexItem indexItem2'><a href='#sanitycheck_check'>sanitycheck_check</a>
+ <li class='indexItem indexItem2'><a href='#sanitycheck_repair'>sanitycheck_repair</a>
+ <li class='indexItem indexItem2'><a href='#template_before_create'>template_before_create</a>
+ <li class='indexItem indexItem2'><a href='#template_before_process'>template_before_process</a>
+ <li class='indexItem indexItem2'><a href='#user_preferences'>user_preferences</a>
+ <li class='indexItem indexItem2'><a href='#webservice'>webservice</a>
+ <li class='indexItem indexItem2'><a href='#webservice_error_codes'>webservice_error_codes</a>
+ </ul>
+ <li class='indexItem indexItem1'><a href='#SEE_ALSO'>SEE ALSO</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::Hook - Extendable extension hooks for Bugzilla code</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<pre class="code"> use Bugzilla::Hook;
+
+ Bugzilla::Hook::process(&#34;hookname&#34;, { arg =&#62; $value, arg2 =&#62; $value2 });</pre>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>Bugzilla allows extension modules to drop in and add routines at arbitrary points in Bugzilla code. These points are referred to as hooks. When a piece of standard Bugzilla code wants to allow an extension to perform additional functions, it uses Bugzilla::Hook&#39;s <a href="#process" class="podlinkpod"
+>&#34;process&#34;</a> subroutine to invoke any extension code if installed.</p>
+
+<p>The implementation of extensions is described in <a href="../Bugzilla/Extension.html" class="podlinkpod"
+>Bugzilla::Extension</a>.</p>
+
+<p>There is sample code for every hook in the Example extension, located in <em class="code">extensions/Example/Extension.pm</em>.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="How_Hooks_Work"
+>How Hooks Work</a></h2>
+
+<p>When a hook named <code class="code">HOOK_NAME</code> is run, Bugzilla looks through all enabled <a href="../Bugzilla/Extension.html" class="podlinkpod"
+>extensions</a> for extensions that implement a subroutine named <code class="code">HOOK_NAME</code>.</p>
+
+<p>See <a href="../Bugzilla/Extension.html" class="podlinkpod"
+>Bugzilla::Extension</a> for more details about how an extension can run code during a hook.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SUBROUTINES"
+>SUBROUTINES</a></h1>
+
+<dl>
+<dt><a name="process"
+><code class="code">process</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Invoke any code hooks with a matching name from any installed extensions.</p>
+
+<p>See <a href="../Bugzilla/Extension.html" class="podlinkpod"
+>Bugzilla::Extension</a> for more information on Bugzilla&#39;s extension mechanism.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="$name_-_The_name_of_the_hook_to_invoke."
+><code class="code">$name</code> - The name of the hook to invoke.</a></dt>
+
+<dd>
+<dt><a
+><code class="code">$args</code> - A hashref. The named args to pass to the hook. They will be passed as arguments to the hook method in the extension.</a></dt>
+</dl>
+
+<dt><a name="Returns_(nothing)"
+><b>Returns</b> (nothing)</a></dt>
+</dl>
+</dd>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="HOOKS"
+>HOOKS</a></h1>
+
+<p>This describes what hooks exist in Bugzilla currently. They are mostly in alphabetical order, but some related hooks are near each other instead of being alphabetical.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="attachment_process_data"
+>attachment_process_data</a></h2>
+
+<p>This happens at the very beginning process of the attachment creation. You can edit the attachment content itself as well as all attributes of the attachment, before they are validated and inserted into the DB.</p>
+
+<p>Params:</p>
+
+<dl>
+<dt><a
+><code class="code">data</code> - A reference pointing either to the content of the file being uploaded or pointing to the filehandle associated with the file.</a></dt>
+
+<dd>
+<dt><a
+><code class="code">attributes</code> - A hashref whose keys are the same as the input to <a href="../Bugzilla/Attachment.html#create" class="podlinkpod"
+>&#34;create&#34; in Bugzilla::Attachment</a>. The data in this hashref hasn&#39;t been validated yet.</a></dt>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="auth_login_methods"
+>auth_login_methods</a></h2>
+
+<p>This allows you to add new login types to Bugzilla. (See <a href="../Bugzilla/Auth/Login.html" class="podlinkpod"
+>Bugzilla::Auth::Login</a>.)</p>
+
+<p>Params:</p>
+
+<dl>
+<dt><a name="modules"
+><code class="code">modules</code></a></dt>
+
+<dd>
+<p>This is a hash--a mapping from login-type &#34;names&#34; to the actual module on disk. The keys will be all the values that were passed to <a href="../Bugzilla/Auth.html#login" class="podlinkpod"
+>&#34;login&#34; in Bugzilla::Auth</a> for the <code class="code">Login</code> parameter. The values are the actual path to the module on disk. (For example, if the key is <code class="code">DB</code>, the value is <em class="code">Bugzilla/Auth/Login/DB.pm</em>.)</p>
+
+<p>For your extension, the path will start with <em class="code">Bugzilla/Extension/Foo/</em>, where &#34;Foo&#34; is the name of your Extension. (See the code in the example extension.)</p>
+
+<p>If your login type is in the hash as a key, you should set that key to the right path to your module. That module&#39;s <code class="code">new</code> method will be called, probably with empty parameters. If your login type is <i>not</i> in the hash, you should not set it.</p>
+
+<p>You will be prevented from adding new keys to the hash, so make sure your key is in there before you modify it. (In other words, you can&#39;t add in login methods that weren&#39;t passed to <a href="../Bugzilla/Auth.html#login" class="podlinkpod"
+>&#34;login&#34; in Bugzilla::Auth</a>.)</p>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="auth_verify_methods"
+>auth_verify_methods</a></h2>
+
+<p>This works just like <a href="#auth_login_methods" class="podlinkpod"
+>&#34;auth_login_methods&#34;</a> except it&#39;s for login verification methods (See <a href="../Bugzilla/Auth/Verify.html" class="podlinkpod"
+>Bugzilla::Auth::Verify</a>.) It also takes a <code class="code">modules</code> parameter, just like <a href="#auth_login_methods" class="podlinkpod"
+>&#34;auth_login_methods&#34;</a>.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="bug_columns"
+>bug_columns</a></h2>
+
+<p><b>DEPRECATED</b> Use <a href="#object_columns" class="podlinkpod"
+>&#34;object_columns&#34;</a> instead.</p>
+
+<p>This allows you to add new fields that will show up in every <a href="../Bugzilla/Bug.html" class="podlinkpod"
+>Bugzilla::Bug</a> object. Note that you will also need to use the <a href="#bug_fields" class="podlinkpod"
+>&#34;bug_fields&#34;</a> hook in conjunction with this hook to make this work.</p>
+
+<p>Params:</p>
+
+<dl>
+<dt><a name="columns_-_An_arrayref_containing_an_array_of_column_names._Push_your_column_name(s)_onto_the_array."
+><code class="code">columns</code> - An arrayref containing an array of column names. Push your column name(s) onto the array.</a></dt>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="bug_end_of_create"
+>bug_end_of_create</a></h2>
+
+<p>This happens at the end of <a href="../Bugzilla/Bug.html#create" class="podlinkpod"
+>&#34;create&#34; in Bugzilla::Bug</a>, after all other changes are made to the database. This occurs inside a database transaction.</p>
+
+<p>Params:</p>
+
+<dl>
+<dt><a name="bug_-_The_created_bug_object."
+><code class="code">bug</code> - The created bug object.</a></dt>
+
+<dd>
+<dt><a name="timestamp_-_The_timestamp_used_for_all_updates_in_this_transaction,_as_a_SQL_date_string."
+><code class="code">timestamp</code> - The timestamp used for all updates in this transaction, as a SQL date string.</a></dt>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="bug_end_of_create_validators"
+>bug_end_of_create_validators</a></h2>
+
+<p>This happens during <a href="../Bugzilla/Bug.html#create" class="podlinkpod"
+>&#34;create&#34; in Bugzilla::Bug</a>, after all parameters have been validated, but before anything has been inserted into the database.</p>
+
+<p>Params:</p>
+
+<dl>
+<dt><a name="params"
+><code class="code">params</code></a></dt>
+
+<dd>
+<p>A hashref. The validated parameters passed to <code class="code">create</code>.</p>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="bug_end_of_update"
+>bug_end_of_update</a></h2>
+
+<p>This happens at the end of <a href="../Bugzilla/Bug.html#update" class="podlinkpod"
+>&#34;update&#34; in Bugzilla::Bug</a>, after all other changes are made to the database. This generally occurs inside a database transaction.</p>
+
+<p>Params:</p>
+
+<dl>
+<dt><a name="bug"
+><code class="code">bug</code></a></dt>
+
+<dd>
+<p>The changed bug object, with all fields set to their updated values.</p>
+
+<dt><a name="old_bug"
+><code class="code">old_bug</code></a></dt>
+
+<dd>
+<p>A bug object pulled from the database before the fields were set to their updated values (so it has the old values available for each field).</p>
+
+<dt><a name="timestamp"
+><code class="code">timestamp</code></a></dt>
+
+<dd>
+<p>The timestamp used for all updates in this transaction, as a SQL date string.</p>
+
+<dt><a name="changes"
+><code class="code">changes</code></a></dt>
+
+<dd>
+<p>The hash of changed fields. <code class="code">$changes-&#62;{field} = [old, new]</code></p>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="bug_check_can_change_field"
+>bug_check_can_change_field</a></h2>
+
+<p>This hook controls what fields users are allowed to change. You can add code here for site-specific policy changes and other customizations.</p>
+
+<p>This hook is only executed if the field&#39;s new and old values differ.</p>
+
+<p>Any denies take priority over any allows. So, if another extension denies a change but yours allows the change, the other extension&#39;s deny will override your extension&#39;s allow.</p>
+
+<p>Params:</p>
+
+<dl>
+<dt><a name="bug"
+><code class="code">bug</code></a></dt>
+
+<dd>
+<p><a href="../Bugzilla/Bug.html" class="podlinkpod"
+>Bugzilla::Bug</a> - The current bug object that this field is changing on.</p>
+
+<dt><a name="field"
+><code class="code">field</code></a></dt>
+
+<dd>
+<p>The name (from the <code class="code">fielddefs</code> table) of the field that we are checking.</p>
+
+<dt><a name="new_value"
+><code class="code">new_value</code></a></dt>
+
+<dd>
+<p>The new value that the field is being changed to.</p>
+
+<dt><a name="old_value"
+><code class="code">old_value</code></a></dt>
+
+<dd>
+<p>The old value that the field is being changed from.</p>
+
+<dt><a name="priv_results"
+><code class="code">priv_results</code></a></dt>
+
+<dd>
+<p><code class="code">array</code> - This is how you explicitly allow or deny a change. You should only push something into this array if you want to explicitly allow or explicitly deny the change, and thus skip all other permission checks that would otherwise happen after this hook is called. If you don&#39;t care about the field change, then don&#39;t push anything into the array.</p>
+
+<p>The pushed value should be a choice from the following constants:</p>
+
+<dl>
+<dt><a name="PRIVILEGES_REQUIRED_NONE"
+><code class="code">PRIVILEGES_REQUIRED_NONE</code></a></dt>
+
+<dd>
+<p>No privileges required. This explicitly <b>allows</b> a change.</p>
+
+<dt><a name="PRIVILEGES_REQUIRED_REPORTER"
+><code class="code">PRIVILEGES_REQUIRED_REPORTER</code></a></dt>
+
+<dd>
+<p>User is not the reporter, assignee or an empowered user, so <b>deny</b>.</p>
+
+<dt><a name="PRIVILEGES_REQUIRED_ASSIGNEE"
+><code class="code">PRIVILEGES_REQUIRED_ASSIGNEE</code></a></dt>
+
+<dd>
+<p>User is not the assignee or an empowered user, so <b>deny</b>.</p>
+
+<dt><a name="PRIVILEGES_REQUIRED_EMPOWERED"
+><code class="code">PRIVILEGES_REQUIRED_EMPOWERED</code></a></dt>
+
+<dd>
+<p>User is not a sufficiently empowered user, so <b>deny</b>.</p>
+</dd>
+</dl>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="bug_fields"
+>bug_fields</a></h2>
+
+<p>Allows the addition of database fields from the bugs table to the standard list of allowable fields in a <a href="../Bugzilla/Bug.html" class="podlinkpod"
+>Bugzilla::Bug</a> object, so that you can call the field as a method.</p>
+
+<p>Note: You should add here the names of any fields you added in <a href="#bug_columns" class="podlinkpod"
+>&#34;bug_columns&#34;</a>.</p>
+
+<p>Params:</p>
+
+<dl>
+<dt><a name="columns_-_A_arrayref_containing_an_array_of_column_names._Push_your_column_name(s)_onto_the_array."
+><code class="code">columns</code> - A arrayref containing an array of column names. Push your column name(s) onto the array.</a></dt>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="bug_format_comment"
+>bug_format_comment</a></h2>
+
+<p>Allows you to do custom parsing on comments before they are displayed. You do this by returning two regular expressions: one that matches the section you want to replace, and then another that says what you want to replace that match with.</p>
+
+<p>The matching and replacement will be run with the <code class="code">/g</code> switch on the regex.</p>
+
+<p>Params:</p>
+
+<dl>
+<dt><a name="regexes"
+><code class="code">regexes</code></a></dt>
+
+<dd>
+<p>An arrayref of hashrefs.</p>
+
+<p>You should push a hashref containing two keys (<code class="code">match</code> and <code class="code">replace</code>) in to this array. <code class="code">match</code> is the regular expression that matches the text you want to replace, <code class="code">replace</code> is what you want to replace that text with. (This gets passed into a regular expression like <code class="code">s/$match/$replace/</code>.)</p>
+
+<p>Instead of specifying a regular expression for <code class="code">replace</code> you can also return a coderef (a reference to a subroutine). If you want to use backreferences (using <code class="code">$1</code>, <code class="code">$2</code>, etc. in your <code class="code">replace</code>), you have to use this method--it won&#39;t work if you specify <code class="code">$1</code>, <code class="code">$2</code> in a regular expression for <code class="code">replace</code>. Your subroutine will get a hashref as its only argument. This hashref contains a single key, <code class="code">matches</code>. <code class="code">matches</code> is an arrayref that contains <code class="code">$1</code>, <code class="code">$2</code>, <code class="code">$3</code>, etc. in order, up to <code class="code">$10</code>. Your subroutine should return what you want to replace the full <code class="code">match</code> with. (See the code example for this hook if you want to see how this actually all works in code. It&#39;s simpler than it sounds.)</p>
+
+<p><b>You are responsible for HTML-escaping your returned data.</b> Failing to do so could open a security hole in Bugzilla.</p>
+
+<dt><a name="text"
+><code class="code">text</code></a></dt>
+
+<dd>
+<p>A <b>reference</b> to the exact text that you are parsing.</p>
+
+<p>Generally you should not modify this yourself. Instead you should be returning regular expressions using the <code class="code">regexes</code> array.</p>
+
+<p>The text has not been parsed in any way. (So, for example, it is not HTML-escaped. You get &#34;&#38;&#34;, not &#34;&#38;amp;&#34;.)</p>
+
+<dt><a name="bug"
+><code class="code">bug</code></a></dt>
+
+<dd>
+<p>The <a href="../Bugzilla/Bug.html" class="podlinkpod"
+>Bugzilla::Bug</a> object that this comment is on. Sometimes this is <code class="code">undef</code>, meaning that we are parsing text that is not on a bug.</p>
+
+<dt><a name="comment"
+><code class="code">comment</code></a></dt>
+
+<dd>
+<p>A <a href="../Bugzilla/Comment.html" class="podlinkpod"
+>Bugzilla::Comment</a> object representing the comment you are about to parse.</p>
+
+<p>Sometimes this is <code class="code">undef</code>, meaning that we are parsing text that is not a bug comment (but could still be some other part of a bug, like the summary line).</p>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="buglist_columns"
+>buglist_columns</a></h2>
+
+<p>This happens in <a href="../Bugzilla/Search.html#COLUMNS" class="podlinkpod"
+>&#34;COLUMNS&#34; in Bugzilla::Search</a>, which determines legal bug list columns for <em class="code">buglist.cgi</em> and <em class="code">colchange.cgi</em>. It gives you the opportunity to add additional display columns.</p>
+
+<p>Params:</p>
+
+<dl>
+<dt><a
+><code class="code">columns</code> - A hashref, where the keys are unique string identifiers for the column being defined and the values are hashrefs with the following fields:</a></dt>
+
+<dd>
+<dl>
+<dt><a name="name_-_The_name_of_the_column_in_the_database."
+><code class="code">name</code> - The name of the column in the database.</a></dt>
+
+<dd>
+<dt><a name="title_-_The_title_of_the_column_as_displayed_to_users."
+><code class="code">title</code> - The title of the column as displayed to users.</a></dt>
+</dl>
+
+<p>The definition is structured as:</p>
+
+<pre class="code"> $columns-&#62;{$id} = { name =&#62; $name, title =&#62; $title };</pre>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="search_operator_field_override"
+>search_operator_field_override</a></h2>
+
+<p>This allows you to modify <a href="../Bugzilla/Search.html#OPERATOR_FIELD_OVERRIDE" class="podlinkpod"
+>&#34;OPERATOR_FIELD_OVERRIDE&#34; in Bugzilla::Search</a>, which determines the search functions for fields. It allows you to specify custom search functionality for certain fields.</p>
+
+<p>See <a href="../Bugzilla/Search.html#OPERATOR_FIELD_OVERRIDE" class="podlinkpod"
+>&#34;OPERATOR_FIELD_OVERRIDE&#34; in Bugzilla::Search</a> for reference and see the code in the example extension.</p>
+
+<p>Note that the interface to this hook is <b>UNSTABLE</b> and it may change in the future.</p>
+
+<p>Params:</p>
+
+<dl>
+<dt><a name="operators_-_See_&#34;OPERATOR_FIELD_OVERRIDE&#34;_in_Bugzilla::Search_to_get_an_idea_of_the_structure."
+><code class="code">operators</code> - See <a href="../Bugzilla/Search.html#OPERATOR_FIELD_OVERRIDE" class="podlinkpod"
+>&#34;OPERATOR_FIELD_OVERRIDE&#34; in Bugzilla::Search</a> to get an idea of the structure.</a></dt>
+
+<dd>
+<dt><a name="search_-_The_Bugzilla::Search_object."
+><code class="code">search</code> - The <a href="../Bugzilla/Search.html" class="podlinkpod"
+>Bugzilla::Search</a> object.</a></dt>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="bugmail_recipients"
+>bugmail_recipients</a></h2>
+
+<p>This allows you to modify the list of users who are going to be receiving a particular bugmail. It also allows you to specify why they are receiving the bugmail.</p>
+
+<p>Users&#39; bugmail preferences will be applied to any users that you add to the list. (So, for example, if you add somebody as though they were a CC on the bug, and their preferences state that they don&#39;t get email when they are a CC, they won&#39;t get email.)</p>
+
+<p>This hook is called before watchers or globalwatchers are added to the recipient list.</p>
+
+<p>Params:</p>
+
+<dl>
+<dt><a name="bug"
+><code class="code">bug</code></a></dt>
+
+<dd>
+<p>The <a href="../Bugzilla/Bug.html" class="podlinkpod"
+>Bugzilla::Bug</a> that bugmail is being sent about.</p>
+
+<dt><a name="recipients"
+><code class="code">recipients</code></a></dt>
+
+<dd>
+<p>This is a hashref. The keys are numeric user ids from the <code class="code">profiles</code> table in the database, for each user who should be receiving this bugmail. The values are hashrefs. The keys in <i>these</i> hashrefs correspond to the &#34;relationship&#34; that the user has to the bug they&#39;re being emailed about, and the value should always be <code class="code">1</code>. The &#34;relationships&#34; are described by the various <code class="code">REL_</code> constants in <a href="../Bugzilla/Constants.html" class="podlinkpod"
+>Bugzilla::Constants</a>.</p>
+
+<p>Here&#39;s an example of adding userid <code class="code">123</code> to the recipient list as though he were on the CC list:</p>
+
+<pre class="code"> $recipients-&#62;{123}-&#62;{+REL_CC} = 1</pre>
+
+<p>(We use <code class="code">+</code> in front of <code class="code">REL_CC</code> so that Perl interprets it as a constant instead of as a string.)</p>
+
+<dt><a name="diffs"
+><code class="code">diffs</code></a></dt>
+
+<dd>
+<p>This is a list of hashes, each hash representing a change to the bug. Each hash has the following members: <code class="code">field_name</code>, <code class="code">bug_when</code>, <code class="code">old</code>, <code class="code">new</code> and <code class="code">who</code> (a <a href="../Bugzilla/User.html" class="podlinkpod"
+>Bugzilla::User</a>). If appropriate, there will also be <code class="code">attach_id</code> or <code class="code">comment_id</code>; if either is present, there will be <code class="code">isprivate</code>. See <code class="code">_get_diffs</code> in <em class="code">Bugzilla/BugMail.pm</em> to see exactly how it is populated. Warning: the format and existence of the &#34;diffs&#34; parameter is subject to change in future releases of Bugzilla.</p>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="bugmail_relationships"
+>bugmail_relationships</a></h2>
+
+<p>There are various sorts of &#34;relationships&#34; that a user can have to a bug, such as Assignee, CC, etc. If you want to add a new type of relationship, you should use this hook.</p>
+
+<p>Params:</p>
+
+<dl>
+<dt><a name="relationships"
+><code class="code">relationships</code></a></dt>
+
+<dd>
+<p>A hashref, where the keys are numbers and the values are strings.</p>
+
+<p>The keys represent a numeric identifier for the relationship. The numeric identifier should be a negative number between -1 and -127. The number must be unique across all extensions. (Negative numbers are used so as not to conflict with relationship identifiers in Bugzilla itself.)</p>
+
+<p>The value is the &#34;name&#34; of this relationship that will show up in email headers in bugmails. The &#34;name&#34; should be short and should contain no spaces.</p>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="config_add_panels"
+>config_add_panels</a></h2>
+
+<p>If you want to add new panels to the Parameters administrative interface, this is where you do it.</p>
+
+<p>Params:</p>
+
+<dl>
+<dt><a name="panel_modules"
+><code class="code">panel_modules</code></a></dt>
+
+<dd>
+<p>A hashref, where the keys are the &#34;name&#34; of the panel and the value is the Perl module representing that panel. For example, if the name is <code class="code">Auth</code>, the value would be <code class="code">Bugzilla::Config::Auth</code>.</p>
+
+<p>For your extension, the Perl module would start with <code class="code">Bugzilla::Extension::Foo</code>, where &#34;Foo&#34; is the name of your Extension. (See the code in the example extension.)</p>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="config_modify_panels"
+>config_modify_panels</a></h2>
+
+<p>This is how you modify already-existing panels in the Parameters administrative interface. For example, if you wanted to add a new Auth method (modifying Bugzilla::Config::Auth) this is how you&#39;d do it.</p>
+
+<p>Params:</p>
+
+<dl>
+<dt><a name="panels"
+><code class="code">panels</code></a></dt>
+
+<dd>
+<p>A hashref, where the keys are lower-case panel &#34;names&#34; (like <code class="code">auth</code>, <code class="code">admin</code>, etc.) and the values are hashrefs. The hashref contains a single key, <code class="code">params</code>. <code class="code">params</code> is an arrayref--the return value from <code class="code">get_param_list</code> for that module. You can modify <code class="code">params</code> and your changes will be reflected in the interface.</p>
+
+<p>Adding new keys to <code class="code">panels</code> will have no effect. You should use <a href="#config_add_panels" class="podlinkpod"
+>&#34;config_add_panels&#34;</a> if you want to add new panels.</p>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="email_in_before_parse"
+>email_in_before_parse</a></h2>
+
+<p>This happens right after an inbound email is converted into an Email::MIME object, but before we start parsing the email to extract field data. This means the email has already been decoded for you. It gives you a chance to interact with the email itself before <a href="../email_in.html" class="podlinkpod"
+>email_in</a> starts parsing its content.</p>
+
+<dl>
+<dt><a name="mail_-_An_Email::MIME_object._The_decoded_incoming_email."
+><code class="code">mail</code> - An Email::MIME object. The decoded incoming email.</a></dt>
+
+<dd>
+<dt><a name="fields_-_A_hashref._The_hash_which_will_contain_extracted_data."
+><code class="code">fields</code> - A hashref. The hash which will contain extracted data.</a></dt>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="email_in_after_parse"
+>email_in_after_parse</a></h2>
+
+<p>This happens after all the data has been extracted from the email, but before the reporter is validated, during <a href="../email_in.html" class="podlinkpod"
+>email_in</a>. This lets you do things after the normal parsing of the email, such as sanitizing field data, changing the user account being used to file a bug, etc.</p>
+
+<dl>
+<dt><a name="fields_-_A_hashref._The_hash_containing_the_extracted_field_data."
+><code class="code">fields</code> - A hashref. The hash containing the extracted field data.</a></dt>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="enter_bug_entrydefaultvars"
+>enter_bug_entrydefaultvars</a></h2>
+
+<p><b>DEPRECATED</b> - Use <a href="#template_before_process" class="podlinkpod"
+>&#34;template_before_process&#34;</a> instead.</p>
+
+<p>This happens right before the template is loaded on enter_bug.cgi.</p>
+
+<p>Params:</p>
+
+<dl>
+<dt><a name="vars_-_A_hashref._The_variables_that_will_be_passed_into_the_template."
+><code class="code">vars</code> - A hashref. The variables that will be passed into the template.</a></dt>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="flag_end_of_update"
+>flag_end_of_update</a></h2>
+
+<p>This happens at the end of <a href="../Bugzilla/Flag.html#update_flags" class="podlinkpod"
+>&#34;update_flags&#34; in Bugzilla::Flag</a>, after all other changes are made to the database and after emails are sent. It gives you a before/after snapshot of flags so you can react to specific flag changes. This generally occurs inside a database transaction.</p>
+
+<p>Note that the interface to this hook is <b>UNSTABLE</b> and it may change in the future.</p>
+
+<p>Params:</p>
+
+<dl>
+<dt><a name="object_-_The_changed_bug_or_attachment_object."
+><code class="code">object</code> - The changed bug or attachment object.</a></dt>
+
+<dd>
+<dt><a name="timestamp_-_The_timestamp_used_for_all_updates_in_this_transaction,_as_a_SQL_date_string."
+><code class="code">timestamp</code> - The timestamp used for all updates in this transaction, as a SQL date string.</a></dt>
+
+<dd>
+<dt><a name="old_flags_-_The_snapshot_of_flag_summaries_from_before_the_change."
+><code class="code">old_flags</code> - The snapshot of flag summaries from before the change.</a></dt>
+
+<dd>
+<dt><a
+><code class="code">new_flags</code> - The snapshot of flag summaries after the change. Call <code class="code">my ($removed, $added) = diff_arrays(old_flags, new_flags)</code> to get the list of changed flags, and search for a specific condition like <code class="code">added eq &#39;review-&#39;</code>.</a></dt>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="group_before_delete"
+>group_before_delete</a></h2>
+
+<p>This happens in <a href="../Bugzilla/Group.html#remove_from_db" class="podlinkpod"
+>&#34;remove_from_db&#34; in Bugzilla::Group</a>, after we&#39;ve confirmed that the group can be deleted, but before any rows have actually been removed from the database. This occurs inside a database transaction.</p>
+
+<p>Params:</p>
+
+<dl>
+<dt><a name="group_-_The_Bugzilla::Group_being_deleted."
+><code class="code">group</code> - The <a href="../Bugzilla/Group.html" class="podlinkpod"
+>Bugzilla::Group</a> being deleted.</a></dt>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="group_end_of_create"
+>group_end_of_create</a></h2>
+
+<p>This happens at the end of <a href="../Bugzilla/Group.html#create" class="podlinkpod"
+>&#34;create&#34; in Bugzilla::Group</a>, after all other changes are made to the database. This occurs inside a database transaction.</p>
+
+<p>Params:</p>
+
+<dl>
+<dt><a name="group"
+><code class="code">group</code></a></dt>
+
+<dd>
+<p>The new <a href="../Bugzilla/Group.html" class="podlinkpod"
+>Bugzilla::Group</a> object that was just created.</p>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="group_end_of_update"
+>group_end_of_update</a></h2>
+
+<p>This happens at the end of <a href="../Bugzilla/Group.html#update" class="podlinkpod"
+>&#34;update&#34; in Bugzilla::Group</a>, after all other changes are made to the database. This occurs inside a database transaction.</p>
+
+<p>Params:</p>
+
+<dl>
+<dt><a name="group_-_The_changed_Bugzilla::Group_object,_with_all_fields_set_to_their_updated_values."
+><code class="code">group</code> - The changed <a href="../Bugzilla/Group.html" class="podlinkpod"
+>Bugzilla::Group</a> object, with all fields set to their updated values.</a></dt>
+
+<dd>
+<dt><a name="changes_-_The_hash_of_changed_fields._$changes-&#62;{$field}_=_[$old,_$new]"
+><code class="code">changes</code> - The hash of changed fields. <code class="code">$changes-&#62;{$field} = [$old, $new]</code></a></dt>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="install_before_final_checks"
+>install_before_final_checks</a></h2>
+
+<p>Allows execution of custom code before the final checks are done in checksetup.pl.</p>
+
+<p>Params:</p>
+
+<dl>
+<dt><a name="silent"
+><code class="code">silent</code></a></dt>
+
+<dd>
+<p>A flag that indicates whether or not checksetup is running in silent mode. If this is true, messages that are <i>always</i> printed by checksetup.pl should be suppressed, but messages about any changes that are just being done this one time should be printed.</p>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="install_update_db"
+>install_update_db</a></h2>
+
+<p>This happens at the very end of all the tables being updated during an installation or upgrade. If you need to modify your custom schema or add new columns to existing tables, do it here. No params are passed.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="db_schema_abstract_schema"
+>db_schema_abstract_schema</a></h2>
+
+<p>This allows you to add tables to Bugzilla. Note that we recommend that you prefix the names of your tables with some word (preferably the name of your Extension), so that they don&#39;t conflict with any future Bugzilla tables.</p>
+
+<p>If you wish to add new <i>columns</i> to existing Bugzilla tables, do that in <a href="#install_update_db" class="podlinkpod"
+>&#34;install_update_db&#34;</a>.</p>
+
+<p>Params:</p>
+
+<dl>
+<dt><a
+><code class="code">schema</code> - A hashref, in the format of <a href="../Bugzilla/DB/Schema.html#ABSTRACT_SCHEMA" class="podlinkpod"
+>&#34;ABSTRACT_SCHEMA&#34; in Bugzilla::DB::Schema</a>. Add new hash keys to make new table definitions. <em class="code">checksetup.pl</em> will automatically add these tables to the database when run.</a></dt>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="mailer_before_send"
+>mailer_before_send</a></h2>
+
+<p>Called right before <a href="../Bugzilla/Mailer.html" class="podlinkpod"
+>Bugzilla::Mailer</a> sends a message to the MTA.</p>
+
+<p>Params:</p>
+
+<dl>
+<dt><a name="email_-_The_Email::MIME_object_that&#39;s_about_to_be_sent."
+><code class="code">email</code> - The <code class="code">Email::MIME</code> object that&#39;s about to be sent.</a></dt>
+
+<dd>
+<dt><a name="mailer_args_-_An_arrayref_that&#39;s_passed_as_mailer_args_to_&#34;new&#34;_in_Email::Send."
+><code class="code">mailer_args</code> - An arrayref that&#39;s passed as <code class="code">mailer_args</code> to <a href="../Email/Send.html#new" class="podlinkpod"
+>&#34;new&#34; in Email::Send</a>.</a></dt>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="object_before_create"
+>object_before_create</a></h2>
+
+<p>This happens at the beginning of <a href="../Bugzilla/Object.html#create" class="podlinkpod"
+>&#34;create&#34; in Bugzilla::Object</a>.</p>
+
+<p>Params:</p>
+
+<dl>
+<dt><a name="class"
+><code class="code">class</code></a></dt>
+
+<dd>
+<p>The name of the class that <code class="code">create</code> was called on. You can check this like <code class="code">if ($class-&#62;isa(&#39;Some::Class&#39;))</code> in your code, to perform specific tasks before <code class="code">create</code> for only certain classes.</p>
+
+<dt><a name="params"
+><code class="code">params</code></a></dt>
+
+<dd>
+<p>A hashref. The set of named parameters passed to <code class="code">create</code>.</p>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="object_before_delete"
+>object_before_delete</a></h2>
+
+<p>This happens in <a href="../Bugzilla/Object.html#remove_from_db" class="podlinkpod"
+>&#34;remove_from_db&#34; in Bugzilla::Object</a>, after we&#39;ve confirmed that the object can be deleted, but before any rows have actually been removed from the database. This sometimes occurs inside a database transaction.</p>
+
+<p>Params:</p>
+
+<dl>
+<dt><a
+><code class="code">object</code> - The <a href="../Bugzilla/Object.html" class="podlinkpod"
+>Bugzilla::Object</a> being deleted. You will probably want to check its type like <code class="code">$object-&#62;isa(&#39;Some::Class&#39;)</code> before doing anything with it.</a></dt>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="object_before_set"
+>object_before_set</a></h2>
+
+<p>Called during <a href="../Bugzilla/Object.html#set" class="podlinkpod"
+>&#34;set&#34; in Bugzilla::Object</a>, before any actual work is done. You can use this to perform actions before a value is changed for specific fields on certain types of objects.</p>
+
+<p>Params:</p>
+
+<dl>
+<dt><a name="object"
+><code class="code">object</code></a></dt>
+
+<dd>
+<p>The object that <code class="code">set</code> was called on. You will probably want to do something like <code class="code">if ($object-&#62;isa(&#39;Some::Class&#39;))</code> in your code to limit your changes to only certain subclasses of Bugzilla::Object.</p>
+
+<dt><a name="field"
+><code class="code">field</code></a></dt>
+
+<dd>
+<p>The name of the field being updated in the object.</p>
+
+<dt><a name="value"
+><code class="code">value</code></a></dt>
+
+<dd>
+<p>The value being set on the object.</p>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="object_columns"
+>object_columns</a></h2>
+
+<p>This hook allows you to add new &#34;fields&#34; to existing Bugzilla objects, that correspond to columns in their tables.</p>
+
+<p>For example, if you added an <code class="code">example</code> column to the &#34;bugs&#34; table, you would have to also add an <code class="code">example</code> field to the <code class="code">Bugzilla::Bug</code> object in order to access that data via Bug objects.</p>
+
+<p>Don&#39;t do anything slow inside this hook--it&#39;s called several times on every page of Bugzilla.</p>
+
+<p>Params:</p>
+
+<dl>
+<dt><a name="class"
+><code class="code">class</code></a></dt>
+
+<dd>
+<p>The name of the class that this hook is being called on. You can check this like <code class="code">if ($class-&#62;isa(&#39;Some::Class&#39;))</code> in your code, to add new fields only for certain classes.</p>
+
+<dt><a name="columns"
+><code class="code">columns</code></a></dt>
+
+<dd>
+<p>An arrayref. Add the string names of columns to this array to add new values to objects.</p>
+
+<p>For example, if you add an <code class="code">example</code> column to a particular table (using <a href="#install_update_db" class="podlinkpod"
+>&#34;install_update_db&#34;</a>), and then push the string <code class="code">example</code> into this array for the object that uses that table, then you can access the information in that column via <code class="code">$object-&#62;{example}</code> on all objects of that type.</p>
+
+<p>This arrayref does not contain the standard column names--you cannot modify or remove standard object columns using this hook.</p>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="object_end_of_create"
+>object_end_of_create</a></h2>
+
+<p>Called at the end of <a href="../Bugzilla/Object.html#create" class="podlinkpod"
+>&#34;create&#34; in Bugzilla::Object</a>, after all other changes are made to the database. This occurs inside a database transaction.</p>
+
+<p>Params:</p>
+
+<dl>
+<dt><a name="class"
+><code class="code">class</code></a></dt>
+
+<dd>
+<p>The name of the class that <code class="code">create</code> was called on. You can check this like <code class="code">if ($class-&#62;isa(&#39;Some::Class&#39;))</code> in your code, to perform specific tasks for only certain classes.</p>
+
+<dt><a name="object"
+><code class="code">object</code></a></dt>
+
+<dd>
+<p>The created object.</p>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="object_end_of_create_validators"
+>object_end_of_create_validators</a></h2>
+
+<p>Called at the end of <a href="../Bugzilla/Object.html#run_create_validators" class="podlinkpod"
+>&#34;run_create_validators&#34; in Bugzilla::Object</a>. You can use this to run additional validation when creating an object.</p>
+
+<p>If a subclass has overridden <code class="code">run_create_validators</code>, then this usually happens <i>before</i> the subclass does its custom validation.</p>
+
+<p>Params:</p>
+
+<dl>
+<dt><a name="class"
+><code class="code">class</code></a></dt>
+
+<dd>
+<p>The name of the class that <code class="code">create</code> was called on. You can check this like <code class="code">if ($class-&#62;isa(&#39;Some::Class&#39;))</code> in your code, to perform specific tasks for only certain classes.</p>
+
+<dt><a name="params"
+><code class="code">params</code></a></dt>
+
+<dd>
+<p>A hashref. The set of named parameters passed to <code class="code">create</code>, modified and validated by the <code class="code">VALIDATORS</code> specified for the object.</p>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="object_end_of_set"
+>object_end_of_set</a></h2>
+
+<p>Called during <a href="../Bugzilla/Object.html#set" class="podlinkpod"
+>&#34;set&#34; in Bugzilla::Object</a>, after all the code of the function has completed (so the value has been validated and the field has been set to the new value). You can use this to perform actions after a value is changed for specific fields on certain types of objects.</p>
+
+<p>The new value is not specifically passed to this hook because you can get it as <code class="code">$object-&#62;{$field}</code>.</p>
+
+<p>Params:</p>
+
+<dl>
+<dt><a name="object"
+><code class="code">object</code></a></dt>
+
+<dd>
+<p>The object that <code class="code">set</code> was called on. You will probably want to do something like <code class="code">if ($object-&#62;isa(&#39;Some::Class&#39;))</code> in your code to limit your changes to only certain subclasses of Bugzilla::Object.</p>
+
+<dt><a name="field"
+><code class="code">field</code></a></dt>
+
+<dd>
+<p>The name of the field that was updated in the object.</p>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="object_end_of_set_all"
+>object_end_of_set_all</a></h2>
+
+<p>This happens at the end of <a href="../Bugzilla/Object.html#set_all" class="podlinkpod"
+>&#34;set_all&#34; in Bugzilla::Object</a>. This is a good place to call custom set_ functions on objects, or to make changes to an object before <code class="code">update()</code> is called.</p>
+
+<p>Params:</p>
+
+<dl>
+<dt><a name="object"
+><code class="code">object</code></a></dt>
+
+<dd>
+<p>The <a href="../Bugzilla/Object.html" class="podlinkpod"
+>Bugzilla::Object</a> which is being updated. You will probably want to do something like <code class="code">if ($object-&#62;isa(&#39;Some::Class&#39;))</code> in your code to limit your changes to only certain subclasses of Bugzilla::Object.</p>
+
+<dt><a name="params"
+><code class="code">params</code></a></dt>
+
+<dd>
+<p>A hashref. The set of named parameters passed to <code class="code">set_all</code>.</p>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="object_end_of_update"
+>object_end_of_update</a></h2>
+
+<p>Called during <a href="../Bugzilla/Object.html#update" class="podlinkpod"
+>&#34;update&#34; in Bugzilla::Object</a>, after changes are made to the database, but while still inside a transaction.</p>
+
+<p>Params:</p>
+
+<dl>
+<dt><a name="object"
+><code class="code">object</code></a></dt>
+
+<dd>
+<p>The object that <code class="code">update</code> was called on. You will probably want to do something like <code class="code">if ($object-&#62;isa(&#39;Some::Class&#39;))</code> in your code to limit your changes to only certain subclasses of Bugzilla::Object.</p>
+
+<dt><a name="old_object"
+><code class="code">old_object</code></a></dt>
+
+<dd>
+<p>The object as it was before it was updated.</p>
+
+<dt><a name="changes"
+><code class="code">changes</code></a></dt>
+
+<dd>
+<p>The fields that have been changed, in the same format that <a href="../Bugzilla/Object.html#update" class="podlinkpod"
+>&#34;update&#34; in Bugzilla::Object</a> returns.</p>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="object_update_columns"
+>object_update_columns</a></h2>
+
+<p>If you&#39;ve added fields to bugs via <a href="#object_columns" class="podlinkpod"
+>&#34;object_columns&#34;</a>, then this hook allows you to say which of those columns should be updated in the database when <a href="../Bugzilla/Object.html#update" class="podlinkpod"
+>&#34;update&#34; in Bugzilla::Object</a> is called on the object.</p>
+
+<p>If you don&#39;t use this hook, then your custom columns won&#39;t be modified in the database by Bugzilla.</p>
+
+<p>Params:</p>
+
+<dl>
+<dt><a name="object"
+><code class="code">object</code></a></dt>
+
+<dd>
+<p>The object that is about to be updated. You should check this like <code class="code">if ($object-&#62;isa(&#39;Some::Class&#39;))</code> in your code, to modify the &#34;update columns&#34; only for certain classes.</p>
+
+<dt><a name="columns"
+><code class="code">columns</code></a></dt>
+
+<dd>
+<p>An arrayref. Add the string names of columns to this array to allow that column to be updated when <code class="code">update()</code> is called on the object.</p>
+
+<p>This arrayref does not contain the standard column names--you cannot stop standard columns from being updated by using this hook.</p>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="object_validators"
+>object_validators</a></h2>
+
+<p>Allows you to add new items to <a href="../Bugzilla/Object.html#VALIDATORS" class="podlinkpod"
+>&#34;VALIDATORS&#34; in Bugzilla::Object</a> for particular classes.</p>
+
+<p>Params:</p>
+
+<dl>
+<dt><a name="class"
+><code class="code">class</code></a></dt>
+
+<dd>
+<p>The name of the class that <code class="code">VALIDATORS</code> was called on. You can check this like <code class="code">if ($class-&#62;isa(&#39;Some::Class&#39;))</code> in your code, to add validators only for certain classes.</p>
+
+<dt><a name="validators"
+><code class="code">validators</code></a></dt>
+
+<dd>
+<p>A hashref, where the keys are database columns and the values are subroutine references. You can add new validators or modify existing ones. If you modify an existing one, you should remember to call the original validator inside of your modified validator. (This way, several extensions can all modify the same validator.)</p>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="page_before_template"
+>page_before_template</a></h2>
+
+<p>This is a simple way to add your own pages to Bugzilla. This hooks <code class="code">page.cgi</code>, which loads templates from <em class="code">template/en/default/pages</em>. For example, <code class="code">page.cgi?id=fields.html</code> loads <em class="code">template/en/default/pages/fields.html.tmpl</em>.</p>
+
+<p>This hook is called right before the template is loaded, so that you can pass your own variables to your own pages.</p>
+
+<p>You can also use this to implement complex custom pages, by doing your own output and then calling <code class="code">exit</code> at the end of the hook, thus preventing the normal <code class="code">page.cgi</code> behavior from occurring.</p>
+
+<p>Params:</p>
+
+<dl>
+<dt><a name="page_id"
+><code class="code">page_id</code></a></dt>
+
+<dd>
+<p>This is the name of the page being loaded, like <code class="code">fields.html</code>.</p>
+
+<p>Note that if two extensions use the same name, it is uncertain which will override the others, so you should be careful with how you name your pages. Usually extensions prefix their pages with a directory named after their extension, so for an extension named &#34;Foo&#34;, page ids usually look like <code class="code">foo/mypage.html</code>.</p>
+
+<dt><a name="vars"
+><code class="code">vars</code></a></dt>
+
+<dd>
+<p>This is a hashref--put variables into here if you want them passed to your template.</p>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="product_confirm_delete"
+>product_confirm_delete</a></h2>
+
+<p><b>DEPRECATED</b> - Use <a href="#template_before_process" class="podlinkpod"
+>&#34;template_before_process&#34;</a> instead.</p>
+
+<p>Called before displaying the confirmation message when deleting a product.</p>
+
+<p>Params:</p>
+
+<dl>
+<dt><a name="vars_-_The_template_vars_hashref."
+><code class="code">vars</code> - The template vars hashref.</a></dt>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="product_end_of_create"
+>product_end_of_create</a></h2>
+
+<p>Called right after a new product has been created, allowing additional changes to be made to the new product&#39;s attributes. This occurs inside of a database transaction, so if the hook throws an error, all previous changes will be rolled back, including the creation of the new product. (However, note that such rollbacks should not normally be used, as some databases that Bugzilla supports have very bad rollback performance. If you want to validate input and throw errors before the Product is created, use <a href="#object_end_of_create_validators" class="podlinkpod"
+>&#34;object_end_of_create_validators&#34;</a> instead, or add a validator using <a href="#object_validators" class="podlinkpod"
+>&#34;object_validators&#34;</a>.)</p>
+
+<p>Params:</p>
+
+<dl>
+<dt><a name="product_-_The_new_Bugzilla::Product_object_that_was_just_created."
+><code class="code">product</code> - The new <a href="../Bugzilla/Product.html" class="podlinkpod"
+>Bugzilla::Product</a> object that was just created.</a></dt>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="quicksearch_map"
+>quicksearch_map</a></h2>
+
+<p>This hook allows you to alter the Quicksearch syntax to include e.g. special searches for custom fields you have.</p>
+
+<p>Params:</p>
+
+<dl>
+<dt><a
+><code class="code">map</code> - a hash where the key is the name you want to use in Quicksearch, and the value is the name from the <code class="code">fielddefs</code> table that you want it to map to. You can modify existing mappings or add new ones.</a></dt>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="sanitycheck_check"
+>sanitycheck_check</a></h2>
+
+<p>This hook allows for extra sanity checks to be added, for use by <em class="code">sanitycheck.cgi</em>.</p>
+
+<p>Params:</p>
+
+<dl>
+<dt><a name="status_-_a_CODEREF_that_allows_status_messages_to_be_displayed_to_the_user._(sanitycheck.cgi&#39;s_Status)"
+><code class="code">status</code> - a CODEREF that allows status messages to be displayed to the user. (<em class="code">sanitycheck.cgi</em>&#39;s <code class="code">Status</code>)</a></dt>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="sanitycheck_repair"
+>sanitycheck_repair</a></h2>
+
+<p>This hook allows for extra sanity check repairs to be made, for use by <em class="code">sanitycheck.cgi</em>.</p>
+
+<p>Params:</p>
+
+<dl>
+<dt><a name="status_-_a_CODEREF_that_allows_status_messages_to_be_displayed_to_the_user._(sanitycheck.cgi&#39;s_Status)"
+><code class="code">status</code> - a CODEREF that allows status messages to be displayed to the user. (<em class="code">sanitycheck.cgi</em>&#39;s <code class="code">Status</code>)</a></dt>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="template_before_create"
+>template_before_create</a></h2>
+
+<p>This hook allows you to modify the configuration of <a href="../Bugzilla/Template.html" class="podlinkpod"
+>Bugzilla::Template</a> objects before they are created. For example, you could add a new global template variable this way.</p>
+
+<p>Params:</p>
+
+<dl>
+<dt><a name="config"
+><code class="code">config</code></a></dt>
+
+<dd>
+<p>A hashref--the configuration that will be passed to <a href="../Template.html#new" class="podlinkpod"
+>&#34;new&#34; in Template</a>. See <a href="http://template-toolkit.org/docs/modules/Template.html#section_CONFIGURATION_SUMMARY" class="podlinkurl"
+>http://template-toolkit.org/docs/modules/Template.html#section_CONFIGURATION_SUMMARY</a> for information on how this configuration variable is structured (or just look at the code for <code class="code">create</code> in <a href="../Bugzilla/Template.html" class="podlinkpod"
+>Bugzilla::Template</a>.)</p>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="template_before_process"
+>template_before_process</a></h2>
+
+<p>This hook is called any time Bugzilla processes a template file, including calls to <code class="code">$template-&#62;process</code>, <code class="code">PROCESS</code> statements in templates, and <code class="code">INCLUDE</code> statements in templates. It is not called when templates process a <code class="code">BLOCK</code>, only when they process a file.</p>
+
+<p>This hook allows you to define additional variables that will be available to the template being processed, or to modify the variables that are currently in the template. It works exactly as though you inserted code to modify template variables at the top of a template.</p>
+
+<p>You probably want to restrict this hook to operating only if a certain file is being processed (which is why you get a <code class="code">file</code> argument below). Otherwise, modifying the <code class="code">vars</code> argument will affect every single template in Bugzilla.</p>
+
+<p><b>Note:</b> This hook is not called if you are already in this hook. (That is, it won&#39;t call itself recursively.) This prevents infinite recursion in situations where this hook needs to process a template (such as if this hook throws an error).</p>
+
+<p>Params:</p>
+
+<dl>
+<dt><a name="vars"
+><code class="code">vars</code></a></dt>
+
+<dd>
+<p>This is the entire set of variables that the current template can see. Technically, this is a <a href="../Template/Stash.html" class="podlinkpod"
+>Template::Stash</a> object, but you can just use it like a hashref if you want.</p>
+
+<dt><a name="file"
+><code class="code">file</code></a></dt>
+
+<dd>
+<p>The name of the template file being processed. This is relative to the main template directory for the language (i.e. for <em class="code">template/en/default/bug/show.html.tmpl</em>, this variable will contain <code class="code">bug/show.html.tmpl</code>).</p>
+
+<dt><a name="context"
+><code class="code">context</code></a></dt>
+
+<dd>
+<p>A <a href="../Template/Context.html" class="podlinkpod"
+>Template::Context</a> object. Usually you will not have to use this, but if you need information about the template itself (other than just its name), you can get it from here.</p>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="user_preferences"
+>user_preferences</a></h2>
+
+<p>This hook allows you to add additional panels to the User Preferences page, and validate data displayed and returned from these panels. It works in combination with the <code class="code">tabs</code> hook available in the <em class="code">template/en/default/account/prefs/prefs.html.tmpl</em> template. To make it work, you must define two templates in your extension: <em class="code">extensions/Foo/template/en/default/hook/account/prefs/prefs-tabs.html.tmpl</em> contains a list of additional panels to include. <em class="code">extensions/Foo/template/en/default/account/prefs/bar.html.tmpl</em> contains the content of the panel itself. See the <code class="code">Example</code> extension to see how things work.</p>
+
+<p>Params:</p>
+
+<dl>
+<dt><a name="current_tab"
+><code class="code">current_tab</code></a></dt>
+
+<dd>
+<p>The name of the current panel being viewed by the user. You should always make sure that the name of the panel matches what you expect it to be. Else you could be interacting with the panel of another extension.</p>
+
+<dt><a name="save_changes"
+><code class="code">save_changes</code></a></dt>
+
+<dd>
+<p>A boolean which is true when data should be validated and the DB updated accordingly. This means the user clicked the &#34;Submit Changes&#34; button.</p>
+
+<dt><a name="handled"
+><code class="code">handled</code></a></dt>
+
+<dd>
+<p>This is a <b>reference</b> to a scalar, not a scalar. (So you would set it like <code class="code">$$handled = 1</code>, not like <code class="code">$handled = 1</code>.) Set this to a true value to let Bugzilla know that the passed-in panel is valid and that you have handled it. (Otherwise, Bugzilla will throw an error that the panel is invalid.) Don&#39;t set this to true if you didn&#39;t handle the panel listed in <code class="code">current_tab</code>.</p>
+
+<dt><a name="vars"
+><code class="code">vars</code></a></dt>
+
+<dd>
+<p>You can add as many new key/value pairs as you want to this hashref. It will be passed to the template.</p>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="webservice"
+>webservice</a></h2>
+
+<p>This hook allows you to add your own modules to the WebService. (See <a href="../Bugzilla/WebService.html" class="podlinkpod"
+>Bugzilla::WebService</a>.)</p>
+
+<p>Params:</p>
+
+<dl>
+<dt><a name="dispatch"
+><code class="code">dispatch</code></a></dt>
+
+<dd>
+<p>A hashref where you can specify the names of your modules and which Perl module handles the functions for that module. (This is actually sent to <a href="../SOAP/Lite.html#dispatch_with" class="podlinkpod"
+>&#34;dispatch_with&#34; in SOAP::Lite</a>. You can see how that&#39;s used in <em class="code">xmlrpc.cgi</em>.)</p>
+
+<p>The Perl module name will most likely start with <code class="code">Bugzilla::Extension::Foo::</code> (where &#34;Foo&#34; is the name of your extension).</p>
+
+<p>Example:</p>
+
+<pre class="code"> $dispatch-&#62;{&#39;Example.Blah&#39;} = &#34;Bugzilla::Extension::Example::Webservice::Blah&#34;;</pre>
+
+<p>And then you&#39;d have a module <em class="code">extensions/Example/lib/Webservice/Blah.pm</em>, and could call methods from that module like <code class="code">Example.Blah.Foo()</code> using the WebServices interface.</p>
+
+<p>It&#39;s recommended that all the keys you put in <code class="code">dispatch</code> start with the name of your extension, so that you don&#39;t conflict with the standard Bugzilla WebService functions (and so that you also don&#39;t conflict with other plugins).</p>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="webservice_error_codes"
+>webservice_error_codes</a></h2>
+
+<p>If your webservice extension throws custom errors, you can set numeric codes for those errors here.</p>
+
+<p>Extensions should use error codes above 10000, unless they are re-using an already-existing error code.</p>
+
+<p>Params:</p>
+
+<dl>
+<dt><a name="error_map"
+><code class="code">error_map</code></a></dt>
+
+<dd>
+<p>A hash that maps the names of errors (like <code class="code">invalid_param</code>) to numbers. See <a href="../Bugzilla/WebService/Constants.html#WS_ERROR_CODE" class="podlinkpod"
+>&#34;WS_ERROR_CODE&#34; in Bugzilla::WebService::Constants</a> for an example.</p>
+</dd>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SEE_ALSO"
+>SEE ALSO</a></h1>
+
+<p><a href="../Bugzilla/Extension.html" class="podlinkpod"
+>Bugzilla::Extension</a></p>
+<p class="backlinkbottom"><b><a name="___bottom" href="../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/Install.html b/docs/en/html/api/Bugzilla/Install.html
new file mode 100644
index 000000000..0116fbf1c
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/Install.html
@@ -0,0 +1,94 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::Install</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href=".././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::Install</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#CONSTANTS'>CONSTANTS</a>
+ <li class='indexItem indexItem1'><a href='#SUBROUTINES'>SUBROUTINES</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::Install - Functions and variables having to do with installation.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<pre class="code"> use Bugzilla::Install;
+ Bugzilla::Install::update_settings();</pre>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>This module is used primarily by <a href="../checksetup.pl.html" class="podlinkpod"
+>checksetup.pl</a> during installation. This module contains functions that deal with general installation issues after the database is completely set up and configured.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="CONSTANTS"
+>CONSTANTS</a></h1>
+
+<dl>
+<dt><a name="SETTINGS"
+><code class="code">SETTINGS</code></a></dt>
+
+<dd>
+<p>Contains information about Settings, used by <a href="#update_settings()" class="podlinkpod"
+>&#34;update_settings()&#34;</a>.</p>
+</dd>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SUBROUTINES"
+>SUBROUTINES</a></h1>
+
+<dl>
+<dt><a name="update_settings()"
+><code class="code">update_settings()</code></a></dt>
+
+<dd>
+<p>Description: Adds and updates Settings for users.</p>
+
+<p>Params: none</p>
+
+<p>Returns: nothing.</p>
+
+<dt><a name="create_default_classification"
+><code class="code">create_default_classification</code></a></dt>
+
+<dd>
+<p>Creates the default &#34;Unclassified&#34; <a href="../Bugzilla/Classification.html" class="podlinkpod"
+>Classification</a> if it doesn&#39;t already exist</p>
+
+<dt><a name="create_default_product()"
+><code class="code">create_default_product()</code></a></dt>
+
+<dd>
+<p>Description: Creates the default product and component if they don&#39;t exist.</p>
+
+<p>Params: none</p>
+
+<p>Returns: nothing</p>
+</dd>
+</dl>
+<p class="backlinkbottom"><b><a name="___bottom" href="../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/Install/CPAN.html b/docs/en/html/api/Bugzilla/Install/CPAN.html
new file mode 100644
index 000000000..26353d8ae
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/Install/CPAN.html
@@ -0,0 +1,79 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::Install::CPAN</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href="../.././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::Install::CPAN</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#SUBROUTINES'>SUBROUTINES</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::Install::CPAN - Routines to install Perl modules from CPAN.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<pre class="code"> use Bugzilla::Install::CPAN;
+
+ set_cpan_config();
+ install_module(&#39;Module::Name&#39;);</pre>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>This is primarily used by <a href="../../install-module.html" class="podlinkpod"
+>install-module</a> to do the &#34;hard work&#34; of installing CPAN modules.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SUBROUTINES"
+>SUBROUTINES</a></h1>
+
+<dl>
+<dt><a name="set_cpan_config"
+><code class="code">set_cpan_config</code></a></dt>
+
+<dd>
+<p>Sets up the configuration of CPAN for this session. Must be called before <a href="#install_module" class="podlinkpod"
+>&#34;install_module&#34;</a>. Takes one boolean parameter. If true, <a href="#install_module" class="podlinkpod"
+>&#34;install_module&#34;</a> will install modules globally instead of to the local <em class="code">lib/</em> directory. On most systems, you have to be root to do that.</p>
+
+<dt><a name="install_module"
+><code class="code">install_module</code></a></dt>
+
+<dd>
+<p>Installs a module from CPAN. Takes two arguments:</p>
+
+<dl>
+<dt><a name="$name_-_The_name_of_the_module,_just_like_you&#39;d_pass_to_the_install_command_in_the_CPAN_shell."
+><code class="code">$name</code> - The name of the module, just like you&#39;d pass to the <code class="code">install</code> command in the CPAN shell.</a></dt>
+
+<dd>
+<dt><a
+><code class="code">$test</code> - If true, we run tests on this module before installing, but we still force the install if the tests fail. This is only used when we internally install a newer CPAN module.</a></dt>
+</dl>
+
+<p>Note that calling this function prints a <b>lot</b> of information to STDOUT and STDERR.</p>
+</dd>
+</dl>
+<p class="backlinkbottom"><b><a name="___bottom" href="../../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/Install/DB.html b/docs/en/html/api/Bugzilla/Install/DB.html
new file mode 100644
index 000000000..069248cd6
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/Install/DB.html
@@ -0,0 +1,76 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::Install::DB</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href="../.././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::Install::DB</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#SUBROUTINES'>SUBROUTINES</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::Install::DB - Fix up the database during installation.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<pre class="code"> use Bugzilla::Install::DB qw(indicate_progress);
+ Bugzilla::Install::DB::update_table_definitions();
+
+ indicate_progress({ total =&#62; $total, current =&#62; $count, every =&#62; 10 });</pre>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>This module is used primarily by <a href="../../checksetup.pl.html" class="podlinkpod"
+>checksetup.pl</a> to modify the database during upgrades.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SUBROUTINES"
+>SUBROUTINES</a></h1>
+
+<dl>
+<dt><a name="update_table_definitions()"
+><code class="code">update_table_definitions()</code></a></dt>
+
+<dd>
+<p>Description: This is the primary code that updates table definitions during upgrades. If you modify the schema in some way, you should add code to the end of this function to make sure that your modifications happen over all installations.</p>
+
+<p>Params: none</p>
+
+<p>Returns: nothing</p>
+
+<dt><a name="update_fielddefs_definition()"
+><code class="code">update_fielddefs_definition()</code></a></dt>
+
+<dd>
+<p>Description: <a href="../../checksetup.pl.html" class="podlinkpod"
+>checksetup.pl</a> depends on the fielddefs table having its schema adjusted before the rest of the tables. So these schema updates happen in a separate function from <a href="#update_table_definitions()" class="podlinkpod"
+>&#34;update_table_definitions()&#34;</a>.</p>
+
+<p>Params: none</p>
+
+<p>Returns: nothing</p>
+</dd>
+</dl>
+<p class="backlinkbottom"><b><a name="___bottom" href="../../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/Install/Filesystem.html b/docs/en/html/api/Bugzilla/Install/Filesystem.html
new file mode 100644
index 000000000..86e713007
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/Install/Filesystem.html
@@ -0,0 +1,88 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::Install::Filesystem</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href="../.././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::Install::Filesystem</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#SUBROUTINES'>SUBROUTINES</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::Install::Filesystem - Fix up the filesystem during installation.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>This module is used primarily by <a href="../../checksetup.pl.html" class="podlinkpod"
+>checksetup.pl</a> to modify the filesystem during installation,
+including creating the data/ directory.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SUBROUTINES"
+>SUBROUTINES</a></h1>
+
+<dl>
+<dt><a name="update_filesystem({_index_html_=_0_})&#62;"
+><code class="code">update_filesystem({ index_html =</code> 0 })&#62;</a></dt>
+
+<dd>
+<p>Description: Creates all the directories and files that Bugzilla needs to function but doesn&#39;t ship with.
+Also does any updates to these files as necessary during an upgrade.</p>
+
+<p>Params: <code class="code">index_html</code> - Whether or not we should create the <em class="code">index.html</em> file.</p>
+
+<p>Returns: nothing</p>
+
+<dt><a name="create_htaccess()"
+><code class="code">create_htaccess()</code></a></dt>
+
+<dd>
+<p>Description: Creates all of the .htaccess files for Apache,
+in the various Bugzilla directories.
+Also updates the .htaccess files if they need updating.</p>
+
+<p>Params: none</p>
+
+<p>Returns: nothing</p>
+
+<dt><a name="fix_all_file_permissions($output)"
+><code class="code">fix_all_file_permissions($output)</code></a></dt>
+
+<dd>
+<p>Description: Sets all the file permissions on all of Bugzilla&#39;s files to what they should be.
+Note that permissions are different depending on whether or not <code class="code">$webservergroup</code> is set in <em class="code">localconfig</em>.</p>
+
+<p>Params: <code class="code">$output</code> - <code class="code">true</code> if you want this function to print out information about what it&#39;s doing.</p>
+
+<p>Returns: nothing</p>
+
+<dt><a name="fix_file_permissions"
+><code class="code">fix_file_permissions</code></a></dt>
+
+<dd>
+<p>Given the name of a file,
+its permissions will be fixed according to how they are supposed to be set in Bugzilla&#39;s current configuration.
+If it fails to set the permissions,
+a warning will be printed to STDERR.</p>
+</dd>
+</dl>
+<p class="backlinkbottom"><b><a name="___bottom" href="../../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/Install/Localconfig.html b/docs/en/html/api/Bugzilla/Install/Localconfig.html
new file mode 100644
index 000000000..f8f61c8f6
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/Install/Localconfig.html
@@ -0,0 +1,124 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::Install::Localconfig</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href="../.././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1></h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#CONSTANTS'>CONSTANTS</a>
+ <li class='indexItem indexItem1'><a href='#SUBROUTINES'>SUBROUTINES</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::Install::Localconfig - Functions and variables dealing with the manipulation and creation of the <em class="code">localconfig</em> file.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<pre class="code"> use Bugzilla::Install::Requirements qw(update_localconfig);
+ update_localconfig({ output =&#62; 1 });</pre>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>This module is used primarily by <a href="../../checksetup.pl.html" class="podlinkpod"
+>checksetup.pl</a> to create and modify the localconfig file. Most scripts should use <a href="../../Bugzilla.html#localconfig" class="podlinkpod"
+>&#34;localconfig&#34; in Bugzilla</a> to access localconfig variables.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="CONSTANTS"
+>CONSTANTS</a></h1>
+
+<dl>
+<dt><a name="LOCALCONFIG_VARS"
+><code class="code">LOCALCONFIG_VARS</code></a></dt>
+
+<dd>
+<p>An array of hashrefs. These hashrefs contain three keys:</p>
+
+<pre class="code"> name - The name of the variable.
+ default - The default value for the variable. Should always be
+ something that can fit in a scalar.
+ desc - Additional text to put in localconfig before the variable
+ definition. Must end in a newline. Each line should start
+ with &#34;#&#34; unless you have some REALLY good reason not
+ to do that.</pre>
+
+<dt><a name="OLD_LOCALCONFIG_VARS"
+><code class="code">OLD_LOCALCONFIG_VARS</code></a></dt>
+
+<dd>
+<p>An array of names of variables. If <code class="code">update_localconfig</code> finds these variables defined in localconfig, it will print out a warning.</p>
+</dd>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SUBROUTINES"
+>SUBROUTINES</a></h1>
+
+<dl>
+<dt><a name="read_localconfig"
+><code class="code">read_localconfig</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Reads the localconfig file and returns all valid values in a hashref.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="$include_deprecated"
+><code class="code">$include_deprecated</code></a></dt>
+
+<dd>
+<p><code class="code">true</code> if you want the returned hashref to include *any* variable currently defined in localconfig, even if it doesn&#39;t exist in <code class="code">LOCALCONFIG_VARS</code>. Generally this is is only for use by <a href="#update_localconfig" class="podlinkpod"
+>&#34;update_localconfig&#34;</a>.</p>
+</dd>
+</dl>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>A hashref of the localconfig variables. If an array is defined in localconfig, it will be an arrayref in the returned hash. If a hash is defined, it will be a hashref in the returned hash. Only includes variables specified in <code class="code">LOCALCONFIG_VARS</code>, unless <code class="code">$include_deprecated</code> is true.</p>
+</dd>
+</dl>
+
+<dt><a name="update_localconfig"
+><code class="code">update_localconfig</code></a></dt>
+
+<dd>
+<p>Description: Adds any new variables to localconfig that aren&#39;t currently defined there. Also optionally prints out a message about vars that *should* be there and aren&#39;t. Exits the program if it adds any new vars.</p>
+
+<p>Params: <code class="code">$output</code> - <code class="code">true</code> if the function should display informational output and warnings. It will always display errors or any message which would cause program execution to halt.</p>
+
+<p>Returns: A hashref, with <code class="code">old_vals</code> being an array of names of variables that were removed, and <code class="code">new_vals</code> being an array of names of variables that were added to localconfig.</p>
+</dd>
+</dl>
+<p class="backlinkbottom"><b><a name="___bottom" href="../../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/Install/Requirements.html b/docs/en/html/api/Bugzilla/Install/Requirements.html
new file mode 100644
index 000000000..219ee2bb8
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/Install/Requirements.html
@@ -0,0 +1,201 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::Install::Requirements</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href="../.././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::Install::Requirements</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#CONSTANTS'>CONSTANTS</a>
+ <li class='indexItem indexItem1'><a href='#SUBROUTINES'>SUBROUTINES</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::Install::Requirements - Functions and variables dealing with Bugzilla&#39;s perl-module requirements.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>This module is used primarily by <code class="code">checksetup.pl</code> to determine whether or not all of Bugzilla&#39;s prerequisites are installed.
+(That is,
+all the perl modules it requires.)</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="CONSTANTS"
+>CONSTANTS</a></h1>
+
+<dl>
+<dt><a name="REQUIRED_MODULES"
+><code class="code">REQUIRED_MODULES</code></a></dt>
+
+<dd>
+<p>An arrayref of hashrefs that describes the perl modules required by Bugzilla.
+The hashes have three keys:</p>
+
+<dl>
+<dt><a name="package_-_The_name_of_the_Perl_package_that_you&#39;d_find_on_CPAN_for_this_requirement."
+><code class="code">package</code> - The name of the Perl package that you&#39;d find on CPAN for this requirement.</a></dt>
+
+<dd>
+<dt><a name="module_-_The_name_of_a_module_that_can_be_passed_to_the_install_command_in_CPAN.pm_to_install_this_module."
+><code class="code">module</code> - The name of a module that can be passed to the <code class="code">install</code> command in <code class="code">CPAN.pm</code> to install this module.</a></dt>
+
+<dd>
+<dt><a name="version_-_The_version_of_this_module_that_we_require,_or_0_if_any_version_is_acceptable."
+><code class="code">version</code> - The version of this module that we require,
+or <code class="code">0</code> if any version is acceptable.</a></dt>
+</dl>
+
+<dt><a name="OPTIONAL_MODULES"
+><code class="code">OPTIONAL_MODULES</code></a></dt>
+
+<dd>
+<p>An arrayref of hashrefs that describes the perl modules that add additional features to Bugzilla if installed.
+Its hashes have all the fields of <a href="#REQUIRED_MODULES" class="podlinkpod"
+>&#34;REQUIRED_MODULES&#34;</a>,
+plus a <code class="code">feature</code> item--an arrayref of strings that describe what features require this module.</p>
+
+<dt><a name="FEATURE_FILES"
+><code class="code">FEATURE_FILES</code></a></dt>
+
+<dd>
+<p>A hashref that describes what files should only be compiled if a certain feature is enabled.
+The feature is the key,
+and the values are arrayrefs of file names (which are passed to <code class="code">glob</code>,
+so shell patterns work).</p>
+</dd>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SUBROUTINES"
+>SUBROUTINES</a></h1>
+
+<dl>
+<dt><a name="check_requirements"
+><code class="code">check_requirements</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>This checks what optional or required perl modules are installed,
+like <code class="code">checksetup.pl</code> does.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a
+><code class="code">$output</code> - <code class="code">true</code> if you want the function to print out information about what it&#39;s doing,
+and the versions of everything installed.</a></dt>
+</dl>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>A hashref containing these values:</p>
+
+<dl>
+<dt><a name="pass_-_Whether_or_not_we_have_all_the_mandatory_requirements."
+><code class="code">pass</code> - Whether or not we have all the mandatory requirements.</a></dt>
+
+<dd>
+<dt><a
+><code class="code">missing</code> - An arrayref containing any required modules that are not installed or that are not up-to-date.
+Each item in the array is a hashref in the format of items from <a href="#REQUIRED_MODULES" class="podlinkpod"
+>&#34;REQUIRED_MODULES&#34;</a>.</a></dt>
+
+<dd>
+<dt><a name="optional_-_The_same_as_missing,_but_for_optional_modules."
+><code class="code">optional</code> - The same as <code class="code">missing</code>,
+but for optional modules.</a></dt>
+
+<dd>
+<dt><a name="apache_-_The_name_of_each_optional_Apache_module_that_is_missing."
+><code class="code">apache</code> - The name of each optional Apache module that is missing.</a></dt>
+
+<dd>
+<dt><a name="have_one_dbd_-_True_if_at_least_one_DBD::_module_is_installed."
+><code class="code">have_one_dbd</code> - True if at least one <code class="code">DBD::</code> module is installed.</a></dt>
+
+<dd>
+<dt><a name="any_missing_-_True_if_there_are_any_missing_Perl_modules,_even_optional_modules."
+><code class="code">any_missing</code> - True if there are any missing Perl modules,
+even optional modules.</a></dt>
+</dl>
+</dd>
+</dl>
+
+<dt><a name="check_graphviz($output)"
+><code class="code">check_graphviz($output)</code></a></dt>
+
+<dd>
+<p>Description: Checks if the graphviz binary specified in the <code class="code">webdotbase</code> parameter is a valid binary,
+or a valid URL.</p>
+
+<p>Params: <code class="code">$output</code> - <code class="code">$true</code> if you want the function to print out information about what it&#39;s doing.</p>
+
+<p>Returns: <code class="code">1</code> if the check was successful,
+<code class="code">0</code> otherwise.</p>
+
+<dt><a name="have_vers($module,_$output)"
+><code class="code">have_vers($module,
+$output)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Tells you whether or not you have the appropriate
+ version of the module requested. It also prints
+ out a message to the user explaining the check
+ and the result.
+
+ Params: C&#60;$module&#62; - A hashref, in the format of an item from
+ L&#60;/REQUIRED_MODULES&#62;.
+ C&#60;$output&#62; - Set to true if you want this function to
+ print information to STDOUT about what it&#39;s
+ doing.
+
+ Returns: C&#60;1&#62; if you have the module installed and you have the
+ appropriate version. C&#60;0&#62; otherwise.</pre>
+
+<dt><a name="install_command($module)"
+><code class="code">install_command($module)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Prints out the appropriate command to install the
+ module specified, depending on whether you&#39;re
+ on Windows or Linux.
+
+ Params: C&#60;$module&#62; - A hashref, in the format of an item from
+ L&#60;/REQUIRED_MODULES&#62;.
+
+ Returns: nothing</pre>
+
+<dt><a name="map_files_to_features"
+><code class="code">map_files_to_features</code></a></dt>
+
+<dd>
+<p>Returns a hashref where file names are the keys and the value is the feature that must be enabled in order to compile that file.</p>
+</dd>
+</dl>
+<p class="backlinkbottom"><b><a name="___bottom" href="../../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/Install/Util.html b/docs/en/html/api/Bugzilla/Install/Util.html
new file mode 100644
index 000000000..1b8afe3eb
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/Install/Util.html
@@ -0,0 +1,259 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::Install::Util</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href="../.././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::Install::Util</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#SUBROUTINES'>SUBROUTINES</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::Install::Util - Utility functions that are useful both during installation and afterwards.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>This module contains various subroutines that are used primarily during installation.
+However,
+these subroutines can also be useful to non-installation code,
+so they have been split out into this module.</p>
+
+<p>The difference between this module and <a href="../../Bugzilla/Util.html" class="podlinkpod"
+>Bugzilla::Util</a> is that this module is safe to <code class="code">use</code> anywhere in Bugzilla,
+even during installation,
+because it depends only on <a href="../../Bugzilla/Constants.html" class="podlinkpod"
+>Bugzilla::Constants</a> and built-in perl modules.</p>
+
+<p>None of the subroutines are exported by default--you must explicitly export them.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SUBROUTINES"
+>SUBROUTINES</a></h1>
+
+<dl>
+<dt><a name="bin_loc"
+><code class="code">bin_loc</code></a></dt>
+
+<dd>
+<p>On *nix systems,
+given the name of a binary,
+returns the path to that binary,
+if the binary is in the <code class="code">PATH</code>.</p>
+
+<dt><a name="get_version_and_os"
+><code class="code">get_version_and_os</code></a></dt>
+
+<dd>
+<p>Returns a hash containing information about what version of Bugzilla we&#39;re running,
+what perl version we&#39;re using,
+and what OS we&#39;re running on.</p>
+
+<dt><a name="get_console_locale"
+><code class="code">get_console_locale</code></a></dt>
+
+<dd>
+<p>Returns the language to use based on the LC_CTYPE value returned by the OS.
+If LC_CTYPE is of the form fr-CH,
+then fr is appended to the list.</p>
+
+<dt><a name="init_console"
+><code class="code">init_console</code></a></dt>
+
+<dd>
+<p>Sets the <code class="code">ANSI_COLORS_DISABLED</code> and <code class="code">HTTP_ACCEPT_LANGUAGE</code> environment variables.</p>
+
+<dt><a name="indicate_progress"
+><code class="code">indicate_progress</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>This prints out lines of dots as a long update is going on,
+to let the user know where we are and that we&#39;re not frozen.
+A new line of dots will start every 60 dots.</p>
+
+<p>Sample usage: <code class="code">indicate_progress({ total =&#62; $total,
+current =&#62; $count,
+every =&#62; 1 })</code></p>
+
+<dt><a name="Sample_Output"
+><b>Sample Output</b></a></dt>
+
+<dd>
+<p>Here&#39;s some sample output with <code class="code">total = 1000</code> and <code class="code">every = 10</code>:</p>
+
+<pre class="code"> ............................................................600/1000 (60%)
+ ........................................</pre>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="total_-_The_total_number_of_items_we&#39;re_processing."
+><code class="code">total</code> - The total number of items we&#39;re processing.</a></dt>
+
+<dd>
+<dt><a name="current_-_The_number_of_the_current_item_we&#39;re_processing."
+><code class="code">current</code> - The number of the current item we&#39;re processing.</a></dt>
+
+<dd>
+<dt><a
+><code class="code">every</code> - How often the function should print out a dot. For example, if this is 10, the function will print out a dot every ten items. Defaults to 1 if not specified.</a></dt>
+</dl>
+
+<dt><a name="Returns:_nothing"
+><b>Returns</b>: nothing</a></dt>
+</dl>
+
+<dt><a name="install_string"
+><code class="code">install_string</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>This is a very simple method of templating strings for installation. It should only be used by code that has to run before the Template Toolkit can be used. (See the comments at the top of the various <a href="../../Bugzilla/Install.html" class="podlinkpod"
+>Bugzilla::Install</a> modules to find out when it&#39;s safe to use Template Toolkit.)</p>
+
+<p>It pulls strings out of the <em class="code">strings.txt.pl</em> &#34;template&#34; and replaces any variable surrounded by double-hashes (##) with a value you specify.</p>
+
+<p>This allows for localization of strings used during installation.</p>
+
+<dt><a name="Example"
+><b>Example</b></a></dt>
+
+<dd>
+<p>Let&#39;s say your template string looks like this:</p>
+
+<pre class="code"> The ##animal## jumped over the ##plant##.</pre>
+
+<p>Let&#39;s say that string is called &#39;animal_jump_plant&#39;. So you call the function like this:</p>
+
+<pre class="code"> install_string(&#39;animal_jump_plant&#39;, { animal =&#62; &#39;fox&#39;, plant =&#62; &#39;tree&#39; });</pre>
+
+<p>That will output this:</p>
+
+<pre class="code"> The fox jumped over the tree.</pre>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="$string_id_-_The_name_of_the_string_from_strings.txt.pl."
+><code class="code">$string_id</code> - The name of the string from <em class="code">strings.txt.pl</em>.</a></dt>
+
+<dd>
+<dt><a name="$vars_-_A_hashref_containing_the_replacement_values_for_variables_inside_of_the_string."
+><code class="code">$vars</code> - A hashref containing the replacement values for variables inside of the string.</a></dt>
+</dl>
+
+<dt><a name="Returns:_The_appropriate_string,_with_variables_replaced."
+><b>Returns</b>: The appropriate string, with variables replaced.</a></dt>
+</dl>
+
+<dt><a name="template_include_path"
+><code class="code">template_include_path</code></a></dt>
+
+<dd>
+<p>Used by <a href="../../Bugzilla/Template.html" class="podlinkpod"
+>Bugzilla::Template</a> and <a href="#install_string" class="podlinkpod"
+>&#34;install_string&#34;</a> to determine the directories where templates are installed. Templates can be installed in many places. They&#39;re listed here in the basic order that they&#39;re searched:</p>
+
+<dl>
+<dt><a name="extensions/$extension/template/$language/$project"
+>extensions/<code class="code">$extension</code>/template/<code class="code">$language</code>/<code class="code">$project</code></a></dt>
+
+<dd>
+<dt><a name="extensions/$extension/template/$language/custom"
+>extensions/<code class="code">$extension</code>/template/<code class="code">$language</code>/custom</a></dt>
+
+<dd>
+<dt><a name="extensions/$extension/template/$language/default"
+>extensions/<code class="code">$extension</code>/template/<code class="code">$language</code>/default</a></dt>
+
+<dd>
+<dt><a name="template/$language/$project"
+>template/<code class="code">$language</code>/<code class="code">$project</code></a></dt>
+
+<dd>
+<dt><a name="template/$language/custom"
+>template/<code class="code">$language</code>/custom</a></dt>
+
+<dd>
+<dt><a name="template/$language/default"
+>template/<code class="code">$language</code>/default</a></dt>
+</dl>
+
+<p><code class="code">$project</code> has to do with installations that are using the <code class="code">$ENV{PROJECT}</code> variable to have different &#34;views&#34; on a single Bugzilla.</p>
+
+<p>The <em class="code">default</em> directory includes templates shipped with Bugzilla.</p>
+
+<p>The <em class="code">custom</em> directory is a directory for local installations to override the <em class="code">default</em> templates. Any individual template in <em class="code">custom</em> will override a template of the same name and path in <em class="code">default</em>.</p>
+
+<p><code class="code">$language</code> is a language code, <code class="code">en</code> being the default language shipped with Bugzilla. Localizers ship other languages.</p>
+
+<p><code class="code">$extension</code> is the name of any directory in the <em class="code">extensions/</em> directory. Each extension has its own directory.</p>
+
+<p>Note that languages are sorted by the user&#39;s preference (as specified in their browser, usually), and extensions are sorted alphabetically.</p>
+
+<dt><a name="include_languages"
+><code class="code">include_languages</code></a></dt>
+
+<dd>
+<p>Used by <a href="../../Bugzilla/Template.html" class="podlinkpod"
+>Bugzilla::Template</a> to determine the languages&#39; list which are compiled with the browser&#39;s <i>Accept-Language</i> and the languages of installed templates.</p>
+
+<dt><a name="vers_cmp"
+><code class="code">vers_cmp</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>This is a comparison function, like you would use in <code class="code">sort</code>, except that it compares two version numbers. So, for example, 2.10 would be greater than 2.2.</p>
+
+<p>It&#39;s based on versioncmp from <a href="../../Sort/Versions.html" class="podlinkpod"
+>Sort::Versions</a>, with some Bugzilla-specific fixes.</p>
+
+<dt><a name="Params:_$a_and_$b_-_The_versions_you_want_to_compare."
+><b>Params</b>: <code class="code">$a</code> and <code class="code">$b</code> - The versions you want to compare.</a></dt>
+
+<dd>
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p><code class="code">-1</code> if <code class="code">$a</code> is less than <code class="code">$b</code>, <code class="code">0</code> if they are equal, or <code class="code">1</code> if <code class="code">$a</code> is greater than <code class="code">$b</code>.</p>
+</dd>
+</dl>
+</dd>
+</dl>
+<p class="backlinkbottom"><b><a name="___bottom" href="../../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/JobQueue.html b/docs/en/html/api/Bugzilla/JobQueue.html
new file mode 100644
index 000000000..1654d4fec
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/JobQueue.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::JobQueue</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href=".././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::JobQueue</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#Inserting_a_Job'>Inserting a Job</a>
+ </ul>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::JobQueue - Interface between Bugzilla and TheSchwartz.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<pre class="code"> use Bugzilla;
+
+ my $obj = Bugzilla-&#62;job_queue();
+ $obj-&#62;insert(&#39;send_mail&#39;, { msg =&#62; $message });</pre>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>Certain tasks should be done asyncronously. The job queue system allows Bugzilla to use some sort of service to schedule jobs to happen asyncronously.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Inserting_a_Job"
+>Inserting a Job</a></h2>
+
+<p>See the synopsis above for an easy to follow example on how to insert a job into the queue. Give it a name and some arguments and the job will be sent away to be done later.</p>
+<p class="backlinkbottom"><b><a name="___bottom" href="../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/JobQueue/Runner.html b/docs/en/html/api/Bugzilla/JobQueue/Runner.html
new file mode 100644
index 000000000..4d5dfd239
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/JobQueue/Runner.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::JobQueue::Runner</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href="../.././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::JobQueue::Runner</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::JobQueue::Runner - A class representing the daemon that runs the job queue.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<pre class="code"> use Bugzilla::JobQueue::Runner;
+ Bugzilla::JobQueue::Runner-&#62;new();</pre>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>This is a subclass of <a href="../../Daemon/Generic.html" class="podlinkpod"
+>Daemon::Generic</a> that is used by <a href="../../jobqueue.html" class="podlinkpod"
+>jobqueue</a> to run the Bugzilla job queue.</p>
+<p class="backlinkbottom"><b><a name="___bottom" href="../../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/Keyword.html b/docs/en/html/api/Bugzilla/Keyword.html
new file mode 100644
index 000000000..20e4e8a4a
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/Keyword.html
@@ -0,0 +1,72 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::Keyword</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href=".././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::Keyword</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#SUBROUTINES'>SUBROUTINES</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::Keyword - A Keyword that can be added to a bug.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<pre class="code"> use Bugzilla::Keyword;
+
+ my $description = $keyword-&#62;description;
+
+ my $keywords = Bugzilla::Keyword-&#62;get_all_with_bug_count();</pre>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>Bugzilla::Keyword represents a keyword that can be added to a bug.</p>
+
+<p>This implements all standard <code class="code">Bugzilla::Object</code> methods. See <a href="../Bugzilla/Object.html" class="podlinkpod"
+>Bugzilla::Object</a> for more details.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SUBROUTINES"
+>SUBROUTINES</a></h1>
+
+<p>This is only a list of subroutines specific to <code class="code">Bugzilla::Keyword</code>. See <a href="../Bugzilla/Object.html" class="podlinkpod"
+>Bugzilla::Object</a> for more subroutines that this object implements.</p>
+
+<dl>
+<dt><a name="get_all_with_bug_count()"
+><code class="code">get_all_with_bug_count()</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Returns all defined keywords. This is an efficient way
+ to get the associated bug counts, as only one SQL query
+ is executed with this method, instead of one per keyword
+ when calling get_all and then bug_count.
+ Params: none
+ Returns: A reference to an array of Keyword objects, or an empty
+ arrayref if there are no keywords.</pre>
+</dd>
+</dl>
+<p class="backlinkbottom"><b><a name="___bottom" href="../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/Migrate.html b/docs/en/html/api/Bugzilla/Migrate.html
new file mode 100644
index 000000000..5c81eba64
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/Migrate.html
@@ -0,0 +1,378 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::Migrate</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href=".././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::Migrate</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#HOW_MIGRATION_WORKS'>HOW MIGRATION WORKS</a>
+ <li class='indexItem indexItem1'><a href='#CONSTRUCTOR'>CONSTRUCTOR</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#load'>load</a>
+ </ul>
+ <li class='indexItem indexItem1'><a href='#METHODS_YOUR_SUBCLASS_CAN_USE'>METHODS YOUR SUBCLASS CAN USE</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#config'>config</a>
+ <li class='indexItem indexItem2'><a href='#debug'>debug</a>
+ <li class='indexItem indexItem2'><a href='#parse_date'>parse_date</a>
+ <li class='indexItem indexItem2'><a href='#translate_bug'>translate_bug</a>
+ <li class='indexItem indexItem2'><a href='#translate_value'>translate_value</a>
+ <li class='indexItem indexItem2'><a href='#translate_field'>translate_field</a>
+ </ul>
+ <li class='indexItem indexItem1'><a href='#METHODS_YOU_MUST_IMPLEMENT'>METHODS YOU MUST IMPLEMENT</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#_read_bugs'>_read_bugs</a>
+ <li class='indexItem indexItem2'><a href='#_read_products'>_read_products</a>
+ <li class='indexItem indexItem2'><a href='#_read_users'>_read_users</a>
+ </ul>
+ <li class='indexItem indexItem1'><a href='#METHODS_YOU_MIGHT_WANT_TO_IMPLEMENT'>METHODS YOU MIGHT WANT TO IMPLEMENT</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#REQUIRED_MODULES'>REQUIRED_MODULES</a>
+ <li class='indexItem indexItem2'><a href='#CUSTOM_FIELDS'>CUSTOM_FIELDS</a>
+ <li class='indexItem indexItem2'><a href='#CONFIG_VARS'>CONFIG_VARS</a>
+ <li class='indexItem indexItem2'><a href='#NON_COMMENT_FIELDS'>NON_COMMENT_FIELDS</a>
+ <li class='indexItem indexItem2'><a href='#after_read'>after_read</a>
+ <li class='indexItem indexItem2'><a href='#before_insert'>before_insert</a>
+ <li class='indexItem indexItem2'><a href='#after_insert'>after_insert</a>
+ </ul>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::Migrate - Functions to migrate from other databases</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>This module acts as a base class for the various modules that migrate from other bug-trackers.</p>
+
+<p>The documentation for this module exists mostly to assist people in creating new migrators for other bug-trackers than the ones currently supported.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="HOW_MIGRATION_WORKS"
+>HOW MIGRATION WORKS</a></h1>
+
+<p>Before writing anything to the Bugzilla database,
+the migrator will read everything from the other bug-tracker&#39;s database.
+Here&#39;s the exact order of what happens:</p>
+
+<ol>
+<li>Users are read from the other bug-tracker.</li>
+
+<li>Products are read from the other bug-tracker.</li>
+
+<li>Bugs are read from the other bug-tracker.</li>
+
+<li>The <a href="#after_read" class="podlinkpod"
+>&#34;after_read&#34;</a> method is called.</li>
+
+<li>All bugs are translated from the other bug-tracker&#39;s fields/values into Bugzilla&#39;s fields values using <a href="#translate_bug" class="podlinkpod"
+>&#34;translate_bug&#34;</a>.</li>
+
+<li>Users are inserted into Bugzilla.</li>
+
+<li>Products are inserted into Bugzilla.</li>
+
+<li>Some migrators need to create custom fields before migrating,
+and so that happens here.</li>
+
+<li>Any legal values that need to be created for any drop-down or multi-select fields are created.
+This is done by reading all the values on every bug that was read in and creating any values that don&#39;t already exist in Bugzilla for every drop-down or multi-select field on each bug.
+This includes creating any product versions and milestones that need to be created.</li>
+
+<li>Bugs are inserted into Bugzilla.</li>
+
+<li>The <a href="#after_insert" class="podlinkpod"
+>&#34;after_insert&#34;</a> method is called.</li>
+</ol>
+
+<p>Everything happens in one big transaction,
+so in general,
+if there are any errors during the process,
+nothing will be changed.</p>
+
+<p>The migrator never creates anything that already exists.
+So users,
+products,
+components,
+etc.
+that already exist will just be re-used by this script,
+not re-created.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="CONSTRUCTOR"
+>CONSTRUCTOR</a></h1>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="load"
+>load</a></h2>
+
+<p>Called like <code class="code">Bugzilla::Migrate-&#62;load(&#39;Module&#39;)</code>.
+Returns a new <code class="code">Bugzilla::Migrate</code> object that can be used to migrate from the requested bug-tracker.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="METHODS_YOUR_SUBCLASS_CAN_USE"
+>METHODS YOUR SUBCLASS CAN USE</a></h1>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="config"
+>config</a></h2>
+
+<p>Takes a single parameter,
+a string,
+and returns the value of the configuration variable with that name (always a scalar).
+The first time you call <code class="code">config</code>,
+if the configuration file hasn&#39;t been read,
+it will be read in.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="debug"
+>debug</a></h2>
+
+<p>If the user hasn&#39;t specified <code class="code">--verbose</code> on the command line,
+this does nothing.</p>
+
+<p>Takes two arguments:</p>
+
+<p>The first argument is a string or reference to print to <code class="code">STDERR</code>.
+If it&#39;s a reference,
+<a href="../Data/Dumper.html" class="podlinkpod"
+>Data::Dumper</a> will be used to print the data structure.</p>
+
+<p>The second argument is a number--the string will only be printed if the user specified <code class="code">--verbose</code> at least that many times on the command line.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="parse_date"
+>parse_date</a></h2>
+
+<p>(Note: Usually you don&#39;t need to call this,
+because <a href="#translate_bug" class="podlinkpod"
+>&#34;translate_bug&#34;</a> handles date translations for you,
+for bug data.)</p>
+
+<p>Parses a date string and returns a formatted date string that can be inserted into the database.
+If the input date is missing a timezone,
+the &#34;timezone&#34; configuration parameter will be used as the timezone of the date.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="translate_bug"
+>translate_bug</a></h2>
+
+<p>(Note: Normally you don&#39;t have to call this yourself,
+as <code class="code">Bugzilla::Migrate</code> does it for you.)</p>
+
+<p>Uses the <code class="code">$translate_fields</code> and <code class="code">$translate_values</code> configuration variables to convert a hashref of &#34;other bug-tracker&#34; fields into Bugzilla fields.
+It takes one argument,
+the hashref to convert.
+Any unrecognized fields will have their value prepended to the <code class="code">comment</code> element in the returned hashref,
+unless they are listed in <a href="#NON_COMMENT_FIELDS" class="podlinkpod"
+>&#34;NON_COMMENT_FIELDS&#34;</a>.</p>
+
+<p>In scalar context,
+returns the translated bug.
+In array context,
+returns both the translated bug and a second hashref containing the values of any untranslated fields that were listed in <code class="code">NON_COMMENT_FIELDS</code>.</p>
+
+<p><b>Note:</b> To save memory,
+the hashref that you pass in will be destroyed (all keys will be deleted).</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="translate_value"
+>translate_value</a></h2>
+
+<p>(Note: Generally you only need to use this during <a href="#_read_products" class="podlinkpod"
+>&#34;_read_products&#34;</a> and <a href="#_read_users" class="podlinkpod"
+>&#34;_read_users&#34;</a> if necessary,
+because the data returned from <a href="#_read_bugs" class="podlinkpod"
+>&#34;_read_bugs&#34;</a> will be put through <a href="#translate_bug" class="podlinkpod"
+>&#34;translate_bug&#34;</a>.)</p>
+
+<p>Uses the <code class="code">$translate_values</code> configuration variable to convert field values from your bug-tracker to Bugzilla.
+Takes two arguments,
+the first being a field name and the second being a value.
+If the value is an arrayref,
+<code class="code">translate_value</code> will be called recursively on all the array elements.</p>
+
+<p>Also,
+any date field will be converted into ISO 8601 format,
+for inserting into the database.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="translate_field"
+>translate_field</a></h2>
+
+<p>(Note: Normally you don&#39;t need to use this,
+because <a href="#translate_bug" class="podlinkpod"
+>&#34;translate_bug&#34;</a> handles it for you.)</p>
+
+<p>Translates a field name in your bug-tracker to a field name in Bugzilla,
+using the rules described in the description of the <code class="code">$translate_fields</code> configuration variable.</p>
+
+<p>Takes a single argument--the name of a field to translate.</p>
+
+<p>Returns <code class="code">undef</code> if the field could not be translated.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="METHODS_YOU_MUST_IMPLEMENT"
+>METHODS YOU MUST IMPLEMENT</a></h1>
+
+<p>These are methods that subclasses must implement:</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="_read_bugs"
+>_read_bugs</a></h2>
+
+<p>Should return an arrayref of hashes.
+The hashes will be passed to <a href="../Bugzilla/Bug.html#create" class="podlinkpod"
+>&#34;create&#34; in Bugzilla::Bug</a> to create bugs in Bugzilla.
+In addition to the normal <code class="code">create</code> fields,
+the hashes can contain three additional items:</p>
+
+<dl>
+<dt><a name="comments"
+>comments</a></dt>
+
+<dd>
+<p>An arrayref of hashes,
+representing comments to be added to the database.
+The keys should be the names of columns in the longdescs table that you want to set for each comment.
+<code class="code">who</code> must be a username instead of a user id,
+though.</p>
+
+<p>You don&#39;t need to specify a value for the <code class="code">bug_id</code> column.</p>
+
+<dt><a name="history"
+>history</a></dt>
+
+<dd>
+<p>An arrayref of hashes,
+representing the history of changes made to this bug.
+The keys should be the names of columns in the bugs_activity table to set for each change.
+<code class="code">who</code> must be a username instead of a user id,
+though,
+and <code class="code">field</code> (containing the name of some field) is taken instead of <code class="code">fieldid</code>.</p>
+
+<p>You don&#39;t need to specify a value for the <code class="code">bug_id</code> column.</p>
+
+<dt><a name="attachments"
+>attachments</a></dt>
+
+<dd>
+<p>An arrayref of hashes,
+representing values to pass to <a href="../Bugzilla/Attachment.html#create" class="podlinkpod"
+>&#34;create&#34; in Bugzilla::Attachment</a>.
+(Remember that the <code class="code">data</code> argument must be a file handle--we recommend using <a href="../IO/File.html#new_tmpfile" class="podlinkpod"
+>&#34;new_tmpfile&#34; in IO::File</a> to create anonymous temporary files for this purpose.) You should specify a <code class="code">submitter</code> argument containing the username of the attachment&#39;s submitter.</p>
+
+<p>You don&#39;t need to specify a value for the the <code class="code">bug</code> argument.</p>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="_read_products"
+>_read_products</a></h2>
+
+<p>Should return an arrayref of hashes to pass to <a href="../Bugzilla/Product.html#create" class="podlinkpod"
+>&#34;create&#34; in Bugzilla::Product</a>.
+In addition to the normal <code class="code">create</code> fields,
+this also accepts an additional argument,
+<code class="code">components</code>,
+which is an arrayref of hashes to pass to <a href="../Bugzilla/Component.html#create" class="podlinkpod"
+>&#34;create&#34; in Bugzilla::Component</a> (though you do not need to specify the <code class="code">product</code> argument for <a href="../Bugzilla/Component.html#create" class="podlinkpod"
+>&#34;create&#34; in Bugzilla::Component</a>).</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="_read_users"
+>_read_users</a></h2>
+
+<p>Should return an arrayref of hashes to be passed to <a href="../Bugzilla/User.html#create" class="podlinkpod"
+>&#34;create&#34; in Bugzilla::User</a>.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="METHODS_YOU_MIGHT_WANT_TO_IMPLEMENT"
+>METHODS YOU MIGHT WANT TO IMPLEMENT</a></h1>
+
+<p>These are methods that you may want to override in your migrator.
+All of these methods are called on an instantiated <a href="../Bugzilla/Migrate.html" class="podlinkpod"
+>Bugzilla::Migrate</a> object of your subclass by <a href="../Bugzilla/Migrate.html" class="podlinkpod"
+>Bugzilla::Migrate</a> itself.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="REQUIRED_MODULES"
+>REQUIRED_MODULES</a></h2>
+
+<p>Returns an arrayref of Perl modules that must be installed in order for your migrator to run,
+in the same format as <a href="../Bugzilla/Install/Requirements.html#REQUIRED_MODULES" class="podlinkpod"
+>&#34;REQUIRED_MODULES&#34; in Bugzilla::Install::Requirements</a>.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="CUSTOM_FIELDS"
+>CUSTOM_FIELDS</a></h2>
+
+<p>Returns a hashref,
+where the keys are the names of custom fields to create in the database before inserting bugs.
+The values of the hashref are the arguments (other than &#34;name&#34;) that should be passed to Bugzilla::Field-&#62;create() when creating the field.
+(<code class="code">custom =&#62; 1</code> will be specified automatically for you,
+so you don&#39;t need to specify it.)</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="CONFIG_VARS"
+>CONFIG_VARS</a></h2>
+
+<p>This should return an array (not an arrayref) in the same format as <a href="../Bugzilla/Install/Localconfig.html#LOCALCONFIG_VARS" class="podlinkpod"
+>&#34;LOCALCONFIG_VARS&#34; in Bugzilla::Install::Localconfig</a>,
+describing configuration variables for migrating from your bug-tracker.
+You should always include the default <code class="code">CONFIG_VARS</code> (by calling $self-&#62;SUPER::CONFIG_VARS) as part of your return value,
+if you override this method.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="NON_COMMENT_FIELDS"
+>NON_COMMENT_FIELDS</a></h2>
+
+<p>An array (not an arrayref).
+If there are fields that are not translated and yet shouldn&#39;t be added to the initial description of the bug when translating bugs,
+then they should be listed here.
+See <a href="#translate_bug" class="podlinkpod"
+>&#34;translate_bug&#34;</a> for more detail.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="after_read"
+>after_read</a></h2>
+
+<p>This is run after all data is read from the other bug-tracker,
+but before the bug fields/values have been translated,
+and before any data is inserted into Bugzilla.
+The default implementation does nothing.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="before_insert"
+>before_insert</a></h2>
+
+<p>This is called after all bugs are translated from their &#34;other bug-tracker&#34; values to Bugzilla values,
+but before any data is inserted into the database or any custom fields are created.
+The default implementation does nothing.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="after_insert"
+>after_insert</a></h2>
+
+<p>This is run after all data is inserted into Bugzilla.
+The default implementation does nothing.</p>
+<p class="backlinkbottom"><b><a name="___bottom" href="../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/Milestone.html b/docs/en/html/api/Bugzilla/Milestone.html
new file mode 100644
index 000000000..9c652e5d8
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/Milestone.html
@@ -0,0 +1,191 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::Milestone</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href=".././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::Milestone</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#METHODS'>METHODS</a>
+ <li class='indexItem indexItem1'><a href='#CLASS_METHODS'>CLASS METHODS</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::Milestone - Bugzilla product milestone class.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<pre class="code"> use Bugzilla::Milestone;
+
+ my $milestone = new Bugzilla::Milestone({ name =&#62; $name, product =&#62; $product });
+
+ my $name = $milestone-&#62;name;
+ my $product_id = $milestone-&#62;product_id;
+ my $product = $milestone-&#62;product;
+ my $sortkey = $milestone-&#62;sortkey;
+
+ my $milestone = Bugzilla::Milestone-&#62;create(
+ { value =&#62; $name, product =&#62; $product, sortkey =&#62; $sortkey });
+
+ $milestone-&#62;set_name($new_name);
+ $milestone-&#62;set_sortkey($new_sortkey);
+ $milestone-&#62;update();
+
+ $milestone-&#62;remove_from_db;</pre>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>Milestone.pm represents a Product Milestone object.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="METHODS"
+>METHODS</a></h1>
+
+<dl>
+<dt><a name="new({name_=_$name,_product_=&#62;_$product})&#62;"
+><code class="code">new({name =</code> $name, product =&#62; $product})&#62;</a></dt>
+
+<dd>
+<pre class="code"> Description: The constructor is used to load an existing milestone
+ by passing a product object and a milestone name.
+
+ Params: $product - a Bugzilla::Product object.
+ $name - the name of a milestone (string).
+
+ Returns: A Bugzilla::Milestone object.</pre>
+
+<dt><a name="name()"
+><code class="code">name()</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Name (value) of the milestone.
+
+ Params: none.
+
+ Returns: The name of the milestone.</pre>
+
+<dt><a name="product_id()"
+><code class="code">product_id()</code></a></dt>
+
+<dd>
+<pre class="code"> Description: ID of the product the milestone belongs to.
+
+ Params: none.
+
+ Returns: The ID of a product.</pre>
+
+<dt><a name="product()"
+><code class="code">product()</code></a></dt>
+
+<dd>
+<pre class="code"> Description: The product object of the product the milestone belongs to.
+
+ Params: none.
+
+ Returns: A Bugzilla::Product object.</pre>
+
+<dt><a name="sortkey()"
+><code class="code">sortkey()</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Sortkey of the milestone.
+
+ Params: none.
+
+ Returns: The sortkey of the milestone.</pre>
+
+<dt><a name="bug_count()"
+><code class="code">bug_count()</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Returns the total of bugs that belong to the milestone.
+
+ Params: none.
+
+ Returns: Integer with the number of bugs.</pre>
+
+<dt><a name="set_name($new_name)"
+><code class="code">set_name($new_name)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Changes the name of the milestone.
+
+ Params: $new_name - new name of the milestone (string). This name
+ must be unique within the product.
+
+ Returns: Nothing.</pre>
+
+<dt><a name="set_sortkey($new_sortkey)"
+><code class="code">set_sortkey($new_sortkey)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Changes the sortkey of the milestone.
+
+ Params: $new_sortkey - new sortkey of the milestone (signed integer).
+
+ Returns: Nothing.</pre>
+
+<dt><a name="update()"
+><code class="code">update()</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Writes the new name and/or the new sortkey into the DB.
+
+ Params: none.
+
+ Returns: A hashref with changes made to the milestone object.</pre>
+
+<dt><a name="remove_from_db()"
+><code class="code">remove_from_db()</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Deletes the current milestone from the DB. The object itself
+ is not destroyed.
+
+ Params: none.
+
+ Returns: Nothing.</pre>
+</dd>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="CLASS_METHODS"
+>CLASS METHODS</a></h1>
+
+<dl>
+<dt><a name="create({value_=_$value,_product_=&#62;_$product,_sortkey_=&#62;_$sortkey})&#62;"
+><code class="code">create({value =</code> $value, product =&#62; $product, sortkey =&#62; $sortkey})&#62;</a></dt>
+
+<dd>
+<pre class="code"> Description: Create a new milestone for the given product.
+
+ Params: $value - name of the new milestone (string). This name
+ must be unique within the product.
+ $product - a Bugzilla::Product object.
+ $sortkey - the sortkey of the new milestone (signed integer)
+
+ Returns: A Bugzilla::Milestone object.</pre>
+</dd>
+</dl>
+<p class="backlinkbottom"><b><a name="___bottom" href="../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/Object.html b/docs/en/html/api/Bugzilla/Object.html
new file mode 100644
index 000000000..30ee23337
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/Object.html
@@ -0,0 +1,575 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::Object</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href=".././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::Object</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#CONSTANTS'>CONSTANTS</a>
+ <li class='indexItem indexItem1'><a href='#METHODS'>METHODS</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#Constructors'>Constructors</a>
+ <li class='indexItem indexItem2'><a href='#Database_Manipulation'>Database Manipulation</a>
+ <li class='indexItem indexItem2'><a href='#Mutators'>Mutators</a>
+ <li class='indexItem indexItem2'><a href='#Simple_Validators'>Simple Validators</a>
+ </ul>
+ <li class='indexItem indexItem1'><a href='#CLASS_FUNCTIONS'>CLASS FUNCTIONS</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::Object - A base class for objects in Bugzilla.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<pre class="code"> my $object = new Bugzilla::Object(1);
+ my $object = new Bugzilla::Object({name =&#62; &#39;TestProduct&#39;});
+
+ my $id = $object-&#62;id;
+ my $name = $object-&#62;name;</pre>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>Bugzilla::Object is a base class for Bugzilla objects. You never actually create a Bugzilla::Object directly, you only make subclasses of it.</p>
+
+<p>Basically, Bugzilla::Object exists to allow developers to create objects more easily. All you have to do is define <code class="code">DB_TABLE</code>, <code class="code">DB_COLUMNS</code>, and sometimes <code class="code">LIST_ORDER</code> and you have a whole new object.</p>
+
+<p>You should also define accessors for any columns other than <code class="code">name</code> or <code class="code">id</code>.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="CONSTANTS"
+>CONSTANTS</a></h1>
+
+<p>Frequently, these will be the only things you have to define in your subclass in order to have a fully-functioning object. <code class="code">DB_TABLE</code> and <code class="code">DB_COLUMNS</code> are required.</p>
+
+<dl>
+<dt><a name="DB_TABLE"
+><code class="code">DB_TABLE</code></a></dt>
+
+<dd>
+<p>The name of the table that these objects are stored in. For example, for <code class="code">Bugzilla::Keyword</code> this would be <code class="code">keyworddefs</code>.</p>
+
+<dt><a name="DB_COLUMNS"
+><code class="code">DB_COLUMNS</code></a></dt>
+
+<dd>
+<p>The names of the columns that you want to read out of the database and into this object. This should be an array.</p>
+
+<p><i>Note</i>: Though normally you will never need to access this constant&#39;s data directly in your subclass, if you do, you should access it by calling the <code class="code">_get_db_columns</code> method instead of accessing the constant directly. (The only exception to this rule is calling <code class="code">SUPER::DB_COLUMNS</code> from within your own <code class="code">DB_COLUMNS</code> subroutine in a subclass.)</p>
+
+<dt><a name="NAME_FIELD"
+><code class="code">NAME_FIELD</code></a></dt>
+
+<dd>
+<p>The name of the column that should be considered to be the unique &#34;name&#34; of this object. The &#39;name&#39; is a <b>string</b> that uniquely identifies this Object in the database. Defaults to &#39;name&#39;. When you specify <code class="code">{name =</code> $name}&#62; to <code class="code">new()</code>, this is the column that will be matched against in the DB.</p>
+
+<dt><a name="ID_FIELD"
+><code class="code">ID_FIELD</code></a></dt>
+
+<dd>
+<p>The name of the column that represents the unique <b>integer</b> ID of this object in the database. Defaults to &#39;id&#39;.</p>
+
+<dt><a name="LIST_ORDER"
+><code class="code">LIST_ORDER</code></a></dt>
+
+<dd>
+<p>The order that <code class="code">new_from_list</code> and <code class="code">get_all</code> should return objects in. This should be the name of a database column. Defaults to <a href="#NAME_FIELD" class="podlinkpod"
+>&#34;NAME_FIELD&#34;</a>.</p>
+
+<dt><a name="VALIDATORS"
+><code class="code">VALIDATORS</code></a></dt>
+
+<dd>
+<p>A hashref that points to a function that will validate each param to <a href="#create" class="podlinkpod"
+>&#34;create&#34;</a>.</p>
+
+<p>Validators are called both by <a href="#create" class="podlinkpod"
+>&#34;create&#34;</a> and <a href="#set" class="podlinkpod"
+>&#34;set&#34;</a>. When they are called by <a href="#create" class="podlinkpod"
+>&#34;create&#34;</a>, the first argument will be the name of the class (what we normally call <code class="code">$class</code>).</p>
+
+<p>When they are called by <a href="#set" class="podlinkpod"
+>&#34;set&#34;</a>, the first argument will be a reference to the current object (what we normally call <code class="code">$self</code>).</p>
+
+<p>The second argument will be the value passed to <a href="#create" class="podlinkpod"
+>&#34;create&#34;</a> or <a href="#set" class="podlinkpod"
+>&#34;set&#34;</a>for that field.</p>
+
+<p>The third argument will be the name of the field being validated. This may be required by validators which validate several distinct fields.</p>
+
+<p>These functions should call <a href="../Bugzilla/Error.html#ThrowUserError" class="podlinkpod"
+>&#34;ThrowUserError&#34; in Bugzilla::Error</a> if they fail.</p>
+
+<p>The validator must return the validated value.</p>
+
+<dt><a name="UPDATE_VALIDATORS"
+><code class="code">UPDATE_VALIDATORS</code></a></dt>
+
+<dd>
+<p>This is just like <a href="#VALIDATORS" class="podlinkpod"
+>&#34;VALIDATORS&#34;</a>, but these validators are called only when updating an object, not when creating it. Any validator that appears here must not appear in <a href="#VALIDATORS" class="podlinkpod"
+>&#34;VALIDATORS&#34;</a>.</p>
+
+<p><a href="../Bugzilla/Bug.html" class="podlinkpod"
+>Bugzilla::Bug</a> has good examples in its code of when to use this.</p>
+
+<dt><a name="VALIDATOR_DEPENDENCIES"
+><code class="code">VALIDATOR_DEPENDENCIES</code></a></dt>
+
+<dd>
+<p>During <a href="#create" class="podlinkpod"
+>&#34;create&#34;</a> and <a href="#set_all" class="podlinkpod"
+>&#34;set_all&#34;</a>, validators are normally called in a somewhat-random order. If you need one field to be validated and set before another field, this constant is how you do it, by saying that one field &#34;depends&#34; on the value of other fields.</p>
+
+<p>This is a hashref, where the keys are field names and the values are arrayrefs of field names. You specify what fields a field depends on using the arrayrefs. So, for example, to say that a <code class="code">component</code> field depends on the <code class="code">product</code> field being set, you would do:</p>
+
+<pre class="code"> component =&#62; [&#39;product&#39;]</pre>
+
+<dt><a name="UPDATE_COLUMNS"
+><code class="code">UPDATE_COLUMNS</code></a></dt>
+
+<dd>
+<p>A list of columns to update when <a href="#update" class="podlinkpod"
+>&#34;update&#34;</a> is called. If a field can&#39;t be changed, it shouldn&#39;t be listed here. (For example, the <a href="#ID_FIELD" class="podlinkpod"
+>&#34;ID_FIELD&#34;</a> usually can&#39;t be updated.)</p>
+
+<dt><a name="REQUIRED_FIELD_MAP"
+><code class="code">REQUIRED_FIELD_MAP</code></a></dt>
+
+<dd>
+<p>This is a hashref that maps database column names to <a href="#create" class="podlinkpod"
+>&#34;create&#34;</a> argument names. You only need to specify values for fields where the argument passed to <a href="#create" class="podlinkpod"
+>&#34;create&#34;</a> has a different name in the database than it does in the <a href="#create" class="podlinkpod"
+>&#34;create&#34;</a> arguments. (For example, <a href="../Bugzilla/Bug.html#create" class="podlinkpod"
+>&#34;create&#34; in Bugzilla::Bug</a> takes a <code class="code">product</code> argument, but the column name in the <code class="code">bugs</code> table is <code class="code">product_id</code>.)</p>
+
+<dt><a name="EXTRA_REQUIRED_FIELDS"
+><code class="code">EXTRA_REQUIRED_FIELDS</code></a></dt>
+
+<dd>
+<p>Normally, Bugzilla::Object automatically figures out which fields are required for <a href="#create" class="podlinkpod"
+>&#34;create&#34;</a>. It then <i>always</i> runs those fields&#39; validators, even if those fields weren&#39;t passed as arguments to <a href="#create" class="podlinkpod"
+>&#34;create&#34;</a>. That way, any default values or required checks can be done for those fields by the validators.</p>
+
+<p><a href="#create" class="podlinkpod"
+>&#34;create&#34;</a> figures out which fields are required by looking for database columns in the <a href="#DB_TABLE" class="podlinkpod"
+>&#34;DB_TABLE&#34;</a> that are NOT NULL and have no DEFAULT set. However, there are some fields that this check doesn&#39;t work for:</p>
+
+<ul>
+<li>Fields that have database defaults (or are marked NULL in the database) but actually have different defaults specified by validators. (For example, the qa_contact field in the <code class="code">bugs</code> table can be NULL, so it won&#39;t be caught as being required. However, in reality it defaults to the component&#39;s initial_qa_contact.)</li>
+
+<li>Fields that have defaults that should be set by validators, but are actually stored in a table different from <a href="#DB_TABLE" class="podlinkpod"
+>&#34;DB_TABLE&#34;</a> (like the &#34;cc&#34; field for bugs, which defaults to the &#34;initialcc&#34; of the Component, but won&#39;t be caught as a normal required field because it&#39;s in a separate table.)</li>
+</ul>
+
+<p>Any field matching the above criteria needs to have its name listed in this constant. For an example of use, see the code of <a href="../Bugzilla/Bug.html" class="podlinkpod"
+>Bugzilla::Bug</a>.</p>
+
+<dt><a name="NUMERIC_COLUMNS"
+><code class="code">NUMERIC_COLUMNS</code></a></dt>
+
+<dd>
+<p>When <a href="#update" class="podlinkpod"
+>&#34;update&#34;</a> is called, it compares each column in the object to its current value in the database. It only updates columns that have changed.</p>
+
+<p>Any column listed in NUMERIC_COLUMNS is treated as a number, not as a string, during these comparisons.</p>
+
+<dt><a name="DATE_COLUMNS"
+><code class="code">DATE_COLUMNS</code></a></dt>
+
+<dd>
+<p>This is much like <a href="#NUMERIC_COLUMNS" class="podlinkpod"
+>&#34;NUMERIC_COLUMNS&#34;</a>, except that it treats strings as dates when being compared. So, for example, <code class="code">2007-01-01</code> would be equal to <code class="code">2007-01-01 00:00:00</code>.</p>
+</dd>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="METHODS"
+>METHODS</a></h1>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Constructors"
+>Constructors</a></h2>
+
+<dl>
+<dt><a name="new"
+><code class="code">new</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>The constructor is used to load an existing object from the database, by id or by name.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<p>If you pass an integer, the integer is the id of the object, from the database, that we want to read in. (id is defined as the value in the <a href="#ID_FIELD" class="podlinkpod"
+>&#34;ID_FIELD&#34;</a> column).</p>
+
+<p>If you pass in a hashref, you can pass a <code class="code">name</code> key. The value of the <code class="code">name</code> key is the case-insensitive name of the object (from <a href="#NAME_FIELD" class="podlinkpod"
+>&#34;NAME_FIELD&#34;</a>) in the DB. You can also pass in an <code class="code">id</code> key which will be interpreted as the id of the object you want (overriding the <code class="code">name</code> key).</p>
+
+<p><b>Additional Parameters Available for Subclasses</b></p>
+
+<p>If you are a subclass of <code class="code">Bugzilla::Object</code>, you can pass <code class="code">condition</code> and <code class="code">values</code> as hash keys, instead of the above.</p>
+
+<p><code class="code">condition</code> is a set of SQL conditions for the WHERE clause, which contain placeholders.</p>
+
+<p><code class="code">values</code> is a reference to an array. The array contains the values for each placeholder in <code class="code">condition</code>, in order.</p>
+
+<p>This is to allow subclasses to have complex parameters, and then to translate those parameters into <code class="code">condition</code> and <code class="code">values</code> when they call <code class="code">$self-</code>SUPER::new&#62; (which is this function, usually).</p>
+
+<p>If you try to call <code class="code">new</code> outside of a subclass with the <code class="code">condition</code> and <code class="code">values</code> parameters, Bugzilla will throw an error. These parameters are intended <b>only</b> for use by subclasses.</p>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>A fully-initialized object, or <code class="code">undef</code> if there is no object in the database matching the parameters you passed in.</p>
+</dd>
+</dl>
+
+<dt><a name="check"
+><code class="code">check</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Checks if there is an object in the database with the specified name, and throws an error if you specified an empty name, or if there is no object in the database with that name.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<p>The parameters are the same as for <a href="#new" class="podlinkpod"
+>&#34;new&#34;</a>, except that if you don&#39;t pass a hashref, the single argument is the <i>name</i> of the object, not the id.</p>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>A fully initialized object, guaranteed.</p>
+
+<dt><a name="Notes_For_Implementors"
+><b>Notes For Implementors</b></a></dt>
+
+<dd>
+<p>If you implement this in your subclass, make sure that you also update the <code class="code">object_name</code> block at the bottom of the <em class="code">global/user-error.html.tmpl</em> template.</p>
+</dd>
+</dl>
+
+<dt><a name="new_from_list(\@id_list)"
+><code class="code">new_from_list(\@id_list)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Creates an array of objects, given an array of ids.
+
+ Params: \@id_list - A reference to an array of numbers, database ids.
+ If any of these are not numeric, the function
+ will throw an error. If any of these are not
+ valid ids in the database, they will simply
+ be skipped.
+
+ Returns: A reference to an array of objects.</pre>
+
+<dt><a name="match"
+><code class="code">match</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Gets a list of objects from the database based on certain criteria.</p>
+
+<p>Basically, a simple way of doing a sort of &#34;SELECT&#34; statement (like SQL) to get objects.</p>
+
+<p>All criteria are joined by <code class="code">AND</code>, so adding more criteria will give you a smaller set of results, not a larger set.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<p>A hashref, where the keys are column names of the table, pointing to the value that you want to match against for that column.</p>
+
+<p>There are two special values, the constants <code class="code">NULL</code> and <code class="code">NOT_NULL</code>, which means &#34;give me objects where this field is NULL or NOT NULL, respectively.&#34;</p>
+
+<p>In addition to the column keys, there are a few special keys that can be used to rig the underlying database queries. These are <code class="code">LIMIT</code>, <code class="code">OFFSET</code>, and <code class="code">WHERE</code>.</p>
+
+<p>The value for the <code class="code">LIMIT</code> key is expected to be an integer defining the number of objects to return, while the value for <code class="code">OFFSET</code> defines the position, relative to the number of objects the query would normally return, at which to begin the result set. If <code class="code">OFFSET</code> is defined without a corresponding <code class="code">LIMIT</code> it is silently ignored.</p>
+
+<p>The <code class="code">WHERE</code> key provides a mechanism for adding arbitrary WHERE clauses to the underlying query. Its value is expected to a hash reference whose keys are the columns, operators and placeholders, and the values are the placeholders&#39; bind value. For example:</p>
+
+<pre class="code"> WHERE =&#62; { &#39;some_column &#62;= ?&#39; =&#62; $some_value }</pre>
+
+<p>would constrain the query to only those objects in the table whose &#39;some_column&#39; column has a value greater than or equal to $some_value.</p>
+
+<p>If you don&#39;t specify any criteria, calling this function is the same as doing <code class="code">[$class-&#62;get_all]</code>.</p>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>An arrayref of objects, or an empty arrayref if there are no matches.</p>
+</dd>
+</dl>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Database_Manipulation"
+>Database Manipulation</a></h2>
+
+<dl>
+<dt><a name="create"
+><code class="code">create</code></a></dt>
+
+<dd>
+<p>Description: Creates a new item in the database. Throws a User Error if any of the passed-in params are invalid.</p>
+
+<p>Params: <code class="code">$params</code> - hashref - A value to put in each database field for this object.</p>
+
+<p>Returns: The Object just created in the database.</p>
+
+<p>Notes: In order for this function to work in your subclass, your subclass&#39;s <a href="#ID_FIELD" class="podlinkpod"
+>&#34;ID_FIELD&#34;</a> must be of <code class="code">SERIAL</code> type in the database.</p>
+
+<pre class="code"> Subclass Implementors: This function basically just
+ calls L&#60;/check_required_create_fields&#62;, then
+ L&#60;/run_create_validators&#62;, and then finally
+ L&#60;/insert_create_data&#62;. So if you have a complex system that
+ you need to implement, you can do it by calling these
+ three functions instead of C&#60;SUPER::create&#62;.</pre>
+
+<dt><a name="check_required_create_fields"
+><code class="code">check_required_create_fields</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Part of <a href="#create" class="podlinkpod"
+>&#34;create&#34;</a>. Modifies the incoming <code class="code">$params</code> argument so that any field that does not have a database default will be checked later by <a href="#run_create_validators" class="podlinkpod"
+>&#34;run_create_validators&#34;</a>, even if that field wasn&#39;t specified as an argument to <a href="#create" class="podlinkpod"
+>&#34;create&#34;</a>.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="$params_-_The_same_as_$params_from_&#34;create&#34;."
+><code class="code">$params</code> - The same as <code class="code">$params</code> from <a href="#create" class="podlinkpod"
+>&#34;create&#34;</a>.</a></dt>
+</dl>
+
+<dt><a name="Returns_(nothing)"
+><b>Returns</b> (nothing)</a></dt>
+</dl>
+
+<dt><a name="run_create_validators"
+><code class="code">run_create_validators</code></a></dt>
+
+<dd>
+<p>Description: Runs the validation of input parameters for <a href="#create" class="podlinkpod"
+>&#34;create&#34;</a>. This subroutine exists so that it can be overridden by subclasses who need to do special validations of their input parameters. This method is <b>only</b> called by <a href="#create" class="podlinkpod"
+>&#34;create&#34;</a>.</p>
+
+<p>Params: The same as <a href="#create" class="podlinkpod"
+>&#34;create&#34;</a>.</p>
+
+<p>Returns: A hash, in a similar format as <code class="code">$params</code>, except that these are the values to be inserted into the database, not the values that were input to <a href="#create" class="podlinkpod"
+>&#34;create&#34;</a>.</p>
+
+<dt><a name="insert_create_data"
+><code class="code">insert_create_data</code></a></dt>
+
+<dd>
+<p>Part of <a href="#create" class="podlinkpod"
+>&#34;create&#34;</a>.</p>
+
+<p>Takes the return value from <a href="#run_create_validators" class="podlinkpod"
+>&#34;run_create_validators&#34;</a> and inserts the data into the database. Returns a newly created object.</p>
+
+<dt><a name="update"
+><code class="code">update</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Saves the values currently in this object to the database. Only the fields specified in <a href="#UPDATE_COLUMNS" class="podlinkpod"
+>&#34;UPDATE_COLUMNS&#34;</a> will be updated, and they will only be updated if their values have changed.</p>
+
+<dt><a name="Params_(none)"
+><b>Params</b> (none)</a></dt>
+
+<dd>
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p><b>In scalar context:</b></p>
+
+<p>A hashref showing what changed during the update. The keys are the column names from <a href="#UPDATE_COLUMNS" class="podlinkpod"
+>&#34;UPDATE_COLUMNS&#34;</a>. If a field was not changed, it will not be in the hash at all. If the field was changed, the key will point to an arrayref. The first item of the arrayref will be the old value, and the second item will be the new value.</p>
+
+<p>If there were no changes, we return a reference to an empty hash.</p>
+
+<p><b>In array context:</b></p>
+
+<p>Returns a list, where the first item is the above hashref. The second item is the object as it was in the database before update() was called. (This is mostly useful to subclasses of <code class="code">Bugzilla::Object</code> that are implementing <code class="code">update</code>.)</p>
+</dd>
+</dl>
+
+<dt><a name="remove_from_db"
+><code class="code">remove_from_db</code></a></dt>
+
+<dd>
+<p>Removes this object from the database. Will throw an error if you can&#39;t remove it for some reason. The object will then be destroyed, as it is not safe to use the object after it has been removed from the database.</p>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Mutators"
+>Mutators</a></h2>
+
+<p>These are used for updating the values in objects, before calling <code class="code">update</code>.</p>
+
+<dl>
+<dt><a name="set"
+><code class="code">set</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Sets a certain hash member of this class to a certain value. Used for updating fields. Calls the validator for this field, if it exists. Subclasses should use this function to implement the various <code class="code">set_</code> mutators for their different fields.</p>
+
+<p>If your class defines a method called <code class="code">_set_global_validator</code>, <code class="code">set</code> will call it with <code class="code">($value, $field)</code> as arguments, after running the validator for this particular field. <code class="code">_set_global_validator</code> does not return anything.</p>
+
+<p>See <a href="#VALIDATORS" class="podlinkpod"
+>&#34;VALIDATORS&#34;</a> for more information.</p>
+
+<p><b>NOTE</b>: This function is intended only for use by subclasses. If you call it from anywhere else, it will throw a <code class="code">CodeError</code>.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a
+><code class="code">$field</code> - The name of the hash member to update. This should be the same as the name of the field in <a href="#VALIDATORS" class="podlinkpod"
+>&#34;VALIDATORS&#34;</a>, if it exists there.</a></dt>
+
+<dd>
+<dt><a name="$value_-_The_value_that_you&#39;re_setting_the_field_to."
+><code class="code">$value</code> - The value that you&#39;re setting the field to.</a></dt>
+</dl>
+
+<dt><a name="Returns_(nothing)"
+><b>Returns</b> (nothing)</a></dt>
+</dl>
+
+<dt><a name="set_all"
+><code class="code">set_all</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>This is a convenience function which is simpler than calling many different <code class="code">set_</code> functions in a row. You pass a hashref of parameters and it calls <code class="code">set_$key($value)</code> for every item in the hashref.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<p>Takes a hashref of the fields that need to be set, pointing to the value that should be passed to the <code class="code">set_</code> function that is called.</p>
+
+<dt><a name="Returns_(nothing)"
+><b>Returns</b> (nothing)</a></dt>
+</dl>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Simple_Validators"
+>Simple Validators</a></h2>
+
+<p>You can use these in your subclass <a href="#VALIDATORS" class="podlinkpod"
+>&#34;VALIDATORS&#34;</a> or <a href="#UPDATE_VALIDATORS" class="podlinkpod"
+>&#34;UPDATE_VALIDATORS&#34;</a>. Note that you have to reference them like <code class="code">\&#38;Bugzilla::Object::check_boolean</code>, you can&#39;t just write <code class="code">\&#38;check_boolean</code>.</p>
+
+<dl>
+<dt><a name="check_boolean"
+><code class="code">check_boolean</code></a></dt>
+
+<dd>
+<p>Returns <code class="code">1</code> if the passed-in value is true, <code class="code">0</code> otherwise.</p>
+</dd>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="CLASS_FUNCTIONS"
+>CLASS FUNCTIONS</a></h1>
+
+<dl>
+<dt><a name="any_exist"
+><code class="code">any_exist</code></a></dt>
+
+<dd>
+<p>Returns <code class="code">1</code> if there are any of these objects in the database, <code class="code">0</code> otherwise.</p>
+
+<dt><a name="get_all"
+><code class="code">get_all</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Returns all objects in this table from the database.
+
+ Params: none.
+
+ Returns: A list of objects, or an empty list if there are none.
+
+ Notes: Note that you must call this as C&#60;$class-&#62;get_all&#62;. For
+ example, C&#60;Bugzilla::Keyword-&#62;get_all&#62;.
+ C&#60;Bugzilla::Keyword::get_all&#62; will not work.</pre>
+</dd>
+</dl>
+<p class="backlinkbottom"><b><a name="___bottom" href="../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/Product.html b/docs/en/html/api/Bugzilla/Product.html
new file mode 100644
index 000000000..33df7593d
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/Product.html
@@ -0,0 +1,279 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::Product</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href=".././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::Product</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#METHODS'>METHODS</a>
+ <li class='indexItem indexItem1'><a href='#SUBROUTINES'>SUBROUTINES</a>
+ <li class='indexItem indexItem1'><a href='#SEE_ALSO'>SEE ALSO</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::Product - Bugzilla product class.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<pre class="code"> use Bugzilla::Product;
+
+ my $product = new Bugzilla::Product(1);
+ my $product = new Bugzilla::Product({ name =&#62; &#39;AcmeProduct&#39; });
+
+ my @components = $product-&#62;components();
+ my $groups_controls = $product-&#62;group_controls();
+ my @milestones = $product-&#62;milestones();
+ my @versions = $product-&#62;versions();
+ my $bugcount = $product-&#62;bug_count();
+ my $bug_ids = $product-&#62;bug_ids();
+ my $has_access = $product-&#62;user_has_access($user);
+ my $flag_types = $product-&#62;flag_types();
+ my $classification = $product-&#62;classification();
+
+ my $id = $product-&#62;id;
+ my $name = $product-&#62;name;
+ my $description = $product-&#62;description;
+ my isactive = $product-&#62;is_active;
+ my $defaultmilestone = $product-&#62;default_milestone;
+ my $classificationid = $product-&#62;classification_id;
+ my $allows_unconfirmed = $product-&#62;allows_unconfirmed;</pre>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>Product.pm represents a product object. It is an implementation of <a href="../Bugzilla/Object.html" class="podlinkpod"
+>Bugzilla::Object</a>, and thus provides all methods that <a href="../Bugzilla/Object.html" class="podlinkpod"
+>Bugzilla::Object</a> provides.</p>
+
+<p>The methods that are specific to <code class="code">Bugzilla::Product</code> are listed below.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="METHODS"
+>METHODS</a></h1>
+
+<dl>
+<dt><a name="components"
+><code class="code">components</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Returns an array of component objects belonging to
+ the product.
+
+ Params: none.
+
+ Returns: An array of Bugzilla::Component object.</pre>
+
+<dt><a name="group_controls()"
+><code class="code">group_controls()</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Returns a hash (group id as key) with all product
+ group controls.
+
+ Params: $full_data (optional, false by default) - when true,
+ the number of bugs per group applicable to the product
+ is also returned. Moreover, bug groups which have no
+ special settings for the product are also returned.
+
+ Returns: A hash with group id as key and hash containing
+ a Bugzilla::Group object and the properties of group
+ relative to the product.</pre>
+
+<dt><a name="groups_available"
+><code class="code">groups_available</code></a></dt>
+
+<dd>
+<p>Tells you what groups are set to Default or Shown for the currently-logged-in user (taking into account both OtherControl and MemberControl). Returns an arrayref of <a href="../Bugzilla/Group.html" class="podlinkpod"
+>Bugzilla::Group</a> objects with an extra hash keys set, <code class="code">is_default</code>, which is true if the group is set to Default for the currently-logged-in user.</p>
+
+<dt><a name="groups_mandatory"
+><code class="code">groups_mandatory</code></a></dt>
+
+<dd>
+<p>Tells you what groups are mandatory for bugs in this product, for the currently-logged-in user. Returns an arrayref of <code class="code">Bugzilla::Group</code> objects.</p>
+
+<dt><a name="group_is_settable"
+><code class="code">group_is_settable</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Tells you whether or not the currently-logged-in user can set a group on a bug (whether or not they match the MemberControl/OtherControl settings for a group in this product). Groups that are <code class="code">Mandatory</code> for the currently-loggeed-in user are also acceptable since from Bugzilla&#39;s perspective, there&#39;s no problem with &#34;setting&#34; a Mandatory group on a bug. (In fact, the user <i>must</i> set the Mandatory group on the bug.)</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="$group_-_Either_a_numeric_group_id_or_a_Bugzilla::Group_object."
+><code class="code">$group</code> - Either a numeric group id or a <a href="../Bugzilla/Group.html" class="podlinkpod"
+>Bugzilla::Group</a> object.</a></dt>
+</dl>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p><code class="code">1</code> if the group is valid in this product, <code class="code">0</code> otherwise.</p>
+</dd>
+</dl>
+
+<dt><a name="groups_valid"
+><code class="code">groups_valid</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Returns an arrayref of <a href="../Bugzilla/Group.html" class="podlinkpod"
+>Bugzilla::Group</a> objects, representing groups that bugs could validly be restricted to within this product. Used mostly when you need the list of all possible groups that could be set in a product by anybody, disregarding whether or not the groups are active or who the currently logged-in user is.</p>
+
+<p><b>Note</b>: This doesn&#39;t check whether or not the current user can add/remove bugs to/from these groups. It just tells you that bugs <i>could be in</i> these groups, in this product.</p>
+
+<dt><a name="Params_(none)"
+><b>Params</b> (none)</a></dt>
+
+<dd>
+<dt><a name="Returns_An_arrayref_of_Bugzilla::Group_objects."
+><b>Returns</b> An arrayref of <a href="../Bugzilla/Group.html" class="podlinkpod"
+>Bugzilla::Group</a> objects.</a></dt>
+</dl>
+
+<dt><a name="group_is_valid"
+><code class="code">group_is_valid</code></a></dt>
+
+<dd>
+<p>Returns <code class="code">1</code> if the passed-in <a href="../Bugzilla/Group.html" class="podlinkpod"
+>Bugzilla::Group</a> or group id could be set on a bug by <i>anybody</i>, in this product. Even inactive groups are considered valid. (This is a shortcut for searching <a href="#groups_valid" class="podlinkpod"
+>&#34;groups_valid&#34;</a> to find out if a group is valid in a particular product.)</p>
+
+<dt><a name="versions"
+><code class="code">versions</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Returns all valid versions for that product.
+
+ Params: none.
+
+ Returns: An array of Bugzilla::Version objects.</pre>
+
+<dt><a name="milestones"
+><code class="code">milestones</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Returns all valid milestones for that product.
+
+ Params: none.
+
+ Returns: An array of Bugzilla::Milestone objects.</pre>
+
+<dt><a name="bug_count()"
+><code class="code">bug_count()</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Returns the total of bugs that belong to the product.
+
+ Params: none.
+
+ Returns: Integer with the number of bugs.</pre>
+
+<dt><a name="bug_ids()"
+><code class="code">bug_ids()</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Returns the IDs of bugs that belong to the product.
+
+ Params: none.
+
+ Returns: An array of integer.</pre>
+
+<dt><a name="user_has_access()"
+><code class="code">user_has_access()</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Tells you whether or not the user is allowed to enter
+ bugs into this product, based on the C&#60;entry&#62; group
+ control. To see whether or not a user can actually
+ enter a bug into a product, use C&#60;$user-&#38;gt;can_enter_product&#62;.
+
+ Params: C&#60;$user&#62; - A Bugzilla::User object.
+
+ Returns C&#60;1&#62; If this user&#39;s groups allow him C&#60;entry&#62; access to
+ this Product, C&#60;0&#62; otherwise.</pre>
+
+<dt><a name="flag_types()"
+><code class="code">flag_types()</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Returns flag types available for at least one of
+ its components.
+
+ Params: none.
+
+ Returns: Two references to an array of flagtype objects.</pre>
+
+<dt><a name="classification()"
+><code class="code">classification()</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Returns the classification the product belongs to.
+
+ Params: none.
+
+ Returns: A Bugzilla::Classification object.</pre>
+</dd>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SUBROUTINES"
+>SUBROUTINES</a></h1>
+
+<dl>
+<dt><a name="preload"
+><code class="code">preload</code></a></dt>
+
+<dd>
+<p>When passed an arrayref of <code class="code">Bugzilla::Product</code> objects, preloads their <a href="#milestones" class="podlinkpod"
+>&#34;milestones&#34;</a>, <a href="#components" class="podlinkpod"
+>&#34;components&#34;</a>, and <a href="#versions" class="podlinkpod"
+>&#34;versions&#34;</a>, which is much faster than calling those accessors on every item in the array individually.</p>
+
+<p>If the 2nd argument passed to <code class="code">preload</code> is true, flag types for these products and their components are also preloaded.</p>
+
+<p>This function is not exported, so must be called like <code class="code">Bugzilla::Product::preload($products)</code>.</p>
+</dd>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SEE_ALSO"
+>SEE ALSO</a></h1>
+
+<p><a href="../Bugzilla/Object.html" class="podlinkpod"
+>Bugzilla::Object</a></p>
+<p class="backlinkbottom"><b><a name="___bottom" href="../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/Search/Recent.html b/docs/en/html/api/Bugzilla/Search/Recent.html
new file mode 100644
index 000000000..537238e27
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/Search/Recent.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::Search::Recent</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href="../.././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::Search::Recent</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::Search::Recent - A search recently run by a logged-in user.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<pre class="code"> use Bugzilla::Search::Recent;</pre>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>This is an implementation of <a href="../../Bugzilla/Object.html" class="podlinkpod"
+>Bugzilla::Object</a>, and so has all the same methods available as <a href="../../Bugzilla/Object.html" class="podlinkpod"
+>Bugzilla::Object</a>, in addition to what is documented below.</p>
+<p class="backlinkbottom"><b><a name="___bottom" href="../../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/Search/Saved.html b/docs/en/html/api/Bugzilla/Search/Saved.html
new file mode 100644
index 000000000..cc41a2aeb
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/Search/Saved.html
@@ -0,0 +1,133 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::Search::Saved</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href="../.././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::Search::Saved</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#METHODS'>METHODS</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#Constructors_and_Database_Manipulation'>Constructors and Database Manipulation</a>
+ <li class='indexItem indexItem2'><a href='#Accessors'>Accessors</a>
+ </ul>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::Search::Saved - A saved search</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<pre class="code"> use Bugzilla::Search::Saved;
+
+ my $query = new Bugzilla::Search::Saved($query_id);
+
+ my $edit_link = $query-&#62;edit_link;
+ my $search_url = $query-&#62;url;
+ my $owner = $query-&#62;user;
+ my $num_subscribers = $query-&#62;shared_with_users;</pre>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>This module exists to represent a <a href="../../Bugzilla/Search.html" class="podlinkpod"
+>Bugzilla::Search</a> that has been saved to the database.</p>
+
+<p>This is an implementation of <a href="../../Bugzilla/Object.html" class="podlinkpod"
+>Bugzilla::Object</a>, and so has all the same methods available as <a href="../../Bugzilla/Object.html" class="podlinkpod"
+>Bugzilla::Object</a>, in addition to what is documented below.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="METHODS"
+>METHODS</a></h1>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Constructors_and_Database_Manipulation"
+>Constructors and Database Manipulation</a></h2>
+
+<dl>
+<dt><a name="new"
+><code class="code">new</code></a></dt>
+
+<dd>
+<p>Takes either an id, or the named parameters <code class="code">user</code> and <code class="code">name</code>. <code class="code">user</code> can be either a <a href="../../Bugzilla/User.html" class="podlinkpod"
+>Bugzilla::User</a> object or a numeric user id.</p>
+
+<p>See also: <a href="../../Bugzilla/Object.html#new" class="podlinkpod"
+>&#34;new&#34; in Bugzilla::Object</a>.</p>
+
+<dt><a name="preload"
+><code class="code">preload</code></a></dt>
+
+<dd>
+<p>Sets <code class="code">link_in_footer</code> for all given saved searches at once, for the currently logged in user. This is much faster than calling this method for each saved search individually.</p>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Accessors"
+>Accessors</a></h2>
+
+<p>These return data about the object, without modifying the object.</p>
+
+<dl>
+<dt><a name="edit_link"
+><code class="code">edit_link</code></a></dt>
+
+<dd>
+<p>A url with which you can edit the search.</p>
+
+<dt><a name="url"
+><code class="code">url</code></a></dt>
+
+<dd>
+<p>The CGI parameters for the search, as a string.</p>
+
+<dt><a name="link_in_footer"
+><code class="code">link_in_footer</code></a></dt>
+
+<dd>
+<p>Whether or not this search should be displayed in the footer for the <i>current user</i> (not the owner of the search, but the person actually using Bugzilla right now).</p>
+
+<dt><a name="type"
+><code class="code">type</code></a></dt>
+
+<dd>
+<p>The numeric id of the type of search this is (from <a href="../../Bugzilla/Constants.html" class="podlinkpod"
+>Bugzilla::Constants</a>).</p>
+
+<dt><a name="shared_with_group"
+><code class="code">shared_with_group</code></a></dt>
+
+<dd>
+<p>The <a href="../../Bugzilla/Group.html" class="podlinkpod"
+>Bugzilla::Group</a> that this search is shared with. <code class="code">undef</code> if this search isn&#39;t shared.</p>
+
+<dt><a name="shared_with_users"
+><code class="code">shared_with_users</code></a></dt>
+
+<dd>
+<p>Returns how many users (besides the author of the saved search) are using the saved search, i.e. have it displayed in their footer.</p>
+</dd>
+</dl>
+<p class="backlinkbottom"><b><a name="___bottom" href="../../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/Status.html b/docs/en/html/api/Bugzilla/Status.html
new file mode 100644
index 000000000..bee56c47e
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/Status.html
@@ -0,0 +1,123 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::Status</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href=".././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::Status</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#METHODS'>METHODS</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::Status - Bug status class.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<pre class="code"> use Bugzilla::Status;
+
+ my $bug_status = new Bugzilla::Status({ name =&#62; &#39;IN_PROGRESS&#39; });
+ my $bug_status = new Bugzilla::Status(4);
+
+ my @closed_bug_statuses = closed_bug_statuses();
+
+ Bugzilla::Status::add_missing_bug_status_transitions($bug_status);</pre>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>Status.pm represents a bug status object. It is an implementation of <a href="../Bugzilla/Object.html" class="podlinkpod"
+>Bugzilla::Object</a>, and thus provides all methods that <a href="../Bugzilla/Object.html" class="podlinkpod"
+>Bugzilla::Object</a> provides.</p>
+
+<p>The methods that are specific to <code class="code">Bugzilla::Status</code> are listed below.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="METHODS"
+>METHODS</a></h1>
+
+<dl>
+<dt><a name="closed_bug_statuses"
+><code class="code">closed_bug_statuses</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Returns a list of C&#60;Bugzilla::Status&#62; objects which can have
+ a resolution associated with them (&#34;closed&#34; bug statuses).
+
+ Params: none.
+
+ Returns: A list of Bugzilla::Status objects.</pre>
+
+<dt><a name="can_change_to"
+><code class="code">can_change_to</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Returns the list of active statuses a bug can be changed to
+ given the current bug status. If this method is called as a
+ class method, then it returns all bug statuses available on
+ bug creation.
+
+ Params: none.
+
+ Returns: A list of Bugzilla::Status objects.</pre>
+
+<dt><a name="comment_required_on_change_from"
+><code class="code">comment_required_on_change_from</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Checks if a comment is required to change to this status from another status, according to the current settings in the workflow.</p>
+
+<p>Note that this doesn&#39;t implement the checks enforced by the various <code class="code">commenton</code> parameters--those are checked by internal checks in <a href="../Bugzilla/Bug.html" class="podlinkpod"
+>Bugzilla::Bug</a>.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<p><code class="code">$old_status</code> - The status you&#39;re changing from.</p>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p><code class="code">1</code> if a comment is required on this change, <code class="code">0</code> if not.</p>
+</dd>
+</dl>
+
+<dt><a name="add_missing_bug_status_transitions"
+><code class="code">add_missing_bug_status_transitions</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Insert all missing transitions to a given bug status.
+
+ Params: $bug_status - The value (name) of a bug status.
+
+ Returns: nothing.</pre>
+</dd>
+</dl>
+<p class="backlinkbottom"><b><a name="___bottom" href="../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/Template.html b/docs/en/html/api/Bugzilla/Template.html
new file mode 100644
index 000000000..f96243dc8
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/Template.html
@@ -0,0 +1,95 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::Template</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href=".././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::Template</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#SUBROUTINES'>SUBROUTINES</a>
+ <li class='indexItem indexItem1'><a href='#METHODS'>METHODS</a>
+ <li class='indexItem indexItem1'><a href='#SEE_ALSO'>SEE ALSO</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::Template - Wrapper around the Template Toolkit <code class="code">Template</code> object</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<pre class="code"> my $template = Bugzilla::Template-&#62;create;
+ my $format = $template-&#62;get_format(&#34;foo/bar&#34;,
+ scalar($cgi-&#62;param(&#39;format&#39;)),
+ scalar($cgi-&#62;param(&#39;ctype&#39;)));</pre>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>This is basically a wrapper so that the correct arguments get passed into the <code class="code">Template</code> constructor.</p>
+
+<p>It should not be used directly by scripts or modules - instead, use <code class="code">Bugzilla-&#62;instance-&#62;template</code> to get an already created module.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SUBROUTINES"
+>SUBROUTINES</a></h1>
+
+<dl>
+<dt><a name="precompile_templates($output)"
+><code class="code">precompile_templates($output)</code></a></dt>
+
+<dd>
+<p>Description: Compiles all of Bugzilla&#39;s templates in every language. Used mostly by <em class="code">checksetup.pl</em>.</p>
+
+<p>Params: <code class="code">$output</code> - <code class="code">true</code> if you want the function to print out information about what it&#39;s doing.</p>
+
+<p>Returns: nothing</p>
+</dd>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="METHODS"
+>METHODS</a></h1>
+
+<dl>
+<dt><a name="get_format($file,_$format,_$ctype)"
+><code class="code">get_format($file, $format, $ctype)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Construct a format object from URL parameters.
+
+ Params: $file - Name of the template to display.
+ $format - When the template exists under several formats
+ (e.g. table or graph), specify the one to choose.
+ $ctype - Content type, see Bugzilla::Constants::contenttypes.
+
+ Returns: A format object.</pre>
+</dd>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SEE_ALSO"
+>SEE ALSO</a></h1>
+
+<p><a href="../Bugzilla.html" class="podlinkpod"
+>Bugzilla</a>, <a href="../Template.html" class="podlinkpod"
+>Template</a></p>
+<p class="backlinkbottom"><b><a name="___bottom" href="../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/Template/Plugin/Bugzilla.html b/docs/en/html/api/Bugzilla/Template/Plugin/Bugzilla.html
new file mode 100644
index 000000000..bf888e98c
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/Template/Plugin/Bugzilla.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::Template::Plugin::Bugzilla</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href="../../.././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../../../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::Template::Plugin::Bugzilla</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#SEE_ALSO'>SEE ALSO</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::Template::Plugin::Bugzilla</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>Template Toolkit plugin to allow access to the persistent <code class="code">Bugzilla</code> object.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SEE_ALSO"
+>SEE ALSO</a></h1>
+
+<p><a href="../../../Bugzilla.html" class="podlinkpod"
+>Bugzilla</a>,
+<a href="../../../Template/Plugin.html" class="podlinkpod"
+>Template::Plugin</a></p>
+<p class="backlinkbottom"><b><a name="___bottom" href="../../../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/Template/Plugin/Hook.html b/docs/en/html/api/Bugzilla/Template/Plugin/Hook.html
new file mode 100644
index 000000000..52c9f9194
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/Template/Plugin/Hook.html
@@ -0,0 +1,98 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::Template::Plugin::Hook</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href="../../.././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../../../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::Template::Plugin::Hook</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#METHODS'>METHODS</a>
+ <li class='indexItem indexItem1'><a href='#SEE_ALSO'>SEE ALSO</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::Template::Plugin::Hook</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>Template Toolkit plugin to process hooks added into templates by extensions.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="METHODS"
+>METHODS</a></h1>
+
+<dl>
+<dt><a name="process"
+><b>process</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Processes hooks added into templates by extensions.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="hook_name"
+><code class="code">hook_name</code></a></dt>
+
+<dd>
+<p>The unique name of the template hook.</p>
+
+<dt><a name="template_(optional)"
+><code class="code">template</code> (optional)</a></dt>
+
+<dd>
+<p>The path of the calling template.
+This is used as a work around to a bug which causes the path to the hook to be incorrect when the hook is called from inside a block.</p>
+
+<p>Example: If the hook <code class="code">lastrow</code> is added to the template <em class="code">show-multiple.html.tmpl</em> and it is desired to force the correct template path,
+the template hook would be:</p>
+
+<pre class="code"> [% Hook.process(&#34;lastrow&#34;, &#34;bug/show-multiple.html.tmpl&#34;) %]</pre>
+</dd>
+</dl>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>Output from processing template extension.</p>
+</dd>
+</dl>
+</dd>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SEE_ALSO"
+>SEE ALSO</a></h1>
+
+<p><a href="../../../Template/Plugin.html" class="podlinkpod"
+>Template::Plugin</a></p>
+
+<p><a href="http://wiki.mozilla.org/Bugzilla:Writing_Extensions" class="podlinkurl"
+>http://wiki.mozilla.org/Bugzilla:Writing_Extensions</a></p>
+<p class="backlinkbottom"><b><a name="___bottom" href="../../../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/Template/Plugin/User.html b/docs/en/html/api/Bugzilla/Template/Plugin/User.html
new file mode 100644
index 000000000..a6cb08800
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/Template/Plugin/User.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::Template::Plugin::User</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href="../../.././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../../../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::Template::Plugin::User</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#SEE_ALSO'>SEE ALSO</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::Template::Plugin::User</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>Template Toolkit plugin to allow access to the <code class="code">User</code> object.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SEE_ALSO"
+>SEE ALSO</a></h1>
+
+<p><a href="../../../Bugzilla/User.html" class="podlinkpod"
+>Bugzilla::User</a>,
+<a href="../../../Template/Plugin.html" class="podlinkpod"
+>Template::Plugin</a></p>
+<p class="backlinkbottom"><b><a name="___bottom" href="../../../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/Token.html b/docs/en/html/api/Bugzilla/Token.html
new file mode 100644
index 000000000..54efa29dc
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/Token.html
@@ -0,0 +1,233 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::Token</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href=".././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::Token</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#SUBROUTINES'>SUBROUTINES</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#Security_related_routines'>Security related routines</a>
+ </ul>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::Token - Provides different routines to manage tokens.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<pre class="code"> use Bugzilla::Token;
+
+ Bugzilla::Token::issue_new_user_account_token($login_name);
+ Bugzilla::Token::IssueEmailChangeToken($user, $old_email, $new_email);
+ Bugzilla::Token::IssuePasswordToken($user);
+ Bugzilla::Token::DeletePasswordTokens($user_id, $reason);
+ Bugzilla::Token::Cancel($token, $cancelaction, $vars);
+
+ Bugzilla::Token::CleanTokenTable();
+
+ my $token = issue_session_token($event);
+ check_token_data($token, $event)
+ delete_token($token);
+
+ my $token = Bugzilla::Token::GenerateUniqueToken($table, $column);
+ my $token = Bugzilla::Token::HasEmailChangeToken($user_id);
+ my ($token, $date, $data) = Bugzilla::Token::GetTokenData($token);</pre>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SUBROUTINES"
+>SUBROUTINES</a></h1>
+
+<dl>
+<dt><a name="issue_new_user_account_token($login_name)"
+><code class="code">issue_new_user_account_token($login_name)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Creates and sends a token per email to the email address
+ requesting a new user account. It doesn&#39;t check whether
+ the user account already exists. The user will have to
+ use this token to confirm the creation of his user account.
+
+ Params: $login_name - The new login name requested by the user.
+
+ Returns: Nothing. It throws an error if the same user made the same
+ request in the last few minutes.</pre>
+
+<dt><a name="sub_IssueEmailChangeToken($user,_$old_email,_$new_email)"
+><code class="code">sub IssueEmailChangeToken($user, $old_email, $new_email)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Sends two distinct tokens per email to the old and new email
+ addresses to confirm the email address change for the given
+ user. These tokens remain valid for the next MAX_TOKEN_AGE days.
+
+ Params: $user - User object of the user requesting a new
+ email address.
+ $old_email - The current (old) email address of the user.
+ $new_email - The new email address of the user.
+
+ Returns: Nothing.</pre>
+
+<dt><a name="IssuePasswordToken($user)"
+><code class="code">IssuePasswordToken($user)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Sends a token per email to the given user. This token
+ can be used to change the password (e.g. in case the user
+ cannot remember his password and wishes to enter a new one).
+
+ Params: $user - User object of the user requesting a new password.
+
+ Returns: Nothing. It throws an error if the same user made the same
+ request in the last few minutes.</pre>
+
+<dt><a name="CleanTokenTable()"
+><code class="code">CleanTokenTable()</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Removes all tokens older than MAX_TOKEN_AGE days from the DB.
+ This means that these tokens will now be considered as invalid.
+
+ Params: None.
+
+ Returns: Nothing.</pre>
+
+<dt><a name="GenerateUniqueToken($table,_$column)"
+><code class="code">GenerateUniqueToken($table, $column)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Generates and returns a unique token. This token is unique
+ in the $column of the $table. This token is NOT stored in the DB.
+
+ Params: $table (optional): The table to look at (default: tokens).
+ $column (optional): The column to look at for uniqueness (default: token).
+
+ Returns: A token which is unique in $column.</pre>
+
+<dt><a name="Cancel($token,_$cancelaction,_$vars)"
+><code class="code">Cancel($token, $cancelaction, $vars)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Invalidates an existing token, generally when the token is used
+ for an action which is not the one expected. An email is sent
+ to the user who originally requested this token to inform him
+ that this token has been invalidated (e.g. because an hacker
+ tried to use this token for some malicious action).
+
+ Params: $token: The token to invalidate.
+ $cancelaction: The reason why this token is invalidated.
+ $vars: Some additional information about this action.
+
+ Returns: Nothing.</pre>
+
+<dt><a name="DeletePasswordTokens($user_id,_$reason)"
+><code class="code">DeletePasswordTokens($user_id, $reason)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Cancels all password tokens for the given user. Emails are sent
+ to the user to inform him about this action.
+
+ Params: $user_id: The user ID of the user account whose password tokens
+ are canceled.
+ $reason: The reason why these tokens are canceled.
+
+ Returns: Nothing.</pre>
+
+<dt><a name="HasEmailChangeToken($user_id)"
+><code class="code">HasEmailChangeToken($user_id)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Returns any existing token currently used for an email change
+ for the given user.
+
+ Params: $user_id - A user ID.
+
+ Returns: A token if it exists, else undef.</pre>
+
+<dt><a name="GetTokenData($token)"
+><code class="code">GetTokenData($token)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Returns all stored data for the given token.
+
+ Params: $token - A valid token.
+
+ Returns: The user ID, the date and time when the token was created and
+ the (event)data stored with that token.</pre>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Security_related_routines"
+>Security related routines</a></h2>
+
+<p>The following routines have been written to be used together as described below, although they can be used separately.</p>
+
+<dl>
+<dt><a name="issue_session_token($event)"
+><code class="code">issue_session_token($event)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Creates and returns a token used internally.
+
+ Params: $event - The event which needs to be stored in the DB for future
+ reference/checks.
+
+ Returns: A unique token.</pre>
+
+<dt><a name="check_token_data($token,_$event)"
+><code class="code">check_token_data($token, $event)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Makes sure the $token has been created by the currently logged in
+ user and to be used for the given $event. If this token is used for
+ an unexpected action (i.e. $event doesn&#39;t match the information stored
+ with the token), a warning is displayed asking whether the user really
+ wants to continue. On success, it returns 1.
+ This is the routine to use for security checks, combined with
+ issue_session_token() and delete_token() as follows:
+
+ 1. First, create a token for some coming action.
+ my $token = issue_session_token($action);
+ 2. Some time later, it&#39;s time to make sure that the expected action
+ is going to be executed, and by the expected user.
+ check_token_data($token, $action);
+ 3. The check has been done and we no longer need this token.
+ delete_token($token);
+
+ Params: $token - The token used for security checks.
+ $event - The expected event to be run.
+
+ Returns: 1 on success, else a warning is thrown.</pre>
+
+<dt><a name="delete_token($token)"
+><code class="code">delete_token($token)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Deletes the specified token. No notification is sent.
+
+ Params: $token - The token to delete.
+
+ Returns: Nothing.</pre>
+</dd>
+</dl>
+<p class="backlinkbottom"><b><a name="___bottom" href="../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/Update.html b/docs/en/html/api/Bugzilla/Update.html
new file mode 100644
index 000000000..8f4fef72a
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/Update.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::Update</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href=".././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::Update</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#FUNCTIONS'>FUNCTIONS</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::Update - Update routines for Bugzilla</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<pre class="code"> use Bugzilla::Update;
+
+ # Get information about new releases
+ my $new_release = Bugzilla::Update::get_notifications();</pre>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>This module contains all required routines to notify you about new releases. It downloads an XML file from bugzilla.org and parses it, in order to display information based on your preferences. Absolutely no information about the Bugzilla version you are running is sent to bugzilla.org.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="FUNCTIONS"
+>FUNCTIONS</a></h1>
+
+<dl>
+<dt><a name="get_notifications()"
+><code class="code">get_notifications()</code></a></dt>
+
+<dd>
+<pre class="code"> Description: This function informs you about new releases, if any.
+
+ Params: None.
+
+ Returns: On success, a reference to a hash with data about
+ new releases, if any.
+ On failure, a reference to a hash with the reason
+ of the failure and the name of the unusable XML file.</pre>
+</dd>
+</dl>
+<p class="backlinkbottom"><b><a name="___bottom" href="../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/User.html b/docs/en/html/api/Bugzilla/User.html
new file mode 100644
index 000000000..5bba649ce
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/User.html
@@ -0,0 +1,641 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::User</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href=".././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::User</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#CONSTANTS'>CONSTANTS</a>
+ <li class='indexItem indexItem1'><a href='#METHODS'>METHODS</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#Constructors'>Constructors</a>
+ <li class='indexItem indexItem2'><a href='#Saved_and_Shared_Queries'>Saved and Shared Queries</a>
+ <li class='indexItem indexItem2'><a href='#Account_Lockout'>Account Lockout</a>
+ <li class='indexItem indexItem2'><a href='#Other_Methods'>Other Methods</a>
+ </ul>
+ <li class='indexItem indexItem1'><a href='#CLASS_FUNCTIONS'>CLASS FUNCTIONS</a>
+ <li class='indexItem indexItem1'><a href='#SEE_ALSO'>SEE ALSO</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::User - Object for a Bugzilla user</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<pre class="code"> use Bugzilla::User;
+
+ my $user = new Bugzilla::User($id);
+
+ my @get_selectable_classifications =
+ $user-&#62;get_selectable_classifications;
+
+ # Class Functions
+ $user = Bugzilla::User-&#62;create({
+ login_name =&#62; $username,
+ realname =&#62; $realname,
+ cryptpassword =&#62; $plaintext_password,
+ disabledtext =&#62; $disabledtext,
+ disable_mail =&#62; 0});</pre>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>This package handles Bugzilla users. Data obtained from here is read-only; there is currently no way to modify a user from this package.</p>
+
+<p>Note that the currently logged in user (if any) is available via <a href="../Bugzilla.html#user" class="podlinkpod"
+>Bugzilla-&#62;user</a>.</p>
+
+<p><code class="code">Bugzilla::User</code> is an implementation of <a href="../Bugzilla/Object.html" class="podlinkpod"
+>Bugzilla::Object</a>, and thus provides all the methods of <a href="../Bugzilla/Object.html" class="podlinkpod"
+>Bugzilla::Object</a> in addition to the methods listed below.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="CONSTANTS"
+>CONSTANTS</a></h1>
+
+<dl>
+<dt><a name="USER_MATCH_MULTIPLE"
+><code class="code">USER_MATCH_MULTIPLE</code></a></dt>
+
+<dd>
+<p>Returned by <code class="code">match_field()</code> when at least one field matched more than one user, but no matches failed.</p>
+
+<dt><a name="USER_MATCH_FAILED"
+><code class="code">USER_MATCH_FAILED</code></a></dt>
+
+<dd>
+<p>Returned by <code class="code">match_field()</code> when at least one field failed to match anything.</p>
+
+<dt><a name="USER_MATCH_SUCCESS"
+><code class="code">USER_MATCH_SUCCESS</code></a></dt>
+
+<dd>
+<p>Returned by <code class="code">match_field()</code> when all fields successfully matched only one user.</p>
+
+<dt><a name="MATCH_SKIP_CONFIRM"
+><code class="code">MATCH_SKIP_CONFIRM</code></a></dt>
+
+<dd>
+<p>Passed in to match_field to tell match_field to never display a confirmation screen.</p>
+</dd>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="METHODS"
+>METHODS</a></h1>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Constructors"
+>Constructors</a></h2>
+
+<dl>
+<dt><a name="super_user"
+><code class="code">super_user</code></a></dt>
+
+<dd>
+<p>Returns a user who is in all groups, but who does not really exist in the database. Used for non-web scripts like <a href="../checksetup.html" class="podlinkpod"
+>checksetup</a> that need to make database changes and so on.</p>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Saved_and_Shared_Queries"
+>Saved and Shared Queries</a></h2>
+
+<dl>
+<dt><a name="queries"
+><code class="code">queries</code></a></dt>
+
+<dd>
+<p>Returns an arrayref of the user&#39;s own saved queries, sorted by name. The array contains <a href="../Bugzilla/Search/Saved.html" class="podlinkpod"
+>Bugzilla::Search::Saved</a> objects.</p>
+
+<dt><a name="queries_subscribed"
+><code class="code">queries_subscribed</code></a></dt>
+
+<dd>
+<p>Returns an arrayref of shared queries that the user has subscribed to. That is, these are shared queries that the user sees in their footer. This array contains <a href="../Bugzilla/Search/Saved.html" class="podlinkpod"
+>Bugzilla::Search::Saved</a> objects.</p>
+
+<dt><a name="queries_available"
+><code class="code">queries_available</code></a></dt>
+
+<dd>
+<p>Returns an arrayref of all queries to which the user could possibly subscribe. This includes the contents of <a href="#queries_subscribed" class="podlinkpod"
+>&#34;queries_subscribed&#34;</a>. An array of <a href="../Bugzilla/Search/Saved.html" class="podlinkpod"
+>Bugzilla::Search::Saved</a> objects.</p>
+
+<dt><a name="flush_queries_cache"
+><code class="code">flush_queries_cache</code></a></dt>
+
+<dd>
+<p>Some code modifies the set of stored queries. Because <code class="code">Bugzilla::User</code> does not handle these modifications, but does cache the result of calling <code class="code">queries</code> internally, such code must call this method to flush the cached result.</p>
+
+<dt><a name="queryshare_groups"
+><code class="code">queryshare_groups</code></a></dt>
+
+<dd>
+<p>An arrayref of group ids. The user can share their own queries with these groups.</p>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Account_Lockout"
+>Account Lockout</a></h2>
+
+<dl>
+<dt><a name="account_is_locked_out"
+><code class="code">account_is_locked_out</code></a></dt>
+
+<dd>
+<p>Returns <code class="code">1</code> if the account has failed to log in too many times recently, and thus is locked out for a period of time. Returns <code class="code">0</code> otherwise.</p>
+
+<dt><a name="account_ip_login_failures"
+><code class="code">account_ip_login_failures</code></a></dt>
+
+<dd>
+<p>Returns an arrayref of hashrefs, that contains information about the recent times that this account has failed to log in from the current remote IP. The hashes contain <code class="code">ip_addr</code>, <code class="code">login_time</code>, and <code class="code">user_id</code>.</p>
+
+<dt><a name="note_login_failure"
+><code class="code">note_login_failure</code></a></dt>
+
+<dd>
+<p>This notes that this account has failed to log in, and stores the fact in the database. The storing happens immediately, it does not wait for you to call <code class="code">update</code>.</p>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Other_Methods"
+>Other Methods</a></h2>
+
+<dl>
+<dt><a name="id"
+><code class="code">id</code></a></dt>
+
+<dd>
+<p>Returns the userid for this user.</p>
+
+<dt><a name="login"
+><code class="code">login</code></a></dt>
+
+<dd>
+<p>Returns the login name for this user.</p>
+
+<dt><a name="email"
+><code class="code">email</code></a></dt>
+
+<dd>
+<p>Returns the user&#39;s email address. Currently this is the same value as the login.</p>
+
+<dt><a name="name"
+><code class="code">name</code></a></dt>
+
+<dd>
+<p>Returns the &#39;real&#39; name for this user, if any.</p>
+
+<dt><a name="showmybugslink"
+><code class="code">showmybugslink</code></a></dt>
+
+<dd>
+<p>Returns <code class="code">1</code> if the user has set his preference to show the &#39;My Bugs&#39; link in the page footer, and <code class="code">0</code> otherwise.</p>
+
+<dt><a name="identity"
+><code class="code">identity</code></a></dt>
+
+<dd>
+<p>Returns a string for the identity of the user. This will be of the form <code class="code">name &#60;email&#62;</code> if the user has specified a name, and <code class="code">email</code> otherwise.</p>
+
+<dt><a name="nick"
+><code class="code">nick</code></a></dt>
+
+<dd>
+<p>Returns a user &#34;nickname&#34; -- i.e. a shorter, not-necessarily-unique name by which to identify the user. Currently the part of the user&#39;s email address before the at sign (@), but that could change, especially if we implement usernames not dependent on email address.</p>
+
+<dt><a name="authorizer"
+><code class="code">authorizer</code></a></dt>
+
+<dd>
+<p>This is the <a href="../Bugzilla/Auth.html" class="podlinkpod"
+>Bugzilla::Auth</a> object that the User logged in with. If the user hasn&#39;t logged in yet, a new, empty Bugzilla::Auth() object is returned.</p>
+
+<dt><a name="set_authorizer($authorizer)"
+><code class="code">set_authorizer($authorizer)</code></a></dt>
+
+<dd>
+<p>Sets the <a href="../Bugzilla/Auth.html" class="podlinkpod"
+>Bugzilla::Auth</a> object to be returned by <code class="code">authorizer()</code>. Should only be called by <code class="code">Bugzilla::Auth::login</code>, for the most part.</p>
+
+<dt><a name="disabledtext"
+><code class="code">disabledtext</code></a></dt>
+
+<dd>
+<p>Returns the disable text of the user, if any.</p>
+
+<dt><a name="settings"
+><code class="code">settings</code></a></dt>
+
+<dd>
+<p>Returns a hash of hashes which holds the user&#39;s settings. The first key is the name of the setting, as found in setting.name. The second key is one of: is_enabled - true if the user is allowed to set the preference themselves; false to force the site defaults for themselves or must accept the global site default value default_value - the global site default for this setting value - the value of this setting for this user. Will be the same as the default_value if the user is not logged in, or if is_default is true. is_default - a boolean to indicate whether the user has chosen to make a preference for themself or use the site default.</p>
+
+<dt><a name="timezone"
+><code class="code">timezone</code></a></dt>
+
+<dd>
+<p>Returns the timezone used to display dates and times to the user, as a DateTime::TimeZone object.</p>
+
+<dt><a name="groups"
+><code class="code">groups</code></a></dt>
+
+<dd>
+<p>Returns an arrayref of <a href="../Bugzilla/Group.html" class="podlinkpod"
+>Bugzilla::Group</a> objects representing groups that this user is a member of.</p>
+
+<dt><a name="groups_as_string"
+><code class="code">groups_as_string</code></a></dt>
+
+<dd>
+<p>Returns a string containing a comma-separated list of numeric group ids. If the user is not a member of any groups, returns &#34;-1&#34;. This is most often used within an SQL IN() function.</p>
+
+<dt><a name="groups_in_sql"
+><code class="code">groups_in_sql</code></a></dt>
+
+<dd>
+<p>This returns an <code class="code">IN</code> clause for SQL, containing either all of the groups the user is in, or <code class="code">-1</code> if the user is in no groups. This takes one argument--the name of the SQL field that should be on the left-hand-side of the <code class="code">IN</code> statement, which defaults to <code class="code">group_id</code> if not specified.</p>
+
+<dt><a name="in_group($group_name,_$product_id)"
+><code class="code">in_group($group_name, $product_id)</code></a></dt>
+
+<dd>
+<p>Determines whether or not a user is in the given group by name. If $product_id is given, it also checks for local privileges for this product.</p>
+
+<dt><a name="in_group_id"
+><code class="code">in_group_id</code></a></dt>
+
+<dd>
+<p>Determines whether or not a user is in the given group by id.</p>
+
+<dt><a name="bless_groups"
+><code class="code">bless_groups</code></a></dt>
+
+<dd>
+<p>Returns an arrayref of <a href="../Bugzilla/Group.html" class="podlinkpod"
+>Bugzilla::Group</a> objects.</p>
+
+<p>The arrayref consists of the groups the user can bless, taking into account that having editusers permissions means that you can bless all groups, and that you need to be able to see a group in order to bless it.</p>
+
+<dt><a name="get_products_by_permission($group)"
+><code class="code">get_products_by_permission($group)</code></a></dt>
+
+<dd>
+<p>Returns a list of product objects for which the user has $group privileges and which he can access. $group must be one of the groups defined in PER_PRODUCT_PRIVILEGES.</p>
+
+<dt><a name="can_see_user(user)"
+><code class="code">can_see_user(user)</code></a></dt>
+
+<dd>
+<p>Returns 1 if the specified user account exists and is visible to the user, 0 otherwise.</p>
+
+<dt><a name="can_edit_product(prod_id)"
+><code class="code">can_edit_product(prod_id)</code></a></dt>
+
+<dd>
+<p>Determines if, given a product id, the user can edit bugs in this product at all.</p>
+
+<dt><a name="can_see_bug(bug_id)"
+><code class="code">can_see_bug(bug_id)</code></a></dt>
+
+<dd>
+<p>Determines if the user can see the specified bug.</p>
+
+<dt><a name="can_see_product(product_name)"
+><code class="code">can_see_product(product_name)</code></a></dt>
+
+<dd>
+<p>Returns 1 if the user can access the specified product, and 0 if the user should not be aware of the existence of the product.</p>
+
+<dt><a name="derive_regexp_groups"
+><code class="code">derive_regexp_groups</code></a></dt>
+
+<dd>
+<p>Bugzilla allows for group inheritance. When data about the user (or any of the groups) changes, the database must be updated. Handling updated groups is taken care of by the constructor. However, when updating the email address, the user may be placed into different groups, based on a new email regexp. This method should be called in such a case to force reresolution of these groups.</p>
+
+<dt><a name="clear_product_cache"
+><code class="code">clear_product_cache</code></a></dt>
+
+<dd>
+<p>Clears the stored values for <a href="#get_selectable_products" class="podlinkpod"
+>&#34;get_selectable_products&#34;</a>, <a href="#get_enterable_products" class="podlinkpod"
+>&#34;get_enterable_products&#34;</a>, etc. so that their data will be read from the database again. Used mostly by <a href="../Bugzilla/Product.html" class="podlinkpod"
+>Bugzilla::Product</a>.</p>
+
+<dt><a name="get_selectable_products"
+><code class="code">get_selectable_products</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Returns all products the user is allowed to access. This list
+ is restricted to some given classification if $classification_id
+ is given.
+
+ Params: $classification_id - (optional) The ID of the classification
+ the products belong to.
+
+ Returns: An array of product objects, sorted by the product name.</pre>
+
+<dt><a name="get_selectable_classifications"
+><code class="code">get_selectable_classifications</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Returns all classifications containing at least one product
+ the user is allowed to view.
+
+ Params: none
+
+ Returns: An array of Bugzilla::Classification objects, sorted by
+ the classification name.</pre>
+
+<dt><a name="can_enter_product($product_name,_$warn)"
+><code class="code">can_enter_product($product_name, $warn)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Returns 1 if the user can enter bugs into the specified product.
+ If the user cannot enter bugs into the product, the behavior of
+ this method depends on the value of $warn:
+ - if $warn is false (or not given), a &#39;false&#39; value is returned;
+ - if $warn is true, an error is thrown.
+
+ Params: $product_name - a product name.
+ $warn - optional parameter, indicating whether an error
+ must be thrown if the user cannot enter bugs
+ into the specified product.
+
+ Returns: 1 if the user can enter bugs into the product,
+ 0 if the user cannot enter bugs into the product and if $warn
+ is false (an error is thrown if $warn is true).</pre>
+
+<dt><a name="get_enterable_products"
+><code class="code">get_enterable_products</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Returns an array of product objects into which the user is
+ allowed to enter bugs.
+
+ Params: none
+
+ Returns: an array of product objects.</pre>
+
+<dt><a name="can_access_product($product)"
+><code class="code">can_access_product($product)</code></a></dt>
+
+<dd>
+<p>Returns 1 if the user can search or enter bugs into the specified product (either a <a href="../Bugzilla/Product.html" class="podlinkpod"
+>Bugzilla::Product</a> or a product name), and 0 if the user should not be aware of the existence of the product.</p>
+
+<dt><a name="get_accessible_products"
+><code class="code">get_accessible_products</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Returns an array of product objects the user can search
+ or enter bugs against.
+
+ Params: none
+
+ Returns: an array of product objects.</pre>
+
+<dt><a name="check_can_admin_product($product_name)"
+><code class="code">check_can_admin_product($product_name)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Checks whether the user is allowed to administrate the product.
+
+ Params: $product_name - a product name.
+
+ Returns: On success, a product object. On failure, an error is thrown.</pre>
+
+<dt><a name="can_request_flag($flag_type)"
+><code class="code">can_request_flag($flag_type)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Checks whether the user can request flags of the given type.
+
+ Params: $flag_type - a Bugzilla::FlagType object.
+
+ Returns: 1 if the user can request flags of the given type,
+ 0 otherwise.</pre>
+
+<dt><a name="can_set_flag($flag_type)"
+><code class="code">can_set_flag($flag_type)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Checks whether the user can set flags of the given type.
+
+ Params: $flag_type - a Bugzilla::FlagType object.
+
+ Returns: 1 if the user can set flags of the given type,
+ 0 otherwise.</pre>
+
+<dt><a name="get_userlist"
+><code class="code">get_userlist</code></a></dt>
+
+<dd>
+<p>Returns a reference to an array of users. The array is populated with hashrefs containing the login, identity and visibility. Users that are not visible to this user will have &#39;visible&#39; set to zero.</p>
+
+<dt><a name="direct_group_membership"
+><code class="code">direct_group_membership</code></a></dt>
+
+<dd>
+<p>Returns a reference to an array of group objects. Groups the user belong to by group inheritance are excluded from the list.</p>
+
+<dt><a name="visible_groups_inherited"
+><code class="code">visible_groups_inherited</code></a></dt>
+
+<dd>
+<p>Returns a list of all groups whose members should be visible to this user. Since this list is flattened already, there is no need for all users to be have derived groups up-to-date to select the users meeting this criteria.</p>
+
+<dt><a name="visible_groups_direct"
+><code class="code">visible_groups_direct</code></a></dt>
+
+<dd>
+<p>Returns a list of groups that the user is aware of.</p>
+
+<dt><a name="visible_groups_as_string"
+><code class="code">visible_groups_as_string</code></a></dt>
+
+<dd>
+<p>Returns the result of <code class="code">visible_groups_inherited</code> as a string (a comma-separated list).</p>
+
+<dt><a name="product_responsibilities"
+><code class="code">product_responsibilities</code></a></dt>
+
+<dd>
+<p>Retrieve user&#39;s product responsibilities as a list of component objects. Each object is a component the user has a responsibility for.</p>
+
+<dt><a name="can_bless"
+><code class="code">can_bless</code></a></dt>
+
+<dd>
+<p>When called with no arguments: Returns <code class="code">1</code> if the user can bless at least one group, returns <code class="code">0</code> otherwise.</p>
+
+<p>When called with one argument: Returns <code class="code">1</code> if the user can bless the group with that id, returns <code class="code">0</code> otherwise.</p>
+
+<dt><a name="wants_bug_mail"
+><code class="code">wants_bug_mail</code></a></dt>
+
+<dd>
+<p>Returns true if the user wants mail for a given bug change.</p>
+
+<dt><a name="wants_mail"
+><code class="code">wants_mail</code></a></dt>
+
+<dd>
+<p>Returns true if the user wants mail for a given set of events. This method is more general than <code class="code">wants_bug_mail</code>, allowing you to check e.g. permissions for flag mail.</p>
+
+<dt><a name="is_mover"
+><code class="code">is_mover</code></a></dt>
+
+<dd>
+<p>Returns true if the user is in the list of users allowed to move bugs to another database. Note that this method doesn&#39;t check whether bug moving is enabled.</p>
+
+<dt><a name="is_insider"
+><code class="code">is_insider</code></a></dt>
+
+<dd>
+<p>Returns true if the user can access private comments and attachments, i.e. if the &#39;insidergroup&#39; parameter is set and the user belongs to this group.</p>
+
+<dt><a name="is_global_watcher"
+><code class="code">is_global_watcher</code></a></dt>
+
+<dd>
+<p>Returns true if the user is a global watcher, i.e. if the &#39;globalwatchers&#39; parameter contains the user.</p>
+</dd>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="CLASS_FUNCTIONS"
+>CLASS FUNCTIONS</a></h1>
+
+<p>These are functions that are not called on a User object, but instead are called &#34;statically,&#34; just like a normal procedural function.</p>
+
+<dl>
+<dt><a name="create"
+><code class="code">create</code></a></dt>
+
+<dd>
+<p>The same as <a href="../Bugzilla/Object.html#create" class="podlinkpod"
+>&#34;create&#34; in Bugzilla::Object</a>.</p>
+
+<p>Params: login_name - <b>Required</b> The login name for the new user. realname - The full name for the new user. cryptpassword - <b>Required</b> The password for the new user. Even though the name says &#34;crypt&#34;, you should just specify a plain-text password. If you specify &#39;*&#39;, the user will not be able to log in using DB authentication. disabledtext - The disable-text for the new user. If given, the user will be disabled, meaning he cannot log in. Defaults to an empty string. disable_mail - If 1, bug-related mail will not be sent to this user; if 0, mail will be sent depending on the user&#39;s email preferences.</p>
+
+<dt><a name="check"
+><code class="code">check</code></a></dt>
+
+<dd>
+<p>Takes a username as its only argument. Throws an error if there is no user with that username. Returns a <code class="code">Bugzilla::User</code> object.</p>
+
+<dt><a name="is_available_username"
+><code class="code">is_available_username</code></a></dt>
+
+<dd>
+<p>Returns a boolean indicating whether or not the supplied username is already taken in Bugzilla.</p>
+
+<p>Params: $username (scalar, string) - The full login name of the username that you are checking. $old_username (scalar, string) - If you are checking an email-change token, insert the &#34;old&#34; username that the user is changing from, here. Then, as long as it&#39;s the right user for that token, he can change his username to $username. (That is, this function will return a boolean true value).</p>
+
+<dt><a name="login_to_id($login,_$throw_error)"
+><code class="code">login_to_id($login, $throw_error)</code></a></dt>
+
+<dd>
+<p>Takes a login name of a Bugzilla user and changes that into a numeric ID for that user. This ID can then be passed to Bugzilla::User::new to create a new user.</p>
+
+<p>If no valid user exists with that login name, then the function returns 0. However, if $throw_error is set, the function will throw a user error instead of returning.</p>
+
+<p>This function can also be used when you want to just find out the userid of a user, but you don&#39;t want the full weight of Bugzilla::User.</p>
+
+<p>However, consider using a Bugzilla::User object instead of this function if you need more information about the user than just their ID.</p>
+
+<dt><a name="user_id_to_login($user_id)"
+><code class="code">user_id_to_login($user_id)</code></a></dt>
+
+<dd>
+<p>Returns the login name of the user account for the given user ID. If no valid user ID is given or the user has no entry in the profiles table, we return an empty string.</p>
+
+<dt><a name="validate_password($passwd1,_$passwd2)"
+><code class="code">validate_password($passwd1, $passwd2)</code></a></dt>
+
+<dd>
+<p>Returns true if a password is valid (i.e. meets Bugzilla&#39;s requirements for length and content), else returns false. Untaints <code class="code">$passwd1</code> if successful.</p>
+
+<p>If a second password is passed in, this function also verifies that the two passwords match.</p>
+
+<dt><a name="match_field($data,_$fields,_$behavior)"
+><code class="code">match_field($data, $fields, $behavior)</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Wrapper for the <code class="code">match()</code> function.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a
+><code class="code">$fields</code> - A hashref with field names as keys and a hash as values. Each hash is of the form { &#39;type&#39; =&#62; &#39;single|multi&#39; }, which specifies whether the field can take a single login name only or several.</a></dt>
+
+<dd>
+<dt><a
+><code class="code">$data</code> (optional) - A hashref with field names as keys and field values as values. If undefined, <code class="code">Bugzilla-&#62;input_params</code> is used.</a></dt>
+
+<dd>
+<dt><a
+><code class="code">$behavior</code> (optional) - If set to <code class="code">MATCH_SKIP_CONFIRM</code>, no confirmation screen is displayed. In that case, the fields which don&#39;t match a unique user are left undefined. If not set, a confirmation screen is displayed if at least one field doesn&#39;t match any login name or match more than one.</a></dt>
+</dl>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>If the third parameter is set to <code class="code">MATCH_SKIP_CONFIRM</code>, the function returns either <code class="code">USER_MATCH_SUCCESS</code> if all fields can be set unambiguously, <code class="code">USER_MATCH_FAILED</code> if at least one field doesn&#39;t match any user account, or <code class="code">USER_MATCH_MULTIPLE</code> if some fields match more than one user account.</p>
+
+<p>If the third parameter is not set, then if all fields could be set unambiguously, nothing is returned, else a confirmation page is displayed.</p>
+
+<dt><a name="Note"
+><b>Note</b></a></dt>
+
+<dd>
+<p>This function must be called early in a script, before anything external is done with the data.</p>
+</dd>
+</dl>
+</dd>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SEE_ALSO"
+>SEE ALSO</a></h1>
+
+<p><a href="../Bugzilla.html" class="podlinkpod"
+>Bugzilla</a></p>
+<p class="backlinkbottom"><b><a name="___bottom" href="../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/User/Setting.html b/docs/en/html/api/Bugzilla/User/Setting.html
new file mode 100644
index 000000000..31c63f554
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/User/Setting.html
@@ -0,0 +1,176 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::User::Setting</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href="../.././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::User::Setting</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#CLASS_FUNCTIONS'>CLASS FUNCTIONS</a>
+ <li class='indexItem indexItem1'><a href='#METHODS'>METHODS</a>
+ <li class='indexItem indexItem1'><a href='#POD_ERRORS'>POD ERRORS</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::User::Setting - Object for a user preference setting</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<p>Setting.pm creates a setting object,
+which is a hash containing the user preference information for a single preference for a single user.
+These are usually accessed through the &#34;settings&#34; object of a user,
+and not directly.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>use Bugzilla::User::Setting; my $settings;</p>
+
+<p>$settings-&#62;{$setting_name} = new Bugzilla::User::Setting( $setting_name,
+$user_id);</p>
+
+<p>OR</p>
+
+<p>$settings-&#62;{$setting_name} = new Bugzilla::User::Setting( $setting_name,
+$user_id,
+$is_enabled,
+$default_value,
+$value,
+$is_default);</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="CLASS_FUNCTIONS"
+>CLASS FUNCTIONS</a></h1>
+
+<dl>
+<dt><a name="add_setting($name,_\@values,_$default_value,_$subclass,_$force_check)"
+><code class="code">add_setting($name,
+\@values,
+$default_value,
+$subclass,
+$force_check)</code></a></dt>
+
+<dd>
+<p>Description: Checks for the existence of a setting,
+and adds it to the database if it does not yet exist.</p>
+
+<p>Params: <code class="code">$name</code> - string - the name of the new setting <code class="code">$values</code> - arrayref - contains the new choices for the new Setting.
+<code class="code">$default_value</code> - string - the site default <code class="code">$subclass</code> - string - name of the module returning the list of valid values.
+This means legal values are not stored in the DB.
+<code class="code">$force_check</code> - boolean - when true,
+the existing setting and all its values are deleted and replaced by new data.</p>
+
+<p>Returns: a pointer to a hash of settings</p>
+
+<dt><a name="get_all_settings($user_id)"
+><code class="code">get_all_settings($user_id)</code></a></dt>
+
+<dd>
+<p>Description: Provides the user&#39;s choices for each setting in the system; if the user has made no choice,
+uses the site default instead.
+Params: <code class="code">$user_id</code> - integer - the user id.
+Returns: a pointer to a hash of settings</p>
+
+<dt><a name="get_defaults($user_id)"
+><code class="code">get_defaults($user_id)</code></a></dt>
+
+<dd>
+<p>Description: When a user is not logged in,
+they must use the site defaults for every settings; this subroutine provides them.
+Params: <code class="code">$user_id</code> (optional) - integer - the user id.
+Note that this optional parameter is mainly for internal use only.
+Returns: A pointer to a hash of settings.
+If $user_id was passed,
+set the user_id value for each setting.</p>
+
+<dt><a name="set_default($setting_name,_$default_value,_$is_enabled)"
+><code class="code">set_default($setting_name,
+$default_value,
+$is_enabled)</code></a></dt>
+
+<dd>
+<p>Description: Sets the global default for a given setting.
+Also sets whether users are allowed to choose their own value for this setting,
+or if they must use the global default.
+Params: <code class="code">$setting_name</code> - string - the name of the setting <code class="code">$default_value</code> - string - the new default value for this setting <code class="code">$is_enabled</code> - boolean - if false,
+all users must use the global default Returns: nothing</p>
+</dd>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="METHODS"
+>METHODS</a></h1>
+
+<dl>
+<dt><a name="legal_values($setting_name)"
+><code class="code">legal_values($setting_name)</code></a></dt>
+
+<dd>
+<p>Description: Returns all legal values for this setting Params: none Returns: A reference to an array containing all legal values</p>
+
+<dt><a name="validate_value"
+><code class="code">validate_value</code></a></dt>
+
+<dd>
+<p>Description: Determines whether a value is valid for the setting by checking against the list of legal values.
+Untaints the parameter if the value is indeed valid,
+and throws a setting_value_invalid code error if not.
+Params: An lvalue containing a candidate for a setting value Returns: nothing</p>
+
+<dt><a name="reset_to_default"
+><code class="code">reset_to_default</code></a></dt>
+
+<dd>
+<p>Description: If a user chooses to use the global default for a given setting,
+their saved entry is removed from the database via this subroutine.
+Params: none Returns: nothing</p>
+
+<dt><a name="set($value)"
+><code class="code">set($value)</code></a></dt>
+
+<dd>
+<p>Description: If a user chooses to use their own value rather than the global value for a given setting,
+OR changes their value for a given setting,
+this subroutine is called to insert or update the database as appropriate.
+Params: <code class="code">$value</code> - string - the new value for this setting for this user.
+Returns: nothing</p>
+</dd>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="POD_ERRORS"
+>POD ERRORS</a></h1>
+
+<p>Hey!
+<b>The above document had some coding errors,
+which are explained below:</b></p>
+
+<dl>
+<dt><a name="Around_line_397:"
+>Around line 397:</a></dt>
+
+<dd>
+<p>You forgot a &#39;=back&#39; before &#39;=head1&#39;</p>
+</dd>
+</dl>
+<p class="backlinkbottom"><b><a name="___bottom" href="../../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/User/Setting/Lang.html b/docs/en/html/api/Bugzilla/User/Setting/Lang.html
new file mode 100644
index 000000000..ea1d27346
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/User/Setting/Lang.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::User::Setting::Lang</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href="../../.././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../../../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::User::Setting::Lang</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#METHODS'>METHODS</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::User::Setting::Lang - Object for a user preference setting for preferred language</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>Lang.pm extends Bugzilla::User::Setting and implements a class specialized for setting the preferred language.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="METHODS"
+>METHODS</a></h1>
+
+<dl>
+<dt><a name="legal_values()"
+><code class="code">legal_values()</code></a></dt>
+
+<dd>
+<p>Description: Returns all legal languages Params: none Returns: A reference to an array containing the names of all legal languages</p>
+</dd>
+</dl>
+<p class="backlinkbottom"><b><a name="___bottom" href="../../../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/User/Setting/Skin.html b/docs/en/html/api/Bugzilla/User/Setting/Skin.html
new file mode 100644
index 000000000..72d01f1e8
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/User/Setting/Skin.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::User::Setting::Skin</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href="../../.././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../../../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::User::Setting::Skin</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#METHODS'>METHODS</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::User::Setting::Skin - Object for a user preference setting for skins</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>Skin.pm extends Bugzilla::User::Setting and implements a class specialized for skins settings.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="METHODS"
+>METHODS</a></h1>
+
+<dl>
+<dt><a name="legal_values()"
+><code class="code">legal_values()</code></a></dt>
+
+<dd>
+<p>Description: Returns all legal skins Params: none Returns: A reference to an array containing the names of all legal skins</p>
+</dd>
+</dl>
+<p class="backlinkbottom"><b><a name="___bottom" href="../../../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/User/Setting/Timezone.html b/docs/en/html/api/Bugzilla/User/Setting/Timezone.html
new file mode 100644
index 000000000..6ca1a8b27
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/User/Setting/Timezone.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::User::Setting::Timezone</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href="../../.././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../../../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::User::Setting::Timezone</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#METHODS'>METHODS</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::User::Setting::Timezone - Object for a user preference setting for desired timezone</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>Timezone.pm extends Bugzilla::User::Setting and implements a class specialized for setting the desired timezone.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="METHODS"
+>METHODS</a></h1>
+
+<dl>
+<dt><a name="legal_values()"
+><code class="code">legal_values()</code></a></dt>
+
+<dd>
+<p>Description: Returns all legal timezones</p>
+
+<p>Params: none</p>
+
+<p>Returns: A reference to an array containing the names of all legal timezones</p>
+</dd>
+</dl>
+<p class="backlinkbottom"><b><a name="___bottom" href="../../../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/Util.html b/docs/en/html/api/Bugzilla/Util.html
new file mode 100644
index 000000000..25351c1ac
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/Util.html
@@ -0,0 +1,428 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::Util</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href=".././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::Util</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#FUNCTIONS'>FUNCTIONS</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#Tainting'>Tainting</a>
+ <li class='indexItem indexItem2'><a href='#Quoting'>Quoting</a>
+ <li class='indexItem indexItem2'><a href='#Environment_and_Location'>Environment and Location</a>
+ <li class='indexItem indexItem2'><a href='#Data_Manipulation'>Data Manipulation</a>
+ <li class='indexItem indexItem2'><a href='#String_Manipulation'>String Manipulation</a>
+ <li class='indexItem indexItem2'><a href='#Formatting_Time'>Formatting Time</a>
+ <li class='indexItem indexItem2'><a href='#Files'>Files</a>
+ <li class='indexItem indexItem2'><a href='#Cryptography'>Cryptography</a>
+ <li class='indexItem indexItem2'><a href='#Validation'>Validation</a>
+ <li class='indexItem indexItem2'><a href='#Database'>Database</a>
+ </ul>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::Util - Generic utility functions for bugzilla</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<pre class="code"> use Bugzilla::Util;
+
+ # Functions for dealing with variable tainting
+ trick_taint($var);
+ detaint_natural($var);
+ detaint_signed($var);
+
+ # Functions for quoting
+ html_quote($var);
+ url_quote($var);
+ xml_quote($var);
+ email_filter($var);
+
+ # Functions for decoding
+ $rv = url_decode($var);
+
+ # Functions that tell you about your environment
+ my $is_cgi = i_am_cgi();
+ my $urlbase = correct_urlbase();
+
+ # Data manipulation
+ ($removed, $added) = diff_arrays(\@old, \@new);
+
+ # Functions for manipulating strings
+ $val = trim(&#34; abc &#34;);
+ $wrapped = wrap_comment($comment);
+
+ # Functions for formatting time
+ format_time($time);
+ datetime_from($time, $timezone);
+
+ # Functions for dealing with files
+ $time = file_mod_time($filename);
+
+ # Cryptographic Functions
+ $crypted_password = bz_crypt($password);
+ $new_password = generate_random_password($password_length);
+
+ # Validation Functions
+ validate_email_syntax($email);
+ validate_date($date);
+
+ # DB-related functions
+ on_main_db {
+ ... code here ...
+ };</pre>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>This package contains various utility functions which do not belong anywhere else.</p>
+
+<p><b>It is not intended as a general dumping group for something which people feel might be useful somewhere, someday</b>. Do not add methods to this package unless it is intended to be used for a significant number of files, and it does not belong anywhere else.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="FUNCTIONS"
+>FUNCTIONS</a></h1>
+
+<p>This package provides several types of routines:</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Tainting"
+>Tainting</a></h2>
+
+<p>Several functions are available to deal with tainted variables. <b>Use these with care</b> to avoid security holes.</p>
+
+<dl>
+<dt><a name="trick_taint($val)"
+><code class="code">trick_taint($val)</code></a></dt>
+
+<dd>
+<p>Tricks perl into untainting a particular variable.</p>
+
+<p>Use trick_taint() when you know that there is no way that the data in a scalar can be tainted, but taint mode still bails on it.</p>
+
+<p><b>WARNING!! Using this routine on data that really could be tainted defeats the purpose of taint mode. It should only be used on variables that have been sanity checked in some way and have been determined to be OK.</b></p>
+
+<dt><a name="detaint_natural($num)"
+><code class="code">detaint_natural($num)</code></a></dt>
+
+<dd>
+<p>This routine detaints a natural number. It returns a true value if the value passed in was a valid natural number, else it returns false. You <b>MUST</b> check the result of this routine to avoid security holes.</p>
+
+<dt><a name="detaint_signed($num)"
+><code class="code">detaint_signed($num)</code></a></dt>
+
+<dd>
+<p>This routine detaints a signed integer. It returns a true value if the value passed in was a valid signed integer, else it returns false. You <b>MUST</b> check the result of this routine to avoid security holes.</p>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Quoting"
+>Quoting</a></h2>
+
+<p>Some values may need to be quoted from perl. However, this should in general be done in the template where possible.</p>
+
+<dl>
+<dt><a name="html_quote($val)"
+><code class="code">html_quote($val)</code></a></dt>
+
+<dd>
+<p>Returns a value quoted for use in HTML, with &#38;, &#60;, &#62;, &#34; and @ being replaced with their appropriate HTML entities. Also, Unicode BiDi controls are deleted.</p>
+
+<dt><a name="html_light_quote($val)"
+><code class="code">html_light_quote($val)</code></a></dt>
+
+<dd>
+<p>Returns a string where only explicitly allowed HTML elements and attributes are kept. All HTML elements and attributes not being in the whitelist are either escaped (if HTML::Scrubber is not installed) or removed.</p>
+
+<dt><a name="url_quote($val)"
+><code class="code">url_quote($val)</code></a></dt>
+
+<dd>
+<p>Quotes characters so that they may be included as part of a url.</p>
+
+<dt><a name="css_class_quote($val)"
+><code class="code">css_class_quote($val)</code></a></dt>
+
+<dd>
+<p>Quotes characters so that they may be used as CSS class names. Spaces and forward slashes are replaced by underscores.</p>
+
+<dt><a name="xml_quote($val)"
+><code class="code">xml_quote($val)</code></a></dt>
+
+<dd>
+<p>This is similar to <code class="code">html_quote</code>, except that &#39; is escaped to &#38;apos;. This is kept separate from html_quote partly for compatibility with previous code (for &#38;apos;) and partly for future handling of non-ASCII characters.</p>
+
+<dt><a name="url_decode($val)"
+><code class="code">url_decode($val)</code></a></dt>
+
+<dd>
+<p>Converts the %xx encoding from the given URL back to its original form.</p>
+
+<dt><a name="email_filter"
+><code class="code">email_filter</code></a></dt>
+
+<dd>
+<p>Removes the hostname from email addresses in the string, if the user currently viewing Bugzilla is logged out. If the user is logged-in, this filter just returns the input string.</p>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Environment_and_Location"
+>Environment and Location</a></h2>
+
+<p>Functions returning information about your environment or location.</p>
+
+<dl>
+<dt><a name="i_am_cgi()"
+><code class="code">i_am_cgi()</code></a></dt>
+
+<dd>
+<p>Tells you whether or not you are being run as a CGI script in a web server. For example, it would return false if the caller is running in a command-line script.</p>
+
+<dt><a name="correct_urlbase()"
+><code class="code">correct_urlbase()</code></a></dt>
+
+<dd>
+<p>Returns either the <code class="code">sslbase</code> or <code class="code">urlbase</code> parameter, depending on the current setting for the <code class="code">ssl_redirect</code> parameter.</p>
+
+<dt><a name="use_attachbase()"
+><code class="code">use_attachbase()</code></a></dt>
+
+<dd>
+<p>Returns true if an alternate host is used to display attachments; false otherwise.</p>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Data_Manipulation"
+>Data Manipulation</a></h2>
+
+<dl>
+<dt><a name="diff_arrays(\@old,_\@new)"
+><code class="code">diff_arrays(\@old, \@new)</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Takes two arrayrefs, and will tell you what it takes to
+ get from @old to @new.
+ Params: @old = array that you are changing from
+ @new = array that you are changing to
+ Returns: A list of two arrayrefs. The first is a reference to an
+ array containing items that were removed from @old. The
+ second is a reference to an array containing items
+ that were added to @old. If both returned arrays are
+ empty, @old and @new contain the same values.</pre>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="String_Manipulation"
+>String Manipulation</a></h2>
+
+<dl>
+<dt><a name="trim($str)"
+><code class="code">trim($str)</code></a></dt>
+
+<dd>
+<p>Removes any leading or trailing whitespace from a string. This routine does not modify the existing string.</p>
+
+<dt><a name="wrap_hard($string,_$size)"
+><code class="code">wrap_hard($string, $size)</code></a></dt>
+
+<dd>
+<p>Wraps a string, so that a line is <i>never</i> longer than <code class="code">$size</code>. Returns the string, wrapped.</p>
+
+<dt><a name="wrap_comment($comment)"
+><code class="code">wrap_comment($comment)</code></a></dt>
+
+<dd>
+<p>Takes a bug comment, and wraps it to the appropriate length. The length is currently specified in <code class="code">Bugzilla::Constants::COMMENT_COLS</code>. Lines beginning with &#34;&#62;&#34; are assumed to be quotes, and they will not be wrapped.</p>
+
+<p>The intended use of this function is to wrap comments that are about to be displayed or emailed. Generally, wrapped text should not be stored in the database.</p>
+
+<dt><a name="find_wrap_point($string,_$maxpos)"
+><code class="code">find_wrap_point($string, $maxpos)</code></a></dt>
+
+<dd>
+<p>Search for a comma, a whitespace or a hyphen to split $string, within the first $maxpos characters. If none of them is found, just split $string at $maxpos. The search starts at $maxpos and goes back to the beginning of the string.</p>
+
+<dt><a name="is_7bit_clean($str)"
+><code class="code">is_7bit_clean($str)</code></a></dt>
+
+<dd>
+<p>Returns true is the string contains only 7-bit characters (ASCII 32 through 126, ASCII 10 (LineFeed) and ASCII 13 (Carrage Return).</p>
+
+<dt><a name="disable_utf8()"
+><code class="code">disable_utf8()</code></a></dt>
+
+<dd>
+<p>Disable utf8 on STDOUT (and display raw data instead).</p>
+
+<dt><a
+><code class="code">clean_text($str)</code> Returns the parameter &#34;cleaned&#34; by exchanging non-printable characters with spaces. Specifically characters (ASCII 0 through 31) and (ASCII 127) will become ASCII 32 (Space).</a></dt>
+
+<dd>
+<dt><a name="get_text"
+><code class="code">get_text</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>This is a method of getting localized strings within Bugzilla code. Use this when you don&#39;t want to display a whole template, you just want a particular string.</p>
+
+<p>It uses the <em class="code">global/message.txt.tmpl</em> template to return a string.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="$message_-_The_identifier_for_the_message."
+><code class="code">$message</code> - The identifier for the message.</a></dt>
+
+<dd>
+<dt><a name="$vars_-_A_hashref._Any_variables_you_want_to_pass_to_the_template."
+><code class="code">$vars</code> - A hashref. Any variables you want to pass to the template.</a></dt>
+</dl>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>A string.</p>
+</dd>
+</dl>
+
+<dt><a name="template_var"
+><code class="code">template_var</code></a></dt>
+
+<dd>
+<p>This is a method of getting the value of a variable from a template in Perl code. The available variables are in the <code class="code">global/field-descs.none.tmpl</code> template. Just pass in the name of the variable that you want the value of.</p>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Formatting_Time"
+>Formatting Time</a></h2>
+
+<dl>
+<dt><a name="format_time($time)"
+><code class="code">format_time($time)</code></a></dt>
+
+<dd>
+<p>Takes a time and converts it to the desired format and timezone. If no format is given, the routine guesses the correct one and returns an empty array if it cannot. If no timezone is given, the user&#39;s timezone is used, as defined in his preferences.</p>
+
+<p>This routine is mainly called from templates to filter dates, see &#34;FILTER time&#34; in <a href="../Bugzilla/Template.html" class="podlinkpod"
+>Bugzilla::Template</a>.</p>
+
+<dt><a name="format_time_decimal($time)"
+><code class="code">format_time_decimal($time)</code></a></dt>
+
+<dd>
+<p>Returns a number with 2 digit precision, unless the last digit is a 0. Then it returns only 1 digit precision.</p>
+
+<dt><a name="datetime_from($time,_$timezone)"
+><code class="code">datetime_from($time, $timezone)</code></a></dt>
+
+<dd>
+<p>Returns a DateTime object given a date string. If the string is not in some valid date format that <code class="code">strptime</code> understands, we return <code class="code">undef</code>.</p>
+
+<p>You can optionally specify a timezone for the returned date. If not specified, defaults to the currently-logged-in user&#39;s timezone, or the Bugzilla server&#39;s local timezone if there isn&#39;t a logged-in user.</p>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Files"
+>Files</a></h2>
+
+<dl>
+<dt><a name="file_mod_time($filename)"
+><code class="code">file_mod_time($filename)</code></a></dt>
+
+<dd>
+<p>Takes a filename and returns the modification time. It returns it in the format of the &#34;mtime&#34; parameter of the perl &#34;stat&#34; function.</p>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Cryptography"
+>Cryptography</a></h2>
+
+<dl>
+<dt><a name="bz_crypt($password,_$salt)"
+><code class="code">bz_crypt($password, $salt)</code></a></dt>
+
+<dd>
+<p>Takes a string and returns a hashed (encrypted) value for it, using a random salt. An optional salt string may also be passed in.</p>
+
+<p>Please always use this function instead of the built-in perl <code class="code">crypt</code> function, when checking or setting a password. Bugzilla does not use <code class="code">crypt</code>.</p>
+
+<dt><a name="generate_random_password($password_length)"
+><code class="code">generate_random_password($password_length)</code></a></dt>
+
+<dd>
+<p>Returns an alphanumeric string with the specified length (10 characters by default). Use this function to generate passwords and tokens.</p>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Validation"
+>Validation</a></h2>
+
+<dl>
+<dt><a name="validate_email_syntax($email)"
+><code class="code">validate_email_syntax($email)</code></a></dt>
+
+<dd>
+<p>Do a syntax checking for a legal email address and returns 1 if the check is successful, else returns 0. Untaints <code class="code">$email</code> if successful.</p>
+
+<dt><a name="validate_date($date)"
+><code class="code">validate_date($date)</code></a></dt>
+
+<dd>
+<p>Make sure the date has the correct format and returns 1 if the check is successful, else returns 0.</p>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Database"
+>Database</a></h2>
+
+<dl>
+<dt><a name="on_main_db"
+><code class="code">on_main_db</code></a></dt>
+
+<dd>
+<p>Runs a block of code always on the main DB. Useful for when you&#39;re inside a subroutine and need to do some writes to the database, but don&#39;t know if Bugzilla is currently using the shadowdb or not. Used like:</p>
+
+<pre class="code"> on_main_db {
+ my $dbh = Bugzilla-&#62;dbh;
+ $dbh-&#62;do(&#34;INSERT ...&#34;);
+ }</pre>
+</dd>
+</dl>
+<p class="backlinkbottom"><b><a name="___bottom" href="../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/Version.html b/docs/en/html/api/Bugzilla/Version.html
new file mode 100644
index 000000000..d6532a419
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/Version.html
@@ -0,0 +1,78 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::Version</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href=".././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::Version</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#METHODS'>METHODS</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::Version - Bugzilla product version class.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<pre class="code"> use Bugzilla::Version;
+
+ my $version = new Bugzilla::Version({ name =&#62; $name, product =&#62; $product });
+
+ my $value = $version-&#62;name;
+ my $product_id = $version-&#62;product_id;
+ my $product = $version-&#62;product;
+
+ my $version = Bugzilla::Version-&#62;create(
+ { value =&#62; $name, product =&#62; $product });
+
+ $version-&#62;set_name($new_name);
+ $version-&#62;update();
+
+ $version-&#62;remove_from_db;</pre>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>Version.pm represents a Product Version object. It is an implementation of <a href="../Bugzilla/Object.html" class="podlinkpod"
+>Bugzilla::Object</a>, and thus provides all methods that <a href="../Bugzilla/Object.html" class="podlinkpod"
+>Bugzilla::Object</a> provides.</p>
+
+<p>The methods that are specific to <code class="code">Bugzilla::Version</code> are listed below.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="METHODS"
+>METHODS</a></h1>
+
+<dl>
+<dt><a name="bug_count()"
+><code class="code">bug_count()</code></a></dt>
+
+<dd>
+<pre class="code"> Description: Returns the total of bugs that belong to the version.
+
+ Params: none.
+
+ Returns: Integer with the number of bugs.</pre>
+</dd>
+</dl>
+<p class="backlinkbottom"><b><a name="___bottom" href="../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/WebService.html b/docs/en/html/api/Bugzilla/WebService.html
new file mode 100644
index 000000000..e41d1d56f
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/WebService.html
@@ -0,0 +1,345 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::WebService</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href=".././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::WebService</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#CALLING_METHODS'>CALLING METHODS</a>
+ <li class='indexItem indexItem1'><a href='#PARAMETERS'>PARAMETERS</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#How_Bugzilla_WebService_Methods_Take_Parameters'>How Bugzilla WebService Methods Take Parameters</a>
+ </ul>
+ <li class='indexItem indexItem1'><a href='#LOGGING_IN'>LOGGING IN</a>
+ <li class='indexItem indexItem1'><a href='#STABLE%2C_EXPERIMENTAL%2C_and_UNSTABLE'>STABLE, EXPERIMENTAL, and UNSTABLE</a>
+ <li class='indexItem indexItem1'><a href='#ERRORS'>ERRORS</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#Transient_vs._Fatal_Errors'>Transient vs. Fatal Errors</a>
+ <li class='indexItem indexItem2'><a href='#Unknown_Errors'>Unknown Errors</a>
+ </ul>
+ <li class='indexItem indexItem1'><a href='#COMMON_PARAMETERS'>COMMON PARAMETERS</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#Limiting_What_Fields_Are_Returned'>Limiting What Fields Are Returned</a>
+ </ul>
+ <li class='indexItem indexItem1'><a href='#SEE_ALSO'>SEE ALSO</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#Server_Types'>Server Types</a>
+ <li class='indexItem indexItem2'><a href='#WebService_Methods'>WebService Methods</a>
+ </ul>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::WebService - The Web Service interface to Bugzilla</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>This is the standard API for external programs that want to interact with Bugzilla.
+It provides various methods in various modules.</p>
+
+<p>You can interact with this API via <a href="../Bugzilla/WebService/Server/XMLRPC.html" class="podlinkpod"
+>XML-RPC</a> or <a href="../Bugzilla/WebService/Server/JSONRPC.html" class="podlinkpod"
+>JSON-RPC</a>.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="CALLING_METHODS"
+>CALLING METHODS</a></h1>
+
+<p>Methods are grouped into &#34;packages&#34;,
+like <code class="code">Bug</code> for <a href="../Bugzilla/WebService/Bug.html" class="podlinkpod"
+>Bugzilla::WebService::Bug</a>.
+So,
+for example,
+<a href="../Bugzilla/WebService/Bug.html#get" class="podlinkpod"
+>&#34;get&#34; in Bugzilla::WebService::Bug</a>,
+is called as <code class="code">Bug.get</code>.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="PARAMETERS"
+>PARAMETERS</a></h1>
+
+<p>The Bugzilla API takes the following various types of parameters:</p>
+
+<dl>
+<dt><a name="int"
+><code class="code">int</code></a></dt>
+
+<dd>
+<p>Integer.
+May be null.</p>
+
+<dt><a name="double"
+><code class="code">double</code></a></dt>
+
+<dd>
+<p>A floating-point number.
+May be null.</p>
+
+<dt><a name="string"
+><code class="code">string</code></a></dt>
+
+<dd>
+<p>A string.
+May be null.</p>
+
+<dt><a name="dateTime"
+><code class="code">dateTime</code></a></dt>
+
+<dd>
+<p>A date/time.
+Represented differently in different interfaces to this API.
+May be null.</p>
+
+<dt><a name="boolean"
+><code class="code">boolean</code></a></dt>
+
+<dd>
+<p>True or false.</p>
+
+<dt><a name="base64"
+><code class="code">base64</code></a></dt>
+
+<dd>
+<p>A base64-encoded string.
+This is the only way to transfer binary data via the WebService.</p>
+
+<dt><a name="array"
+><code class="code">array</code></a></dt>
+
+<dd>
+<p>An array.
+There may be mixed types in an array.</p>
+
+<p>In example code,
+you will see the characters <code class="code">[</code> and <code class="code">]</code> used to represent the beginning and end of arrays.</p>
+
+<p>In our example code in these API docs,
+an array that contains the numbers 1,
+2,
+and 3 would look like:</p>
+
+<pre class="code"> [1, 2, 3]</pre>
+
+<dt><a name="struct"
+><code class="code">struct</code></a></dt>
+
+<dd>
+<p>A mapping of keys to values. Called a &#34;hash&#34;, &#34;dict&#34;, or &#34;map&#34; in some other programming languages. We sometimes call this a &#34;hash&#34; in the API documentation.</p>
+
+<p>The keys are strings, and the values can be any type.</p>
+
+<p>In example code, you will see the characters <code class="code">{</code> and <code class="code">}</code> used to represent the beginning and end of structs.</p>
+
+<p>For example, a struct with an &#34;fruit&#34; key whose value is &#34;oranges&#34;, and a &#34;vegetable&#34; key whose value is &#34;lettuce&#34; would look like:</p>
+
+<pre class="code"> { fruit =&#62; &#39;oranges&#39;, vegetable =&#62; &#39;lettuce&#39; }</pre>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="How_Bugzilla_WebService_Methods_Take_Parameters"
+>How Bugzilla WebService Methods Take Parameters</a></h2>
+
+<p><b>All</b> Bugzilla WebService functions use <i>named</i> parameters. The individual <code class="code">Bugzilla::WebService::Server</code> modules explain how this is implemented for those frontends.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="LOGGING_IN"
+>LOGGING IN</a></h1>
+
+<p>There are various ways to log in:</p>
+
+<dl>
+<dt><a name="User.login"
+><code class="code">User.login</code></a></dt>
+
+<dd>
+<p>You can use <a href="../Bugzilla/WebService/User.html#login" class="podlinkpod"
+>&#34;login&#34; in Bugzilla::WebService::User</a> to log in as a Bugzilla user. This issues standard HTTP cookies that you must then use in future calls, so your client must be capable of receiving and transmitting cookies.</p>
+
+<dt><a name="Bugzilla_login_and_Bugzilla_password"
+><code class="code">Bugzilla_login</code> and <code class="code">Bugzilla_password</code></a></dt>
+
+<dd>
+<p><b>Added in Bugzilla 3.6</b></p>
+
+<p>You can specify <code class="code">Bugzilla_login</code> and <code class="code">Bugzilla_password</code> as arguments to any WebService method, and you will be logged in as that user if your credentials are correct. Here are the arguments you can specify to any WebService method to perform a login:</p>
+
+<dl>
+<dt><a name="Bugzilla_login_(string)_-_A_user&#39;s_login_name."
+><code class="code">Bugzilla_login</code> (string) - A user&#39;s login name.</a></dt>
+
+<dd>
+<dt><a name="Bugzilla_password_(string)_-_That_user&#39;s_password."
+><code class="code">Bugzilla_password</code> (string) - That user&#39;s password.</a></dt>
+
+<dd>
+<dt><a name="Bugzilla_restrictlogin_(boolean)_-_Optional._If_true,_then_your_login_will_only_be_valid_for_your_IP_address."
+><code class="code">Bugzilla_restrictlogin</code> (boolean) - Optional. If true, then your login will only be valid for your IP address.</a></dt>
+
+<dd>
+<dt><a
+><code class="code">Bugzilla_rememberlogin</code> (boolean) - Optional. If true, then the cookie sent back to you with the method response will not expire.</a></dt>
+</dl>
+
+<p>The <code class="code">Bugzilla_restrictlogin</code> and <code class="code">Bugzilla_rememberlogin</code> options are only used when you have also specified <code class="code">Bugzilla_login</code> and <code class="code">Bugzilla_password</code>.</p>
+
+<p>Note that Bugzilla will return HTTP cookies along with the method response when you use these arguments (just like the <code class="code">User.login</code> method above).</p>
+</dd>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="STABLE,_EXPERIMENTAL,_and_UNSTABLE"
+>STABLE, EXPERIMENTAL, and UNSTABLE</a></h1>
+
+<p>Methods are marked <b>STABLE</b> if you can expect their parameters and return values not to change between versions of Bugzilla. You are best off always using methods marked <b>STABLE</b>. We may add parameters and additional items to the return values, but your old code will always continue to work with any new changes we make. If we ever break a <b>STABLE</b> interface, we&#39;ll post a big notice in the Release Notes, and it will only happen during a major new release.</p>
+
+<p>Methods (or parts of methods) are marked <b>EXPERIMENTAL</b> if we <i>believe</i> they will be stable, but there&#39;s a slight chance that small parts will change in the future.</p>
+
+<p>Certain parts of a method&#39;s description may be marked as <b>UNSTABLE</b>, in which case those parts are not guaranteed to stay the same between Bugzilla versions.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="ERRORS"
+>ERRORS</a></h1>
+
+<p>If a particular webservice call fails, it will throw an error in the appropriate format for the frontend that you are using. For all frontends, there is at least a numeric error code and descriptive text for the error.</p>
+
+<p>The various errors that functions can throw are specified by the documentation of those functions.</p>
+
+<p>Each error that Bugzilla can throw has a specific numeric code that will not change between versions of Bugzilla. If your code needs to know what error Bugzilla threw, use the numeric code. Don&#39;t try to parse the description, because that may change from version to version of Bugzilla.</p>
+
+<p>Note that if you display the error to the user in an HTML program, make sure that you properly escape the error, as it will not be HTML-escaped.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Transient_vs._Fatal_Errors"
+>Transient vs. Fatal Errors</a></h2>
+
+<p>If the error code is a number greater than 0, the error is considered &#34;transient,&#34; which means that it was an error made by the user, not some problem with Bugzilla itself.</p>
+
+<p>If the error code is a number less than 0, the error is &#34;fatal,&#34; which means that it&#39;s some error in Bugzilla itself that probably requires administrative attention.</p>
+
+<p>Negative numbers and positive numbers don&#39;t overlap. That is, if there&#39;s an error 302, there won&#39;t be an error -302.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Unknown_Errors"
+>Unknown Errors</a></h2>
+
+<p>Sometimes a function will throw an error that doesn&#39;t have a specific error code. In this case, the code will be <code class="code">-32000</code> if it&#39;s a &#34;fatal&#34; error, and <code class="code">32000</code> if it&#39;s a &#34;transient&#34; error.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="COMMON_PARAMETERS"
+>COMMON PARAMETERS</a></h1>
+
+<p>Many Webservice methods take similar arguments. Instead of re-writing the documentation for each method, we document the parameters here, once, and then refer back to this documentation from the individual methods where these parameters are used.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Limiting_What_Fields_Are_Returned"
+>Limiting What Fields Are Returned</a></h2>
+
+<p>Many WebService methods return an array of structs with various fields in the structs. (For example, <a href="../Bugzilla/WebService/Bug.html#get" class="podlinkpod"
+>&#34;get&#34; in Bugzilla::WebService::Bug</a> returns a list of <code class="code">bugs</code> that have fields like <code class="code">id</code>, <code class="code">summary</code>, <code class="code">creation_time</code>, etc.)</p>
+
+<p>These parameters allow you to limit what fields are present in the structs, to possibly improve performance or save some bandwidth.</p>
+
+<dl>
+<dt><a name="include_fields"
+><code class="code">include_fields</code></a></dt>
+
+<dd>
+<p><code class="code">array</code> An array of strings, representing the (case-sensitive) names of fields in the return value. Only the fields specified in this hash will be returned, the rest will not be included.</p>
+
+<p>If you specify an empty array, then this function will return empty hashes.</p>
+
+<p>Invalid field names are ignored.</p>
+
+<p>Example:</p>
+
+<pre class="code"> User.get( ids =&#62; [1], include_fields =&#62; [&#39;id&#39;, &#39;name&#39;] )</pre>
+
+<p>would return something like:</p>
+
+<pre class="code"> { users =&#62; [{ id =&#62; 1, name =&#62; &#39;user@domain.com&#39; }] }</pre>
+
+<dt><a name="exclude_fields"
+><code class="code">exclude_fields</code></a></dt>
+
+<dd>
+<p><code class="code">array</code> An array of strings, representing the (case-sensitive) names of fields in the return value. The fields specified will not be included in the returned hashes.</p>
+
+<p>If you specify all the fields, then this function will return empty hashes.</p>
+
+<p>Invalid field names are ignored.</p>
+
+<p>Specifying fields here overrides <code class="code">include_fields</code>, so if you specify a field in both, it will be excluded, not included.</p>
+
+<p>Example:</p>
+
+<pre class="code"> User.get( ids =&#62; [1], exclude_fields =&#62; [&#39;name&#39;] )</pre>
+
+<p>would return something like:</p>
+
+<pre class="code"> { users =&#62; [{ id =&#62; 1, real_name =&#62; &#39;John Smith&#39; }] }</pre>
+</dd>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SEE_ALSO"
+>SEE ALSO</a></h1>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Server_Types"
+>Server Types</a></h2>
+
+<dl>
+<dt><a name="Bugzilla::WebService::Server::XMLRPC"
+><a href="../Bugzilla/WebService/Server/XMLRPC.html" class="podlinkpod"
+>Bugzilla::WebService::Server::XMLRPC</a></a></dt>
+
+<dd>
+<dt><a name="Bugzilla::WebService::Server::JSONRPC"
+><a href="../Bugzilla/WebService/Server/JSONRPC.html" class="podlinkpod"
+>Bugzilla::WebService::Server::JSONRPC</a></a></dt>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="WebService_Methods"
+>WebService Methods</a></h2>
+
+<dl>
+<dt><a name="Bugzilla::WebService::Bug"
+><a href="../Bugzilla/WebService/Bug.html" class="podlinkpod"
+>Bugzilla::WebService::Bug</a></a></dt>
+
+<dd>
+<dt><a name="Bugzilla::WebService::Bugzilla"
+><a href="../Bugzilla/WebService/Bugzilla.html" class="podlinkpod"
+>Bugzilla::WebService::Bugzilla</a></a></dt>
+
+<dd>
+<dt><a name="Bugzilla::WebService::Product"
+><a href="../Bugzilla/WebService/Product.html" class="podlinkpod"
+>Bugzilla::WebService::Product</a></a></dt>
+
+<dd>
+<dt><a name="Bugzilla::WebService::User"
+><a href="../Bugzilla/WebService/User.html" class="podlinkpod"
+>Bugzilla::WebService::User</a></a></dt>
+</dl>
+<p class="backlinkbottom"><b><a name="___bottom" href="../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/WebService/Bug.html b/docs/en/html/api/Bugzilla/WebService/Bug.html
new file mode 100644
index 000000000..58c7cf06a
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/WebService/Bug.html
@@ -0,0 +1,2557 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::Webservice::Bug</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href="../.././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::Webservice::Bug</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#METHODS'>METHODS</a>
+ <li class='indexItem indexItem1'><a href='#Utility_Functions'>Utility Functions</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#fields'>fields</a>
+ <li class='indexItem indexItem2'><a href='#legal_values'>legal_values</a>
+ </ul>
+ <li class='indexItem indexItem1'><a href='#Bug_Information'>Bug Information</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#attachments'>attachments</a>
+ <li class='indexItem indexItem2'><a href='#comments'>comments</a>
+ <li class='indexItem indexItem2'><a href='#get'>get</a>
+ <li class='indexItem indexItem2'><a href='#history'>history</a>
+ <li class='indexItem indexItem2'><a href='#search'>search</a>
+ </ul>
+ <li class='indexItem indexItem1'><a href='#Bug_Creation_and_Modification'>Bug Creation and Modification</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#create'>create</a>
+ <li class='indexItem indexItem2'><a href='#add_attachment'>add_attachment</a>
+ <li class='indexItem indexItem2'><a href='#add_comment'>add_comment</a>
+ <li class='indexItem indexItem2'><a href='#update'>update</a>
+ <li class='indexItem indexItem2'><a href='#update_see_also'>update_see_also</a>
+ </ul>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::Webservice::Bug - The API for creating,
+changing,
+and getting the details of bugs.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>This part of the Bugzilla API allows you to file a new bug in Bugzilla,
+or get information about bugs that have already been filed.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="METHODS"
+>METHODS</a></h1>
+
+<p>See <a href="../../Bugzilla/WebService.html" class="podlinkpod"
+>Bugzilla::WebService</a> for a description of how parameters are passed,
+and what <b>STABLE</b>,
+<b>UNSTABLE</b>,
+and <b>EXPERIMENTAL</b> mean.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="Utility_Functions"
+>Utility Functions</a></h1>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="fields"
+>fields</a></h2>
+
+<p><b>UNSTABLE</b></p>
+
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Get information about valid bug fields,
+including the lists of legal values for each field.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<p>You can pass either field ids or field names.</p>
+
+<p><b>Note</b>: If neither <code class="code">ids</code> nor <code class="code">names</code> is specified,
+then all non-obsolete fields will be returned.</p>
+
+<p>In addition to the parameters below,
+this method also accepts the standard <a href="../../Bugzilla/WebService.html#include_fields" class="podlinkpod"
+>include_fields</a> and <a href="../../Bugzilla/WebService.html#exclude_fields" class="podlinkpod"
+>exclude_fields</a> arguments.</p>
+
+<dl>
+<dt><a name="ids_(array)_-_An_array_of_integer_field_ids."
+><code class="code">ids</code> (array) - An array of integer field ids.</a></dt>
+
+<dd>
+<dt><a name="names_(array)_-_An_array_of_strings_representing_field_names."
+><code class="code">names</code> (array) - An array of strings representing field names.</a></dt>
+</dl>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>A hash containing a single element,
+<code class="code">fields</code>.
+This is an array of hashes,
+containing the following keys:</p>
+
+<dl>
+<dt><a name="id"
+><code class="code">id</code></a></dt>
+
+<dd>
+<p><code class="code">int</code> An integer id uniquely idenfifying this field in this installation only.</p>
+
+<dt><a name="type"
+><code class="code">type</code></a></dt>
+
+<dd>
+<p><code class="code">int</code> The number of the fieldtype.
+The following values are defined:</p>
+
+<dl>
+<dt><a name="0_Unknown"
+><code class="code">0</code> Unknown</a></dt>
+
+<dd>
+<dt><a name="1_Free_Text"
+><code class="code">1</code> Free Text</a></dt>
+
+<dd>
+<dt><a name="2_Drop_Down"
+><code class="code">2</code> Drop Down</a></dt>
+
+<dd>
+<dt><a name="3_Multiple-Selection_Box"
+><code class="code">3</code> Multiple-Selection Box</a></dt>
+
+<dd>
+<dt><a name="4_Large_Text_Box"
+><code class="code">4</code> Large Text Box</a></dt>
+
+<dd>
+<dt><a name="5_Date/Time"
+><code class="code">5</code> Date/Time</a></dt>
+
+<dd>
+<dt><a name="6_Bug_Id"
+><code class="code">6</code> Bug Id</a></dt>
+
+<dd>
+<dt><a name="7_Bug_URLs_(&#34;See_Also&#34;)"
+><code class="code">7</code> Bug URLs (&#34;See Also&#34;)</a></dt>
+</dl>
+
+<dt><a name="is_custom"
+><code class="code">is_custom</code></a></dt>
+
+<dd>
+<p><code class="code">boolean</code> True when this is a custom field,
+false otherwise.</p>
+
+<dt><a name="name"
+><code class="code">name</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The internal name of this field.
+This is a unique identifier for this field.
+If this is not a custom field,
+then this name will be the same across all Bugzilla installations.</p>
+
+<dt><a name="display_name"
+><code class="code">display_name</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The name of the field,
+as it is shown in the user interface.</p>
+
+<dt><a name="is_mandatory"
+><code class="code">is_mandatory</code></a></dt>
+
+<dd>
+<p><code class="code">boolean</code> True if the field must have a value when filing new bugs.
+Also,
+mandatory fields cannot have their value cleared when updating bugs.</p>
+
+<dt><a name="is_on_bug_entry"
+><code class="code">is_on_bug_entry</code></a></dt>
+
+<dd>
+<p><code class="code">boolean</code> For custom fields,
+this is true if the field is shown when you enter a new bug.
+For standard fields,
+this is currently always false,
+even if the field shows up when entering a bug.
+(To know whether or not a standard field is valid on bug entry,
+see <a href="#create" class="podlinkpod"
+>&#34;create&#34;</a>.)</p>
+
+<dt><a name="visibility_field"
+><code class="code">visibility_field</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The name of a field that controls the visibility of this field in the user interface.
+This field only appears in the user interface when the named field is equal to one of the values in <code class="code">visibility_values</code>.
+Can be null.</p>
+
+<dt><a name="visibility_values"
+><code class="code">visibility_values</code></a></dt>
+
+<dd>
+<p><code class="code">array</code> of <code class="code">string</code>s This field is only shown when <code class="code">visibility_field</code> matches one of these values.
+When <code class="code">visibility_field</code> is null,
+then this is an empty array.</p>
+
+<dt><a name="value_field"
+><code class="code">value_field</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The name of the field that controls whether or not particular values of the field are shown in the user interface.
+Can be null.</p>
+
+<dt><a name="values"
+><code class="code">values</code></a></dt>
+
+<dd>
+<p>This is an array of hashes,
+representing the legal values for select-type (drop-down and multiple-selection) fields.
+This is also populated for the <code class="code">component</code>,
+<code class="code">version</code>,
+and <code class="code">target_milestone</code> fields,
+but not for the <code class="code">product</code> field (you must use <a href="../../Bugzilla/WebService/Product.html#get_accessible_products" class="podlinkpod"
+>Product.get_accessible_products</a> for that.</p>
+
+<p>For fields that aren&#39;t select-type fields,
+this will simply be an empty array.</p>
+
+<p>Each hash has the following keys:</p>
+
+<dl>
+<dt><a name="name"
+><code class="code">name</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The actual value--this is what you would specify for this field in <a href="#create" class="podlinkpod"
+>&#34;create&#34;</a>,
+etc.</p>
+
+<dt><a name="sortkey"
+><code class="code">sortkey</code></a></dt>
+
+<dd>
+<p><code class="code">int</code> Values,
+when displayed in a list,
+are sorted first by this integer and then secondly by their name.</p>
+
+<dt><a name="visibility_values"
+><code class="code">visibility_values</code></a></dt>
+
+<dd>
+<p>If <code class="code">value_field</code> is defined for this field,
+then this value is only shown if the <code class="code">value_field</code> is set to one of the values listed in this array.
+Note that for per-product fields,
+<code class="code">value_field</code> is set to <code class="code">&#39;product&#39;</code> and <code class="code">visibility_values</code> will reflect which product(s) this value appears in.</p>
+
+<dt><a name="is_open"
+><code class="code">is_open</code></a></dt>
+
+<dd>
+<p><code class="code">boolean</code> For <code class="code">bug_status</code> values,
+determines whether this status specifies that the bug is &#34;open&#34; (true) or &#34;closed&#34; (false).
+This item is only included for the <code class="code">bug_status</code> field.</p>
+
+<dt><a name="can_change_to"
+><code class="code">can_change_to</code></a></dt>
+
+<dd>
+<p>For <code class="code">bug_status</code> values,
+this is an array of hashes that determines which statuses you can transition to from this status.
+(This item is only included for the <code class="code">bug_status</code> field.)</p>
+
+<p>Each hash contains the following items:</p>
+
+<dl>
+<dt><a name="name"
+><code class="code">name</code></a></dt>
+
+<dd>
+<p>the name of the new status</p>
+
+<dt><a name="comment_required"
+><code class="code">comment_required</code></a></dt>
+
+<dd>
+<p>this <code class="code">boolean</code> True if a comment is required when you change a bug into this status using this transition.</p>
+</dd>
+</dl>
+</dd>
+</dl>
+</dd>
+</dl>
+
+<dt><a name="Errors"
+><b>Errors</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="51_(Invalid_Field_Name_or_Id)"
+>51 (Invalid Field Name or Id)</a></dt>
+
+<dd>
+<p>You specified an invalid field name or id.</p>
+</dd>
+</dl>
+
+<dt><a name="History"
+><b>History</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Added_in_Bugzilla_3.6."
+>Added in Bugzilla <b>3.6</b>.</a></dt>
+
+<dd>
+<dt><a name="The_is_mandatory_return_value_was_added_in_Bugzilla_4.0."
+>The <code class="code">is_mandatory</code> return value was added in Bugzilla <b>4.0</b>.</a></dt>
+</dl>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="legal_values"
+>legal_values</a></h2>
+
+<p><b>DEPRECATED</b> - Use <a href="#fields" class="podlinkpod"
+>&#34;fields&#34;</a> instead.</p>
+
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Tells you what values are allowed for a particular field.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a
+><code class="code">field</code> - The name of the field you want information about.
+This should be the same as the name you would use in <a href="#create" class="podlinkpod"
+>&#34;create&#34;</a>,
+below.</a></dt>
+
+<dd>
+<dt><a
+><code class="code">product_id</code> - If you&#39;re picking a product-specific field,
+you have to specify the id of the product you want the values for.</a></dt>
+</dl>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p><code class="code">values</code> - An array of strings: the legal values for this field.
+The values will be sorted as they normally would be in Bugzilla.</p>
+
+<dt><a name="Errors"
+><b>Errors</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="106_(Invalid_Product)"
+>106 (Invalid Product)</a></dt>
+
+<dd>
+<p>You were required to specify a product,
+and either you didn&#39;t,
+or you specified an invalid product (or a product that you can&#39;t access).</p>
+
+<dt><a name="108_(Invalid_Field_Name)"
+>108 (Invalid Field Name)</a></dt>
+
+<dd>
+<p>You specified a field that doesn&#39;t exist or isn&#39;t a drop-down field.</p>
+</dd>
+</dl>
+</dd>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="Bug_Information"
+>Bug Information</a></h1>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="attachments"
+>attachments</a></h2>
+
+<p><b>EXPERIMENTAL</b></p>
+
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>It allows you to get data about attachments,
+given a list of bugs and/or attachment ids.</p>
+
+<p><b>Note</b>: Private attachments will only be returned if you are in the insidergroup or if you are the submitter of the attachment.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<p><b>Note</b>: At least one of <code class="code">ids</code> or <code class="code">attachment_ids</code> is required.</p>
+
+<dl>
+<dt><a name="ids"
+><code class="code">ids</code></a></dt>
+
+<dd>
+<p>See the description of the <code class="code">ids</code> parameter in the <a href="#get" class="podlinkpod"
+>&#34;get&#34;</a> method.</p>
+
+<dt><a name="attachment_ids"
+><code class="code">attachment_ids</code></a></dt>
+
+<dd>
+<p><code class="code">array</code> An array of integer attachment ids.</p>
+</dd>
+</dl>
+
+<p>Also accepts the <a href="../../Bugzilla/WebService.html#include_fields" class="podlinkpod"
+>include_fields</a>,
+and <a href="../../Bugzilla/WebService.html#exclude_fields" class="podlinkpod"
+>exclude_fields</a> arguments.</p>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>A hash containing two elements: <code class="code">bugs</code> and <code class="code">attachments</code>.
+The return value looks like this:</p>
+
+<pre class="code"> {
+ bugs =&#62; {
+ 1345 =&#62; {
+ attachments =&#62; [
+ { (attachment) },
+ { (attachment) }
+ ]
+ },
+ 9874 =&#62; {
+ attachments =&#62; [
+ { (attachment) },
+ { (attachment) }
+ ]
+
+ },
+ },
+
+ attachments =&#62; {
+ 234 =&#62; { (attachment) },
+ 123 =&#62; { (attachment) },
+ }
+ }</pre>
+
+<p>The attachments of any bugs that you specified in the <code class="code">ids</code> argument in input are returned in <code class="code">bugs</code> on output. <code class="code">bugs</code> is a hash that has integer bug IDs for keys and contains a single key, <code class="code">attachments</code>. That key points to an arrayref that contains attachments as a hash. (Fields for attachments are described below.)</p>
+
+<p>For any attachments that you specified directly in <code class="code">attachment_ids</code>, they are returned in <code class="code">attachments</code> on output. This is a hash where the attachment ids point directly to hashes describing the individual attachment.</p>
+
+<p>The fields for each attachment (where it says <code class="code">(attachment)</code> in the diagram above) are:</p>
+
+<dl>
+<dt><a name="data"
+><code class="code">data</code></a></dt>
+
+<dd>
+<p><code class="code">base64</code> The raw data of the attachment, encoded as Base64.</p>
+
+<dt><a name="creation_time"
+><code class="code">creation_time</code></a></dt>
+
+<dd>
+<p><code class="code">dateTime</code> The time the attachment was created.</p>
+
+<dt><a name="last_change_time"
+><code class="code">last_change_time</code></a></dt>
+
+<dd>
+<p><code class="code">dateTime</code> The last time the attachment was modified.</p>
+
+<dt><a name="id"
+><code class="code">id</code></a></dt>
+
+<dd>
+<p><code class="code">int</code> The numeric id of the attachment.</p>
+
+<dt><a name="bug_id"
+><code class="code">bug_id</code></a></dt>
+
+<dd>
+<p><code class="code">int</code> The numeric id of the bug that the attachment is attached to.</p>
+
+<dt><a name="file_name"
+><code class="code">file_name</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The file name of the attachment.</p>
+
+<dt><a name="summary"
+><code class="code">summary</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> A short string describing the attachment.</p>
+
+<p>Also returned as <code class="code">description</code>, for backwards-compatibility with older Bugzillas. (However, this backwards-compatibility will go away in Bugzilla 5.0.)</p>
+
+<dt><a name="content_type"
+><code class="code">content_type</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The MIME type of the attachment.</p>
+
+<dt><a name="is_private"
+><code class="code">is_private</code></a></dt>
+
+<dd>
+<p><code class="code">boolean</code> True if the attachment is private (only visible to a certain group called the &#34;insidergroup&#34;), False otherwise.</p>
+
+<dt><a name="is_obsolete"
+><code class="code">is_obsolete</code></a></dt>
+
+<dd>
+<p><code class="code">boolean</code> True if the attachment is obsolete, False otherwise.</p>
+
+<dt><a name="is_url"
+><code class="code">is_url</code></a></dt>
+
+<dd>
+<p><code class="code">boolean</code> True if the attachment is a URL instead of actual data, False otherwise. Note that such attachments only happen when the Bugzilla installation has at some point had the <code class="code">allow_attach_url</code> parameter enabled.</p>
+
+<dt><a name="is_patch"
+><code class="code">is_patch</code></a></dt>
+
+<dd>
+<p><code class="code">boolean</code> True if the attachment is a patch, False otherwise.</p>
+
+<dt><a name="creator"
+><code class="code">creator</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The login name of the user that created the attachment.</p>
+
+<p>Also returned as <code class="code">attacher</code>, for backwards-compatibility with older Bugzillas. (However, this backwards-compatibility will go away in Bugzilla 5.0.)</p>
+</dd>
+</dl>
+
+<dt><a name="Errors"
+><b>Errors</b></a></dt>
+
+<dd>
+<p>This method can throw all the same errors as <a href="#get" class="podlinkpod"
+>&#34;get&#34;</a>. In addition, it can also throw the following error:</p>
+
+<dl>
+<dt><a name="304_(Auth_Failure,_Attachment_is_Private)"
+>304 (Auth Failure, Attachment is Private)</a></dt>
+
+<dd>
+<p>You specified the id of a private attachment in the <code class="code">attachment_ids</code> argument, and you are not in the &#34;insider group&#34; that can see private attachments.</p>
+</dd>
+</dl>
+
+<dt><a name="History"
+><b>History</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Added_in_Bugzilla_3.6."
+>Added in Bugzilla <b>3.6</b>.</a></dt>
+
+<dd>
+<dt><a name="In_Bugzilla_4.0,_the_attacher_return_value_was_renamed_to_creator."
+>In Bugzilla <b>4.0</b>, the <code class="code">attacher</code> return value was renamed to <code class="code">creator</code>.</a></dt>
+
+<dd>
+<dt><a name="In_Bugzilla_4.0,_the_description_return_value_was_renamed_to_summary."
+>In Bugzilla <b>4.0</b>, the <code class="code">description</code> return value was renamed to <code class="code">summary</code>.</a></dt>
+
+<dd>
+<dt><a name="The_data_return_value_was_added_in_Bugzilla_4.0."
+>The <code class="code">data</code> return value was added in Bugzilla <b>4.0</b>.</a></dt>
+</dl>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="comments"
+>comments</a></h2>
+
+<p><b>STABLE</b></p>
+
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>This allows you to get data about comments, given a list of bugs and/or comment ids.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<p><b>Note</b>: At least one of <code class="code">ids</code> or <code class="code">comment_ids</code> is required.</p>
+
+<p>In addition to the parameters below, this method also accepts the standard <a href="../../Bugzilla/WebService.html#include_fields" class="podlinkpod"
+>include_fields</a> and <a href="../../Bugzilla/WebService.html#exclude_fields" class="podlinkpod"
+>exclude_fields</a> arguments.</p>
+
+<dl>
+<dt><a name="ids"
+><code class="code">ids</code></a></dt>
+
+<dd>
+<p><code class="code">array</code> An array that can contain both bug IDs and bug aliases. All of the comments (that are visible to you) will be returned for the specified bugs.</p>
+
+<dt><a name="comment_ids"
+><code class="code">comment_ids</code></a></dt>
+
+<dd>
+<p><code class="code">array</code> An array of integer comment_ids. These comments will be returned individually, separate from any other comments in their respective bugs.</p>
+
+<dt><a name="new_since"
+><code class="code">new_since</code></a></dt>
+
+<dd>
+<p><code class="code">dateTime</code> If specified, the method will only return comments <i>newer</i> than this time. This only affects comments returned from the <code class="code">ids</code> argument. You will always be returned all comments you request in the <code class="code">comment_ids</code> argument, even if they are older than this date.</p>
+</dd>
+</dl>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>Two items are returned:</p>
+
+<dl>
+<dt><a name="bugs"
+><code class="code">bugs</code></a></dt>
+
+<dd>
+<p>This is used for bugs specified in <code class="code">ids</code>. This is a hash, where the keys are the numeric ids of the bugs, and the value is a hash with a single key, <code class="code">comments</code>, which is an array of comments. (The format of comments is described below.)</p>
+
+<p>Note that any individual bug will only be returned once, so if you specify an id multiple times in <code class="code">ids</code>, it will still only be returned once.</p>
+
+<dt><a name="comments"
+><code class="code">comments</code></a></dt>
+
+<dd>
+<p>Each individual comment requested in <code class="code">comment_ids</code> is returned here, in a hash where the numeric comment id is the key, and the value is the comment. (The format of comments is described below.)</p>
+</dd>
+</dl>
+
+<p>A &#34;comment&#34; as described above is a hash that contains the following keys:</p>
+
+<dl>
+<dt><a name="id"
+>id</a></dt>
+
+<dd>
+<p><code class="code">int</code> The globally unique ID for the comment.</p>
+
+<dt><a name="bug_id"
+>bug_id</a></dt>
+
+<dd>
+<p><code class="code">int</code> The ID of the bug that this comment is on.</p>
+
+<dt><a name="attachment_id"
+>attachment_id</a></dt>
+
+<dd>
+<p><code class="code">int</code> If the comment was made on an attachment, this will be the ID of that attachment. Otherwise it will be null.</p>
+
+<dt><a name="text"
+>text</a></dt>
+
+<dd>
+<p><code class="code">string</code> The actual text of the comment.</p>
+
+<dt><a name="creator"
+>creator</a></dt>
+
+<dd>
+<p><code class="code">string</code> The login name of the comment&#39;s author.</p>
+
+<p>Also returned as <code class="code">author</code>, for backwards-compatibility with older Bugzillas. (However, this backwards-compatibility will go away in Bugzilla 5.0.)</p>
+
+<dt><a name="time"
+>time</a></dt>
+
+<dd>
+<p><code class="code">dateTime</code> The time (in Bugzilla&#39;s timezone) that the comment was added.</p>
+
+<dt><a name="is_private"
+>is_private</a></dt>
+
+<dd>
+<p><code class="code">boolean</code> True if this comment is private (only visible to a certain group called the &#34;insidergroup&#34;), False otherwise.</p>
+</dd>
+</dl>
+
+<dt><a name="Errors"
+><b>Errors</b></a></dt>
+
+<dd>
+<p>This method can throw all the same errors as <a href="#get" class="podlinkpod"
+>&#34;get&#34;</a>. In addition, it can also throw the following errors:</p>
+
+<dl>
+<dt><a name="110_(Comment_Is_Private)"
+>110 (Comment Is Private)</a></dt>
+
+<dd>
+<p>You specified the id of a private comment in the <code class="code">comment_ids</code> argument, and you are not in the &#34;insider group&#34; that can see private comments.</p>
+
+<dt><a name="111_(Invalid_Comment_ID)"
+>111 (Invalid Comment ID)</a></dt>
+
+<dd>
+<p>You specified an id in the <code class="code">comment_ids</code> argument that is invalid--either you specified something that wasn&#39;t a number, or there is no comment with that id.</p>
+</dd>
+</dl>
+
+<dt><a name="History"
+><b>History</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Added_in_Bugzilla_3.4."
+>Added in Bugzilla <b>3.4</b>.</a></dt>
+
+<dd>
+<dt><a name="attachment_id_was_added_to_the_return_value_in_Bugzilla_3.6."
+><code class="code">attachment_id</code> was added to the return value in Bugzilla <b>3.6</b>.</a></dt>
+
+<dd>
+<dt><a name="In_Bugzilla_4.0,_the_author_return_value_was_renamed_to_creator."
+>In Bugzilla <b>4.0</b>, the <code class="code">author</code> return value was renamed to <code class="code">creator</code>.</a></dt>
+</dl>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="get"
+>get</a></h2>
+
+<p><b>STABLE</b></p>
+
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Gets information about particular bugs in the database.</p>
+
+<p>Note: Can also be called as &#34;get_bugs&#34; for compatibilty with Bugzilla 3.0 API.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<p>In addition to the parameters below, this method also accepts the standard <a href="../../Bugzilla/WebService.html#include_fields" class="podlinkpod"
+>include_fields</a> and <a href="../../Bugzilla/WebService.html#exclude_fields" class="podlinkpod"
+>exclude_fields</a> arguments.</p>
+
+<dl>
+<dt><a name="ids"
+><code class="code">ids</code></a></dt>
+
+<dd>
+<p>An array of numbers and strings.</p>
+
+<p>If an element in the array is entirely numeric, it represents a bug_id from the Bugzilla database to fetch. If it contains any non-numeric characters, it is considered to be a bug alias instead, and the bug with that alias will be loaded.</p>
+
+<p>Note that it&#39;s possible for aliases to be disabled in Bugzilla, in which case you will be told that you have specified an invalid bug_id if you try to specify an alias. (It will be error 100.)</p>
+
+<dt><a name="permissive_EXPERIMENTAL"
+><code class="code">permissive</code> <b>EXPERIMENTAL</b></a></dt>
+
+<dd>
+<p><code class="code">boolean</code> Normally, if you request any inaccessible or invalid bug ids, Bug.get will throw an error. If this parameter is True, instead of throwing an error we return an array of hashes with a <code class="code">id</code>, <code class="code">faultString</code> and <code class="code">faultCode</code> for each bug that fails, and return normal information for the other bugs that were accessible.</p>
+</dd>
+</dl>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>Two items are returned:</p>
+
+<dl>
+<dt><a name="bugs"
+><code class="code">bugs</code></a></dt>
+
+<dd>
+<p>An array of hashes that contains information about the bugs with the valid ids. Each hash contains the following items:</p>
+
+<dl>
+<dt><a name="alias"
+><code class="code">alias</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The unique alias of this bug.</p>
+
+<dt><a name="assigned_to"
+><code class="code">assigned_to</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The login name of the user to whom the bug is assigned.</p>
+
+<dt><a name="blocks"
+><code class="code">blocks</code></a></dt>
+
+<dd>
+<p><code class="code">array</code> of <code class="code">int</code>s. The ids of bugs that are &#34;blocked&#34; by this bug.</p>
+
+<dt><a name="cc"
+><code class="code">cc</code></a></dt>
+
+<dd>
+<p><code class="code">array</code> of <code class="code">string</code>s. The login names of users on the CC list of this bug.</p>
+
+<dt><a name="classification"
+><code class="code">classification</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The name of the current classification the bug is in.</p>
+
+<dt><a name="component"
+><code class="code">component</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The name of the current component of this bug.</p>
+
+<dt><a name="creation_time"
+><code class="code">creation_time</code></a></dt>
+
+<dd>
+<p><code class="code">dateTime</code> When the bug was created.</p>
+
+<dt><a name="creator"
+><code class="code">creator</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The login name of the person who filed this bug (the reporter).</p>
+
+<dt><a name="deadline"
+><code class="code">deadline</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The day that this bug is due to be completed, in the format <code class="code">YYYY-MM-DD</code>.</p>
+
+<p>If you are not in the time-tracking group, this field will not be included in the return value.</p>
+
+<dt><a name="depends_on"
+><code class="code">depends_on</code></a></dt>
+
+<dd>
+<p><code class="code">array</code> of <code class="code">int</code>s. The ids of bugs that this bug &#34;depends on&#34;.</p>
+
+<dt><a name="dupe_of"
+><code class="code">dupe_of</code></a></dt>
+
+<dd>
+<p><code class="code">int</code> The bug ID of the bug that this bug is a duplicate of. If this bug isn&#39;t a duplicate of any bug, this will be null.</p>
+
+<dt><a name="estimated_time"
+><code class="code">estimated_time</code></a></dt>
+
+<dd>
+<p><code class="code">double</code> The number of hours that it was estimated that this bug would take.</p>
+
+<p>If you are not in the time-tracking group, this field will not be included in the return value.</p>
+
+<dt><a name="groups"
+><code class="code">groups</code></a></dt>
+
+<dd>
+<p><code class="code">array</code> of <code class="code">string</code>s. The names of all the groups that this bug is in.</p>
+
+<dt><a name="id"
+><code class="code">id</code></a></dt>
+
+<dd>
+<p><code class="code">int</code> The unique numeric id of this bug.</p>
+
+<dt><a name="is_cc_accessible"
+><code class="code">is_cc_accessible</code></a></dt>
+
+<dd>
+<p><code class="code">boolean</code> If true, this bug can be accessed by members of the CC list, even if they are not in the groups the bug is restricted to.</p>
+
+<dt><a name="is_confirmed"
+><code class="code">is_confirmed</code></a></dt>
+
+<dd>
+<p><code class="code">boolean</code> True if the bug has been confirmed. Usually this means that the bug has at some point been moved out of the <code class="code">UNCONFIRMED</code> status and into another open status.</p>
+
+<dt><a name="is_open"
+><code class="code">is_open</code></a></dt>
+
+<dd>
+<p><code class="code">boolean</code> True if this bug is open, false if it is closed.</p>
+
+<dt><a name="is_creator_accessible"
+><code class="code">is_creator_accessible</code></a></dt>
+
+<dd>
+<p><code class="code">boolean</code> If true, this bug can be accessed by the creator (reporter) of the bug, even if he or she is not a member of the groups the bug is restricted to.</p>
+
+<dt><a name="keywords"
+><code class="code">keywords</code></a></dt>
+
+<dd>
+<p><code class="code">array</code> of <code class="code">string</code>s. Each keyword that is on this bug.</p>
+
+<dt><a name="last_change_time"
+><code class="code">last_change_time</code></a></dt>
+
+<dd>
+<p><code class="code">dateTime</code> When the bug was last changed.</p>
+
+<dt><a name="op_sys"
+><code class="code">op_sys</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The name of the operating system that the bug was filed against.</p>
+
+<dt><a name="platform"
+><code class="code">platform</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The name of the platform (hardware) that the bug was filed against.</p>
+
+<dt><a name="priority"
+><code class="code">priority</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The priority of the bug.</p>
+
+<dt><a name="product"
+><code class="code">product</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The name of the product this bug is in.</p>
+
+<dt><a name="qa_contact"
+><code class="code">qa_contact</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The login name of the current QA Contact on the bug.</p>
+
+<dt><a name="remaining_time"
+><code class="code">remaining_time</code></a></dt>
+
+<dd>
+<p><code class="code">double</code> The number of hours of work remaining until work on this bug is complete.</p>
+
+<p>If you are not in the time-tracking group, this field will not be included in the return value.</p>
+
+<dt><a name="resolution"
+><code class="code">resolution</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The current resolution of the bug, or an empty string if the bug is open.</p>
+
+<dt><a name="see_also"
+><code class="code">see_also</code></a></dt>
+
+<dd>
+<p><b>UNSTABLE</b></p>
+
+<p><code class="code">array</code> of <code class="code">string</code>s. The URLs in the See Also field on the bug.</p>
+
+<dt><a name="severity"
+><code class="code">severity</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The current severity of the bug.</p>
+
+<dt><a name="status"
+><code class="code">status</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The current status of the bug.</p>
+
+<dt><a name="summary"
+><code class="code">summary</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The summary of this bug.</p>
+
+<dt><a name="target_milestone"
+><code class="code">target_milestone</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The milestone that this bug is supposed to be fixed by, or for closed bugs, the milestone that it was fixed for.</p>
+
+<dt><a name="update_token"
+><code class="code">update_token</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The token that you would have to pass to the <em class="code">process_bug.cgi</em> page in order to update this bug. This changes every time the bug is updated.</p>
+
+<p>This field is not returned to logged-out users.</p>
+
+<dt><a name="url"
+><code class="code">url</code></a></dt>
+
+<dd>
+<p><b>UNSTABLE</b></p>
+
+<p><code class="code">string</code> A URL that demonstrates the problem described in the bug, or is somehow related to the bug report.</p>
+
+<dt><a name="version"
+><code class="code">version</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The version the bug was reported against.</p>
+
+<dt><a name="whiteboard"
+><code class="code">whiteboard</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The value of the &#34;status whiteboard&#34; field on the bug.</p>
+
+<dt><a name="custom_fields"
+><i>custom fields</i></a></dt>
+
+<dd>
+<p>Every custom field in this installation will also be included in the return value. Most fields are returned as <code class="code">string</code>s. However, some field types have different return values:</p>
+
+<dl>
+<dt><a name="Bug_ID_Fields_-_int"
+>Bug ID Fields - <code class="code">int</code></a></dt>
+
+<dd>
+<dt><a name="Multiple-Selection_Fields_-_array_of_strings."
+>Multiple-Selection Fields - <code class="code">array</code> of <code class="code">string</code>s.</a></dt>
+
+<dd>
+<dt><a name="Date/Time_Fields_-_dateTime"
+>Date/Time Fields - <code class="code">dateTime</code></a></dt>
+</dl>
+</dd>
+</dl>
+
+<dt><a name="faults_EXPERIMENTAL"
+><code class="code">faults</code> <b>EXPERIMENTAL</b></a></dt>
+
+<dd>
+<p>An array of hashes that contains invalid bug ids with error messages returned for them. Each hash contains the following items:</p>
+
+<dl>
+<dt><a name="id"
+>id</a></dt>
+
+<dd>
+<p><code class="code">int</code> The numeric bug_id of this bug.</p>
+
+<dt><a name="faultString"
+>faultString</a></dt>
+
+<dd>
+<p>c&#60;string&#62; This will only be returned for invalid bugs if the <code class="code">permissive</code> argument was set when calling Bug.get, and it is an error indicating that the bug id was invalid.</p>
+
+<dt><a name="faultCode"
+>faultCode</a></dt>
+
+<dd>
+<p>c&#60;int&#62; This will only be returned for invalid bugs if the <code class="code">permissive</code> argument was set when calling Bug.get, and it is the error code for the invalid bug error.</p>
+</dd>
+</dl>
+</dd>
+</dl>
+
+<dt><a name="Errors"
+><b>Errors</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="100_(Invalid_Bug_Alias)"
+>100 (Invalid Bug Alias)</a></dt>
+
+<dd>
+<p>If you specified an alias and either: (a) the Bugzilla you&#39;re querying doesn&#39;t support aliases or (b) there is no bug with that alias.</p>
+
+<dt><a name="101_(Invalid_Bug_ID)"
+>101 (Invalid Bug ID)</a></dt>
+
+<dd>
+<p>The bug_id you specified doesn&#39;t exist in the database.</p>
+
+<dt><a name="102_(Access_Denied)"
+>102 (Access Denied)</a></dt>
+
+<dd>
+<p>You do not have access to the bug_id you specified.</p>
+</dd>
+</dl>
+
+<dt><a name="History"
+><b>History</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="permissive_argument_added_to_this_method&#39;s_params_in_Bugzilla_3.4."
+><code class="code">permissive</code> argument added to this method&#39;s params in Bugzilla <b>3.4</b>.</a></dt>
+
+<dd>
+<dt><a name="The_following_properties_were_added_to_this_method&#39;s_return_values_in_Bugzilla_3.4:"
+>The following properties were added to this method&#39;s return values in Bugzilla <b>3.4</b>:</a></dt>
+
+<dd>
+<dl>
+<dt><a name="For_bugs"
+>For <code class="code">bugs</code></a></dt>
+
+<dd>
+<dl>
+<dt><a name="assigned_to"
+>assigned_to</a></dt>
+
+<dd>
+<dt><a name="component"
+>component</a></dt>
+
+<dd>
+<dt><a name="dupe_of"
+>dupe_of</a></dt>
+
+<dd>
+<dt><a name="is_open"
+>is_open</a></dt>
+
+<dd>
+<dt><a name="priority"
+>priority</a></dt>
+
+<dd>
+<dt><a name="product"
+>product</a></dt>
+
+<dd>
+<dt><a name="resolution"
+>resolution</a></dt>
+
+<dd>
+<dt><a name="severity"
+>severity</a></dt>
+
+<dd>
+<dt><a name="status"
+>status</a></dt>
+</dl>
+
+<dt><a name="faults"
+><code class="code">faults</code></a></dt>
+</dl>
+
+<dt><a
+>In Bugzilla <b>4.0</b>, the following items were added to the <code class="code">bugs</code> return value: <code class="code">blocks</code>, <code class="code">cc</code>, <code class="code">classification</code>, <code class="code">creator</code>, <code class="code">deadline</code>, <code class="code">depends_on</code>, <code class="code">estimated_time</code>, <code class="code">is_cc_accessible</code>, <code class="code">is_confirmed</code>, <code class="code">is_creator_accessible</code>, <code class="code">groups</code>, <code class="code">keywords</code>, <code class="code">op_sys</code>, <code class="code">platform</code>, <code class="code">qa_contact</code>, <code class="code">remaining_time</code>, <code class="code">see_also</code>, <code class="code">target_milestone</code>, <code class="code">update_token</code>, <code class="code">url</code>, <code class="code">version</code>, <code class="code">whiteboard</code>, and all custom fields.</a></dt>
+</dl>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="history"
+>history</a></h2>
+
+<p><b>EXPERIMENTAL</b></p>
+
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Gets the history of changes for particular bugs in the database.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="ids"
+><code class="code">ids</code></a></dt>
+
+<dd>
+<p>An array of numbers and strings.</p>
+
+<p>If an element in the array is entirely numeric, it represents a bug_id from the Bugzilla database to fetch. If it contains any non-numeric characters, it is considered to be a bug alias instead, and the data bug with that alias will be loaded.</p>
+
+<p>Note that it&#39;s possible for aliases to be disabled in Bugzilla, in which case you will be told that you have specified an invalid bug_id if you try to specify an alias. (It will be error 100.)</p>
+</dd>
+</dl>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>A hash containing a single element, <code class="code">bugs</code>. This is an array of hashes, containing the following keys:</p>
+
+<dl>
+<dt><a name="id"
+>id</a></dt>
+
+<dd>
+<p><code class="code">int</code> The numeric id of the bug.</p>
+
+<dt><a name="alias"
+>alias</a></dt>
+
+<dd>
+<p><code class="code">string</code> The alias of this bug. If there is no alias or aliases are disabled in this Bugzilla, this will be undef.</p>
+
+<dt><a name="history"
+>history</a></dt>
+
+<dd>
+<p><code class="code">array</code> An array of hashes, each hash having the following keys:</p>
+
+<dl>
+<dt><a name="when"
+>when</a></dt>
+
+<dd>
+<p><code class="code">dateTime</code> The date the bug activity/change happened.</p>
+
+<dt><a name="who"
+>who</a></dt>
+
+<dd>
+<p><code class="code">string</code> The login name of the user who performed the bug change.</p>
+
+<dt><a name="changes"
+>changes</a></dt>
+
+<dd>
+<p><code class="code">array</code> An array of hashes which contain all the changes that happened to the bug at this time (as specified by <code class="code">when</code>). Each hash contains the following items:</p>
+
+<dl>
+<dt><a name="field_name"
+>field_name</a></dt>
+
+<dd>
+<p><code class="code">string</code> The name of the bug field that has changed.</p>
+
+<dt><a name="removed"
+>removed</a></dt>
+
+<dd>
+<p><code class="code">string</code> The previous value of the bug field which has been deleted by the change.</p>
+
+<dt><a name="added"
+>added</a></dt>
+
+<dd>
+<p><code class="code">string</code> The new value of the bug field which has been added by the change.</p>
+
+<dt><a name="attachment_id"
+>attachment_id</a></dt>
+
+<dd>
+<p><code class="code">int</code> The id of the attachment that was changed. This only appears if the change was to an attachment, otherwise <code class="code">attachment_id</code> will not be present in this hash.</p>
+</dd>
+</dl>
+</dd>
+</dl>
+</dd>
+</dl>
+
+<dt><a name="Errors"
+><b>Errors</b></a></dt>
+
+<dd>
+<p>The same as <a href="#get" class="podlinkpod"
+>&#34;get&#34;</a>.</p>
+
+<dt><a name="History"
+><b>History</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Added_in_Bugzilla_3.4."
+>Added in Bugzilla <b>3.4</b>.</a></dt>
+</dl>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="search"
+>search</a></h2>
+
+<p><b>UNSTABLE</b></p>
+
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Allows you to search for bugs based on particular criteria.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<p>Unless otherwise specified in the description of a parameter, bugs are returned if they match <i>exactly</i> the criteria you specify in these parameters. That is, we don&#39;t match against substrings--if a bug is in the &#34;Widgets&#34; product and you ask for bugs in the &#34;Widg&#34; product, you won&#39;t get anything.</p>
+
+<p>Criteria are joined in a logical AND. That is, you will be returned bugs that match <i>all</i> of the criteria, not bugs that match <i>any</i> of the criteria.</p>
+
+<p>Each parameter can be either the type it says, or an array of the types it says. If you pass an array, it means &#34;Give me bugs with <i>any</i> of these values.&#34; For example, if you wanted bugs that were in either the &#34;Foo&#34; or &#34;Bar&#34; products, you&#39;d pass:</p>
+
+<pre class="code"> product =&#62; [&#39;Foo&#39;, &#39;Bar&#39;]</pre>
+
+<p>Some Bugzillas may treat your arguments case-sensitively, depending on what database system they are using. Most commonly, though, Bugzilla is not case-sensitive with the arguments passed (because MySQL is the most-common database to use with Bugzilla, and MySQL is not case sensitive).</p>
+
+<dl>
+<dt><a name="alias"
+><code class="code">alias</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The unique alias for this bug. Note that you can search by alias even if the alias field is disabled in this Bugzilla, but it&#39;s likely that there won&#39;t be any aliases set on bugs, in that case.</p>
+
+<dt><a name="assigned_to"
+><code class="code">assigned_to</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The login name of a user that a bug is assigned to.</p>
+
+<dt><a name="component"
+><code class="code">component</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The name of the Component that the bug is in. Note that if there are multiple Components with the same name, and you search for that name, bugs in <i>all</i> those Components will be returned. If you don&#39;t want this, be sure to also specify the <code class="code">product</code> argument.</p>
+
+<dt><a name="creation_time"
+><code class="code">creation_time</code></a></dt>
+
+<dd>
+<p><code class="code">dateTime</code> Searches for bugs that were created at this time or later. May not be an array.</p>
+
+<dt><a name="creator"
+><code class="code">creator</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The login name of the user who created the bug.</p>
+
+<dt><a name="id"
+><code class="code">id</code></a></dt>
+
+<dd>
+<p><code class="code">int</code> The numeric id of the bug.</p>
+
+<dt><a name="last_change_time"
+><code class="code">last_change_time</code></a></dt>
+
+<dd>
+<p><code class="code">dateTime</code> Searches for bugs that were modified at this time or later. May not be an array.</p>
+
+<dt><a name="limit"
+><code class="code">limit</code></a></dt>
+
+<dd>
+<p><code class="code">int</code> Limit the number of results returned to <code class="code">int</code> records.</p>
+
+<dt><a name="offset"
+><code class="code">offset</code></a></dt>
+
+<dd>
+<p><code class="code">int</code> Used in conjunction with the <code class="code">limit</code> argument, <code class="code">offset</code> defines the starting position for the search. For example, given a search that would return 100 bugs, setting <code class="code">limit</code> to 10 and <code class="code">offset</code> to 10 would return bugs 11 through 20 from the set of 100.</p>
+
+<dt><a name="op_sys"
+><code class="code">op_sys</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The &#34;Operating System&#34; field of a bug.</p>
+
+<dt><a name="platform"
+><code class="code">platform</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The Platform (sometimes called &#34;Hardware&#34;) field of a bug.</p>
+
+<dt><a name="priority"
+><code class="code">priority</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The Priority field on a bug.</p>
+
+<dt><a name="product"
+><code class="code">product</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The name of the Product that the bug is in.</p>
+
+<dt><a name="creator"
+><code class="code">creator</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The login name of the user who reported the bug.</p>
+
+<p>You can also pass this argument with the name <code class="code">reporter</code>, for backwards compatibility with older Bugzillas.</p>
+
+<dt><a name="resolution"
+><code class="code">resolution</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The current resolution--only set if a bug is closed. You can find open bugs by searching for bugs with an empty resolution.</p>
+
+<dt><a name="severity"
+><code class="code">severity</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The Severity field on a bug.</p>
+
+<dt><a name="status"
+><code class="code">status</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The current status of a bug (not including its resolution, if it has one, which is a separate field above).</p>
+
+<dt><a name="summary"
+><code class="code">summary</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> Searches for substrings in the single-line Summary field on bugs. If you specify an array, then bugs whose summaries match <i>any</i> of the passed substrings will be returned.</p>
+
+<p>Note that unlike searching in the Bugzilla UI, substrings are not split on spaces. So searching for <code class="code">foo bar</code> will match &#34;This is a foo bar&#34; but not &#34;This foo is a bar&#34;. <code class="code">[&#39;foo&#39;, &#39;bar&#39;]</code>, would, however, match the second item.</p>
+
+<dt><a name="target_milestone"
+><code class="code">target_milestone</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The Target Milestone field of a bug. Note that even if this Bugzilla does not have the Target Milestone field enabled, you can still search for bugs by Target Milestone. However, it is likely that in that case, most bugs will not have a Target Milestone set (it defaults to &#34;---&#34; when the field isn&#39;t enabled).</p>
+
+<dt><a name="qa_contact"
+><code class="code">qa_contact</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The login name of the bug&#39;s QA Contact. Note that even if this Bugzilla does not have the QA Contact field enabled, you can still search for bugs by QA Contact (though it is likely that no bug will have a QA Contact set, if the field is disabled).</p>
+
+<dt><a name="url"
+><code class="code">url</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The &#34;URL&#34; field of a bug.</p>
+
+<dt><a name="version"
+><code class="code">version</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The Version field of a bug.</p>
+
+<dt><a name="whiteboard"
+><code class="code">whiteboard</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> Search the &#34;Status Whiteboard&#34; field on bugs for a substring. Works the same as the <code class="code">summary</code> field described above, but searches the Status Whiteboard field.</p>
+</dd>
+</dl>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>The same as <a href="#get" class="podlinkpod"
+>&#34;get&#34;</a>.</p>
+
+<p>Note that you will only be returned information about bugs that you can see. Bugs that you can&#39;t see will be entirely excluded from the results. So, if you want to see private bugs, you will have to first log in and <i>then</i> call this method.</p>
+
+<dt><a name="Errors"
+><b>Errors</b></a></dt>
+
+<dd>
+<p>Currently, this function doesn&#39;t throw any special errors (other than the ones that all webservice functions can throw). If you specify an invalid value for a particular field, you just won&#39;t get any results for that value.</p>
+
+<dt><a name="History"
+><b>History</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Added_in_Bugzilla_3.4."
+>Added in Bugzilla <b>3.4</b>.</a></dt>
+
+<dd>
+<dt><a name="Searching_by_votes_was_removed_in_Bugzilla_4.0."
+>Searching by <code class="code">votes</code> was removed in Bugzilla <b>4.0</b>.</a></dt>
+
+<dd>
+<dt><a name="The_reporter_input_parameter_was_renamed_to_creator_in_Bugzilla_4.0."
+>The <code class="code">reporter</code> input parameter was renamed to <code class="code">creator</code> in Bugzilla <b>4.0</b>.</a></dt>
+</dl>
+</dd>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="Bug_Creation_and_Modification"
+>Bug Creation and Modification</a></h1>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="create"
+>create</a></h2>
+
+<p><b>STABLE</b></p>
+
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>This allows you to create a new bug in Bugzilla. If you specify any invalid fields, they will be ignored. If you specify any fields you are not allowed to set, they will just be set to their defaults or ignored.</p>
+
+<p>You cannot currently set all the items here that you can set on enter_bug.cgi.</p>
+
+<p>The WebService interface may allow you to set things other than those listed here, but realize that anything undocumented is <b>UNSTABLE</b> and will very likely change in the future.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<p>Some params must be set, or an error will be thrown. These params are marked <b>Required</b>.</p>
+
+<p>Some parameters can have defaults set in Bugzilla, by the administrator. If these parameters have defaults set, you can omit them. These parameters are marked <b>Defaulted</b>.</p>
+
+<p>Clients that want to be able to interact uniformly with multiple Bugzillas should always set both the params marked <b>Required</b> and those marked <b>Defaulted</b>, because some Bugzillas may not have defaults set for <b>Defaulted</b> parameters, and then this method will throw an error if you don&#39;t specify them.</p>
+
+<p>The descriptions of the parameters below are what they mean when Bugzilla is being used to track software bugs. They may have other meanings in some installations.</p>
+
+<dl>
+<dt><a name="product_(string)_Required_-_The_name_of_the_product_the_bug_is_being_filed_against."
+><code class="code">product</code> (string) <b>Required</b> - The name of the product the bug is being filed against.</a></dt>
+
+<dd>
+<dt><a name="component_(string)_Required_-_The_name_of_a_component_in_the_product_above."
+><code class="code">component</code> (string) <b>Required</b> - The name of a component in the product above.</a></dt>
+
+<dd>
+<dt><a name="summary_(string)_Required_-_A_brief_description_of_the_bug_being_filed."
+><code class="code">summary</code> (string) <b>Required</b> - A brief description of the bug being filed.</a></dt>
+
+<dd>
+<dt><a name="version_(string)_Required_-_A_version_of_the_product_above;_the_version_the_bug_was_found_in."
+><code class="code">version</code> (string) <b>Required</b> - A version of the product above; the version the bug was found in.</a></dt>
+
+<dd>
+<dt><a
+><code class="code">description</code> (string) <b>Defaulted</b> - The initial description for this bug. Some Bugzilla installations require this to not be blank.</a></dt>
+
+<dd>
+<dt><a name="op_sys_(string)_Defaulted_-_The_operating_system_the_bug_was_discovered_on."
+><code class="code">op_sys</code> (string) <b>Defaulted</b> - The operating system the bug was discovered on.</a></dt>
+
+<dd>
+<dt><a name="platform_(string)_Defaulted_-_What_type_of_hardware_the_bug_was_experienced_on."
+><code class="code">platform</code> (string) <b>Defaulted</b> - What type of hardware the bug was experienced on.</a></dt>
+
+<dd>
+<dt><a
+><code class="code">priority</code> (string) <b>Defaulted</b> - What order the bug will be fixed in by the developer, compared to the developer&#39;s other bugs.</a></dt>
+
+<dd>
+<dt><a name="severity_(string)_Defaulted_-_How_severe_the_bug_is."
+><code class="code">severity</code> (string) <b>Defaulted</b> - How severe the bug is.</a></dt>
+
+<dd>
+<dt><a
+><code class="code">alias</code> (string) - A brief alias for the bug that can be used instead of a bug number when accessing this bug. Must be unique in all of this Bugzilla.</a></dt>
+
+<dd>
+<dt><a name="assigned_to_(username)_-_A_user_to_assign_this_bug_to,_if_you_don&#39;t_want_it_to_be_assigned_to_the_component_owner."
+><code class="code">assigned_to</code> (username) - A user to assign this bug to, if you don&#39;t want it to be assigned to the component owner.</a></dt>
+
+<dd>
+<dt><a name="cc_(array)_-_An_array_of_usernames_to_CC_on_this_bug."
+><code class="code">cc</code> (array) - An array of usernames to CC on this bug.</a></dt>
+
+<dd>
+<dt><a name="comment_is_private_(boolean)_-_If_set_to_true,_the_description_is_private,_otherwise_it_is_assumed_to_be_public."
+><code class="code">comment_is_private</code> (boolean) - If set to true, the description is private, otherwise it is assumed to be public.</a></dt>
+
+<dd>
+<dt><a
+><code class="code">groups</code> (array) - An array of group names to put this bug into. You can see valid group names on the Permissions tab of the Preferences screen, or, if you are an administrator, in the Groups control panel. Note that invalid group names or groups that the bug can&#39;t be restricted to are silently ignored. If you don&#39;t specify this argument, then a bug will be added into all the groups that are set as being &#34;Default&#34; for this product. (If you want to avoid that, you should specify <code class="code">groups</code> as an empty array.)</a></dt>
+
+<dd>
+<dt><a
+><code class="code">qa_contact</code> (username) - If this installation has QA Contacts enabled, you can set the QA Contact here if you don&#39;t want to use the component&#39;s default QA Contact.</a></dt>
+
+<dd>
+<dt><a
+><code class="code">status</code> (string) - The status that this bug should start out as. Note that only certain statuses can be set on bug creation.</a></dt>
+
+<dd>
+<dt><a name="target_milestone_(string)_-_A_valid_target_milestone_for_this_product."
+><code class="code">target_milestone</code> (string) - A valid target milestone for this product.</a></dt>
+</dl>
+
+<p>In addition to the above parameters, if your installation has any custom fields, you can set them just by passing in the name of the field and its value as a string.</p>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>A hash with one element, <code class="code">id</code>. This is the id of the newly-filed bug.</p>
+
+<dt><a name="Errors"
+><b>Errors</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="51_(Invalid_Object)"
+>51 (Invalid Object)</a></dt>
+
+<dd>
+<p>You specified a field value that is invalid. The error message will have more details.</p>
+
+<dt><a name="103_(Invalid_Alias)"
+>103 (Invalid Alias)</a></dt>
+
+<dd>
+<p>The alias you specified is invalid for some reason. See the error message for more details.</p>
+
+<dt><a name="104_(Invalid_Field)"
+>104 (Invalid Field)</a></dt>
+
+<dd>
+<p>One of the drop-down fields has an invalid value, or a value entered in a text field is too long. The error message will have more detail.</p>
+
+<dt><a name="105_(Invalid_Component)"
+>105 (Invalid Component)</a></dt>
+
+<dd>
+<p>You didn&#39;t specify a component.</p>
+
+<dt><a name="106_(Invalid_Product)"
+>106 (Invalid Product)</a></dt>
+
+<dd>
+<p>Either you didn&#39;t specify a product, this product doesn&#39;t exist, or you don&#39;t have permission to enter bugs in this product.</p>
+
+<dt><a name="107_(Invalid_Summary)"
+>107 (Invalid Summary)</a></dt>
+
+<dd>
+<p>You didn&#39;t specify a summary for the bug.</p>
+
+<dt><a name="116_(Dependency_Loop)"
+>116 (Dependency Loop)</a></dt>
+
+<dd>
+<p>You specified values in the <code class="code">blocks</code> or <code class="code">depends_on</code> fields that would cause a circular dependency between bugs.</p>
+
+<dt><a name="504_(Invalid_User)"
+>504 (Invalid User)</a></dt>
+
+<dd>
+<p>Either the QA Contact, Assignee, or CC lists have some invalid user in them. The error message will have more details.</p>
+</dd>
+</dl>
+
+<dt><a name="History"
+><b>History</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Before_3.0.4,_parameters_marked_as_Defaulted_were_actually_Required,_due_to_a_bug_in_Bugzilla."
+>Before <b>3.0.4</b>, parameters marked as <b>Defaulted</b> were actually <b>Required</b>, due to a bug in Bugzilla.</a></dt>
+
+<dd>
+<dt><a
+>The <code class="code">groups</code> argument was added in Bugzilla <b>4.0</b>. Before Bugzilla 4.0, bugs were only added into Mandatory groups by this method.</a></dt>
+
+<dd>
+<dt><a
+>The <code class="code">comment_is_private</code> argument was added in Bugzilla <b>4.0</b>. Before Bugzilla 4.0, you had to use the undocumented <code class="code">commentprivacy</code> argument.</a></dt>
+
+<dd>
+<dt><a name="Error_116_was_added_in_Bugzilla_4.0._Before_that,_dependency_loop_errors_had_a_generic_code_of_32000."
+>Error 116 was added in Bugzilla <b>4.0</b>. Before that, dependency loop errors had a generic code of <code class="code">32000</code>.</a></dt>
+</dl>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="add_attachment"
+>add_attachment</a></h2>
+
+<p><b>UNSTABLE</b></p>
+
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>This allows you to add an attachment to a bug in Bugzilla.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="ids"
+><code class="code">ids</code></a></dt>
+
+<dd>
+<p><b>Required</b> <code class="code">array</code> An array of ints and/or strings--the ids or aliases of bugs that you want to add this attachment to. The same attachment and comment will be added to all these bugs.</p>
+
+<dt><a name="data"
+><code class="code">data</code></a></dt>
+
+<dd>
+<p><b>Required</b> <code class="code">base64</code> The content of the attachment.</p>
+
+<dt><a name="file_name"
+><code class="code">file_name</code></a></dt>
+
+<dd>
+<p><b>Required</b> <code class="code">string</code> The &#34;file name&#34; that will be displayed in the UI for this attachment.</p>
+
+<dt><a name="summary"
+><code class="code">summary</code></a></dt>
+
+<dd>
+<p><b>Required</b> <code class="code">string</code> A short string describing the attachment.</p>
+
+<dt><a name="content_type"
+><code class="code">content_type</code></a></dt>
+
+<dd>
+<p><b>Required</b> <code class="code">string</code> The MIME type of the attachment, like <code class="code">text/plain</code> or <code class="code">image/png</code>.</p>
+
+<dt><a name="comment"
+><code class="code">comment</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> A comment to add along with this attachment.</p>
+
+<dt><a name="is_patch"
+><code class="code">is_patch</code></a></dt>
+
+<dd>
+<p><code class="code">boolean</code> True if Bugzilla should treat this attachment as a patch. If you specify this, you do not need to specify a <code class="code">content_type</code>. The <code class="code">content_type</code> of the attachment will be forced to <code class="code">text/plain</code>.</p>
+
+<p>Defaults to False if not specified.</p>
+
+<dt><a name="is_private"
+><code class="code">is_private</code></a></dt>
+
+<dd>
+<p><code class="code">boolean</code> True if the attachment should be private (restricted to the &#34;insidergroup&#34;), False if the attachment should be public.</p>
+
+<p>Defaults to False if not specified.</p>
+
+<dt><a name="is_url"
+><code class="code">is_url</code></a></dt>
+
+<dd>
+<p><code class="code">boolean</code> True if the attachment is just a URL, pointing to data elsewhere. If so, the <code class="code">data</code> item should just contain the URL.</p>
+
+<p>Defaults to False if not specified.</p>
+</dd>
+</dl>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>A single item <code class="code">attachments</code>, which contains the created attachments in the same format as the <code class="code">attachments</code> return value from <a href="#attachments" class="podlinkpod"
+>&#34;attachments&#34;</a>.</p>
+
+<dt><a name="Errors"
+><b>Errors</b></a></dt>
+
+<dd>
+<p>This method can throw all the same errors as <a href="#get" class="podlinkpod"
+>&#34;get&#34;</a>, plus:</p>
+
+<dl>
+<dt><a name="600_(Attachment_Too_Large)"
+>600 (Attachment Too Large)</a></dt>
+
+<dd>
+<p>You tried to attach a file that was larger than Bugzilla will accept.</p>
+
+<dt><a name="601_(Invalid_MIME_Type)"
+>601 (Invalid MIME Type)</a></dt>
+
+<dd>
+<p>You specified a <code class="code">content_type</code> argument that was blank, not a valid MIME type, or not a MIME type that Bugzilla accepts for attachments.</p>
+
+<dt><a name="602_(Illegal_URL)"
+>602 (Illegal URL)</a></dt>
+
+<dd>
+<p>You specified <code class="code">is_url</code> as True, but the data that you attempted to attach was not a valid URL.</p>
+
+<dt><a name="603_(File_Name_Not_Specified)"
+>603 (File Name Not Specified)</a></dt>
+
+<dd>
+<p>You did not specify a valid for the <code class="code">file_name</code> argument.</p>
+
+<dt><a name="604_(Summary_Required)"
+>604 (Summary Required)</a></dt>
+
+<dd>
+<p>You did not specify a value for the <code class="code">summary</code> argument.</p>
+
+<dt><a name="605_(URL_Attaching_Disabled)"
+>605 (URL Attaching Disabled)</a></dt>
+
+<dd>
+<p>You attempted to attach a URL, setting <code class="code">is_url</code> to True, but this Bugzilla does not support attaching URLs.</p>
+
+<dt><a name="606_(Empty_Data)"
+>606 (Empty Data)</a></dt>
+
+<dd>
+<p>You set the &#34;data&#34; field to an empty string.</p>
+</dd>
+</dl>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="add_comment"
+>add_comment</a></h2>
+
+<p><b>STABLE</b></p>
+
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>This allows you to add a comment to a bug in Bugzilla.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="id_(int)_Required_-_The_id_or_alias_of_the_bug_to_append_a_comment_to."
+><code class="code">id</code> (int) <b>Required</b> - The id or alias of the bug to append a comment to.</a></dt>
+
+<dd>
+<dt><a
+><code class="code">comment</code> (string) <b>Required</b> - The comment to append to the bug. If this is empty or all whitespace, an error will be thrown saying that you did not set the <code class="code">comment</code> parameter.</a></dt>
+
+<dd>
+<dt><a name="is_private_(boolean)_-_If_set_to_true,_the_comment_is_private,_otherwise_it_is_assumed_to_be_public."
+><code class="code">is_private</code> (boolean) - If set to true, the comment is private, otherwise it is assumed to be public.</a></dt>
+
+<dd>
+<dt><a
+><code class="code">work_time</code> (double) - Adds this many hours to the &#34;Hours Worked&#34; on the bug. If you are not in the time tracking group, this value will be ignored.</a></dt>
+</dl>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>A hash with one element, <code class="code">id</code> whose value is the id of the newly-created comment.</p>
+
+<dt><a name="Errors"
+><b>Errors</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="54_(Hours_Worked_Too_Large)"
+>54 (Hours Worked Too Large)</a></dt>
+
+<dd>
+<p>You specified a <code class="code">work_time</code> larger than the maximum allowed value of <code class="code">99999.99</code>.</p>
+
+<dt><a name="100_(Invalid_Bug_Alias)"
+>100 (Invalid Bug Alias)</a></dt>
+
+<dd>
+<p>If you specified an alias and either: (a) the Bugzilla you&#39;re querying doesn&#39;t support aliases or (b) there is no bug with that alias.</p>
+
+<dt><a name="101_(Invalid_Bug_ID)"
+>101 (Invalid Bug ID)</a></dt>
+
+<dd>
+<p>The id you specified doesn&#39;t exist in the database.</p>
+
+<dt><a name="109_(Bug_Edit_Denied)"
+>109 (Bug Edit Denied)</a></dt>
+
+<dd>
+<p>You did not have the necessary rights to edit the bug.</p>
+
+<dt><a name="113_(Can&#39;t_Make_Private_Comments)"
+>113 (Can&#39;t Make Private Comments)</a></dt>
+
+<dd>
+<p>You tried to add a private comment, but don&#39;t have the necessary rights.</p>
+
+<dt><a name="114_(Comment_Too_Long)"
+>114 (Comment Too Long)</a></dt>
+
+<dd>
+<p>You tried to add a comment longer than the maximum allowed length (65,535 characters).</p>
+</dd>
+</dl>
+
+<dt><a name="History"
+><b>History</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Added_in_Bugzilla_3.2."
+>Added in Bugzilla <b>3.2</b>.</a></dt>
+
+<dd>
+<dt><a name="Modified_to_return_the_new_comment&#39;s_id_in_Bugzilla_3.4"
+>Modified to return the new comment&#39;s id in Bugzilla <b>3.4</b></a></dt>
+
+<dd>
+<dt><a name="Modified_to_throw_an_error_if_you_try_to_add_a_private_comment_but_can&#39;t,_in_Bugzilla_3.4."
+>Modified to throw an error if you try to add a private comment but can&#39;t, in Bugzilla <b>3.4</b>.</a></dt>
+
+<dd>
+<dt><a
+>Before Bugzilla <b>3.6</b>, the <code class="code">is_private</code> argument was called <code class="code">private</code>, and you can still call it <code class="code">private</code> for backwards-compatibility purposes if you wish.</a></dt>
+
+<dd>
+<dt><a name="Before_Bugzilla_3.6,_error_54_and_error_114_had_a_generic_error_code_of_32000."
+>Before Bugzilla <b>3.6</b>, error 54 and error 114 had a generic error code of 32000.</a></dt>
+</dl>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="update"
+>update</a></h2>
+
+<p><b>UNSTABLE</b></p>
+
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Allows you to update the fields of a bug. Automatically sends emails out about the changes.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="ids"
+><code class="code">ids</code></a></dt>
+
+<dd>
+<p>Array of <code class="code">int</code>s or <code class="code">string</code>s. The ids or aliases of the bugs that you want to modify.</p>
+</dd>
+</dl>
+
+<p><b>Note</b>: All following fields specify the values you want to set on the bugs you are updating.</p>
+
+<dl>
+<dt><a name="alias"
+><code class="code">alias</code></a></dt>
+
+<dd>
+<p>(string) The alias of the bug. You can only set this if you are modifying a single bug. If there is more than one bug specified in <code class="code">ids</code>, passing in a value for <code class="code">alias</code> will cause an error to be thrown.</p>
+
+<dt><a name="assigned_to"
+><code class="code">assigned_to</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The full login name of the user this bug is assigned to.</p>
+
+<dt><a name="blocks"
+><code class="code">blocks</code></a></dt>
+
+<dd>
+<dt><a name="depends_on"
+><code class="code">depends_on</code></a></dt>
+
+<dd>
+<p><code class="code">hash</code> These specify the bugs that this bug blocks or depends on, respectively. To set these, you should pass a hash as the value. The hash may contain the following fields:</p>
+
+<dl>
+<dt><a name="add_An_array_of_ints._Bug_ids_to_add_to_this_field."
+><code class="code">add</code> An array of <code class="code">int</code>s. Bug ids to add to this field.</a></dt>
+
+<dd>
+<dt><a
+><code class="code">remove</code> An array of <code class="code">int</code>s. Bug ids to remove from this field. If the bug ids are not already in the field, they will be ignored.</a></dt>
+
+<dd>
+<dt><a
+><code class="code">set</code> An array of <code class="code">int</code>s. An exact set of bug ids to set this field to, overriding the current value. If you specify <code class="code">set</code>, then <code class="code">add</code> and <code class="code">remove</code> will be ignored.</a></dt>
+</dl>
+
+<dt><a name="cc"
+><code class="code">cc</code></a></dt>
+
+<dd>
+<p><code class="code">hash</code> The users on the cc list. To modify this field, pass a hash, which may have the following fields:</p>
+
+<dl>
+<dt><a
+><code class="code">add</code> Array of <code class="code">string</code>s. User names to add to the CC list. They must be full user names, and an error will be thrown if you pass in an invalid user name.</a></dt>
+
+<dd>
+<dt><a
+><code class="code">remove</code> Array of <code class="code">string</code>s. User names to remove from the CC list. They must be full user names, and an error will be thrown if you pass in an invalid user name.</a></dt>
+</dl>
+
+<dt><a name="is_cc_accessible"
+><code class="code">is_cc_accessible</code></a></dt>
+
+<dd>
+<p><code class="code">boolean</code> Whether or not users in the CC list are allowed to access the bug, even if they aren&#39;t in a group that can normally access the bug.</p>
+
+<dt><a name="comment"
+><code class="code">comment</code></a></dt>
+
+<dd>
+<p><code class="code">hash</code>. A comment on the change. The hash may contain the following fields:</p>
+
+<dl>
+<dt><a
+><code class="code">body</code> <code class="code">string</code> The actual text of the comment. <b>Note</b>: For compatibility with the parameters to <a href="#add_comment" class="podlinkpod"
+>&#34;add_comment&#34;</a>, you can also call this field <code class="code">comment</code>, if you want.</a></dt>
+
+<dd>
+<dt><a
+><code class="code">is_private</code> <code class="code">boolean</code> Whether the comment is private or not. If you try to make a comment private and you don&#39;t have the permission to, an error will be thrown.</a></dt>
+</dl>
+
+<dt><a name="comment_is_private"
+><code class="code">comment_is_private</code></a></dt>
+
+<dd>
+<p><code class="code">hash</code> This is how you update the privacy of comments that are already on a bug. This is a hash, where the keys are the <code class="code">int</code> id of comments (not their count on a bug, like #1, #2, #3, but their globally-unique id, as returned by <a href="#comments" class="podlinkpod"
+>&#34;comments&#34;</a>) and the value is a <code class="code">boolean</code> which specifies whether that comment should become private (<code class="code">true</code>) or public (<code class="code">false</code>).</p>
+
+<p>The comment ids must be valid for the bug being updated. Thus, it is not practical to use this while updating multiple bugs at once, as a single comment id will never be valid on multiple bugs.</p>
+
+<dt><a name="component"
+><code class="code">component</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The Component the bug is in.</p>
+
+<dt><a name="deadline"
+><code class="code">deadline</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The Deadline field--a date specifying when the bug must be completed by, in the format <code class="code">YYYY-MM-DD</code>.</p>
+
+<dt><a name="dupe_of"
+><code class="code">dupe_of</code></a></dt>
+
+<dd>
+<p><code class="code">int</code> The bug that this bug is a duplicate of. If you want to mark a bug as a duplicate, the safest thing to do is to set this value and <i>not</i> set the <code class="code">status</code> or <code class="code">resolution</code> fields. They will automatically be set by Bugzilla to the appropriate values for duplicate bugs.</p>
+
+<dt><a name="estimated_time"
+><code class="code">estimated_time</code></a></dt>
+
+<dd>
+<p><code class="code">double</code> The total estimate of time required to fix the bug, in hours. This is the <i>total</i> estimate, not the amount of time remaining to fix it.</p>
+
+<dt><a name="groups"
+><code class="code">groups</code></a></dt>
+
+<dd>
+<p><code class="code">hash</code> The groups a bug is in. To modify this field, pass a hash, which may have the following fields:</p>
+
+<dl>
+<dt><a
+><code class="code">add</code> Array of <code class="code">string</code>s. The names of groups to add. Passing in an invalid group name or a group that you cannot add to this bug will cause an error to be thrown.</a></dt>
+
+<dd>
+<dt><a
+><code class="code">remove</code> Array of <code class="code">string</code>s. The names of groups to remove. Passing in an invalid group name or a group that you cannot remove from this bug will cause an error to be thrown.</a></dt>
+</dl>
+
+<dt><a name="keywords"
+><code class="code">keywords</code></a></dt>
+
+<dd>
+<p><code class="code">hash</code> Keywords on the bug. To modify this field, pass a hash, which may have the following fields:</p>
+
+<dl>
+<dt><a
+><code class="code">add</code> An array of <code class="code">strings</code>s. The names of keywords to add to the field on the bug. Passing something that isn&#39;t a valid keyword name will cause an error to be thrown.</a></dt>
+
+<dd>
+<dt><a
+><code class="code">remove</code> An array of <code class="code">string</code>s. The names of keywords to remove from the field on the bug. Passing something that isn&#39;t a valid keyword name will cause an error to be thrown.</a></dt>
+
+<dd>
+<dt><a
+><code class="code">set</code> An array of <code class="code">strings</code>s. An exact set of keywords to set the field to, on the bug. Passing something that isn&#39;t a valid keyword name will cause an error to be thrown. Specifying <code class="code">set</code> overrides <code class="code">add</code> and <code class="code">remove</code>.</a></dt>
+</dl>
+
+<dt><a name="op_sys"
+><code class="code">op_sys</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The Operating System (&#34;OS&#34;) field on the bug.</p>
+
+<dt><a name="platform"
+><code class="code">platform</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The Platform or &#34;Hardware&#34; field on the bug.</p>
+
+<dt><a name="priority"
+><code class="code">priority</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The Priority field on the bug.</p>
+
+<dt><a name="product"
+><code class="code">product</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The name of the product that the bug is in. If you change this, you will probably also want to change <code class="code">target_milestone</code>, <code class="code">version</code>, and <code class="code">component</code>, since those have different legal values in every product.</p>
+
+<p>If you cannot change the <code class="code">target_milestone</code> field, it will be reset to the default for the product, when you move a bug to a new product.</p>
+
+<p>You may also wish to add or remove groups, as which groups are valid on a bug depends on the product. Groups that are not valid in the new product will be automatically removed, and groups which are mandatory in the new product will be automaticaly added, but no other automatic group changes will be done.</p>
+
+<p>Note that users can only move a bug into a product if they would normally have permission to file new bugs in that product.</p>
+
+<dt><a name="qa_contact"
+><code class="code">qa_contact</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The full login name of the bug&#39;s QA Contact.</p>
+
+<dt><a name="is_creator_accessible"
+><code class="code">is_creator_accessible</code></a></dt>
+
+<dd>
+<p><code class="code">boolean</code> Whether or not the bug&#39;s reporter is allowed to access the bug, even if he or she isn&#39;t in a group that can normally access the bug.</p>
+
+<dt><a name="remaining_time"
+><code class="code">remaining_time</code></a></dt>
+
+<dd>
+<p><code class="code">double</code> How much work time is remaining to fix the bug, in hours. If you set <code class="code">work_time</code> but don&#39;t explicitly set <code class="code">remaining_time</code>, then the <code class="code">work_time</code> will be deducted from the bug&#39;s <code class="code">remaining_time</code>.</p>
+
+<dt><a name="reset_assigned_to"
+><code class="code">reset_assigned_to</code></a></dt>
+
+<dd>
+<p><code class="code">boolean</code> If true, the <code class="code">assigned_to</code> field will be reset to the default for the component that the bug is in. (If you have set the component at the same time as using this, then the component used will be the new component, not the old one.)</p>
+
+<dt><a name="reset_qa_contact"
+><code class="code">reset_qa_contact</code></a></dt>
+
+<dd>
+<p><code class="code">boolean</code> If true, the <code class="code">qa_contact</code> field will be reset to the default for the component that the bug is in. (If you have set the component at the same time as using this, then the component used will be the new component, not the old one.)</p>
+
+<dt><a name="resolution"
+><code class="code">resolution</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The current resolution. May only be set if you are closing a bug or if you are modifying an already-closed bug. Attempting to set the resolution to <i>any</i> value (even an empty or null string) on an open bug will cause an error to be thrown.</p>
+
+<p>If you change the <code class="code">status</code> field to an open status, the resolution field will automatically be cleared, so you don&#39;t have to clear it manually.</p>
+
+<dt><a name="see_also"
+><code class="code">see_also</code></a></dt>
+
+<dd>
+<p><code class="code">hash</code> The See Also field on a bug, specifying URLs to bugs in other bug trackers. To modify this field, pass a hash, which may have the following fields:</p>
+
+<dl>
+<dt><a
+><code class="code">add</code> An array of <code class="code">strings</code>s. URLs to add to the field. Each URL must be a valid URL to a bug-tracker, or an error will be thrown.</a></dt>
+
+<dd>
+<dt><a name="remove_An_array_of_strings._URLs_to_remove_from_the_field._Invalid_URLs_will_be_ignored."
+><code class="code">remove</code> An array of <code class="code">string</code>s. URLs to remove from the field. Invalid URLs will be ignored.</a></dt>
+</dl>
+
+<dt><a name="severity"
+><code class="code">severity</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The Severity field of a bug.</p>
+
+<dt><a name="status"
+><code class="code">status</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The status you want to change the bug to. Note that if a bug is changing from open to closed, you should also specify a <code class="code">resolution</code>.</p>
+
+<dt><a name="summary"
+><code class="code">summary</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The Summary field of the bug.</p>
+
+<dt><a name="target_milestone"
+><code class="code">target_milestone</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The bug&#39;s Target Milestone.</p>
+
+<dt><a name="url"
+><code class="code">url</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The &#34;URL&#34; field of a bug.</p>
+
+<dt><a name="version"
+><code class="code">version</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The bug&#39;s Version field.</p>
+
+<dt><a name="whiteboard"
+><code class="code">whiteboard</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The Status Whiteboard field of a bug.</p>
+
+<dt><a name="work_time"
+><code class="code">work_time</code></a></dt>
+
+<dd>
+<p><code class="code">double</code> The number of hours worked on this bug as part of this change. If you set <code class="code">work_time</code> but don&#39;t explicitly set <code class="code">remaining_time</code>, then the <code class="code">work_time</code> will be deducted from the bug&#39;s <code class="code">remaining_time</code>.</p>
+</dd>
+</dl>
+
+<p>You can also set the value of any custom field by passing its name as a parameter, and the value to set the field to. For multiple-selection fields, the value should be an array of strings.</p>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>A <code class="code">hash</code> with a single field, &#34;bugs&#34;. This points to an array of hashes with the following fields:</p>
+
+<dl>
+<dt><a name="id"
+><code class="code">id</code></a></dt>
+
+<dd>
+<p><code class="code">int</code> The id of the bug that was updated.</p>
+
+<dt><a name="alias"
+><code class="code">alias</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The alias of the bug that was updated, if aliases are enabled and this bug has an alias.</p>
+
+<dt><a name="last_change_time"
+><code class="code">last_change_time</code></a></dt>
+
+<dd>
+<p><code class="code">dateTime</code> The exact time that this update was done at, for this bug. If no update was done (that is, no fields had their values changed and no comment was added) then this will instead be the last time the bug was updated.</p>
+
+<dt><a name="changes"
+><code class="code">changes</code></a></dt>
+
+<dd>
+<p><code class="code">hash</code> The changes that were actually done on this bug. The keys are the names of the fields that were changed, and the values are a hash with two keys:</p>
+
+<dl>
+<dt><a
+><code class="code">added</code> (<code class="code">string</code>) The values that were added to this field, possibly a comma-and-space-separated list if multiple values were added.</a></dt>
+
+<dd>
+<dt><a
+><code class="code">removed</code> (<code class="code">string</code>) The values that were removed from this field, possibly a comma-and-space-separated list if multiple values were removed.</a></dt>
+</dl>
+</dd>
+</dl>
+
+<p>Here&#39;s an example of what a return value might look like:</p>
+
+<pre class="code"> {
+ bugs =&#62; [
+ {
+ id =&#62; 123,
+ alias =&#62; &#39;foo&#39;,
+ last_change_time =&#62; &#39;2010-01-01T12:34:56&#39;,
+ changes =&#62; {
+ status =&#62; {
+ removed =&#62; &#39;NEW&#39;,
+ added =&#62; &#39;ASSIGNED&#39;
+ },
+ keywords =&#62; {
+ removed =&#62; &#39;bar&#39;,
+ added =&#62; &#39;qux, quo, qui&#39;,
+ }
+ },
+ }
+ ]
+ }</pre>
+
+<p>Currently, some fields are not tracked in changes: <code class="code">comment</code>, <code class="code">comment_is_private</code>, and <code class="code">work_time</code>. This means that they will not show up in the return value even if they were successfully updated. This may change in a future version of Bugzilla.</p>
+
+<dt><a name="Errors"
+><b>Errors</b></a></dt>
+
+<dd>
+<p>This function can throw all of the errors that <a href="#get" class="podlinkpod"
+>&#34;get&#34;</a>, <a href="#create" class="podlinkpod"
+>&#34;create&#34;</a>, and <a href="#add_comment" class="podlinkpod"
+>&#34;add_comment&#34;</a> can throw, plus:</p>
+
+<dl>
+<dt><a name="50_(Empty_Field)"
+>50 (Empty Field)</a></dt>
+
+<dd>
+<p>You tried to set some field to be empty, but that field cannot be empty. The error message will have more details.</p>
+
+<dt><a name="52_(Input_Not_A_Number)"
+>52 (Input Not A Number)</a></dt>
+
+<dd>
+<p>You tried to set a numeric field to a value that wasn&#39;t numeric.</p>
+
+<dt><a name="54_(Number_Too_Large)"
+>54 (Number Too Large)</a></dt>
+
+<dd>
+<p>You tried to set a numeric field to a value larger than that field can accept.</p>
+
+<dt><a name="55_(Number_Too_Small)"
+>55 (Number Too Small)</a></dt>
+
+<dd>
+<p>You tried to set a negative value in a numeric field that does not accept negative values.</p>
+
+<dt><a name="56_(Bad_Date/Time)"
+>56 (Bad Date/Time)</a></dt>
+
+<dd>
+<p>You specified an invalid date or time in a date/time field (such as the <code class="code">deadline</code> field or a custom date/time field).</p>
+
+<dt><a name="112_(See_Also_Invalid)"
+>112 (See Also Invalid)</a></dt>
+
+<dd>
+<p>You attempted to add an invalid value to the <code class="code">see_also</code> field.</p>
+
+<dt><a name="115_(Permission_Denied)"
+>115 (Permission Denied)</a></dt>
+
+<dd>
+<p>You don&#39;t have permission to change a particular field to a particular value. The error message will have more detail.</p>
+
+<dt><a name="116_(Dependency_Loop)"
+>116 (Dependency Loop)</a></dt>
+
+<dd>
+<p>You specified a value in the <code class="code">blocks</code> or <code class="code">depends_on</code> fields that causes a dependency loop.</p>
+
+<dt><a name="117_(Invalid_Comment_ID)"
+>117 (Invalid Comment ID)</a></dt>
+
+<dd>
+<p>You specified a comment id in <code class="code">comment_is_private</code> that isn&#39;t on this bug.</p>
+
+<dt><a name="118_(Duplicate_Loop)"
+>118 (Duplicate Loop)</a></dt>
+
+<dd>
+<p>You specified a value for <code class="code">dupe_of</code> that causes an infinite loop of duplicates.</p>
+
+<dt><a name="119_(dupe_of_Required)"
+>119 (dupe_of Required)</a></dt>
+
+<dd>
+<p>You changed the resolution to <code class="code">DUPLICATE</code> but did not specify a value for the <code class="code">dupe_of</code> field.</p>
+
+<dt><a name="120_(Group_Add/Remove_Denied)"
+>120 (Group Add/Remove Denied)</a></dt>
+
+<dd>
+<p>You tried to add or remove a group that you don&#39;t have permission to modify for this bug, or you tried to add a group that isn&#39;t valid in this product.</p>
+
+<dt><a name="121_(Resolution_Required)"
+>121 (Resolution Required)</a></dt>
+
+<dd>
+<p>You tried to set the <code class="code">status</code> field to a closed status, but you didn&#39;t specify a resolution.</p>
+
+<dt><a name="122_(Resolution_On_Open_Status)"
+>122 (Resolution On Open Status)</a></dt>
+
+<dd>
+<p>This bug has an open status, but you specified a value for the <code class="code">resolution</code> field.</p>
+
+<dt><a name="123_(Invalid_Status_Transition)"
+>123 (Invalid Status Transition)</a></dt>
+
+<dd>
+<p>You tried to change from one status to another, but the status workflow rules don&#39;t allow that change.</p>
+</dd>
+</dl>
+
+<dt><a name="History"
+><b>History</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Added_in_Bugzilla_4.0."
+>Added in Bugzilla <b>4.0</b>.</a></dt>
+</dl>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="update_see_also"
+>update_see_also</a></h2>
+
+<p><b>EXPERIMENTAL</b></p>
+
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Adds or removes URLs for the &#34;See Also&#34; field on bugs. These URLs must point to some valid bug in some Bugzilla installation or in Launchpad.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="ids"
+><code class="code">ids</code></a></dt>
+
+<dd>
+<p>Array of <code class="code">int</code>s or <code class="code">string</code>s. The ids or aliases of bugs that you want to modify.</p>
+
+<dt><a name="add"
+><code class="code">add</code></a></dt>
+
+<dd>
+<p>Array of <code class="code">string</code>s. URLs to Bugzilla bugs. These URLs will be added to the See Also field. They must be valid URLs to <code class="code">show_bug.cgi</code> in a Bugzilla installation or to a bug filed at launchpad.net.</p>
+
+<p>If the URLs don&#39;t start with <code class="code">http://</code> or <code class="code">https://</code>, it will be assumed that <code class="code">http://</code> should be added to the beginning of the string.</p>
+
+<p>It is safe to specify URLs that are already in the &#34;See Also&#34; field on a bug--they will just be silently ignored.</p>
+
+<dt><a name="remove"
+><code class="code">remove</code></a></dt>
+
+<dd>
+<p>Array of <code class="code">string</code>s. These URLs will be removed from the See Also field. You must specify the full URL that you want removed. However, matching is done case-insensitively, so you don&#39;t have to specify the URL in exact case, if you don&#39;t want to.</p>
+
+<p>If you specify a URL that is not in the See Also field of a particular bug, it will just be silently ignored. Invaild URLs are currently silently ignored, though this may change in some future version of Bugzilla.</p>
+</dd>
+</dl>
+
+<p>NOTE: If you specify the same URL in both <code class="code">add</code> and <code class="code">remove</code>, it will be <i>added</i>. (That is, <code class="code">add</code> overrides <code class="code">remove</code>.)</p>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p><code class="code">changes</code>, a hash where the keys are numeric bug ids and the contents are a hash with one key, <code class="code">see_also</code>. <code class="code">see_also</code> points to a hash, which contains two keys, <code class="code">added</code> and <code class="code">removed</code>. These are arrays of strings, representing the actual changes that were made to the bug.</p>
+
+<p>Here&#39;s a diagram of what the return value looks like for updating bug ids 1 and 2:</p>
+
+<pre class="code"> {
+ changes =&#62; {
+ 1 =&#62; {
+ see_also =&#62; {
+ added =&#62; (an array of bug URLs),
+ removed =&#62; (an array of bug URLs),
+ }
+ },
+ 2 =&#62; {
+ see_also =&#62; {
+ added =&#62; (an array of bug URLs),
+ removed =&#62; (an array of bug URLs),
+ }
+ }
+ }
+ }</pre>
+
+<p>This return value allows you to tell what this method actually did. It is in this format to be compatible with the return value of a future <code class="code">Bug.update</code> method.</p>
+
+<dt><a name="Errors"
+><b>Errors</b></a></dt>
+
+<dd>
+<p>This method can throw all of the errors that <a href="#get" class="podlinkpod"
+>&#34;get&#34;</a> throws, plus:</p>
+
+<dl>
+<dt><a name="109_(Bug_Edit_Denied)"
+>109 (Bug Edit Denied)</a></dt>
+
+<dd>
+<p>You did not have the necessary rights to edit the bug.</p>
+
+<dt><a name="112_(Invalid_Bug_URL)"
+>112 (Invalid Bug URL)</a></dt>
+
+<dd>
+<p>One of the URLs you provided did not look like a valid bug URL.</p>
+
+<dt><a name="115_(See_Also_Edit_Denied)"
+>115 (See Also Edit Denied)</a></dt>
+
+<dd>
+<p>You did not have the necessary rights to edit the See Also field for this bug.</p>
+</dd>
+</dl>
+
+<dt><a name="History"
+><b>History</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Added_in_Bugzilla_3.4."
+>Added in Bugzilla <b>3.4</b>.</a></dt>
+
+<dd>
+<dt><a name="Before_Bugzilla_3.6,_error_115_had_a_generic_error_code_of_32000."
+>Before Bugzilla <b>3.6</b>, error 115 had a generic error code of 32000.</a></dt>
+</dl>
+</dd>
+</dl>
+<p class="backlinkbottom"><b><a name="___bottom" href="../../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/WebService/Bugzilla.html b/docs/en/html/api/Bugzilla/WebService/Bugzilla.html
new file mode 100644
index 000000000..60ddc11ea
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/WebService/Bugzilla.html
@@ -0,0 +1,253 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::WebService::Bugzilla</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href="../.././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::WebService::Bugzilla</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#METHODS'>METHODS</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#version'>version</a>
+ <li class='indexItem indexItem2'><a href='#extensions'>extensions</a>
+ <li class='indexItem indexItem2'><a href='#timezone'>timezone</a>
+ <li class='indexItem indexItem2'><a href='#time'>time</a>
+ </ul>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::WebService::Bugzilla - Global functions for the webservice interface.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>This provides functions that tell you about Bugzilla in general.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="METHODS"
+>METHODS</a></h1>
+
+<p>See <a href="../../Bugzilla/WebService.html" class="podlinkpod"
+>Bugzilla::WebService</a> for a description of how parameters are passed,
+and what <b>STABLE</b>,
+<b>UNSTABLE</b>,
+and <b>EXPERIMENTAL</b> mean.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="version"
+>version</a></h2>
+
+<p><b>STABLE</b></p>
+
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Returns the current version of Bugzilla.</p>
+
+<dt><a name="Params_(none)"
+><b>Params</b> (none)</a></dt>
+
+<dd>
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>A hash with a single item,
+<code class="code">version</code>,
+that is the version as a string.</p>
+
+<dt><a name="Errors_(none)"
+><b>Errors</b> (none)</a></dt>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="extensions"
+>extensions</a></h2>
+
+<p><b>EXPERIMENTAL</b></p>
+
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Gets information about the extensions that are currently installed and enabled in this Bugzilla.</p>
+
+<dt><a name="Params_(none)"
+><b>Params</b> (none)</a></dt>
+
+<dd>
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>A hash with a single item,
+<code class="code">extensions</code>.
+This points to a hash.
+<i>That</i> hash contains the names of extensions as keys,
+and the values are a hash.
+That hash contains a single key <code class="code">version</code>,
+which is the version of the extension,
+or <code class="code">0</code> if the extension hasn&#39;t defined a version.</p>
+
+<p>The return value looks something like this:</p>
+
+<pre class="code"> extensions =&#62; {
+ Example =&#62; {
+ version =&#62; &#39;3.6&#39;,
+ },
+ BmpConvert =&#62; {
+ version =&#62; &#39;1.0&#39;,
+ },
+ }</pre>
+
+<dt><a name="History"
+><b>History</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Added_in_Bugzilla_3.2."
+>Added in Bugzilla <b>3.2</b>.</a></dt>
+
+<dd>
+<dt><a
+>As of Bugzilla <b>3.6</b>, the names of extensions are canonical names that the extensions define themselves. Before 3.6, the names of the extensions depended on the directory they were in on the Bugzilla server.</a></dt>
+</dl>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="timezone"
+>timezone</a></h2>
+
+<p><b>DEPRECATED</b> This method may be removed in a future version of Bugzilla. Use <a href="#time" class="podlinkpod"
+>&#34;time&#34;</a> instead.</p>
+
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Returns the timezone that Bugzilla expects dates and times in.</p>
+
+<dt><a name="Params_(none)"
+><b>Params</b> (none)</a></dt>
+
+<dd>
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>A hash with a single item, <code class="code">timezone</code>, that is the timezone offset as a string in (+/-)XXXX (RFC 2822) format.</p>
+
+<dt><a name="History"
+><b>History</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="As_of_Bugzilla_3.6,_the_timezone_returned_is_always_+0000_(the_UTC_timezone)."
+>As of Bugzilla <b>3.6</b>, the timezone returned is always <code class="code">+0000</code> (the UTC timezone).</a></dt>
+</dl>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="time"
+>time</a></h2>
+
+<p><b>STABLE</b></p>
+
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Gets information about what time the Bugzilla server thinks it is, and what timezone it&#39;s running in.</p>
+
+<dt><a name="Params_(none)"
+><b>Params</b> (none)</a></dt>
+
+<dd>
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>A struct with the following items:</p>
+
+<dl>
+<dt><a name="db_time"
+><code class="code">db_time</code></a></dt>
+
+<dd>
+<p><code class="code">dateTime</code> The current time in UTC, according to the Bugzilla <i>database server</i>.</p>
+
+<p>Note that Bugzilla assumes that the database and the webserver are running in the same time zone. However, if the web server and the database server aren&#39;t synchronized for some reason, <i>this</i> is the time that you should rely on for doing searches and other input to the WebService.</p>
+
+<dt><a name="web_time"
+><code class="code">web_time</code></a></dt>
+
+<dd>
+<p><code class="code">dateTime</code> This is the current time in UTC, according to Bugzilla&#39;s <i>web server</i>.</p>
+
+<p>This might be different by a second from <code class="code">db_time</code> since this comes from a different source. If it&#39;s any more different than a second, then there is likely some problem with this Bugzilla instance. In this case you should rely on the <code class="code">db_time</code>, not the <code class="code">web_time</code>.</p>
+
+<dt><a name="web_time_utc"
+><code class="code">web_time_utc</code></a></dt>
+
+<dd>
+<p>Identical to <code class="code">web_time</code>. (Exists only for backwards-compatibility with versions of Bugzilla before 3.6.)</p>
+
+<dt><a name="tz_name"
+><code class="code">tz_name</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The literal string <code class="code">UTC</code>. (Exists only for backwards-compatibility with versions of Bugzilla before 3.6.)</p>
+
+<dt><a name="tz_short_name"
+><code class="code">tz_short_name</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The literal string <code class="code">UTC</code>. (Exists only for backwards-compatibility with versions of Bugzilla before 3.6.)</p>
+
+<dt><a name="tz_offset"
+><code class="code">tz_offset</code></a></dt>
+
+<dd>
+<p><code class="code">string</code> The literal string <code class="code">+0000</code>. (Exists only for backwards-compatibility with versions of Bugzilla before 3.6.)</p>
+</dd>
+</dl>
+
+<dt><a name="History"
+><b>History</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Added_in_Bugzilla_3.4."
+>Added in Bugzilla <b>3.4</b>.</a></dt>
+
+<dd>
+<dt><a
+>As of Bugzilla <b>3.6</b>, this method returns all data as though the server were in the UTC timezone, instead of returning information in the server&#39;s local timezone.</a></dt>
+</dl>
+</dd>
+</dl>
+<p class="backlinkbottom"><b><a name="___bottom" href="../../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/WebService/Product.html b/docs/en/html/api/Bugzilla/WebService/Product.html
new file mode 100644
index 000000000..33e29197b
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/WebService/Product.html
@@ -0,0 +1,195 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::Webservice::Product</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href="../.././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::Webservice::Product</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#METHODS'>METHODS</a>
+ <li class='indexItem indexItem1'><a href='#List_Products'>List Products</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#get_selectable_products'>get_selectable_products</a>
+ <li class='indexItem indexItem2'><a href='#get_enterable_products'>get_enterable_products</a>
+ <li class='indexItem indexItem2'><a href='#get_accessible_products'>get_accessible_products</a>
+ <li class='indexItem indexItem2'><a href='#get'>get</a>
+ </ul>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::Webservice::Product - The Product API</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>This part of the Bugzilla API allows you to list the available Products and get information about them.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="METHODS"
+>METHODS</a></h1>
+
+<p>See <a href="../../Bugzilla/WebService.html" class="podlinkpod"
+>Bugzilla::WebService</a> for a description of how parameters are passed,
+and what <b>STABLE</b>,
+<b>UNSTABLE</b>,
+and <b>EXPERIMENTAL</b> mean.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="List_Products"
+>List Products</a></h1>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="get_selectable_products"
+>get_selectable_products</a></h2>
+
+<p><b>EXPERIMENTAL</b></p>
+
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Returns a list of the ids of the products the user can search on.</p>
+
+<dt><a name="Params_(none)"
+><b>Params</b> (none)</a></dt>
+
+<dd>
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>A hash containing one item,
+<code class="code">ids</code>,
+that contains an array of product ids.</p>
+
+<dt><a name="Errors_(none)"
+><b>Errors</b> (none)</a></dt>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="get_enterable_products"
+>get_enterable_products</a></h2>
+
+<p><b>EXPERIMENTAL</b></p>
+
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Returns a list of the ids of the products the user can enter bugs against.</p>
+
+<dt><a name="Params_(none)"
+><b>Params</b> (none)</a></dt>
+
+<dd>
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>A hash containing one item,
+<code class="code">ids</code>,
+that contains an array of product ids.</p>
+
+<dt><a name="Errors_(none)"
+><b>Errors</b> (none)</a></dt>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="get_accessible_products"
+>get_accessible_products</a></h2>
+
+<p><b>UNSTABLE</b></p>
+
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Returns a list of the ids of the products the user can search or enter bugs against.</p>
+
+<dt><a name="Params_(none)"
+><b>Params</b> (none)</a></dt>
+
+<dd>
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>A hash containing one item,
+<code class="code">ids</code>,
+that contains an array of product ids.</p>
+
+<dt><a name="Errors_(none)"
+><b>Errors</b> (none)</a></dt>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="get"
+>get</a></h2>
+
+<p><b>EXPERIMENTAL</b></p>
+
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Returns a list of information about the products passed to it.</p>
+
+<p>Note: Can also be called as &#34;get_products&#34; for compatibilty with Bugzilla 3.0 API.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<p>A hash containing one item,
+<code class="code">ids</code>,
+that is an array of product ids.</p>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>A hash containing one item,
+<code class="code">products</code>,
+that is an array of hashes.
+Each hash describes a product,
+and has the following items: <code class="code">id</code>,
+<code class="code">name</code>,
+<code class="code">description</code>,
+and <code class="code">internals</code>.
+The <code class="code">id</code> item is the id of the product.
+The <code class="code">name</code> item is the name of the product.
+The <code class="code">description</code> is the description of the product.
+Finally,
+the <code class="code">internals</code> is an internal representation of the product.</p>
+
+<p>Note,
+that if the user tries to access a product that is not in the list of accessible products for the user,
+or a product that does not exist,
+that is silently ignored,
+and no information about that product is returned.</p>
+
+<dt><a name="Errors_(none)"
+><b>Errors</b> (none)</a></dt>
+</dl>
+<p class="backlinkbottom"><b><a name="___bottom" href="../../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/WebService/Server/JSONRPC.html b/docs/en/html/api/Bugzilla/WebService/Server/JSONRPC.html
new file mode 100644
index 000000000..52b0bc9b4
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/WebService/Server/JSONRPC.html
@@ -0,0 +1,171 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::WebService::Server::JSONRPC</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href="../../.././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../../../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::WebService::Server::JSONRPC</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#JSON-RPC'>JSON-RPC</a>
+ <li class='indexItem indexItem1'><a href='#CONNECTING'>CONNECTING</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#Connecting_via_GET'>Connecting via GET</a>
+ <li class='indexItem indexItem2'><a href='#JSONP'>JSONP</a>
+ </ul>
+ <li class='indexItem indexItem1'><a href='#PARAMETERS'>PARAMETERS</a>
+ <li class='indexItem indexItem1'><a href='#ERRORS'>ERRORS</a>
+ <li class='indexItem indexItem1'><a href='#SEE_ALSO'>SEE ALSO</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::WebService::Server::JSONRPC - The JSON-RPC Interface to Bugzilla</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>This documentation describes things about the Bugzilla WebService that are specific to JSON-RPC.
+For a general overview of the Bugzilla WebServices,
+see <a href="../../../Bugzilla/WebService.html" class="podlinkpod"
+>Bugzilla::WebService</a>.</p>
+
+<p>Please note that <i>everything</i> about this JSON-RPC interface is <b>EXPERIMENTAL</b>.
+If you want a fully stable API,
+please use the <code class="code">Bugzilla::WebService::Server::XMLRPC|XML-RPC</code> interface.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="JSON-RPC"
+>JSON-RPC</a></h1>
+
+<p>Bugzilla supports both JSON-RPC 1.0 and 1.1.
+We recommend that you use JSON-RPC 1.0 instead of 1.1,
+though,
+because 1.1 is deprecated.</p>
+
+<p>At some point in the future,
+Bugzilla may also support JSON-RPC 2.0.</p>
+
+<p>The JSON-RPC standards are described at <a href="http://json-rpc.org/" class="podlinkurl"
+>http://json-rpc.org/</a>.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="CONNECTING"
+>CONNECTING</a></h1>
+
+<p>The endpoint for the JSON-RPC interface is the <code class="code">jsonrpc.cgi</code> script in your Bugzilla installation.
+For example,
+if your Bugzilla is at <code class="code">bugzilla.yourdomain.com</code>,
+then your JSON-RPC client would access the API via: <code class="code">http://bugzilla.yourdomain.com/jsonrpc.cgi</code></p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Connecting_via_GET"
+>Connecting via GET</a></h2>
+
+<p>The most powerful way to access the JSON-RPC interface is by HTTP POST.
+However,
+for convenience,
+you can also access certain methods by using GET (a normal webpage load).
+Methods that modify the database or cause some action to happen in Bugzilla cannot be called over GET.
+Only methods that simply return data can be used over GET.</p>
+
+<p>For security reasons,
+when you connect over GET,
+cookie authentication is not accepted.
+If you want to authenticate using GET,
+you have to use the <code class="code">Bugzilla_login</code> and <code class="code">Bugzilla_password</code> method described at <a href="../../../Bugzilla/WebService.html#LOGGING_IN" class="podlinkpod"
+>&#34;LOGGING IN&#34; in Bugzilla::WebService</a>.</p>
+
+<p>To connect over GET,
+simply send the values that you&#39;d normally send for each JSON-RPC argument as URL parameters,
+with the <code class="code">params</code> item being a JSON string.</p>
+
+<p>The simplest example is a call to <code class="code">Bugzilla.time</code>:</p>
+
+<pre class="code"> jsonrpc.cgi?method=Bugzilla.time</pre>
+
+<p>Here&#39;s a call to <code class="code">User.get</code>, with several parameters:</p>
+
+<pre class="code"> jsonrpc.cgi?method=User.get&#38;params=[ { &#34;ids&#34;: [1,2], &#34;names&#34;: [&#34;user@domain.com&#34;] } ]</pre>
+
+<p>Although in reality you would url-encode the <code class="code">params</code> argument, so it would look more like this:</p>
+
+<pre class="code"> jsonrpc.cgi?method=User.get&#38;params=%5B+%7B+%22ids%22%3A+%5B1%2C2%5D%2C+%22names%22%3A+%5B%22user%40domain.com%22%5D+%7D+%5D</pre>
+
+<p>You can also specify <code class="code">version</code> as a URL parameter, if you want to specify what version of the JSON-RPC protocol you&#39;re using, and <code class="code">id</code> as a URL parameter if you want there to be a specific <code class="code">id</code> value in the returned JSON-RPC response.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="JSONP"
+>JSONP</a></h2>
+
+<p>When calling the JSON-RPC WebService over GET, you can use the &#34;JSONP&#34; method of doing cross-domain requests, if you want to access the WebService directly on a web page from another site. JSONP is described at <a href="http://bob.pythonmac.org/archives/2005/12/05/remote-json-jsonp/" class="podlinkurl"
+>http://bob.pythonmac.org/archives/2005/12/05/remote-json-jsonp/</a>.</p>
+
+<p>To use JSONP with Bugzilla&#39;s JSON-RPC WebService, simply specify a <code class="code">callback</code> parameter to jsonrpc.cgi when using it via GET as described above. For example, here&#39;s some HTML you could use to get the data from <code class="code">Bugzilla.time</code> on a remote website, using JSONP:</p>
+
+<pre class="code"> &#60;script type=&#34;text/javascript&#34;
+ src=&#34;http://bugzilla.example.com/jsonrpc.cgi?method=Bugzilla.time&#38;amp;callback=foo&#34;&#62;</pre>
+
+<p>That would call the <code class="code">Bugzilla.time</code> method and pass its value to a function called <code class="code">foo</code> as the only argument. All the other URL parameters (such as <code class="code">params</code>, for passing in arguments to methods) that can be passed to <code class="code">jsonrpc.cgi</code> during GET requests are also available, of course. The above is just the simplest possible example.</p>
+
+<p>The values returned when using JSONP are identical to the values returned when not using JSONP, so you will also get error messages if there is an error.</p>
+
+<p>The <code class="code">callback</code> URL parameter may only contain letters, numbers, periods, and the underscore (<code class="code">_</code>) character. Including any other characters will cause Bugzilla to throw an error. (This error will be a normal JSON-RPC response, not JSONP.)</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="PARAMETERS"
+>PARAMETERS</a></h1>
+
+<p>For JSON-RPC 1.0, the very first parameter should be an object containing the named parameters. For example, if you were passing two named parameters, one called <code class="code">foo</code> and the other called <code class="code">bar</code>, the <code class="code">params</code> element of your JSON-RPC call would look like:</p>
+
+<pre class="code"> &#34;params&#34;: [{ &#34;foo&#34;: 1, &#34;bar&#34;: &#34;something&#34; }]</pre>
+
+<p>For JSON-RPC 1.1, you can pass parameters either in the above fashion or using the standard named-parameters mechanism of JSON-RPC 1.1.</p>
+
+<p><code class="code">dateTime</code> fields are strings in the standard ISO-8601 format: <code class="code">YYYY-MM-DDTHH:MM:SSZ</code>, where <code class="code">T</code> and <code class="code">Z</code> are a literal T and Z, respectively. The &#34;Z&#34; means that all times are in UTC timezone--times are always returned in UTC, and should be passed in as UTC. (Note: The JSON-RPC interface currently also accepts non-UTC times for any values passed in, if they include a time-zone specifier that follows the ISO-8601 standard, instead of &#34;Z&#34; at the end. This behavior is expected to continue into the future, but to be fully safe for forward-compatibility with all future versions of Bugzilla, it is safest to pass in all times as UTC with the &#34;Z&#34; timezone specifier.)</p>
+
+<p><code class="code">base64</code> fields are strings that have been base64 encoded. Note that although normal base64 encoding includes newlines to break up the data, newlines within a string are not valid JSON, so you should not insert newlines into your base64-encoded string.</p>
+
+<p>All other types are standard JSON types.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="ERRORS"
+>ERRORS</a></h1>
+
+<p>JSON-RPC 1.0 and JSON-RPC 1.1 both return an <code class="code">error</code> element when they throw an error. In Bugzilla, the error contents look like:</p>
+
+<pre class="code"> { message: &#39;Some message here&#39;, code: 123 }</pre>
+
+<p>So, for example, in JSON-RPC 1.0, an error response would look like:</p>
+
+<pre class="code"> {
+ result: null,
+ error: { message: &#39;Some message here&#39;, code: 123 },
+ id: 1
+ }</pre>
+
+<p>Every error has a &#34;code&#34;, as described in <a href="../../../Bugzilla/WebService.html#ERRORS" class="podlinkpod"
+>&#34;ERRORS&#34; in Bugzilla::WebService</a>. Errors with a numeric <code class="code">code</code> higher than 100000 are errors thrown by the JSON-RPC library that Bugzilla uses, not by Bugzilla.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SEE_ALSO"
+>SEE ALSO</a></h1>
+
+<p><a href="../../../Bugzilla/WebService.html" class="podlinkpod"
+>Bugzilla::WebService</a></p>
+<p class="backlinkbottom"><b><a name="___bottom" href="../../../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/WebService/Server/XMLRPC.html b/docs/en/html/api/Bugzilla/WebService/Server/XMLRPC.html
new file mode 100644
index 000000000..118a6268a
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/WebService/Server/XMLRPC.html
@@ -0,0 +1,126 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::WebService::Server::XMLRPC</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href="../../.././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../../../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::WebService::Server::XMLRPC</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#XML-RPC'>XML-RPC</a>
+ <li class='indexItem indexItem1'><a href='#CONNECTING'>CONNECTING</a>
+ <li class='indexItem indexItem1'><a href='#PARAMETERS'>PARAMETERS</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#How_XML-RPC_WebService_Methods_Take_Parameters'>How XML-RPC WebService Methods Take Parameters</a>
+ </ul>
+ <li class='indexItem indexItem1'><a href='#EXTENSIONS_TO_THE_XML-RPC_STANDARD'>EXTENSIONS TO THE XML-RPC STANDARD</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#Undefined_Values'>Undefined Values</a>
+ </ul>
+ <li class='indexItem indexItem1'><a href='#SEE_ALSO'>SEE ALSO</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::WebService::Server::XMLRPC - The XML-RPC Interface to Bugzilla</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>This documentation describes things about the Bugzilla WebService that are specific to XML-RPC.
+For a general overview of the Bugzilla WebServices,
+see <a href="../../../Bugzilla/WebService.html" class="podlinkpod"
+>Bugzilla::WebService</a>.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="XML-RPC"
+>XML-RPC</a></h1>
+
+<p>The XML-RPC standard is described here: <a href="http://www.xmlrpc.com/spec" class="podlinkurl"
+>http://www.xmlrpc.com/spec</a></p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="CONNECTING"
+>CONNECTING</a></h1>
+
+<p>The endpoint for the XML-RPC interface is the <code class="code">xmlrpc.cgi</code> script in your Bugzilla installation.
+For example,
+if your Bugzilla is at <code class="code">bugzilla.yourdomain.com</code>,
+then your XML-RPC client would access the API via: <code class="code">http://bugzilla.yourdomain.com/xmlrpc.cgi</code></p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="PARAMETERS"
+>PARAMETERS</a></h1>
+
+<p><code class="code">dateTime</code> fields are the standard <code class="code">dateTime.iso8601</code> XML-RPC field.
+They should be in <code class="code">YYYY-MM-DDTHH:MM:SS</code> format (where <code class="code">T</code> is a literal T).
+As of Bugzilla <b>3.6</b>,
+Bugzilla always expects <code class="code">dateTime</code> fields to be in the UTC timezone,
+and all returned <code class="code">dateTime</code> values are in the UTC timezone.</p>
+
+<p>All other fields are standard XML-RPC types.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="How_XML-RPC_WebService_Methods_Take_Parameters"
+>How XML-RPC WebService Methods Take Parameters</a></h2>
+
+<p>All functions take a single argument,
+a <code class="code">&#60;struct&#62;</code> that contains all parameters.
+The names of the parameters listed in the API docs for each function are the <code class="code">&#60;name&#62;</code> element for the struct <code class="code">&#60;member&#62;</code>s.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="EXTENSIONS_TO_THE_XML-RPC_STANDARD"
+>EXTENSIONS TO THE XML-RPC STANDARD</a></h1>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Undefined_Values"
+>Undefined Values</a></h2>
+
+<p>Normally,
+XML-RPC does not allow empty values for <code class="code">int</code>,
+<code class="code">double</code>,
+or <code class="code">dateTime.iso8601</code> fields.
+Bugzilla does--it treats empty values as <code class="code">undef</code> (called <code class="code">NULL</code> or <code class="code">None</code> in some programming languages).</p>
+
+<p>Bugzilla accepts a timezone specifier at the end of <code class="code">dateTime.iso8601</code> fields that are specified as method arguments.
+The format of the timezone specifier is specified in the ISO-8601 standard.
+If no timezone specifier is included,
+the passed-in time is assumed to be in the UTC timezone.
+Bugzilla will never output a timezone specifier on returned data,
+because doing so would violate the XML-RPC specification.
+All returned times are in the UTC timezone.</p>
+
+<p>Bugzilla also accepts an element called <code class="code">&#60;nil&#62;</code>,
+as specified by the XML-RPC extension here: <a href="http://ontosys.com/xml-rpc/extensions.php" class="podlinkurl"
+>http://ontosys.com/xml-rpc/extensions.php</a>,
+which is always considered to be <code class="code">undef</code>,
+no matter what it contains.</p>
+
+<p>Bugzilla does not use <code class="code">&#60;nil&#62;</code> values in returned data,
+because currently most clients do not support <code class="code">&#60;nil&#62;</code>.
+Instead,
+any fields with <code class="code">undef</code> values will be stripped from the response completely.
+Therefore <b>the client must handle the fact that some expected fields may not be returned</b>.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SEE_ALSO"
+>SEE ALSO</a></h1>
+
+<p><a href="../../../Bugzilla/WebService.html" class="podlinkpod"
+>Bugzilla::WebService</a></p>
+<p class="backlinkbottom"><b><a name="___bottom" href="../../../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/WebService/User.html b/docs/en/html/api/Bugzilla/WebService/User.html
new file mode 100644
index 000000000..4d69fc532
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/WebService/User.html
@@ -0,0 +1,532 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::Webservice::User</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href="../.././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::Webservice::User</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#METHODS'>METHODS</a>
+ <li class='indexItem indexItem1'><a href='#Logging_In_and_Out'>Logging In and Out</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#login'>login</a>
+ <li class='indexItem indexItem2'><a href='#logout'>logout</a>
+ </ul>
+ <li class='indexItem indexItem1'><a href='#Account_Creation'>Account Creation</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#offer_account_by_email'>offer_account_by_email</a>
+ <li class='indexItem indexItem2'><a href='#create'>create</a>
+ </ul>
+ <li class='indexItem indexItem1'><a href='#User_Info'>User Info</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#get'>get</a>
+ </ul>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::Webservice::User - The User Account and Login API</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>This part of the Bugzilla API allows you to create User Accounts and log in/out using an existing account.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="METHODS"
+>METHODS</a></h1>
+
+<p>See <a href="../../Bugzilla/WebService.html" class="podlinkpod"
+>Bugzilla::WebService</a> for a description of how parameters are passed,
+and what <b>STABLE</b>,
+<b>UNSTABLE</b>,
+and <b>EXPERIMENTAL</b> mean.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="Logging_In_and_Out"
+>Logging In and Out</a></h1>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="login"
+>login</a></h2>
+
+<p><b>STABLE</b></p>
+
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Logging in,
+with a username and password,
+is required for many Bugzilla installations,
+in order to search for bugs,
+post new bugs,
+etc.
+This method logs in an user.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="login_(string)_-_The_user&#39;s_login_name."
+><code class="code">login</code> (string) - The user&#39;s login name.</a></dt>
+
+<dd>
+<dt><a name="password_(string)_-_The_user&#39;s_password."
+><code class="code">password</code> (string) - The user&#39;s password.</a></dt>
+
+<dd>
+<dt><a
+><code class="code">remember</code> (bool) <b>Optional</b> - if the cookies returned by the call to login should expire with the session or not.
+In order for this option to have effect the Bugzilla server must be configured to allow the user to set this option - the Bugzilla parameter <i>rememberlogin</i> must be set to &#34;defaulton&#34; or &#34;defaultoff&#34;.
+Addionally,
+the client application must implement management of cookies across sessions.</a></dt>
+</dl>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>On success,
+a hash containing one item,
+<code class="code">id</code>,
+the numeric id of the user that was logged in.
+A set of http cookies is also sent with the response.
+These cookies must be sent along with any future requests to the webservice,
+for the duration of the session.</p>
+
+<dt><a name="Errors"
+><b>Errors</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="300_(Invalid_Username_or_Password)"
+>300 (Invalid Username or Password)</a></dt>
+
+<dd>
+<p>The username does not exist,
+or the password is wrong.</p>
+
+<dt><a name="301_(Account_Disabled)"
+>301 (Account Disabled)</a></dt>
+
+<dd>
+<p>The account has been disabled.
+A reason may be specified with the error.</p>
+
+<dt><a name="305_(New_Password_Required)"
+>305 (New Password Required)</a></dt>
+
+<dd>
+<p>The current password is correct,
+but the user is asked to change his password.</p>
+
+<dt><a name="50_(Param_Required)"
+>50 (Param Required)</a></dt>
+
+<dd>
+<p>A login or password parameter was not provided.</p>
+</dd>
+</dl>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="logout"
+>logout</a></h2>
+
+<p><b>STABLE</b></p>
+
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Log out the user.
+Does nothing if there is no user logged in.</p>
+
+<dt><a name="Params_(none)"
+><b>Params</b> (none)</a></dt>
+
+<dd>
+<dt><a name="Returns_(nothing)"
+><b>Returns</b> (nothing)</a></dt>
+
+<dd>
+<dt><a name="Errors_(none)"
+><b>Errors</b> (none)</a></dt>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="Account_Creation"
+>Account Creation</a></h1>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="offer_account_by_email"
+>offer_account_by_email</a></h2>
+
+<p><b>STABLE</b></p>
+
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Sends an email to the user,
+offering to create an account.
+The user will have to click on a URL in the email,
+and choose their password and real name.</p>
+
+<p>This is the recommended way to create a Bugzilla account.</p>
+
+<dt><a name="Param"
+><b>Param</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="email_(string)_-_the_email_to_send_the_offer_to."
+><code class="code">email</code> (string) - the email to send the offer to.</a></dt>
+</dl>
+
+<dt><a name="Returns_(nothing)"
+><b>Returns</b> (nothing)</a></dt>
+
+<dd>
+<dt><a name="Errors"
+><b>Errors</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="500_(Illegal_Email_Address)"
+>500 (Illegal Email Address)</a></dt>
+
+<dd>
+<p>This Bugzilla does not allow you to create accounts with the format of email address you specified.
+Account creation may be entirely disabled.</p>
+
+<dt><a name="501_(Account_Already_Exists)"
+>501 (Account Already Exists)</a></dt>
+
+<dd>
+<p>An account with that email address already exists in Bugzilla.</p>
+</dd>
+</dl>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="create"
+>create</a></h2>
+
+<p><b>STABLE</b></p>
+
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Creates a user account directly in Bugzilla,
+password and all.
+Instead of this,
+you should use <a href="#offer_account_by_email" class="podlinkpod"
+>&#34;offer_account_by_email&#34;</a> when possible,
+because that makes sure that the email address specified can actually receive an email.
+This function does not check that.</p>
+
+<p>You must be logged in and have the <code class="code">editusers</code> privilege in order to call this function.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="email_(string)_-_The_email_address_for_the_new_user."
+><code class="code">email</code> (string) - The email address for the new user.</a></dt>
+
+<dd>
+<dt><a name="full_name_(string)_Optional_-_The_user&#39;s_full_name._Will_be_set_to_empty_if_not_specified."
+><code class="code">full_name</code> (string) <b>Optional</b> - The user&#39;s full name.
+Will be set to empty if not specified.</a></dt>
+
+<dd>
+<dt><a
+><code class="code">password</code> (string) <b>Optional</b> - The password for the new user account,
+in plain text.
+It will be stripped of leading and trailing whitespace.
+If blank or not specified,
+the newly created account will exist in Bugzilla,
+but will not be allowed to log in using DB authentication until a password is set either by the user (through resetting their password) or by the administrator.</a></dt>
+</dl>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>A hash containing one item,
+<code class="code">id</code>,
+the numeric id of the user that was created.</p>
+
+<dt><a name="Errors"
+><b>Errors</b></a></dt>
+
+<dd>
+<p>The same as <a href="#offer_account_by_email" class="podlinkpod"
+>&#34;offer_account_by_email&#34;</a>.
+If a password is specified,
+the function may also throw:</p>
+
+<dl>
+<dt><a name="502_(Password_Too_Short)"
+>502 (Password Too Short)</a></dt>
+
+<dd>
+<p>The password specified is too short.
+(Usually,
+this means the password is under three characters.)</p>
+</dd>
+</dl>
+
+<dt><a name="History"
+><b>History</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Error_503_(Password_Too_Long)_removed_in_Bugzilla_3.6."
+>Error 503 (Password Too Long) removed in Bugzilla <b>3.6</b>.</a></dt>
+</dl>
+</dd>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="User_Info"
+>User Info</a></h1>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="get"
+>get</a></h2>
+
+<p><b>STABLE</b></p>
+
+<dl>
+<dt><a name="Description"
+><b>Description</b></a></dt>
+
+<dd>
+<p>Gets information about user accounts in Bugzilla.</p>
+
+<dt><a name="Params"
+><b>Params</b></a></dt>
+
+<dd>
+<p><b>Note</b>: At least one of <code class="code">ids</code>,
+<code class="code">names</code>,
+or <code class="code">match</code> must be specified.</p>
+
+<p><b>Note</b>: Users will not be returned more than once,
+so even if a user is matched by more than one argument,
+only one user will be returned.</p>
+
+<p>In addition to the parameters below,
+this method also accepts the standard <a href="../../Bugzilla/WebService.html#include_fields" class="podlinkpod"
+>include_fields</a> and <a href="../../Bugzilla/WebService.html#exclude_fields" class="podlinkpod"
+>exclude_fields</a> arguments.</p>
+
+<dl>
+<dt><a name="ids_(array)"
+><code class="code">ids</code> (array)</a></dt>
+
+<dd>
+<p>An array of integers,
+representing user ids.</p>
+
+<p>Logged-out users cannot pass this parameter to this function.
+If they try,
+they will get an error.
+Logged-in users will get an error if they specify the id of a user they cannot see.</p>
+
+<dt><a name="names_(array)"
+><code class="code">names</code> (array)</a></dt>
+
+<dd>
+<p>An array of login names (strings).</p>
+
+<dt><a name="match_(array)"
+><code class="code">match</code> (array)</a></dt>
+
+<dd>
+<p>An array of strings.
+This works just like &#34;user matching&#34; in Bugzilla itself.
+Users will be returned whose real name or login name contains any one of the specified strings.
+Users that you cannot see will not be included in the returned list.</p>
+
+<p>Some Bugzilla installations have user-matching turned off,
+in which case you will only be returned exact matches.</p>
+
+<p>Most installations have a limit on how many matches are returned for each string,
+which defaults to 1000 but can be changed by the Bugzilla administrator.</p>
+
+<p>Logged-out users cannot use this argument,
+and an error will be thrown if they try.
+(This is to make it harder for spammers to harvest email addresses from Bugzilla,
+and also to enforce the user visibility restrictions that are implemented on some Bugzillas.)</p>
+
+<dt><a name="group_ids_(array)"
+><code class="code">group_ids</code> (array)</a></dt>
+
+<dd>
+<dt><a name="groups_(array)"
+><code class="code">groups</code> (array)</a></dt>
+
+<dd>
+<p><code class="code">group_ids</code> is an array of numeric ids for groups that a user can be in.
+<code class="code">groups</code> is an array of names of groups that a user can be in.
+If these are specified,
+they limit the return value to users who are in <i>any</i> of the groups specified.</p>
+
+<dt><a name="include_disabled_(boolean)"
+><code class="code">include_disabled</code> (boolean)</a></dt>
+
+<dd>
+<p>By default,
+when using the <code class="code">match</code> parameter,
+disabled users are excluded from the returned results unless their full username is identical to the match string.
+Setting <code class="code">include_disabled</code> to <code class="code">true</code> will include disabled users in the returned results even if their username doesn&#39;t fully match the input string.</p>
+</dd>
+</dl>
+
+<dt><a name="Returns"
+><b>Returns</b></a></dt>
+
+<dd>
+<p>A hash containing one item,
+<code class="code">users</code>,
+that is an array of hashes.
+Each hash describes a user,
+and has the following items:</p>
+
+<dl>
+<dt><a name="id"
+>id</a></dt>
+
+<dd>
+<p><code class="code">int</code> The unique integer ID that Bugzilla uses to represent this user.
+Even if the user&#39;s login name changes,
+this will not change.</p>
+
+<dt><a name="real_name"
+>real_name</a></dt>
+
+<dd>
+<p><code class="code">string</code> The actual name of the user.
+May be blank.</p>
+
+<dt><a name="email"
+>email</a></dt>
+
+<dd>
+<p><code class="code">string</code> The email address of the user.</p>
+
+<dt><a name="name"
+>name</a></dt>
+
+<dd>
+<p><code class="code">string</code> The login name of the user.
+Note that in some situations this is different than their email.</p>
+
+<dt><a name="can_login"
+>can_login</a></dt>
+
+<dd>
+<p><code class="code">boolean</code> A boolean value to indicate if the user can login into bugzilla.</p>
+
+<dt><a name="email_enabled"
+>email_enabled</a></dt>
+
+<dd>
+<p><code class="code">boolean</code> A boolean value to indicate if bug-related mail will be sent to the user or not.</p>
+
+<dt><a name="login_denied_text"
+>login_denied_text</a></dt>
+
+<dd>
+<p><code class="code">string</code> A text field that holds the reason for disabling a user from logging into bugzilla,
+if empty then the user account is enabled.
+Otherwise it is disabled/closed.</p>
+
+<p><b>Note</b>: If you are not logged in to Bugzilla when you call this function,
+you will only be returned the <code class="code">id</code>,
+<code class="code">name</code>,
+and <code class="code">real_name</code> items.
+If you are logged in and not in editusers group,
+you will only be returned the <code class="code">id</code>,
+<code class="code">name</code>,
+<code class="code">real_name</code>,
+<code class="code">email</code>,
+and <code class="code">can_login</code> items.</p>
+</dd>
+</dl>
+
+<dt><a name="Errors"
+><b>Errors</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="51_(Bad_Login_Name_or_Group_Name)"
+>51 (Bad Login Name or Group Name)</a></dt>
+
+<dd>
+<p>You passed an invalid login name in the &#34;names&#34; array or a bad group name/id in the <code class="code">groups</code>/<code class="code">group_ids</code> arguments.</p>
+
+<dt><a name="304_(Authorization_Required)"
+>304 (Authorization Required)</a></dt>
+
+<dd>
+<p>You are logged in,
+but you are not authorized to see one of the users you wanted to get information about by user id.</p>
+
+<dt><a name="505_(User_Access_By_Id_or_User-Matching_Denied)"
+>505 (User Access By Id or User-Matching Denied)</a></dt>
+
+<dd>
+<p>Logged-out users cannot use the &#34;ids&#34; or &#34;match&#34; arguments to this function.</p>
+</dd>
+</dl>
+
+<dt><a name="History"
+><b>History</b></a></dt>
+
+<dd>
+<dl>
+<dt><a name="Added_in_Bugzilla_3.4."
+>Added in Bugzilla <b>3.4</b>.</a></dt>
+
+<dd>
+<dt><a name="group_ids_and_groups_were_added_in_Bugzilla_4.0."
+><code class="code">group_ids</code> and <code class="code">groups</code> were added in Bugzilla <b>4.0</b>.</a></dt>
+
+<dd>
+<dt><a name="include_disabled_added_in_Bugzilla_4.0._Default_behavior_for_match_has_changed_to_only_returning_enabled_accounts."
+><code class="code">include_disabled</code> added in Bugzilla <b>4.0</b>.
+Default behavior for <code class="code">match</code> has changed to only returning enabled accounts.</a></dt>
+</dl>
+</dd>
+</dl>
+<p class="backlinkbottom"><b><a name="___bottom" href="../../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/WebService/Util.html b/docs/en/html/api/Bugzilla/WebService/Util.html
new file mode 100644
index 000000000..83c891881
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/WebService/Util.html
@@ -0,0 +1,77 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::WebService::Util</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href="../.././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1></h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#METHODS'>METHODS</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#filter'>filter</a>
+ <li class='indexItem indexItem2'><a href='#filter_wants'>filter_wants</a>
+ <li class='indexItem indexItem2'><a href='#validate'>validate</a>
+ </ul>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::WebService::Util - Utility functions used inside of the WebService code.
+These are <b>not</b> functions that can be called via the WebService.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>This is somewhat like <a href="../../Bugzilla/Util.html" class="podlinkpod"
+>Bugzilla::Util</a>,
+but these functions are only used internally in the WebService code.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<pre class="code"> filter({ include_fields =&#62; [&#39;id&#39;, &#39;name&#39;],
+ exclude_fields =&#62; [&#39;name&#39;] }, $hash);
+ my $wants = filter_wants $params, &#39;field_name&#39;;
+ validate(@_, &#39;ids&#39;);</pre>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="METHODS"
+>METHODS</a></h1>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="filter"
+>filter</a></h2>
+
+<p>This helps implement the <code class="code">include_fields</code> and <code class="code">exclude_fields</code> arguments of WebService methods. Given a hash (the second argument to this subroutine), this will remove any keys that are <i>not</i> in <code class="code">include_fields</code> and then remove any keys that <i>are</i> in <code class="code">exclude_fields</code>.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="filter_wants"
+>filter_wants</a></h2>
+
+<p>Returns <code class="code">1</code> if a filter would preserve the specified field when passing a hash to <a href="#filter" class="podlinkpod"
+>&#34;filter&#34;</a>, <code class="code">0</code> otherwise.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="validate"
+>validate</a></h2>
+
+<p>This helps in the validation of parameters passed into the WebSerice methods. Currently it converts listed parameters into an array reference if the client only passed a single scalar value. It modifies the parameters hash in place so other parameters should be unaltered.</p>
+<p class="backlinkbottom"><b><a name="___bottom" href="../../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/Whine.html b/docs/en/html/api/Bugzilla/Whine.html
new file mode 100644
index 000000000..704236519
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/Whine.html
@@ -0,0 +1,113 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::Whine</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href=".././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::Whine</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#METHODS'>METHODS</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#Constructors'>Constructors</a>
+ <li class='indexItem indexItem2'><a href='#Accessors'>Accessors</a>
+ </ul>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::Whine - A Whine event</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<pre class="code"> use Bugzilla::Whine;
+
+ my $event = new Bugzilla::Whine($event_id);
+
+ my $subject = $event-&#62;subject;
+ my $body = $event-&#62;body;
+ my $mailifnobugs = $event-&#62;mail_if_no_bugs;
+ my $user = $event-&#62;user;</pre>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>This module exists to represent a whine event that has been saved to the database.</p>
+
+<p>This is an implementation of <a href="../Bugzilla/Object.html" class="podlinkpod"
+>Bugzilla::Object</a>, and so has all the same methods available as <a href="../Bugzilla/Object.html" class="podlinkpod"
+>Bugzilla::Object</a>, in addition to what is documented below.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="METHODS"
+>METHODS</a></h1>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Constructors"
+>Constructors</a></h2>
+
+<dl>
+<dt><a name="new"
+><code class="code">new</code></a></dt>
+
+<dd>
+<p>Does not accept a bare <code class="code">name</code> argument. Instead, accepts only an id.</p>
+
+<p>See also: <a href="../Bugzilla/Object.html#new" class="podlinkpod"
+>&#34;new&#34; in Bugzilla::Object</a>.</p>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Accessors"
+>Accessors</a></h2>
+
+<p>These return data about the object, without modifying the object.</p>
+
+<dl>
+<dt><a name="subject"
+><code class="code">subject</code></a></dt>
+
+<dd>
+<p>Returns the subject of the whine event.</p>
+
+<dt><a name="body"
+><code class="code">body</code></a></dt>
+
+<dd>
+<p>Returns the body of the whine event.</p>
+
+<dt><a name="mail_if_no_bugs"
+><code class="code">mail_if_no_bugs</code></a></dt>
+
+<dd>
+<p>Returns a numeric 1(<code class="code">true</code>) or 0(<code class="code">false</code>) to represent whether this whine event object is supposed to be mailed even if there are no bugs returned by the query.</p>
+
+<dt><a name="user"
+><code class="code">user</code></a></dt>
+
+<dd>
+<p>Returns the <a href="../Bugzilla/User.html" class="podlinkpod"
+>Bugzilla::User</a> object for the owner of the <a href="../Bugzilla/Whine.html" class="podlinkpod"
+>Bugzilla::Whine</a> event.</p>
+</dd>
+</dl>
+<p class="backlinkbottom"><b><a name="___bottom" href="../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/Whine/Query.html b/docs/en/html/api/Bugzilla/Whine/Query.html
new file mode 100644
index 000000000..ba9729fb1
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/Whine/Query.html
@@ -0,0 +1,126 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::Whine::Query</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href="../.././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::Whine::Query</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#METHODS'>METHODS</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#Constructors'>Constructors</a>
+ <li class='indexItem indexItem2'><a href='#Accessors'>Accessors</a>
+ </ul>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::Whine::Query - A query object used by <a href="../../Bugzilla/Whine.html" class="podlinkpod"
+>Bugzilla::Whine</a>.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<pre class="code"> use Bugzilla::Whine::Query;
+
+ my $query = new Bugzilla::Whine::Query($id);
+
+ my $event_id = $query-&#62;eventid;
+ my $id = $query-&#62;id;
+ my $query_name = $query-&#62;name;
+ my $sortkey = $query-&#62;sortkey;
+ my $one_email_per_bug = $query-&#62;one_email_per_bug;
+ my $title = $query-&#62;title;</pre>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>This module exists to represent a query for a <a href="../../Bugzilla/Whine/Event.html" class="podlinkpod"
+>Bugzilla::Whine::Event</a>. Each event, which are groups of schedules and queries based on how the user configured the event, may have zero or more queries associated with it. Additionally, the queries are selected from the user&#39;s saved searches, or <a href="../../Bugzilla/Search/Saved.html" class="podlinkpod"
+>Bugzilla::Search::Saved</a> object with a matching <code class="code">name</code> attribute for the user.</p>
+
+<p>This is an implementation of <a href="../../Bugzilla/Object.html" class="podlinkpod"
+>Bugzilla::Object</a>, and so has all the same methods available as <a href="../../Bugzilla/Object.html" class="podlinkpod"
+>Bugzilla::Object</a>, in addition to what is documented below.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="METHODS"
+>METHODS</a></h1>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Constructors"
+>Constructors</a></h2>
+
+<dl>
+<dt><a name="new"
+><code class="code">new</code></a></dt>
+
+<dd>
+<p>Does not accept a bare <code class="code">name</code> argument. Instead, accepts only an id.</p>
+
+<p>See also: <a href="../../Bugzilla/Object.html#new" class="podlinkpod"
+>&#34;new&#34; in Bugzilla::Object</a>.</p>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Accessors"
+>Accessors</a></h2>
+
+<p>These return data about the object, without modifying the object.</p>
+
+<dl>
+<dt><a name="event_id"
+><code class="code">event_id</code></a></dt>
+
+<dd>
+<p>The <a href="../../Bugzilla/Whine/Event.html" class="podlinkpod"
+>Bugzilla::Whine::Event</a> object id for this object.</p>
+
+<dt><a name="name"
+><code class="code">name</code></a></dt>
+
+<dd>
+<p>The <a href="../../Bugzilla/Search/Saved.html" class="podlinkpod"
+>Bugzilla::Search::Saved</a> query object name for this object.</p>
+
+<dt><a name="sortkey"
+><code class="code">sortkey</code></a></dt>
+
+<dd>
+<p>The relational sorting key as compared with other <a href="../../Bugzilla/Whine/Query.html" class="podlinkpod"
+>Bugzilla::Whine::Query</a> objects.</p>
+
+<dt><a name="one_email_per_bug"
+><code class="code">one_email_per_bug</code></a></dt>
+
+<dd>
+<p>Returns a numeric 1(<code class="code">true</code>) or 0(<code class="code">false</code>) to represent whether this <a href="../../Bugzilla/Whine/Query.html" class="podlinkpod"
+>Bugzilla::Whine::Query</a> object is supposed to be mailed as a list of bugs or one email per bug.</p>
+
+<dt><a name="title"
+><code class="code">title</code></a></dt>
+
+<dd>
+<p>The title of this object as it appears in the user forms and emails.</p>
+</dd>
+</dl>
+<p class="backlinkbottom"><b><a name="___bottom" href="../../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/Bugzilla/Whine/Schedule.html b/docs/en/html/api/Bugzilla/Whine/Schedule.html
new file mode 100644
index 000000000..ffd7bd1ec
--- /dev/null
+++ b/docs/en/html/api/Bugzilla/Whine/Schedule.html
@@ -0,0 +1,139 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+Bugzilla::Whine::Schedule</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href="../.././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>Bugzilla::Whine::Schedule</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#METHODS'>METHODS</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#Constructors'>Constructors</a>
+ <li class='indexItem indexItem2'><a href='#Accessors'>Accessors</a>
+ </ul>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>Bugzilla::Whine::Schedule - A schedule object used by <a href="../../Bugzilla/Whine.html" class="podlinkpod"
+>Bugzilla::Whine</a>.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<pre class="code"> use Bugzilla::Whine::Schedule;
+
+ my $schedule = new Bugzilla::Whine::Schedule($schedule_id);
+
+ my $event_id = $schedule-&#62;eventid;
+ my $run_day = $schedule-&#62;run_day;
+ my $run_time = $schedule-&#62;run_time;
+ my $is_group = $schedule-&#62;mailto_is_group;
+ my $object = $schedule-&#62;mailto;
+ my $array_ref = $schedule-&#62;mailto_users;</pre>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>This module exists to represent a <a href="../../Bugzilla/Whine.html" class="podlinkpod"
+>Bugzilla::Whine</a> event schedule.</p>
+
+<p>This is an implementation of <a href="../../Bugzilla/Object.html" class="podlinkpod"
+>Bugzilla::Object</a>, and so has all the same methods available as <a href="../../Bugzilla/Object.html" class="podlinkpod"
+>Bugzilla::Object</a>, in addition to what is documented below.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="METHODS"
+>METHODS</a></h1>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Constructors"
+>Constructors</a></h2>
+
+<dl>
+<dt><a name="new"
+><code class="code">new</code></a></dt>
+
+<dd>
+<p>Does not accept a bare <code class="code">name</code> argument. Instead, accepts only an id.</p>
+
+<p>See also: <a href="../../Bugzilla/Object.html#new" class="podlinkpod"
+>&#34;new&#34; in Bugzilla::Object</a>.</p>
+</dd>
+</dl>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Accessors"
+>Accessors</a></h2>
+
+<p>These return data about the object, without modifying the object.</p>
+
+<dl>
+<dt><a name="event_id"
+><code class="code">event_id</code></a></dt>
+
+<dd>
+<p>The <a href="../../Bugzilla/Whine.html" class="podlinkpod"
+>Bugzilla::Whine</a> event object id for this object.</p>
+
+<dt><a name="run_day"
+><code class="code">run_day</code></a></dt>
+
+<dd>
+<p>The day or day pattern that a <a href="../../Bugzilla/Whine.html" class="podlinkpod"
+>Bugzilla::Whine</a> event is scheduled to run.</p>
+
+<dt><a name="run_time"
+><code class="code">run_time</code></a></dt>
+
+<dd>
+<p>The time or time pattern that a <a href="../../Bugzilla/Whine.html" class="podlinkpod"
+>Bugzilla::Whine</a> event is scheduled to run.</p>
+
+<dt><a name="mailto_is_group"
+><code class="code">mailto_is_group</code></a></dt>
+
+<dd>
+<p>Returns a numeric 1 (<code class="code">group</code>) or 0 (<code class="code">user</code>) to represent whether <a href="#mailto" class="podlinkpod"
+>&#34;mailto&#34;</a> is a group or user.</p>
+
+<dt><a name="mailto"
+><code class="code">mailto</code></a></dt>
+
+<dd>
+<p>This is either a <a href="../../Bugzilla/User.html" class="podlinkpod"
+>Bugzilla::User</a> or <a href="../../Bugzilla/Group.html" class="podlinkpod"
+>Bugzilla::Group</a> object to represent the user or group this scheduled event is set to be mailed to.</p>
+
+<dt><a name="mailto_users"
+><code class="code">mailto_users</code></a></dt>
+
+<dd>
+<p>Returns an array reference of <a href="../../Bugzilla/User.html" class="podlinkpod"
+>Bugzilla::User</a>s. This is derived from the <a href="../../Bugzilla/Group.html" class="podlinkpod"
+>Bugzilla::Group</a> stored in <a href="#mailto" class="podlinkpod"
+>&#34;mailto&#34;</a> if <a href="#mailto_is_group" class="podlinkpod"
+>&#34;mailto_is_group&#34;</a> is true and the group is still active, otherwise it will contain a single array element for the <a href="../../Bugzilla/User.html" class="podlinkpod"
+>Bugzilla::User</a> in <a href="#mailto" class="podlinkpod"
+>&#34;mailto&#34;</a>.</p>
+</dd>
+</dl>
+<p class="backlinkbottom"><b><a name="___bottom" href="../../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/checksetup.html b/docs/en/html/api/checksetup.html
new file mode 100644
index 000000000..e2babadb6
--- /dev/null
+++ b/docs/en/html/api/checksetup.html
@@ -0,0 +1,267 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+checksetup.pl</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href="./../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>checksetup.pl</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#OPTIONS'>OPTIONS</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#MODIFYING_CHECKSETUP'>MODIFYING CHECKSETUP</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#How_Checksetup_Works'>How Checksetup Works</a>
+ <li class='indexItem indexItem2'><a href='#Modifying_the_Database'>Modifying the Database</a>
+ </ul>
+ <li class='indexItem indexItem1'><a href='#RUNNING_CHECKSETUP_NON-INTERACTIVELY'>RUNNING CHECKSETUP NON-INTERACTIVELY</a>
+ <li class='indexItem indexItem1'><a href='#SEE_ALSO'>SEE ALSO</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>checksetup.pl - A do-it-all upgrade and installation script for Bugzilla.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<pre class="code"> ./checksetup.pl [--help|--check-modules|--version]
+ ./checksetup.pl [SCRIPT [--verbose]] [--no-templates|-t]
+ [--make-admin=user@domain.com]
+ [--reset-password=user@domain.com]</pre>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="OPTIONS"
+>OPTIONS</a></h1>
+
+<dl>
+<dt><a name="SCRIPT"
+><em class="code">SCRIPT</em></a></dt>
+
+<dd>
+<p>Name of script to drive non-interactive mode. This script should define an <code class="code">%answer</code> hash whose keys are variable names and the values answers to all the questions checksetup.pl asks. For details on the format of this script, do <code class="code">perldoc checksetup.pl</code> and look for the <a href="#RUNNING_CHECKSETUP_NON-INTERACTIVELY" class="podlinkpod"
+>&#34;RUNNING CHECKSETUP NON-INTERACTIVELY&#34;</a> section.</p>
+
+<dt><a name="--help"
+><b>--help</b></a></dt>
+
+<dd>
+<p>Display this help text</p>
+
+<dt><a name="--check-modules"
+><b>--check-modules</b></a></dt>
+
+<dd>
+<p>Only check for correct module dependencies and quit afterward.</p>
+
+<dt><a name="--make-admin=username@domain.com"
+><b>--make-admin</b>=username@domain.com</a></dt>
+
+<dd>
+<p>Makes the specified user into a Bugzilla administrator. This is in case you accidentally lock yourself out of the Bugzilla administrative interface.</p>
+
+<dt><a name="--reset-password=user@domain.com"
+><b>--reset-password</b>=user@domain.com</a></dt>
+
+<dd>
+<p>Resets the specified user&#39;s password. checksetup.pl will prompt you to enter a new password for the user.</p>
+
+<dt><a name="--no-templates_(-t)"
+><b>--no-templates</b> (<b>-t</b>)</a></dt>
+
+<dd>
+<p>Don&#39;t compile the templates at all. Existing compiled templates will remain; missing compiled templates will not be created. (Used primarily by developers to speed up checksetup.) Use this switch at your own risk.</p>
+
+<dt><a name="--verbose"
+><b>--verbose</b></a></dt>
+
+<dd>
+<p>Output results of SCRIPT being processed.</p>
+
+<dt><a name="--version"
+><b>--version</b></a></dt>
+
+<dd>
+<p>Display the version of Bugzilla, Perl, and some info about the system that Bugzilla is being installed on, and then exit.</p>
+</dd>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>Hey, what&#39;s this?</p>
+
+<p><em class="code">checksetup.pl</em> is a script that is supposed to run during installation time and also after every upgrade.</p>
+
+<p>The goal of this script is to make the installation even easier. It does this by doing things for you as well as testing for problems in advance.</p>
+
+<p>You can run the script whenever you like. You MUST run it after you update Bugzilla, because it will then update your SQL table definitions to resync them with the code.</p>
+
+<p>You can see all the details of what the script does at <a href="#How_Checksetup_Works" class="podlinkpod"
+>&#34;How Checksetup Works&#34;</a>.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="MODIFYING_CHECKSETUP"
+>MODIFYING CHECKSETUP</a></h1>
+
+<p>There should be no need for Bugzilla Administrators to modify this script; all user-configurable stuff has been moved into a local configuration file called <em class="code">localconfig</em>. When that file in changed and <em class="code">checksetup.pl</em> is run, then the user&#39;s changes will be reflected back into the database.</p>
+
+<p>However, developers often need to modify the installation process. This section explains how <em class="code">checksetup.pl</em> works, so that you know the right part to modify.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="How_Checksetup_Works"
+>How Checksetup Works</a></h2>
+
+<p><em class="code">checksetup.pl</em> runs through several stages during installation:</p>
+
+<ol>
+<li>Checks if the required and optional perl modules are installed, using <a href="./Bugzilla/Install/Requirements.html#check_requirements" class="podlinkpod"
+>&#34;check_requirements&#34; in Bugzilla::Install::Requirements</a>.</li>
+
+<li>Creates or updates the <em class="code">localconfig</em> file, using <a href="./Bugzilla/Install/Localconfig.html#update_localconfig" class="podlinkpod"
+>&#34;update_localconfig&#34; in Bugzilla::Install::Localconfig</a>.</li>
+
+<li>Checks the DBD and database version, using <a href="./Bugzilla/DB.html#bz_check_requirements" class="podlinkpod"
+>&#34;bz_check_requirements&#34; in Bugzilla::DB</a>.</li>
+
+<li>Creates the Bugzilla database if it doesn&#39;t exist, using <a href="./Bugzilla/DB.html#bz_create_database" class="podlinkpod"
+>&#34;bz_create_database&#34; in Bugzilla::DB</a>.</li>
+
+<li>Creates all of the tables in the Bugzilla database, using <a href="./Bugzilla/DB.html#bz_setup_database" class="podlinkpod"
+>&#34;bz_setup_database&#34; in Bugzilla::DB</a>.
+<p>Note that all the table definitions are stored in <a href="./Bugzilla/DB/Schema.html#ABSTRACT_SCHEMA" class="podlinkpod"
+>&#34;ABSTRACT_SCHEMA&#34; in Bugzilla::DB::Schema</a>.</p>
+</li>
+
+<li>Puts the values into the enum tables (like <code class="code">resolution</code>, <code class="code">bug_status</code>, etc.) using <a href="./Bugzilla/DB.html#bz_populate_enum_tables" class="podlinkpod"
+>&#34;bz_populate_enum_tables&#34; in Bugzilla::DB</a>.</li>
+
+<li>Creates any files that Bugzilla needs but doesn&#39;t ship with, using <a href="./Bugzilla/Install/Filesystem.html#update_filesystem" class="podlinkpod"
+>&#34;update_filesystem&#34; in Bugzilla::Install::Filesystem</a>.</li>
+
+<li>Creates the <em class="code">.htaccess</em> files if you haven&#39;t specified not to in <em class="code">localconfig</em>. It does this with <a href="./Bugzilla/Install/Filesystem.html#create_htaccess" class="podlinkpod"
+>&#34;create_htaccess&#34; in Bugzilla::Install::Filesystem</a>.</li>
+
+<li>Updates the system parameters (stored in <em class="code">data/params</em>), using <a href="./Bugzilla/Config.html#update_params" class="podlinkpod"
+>&#34;update_params&#34; in Bugzilla::Config</a>.</li>
+
+<li>Pre-compiles all templates, to improve the speed of Bugzilla. It uses <a href="./Bugzilla/Template.html#precompile_templates" class="podlinkpod"
+>&#34;precompile_templates&#34; in Bugzilla::Template</a> to do this.</li>
+
+<li>Fixes all file permissions to be secure. It does this differently depending on whether or not you&#39;ve specified <code class="code">$webservergroup</code> in <em class="code">localconfig</em>.
+<p>The function that does this is <a href="./Bugzilla/Install/Filesystem.html#fix_all_file_permissions" class="podlinkpod"
+>&#34;fix_all_file_permissions&#34; in Bugzilla::Install::Filesystem</a>.</p>
+</li>
+
+<li>Populates the <code class="code">fielddefs</code> table, using <a href="./Bugzilla/Field.html#populate_field_definitions" class="podlinkpod"
+>&#34;populate_field_definitions&#34; in Bugzilla::Field</a>.</li>
+
+<li>This is the major part of checksetup--updating the table definitions from one version of Bugzilla to another.
+<p>The code for this is in <a href="./Bugzilla/Install/DB.html#update_table_definitions" class="podlinkpod"
+>&#34;update_table_definitions&#34; in Bugzilla::Install::DB</a>.</p>
+
+<p>This includes creating the default Classification (using <a href="./Bugzilla/Install.html#create_default_classification" class="podlinkpod"
+>&#34;create_default_classification&#34; in Bugzilla::Install</a>) and setting up all the foreign keys for all tables, using <a href="./Bugzilla/DB.html#bz_setup_foreign_keys" class="podlinkpod"
+>&#34;bz_setup_foreign_keys&#34; in Bugzilla::DB</a>.</p>
+</li>
+
+<li>Creates the system groups--the ones like <code class="code">editbugs</code>, <code class="code">admin</code>, and so on. This is <a href="./Bugzilla/Install.html#update_system_groups" class="podlinkpod"
+>&#34;update_system_groups&#34; in Bugzilla::Install</a>.</li>
+
+<li>Creates all of the user-adjustable preferences that appear on the &#34;General Preferences&#34; screen. This is <a href="./Bugzilla/Install.html#update_settings" class="podlinkpod"
+>&#34;update_settings&#34; in Bugzilla::Install</a>.</li>
+
+<li>Creates an administrator, if one doesn&#39;t already exist, using <a href="./Bugzilla/Install.html#create_admin" class="podlinkpod"
+>&#34;create_admin&#34; in Bugzilla::Install</a>.
+<p>We also can make somebody an admin at this step, if the user specified the <code class="code">--make-admin</code> switch.</p>
+</li>
+
+<li>Creates the default Product and Component, using <a href="./Bugzilla/Install.html#create_default_product" class="podlinkpod"
+>&#34;create_default_product&#34; in Bugzilla::Install</a>.</li>
+</ol>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Modifying_the_Database"
+>Modifying the Database</a></h2>
+
+<p>Sometimes you&#39;ll want to modify the database. In fact, that&#39;s mostly what checksetup does, is upgrade old Bugzilla databases to the modern format.</p>
+
+<p>If you&#39;d like to know how to make changes to the datbase, see the information in the Bugzilla Developer&#39;s Guide, at: <a href="http://www.bugzilla.org/docs/developer.html#sql-schema" class="podlinkurl"
+>http://www.bugzilla.org/docs/developer.html#sql-schema</a></p>
+
+<p>Also see <a href="./Bugzilla/DB.html#Schema_Modification_Methods" class="podlinkpod"
+>&#34;Schema Modification Methods&#34; in Bugzilla::DB</a> and <a href="./Bugzilla/DB.html#Schema_Information_Methods" class="podlinkpod"
+>&#34;Schema Information Methods&#34; in Bugzilla::DB</a>.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="RUNNING_CHECKSETUP_NON-INTERACTIVELY"
+>RUNNING CHECKSETUP NON-INTERACTIVELY</a></h1>
+
+<p>To operate checksetup non-interactively, run it with a single argument specifying a filename that contains the information usually obtained by prompting the user or by editing localconfig.</p>
+
+<p>The format of that file is as follows:</p>
+
+<pre class="code"> $answer{&#39;db_host&#39;} = &#39;localhost&#39;;
+ $answer{&#39;db_driver&#39;} = &#39;mydbdriver&#39;;
+ $answer{&#39;db_port&#39;} = 0;
+ $answer{&#39;db_name&#39;} = &#39;mydbname&#39;;
+ $answer{&#39;db_user&#39;} = &#39;mydbuser&#39;;
+ $answer{&#39;db_pass&#39;} = &#39;mydbpass&#39;;
+
+ $answer{&#39;urlbase&#39;} = &#39;http://bugzilla.mydomain.com/&#39;;
+
+ (Any localconfig variable or parameter can be specified as above.)
+
+ $answer{&#39;ADMIN_EMAIL&#39;} = &#39;myadmin@mydomain.net&#39;;
+ $answer{&#39;ADMIN_PASSWORD&#39;} = &#39;fooey&#39;;
+ $answer{&#39;ADMIN_REALNAME&#39;} = &#39;Joel Peshkin&#39;;
+
+ $answer{&#39;SMTP_SERVER&#39;} = &#39;mail.mydomain.net&#39;;
+
+ $answer{&#39;NO_PAUSE&#39;} = 1</pre>
+
+<p><code class="code">NO_PAUSE</code> means &#34;never stop and prompt the user to hit Enter to continue, just go ahead and do things, even if they are potentially dangerous.&#34; Don&#39;t set this to 1 unless you know what you are doing.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SEE_ALSO"
+>SEE ALSO</a></h1>
+
+<ul>
+<li><a href="./Bugzilla/Install/Requirements.html" class="podlinkpod"
+>Bugzilla::Install::Requirements</a></li>
+
+<li><a href="./Bugzilla/Install/Localconfig.html" class="podlinkpod"
+>Bugzilla::Install::Localconfig</a></li>
+
+<li><a href="./Bugzilla/Install/Filesystem.html" class="podlinkpod"
+>Bugzilla::Install::Filesystem</a></li>
+
+<li><a href="./Bugzilla/Install/DB.html" class="podlinkpod"
+>Bugzilla::Install::DB</a></li>
+
+<li><a href="./Bugzilla/Install.html" class="podlinkpod"
+>Bugzilla::Install</a></li>
+
+<li><a href="./Bugzilla/Config.html#update_params" class="podlinkpod"
+>&#34;update_params&#34; in Bugzilla::Config</a></li>
+
+<li><a href="./Bugzilla/DB.html#CONNECTION" class="podlinkpod"
+>&#34;CONNECTION&#34; in Bugzilla::DB</a></li>
+</ul>
+<p class="backlinkbottom"><b><a name="___bottom" href="index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/contrib/bz_webservice_demo.html b/docs/en/html/api/contrib/bz_webservice_demo.html
new file mode 100644
index 000000000..d8ab702bd
--- /dev/null
+++ b/docs/en/html/api/contrib/bz_webservice_demo.html
@@ -0,0 +1,301 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+bz_webservice_demo.pl</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href=".././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>bz_webservice_demo.pl</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#OPTIONS'>OPTIONS</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#Initialization'>Initialization</a>
+ <li class='indexItem indexItem2'><a href='#Checking_Bugzilla%27s_version'>Checking Bugzilla&#39;s version</a>
+ <li class='indexItem indexItem2'><a href='#Checking_Bugzilla%27s_timezone'>Checking Bugzilla&#39;s timezone</a>
+ <li class='indexItem indexItem2'><a href='#Getting_Extension_Information'>Getting Extension Information</a>
+ <li class='indexItem indexItem2'><a href='#Logging_In_and_Out'>Logging In and Out</a>
+ <ul class='indexList indexList3'>
+ <li class='indexItem indexItem3'><a href='#Using_Bugzilla%27s_Environment_Authentication'>Using Bugzilla&#39;s Environment Authentication</a>
+ <li class='indexItem indexItem3'><a href='#Using_Bugzilla%27s_CGI_Variable_Authentication'>Using Bugzilla&#39;s CGI Variable Authentication</a>
+ </ul>
+ <li class='indexItem indexItem2'><a href='#Retrieving_Bug_Information'>Retrieving Bug Information</a>
+ <li class='indexItem indexItem2'><a href='#Retrieving_Product_Information'>Retrieving Product Information</a>
+ <li class='indexItem indexItem2'><a href='#Creating_A_Bug'>Creating A Bug</a>
+ <li class='indexItem indexItem2'><a href='#Getting_Legal_Field_Values'>Getting Legal Field Values</a>
+ <li class='indexItem indexItem2'><a href='#Adding_a_comment_to_a_bug'>Adding a comment to a bug</a>
+ </ul>
+ <li class='indexItem indexItem1'><a href='#NOTES'>NOTES</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#Character_Set_Encoding'>Character Set Encoding</a>
+ <li class='indexItem indexItem2'><a href='#Format_For_Create_File'>Format For Create File</a>
+ </ul>
+ <li class='indexItem indexItem1'><a href='#SEE_ALSO'>SEE ALSO</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>bz_webservice_demo.pl - Show how to talk to Bugzilla via XMLRPC</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<p><code class="code">bz_webservice_demo.pl [options]</code></p>
+
+<p><code class="code">bz_webservice_demo.pl --help</code> for detailed help</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="OPTIONS"
+>OPTIONS</a></h1>
+
+<dl>
+<dt><a name="--help,_-h,_-?"
+>--help,
+-h,
+-?</a></dt>
+
+<dd>
+<p>Print a short help message and exit.</p>
+
+<dt><a name="--uri"
+>--uri</a></dt>
+
+<dd>
+<p>URI to Bugzilla&#39;s <code class="code">xmlrpc.cgi</code> script,
+along the lines of <code class="code">http://your.bugzilla.installation/path/to/bugzilla/xmlrpc.cgi</code>.</p>
+
+<dt><a name="--login"
+>--login</a></dt>
+
+<dd>
+<p>Bugzilla login name.
+Specify this together with <b>--password</b> in order to log in.</p>
+
+<p>Specify this without a value in order to log out.</p>
+
+<dt><a name="--password"
+>--password</a></dt>
+
+<dd>
+<p>Bugzilla password.
+Specify this together with <b>--login</b> in order to log in.</p>
+
+<dt><a name="--rememberlogin"
+>--rememberlogin</a></dt>
+
+<dd>
+<p>Gives access to Bugzilla&#39;s &#34;Bugzilla_remember&#34; option.
+Specify this option while logging in to do the same thing as ticking the <code class="code">Bugzilla_remember</code> box on Bugilla&#39;s log in form.
+Don&#39;t specify this option to do the same thing as unchecking the box.</p>
+
+<p>See Bugzilla&#39;s rememberlogin parameter for details.</p>
+
+<dt><a name="--bug_id"
+>--bug_id</a></dt>
+
+<dd>
+<p>Pass a bug ID to have <code class="code">bz_webservice_demo.pl</code> do some bug-related test calls.</p>
+
+<dt><a name="--product_name"
+>--product_name</a></dt>
+
+<dd>
+<p>Pass a product name to have <code class="code">bz_webservice_demo.pl</code> do some product-related test calls.</p>
+
+<dt><a name="--create"
+>--create</a></dt>
+
+<dd>
+<p>Specify a file that contains settings for the creating of a new bug.</p>
+
+<dt><a name="--field"
+>--field</a></dt>
+
+<dd>
+<p>Pass a field name to get legal values for this field.
+It must be either a global select field (such as bug_status,
+resolution,
+rep_platform,
+op_sys,
+priority,
+bug_severity) or a custom select field.</p>
+
+<dt><a name="--comment"
+>--comment</a></dt>
+
+<dd>
+<p>A comment to add to a bug identified by <b>--bug_id</b>.
+You must also pass a <b>--login</b> and <b>--password</b> to log in to Bugzilla.</p>
+
+<dt><a name="--private"
+>--private</a></dt>
+
+<dd>
+<p>An optional non-zero value to specify <b>--comment</b> as private.</p>
+
+<dt><a name="--worktime"
+>--worktime</a></dt>
+
+<dd>
+<p>An optional double precision number specifying the work time for <b>--comment</b>.</p>
+
+<dt><a name="--extension_info"
+>--extension_info</a></dt>
+
+<dd>
+<p>If specified on the command line,
+the script returns the information about the extensions that are installed.</p>
+</dd>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Initialization"
+>Initialization</a></h2>
+
+<p>Using the XMLRPC::Lite class,
+you set up a proxy,
+as shown in this script.
+Bugzilla&#39;s XMLRPC URI ends in <code class="code">xmlrpc.cgi</code>,
+so your URI looks along the lines of <code class="code">http://your.bugzilla.installation/path/to/bugzilla/xmlrpc.cgi</code>.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Checking_Bugzilla&#39;s_version"
+>Checking Bugzilla&#39;s version</a></h2>
+
+<p>To make sure the Bugzilla you&#39;re connecting to supports the methods you wish to call,
+you may want to compare the result of <code class="code">Bugzilla.version</code> to the minimum required version your application needs.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Checking_Bugzilla&#39;s_timezone"
+>Checking Bugzilla&#39;s timezone</a></h2>
+
+<p>To make sure that you understand the dates and times that Bugzilla returns to you,
+you may want to call <code class="code">Bugzilla.timezone</code>.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Getting_Extension_Information"
+>Getting Extension Information</a></h2>
+
+<p>Returns all the information any extensions have decided to provide to the webservice.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Logging_In_and_Out"
+>Logging In and Out</a></h2>
+
+<h3><a class='u' href='#___top' title='click to go to top of document'
+name="Using_Bugzilla&#39;s_Environment_Authentication"
+>Using Bugzilla&#39;s Environment Authentication</a></h3>
+
+<p>Use a <code class="code">http://login:password@your.bugzilla.installation/path/to/bugzilla/xmlrpc.cgi</code> style URI.
+You don&#39;t log out if you&#39;re using this kind of authentication.</p>
+
+<h3><a class='u' href='#___top' title='click to go to top of document'
+name="Using_Bugzilla&#39;s_CGI_Variable_Authentication"
+>Using Bugzilla&#39;s CGI Variable Authentication</a></h3>
+
+<p>Use the <code class="code">User.login</code> and <code class="code">User.logout</code> calls to log in and out,
+as shown in this script.</p>
+
+<p>The <code class="code">Bugzilla_remember</code> parameter is optional.
+If omitted,
+Bugzilla&#39;s defaults apply (as specified by its <code class="code">rememberlogin</code> parameter).</p>
+
+<p>Bugzilla hands back cookies you&#39;ll need to pass along during your work calls.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Retrieving_Bug_Information"
+>Retrieving Bug Information</a></h2>
+
+<p>Call <code class="code">Bug.get</code> with the ID of the bug you want to know more of.
+The call will return a <code class="code">Bugzilla::Bug</code> object.</p>
+
+<p>Note: You can also use &#34;Bug.get_bugs&#34; for compatibility with Bugzilla 3.0 API.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Retrieving_Product_Information"
+>Retrieving Product Information</a></h2>
+
+<p>Call <code class="code">Product.get_product</code> with the name of the product you want to know more of.
+The call will return a <code class="code">Bugzilla::Product</code> object.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Creating_A_Bug"
+>Creating A Bug</a></h2>
+
+<p>Call <code class="code">Bug.create</code> with the settings read from the file indicated on the command line.
+The file must contain a valid anonymous hash to use as argument for the call to <code class="code">Bug.create</code>.
+The call will return a hash with a bug id for the newly created bug.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Getting_Legal_Field_Values"
+>Getting Legal Field Values</a></h2>
+
+<p>Call <code class="code">Bug.legal_values</code> with the name of the field (including custom select fields).
+The call will return a reference to an array with the list of legal values for this field.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Adding_a_comment_to_a_bug"
+>Adding a comment to a bug</a></h2>
+
+<p>Call <code class="code">Bug.add_comment</code> with the bug id,
+the comment text,
+and optionally the number of hours you worked on the bug,
+and a boolean indicating if the comment is private or not.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NOTES"
+>NOTES</a></h1>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Character_Set_Encoding"
+>Character Set Encoding</a></h2>
+
+<p>Make sure that your application either uses the same character set encoding as Bugzilla does,
+or that it converts correspondingly when using the web service API.
+By default,
+Bugzilla uses UTF-8 as its character set encoding.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Format_For_Create_File"
+>Format For Create File</a></h2>
+
+<p>The create format file is a piece of Perl code,
+that should look something like this:</p>
+
+<pre class="code"> {
+ product =&#62; &#34;TestProduct&#34;,
+ component =&#62; &#34;TestComponent&#34;,
+ summary =&#62; &#34;TestBug - created from bz_webservice_demo.pl&#34;,
+ version =&#62; &#34;unspecified&#34;,
+ description =&#62; &#34;This is a description of the bug... hohoho&#34;,
+ op_sys =&#62; &#34;All&#34;,
+ platform =&#62; &#34;All&#34;,
+ priority =&#62; &#34;P4&#34;,
+ severity =&#62; &#34;normal&#34;
+ };</pre>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SEE_ALSO"
+>SEE ALSO</a></h1>
+
+<p>There are code comments in <code class="code">bz_webservice_demo.pl</code> which might be of further help to you.</p>
+<p class="backlinkbottom"><b><a name="___bottom" href="../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/contrib/bzdbcopy.html b/docs/en/html/api/contrib/bzdbcopy.html
new file mode 100644
index 000000000..29e343cdc
--- /dev/null
+++ b/docs/en/html/api/contrib/bzdbcopy.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+bzdbcopy.pl</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href=".././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>bzdbcopy.pl</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>bzdbcopy.pl - Copies data from one Bugzilla database to another.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>The intended use of this script is to copy data from an installation running on one DB platform to an installation running on another DB platform.</p>
+
+<p>It must be run from the directory containing your Bugzilla installation.
+That means if this script is in the contrib/ directory,
+you should be running it as: <code class="code">./contrib/bzdbcopy.pl</code></p>
+
+<p>Note: Both schemas must already exist and be <b>IDENTICAL</b>.
+(That is,
+they must have both been created/updated by the same version of checksetup.pl.) This script will <b>DESTROY ALL CURRENT DATA</b> in the target database.</p>
+
+<p>Both Schemas must be at least from Bugzilla 2.19.3,
+but if you&#39;re running a Bugzilla from before 2.20rc2,
+you&#39;ll need the patch at: <a href="http://bugzilla.mozilla.org/show_bug.cgi?id=300311" class="podlinkurl"
+>http://bugzilla.mozilla.org/show_bug.cgi?id=300311</a> in order to be able to run this script.</p>
+
+<p>Before you using it,
+you have to correctly set all the variables in the &#34;User-Configurable Settings&#34; section at the top of the script.
+The <code class="code">SOURCE</code> settings are for the database you&#39;re copying from,
+and the <code class="code">TARGET</code> settings are for the database you&#39;re copying to.
+The <code class="code">DB_TYPE</code> is the name of a DB driver from the <em class="code">Bugzilla/DB/</em> directory.</p>
+<p class="backlinkbottom"><b><a name="___bottom" href="../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/contrib/console.html b/docs/en/html/api/contrib/console.html
new file mode 100644
index 000000000..bd72578eb
--- /dev/null
+++ b/docs/en/html/api/contrib/console.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+console.pl</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href=".././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>console.pl</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p><b>console.pl</b> - command-line interface to Bugzilla API</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<p>$ <b>contrib/console.pl</b></p>
+
+<p>Bugzilla&#62; <b>b(5)-&#62;short_desc</b></p>
+
+<blockquote>
+<p>&#34;Misplaced Widget&#34;</p>
+</blockquote>
+
+<p>Bugzilla&#62; <b>$f = f &#34;cf_subsystem&#34;; scalar @{$f-&#62;legal_values}</b></p>
+
+<blockquote>
+<p>177</p>
+</blockquote>
+
+<p>Bugzilla&#62; <b>p filter html_light,
+&#34;1 &#60; 2 &#60;b&#62;3&#60;/b&#62;&#34;</b></p>
+
+<blockquote>
+<p>1 &#38;lt; 2 &#60;b&#62;3&#60;/b&#62;</p>
+</blockquote>
+
+<p>Bugzilla&#62; <b>$u = u 5; $u-&#62;groups; d $u</b></p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>Loads Bugzilla packages and prints expressions with Data::Dumper.
+Useful for checking results of Bugzilla API calls without opening a debug file from a cgi.</p>
+<p class="backlinkbottom"><b><a name="___bottom" href="../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/contrib/extension-convert.html b/docs/en/html/api/contrib/extension-convert.html
new file mode 100644
index 000000000..d87b9eb56
--- /dev/null
+++ b/docs/en/html/api/contrib/extension-convert.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+extension-convert.pl</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href=".././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>extension-convert.pl</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>extension-convert.pl - Convert extensions from the pre-3.6 format to the 3.6 format.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<pre class="code"> contrib/extension-convert.pl name
+
+ Converts an extension in the F&#60;extensions/&#62; directory into the new
+ extension layout for Bugzilla 3.6.</pre>
+<p class="backlinkbottom"><b><a name="___bottom" href="../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/contrib/merge-users.html b/docs/en/html/api/contrib/merge-users.html
new file mode 100644
index 000000000..bd87a2180
--- /dev/null
+++ b/docs/en/html/api/contrib/merge-users.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+merge-users.pl</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href=".././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>merge-users.pl</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>merge-users.pl - Merge two user accounts.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<pre class="code"> This script moves activity from one user account to another.
+ Specify the two accounts on the command line, e.g.:
+
+ ./merge-users.pl old_account@foo.com new_account@bar.com
+ or:
+ ./merge-users.pl id:old_userid id:new_userid
+ or:
+ ./merge-users.pl id:old_userid new_account@bar.com
+
+ Notes: - the new account must already exist.
+ - the id:old_userid syntax permits you to migrate
+ activity from a deleted account to an existing one.</pre>
+<p class="backlinkbottom"><b><a name="___bottom" href="../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/contrib/recode.html b/docs/en/html/api/contrib/recode.html
new file mode 100644
index 000000000..d9f3cb7e2
--- /dev/null
+++ b/docs/en/html/api/contrib/recode.html
@@ -0,0 +1,102 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+recode.pl</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href=".././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>recode.pl</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#OPTIONS'>OPTIONS</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>recode.pl - Converts a database from one encoding (or multiple encodings) to UTF-8.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<pre class="code"> contrib/recode.pl [--guess [--show-failures]] [--charset=iso-8859-2]
+ [--overrides=file_name]
+
+ --dry-run Don&#39;t modify the database.
+
+ --charset Primary charset your data is currently in. This can be
+ optionally omitted if you do --guess.
+
+ --guess Try to guess the charset of the data.
+
+ --show-failures If we fail to guess, show where we failed.
+
+ --overrides Specify a file containing overrides. See --help
+ for more info.
+
+ --help Display detailed help.
+
+ If you aren&#39;t sure what to do, try:
+
+ contrib/recode.pl --guess --charset=cp1252</pre>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="OPTIONS"
+>OPTIONS</a></h1>
+
+<dl>
+<dt><a name="--dry-run"
+>--dry-run</a></dt>
+
+<dd>
+<p>Don&#39;t modify the database, just print out what the conversions will be.</p>
+
+<p>recode.pl will print out a Key for each item. You can use this in the overrides file, described below.</p>
+
+<dt><a name="--guess"
+>--guess</a></dt>
+
+<dd>
+<p>If your database is in multiple different encodings, specify this switch and recode.pl will do its best to determine the original charset of the data. The detection is usually very reliable.</p>
+
+<p>If recode.pl cannot guess the charset, it will leave the data alone, unless you&#39;ve specified --charset.</p>
+
+<dt><a name="--charset=charset-name"
+>--charset=charset-name</a></dt>
+
+<dd>
+<p>If you do not specify --guess, then your database is converted from this character set into the UTF-8.</p>
+
+<p>If you have specified --guess, recode.pl will use this charset as a fallback--when it cannot guess the charset of a particular piece of data, it will guess that the data is in this charset and convert it from this charset to UTF-8.</p>
+
+<p>charset-name must be a charset that is known to perl&#39;s Encode module. To see a list of available charsets, do:</p>
+
+<p><code class="code">perl -MEncode -e &#39;print join(&#34;\n&#34;, Encode-&#62;encodings(&#34;:all&#34;))&#39;</code></p>
+
+<dt><a name="--show-failures"
+>--show-failures</a></dt>
+
+<dd>
+<p>If --guess fails to guess a charset, print out the data it failed on.</p>
+
+<dt><a name="--overrides=file_name"
+>--overrides=file_name</a></dt>
+
+<dd>
+<p>This is a way of specifying certain encodings to override the encodings of --guess. The file is a series of lines. The line should start with the Key from --dry-run, and then a space, and then the encoding you&#39;d like to use.</p>
+</dd>
+</dl>
+<p class="backlinkbottom"><b><a name="___bottom" href="../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/email_in.html b/docs/en/html/api/email_in.html
new file mode 100644
index 000000000..da49e9772
--- /dev/null
+++ b/docs/en/html/api/email_in.html
@@ -0,0 +1,158 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+email_in.pl</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href="./../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>email_in.pl</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <ul class='indexList indexList2'>
+ <li class='indexItem indexItem2'><a href='#Creating_a_New_Bug'>Creating a New Bug</a>
+ <li class='indexItem indexItem2'><a href='#Modifying_an_Existing_Bug'>Modifying an Existing Bug</a>
+ <ul class='indexList indexList3'>
+ <li class='indexItem indexItem3'><a href='#Adding%2FRemoving_CCs'>Adding/Removing CCs</a>
+ </ul>
+ <li class='indexItem indexItem2'><a href='#Errors'>Errors</a>
+ </ul>
+ <li class='indexItem indexItem1'><a href='#CAUTION'>CAUTION</a>
+ <li class='indexItem indexItem1'><a href='#LIMITATIONS'>LIMITATIONS</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>email_in.pl - The Bugzilla Inbound Email Interface</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<p>./email_in.pl [-vvv] [--default name=value] [--override name=value] &#60; email.txt</p>
+
+<p>Reads an email on STDIN (the standard input).</p>
+
+<p>Options:</p>
+
+<pre class="code"> --verbose (-v) - Make the script print more to STDERR.
+ Specify multiple times to print even more.
+
+ --default name=value - Specify defaults for field values, like
+ product=TestProduct. Can be specified multiple
+ times to specify defaults for multiple fields.
+
+ --override name=value - Override field values specified in the email,
+ like product=TestProduct. Can be specified
+ multiple times to override multiple fields.</pre>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>This script processes inbound email and creates a bug, or appends data to an existing bug.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Creating_a_New_Bug"
+>Creating a New Bug</a></h2>
+
+<p>The script expects to read an email with the following format:</p>
+
+<pre class="code"> From: account@domain.com
+ Subject: Bug Summary
+
+ @product ProductName
+ @component ComponentName
+ @version 1.0
+
+ This is a bug description. It will be entered into the bug exactly as
+ written here.
+
+ It can be multiple paragraphs.
+
+ --
+ This is a signature line, and will be removed automatically, It will not
+ be included in the bug description.</pre>
+
+<p>For the list of valid field names for the <code class="code">@</code> fields, including a list of which ones are required, see <a href="./Bugzilla/WebService/Bug.html#create" class="podlinkpod"
+>&#34;create&#34; in Bugzilla::WebService::Bug</a>. (Note, however, that you cannot specify <code class="code">@description</code> as a field-- you just add a comment by adding text after the <code class="code">@</code> fields.)</p>
+
+<p>The values for the fields can be split across multiple lines, but note that a newline will be parsed as a single space, for the value. So, for example:</p>
+
+<pre class="code"> @summary This is a very long
+ description</pre>
+
+<p>Will be parsed as &#34;This is a very long description&#34;.</p>
+
+<p>If you specify <code class="code">@summary</code>, it will override the summary you specify in the Subject header.</p>
+
+<p><code class="code">account@domain.com</code> (the value of the <code class="code">From</code> header) must be a valid Bugzilla account.</p>
+
+<p>Note that signatures must start with &#39;-- &#39;, the standard signature border.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Modifying_an_Existing_Bug"
+>Modifying an Existing Bug</a></h2>
+
+<p>Bugzilla determines what bug you want to modify in one of two ways:</p>
+
+<ul>
+<li>Your subject starts with [Bug 123456] -- then it modifies bug 123456.</li>
+
+<li>You include <code class="code">@id 123456</code> in the first lines of the email.</li>
+</ul>
+
+<p>If you do both, <code class="code">@id</code> takes precedence.</p>
+
+<p>You send your email in the same format as for creating a bug, except that you only specify the fields you want to change. If the very first non-blank line of the email doesn&#39;t begin with <code class="code">@</code>, then it will be assumed that you are only adding a comment to the bug.</p>
+
+<p>Note that when updating a bug, the <code class="code">Subject</code> header is ignored, except for getting the bug ID. If you want to change the bug&#39;s summary, you have to specify <code class="code">@summary</code> as one of the fields to change.</p>
+
+<p>Please remember not to include any extra text in your emails, as that text will also be added as a comment. This includes any text that your email client automatically quoted and included, if this is a reply to another email.</p>
+
+<h3><a class='u' href='#___top' title='click to go to top of document'
+name="Adding/Removing_CCs"
+>Adding/Removing CCs</a></h3>
+
+<p>To add CCs, you can specify them in a comma-separated list in <code class="code">@cc</code>.</p>
+
+<p>To remove CCs, specify them as a comma-separated list in <code class="code">@removecc</code>.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="Errors"
+>Errors</a></h2>
+
+<p>If your request cannot be completed for any reason, Bugzilla will send an email back to you. If your request succeeds, Bugzilla will not send you anything.</p>
+
+<p>If any part of your request fails, all of it will fail. No partial changes will happen.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="CAUTION"
+>CAUTION</a></h1>
+
+<p>The script does not do any validation that the user is who they say they are. That is, it accepts <i>any</i> &#39;From&#39; address, as long as it&#39;s a valid Bugzilla account. So make sure that your MTA validates that the message is actually coming from who it says it&#39;s coming from, and only allow access to the inbound email system from people you trust.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="LIMITATIONS"
+>LIMITATIONS</a></h1>
+
+<p>The email interface only accepts emails that are correctly formatted per RFC2822. If you send it an incorrectly formatted message, it may behave in an unpredictable fashion.</p>
+
+<p>You cannot send an HTML mail along with attachments. If you do, Bugzilla will reject your email, saying that it doesn&#39;t contain any text. This is a bug in <a href="./Email/MIME/Attachment/Stripper.html" class="podlinkpod"
+>Email::MIME::Attachment::Stripper</a> that we can&#39;t work around.</p>
+
+<p>You cannot modify Flags through the email interface.</p>
+<p class="backlinkbottom"><b><a name="___bottom" href="index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/extensions/create.html b/docs/en/html/api/extensions/create.html
new file mode 100644
index 000000000..6ff15e91b
--- /dev/null
+++ b/docs/en/html/api/extensions/create.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+extensions/create.pl</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href=".././../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="../index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>extensions/create.pl</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>extensions/create.pl - Create a framework for a new Bugzilla Extension.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<pre class="code"> extensions/create.pl NAME
+
+ Creates a framework for an extension called NAME in the F&#60;extensions/&#62;
+ directory.</pre>
+<p class="backlinkbottom"><b><a name="___bottom" href="../index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/importxml.html b/docs/en/html/api/importxml.html
new file mode 100644
index 000000000..6df5ee99f
--- /dev/null
+++ b/docs/en/html/api/importxml.html
@@ -0,0 +1,103 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+importxml</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href="./../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>importxml</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#OPTIONS'>OPTIONS</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>importxml - Import bugzilla bug data from xml.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<pre class="code"> importxml.pl [options] [file ...]</pre>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="OPTIONS"
+>OPTIONS</a></h1>
+
+<dl>
+<dt><a name="-?"
+><b>-?</b></a></dt>
+
+<dd>
+<p>Print a brief help message and exit.</p>
+
+<dt><a name="-v"
+><b>-v</b></a></dt>
+
+<dd>
+<p>Print error and debug information. Mulltiple -v increases verbosity</p>
+
+<dt><a name="-m_--sendmail"
+><b>-m</b> <b>--sendmail</b></a></dt>
+
+<dd>
+<p>Send mail to exporter with a log of bugs imported and any errors.</p>
+
+<dt><a name="--attach_path"
+><b>--attach_path</b></a></dt>
+
+<dd>
+<p>The path to the attachment files. (Required if encoding=&#34;filename&#34; is used for attachments.)</p>
+
+<dt><a name="--product=name"
+><b>--product=name</b></a></dt>
+
+<dd>
+<p>The product to put the bug in if the product specified in the XML doesn&#39;t exist.</p>
+
+<dt><a name="--component=name"
+><b>--component=name</b></a></dt>
+
+<dd>
+<p>The component to put the bug in if the component specified in the XML doesn&#39;t exist.</p>
+</dd>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<pre class="code"> This script is used to import bugs from another installation of bugzilla.
+ It can be used in two ways.
+ First using the move function of bugzilla
+ on another system will send mail to an alias provided by
+ the administrator of the target installation (you). Set up an alias
+ similar to the one given below so this mail will be automatically
+ run by this script and imported into your database. Run &#39;newaliases&#39;
+ after adding this alias to your aliases file. Make sure your sendmail
+ installation is configured to allow mail aliases to execute code.
+
+ bugzilla-import: &#34;|/usr/bin/perl /opt/bugzilla/importxml.pl --mail&#34;
+
+ Second it can be run from the command line with any xml file from
+ STDIN that conforms to the bugzilla DTD. In this case you can pass
+ an argument to set whether you want to send the
+ mail that will be sent to the exporter and maintainer normally.
+
+ importxml.pl [options] bugsfile.xml</pre>
+<p class="backlinkbottom"><b><a name="___bottom" href="index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/index.html b/docs/en/html/api/index.html
new file mode 100644
index 000000000..66e74026f
--- /dev/null
+++ b/docs/en/html/api/index.html
@@ -0,0 +1,349 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <title>Bugzilla 4.0 API Documentation</title>
+
+<link rel="stylesheet" title="style" type="text/css" href="./../../../style.css" media="all" >
+
+</head>
+ <body class="contentspage">
+ <h1>Bugzilla 4.0 API Documentation</h1>
+<dl class='superindex'>
+<dt><a name="Extensions">Extensions</a></dt>
+<dd>
+<table class="pod_desc_table">
+<tr class="even">
+ <th><a href="./extensions/create.html">extensions::create</a></th>
+ <td>Create a framework for a new Bugzilla Extension.</td>
+</tr>
+</table></dd>
+
+<dt><a name="Files">Files</a></dt>
+<dd>
+<table class="pod_desc_table">
+<tr class="even">
+ <th><a href="./checksetup.html">checksetup</a></th>
+ <td>A do-it-all upgrade and installation script for Bugzilla.</td>
+</tr>
+<tr class="odd">
+ <th><a href="./contrib/bz_webservice_demo.html">contrib::bz_webservice_demo</a></th>
+ <td>Show how to talk to Bugzilla via XMLRPC</td>
+</tr>
+<tr class="even">
+ <th><a href="./contrib/bzdbcopy.html">contrib::bzdbcopy</a></th>
+ <td>Copies data from one Bugzilla database to another.</td>
+</tr>
+<tr class="odd">
+ <th><a href="./contrib/console.html">contrib::console</a></th>
+ <td>command-line interface to Bugzilla API</td>
+</tr>
+<tr class="even">
+ <th><a href="./contrib/extension-convert.html">contrib::extension-convert</a></th>
+ <td>Convert extensions from the pre-3.6 format to the 3.6 format.</td>
+</tr>
+<tr class="odd">
+ <th><a href="./contrib/merge-users.html">contrib::merge-users</a></th>
+ <td>Merge two user accounts.</td>
+</tr>
+<tr class="even">
+ <th><a href="./contrib/recode.html">contrib::recode</a></th>
+ <td>Converts a database from one encoding (or multiple encodings) to UTF-8.</td>
+</tr>
+<tr class="odd">
+ <th><a href="./email_in.html">email_in</a></th>
+ <td>The Bugzilla Inbound Email Interface</td>
+</tr>
+<tr class="even">
+ <th><a href="./importxml.html">importxml</a></th>
+ <td>Import bugzilla bug data from xml.</td>
+</tr>
+<tr class="odd">
+ <th><a href="./install-module.html">install-module</a></th>
+ <td>Installs or upgrades modules from CPAN. This script does not run on Windows.</td>
+</tr>
+<tr class="even">
+ <th><a href="./jobqueue.html">jobqueue</a></th>
+ <td>Runs jobs in the background for Bugzilla.</td>
+</tr>
+<tr class="odd">
+ <th><a href="./migrate.html">migrate</a></th>
+ <td>A script to migrate from other bug-trackers to Bugzilla.</td>
+</tr>
+<tr class="even">
+ <th><a href="./sanitycheck.html">sanitycheck</a></th>
+ <td>Perl script to perform a sanity check at the command line</td>
+</tr>
+</table></dd>
+
+<dt><a name="Modules">Modules</a></dt>
+<dd>
+<table class="pod_desc_table">
+<tr class="even">
+ <th><a href="./Bugzilla.html">Bugzilla</a></th>
+ <td>Semi-persistent collection of various objects used by scripts and modules</td>
+</tr>
+<tr class="odd">
+ <th><a href="./Bugzilla/Attachment.html">Bugzilla::Attachment</a></th>
+ <td>Bugzilla attachment class.</td>
+</tr>
+<tr class="even">
+ <th><a href="./Bugzilla/Auth.html">Bugzilla::Auth</a></th>
+ <td>An object that authenticates the login credentials for a user.</td>
+</tr>
+<tr class="odd">
+ <th><a href="./Bugzilla/Auth/Login.html">Bugzilla::Auth::Login</a></th>
+ <td>Gets username/password data from the user.</td>
+</tr>
+<tr class="even">
+ <th><a href="./Bugzilla/Auth/Verify.html">Bugzilla::Auth::Verify</a></th>
+ <td>An object that verifies usernames and passwords.</td>
+</tr>
+<tr class="odd">
+ <th><a href="./Bugzilla/CGI.html">Bugzilla::CGI</a></th>
+ <td>CGI handling for Bugzilla</td>
+</tr>
+<tr class="even">
+ <th><a href="./Bugzilla/Classification.html">Bugzilla::Classification</a></th>
+ <td>Bugzilla classification class.</td>
+</tr>
+<tr class="odd">
+ <th><a href="./Bugzilla/Comment.html">Bugzilla::Comment</a></th>
+ <td>A Comment for a given bug</td>
+</tr>
+<tr class="even">
+ <th><a href="./Bugzilla/Component.html">Bugzilla::Component</a></th>
+ <td>Bugzilla product component class.</td>
+</tr>
+<tr class="odd">
+ <th><a href="./Bugzilla/Config.html">Bugzilla::Config</a></th>
+ <td>Configuration parameters for Bugzilla</td>
+</tr>
+<tr class="even">
+ <th><a href="./Bugzilla/Config/Common.html">Bugzilla::Config::Common</a></th>
+ <td>Parameter checking functions</td>
+</tr>
+<tr class="odd">
+ <th><a href="./Bugzilla/DB.html">Bugzilla::DB</a></th>
+ <td>Database access routines, using DBI</td>
+</tr>
+<tr class="even">
+ <th><a href="./Bugzilla/DB/Mysql.html">Bugzilla::DB::Mysql</a></th>
+ <td>Bugzilla database compatibility layer for MySQL</td>
+</tr>
+<tr class="odd">
+ <th><a href="./Bugzilla/DB/Oracle.html">Bugzilla::DB::Oracle</a></th>
+ <td>Bugzilla database compatibility layer for Oracle</td>
+</tr>
+<tr class="even">
+ <th><a href="./Bugzilla/DB/Pg.html">Bugzilla::DB::Pg</a></th>
+ <td>Bugzilla database compatibility layer for PostgreSQL</td>
+</tr>
+<tr class="odd">
+ <th><a href="./Bugzilla/DB/Schema.html">Bugzilla::DB::Schema</a></th>
+ <td>Abstract database schema for Bugzilla</td>
+</tr>
+<tr class="even">
+ <th><a href="./Bugzilla/Error.html">Bugzilla::Error</a></th>
+ <td>Error handling utilities for Bugzilla</td>
+</tr>
+<tr class="odd">
+ <th><a href="./Bugzilla/Extension.html">Bugzilla::Extension</a></th>
+ <td>Base class for Bugzilla Extensions.</td>
+</tr>
+<tr class="even">
+ <th><a href="./Bugzilla/Field.html">Bugzilla::Field</a></th>
+ <td>a particular piece of information about bugs and useful routines for form field manipulation</td>
+</tr>
+<tr class="odd">
+ <th><a href="./Bugzilla/Field/Choice.html">Bugzilla::Field::Choice</a></th>
+ <td>A legal value for a &#60;select&#62;-type field.</td>
+</tr>
+<tr class="even">
+ <th><a href="./Bugzilla/Field/ChoiceInterface.html">Bugzilla::Field::ChoiceInterface</a></th>
+ <td>Makes an object act like a Bugzilla::Field::Choice.</td>
+</tr>
+<tr class="odd">
+ <th><a href="./Bugzilla/Flag.html">Bugzilla::Flag</a></th>
+ <td>A module to deal with Bugzilla flag values.</td>
+</tr>
+<tr class="even">
+ <th><a href="./Bugzilla/FlagType.html">Bugzilla::FlagType</a></th>
+ <td>A module to deal with Bugzilla flag types.</td>
+</tr>
+<tr class="odd">
+ <th><a href="./Bugzilla/Group.html">Bugzilla::Group</a></th>
+ <td>Bugzilla group class.</td>
+</tr>
+<tr class="even">
+ <th><a href="./Bugzilla/Hook.html">Bugzilla::Hook</a></th>
+ <td>Extendable extension hooks for Bugzilla code</td>
+</tr>
+<tr class="odd">
+ <th><a href="./Bugzilla/Install.html">Bugzilla::Install</a></th>
+ <td>Functions and variables having to do with installation.</td>
+</tr>
+<tr class="even">
+ <th><a href="./Bugzilla/Install/CPAN.html">Bugzilla::Install::CPAN</a></th>
+ <td>Routines to install Perl modules from CPAN.</td>
+</tr>
+<tr class="odd">
+ <th><a href="./Bugzilla/Install/DB.html">Bugzilla::Install::DB</a></th>
+ <td>Fix up the database during installation.</td>
+</tr>
+<tr class="even">
+ <th><a href="./Bugzilla/Install/Filesystem.html">Bugzilla::Install::Filesystem</a></th>
+ <td>Fix up the filesystem during installation.</td>
+</tr>
+<tr class="odd">
+ <th><a href="./Bugzilla/Install/Localconfig.html">Bugzilla::Install::Localconfig</a></th>
+ <td></td>
+</tr>
+<tr class="even">
+ <th><a href="./Bugzilla/Install/Requirements.html">Bugzilla::Install::Requirements</a></th>
+ <td>Functions and variables dealing with Bugzilla&#39;s perl-module requirements.</td>
+</tr>
+<tr class="odd">
+ <th><a href="./Bugzilla/Install/Util.html">Bugzilla::Install::Util</a></th>
+ <td>Utility functions that are useful both during installation and afterwards.</td>
+</tr>
+<tr class="even">
+ <th><a href="./Bugzilla/JobQueue.html">Bugzilla::JobQueue</a></th>
+ <td>Interface between Bugzilla and TheSchwartz.</td>
+</tr>
+<tr class="odd">
+ <th><a href="./Bugzilla/JobQueue/Runner.html">Bugzilla::JobQueue::Runner</a></th>
+ <td>A class representing the daemon that runs the job queue.</td>
+</tr>
+<tr class="even">
+ <th><a href="./Bugzilla/Keyword.html">Bugzilla::Keyword</a></th>
+ <td>A Keyword that can be added to a bug.</td>
+</tr>
+<tr class="odd">
+ <th><a href="./Bugzilla/Migrate.html">Bugzilla::Migrate</a></th>
+ <td>Functions to migrate from other databases</td>
+</tr>
+<tr class="even">
+ <th><a href="./Bugzilla/Milestone.html">Bugzilla::Milestone</a></th>
+ <td>Bugzilla product milestone class.</td>
+</tr>
+<tr class="odd">
+ <th><a href="./Bugzilla/Object.html">Bugzilla::Object</a></th>
+ <td>A base class for objects in Bugzilla.</td>
+</tr>
+<tr class="even">
+ <th><a href="./Bugzilla/Product.html">Bugzilla::Product</a></th>
+ <td>Bugzilla product class.</td>
+</tr>
+<tr class="odd">
+ <th><a href="./Bugzilla/Search/Recent.html">Bugzilla::Search::Recent</a></th>
+ <td>A search recently run by a logged-in user.</td>
+</tr>
+<tr class="even">
+ <th><a href="./Bugzilla/Search/Saved.html">Bugzilla::Search::Saved</a></th>
+ <td>A saved search</td>
+</tr>
+<tr class="odd">
+ <th><a href="./Bugzilla/Status.html">Bugzilla::Status</a></th>
+ <td>Bug status class.</td>
+</tr>
+<tr class="even">
+ <th><a href="./Bugzilla/Template.html">Bugzilla::Template</a></th>
+ <td>Wrapper around the Template Toolkit Template object</td>
+</tr>
+<tr class="odd">
+ <th><a href="./Bugzilla/Template/Plugin/Bugzilla.html">Bugzilla::Template::Plugin::Bugzilla</a></th>
+ <td></td>
+</tr>
+<tr class="even">
+ <th><a href="./Bugzilla/Template/Plugin/Hook.html">Bugzilla::Template::Plugin::Hook</a></th>
+ <td></td>
+</tr>
+<tr class="odd">
+ <th><a href="./Bugzilla/Template/Plugin/User.html">Bugzilla::Template::Plugin::User</a></th>
+ <td></td>
+</tr>
+<tr class="even">
+ <th><a href="./Bugzilla/Token.html">Bugzilla::Token</a></th>
+ <td>Provides different routines to manage tokens.</td>
+</tr>
+<tr class="odd">
+ <th><a href="./Bugzilla/Update.html">Bugzilla::Update</a></th>
+ <td>Update routines for Bugzilla</td>
+</tr>
+<tr class="even">
+ <th><a href="./Bugzilla/User.html">Bugzilla::User</a></th>
+ <td>Object for a Bugzilla user</td>
+</tr>
+<tr class="odd">
+ <th><a href="./Bugzilla/User/Setting.html">Bugzilla::User::Setting</a></th>
+ <td>Object for a user preference setting</td>
+</tr>
+<tr class="even">
+ <th><a href="./Bugzilla/User/Setting/Lang.html">Bugzilla::User::Setting::Lang</a></th>
+ <td>Object for a user preference setting for preferred language</td>
+</tr>
+<tr class="odd">
+ <th><a href="./Bugzilla/User/Setting/Skin.html">Bugzilla::User::Setting::Skin</a></th>
+ <td>Object for a user preference setting for skins</td>
+</tr>
+<tr class="even">
+ <th><a href="./Bugzilla/User/Setting/Timezone.html">Bugzilla::User::Setting::Timezone</a></th>
+ <td>Object for a user preference setting for desired timezone</td>
+</tr>
+<tr class="odd">
+ <th><a href="./Bugzilla/Util.html">Bugzilla::Util</a></th>
+ <td>Generic utility functions for bugzilla</td>
+</tr>
+<tr class="even">
+ <th><a href="./Bugzilla/Version.html">Bugzilla::Version</a></th>
+ <td>Bugzilla product version class.</td>
+</tr>
+<tr class="odd">
+ <th><a href="./Bugzilla/WebService.html">Bugzilla::WebService</a></th>
+ <td>The Web Service interface to Bugzilla</td>
+</tr>
+<tr class="even">
+ <th><a href="./Bugzilla/WebService/Bug.html">Bugzilla::WebService::Bug</a></th>
+ <td>The API for creating, changing, and getting the details of bugs.</td>
+</tr>
+<tr class="odd">
+ <th><a href="./Bugzilla/WebService/Bugzilla.html">Bugzilla::WebService::Bugzilla</a></th>
+ <td>Global functions for the webservice interface.</td>
+</tr>
+<tr class="even">
+ <th><a href="./Bugzilla/WebService/Product.html">Bugzilla::WebService::Product</a></th>
+ <td>The Product API</td>
+</tr>
+<tr class="odd">
+ <th><a href="./Bugzilla/WebService/Server/JSONRPC.html">Bugzilla::WebService::Server::JSONRPC</a></th>
+ <td>The JSON-RPC Interface to Bugzilla</td>
+</tr>
+<tr class="even">
+ <th><a href="./Bugzilla/WebService/Server/XMLRPC.html">Bugzilla::WebService::Server::XMLRPC</a></th>
+ <td>The XML-RPC Interface to Bugzilla</td>
+</tr>
+<tr class="odd">
+ <th><a href="./Bugzilla/WebService/User.html">Bugzilla::WebService::User</a></th>
+ <td>The User Account and Login API</td>
+</tr>
+<tr class="even">
+ <th><a href="./Bugzilla/WebService/Util.html">Bugzilla::WebService::Util</a></th>
+ <td></td>
+</tr>
+<tr class="odd">
+ <th><a href="./Bugzilla/Whine.html">Bugzilla::Whine</a></th>
+ <td>A Whine event</td>
+</tr>
+<tr class="even">
+ <th><a href="./Bugzilla/Whine/Query.html">Bugzilla::Whine::Query</a></th>
+ <td>A query object used by Bugzilla::Whine.</td>
+</tr>
+<tr class="odd">
+ <th><a href="./Bugzilla/Whine/Schedule.html">Bugzilla::Whine::Schedule</a></th>
+ <td>A schedule object used by Bugzilla::Whine.</td>
+</tr>
+</table></dd>
+
+</dl>
+</body></html> \ No newline at end of file
diff --git a/docs/en/html/api/install-module.html b/docs/en/html/api/install-module.html
new file mode 100644
index 000000000..031ef677e
--- /dev/null
+++ b/docs/en/html/api/install-module.html
@@ -0,0 +1,98 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+install-module.pl</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href="./../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>install-module.pl</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#OPTIONS'>OPTIONS</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>install-module.pl - Installs or upgrades modules from CPAN.
+This script does not run on Windows.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<pre class="code"> ./install-module.pl Module::Name [--global]
+ ./install-module.pl --all [--global]
+ ./install-module.pl --upgrade-all [--global]
+ ./install-module.pl --show-config
+ ./install-module.pl --shell
+
+ Do &#34;./install-module.pl --help&#34; for more information.</pre>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="OPTIONS"
+>OPTIONS</a></h1>
+
+<dl>
+<dt><a name="Module::Name"
+><b>Module::Name</b></a></dt>
+
+<dd>
+<p>The name of a module that you want to install from CPAN. This is the same thing that you&#39;d give to the <code class="code">install</code> command in the CPAN shell.</p>
+
+<p>You can specify multiple module names separated by a space to install multiple modules.</p>
+
+<dt><a name="--global"
+><b>--global</b></a></dt>
+
+<dd>
+<p>This makes install-module install modules globally for all applications, instead of just for Bugzilla.</p>
+
+<p>On most systems, you have to be root for <code class="code">--global</code> to work.</p>
+
+<dt><a name="--all"
+><b>--all</b></a></dt>
+
+<dd>
+<p>This will make install-module do its best to install every required and optional module that is not installed that Bugzilla can use.</p>
+
+<p>Some modules may fail to install. You can run checksetup.pl to see which installed properly.</p>
+
+<dt><a name="--upgrade-all"
+><b>--upgrade-all</b></a></dt>
+
+<dd>
+<p>This is like <code class="code">--all</code>, except it forcibly installs the very latest version of every Bugzilla prerequisite, whether or not you already have them installed.</p>
+
+<dt><a name="--show-config"
+><b>--show-config</b></a></dt>
+
+<dd>
+<p>Prints out the CPAN configuration in raw Perl format. Useful for debugging.</p>
+
+<dt><a name="--shell"
+><b>--shell</b></a></dt>
+
+<dd>
+<p>Starts a CPAN shell using the configuration of <em class="code">install-module.pl</em>.</p>
+
+<dt><a name="--help"
+><b>--help</b></a></dt>
+
+<dd>
+<p>Shows this help.</p>
+</dd>
+</dl>
+<p class="backlinkbottom"><b><a name="___bottom" href="index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/jobqueue.html b/docs/en/html/api/jobqueue.html
new file mode 100644
index 000000000..b8a5bb2c2
--- /dev/null
+++ b/docs/en/html/api/jobqueue.html
@@ -0,0 +1,77 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+jobqueue.pl</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href="./../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>jobqueue.pl</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+ <li class='indexItem indexItem1'><a href='#Running_jobqueue.pl_as_a_System_Service'>Running jobqueue.pl as a System Service</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>jobqueue.pl - Runs jobs in the background for Bugzilla.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<pre class="code"> ./jobqueue.pl [OPTIONS] COMMAND
+
+ OPTIONS:
+ -f Run in the foreground (don&#39;t detach)
+ -d Output a lot of debugging information
+ -p file Specify the file where jobqueue.pl should store its current
+ process id. Defaults to F&#60;data/jobqueue.pl.pid&#62;.
+ -n name What should this process call itself in the system log?
+ Defaults to the full path you used to invoke the script.
+
+ COMMANDS:
+ start Starts a new jobqueue daemon if there isn&#39;t one running already
+ stop Stops a running jobqueue daemon
+ restart Stops a running jobqueue if one is running, and then
+ starts a new one.
+ check Report the current status of the daemon.
+ install On some *nix systems, this automatically installs and
+ configures jobqueue.pl as a system service so that it will
+ start every time the machine boots.
+ uninstall Removes the system service for jobqueue.pl.
+ help Display this usage info
+ version Display the version of jobqueue.pl</pre>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>See <a href="./Bugzilla/JobQueue.html" class="podlinkpod"
+>Bugzilla::JobQueue</a> and <a href="./Bugzilla/JobQueue/Runner.html" class="podlinkpod"
+>Bugzilla::JobQueue::Runner</a>.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="Running_jobqueue.pl_as_a_System_Service"
+>Running jobqueue.pl as a System Service</a></h1>
+
+<p>For systems that use Upstart or SysV Init, there is a SysV/Upstart init script included with Bugzilla for jobqueue.pl: <em class="code">contrib/bugzilla-queue</em>. It should work out-of-the-box on RHEL, Fedora, CentOS etc.</p>
+
+<p>You can install it by doing <code class="code">./jobqueue.pl install</code> as root, after already having run <a href="./checksetup.html" class="podlinkpod"
+>checksetup</a> at least once to completion on this Bugzilla installation.</p>
+
+<p>If you are using a system that isn&#39;t RHEL, Fedora, CentOS, etc., then you may have to modify <em class="code">contrib/bugzilla-queue</em> and install it yourself manually in order to get <code class="code">jobqueue.pl</code> running as a system service.</p>
+<p class="backlinkbottom"><b><a name="___bottom" href="index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/migrate.html b/docs/en/html/api/migrate.html
new file mode 100644
index 000000000..667d9845c
--- /dev/null
+++ b/docs/en/html/api/migrate.html
@@ -0,0 +1,78 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+migrate.pl</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href="./../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>migrate.pl</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#OPTIONS'>OPTIONS</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>migrate.pl - A script to migrate from other bug-trackers to Bugzilla.</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<pre class="code"> ./migrate.pl --from=&#60;tracker&#62; [--verbose] [--dry-run]
+
+ Migrates from another bug-tracker to Bugzilla. If you want
+ to upgrade Bugzilla, use checksetup.pl instead.
+
+ Always test this on a backup copy of your database before
+ running it on your live Bugzilla.</pre>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="OPTIONS"
+>OPTIONS</a></h1>
+
+<dl>
+<dt><a name="--from=tracker"
+><b>--from=tracker</b></a></dt>
+
+<dd>
+<p>Specifies what bug-tracker you&#39;re migrating from. To see what values are valid, see the contents of the <em class="code">Bugzilla/Migrate/</em> directory.</p>
+
+<dt><a name="--dry-run"
+><b>--dry-run</b></a></dt>
+
+<dd>
+<p>Don&#39;t modify the Bugzilla database at all, just test the import. Note that this could cause significant slowdown and other strange effects on a live Bugzilla, so only use it on a test instance.</p>
+
+<dt><a name="--verbose"
+><b>--verbose</b></a></dt>
+
+<dd>
+<p>If specified, this script will output extra debugging information to STDERR. Specify multiple times (up to three) for more information.</p>
+</dd>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>This script copies data from another bug-tracker into Bugzilla. It migrates users, products, and bugs from the other bug-tracker into this Bugzilla, without removing any of the data currently in this Bugzilla.</p>
+
+<p>Note that you will need enough space in your temporary directory to hold the size of all attachments in your current bug-tracker.</p>
+
+<p>You may also need to increase the number of file handles a process is allowed to hold open (as the migrator will create a file handle for each attachment in your database). On Linux and simliar systems, you can do this as root by typing <code class="code">ulimit -n 65535</code> before running your script.</p>
+<p class="backlinkbottom"><b><a name="___bottom" href="index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/api/sanitycheck.html b/docs/en/html/api/sanitycheck.html
new file mode 100644
index 000000000..f132207fe
--- /dev/null
+++ b/docs/en/html/api/sanitycheck.html
@@ -0,0 +1,69 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>
+sanitycheck.pl</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" title="style" type="text/css" href="./../../../style.css" media="all" >
+
+</head>
+ <body id="pod">
+<p class="backlinktop"><b><a name="___top" href="index.html" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>
+<h1>sanitycheck.pl</h1>
+<div class='indexgroup'>
+<ul class='indexList indexList1'>
+ <li class='indexItem indexItem1'><a href='#NAME'>NAME</a>
+ <li class='indexItem indexItem1'><a href='#SYNOPSIS'>SYNOPSIS</a>
+ <li class='indexItem indexItem1'><a href='#OPTIONS'>OPTIONS</a>
+ <li class='indexItem indexItem1'><a href='#DESCRIPTION'>DESCRIPTION</a>
+</ul>
+</div>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="NAME"
+>NAME</a></h1>
+
+<p>sanitycheck.pl - Perl script to perform a sanity check at the command line</p>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="SYNOPSIS"
+>SYNOPSIS</a></h1>
+
+<pre class="code"> ./sanitycheck.pl [--help]
+ ./sanitycheck.pl [--verbose] --login &#60;user@domain.com&#62;</pre>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="OPTIONS"
+>OPTIONS</a></h1>
+
+<dl>
+<dt><a name="--help"
+><b>--help</b></a></dt>
+
+<dd>
+<p>Displays this help text</p>
+
+<dt><a name="--verbose"
+><b>--verbose</b></a></dt>
+
+<dd>
+<p>Causes this script to be more verbose in its output. Without this option, the script will return only errors. With the option, the script will append all output to the email.</p>
+
+<dt><a name="--login"
+><b>--login</b></a></dt>
+
+<dd>
+<p>This should be passed the email address of a user that is capable of running the Sanity Check process, a user with the editcomponents priv. This user will receive an email with the results of the script run.</p>
+</dd>
+</dl>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="DESCRIPTION"
+>DESCRIPTION</a></h1>
+
+<p>This script provides a way of running a &#39;Sanity Check&#39; on the database via either a CLI or cron. It is equivalent to calling sanitycheck.cgi via a web broswer.</p>
+<p class="backlinkbottom"><b><a name="___bottom" href="index.html" title="All Documents">&lt;&lt;</a></b></p>
+
+<!-- end doc -->
+
+</body></html>
diff --git a/docs/en/html/attachments.html b/docs/en/html/attachments.html
new file mode 100644
index 000000000..6f166ca94
--- /dev/null
+++ b/docs/en/html/attachments.html
@@ -0,0 +1,364 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Attachments</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Using Bugzilla"
+HREF="using.html"><LINK
+REL="PREVIOUS"
+TITLE="Filing Bugs"
+HREF="bugreports.html"><LINK
+REL="NEXT"
+TITLE="Hints and Tips"
+HREF="hintsandtips.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="bugreports.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 5. Using Bugzilla</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="hintsandtips.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="attachments"
+>5.7. Attachments</A
+></H1
+><P
+>&#13; You should use attachments, rather than comments, for large chunks of ASCII
+ data, such as trace, debugging output files, or log files. That way, it
+ doesn't bloat the bug for everyone who wants to read it, and cause people to
+ receive fat, useless mails.
+ </P
+><P
+>You should make sure to trim screenshots. There's no need to show the
+ whole screen if you are pointing out a single-pixel problem.
+ </P
+><P
+>Bugzilla stores and uses a Content-Type for each attachment
+ (e.g. text/html). To download an attachment as a different
+ Content-Type (e.g. application/xhtml+xml), you can override this
+ using a 'content_type' parameter on the URL, e.g.
+ <TT
+CLASS="filename"
+>&#38;content_type=text/plain</TT
+>.
+ </P
+><P
+>&#13; If you have a really large attachment, something that does not need to
+ be recorded forever (as most attachments are), or something that is too
+ big for your database, you can mark your attachment as a
+ <SPAN
+CLASS="QUOTE"
+>"Big File"</SPAN
+>, assuming the administrator of the installation
+ has enabled this feature. Big Files are stored directly on disk instead
+ of in the database. The maximum size of a <SPAN
+CLASS="QUOTE"
+>"Big File"</SPAN
+> is
+ normally larger than the maximum size of a regular attachment. Independently
+ of the storage system used, an administrator can delete these attachments
+ at any time. Nevertheless, if these files are stored in the database, the
+ <SPAN
+CLASS="QUOTE"
+>"allow_attachment_deletion"</SPAN
+> parameter (which is turned off
+ by default) must be enabled in order to delete them.
+ </P
+><P
+>&#13; Also, if the administrator turned on the <SPAN
+CLASS="QUOTE"
+>"allow_attach_url"</SPAN
+>
+ parameter, you can enter the URL pointing to the attachment instead of
+ uploading the attachment itself. For example, this is useful if you want to
+ point to an external application, a website or a very large file. Note that
+ there is no guarantee that the source file will always be available, nor
+ that its content will remain unchanged.
+ </P
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="patchviewer"
+>5.7.1. Patch Viewer</A
+></H2
+><P
+>Viewing and reviewing patches in Bugzilla is often difficult due to
+ lack of context, improper format and the inherent readability issues that
+ raw patches present. Patch Viewer is an enhancement to Bugzilla designed
+ to fix that by offering increased context, linking to sections, and
+ integrating with Bonsai, LXR and CVS.</P
+><P
+>Patch viewer allows you to:</P
+><P
+></P
+><TABLE
+BORDER="0"
+><TBODY
+><TR
+><TD
+>View patches in color, with side-by-side view rather than trying
+ to interpret the contents of the patch.</TD
+></TR
+><TR
+><TD
+>See the difference between two patches.</TD
+></TR
+><TR
+><TD
+>Get more context in a patch.</TD
+></TR
+><TR
+><TD
+>Collapse and expand sections of a patch for easy
+ reading.</TD
+></TR
+><TR
+><TD
+>Link to a particular section of a patch for discussion or
+ review</TD
+></TR
+><TR
+><TD
+>Go to Bonsai or LXR to see more context, blame, and
+ cross-references for the part of the patch you are looking at</TD
+></TR
+><TR
+><TD
+>Create a rawtext unified format diff out of any patch, no
+ matter what format it came from</TD
+></TR
+></TBODY
+></TABLE
+><P
+></P
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="patchviewer_view"
+>5.7.1.1. Viewing Patches in Patch Viewer</A
+></H3
+><P
+>The main way to view a patch in patch viewer is to click on the
+ "Diff" link next to a patch in the Attachments list on a bug. You may
+ also do this within the edit window by clicking the "View Attachment As
+ Diff" button in the Edit Attachment screen.</P
+></DIV
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="patchviewer_diff"
+>5.7.1.2. Seeing the Difference Between Two Patches</A
+></H3
+><P
+>To see the difference between two patches, you must first view the
+ newer patch in Patch Viewer. Then select the older patch from the
+ dropdown at the top of the page ("Differences between [dropdown] and
+ this patch") and click the "Diff" button. This will show you what
+ is new or changed in the newer patch.</P
+></DIV
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="patchviewer_context"
+>5.7.1.3. Getting More Context in a Patch</A
+></H3
+><P
+>To get more context in a patch, you put a number in the textbox at
+ the top of Patch Viewer ("Patch / File / [textbox]") and hit enter.
+ This will give you that many lines of context before and after each
+ change. Alternatively, you can click on the "File" link there and it
+ will show each change in the full context of the file. This feature only
+ works against files that were diffed using "cvs diff".</P
+></DIV
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="patchviewer_collapse"
+>5.7.1.4. Collapsing and Expanding Sections of a Patch</A
+></H3
+><P
+>To view only a certain set of files in a patch (for example, if a
+ patch is absolutely huge and you want to only review part of it at a
+ time), you can click the "(+)" and "(-)" links next to each file (to
+ expand it or collapse it). If you want to collapse all files or expand
+ all files, you can click the "Collapse All" and "Expand All" links at the
+ top of the page.</P
+></DIV
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="patchviewer_link"
+>5.7.1.5. Linking to a Section of a Patch</A
+></H3
+><P
+>To link to a section of a patch (for example, if you want to be
+ able to give someone a URL to show them which part you are talking
+ about) you simply click the "Link Here" link on the section header. The
+ resulting URL can be copied and used in discussion.</P
+></DIV
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="patchviewer_bonsai_lxr"
+>5.7.1.6. Going to Bonsai and LXR</A
+></H3
+><P
+>To go to Bonsai to get blame for the lines you are interested in,
+ you can click the "Lines XX-YY" link on the section header you are
+ interested in. This works even if the patch is against an old
+ version of the file, since Bonsai stores all versions of the file.</P
+><P
+>To go to LXR, you click on the filename on the file header
+ (unfortunately, since LXR only does the most recent version, line
+ numbers are likely to rot).</P
+></DIV
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="patchviewer_unified_diff"
+>5.7.1.7. Creating a Unified Diff</A
+></H3
+><P
+>If the patch is not in a format that you like, you can turn it
+ into a unified diff format by clicking the "Raw Unified" link at the top
+ of the page.</P
+></DIV
+></DIV
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="bugreports.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="hintsandtips.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Filing Bugs</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="using.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Hints and Tips</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/bug_page.html b/docs/en/html/bug_page.html
new file mode 100644
index 000000000..e50c2f4b7
--- /dev/null
+++ b/docs/en/html/bug_page.html
@@ -0,0 +1,510 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Anatomy of a Bug</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Using Bugzilla"
+HREF="using.html"><LINK
+REL="PREVIOUS"
+TITLE="Create a Bugzilla Account"
+HREF="myaccount.html"><LINK
+REL="NEXT"
+TITLE="Life Cycle of a Bug"
+HREF="lifecycle.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="myaccount.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 5. Using Bugzilla</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="lifecycle.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="bug_page"
+>5.3. Anatomy of a Bug</A
+></H1
+><P
+>The core of Bugzilla is the screen which displays a particular
+ bug. It's a good place to explain some Bugzilla concepts.
+ <A
+HREF="http://landfill.bugzilla.org/bugzilla-4.0-branch/show_bug.cgi?id=1"
+TARGET="_top"
+>&#13; Bug 1 on Landfill</A
+>
+
+ is a good example. Note that the labels for most fields are hyperlinks;
+ clicking them will take you to context-sensitive help on that
+ particular field. Fields marked * may not be present on every
+ installation of Bugzilla.</P
+><P
+></P
+><OL
+TYPE="1"
+><LI
+><P
+>&#13; <EM
+>Product and Component</EM
+>:
+ Bugs are divided up by Product and Component, with a Product
+ having one or more Components in it. For example,
+ bugzilla.mozilla.org's "Bugzilla" Product is composed of several
+ Components:
+ <P
+></P
+><TABLE
+BORDER="0"
+><TBODY
+><TR
+><TD
+>&#13; <EM
+>Administration:</EM
+>
+ Administration of a Bugzilla installation.</TD
+></TR
+><TR
+><TD
+>&#13; <EM
+>Bugzilla-General:</EM
+>
+ Anything that doesn't fit in the other components, or spans
+ multiple components.</TD
+></TR
+><TR
+><TD
+>&#13; <EM
+>Creating/Changing Bugs:</EM
+>
+ Creating, changing, and viewing bugs.</TD
+></TR
+><TR
+><TD
+>&#13; <EM
+>Documentation:</EM
+>
+ The Bugzilla documentation, including The Bugzilla Guide.</TD
+></TR
+><TR
+><TD
+>&#13; <EM
+>Email:</EM
+>
+ Anything to do with email sent by Bugzilla.</TD
+></TR
+><TR
+><TD
+>&#13; <EM
+>Installation:</EM
+>
+ The installation process of Bugzilla.</TD
+></TR
+><TR
+><TD
+>&#13; <EM
+>Query/Buglist:</EM
+>
+ Anything to do with searching for bugs and viewing the
+ buglists.</TD
+></TR
+><TR
+><TD
+>&#13; <EM
+>Reporting/Charting:</EM
+>
+ Getting reports from Bugzilla.</TD
+></TR
+><TR
+><TD
+>&#13; <EM
+>User Accounts:</EM
+>
+ Anything about managing a user account from the user's perspective.
+ Saved queries, creating accounts, changing passwords, logging in,
+ etc.</TD
+></TR
+><TR
+><TD
+>&#13; <EM
+>User Interface:</EM
+>
+ General issues having to do with the user interface cosmetics (not
+ functionality) including cosmetic issues, HTML templates,
+ etc.</TD
+></TR
+></TBODY
+></TABLE
+><P
+></P
+>
+ </P
+></LI
+><LI
+><P
+>&#13; <EM
+>Status and Resolution:</EM
+>
+
+ These define exactly what state the bug is in - from not even
+ being confirmed as a bug, through to being fixed and the fix
+ confirmed by Quality Assurance. The different possible values for
+ Status and Resolution on your installation should be documented in the
+ context-sensitive help for those items.</P
+></LI
+><LI
+><P
+>&#13; <EM
+>Assigned To:</EM
+>
+ The person responsible for fixing the bug.</P
+></LI
+><LI
+><P
+>&#13; <EM
+>*QA Contact:</EM
+>
+ The person responsible for quality assurance on this bug.</P
+></LI
+><LI
+><P
+>&#13; <EM
+>*URL:</EM
+>
+ A URL associated with the bug, if any.</P
+></LI
+><LI
+><P
+>&#13; <EM
+>Summary:</EM
+>
+ A one-sentence summary of the problem.</P
+></LI
+><LI
+><P
+>&#13; <EM
+>*Status Whiteboard:</EM
+>
+ (a.k.a. Whiteboard) A free-form text area for adding short notes
+ and tags to a bug.</P
+></LI
+><LI
+><P
+>&#13; <EM
+>*Keywords:</EM
+>
+ The administrator can define keywords which you can use to tag and
+ categorise bugs - e.g. The Mozilla Project has keywords like crash
+ and regression.</P
+></LI
+><LI
+><P
+>&#13; <EM
+>Platform and OS:</EM
+>
+ These indicate the computing environment where the bug was
+ found.</P
+></LI
+><LI
+><P
+>&#13; <EM
+>Version:</EM
+>
+ The "Version" field is usually used for versions of a product which
+ have been released, and is set to indicate which versions of a
+ Component have the particular problem the bug report is
+ about.</P
+></LI
+><LI
+><P
+>&#13; <EM
+>Priority:</EM
+>
+ The bug assignee uses this field to prioritize his or her bugs.
+ It's a good idea not to change this on other people's bugs.</P
+></LI
+><LI
+><P
+>&#13; <EM
+>Severity:</EM
+>
+ This indicates how severe the problem is - from blocker
+ ("application unusable") to trivial ("minor cosmetic issue"). You
+ can also use this field to indicate whether a bug is an enhancement
+ request.</P
+></LI
+><LI
+><P
+>&#13; <EM
+>*Target:</EM
+>
+ (a.k.a. Target Milestone) A future version by which the bug is to
+ be fixed. e.g. The Bugzilla Project's milestones for future
+ Bugzilla versions are 2.18, 2.20, 3.0, etc. Milestones are not
+ restricted to numbers, thought - you can use any text strings, such
+ as dates.</P
+></LI
+><LI
+><P
+>&#13; <EM
+>Reporter:</EM
+>
+ The person who filed the bug.</P
+></LI
+><LI
+><P
+>&#13; <EM
+>CC list:</EM
+>
+ A list of people who get mail when the bug changes.</P
+></LI
+><LI
+><P
+>&#13; <EM
+>*Time Tracking:</EM
+>
+ This form can be used for time tracking.
+ To use this feature, you have to be blessed group membership
+ specified by the <SPAN
+CLASS="QUOTE"
+>"timetrackinggroup"</SPAN
+> parameter.
+ <P
+></P
+><TABLE
+BORDER="0"
+><TBODY
+><TR
+><TD
+>&#13; <EM
+>Orig. Est.:</EM
+>
+ This field shows the original estimated time.</TD
+></TR
+><TR
+><TD
+>&#13; <EM
+>Current Est.:</EM
+>
+ This field shows the current estimated time.
+ This number is calculated from <SPAN
+CLASS="QUOTE"
+>"Hours Worked"</SPAN
+>
+ and <SPAN
+CLASS="QUOTE"
+>"Hours Left"</SPAN
+>.</TD
+></TR
+><TR
+><TD
+>&#13; <EM
+>Hours Worked:</EM
+>
+ This field shows the number of hours worked.</TD
+></TR
+><TR
+><TD
+>&#13; <EM
+>Hours Left:</EM
+>
+ This field shows the <SPAN
+CLASS="QUOTE"
+>"Current Est."</SPAN
+> -
+ <SPAN
+CLASS="QUOTE"
+>"Hours Worked"</SPAN
+>.
+ This value + <SPAN
+CLASS="QUOTE"
+>"Hours Worked"</SPAN
+> will become the
+ new Current Est.</TD
+></TR
+><TR
+><TD
+>&#13; <EM
+>%Complete:</EM
+>
+ This field shows what percentage of the task is complete.</TD
+></TR
+><TR
+><TD
+>&#13; <EM
+>Gain:</EM
+>
+ This field shows the number of hours that the bug is ahead of the
+ <SPAN
+CLASS="QUOTE"
+>"Orig. Est."</SPAN
+>.</TD
+></TR
+><TR
+><TD
+>&#13; <EM
+>Deadline:</EM
+>
+ This field shows the deadline for this bug.</TD
+></TR
+></TBODY
+></TABLE
+><P
+></P
+>
+ </P
+></LI
+><LI
+><P
+>&#13; <EM
+>Attachments:</EM
+>
+ You can attach files (e.g. testcases or patches) to bugs. If there
+ are any attachments, they are listed in this section. Attachments are
+ normally stored in the Bugzilla database, unless they are marked as
+ Big Files, which are stored directly on disk.
+ </P
+></LI
+><LI
+><P
+>&#13; <EM
+>*Dependencies:</EM
+>
+ If this bug cannot be fixed unless other bugs are fixed (depends
+ on), or this bug stops other bugs being fixed (blocks), their
+ numbers are recorded here.</P
+></LI
+><LI
+><P
+>&#13; <EM
+>*Votes:</EM
+>
+ Whether this bug has any votes.</P
+></LI
+><LI
+><P
+>&#13; <EM
+>Additional Comments:</EM
+>
+ You can add your two cents to the bug discussion here, if you have
+ something worthwhile to say.</P
+></LI
+></OL
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="myaccount.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="lifecycle.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Create a Bugzilla Account</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="using.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Life Cycle of a Bug</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/bug_status_workflow.html b/docs/en/html/bug_status_workflow.html
new file mode 100644
index 000000000..e4f0550c0
--- /dev/null
+++ b/docs/en/html/bug_status_workflow.html
@@ -0,0 +1,162 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Bug Status Workflow</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Administering Bugzilla"
+HREF="administration.html"><LINK
+REL="PREVIOUS"
+TITLE="Legal Values"
+HREF="edit-values.html"><LINK
+REL="NEXT"
+TITLE="Voting"
+HREF="voting.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="edit-values.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 3. Administering Bugzilla</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="voting.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="bug_status_workflow"
+>3.12. Bug Status Workflow</A
+></H1
+><P
+>&#13; The bug status workflow is no longer hardcoded but can be freely customized
+ from the web interface. Only one bug status cannot be renamed nor deleted,
+ UNCONFIRMED, but the workflow involving it is free. The configuration
+ page displays all existing bug statuses twice, first on the left for bug
+ statuses we come from and on the top for bug statuses we move to.
+ If the checkbox is checked, then the transition between the two bug statuses
+ is legal, else it's forbidden independently of your privileges. The bug status
+ used for the "duplicate_or_move_bug_status" parameter must be part of the
+ workflow as that is the bug status which will be used when duplicating or
+ moving a bug, so it must be available from each bug status.
+ </P
+><P
+>&#13; When the workflow is set, the "View Current Triggers" link below the table
+ lets you set which transitions require a comment from the user.
+ </P
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="edit-values.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="voting.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Legal Values</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="administration.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Voting</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/bugreports.html b/docs/en/html/bugreports.html
new file mode 100644
index 000000000..b0a58604c
--- /dev/null
+++ b/docs/en/html/bugreports.html
@@ -0,0 +1,353 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Filing Bugs</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Using Bugzilla"
+HREF="using.html"><LINK
+REL="PREVIOUS"
+TITLE="Searching for Bugs"
+HREF="query.html"><LINK
+REL="NEXT"
+TITLE="Attachments"
+HREF="attachments.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="query.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 5. Using Bugzilla</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="attachments.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="bugreports"
+>5.6. Filing Bugs</A
+></H1
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="fillingbugs"
+>5.6.1. Reporting a New Bug</A
+></H2
+><P
+>Years of bug writing experience has been distilled for your
+ reading pleasure into the
+ <A
+HREF="http://landfill.bugzilla.org/bugzilla-4.0-branch/page.cgi?id=bug-writing.html"
+TARGET="_top"
+>&#13; Bug Writing Guidelines</A
+>.
+ While some of the advice is Mozilla-specific, the basic principles of
+ reporting Reproducible, Specific bugs, isolating the Product you are
+ using, the Version of the Product, the Component which failed, the
+ Hardware Platform, and Operating System you were using at the time of
+ the failure go a long way toward ensuring accurate, responsible fixes
+ for the bug that bit you.</P
+><P
+>The procedure for filing a bug is as follows:</P
+><P
+></P
+><OL
+TYPE="1"
+><LI
+><P
+>&#13; Click the <SPAN
+CLASS="QUOTE"
+>"New"</SPAN
+> link available in the footer
+ of pages, or the <SPAN
+CLASS="QUOTE"
+>"Enter a new bug report"</SPAN
+> link
+ displayed on the home page of the Bugzilla installation.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; If you want to file a test bug to see how Bugzilla works,
+ you can do it on one of our test installations on
+ <A
+HREF="http://landfill.bugzilla.org/bugzilla-4.0-branch/"
+TARGET="_top"
+>Landfill</A
+>.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></LI
+><LI
+><P
+>&#13; You first have to select the product in which you found a bug.
+ </P
+></LI
+><LI
+><P
+>&#13; You now see a form where you can specify the component (part of
+ the product which is affected by the bug you discovered; if you have
+ no idea, just select <SPAN
+CLASS="QUOTE"
+>"General"</SPAN
+> if such a component exists),
+ the version of the program you were using, the Operating System and
+ platform your program is running on and the severity of the bug (if the
+ bug you found crashes the program, it's probably a major or a critical
+ bug; if it's a typo somewhere, that's something pretty minor; if it's
+ something you would like to see implemented, then that's an enhancement).
+ </P
+></LI
+><LI
+><P
+>&#13; You now have to give a short but descriptive summary of the bug you found.
+ <SPAN
+CLASS="QUOTE"
+>"My program is crashing all the time"</SPAN
+> is a very poor summary
+ and doesn't help developers at all. Try something more meaningful or
+ your bug will probably be ignored due to a lack of precision.
+ The next step is to give a very detailed list of steps to reproduce
+ the problem you encountered. Try to limit these steps to a minimum set
+ required to reproduce the problem. This will make the life of
+ developers easier, and the probability that they consider your bug in
+ a reasonable timeframe will be much higher.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Try to make sure that everything in the summary is also in the first
+ comment. Summaries are often updated and this will ensure your original
+ information is easily accessible.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></LI
+><LI
+><P
+>&#13; As you file the bug, you can also attach a document (testcase, patch,
+ or screenshot of the problem).
+ </P
+></LI
+><LI
+><P
+>&#13; Depending on the Bugzilla installation you are using and the product in
+ which you are filing the bug, you can also request developers to consider
+ your bug in different ways (such as requesting review for the patch you
+ just attached, requesting your bug to block the next release of the
+ product, and many other product specific requests).
+ </P
+></LI
+><LI
+><P
+>&#13; Now is a good time to read your bug report again. Remove all misspellings,
+ otherwise your bug may not be found by developers running queries for some
+ specific words, and so your bug would not get any attention.
+ Also make sure you didn't forget any important information developers
+ should know in order to reproduce the problem, and make sure your
+ description of the problem is explicit and clear enough.
+ When you think your bug report is ready to go, the last step is to
+ click the <SPAN
+CLASS="QUOTE"
+>"Commit"</SPAN
+> button to add your report into the database.
+ </P
+></LI
+></OL
+><P
+>&#13; You do not need to put "any" or similar strings in the URL field.
+ If there is no specific URL associated with the bug, leave this
+ field blank.
+ </P
+><P
+>If you feel a bug you filed was incorrectly marked as a
+ DUPLICATE of another, please question it in your bug, not
+ the bug it was duped to. Feel free to CC the person who duped it
+ if they are not already CCed.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="cloningbugs"
+>5.6.2. Clone an Existing Bug</A
+></H2
+><P
+>&#13; Starting with version 2.20, Bugzilla has a feature that allows you
+ to clone an existing bug. The newly created bug will inherit
+ most settings from the old bug. This allows you to track more
+ easily similar concerns in a new bug. To use this, go to the bug
+ that you want to clone, then click the <SPAN
+CLASS="QUOTE"
+>"Clone This Bug"</SPAN
+>
+ link on the bug page. This will take you to the <SPAN
+CLASS="QUOTE"
+>"Enter Bug"</SPAN
+>
+ page that is filled with the values that the old bug has.
+ You can change those values and/or texts if needed.
+ </P
+></DIV
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="query.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="attachments.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Searching for Bugs</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="using.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Attachments</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/classifications.html b/docs/en/html/classifications.html
new file mode 100644
index 000000000..266fd1200
--- /dev/null
+++ b/docs/en/html/classifications.html
@@ -0,0 +1,165 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Classifications</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Administering Bugzilla"
+HREF="administration.html"><LINK
+REL="PREVIOUS"
+TITLE="User Administration"
+HREF="useradmin.html"><LINK
+REL="NEXT"
+TITLE="Products"
+HREF="products.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="useradmin.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 3. Administering Bugzilla</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="products.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="classifications"
+>3.3. Classifications</A
+></H1
+><P
+>Classifications tend to be used in order to group several related
+ products into one distinct entity.</P
+><P
+>The classifications layer is disabled by default; it can be turned
+ on or off using the useclassification parameter,
+ in the <EM
+>Bug Fields</EM
+> section of the edit parameters screen.</P
+><P
+>Access to the administration of classifications is controlled using
+ the <EM
+>editclassifications</EM
+> system group, which defines
+ a privilege for creating, destroying, and editing classifications.</P
+><P
+>When activated, classifications will introduce an additional
+ step when filling bugs (dedicated to classification selection), and they
+ will also appear in the advanced search form.</P
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="useradmin.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="products.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>User Administration</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="administration.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Products</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/cmdline-bugmail.html b/docs/en/html/cmdline-bugmail.html
new file mode 100644
index 000000000..23f766e6d
--- /dev/null
+++ b/docs/en/html/cmdline-bugmail.html
@@ -0,0 +1,188 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Command-line 'Send Unsent Bug-mail' tool</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Contrib"
+HREF="patches.html"><LINK
+REL="PREVIOUS"
+TITLE="Command-line Search Interface"
+HREF="cmdline.html"><LINK
+REL="NEXT"
+TITLE="Manual Installation of Perl Modules"
+HREF="install-perlmodules-manual.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="cmdline.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Appendix B. Contrib</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="install-perlmodules-manual.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="cmdline-bugmail"
+>B.2. Command-line 'Send Unsent Bug-mail' tool</A
+></H1
+><P
+>&#13; Within the <TT
+CLASS="filename"
+>contrib</TT
+> directory
+ exists a utility with the descriptive (if compact) name
+ of <TT
+CLASS="filename"
+>sendunsentbugmail.pl</TT
+>. The purpose of this
+ script is, simply, to send out any bug-related mail that should
+ have been sent by now, but for one reason or another has not.
+ </P
+><P
+>&#13; To accomplish this task, <TT
+CLASS="filename"
+>sendunsentbugmail.pl</TT
+> uses
+ the same mechanism as the <TT
+CLASS="filename"
+>sanitycheck.cgi</TT
+> script;
+ it scans through the entire database looking for bugs with changes that
+ were made more than 30 minutes ago, but where there is no record of
+ anyone related to that bug having been sent mail. Having compiled a list,
+ it then uses the standard rules to determine who gets mail, and sends it
+ out.
+ </P
+><P
+>&#13; As the script runs, it indicates the bug for which it is currently
+ sending mail; when it has finished, it gives a numerical count of how
+ many mails were sent and how many people were excluded. (Individual
+ user names are not recorded or displayed.) If the script produces
+ no output, that means no unsent mail was detected.
+ </P
+><P
+>&#13; <EM
+>Usage</EM
+>: move the sendunsentbugmail.pl script
+ up into the main directory, ensure it has execute permission, and run it
+ from the command line (or from a cron job) with no parameters.
+ </P
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="cmdline.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="install-perlmodules-manual.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Command-line Search Interface</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="patches.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Manual Installation of Perl Modules</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/cmdline.html b/docs/en/html/cmdline.html
new file mode 100644
index 000000000..9c98069b7
--- /dev/null
+++ b/docs/en/html/cmdline.html
@@ -0,0 +1,281 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Command-line Search Interface</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Contrib"
+HREF="patches.html"><LINK
+REL="PREVIOUS"
+TITLE="Contrib"
+HREF="patches.html"><LINK
+REL="NEXT"
+TITLE="Command-line 'Send Unsent Bug-mail' tool"
+HREF="cmdline-bugmail.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="patches.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Appendix B. Contrib</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="cmdline-bugmail.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="cmdline"
+>B.1. Command-line Search Interface</A
+></H1
+><P
+>&#13; There are a suite of Unix utilities for searching Bugzilla from the
+ command line. They live in the
+ <TT
+CLASS="filename"
+>contrib/cmdline</TT
+> directory.
+ There are three files - <TT
+CLASS="filename"
+>query.conf</TT
+>,
+ <TT
+CLASS="filename"
+>buglist</TT
+> and <TT
+CLASS="filename"
+>bugs</TT
+>.
+ </P
+><DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; These files pre-date the templatization work done as part of the
+ 2.16 release, and have not been updated.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; <TT
+CLASS="filename"
+>query.conf</TT
+> contains the mapping from
+ options to field names and comparison types. Quoted option names
+ are <SPAN
+CLASS="QUOTE"
+>"grepped"</SPAN
+> for, so it should be easy to edit this
+ file. Comments (#) have no effect; you must make sure these lines
+ do not contain any quoted <SPAN
+CLASS="QUOTE"
+>"option"</SPAN
+>.
+ </P
+><P
+>&#13; <TT
+CLASS="filename"
+>buglist</TT
+> is a shell script that submits a
+ Bugzilla query and writes the resulting HTML page to stdout.
+ It supports both short options, (such as <SPAN
+CLASS="QUOTE"
+>"-Afoo"</SPAN
+>
+ or <SPAN
+CLASS="QUOTE"
+>"-Rbar"</SPAN
+>) and long options (such
+ as <SPAN
+CLASS="QUOTE"
+>"--assignedto=foo"</SPAN
+> or <SPAN
+CLASS="QUOTE"
+>"--reporter=bar"</SPAN
+>).
+ If the first character of an option is not <SPAN
+CLASS="QUOTE"
+>"-"</SPAN
+>, it is
+ treated as if it were prefixed with <SPAN
+CLASS="QUOTE"
+>"--default="</SPAN
+>.
+ </P
+><P
+>&#13; The column list is taken from the COLUMNLIST environment variable.
+ This is equivalent to the <SPAN
+CLASS="QUOTE"
+>"Change Columns"</SPAN
+> option
+ that is available when you list bugs in buglist.cgi. If you have
+ already used Bugzilla, grep for COLUMNLIST in your cookies file
+ to see your current COLUMNLIST setting.
+ </P
+><P
+>&#13; <TT
+CLASS="filename"
+>bugs</TT
+> is a simple shell script which calls
+ <TT
+CLASS="filename"
+>buglist</TT
+> and extracts the
+ bug numbers from the output. Adding the prefix
+ <SPAN
+CLASS="QUOTE"
+>"http://bugzilla.mozilla.org/buglist.cgi?bug_id="</SPAN
+>
+ turns the bug list into a working link if any bugs are found.
+ Counting bugs is easy. Pipe the results through
+ <B
+CLASS="command"
+>sed -e 's/,/ /g' | wc | awk '{printf $2 "\n"}'</B
+>
+ </P
+><P
+>&#13; Akkana Peck says she has good results piping
+ <TT
+CLASS="filename"
+>buglist</TT
+> output through
+ <B
+CLASS="command"
+>w3m -T text/html -dump</B
+>
+ </P
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="patches.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="cmdline-bugmail.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Contrib</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="patches.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Command-line 'Send Unsent Bug-mail' tool</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/components.html b/docs/en/html/components.html
new file mode 100644
index 000000000..4c143c9b5
--- /dev/null
+++ b/docs/en/html/components.html
@@ -0,0 +1,224 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Components</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Administering Bugzilla"
+HREF="administration.html"><LINK
+REL="PREVIOUS"
+TITLE="Products"
+HREF="products.html"><LINK
+REL="NEXT"
+TITLE="Versions"
+HREF="versions.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="products.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 3. Administering Bugzilla</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="versions.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="components"
+>3.5. Components</A
+></H1
+><P
+>Components are subsections of a Product. E.g. the computer game
+ you are designing may have a "UI"
+ component, an "API" component, a "Sound System" component, and a
+ "Plugins" component, each overseen by a different programmer. It
+ often makes sense to divide Components in Bugzilla according to the
+ natural divisions of responsibility within your Product or
+ company.</P
+><P
+>&#13; Each component has a default assignee and (if you turned it on in the parameters),
+ a QA Contact. The default assignee should be the primary person who fixes bugs in
+ that component. The QA Contact should be the person who will ensure
+ these bugs are completely fixed. The Assignee, QA Contact, and Reporter
+ will get email when new bugs are created in this Component and when
+ these bugs change. Default Assignee and Default QA Contact fields only
+ dictate the
+ <EM
+>default assignments</EM
+>;
+ these can be changed on bug submission, or at any later point in
+ a bug's life.</P
+><P
+>To create a new Component:</P
+><P
+></P
+><OL
+TYPE="1"
+><LI
+><P
+>Select the <SPAN
+CLASS="QUOTE"
+>"Edit components"</SPAN
+> link
+ from the <SPAN
+CLASS="QUOTE"
+>"Edit product"</SPAN
+> page</P
+></LI
+><LI
+><P
+>Select the <SPAN
+CLASS="QUOTE"
+>"Add"</SPAN
+> link in the bottom right.</P
+></LI
+><LI
+><P
+>Fill out the <SPAN
+CLASS="QUOTE"
+>"Component"</SPAN
+> field, a
+ short <SPAN
+CLASS="QUOTE"
+>"Description"</SPAN
+>, the
+ <SPAN
+CLASS="QUOTE"
+>"Default Assignee"</SPAN
+>, <SPAN
+CLASS="QUOTE"
+>"Default CC List"</SPAN
+>
+ and <SPAN
+CLASS="QUOTE"
+>"Default QA Contact"</SPAN
+> (if enabled).
+ The <SPAN
+CLASS="QUOTE"
+>"Component Description"</SPAN
+> field may contain a
+ limited subset of HTML tags. The <SPAN
+CLASS="QUOTE"
+>"Default Assignee"</SPAN
+>
+ field must be a login name already existing in the Bugzilla database.
+ </P
+></LI
+></OL
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="products.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="versions.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Products</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="administration.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Versions</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/configuration.html b/docs/en/html/configuration.html
new file mode 100644
index 000000000..90344b592
--- /dev/null
+++ b/docs/en/html/configuration.html
@@ -0,0 +1,1867 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Configuration</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Installing Bugzilla"
+HREF="installing-bugzilla.html"><LINK
+REL="PREVIOUS"
+TITLE="Installation"
+HREF="installation.html"><LINK
+REL="NEXT"
+TITLE="Optional Additional Configuration"
+HREF="extraconfig.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="installation.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 2. Installing Bugzilla</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="extraconfig.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="configuration"
+>2.2. Configuration</A
+></H1
+><DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Poorly-configured MySQL and Bugzilla installations have
+ given attackers full access to systems in the past. Please take the
+ security parts of these guidelines seriously, even for Bugzilla
+ machines hidden away behind your firewall. Be certain to read
+ <A
+HREF="security.html"
+>Chapter 4</A
+> for some important security tips.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="localconfig"
+>2.2.1. localconfig</A
+></H2
+><P
+>&#13; You should now run <TT
+CLASS="filename"
+>checksetup.pl</TT
+> again, this time
+ without the <TT
+CLASS="literal"
+>--check-modules</TT
+> switch.
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="screen"
+><SAMP
+CLASS="prompt"
+>bash#</SAMP
+> ./checksetup.pl</PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>&#13; This time, <TT
+CLASS="filename"
+>checksetup.pl</TT
+> should tell you that all
+ the correct modules are installed and will display a message about, and
+ write out a file called, <TT
+CLASS="filename"
+>localconfig</TT
+>. This file
+ contains the default settings for a number of Bugzilla parameters.
+ </P
+><P
+>&#13; Load this file in your editor. The only two values you
+ <EM
+>need</EM
+> to change are $db_driver and $db_pass,
+ respectively the type of the database and the password for
+ the user you will create for your database. Pick a strong
+ password (for simplicity, it should not contain single quote
+ characters) and put it here. $db_driver can be either 'mysql',
+ 'Pg' or 'oracle'.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; In Oracle, <TT
+CLASS="literal"
+>$db_name</TT
+> should actually be
+ the SID name of your database (e.g. "XE" if you are using Oracle XE).
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; You may need to change the value of
+ <EM
+>webservergroup</EM
+> if your web server does not
+ run in the "apache" group. On Debian, for example, Apache runs in
+ the "www-data" group. If you are going to run Bugzilla on a
+ machine where you do not have root access (such as on a shared web
+ hosting account), you will need to leave
+ <EM
+>webservergroup</EM
+> empty, ignoring the warnings
+ that <TT
+CLASS="filename"
+>checksetup.pl</TT
+> will subsequently display
+ every time it is run.
+ </P
+><DIV
+CLASS="caution"
+><P
+></P
+><TABLE
+CLASS="caution"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/caution.gif"
+HSPACE="5"
+ALT="Caution"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; If you are using suexec, you should use your own primary group
+ for <EM
+>webservergroup</EM
+> rather than leaving it
+ empty, and see the additional directions in the suexec section
+ <A
+HREF="nonroot.html#suexec"
+>Section 2.6.6.1</A
+>.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; The other options in the <TT
+CLASS="filename"
+>localconfig</TT
+> file
+ are documented by their accompanying comments. If you have a slightly
+ non-standard database setup, you may wish to change one or more of
+ the other "$db_*" parameters.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="database-engine"
+>2.2.2. Database Server</A
+></H2
+><P
+>&#13; This section deals with configuring your database server for use
+ with Bugzilla. Currently, MySQL (<A
+HREF="configuration.html#mysql"
+>Section 2.2.2.2</A
+>),
+ PostgreSQL (<A
+HREF="configuration.html#postgresql"
+>Section 2.2.2.3</A
+>) and Oracle (<A
+HREF="configuration.html#oracle"
+>Section 2.2.2.4</A
+>)
+ are available.
+ </P
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="database-schema"
+>2.2.2.1. Bugzilla Database Schema</A
+></H3
+><P
+>&#13; The Bugzilla database schema is available at
+ <A
+HREF="http://www.ravenbrook.com/project/p4dti/tool/cgi/bugzilla-schema/"
+TARGET="_top"
+>Ravenbrook</A
+>.
+ This very valuable tool can generate a written description of
+ the Bugzilla database schema for any version of Bugzilla. It
+ can also generate a diff between two versions to help someone
+ see what has changed.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="mysql"
+>2.2.2.2. MySQL</A
+></H3
+><DIV
+CLASS="caution"
+><P
+></P
+><TABLE
+CLASS="caution"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/caution.gif"
+HSPACE="5"
+ALT="Caution"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; MySQL's default configuration is insecure.
+ We highly recommend to run <TT
+CLASS="filename"
+>mysql_secure_installation</TT
+>
+ on Linux or the MySQL installer on Windows, and follow the instructions.
+ Important points to note are:
+ <P
+></P
+><OL
+TYPE="1"
+><LI
+><P
+>Be sure that the root account has a secure password set.</P
+></LI
+><LI
+><P
+>Do not create an anonymous account, and if it exists, say "yes"
+ to remove it.</P
+></LI
+><LI
+><P
+>If your web server and MySQL server are on the same machine,
+ you should disable the network access.</P
+></LI
+></OL
+>
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="section"
+><H4
+CLASS="section"
+><A
+NAME="mysql-max-allowed-packet"
+>2.2.2.2.1. Allow large attachments and many comments</A
+></H4
+><P
+>By default, MySQL will only allow you to insert things
+ into the database that are smaller than 1MB. Attachments
+ may be larger than this. Also, Bugzilla combines all comments
+ on a single bug into one field for full-text searching, and the
+ combination of all comments on a single bug could in some cases
+ be larger than 1MB.</P
+><P
+>To change MySQL's default, you need to edit your MySQL
+ configuration file, which is usually <TT
+CLASS="filename"
+>/etc/my.cnf</TT
+>
+ on Linux. We recommend that you allow at least 4MB packets by
+ adding the "max_allowed_packet" parameter to your MySQL
+ configuration in the "[mysqld]" section, like this:</P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="screen"
+>[mysqld]
+# Allow packets up to 4MB
+max_allowed_packet=4M
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="section"
+><H4
+CLASS="section"
+><A
+NAME="AEN438"
+>2.2.2.2.2. Allow small words in full-text indexes</A
+></H4
+><P
+>By default, words must be at least four characters in length
+ in order to be indexed by MySQL's full-text indexes. This causes
+ a lot of Bugzilla specific words to be missed, including "cc",
+ "ftp" and "uri".</P
+><P
+>MySQL can be configured to index those words by setting the
+ ft_min_word_len param to the minimum size of the words to index.
+ This can be done by modifying the <TT
+CLASS="filename"
+>/etc/my.cnf</TT
+>
+ according to the example below:</P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="screen"
+> [mysqld]
+ # Allow small words in full-text indexes
+ ft_min_word_len=2</PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>Rebuilding the indexes can be done based on documentation found at
+ <A
+HREF="http://www.mysql.com/doc/en/Fulltext_Fine-tuning.html"
+TARGET="_top"
+>http://www.mysql.com/doc/en/Fulltext_Fine-tuning.html</A
+>.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H4
+CLASS="section"
+><A
+NAME="install-setupdatabase-adduser"
+>2.2.2.2.3. Add a user to MySQL</A
+></H4
+><P
+>&#13; You need to add a new MySQL user for Bugzilla to use.
+ (It's not safe to have Bugzilla use the MySQL root account.)
+ The following instructions assume the defaults in
+ <TT
+CLASS="filename"
+>localconfig</TT
+>; if you changed those,
+ you need to modify the SQL command appropriately. You will
+ need the <TT
+CLASS="replaceable"
+><I
+>$db_pass</I
+></TT
+> password you
+ set in <TT
+CLASS="filename"
+>localconfig</TT
+> in
+ <A
+HREF="configuration.html#localconfig"
+>Section 2.2.1</A
+>.
+ </P
+><P
+>&#13; We use an SQL <B
+CLASS="command"
+>GRANT</B
+> command to create
+ a <SPAN
+CLASS="QUOTE"
+>"bugs"</SPAN
+> user. This also restricts the
+ <SPAN
+CLASS="QUOTE"
+>"bugs"</SPAN
+>user to operations within a database
+ called <SPAN
+CLASS="QUOTE"
+>"bugs"</SPAN
+>, and only allows the account
+ to connect from <SPAN
+CLASS="QUOTE"
+>"localhost"</SPAN
+>. Modify it to
+ reflect your setup if you will be connecting from another
+ machine or as a different user.
+ </P
+><P
+>&#13; Run the <TT
+CLASS="filename"
+>mysql</TT
+> command-line client and enter:
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="screen"
+>&#13; <SAMP
+CLASS="prompt"
+>mysql&#62;</SAMP
+> GRANT SELECT, INSERT,
+ UPDATE, DELETE, INDEX, ALTER, CREATE, LOCK TABLES,
+ CREATE TEMPORARY TABLES, DROP, REFERENCES ON bugs.*
+ TO bugs@localhost IDENTIFIED BY '<TT
+CLASS="replaceable"
+><I
+>$db_pass</I
+></TT
+>';
+ <SAMP
+CLASS="prompt"
+>mysql&#62;</SAMP
+> FLUSH PRIVILEGES;
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="section"
+><H4
+CLASS="section"
+><A
+NAME="AEN465"
+>2.2.2.2.4. Permit attachments table to grow beyond 4GB</A
+></H4
+><P
+>&#13; By default, MySQL will limit the size of a table to 4GB.
+ This limit is present even if the underlying filesystem
+ has no such limit. To set a higher limit, follow these
+ instructions.
+ </P
+><P
+>&#13; After you have completed the rest of the installation (or at least the
+ database setup parts), you should run the <TT
+CLASS="filename"
+>MySQL</TT
+>
+ command-line client and enter the following, replacing <TT
+CLASS="literal"
+>$bugs_db</TT
+>
+ with your Bugzilla database name (<EM
+>bugs</EM
+> by default):
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="screen"
+>&#13; <SAMP
+CLASS="prompt"
+>mysql&#62;</SAMP
+> use <TT
+CLASS="replaceable"
+><I
+>$bugs_db</I
+></TT
+>
+ <SAMP
+CLASS="prompt"
+>mysql&#62;</SAMP
+> ALTER TABLE attachments
+ AVG_ROW_LENGTH=1000000, MAX_ROWS=20000;
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>&#13; The above command will change the limit to 20GB. Mysql will have
+ to make a temporary copy of your entire table to do this. Ideally,
+ you should do this when your attachments table is still small.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; This does not affect Big Files, attachments that are stored directly
+ on disk instead of in the database.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="postgresql"
+>2.2.2.3. PostgreSQL</A
+></H3
+><DIV
+CLASS="section"
+><H4
+CLASS="section"
+><A
+NAME="AEN481"
+>2.2.2.3.1. Add a User to PostgreSQL</A
+></H4
+><P
+>You need to add a new user to PostgreSQL for the Bugzilla
+ application to use when accessing the database. The following instructions
+ assume the defaults in <TT
+CLASS="filename"
+>localconfig</TT
+>; if you
+ changed those, you need to modify the commands appropriately. You will
+ need the <TT
+CLASS="replaceable"
+><I
+>$db_pass</I
+></TT
+> password you
+ set in <TT
+CLASS="filename"
+>localconfig</TT
+> in
+ <A
+HREF="configuration.html#localconfig"
+>Section 2.2.1</A
+>.</P
+><P
+>On most systems, to create the user in PostgreSQL, you will need to
+ login as the root user, and then</P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="screen"
+> <SAMP
+CLASS="prompt"
+>bash#</SAMP
+> su - postgres</PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>As the postgres user, you then need to create a new user: </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="screen"
+> <SAMP
+CLASS="prompt"
+>bash$</SAMP
+> createuser -U postgres -dRSP bugs</PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>When asked for a password, provide the password which will be set as
+ <TT
+CLASS="replaceable"
+><I
+>$db_pass</I
+></TT
+> in <TT
+CLASS="filename"
+>localconfig</TT
+>.
+ The created user will not be a superuser (-S) and will not be able to create
+ new users (-R). He will only have the ability to create databases (-d).</P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>If your are running PostgreSQL 8.0, you must replace -dRSP by -dAP.</P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><H4
+CLASS="section"
+><A
+NAME="AEN499"
+>2.2.2.3.2. Configure PostgreSQL</A
+></H4
+><P
+>Now, you will need to edit <TT
+CLASS="filename"
+>pg_hba.conf</TT
+> which is
+ usually located in <TT
+CLASS="filename"
+>/var/lib/pgsql/data/</TT
+>. In this file,
+ you will need to add a new line to it as follows:</P
+><P
+>&#13; <SAMP
+CLASS="computeroutput"
+>host all bugs 127.0.0.1 255.255.255.255 md5</SAMP
+>
+ </P
+><P
+>This means that for TCP/IP (host) connections, allow connections from
+ '127.0.0.1' to 'all' databases on this server from the 'bugs' user, and use
+ password authentication (md5) for that user.</P
+><P
+>Now, you will need to restart PostgreSQL, but you will need to fully
+ stop and start the server rather than just restarting due to the possibility
+ of a change to <TT
+CLASS="filename"
+>postgresql.conf</TT
+>. After the server has
+ restarted, you will need to edit <TT
+CLASS="filename"
+>localconfig</TT
+>, finding
+ the <TT
+CLASS="literal"
+>$db_driver</TT
+> variable and setting it to
+ <TT
+CLASS="literal"
+>Pg</TT
+> and changing the password in <TT
+CLASS="literal"
+>$db_pass</TT
+>
+ to the one you picked previously, while setting up the account.</P
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="oracle"
+>2.2.2.4. Oracle</A
+></H3
+><DIV
+CLASS="section"
+><H4
+CLASS="section"
+><A
+NAME="AEN515"
+>2.2.2.4.1. Create a New Tablespace</A
+></H4
+><P
+>&#13; You can use the existing tablespace or create a new one for Bugzilla.
+ To create a new tablespace, run the following command:
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#13; CREATE TABLESPACE bugs
+ DATAFILE '<TT
+CLASS="replaceable"
+><I
+>$path_to_datafile</I
+></TT
+>' SIZE 500M
+ AUTOEXTEND ON NEXT 30M MAXSIZE UNLIMITED
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>&#13; Here, the name of the tablespace is 'bugs', but you can
+ choose another name. <TT
+CLASS="replaceable"
+><I
+>$path_to_datafile</I
+></TT
+> is
+ the path to the file containing your database, for instance
+ <TT
+CLASS="filename"
+>/u01/oradata/bugzilla.dbf</TT
+>.
+ The initial size of the database file is set in this example to 500 Mb,
+ with an increment of 30 Mb everytime we reach the size limit of the file.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H4
+CLASS="section"
+><A
+NAME="AEN523"
+>2.2.2.4.2. Add a User to Oracle</A
+></H4
+><P
+>&#13; The user name and password must match what you set in
+ <TT
+CLASS="filename"
+>localconfig</TT
+> (<TT
+CLASS="literal"
+>$db_user</TT
+>
+ and <TT
+CLASS="literal"
+>$db_pass</TT
+>, respectively). Here, we assume that
+ the user name is 'bugs' and the tablespace name is the same
+ as above.
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#13; CREATE USER bugs
+ IDENTIFIED BY "<TT
+CLASS="replaceable"
+><I
+>$db_pass</I
+></TT
+>"
+ DEFAULT TABLESPACE bugs
+ TEMPORARY TABLESPACE TEMP
+ PROFILE DEFAULT;
+ -- GRANT/REVOKE ROLE PRIVILEGES
+ GRANT CONNECT TO bugs;
+ GRANT RESOURCE TO bugs;
+ -- GRANT/REVOKE SYSTEM PRIVILEGES
+ GRANT UNLIMITED TABLESPACE TO bugs;
+ GRANT EXECUTE ON CTXSYS.CTX_DDL TO bugs;
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="section"
+><H4
+CLASS="section"
+><A
+NAME="AEN531"
+>2.2.2.4.3. Configure the Web Server</A
+></H4
+><P
+>&#13; If you use Apache, append these lines to <TT
+CLASS="filename"
+>httpd.conf</TT
+>
+ to set ORACLE_HOME and LD_LIBRARY_PATH. For instance:
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#13; SetEnv ORACLE_HOME /u01/app/oracle/product/10.2.0/
+ SetEnv LD_LIBRARY_PATH /u01/app/oracle/product/10.2.0/lib/
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>&#13; When this is done, restart your web server.
+ </P
+></DIV
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="AEN537"
+>2.2.3. checksetup.pl</A
+></H2
+><P
+>&#13; Next, rerun <TT
+CLASS="filename"
+>checksetup.pl</TT
+>. It reconfirms
+ that all the modules are present, and notices the altered
+ localconfig file, which it assumes you have edited to your
+ satisfaction. It compiles the UI templates,
+ connects to the database using the 'bugs'
+ user you created and the password you defined, and creates the
+ 'bugs' database and the tables therein.
+ </P
+><P
+>&#13; After that, it asks for details of an administrator account. Bugzilla
+ can have multiple administrators - you can create more later - but
+ it needs one to start off with.
+ Enter the email address of an administrator, his or her full name,
+ and a suitable Bugzilla password.
+ </P
+><P
+>&#13; <TT
+CLASS="filename"
+>checksetup.pl</TT
+> will then finish. You may rerun
+ <TT
+CLASS="filename"
+>checksetup.pl</TT
+> at any time if you wish.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="http"
+>2.2.4. Web server</A
+></H2
+><P
+>&#13; Configure your web server according to the instructions in the
+ appropriate section. (If it makes a difference in your choice,
+ the Bugzilla Team recommends Apache.) To check whether your web server
+ is correctly configured, try to access <TT
+CLASS="filename"
+>testagent.cgi</TT
+>
+ from your web server. If "OK" is displayed, then your configuration
+ is successful. Regardless of which web server
+ you are using, however, ensure that sensitive information is
+ not remotely available by properly applying the access controls in
+ <A
+HREF="security-webserver.html#security-webserver-access"
+>Section 4.2.1</A
+>. You can run
+ <TT
+CLASS="filename"
+>testserver.pl</TT
+> to check if your web server serves
+ Bugzilla files as expected.
+ </P
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="http-apache"
+>2.2.4.1. Bugzilla using Apache</A
+></H3
+><P
+>You have two options for running Bugzilla under Apache -
+ <A
+HREF="configuration.html#http-apache-mod_cgi"
+>mod_cgi</A
+> (the default) and
+ <A
+HREF="configuration.html#http-apache-mod_perl"
+>mod_perl</A
+> (new in Bugzilla
+ 2.23)
+ </P
+><DIV
+CLASS="section"
+><H4
+CLASS="section"
+><A
+NAME="http-apache-mod_cgi"
+>2.2.4.1.1. Apache <SPAN
+CLASS="productname"
+>httpd</SPAN
+> with mod_cgi</A
+></H4
+><P
+>&#13; To configure your Apache web server to work with Bugzilla while using
+ mod_cgi, do the following:
+ </P
+><DIV
+CLASS="procedure"
+><OL
+TYPE="1"
+><LI
+CLASS="step"
+><P
+>&#13; Load <TT
+CLASS="filename"
+>httpd.conf</TT
+> in your editor.
+ In Fedora and Red Hat Linux, this file is found in
+ <TT
+CLASS="filename"
+>/etc/httpd/conf</TT
+>.
+ </P
+></LI
+><LI
+CLASS="step"
+><P
+>&#13; Apache uses <SAMP
+CLASS="computeroutput"
+>&#60;Directory&#62;</SAMP
+>
+ directives to permit fine-grained permission setting. Add the
+ following lines to a directive that applies to the location
+ of your Bugzilla installation. (If such a section does not
+ exist, you'll want to add one.) In this example, Bugzilla has
+ been installed at
+ <TT
+CLASS="filename"
+>/var/www/html/bugzilla</TT
+>.
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#13; &#60;Directory /var/www/html/bugzilla&#62;
+ AddHandler cgi-script .cgi
+ Options +Indexes +ExecCGI
+ DirectoryIndex index.cgi
+ AllowOverride Limit FileInfo Indexes
+ &#60;/Directory&#62;
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>&#13; These instructions: allow apache to run .cgi files found
+ within the bugzilla directory; instructs the server to look
+ for a file called <TT
+CLASS="filename"
+>index.cgi</TT
+> if someone
+ only types the directory name into the browser; and allows
+ Bugzilla's <TT
+CLASS="filename"
+>.htaccess</TT
+> files to override
+ global permissions.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; It is possible to make these changes globally, or to the
+ directive controlling Bugzilla's parent directory (e.g.
+ <SAMP
+CLASS="computeroutput"
+>&#60;Directory /var/www/html/&#62;</SAMP
+>).
+ Such changes would also apply to the Bugzilla directory...
+ but they would also apply to many other places where they
+ may or may not be appropriate. In most cases, including
+ this one, it is better to be as restrictive as possible
+ when granting extra access.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; On Windows, you may have to also add the
+ <SAMP
+CLASS="computeroutput"
+>ScriptInterpreterSource Registry-Strict</SAMP
+>
+ line, see <A
+HREF="os-specific.html#win32-http"
+>Windows specific notes</A
+>.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></LI
+><LI
+CLASS="step"
+><P
+>&#13; <TT
+CLASS="filename"
+>checksetup.pl</TT
+> can set tighter permissions
+ on Bugzilla's files and directories if it knows what group the
+ web server runs as. Find the <SAMP
+CLASS="computeroutput"
+>Group</SAMP
+>
+ line in <TT
+CLASS="filename"
+>httpd.conf</TT
+>, place the value found
+ there in the <TT
+CLASS="replaceable"
+><I
+>$webservergroup</I
+></TT
+> variable
+ in <TT
+CLASS="filename"
+>localconfig</TT
+>, then rerun
+ <TT
+CLASS="filename"
+>checksetup.pl</TT
+>.
+ </P
+></LI
+><LI
+CLASS="step"
+><P
+>&#13; Optional: If Bugzilla does not actually reside in the webspace
+ directory, but instead has been symbolically linked there, you
+ will need to add the following to the
+ <SAMP
+CLASS="computeroutput"
+>Options</SAMP
+> line of the Bugzilla
+ <SAMP
+CLASS="computeroutput"
+>&#60;Directory&#62;</SAMP
+> directive
+ (the same one as in the step above):
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#13; +FollowSymLinks
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>&#13; Without this directive, Apache will not follow symbolic links
+ to places outside its own directory structure, and you will be
+ unable to run Bugzilla.
+ </P
+></LI
+></OL
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><H4
+CLASS="section"
+><A
+NAME="http-apache-mod_perl"
+>2.2.4.1.2. Apache <SPAN
+CLASS="productname"
+>httpd</SPAN
+> with mod_perl</A
+></H4
+><P
+>Some configuration is required to make Bugzilla work with Apache
+ and mod_perl</P
+><DIV
+CLASS="procedure"
+><OL
+TYPE="1"
+><LI
+CLASS="step"
+><P
+>&#13; Load <TT
+CLASS="filename"
+>httpd.conf</TT
+> in your editor.
+ In Fedora and Red Hat Linux, this file is found in
+ <TT
+CLASS="filename"
+>/etc/httpd/conf</TT
+>.
+ </P
+></LI
+><LI
+CLASS="step"
+><P
+>Add the following information to your httpd.conf file, substituting
+ where appropriate with your own local paths.</P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>This should be used instead of the &#60;Directory&#62; block
+ shown above. This should also be above any other <TT
+CLASS="literal"
+>mod_perl</TT
+>
+ directives within the <TT
+CLASS="filename"
+>httpd.conf</TT
+> and must be specified
+ in the order as below.</P
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>You should also ensure that you have disabled <TT
+CLASS="literal"
+>KeepAlive</TT
+>
+ support in your Apache install when utilizing Bugzilla under mod_perl</P
+></TD
+></TR
+></TABLE
+></DIV
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#13; PerlSwitches -w -T
+ PerlConfigRequire /var/www/html/bugzilla/mod_perl.pl
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+></LI
+><LI
+CLASS="step"
+><P
+>&#13; <TT
+CLASS="filename"
+>checksetup.pl</TT
+> can set tighter permissions
+ on Bugzilla's files and directories if it knows what group the
+ web server runs as. Find the <SAMP
+CLASS="computeroutput"
+>Group</SAMP
+>
+ line in <TT
+CLASS="filename"
+>httpd.conf</TT
+>, place the value found
+ there in the <TT
+CLASS="replaceable"
+><I
+>$webservergroup</I
+></TT
+> variable
+ in <TT
+CLASS="filename"
+>localconfig</TT
+>, then rerun
+ <TT
+CLASS="filename"
+>checksetup.pl</TT
+>.
+ </P
+></LI
+></OL
+></DIV
+><P
+>On restarting Apache, Bugzilla should now be running within the
+ mod_perl environment. Please ensure you have run checksetup.pl to set
+ permissions before you restart Apache.</P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>Please bear the following points in mind when looking at using
+ Bugzilla under mod_perl:
+ <P
+></P
+><UL
+><LI
+><P
+>&#13; mod_perl support in Bugzilla can take up a HUGE amount of RAM. You could be
+ looking at 30MB per httpd child, easily. Basically, you just need a lot of RAM.
+ The more RAM you can get, the better. mod_perl is basically trading RAM for
+ speed. At least 2GB total system RAM is recommended for running Bugzilla under
+ mod_perl.
+ </P
+></LI
+><LI
+><P
+>&#13; Under mod_perl, you have to restart Apache if you make any manual change to
+ any Bugzilla file. You can't just reload--you have to actually
+ <EM
+>restart</EM
+> the server (as in make sure it stops and starts
+ again). You <EM
+>can</EM
+> change localconfig and the params file
+ manually, if you want, because those are re-read every time you load a page.
+ </P
+></LI
+><LI
+><P
+>&#13; You must run in Apache's Prefork MPM (this is the default). The Worker MPM
+ may not work--we haven't tested Bugzilla's mod_perl support under threads.
+ (And, in fact, we're fairly sure it <EM
+>won't</EM
+> work.)
+ </P
+></LI
+><LI
+><P
+>&#13; Bugzilla generally expects to be the only mod_perl application running on
+ your entire server. It may or may not work if there are other applications also
+ running under mod_perl. It does try its best to play nice with other mod_perl
+ applications, but it still may have conflicts.
+ </P
+></LI
+><LI
+><P
+>&#13; It is recommended that you have one Bugzilla instance running under mod_perl
+ on your server. Bugzilla has not been tested with more than one instance running.
+ </P
+></LI
+></UL
+>
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="http-iis"
+>2.2.4.2. Microsoft <SPAN
+CLASS="productname"
+>Internet Information Services</SPAN
+></A
+></H3
+><P
+>&#13; If you are running Bugzilla on Windows and choose to use
+ Microsoft's <SPAN
+CLASS="productname"
+>Internet Information Services</SPAN
+>
+ or <SPAN
+CLASS="productname"
+>Personal Web Server</SPAN
+> you will need
+ to perform a number of other configuration steps as explained below.
+ You may also want to refer to the following Microsoft Knowledge
+ Base articles:
+ <A
+HREF="http://support.microsoft.com/default.aspx?scid=kb;en-us;245225"
+TARGET="_top"
+>245225</A
+>
+ <SPAN
+CLASS="QUOTE"
+>"HOW TO: Configure and Test a PERL Script with IIS 4.0,
+ 5.0, and 5.1"</SPAN
+> (for <SPAN
+CLASS="productname"
+>Internet Information
+ Services</SPAN
+>) and
+ <A
+HREF="http://support.microsoft.com/default.aspx?scid=kb;en-us;231998"
+TARGET="_top"
+>231998</A
+>
+ <SPAN
+CLASS="QUOTE"
+>"HOW TO: FP2000: How to Use Perl with Microsoft Personal Web
+ Server on Windows 95/98"</SPAN
+> (for <SPAN
+CLASS="productname"
+>Personal Web
+ Server</SPAN
+>).
+ </P
+><P
+>&#13; You will need to create a virtual directory for the Bugzilla
+ install. Put the Bugzilla files in a directory that is named
+ something <EM
+>other</EM
+> than what you want your
+ end-users accessing. That is, if you want your users to access
+ your Bugzilla installation through
+ <SPAN
+CLASS="QUOTE"
+>"http://&#60;yourdomainname&#62;/Bugzilla"</SPAN
+>, then do
+ <EM
+>not</EM
+> put your Bugzilla files in a directory
+ named <SPAN
+CLASS="QUOTE"
+>"Bugzilla"</SPAN
+>. Instead, place them in a different
+ location, and then use the IIS Administration tool to create a
+ Virtual Directory named "Bugzilla" that acts as an alias for the
+ actual location of the files. When creating that virtual directory,
+ make sure you add the <SPAN
+CLASS="QUOTE"
+>"Execute (such as ISAPI applications or
+ CGI)"</SPAN
+> access permission.
+ </P
+><P
+>&#13; You will also need to tell IIS how to handle Bugzilla's
+ .cgi files. Using the IIS Administration tool again, open up
+ the properties for the new virtual directory and select the
+ Configuration option to access the Script Mappings. Create an
+ entry mapping .cgi to:
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#13;&#60;full path to perl.exe &#62;\perl.exe -x&#60;full path to Bugzilla&#62; -wT "%s" %s
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>&#13; For example:
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#13;c:\perl\bin\perl.exe -xc:\bugzilla -wT "%s" %s
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; The ActiveState install may have already created an entry for
+ .pl files that is limited to <SPAN
+CLASS="QUOTE"
+>"GET,HEAD,POST"</SPAN
+>. If
+ so, this mapping should be <EM
+>removed</EM
+> as
+ Bugzilla's .pl files are not designed to be run via a web server.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; IIS will also need to know that the index.cgi should be treated
+ as a default document. On the Documents tab page of the virtual
+ directory properties, you need to add index.cgi as a default
+ document type. If you wish, you may remove the other default
+ document types for this particular virtual directory, since Bugzilla
+ doesn't use any of them.
+ </P
+><P
+>&#13; Also, and this can't be stressed enough, make sure that files
+ such as <TT
+CLASS="filename"
+>localconfig</TT
+> and your
+ <TT
+CLASS="filename"
+>data</TT
+> directory are
+ secured as described in <A
+HREF="security-webserver.html#security-webserver-access"
+>Section 4.2.1</A
+>.
+ </P
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="install-config-bugzilla"
+>2.2.5. Bugzilla</A
+></H2
+><P
+>&#13; Your Bugzilla should now be working. Access
+ <TT
+CLASS="filename"
+>http://&#60;your-bugzilla-server&#62;/</TT
+> -
+ you should see the Bugzilla
+ front page. If not, consult the Troubleshooting section,
+ <A
+HREF="troubleshooting.html"
+>Appendix A</A
+>.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; The URL above may be incorrect if you installed Bugzilla into a
+ subdirectory or used a symbolic link from your web site root to
+ the Bugzilla directory.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; Log in with the administrator account you defined in the last
+ <TT
+CLASS="filename"
+>checksetup.pl</TT
+> run. You should go through
+ the Parameters page and see if there are any you wish to change.
+ They key parameters are documented in <A
+HREF="parameters.html"
+>Section 3.1</A
+>;
+ you should certainly alter
+ <B
+CLASS="command"
+>maintainer</B
+> and <B
+CLASS="command"
+>urlbase</B
+>;
+ you may also want to alter
+ <B
+CLASS="command"
+>cookiepath</B
+> or <B
+CLASS="command"
+>requirelogin</B
+>.
+ </P
+><P
+>&#13; Bugzilla has several optional features which require extra
+ configuration. You can read about those in
+ <A
+HREF="extraconfig.html"
+>Section 2.3</A
+>.
+ </P
+></DIV
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="installation.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="extraconfig.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Installation</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="installing-bugzilla.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Optional Additional Configuration</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/conventions.html b/docs/en/html/conventions.html
new file mode 100644
index 000000000..eddd2b64d
--- /dev/null
+++ b/docs/en/html/conventions.html
@@ -0,0 +1,416 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Document Conventions</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="About This Guide"
+HREF="about.html"><LINK
+REL="PREVIOUS"
+TITLE="Credits"
+HREF="credits.html"><LINK
+REL="NEXT"
+TITLE="Installing Bugzilla"
+HREF="installing-bugzilla.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="credits.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 1. About This Guide</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="installing-bugzilla.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="conventions"
+>1.5. Document Conventions</A
+></H1
+><P
+>This document uses the following conventions:</P
+><DIV
+CLASS="informaltable"
+><P
+></P
+><A
+NAME="AEN103"
+></A
+><TABLE
+BORDER="0"
+FRAME="void"
+CLASS="CALSTABLE"
+><COL><COL><THEAD
+><TR
+><TH
+>Descriptions</TH
+><TH
+>Appearance</TH
+></TR
+></THEAD
+><TBODY
+><TR
+><TD
+>Caution</TD
+><TD
+>&#13; <DIV
+CLASS="caution"
+><P
+></P
+><TABLE
+CLASS="caution"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/caution.gif"
+HSPACE="5"
+ALT="Caution"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>Don't run with scissors!</P
+></TD
+></TR
+></TABLE
+></DIV
+>
+ </TD
+></TR
+><TR
+><TD
+>Hint or Tip</TD
+><TD
+>&#13; <DIV
+CLASS="tip"
+><P
+></P
+><TABLE
+CLASS="tip"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/tip.gif"
+HSPACE="5"
+ALT="Tip"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>For best results... </P
+></TD
+></TR
+></TABLE
+></DIV
+>
+ </TD
+></TR
+><TR
+><TD
+>Note</TD
+><TD
+>&#13; <DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>Dear John...</P
+></TD
+></TR
+></TABLE
+></DIV
+>
+ </TD
+></TR
+><TR
+><TD
+>Warning</TD
+><TD
+>&#13; <DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>Read this or the cat gets it.</P
+></TD
+></TR
+></TABLE
+></DIV
+>
+ </TD
+></TR
+><TR
+><TD
+>File or directory name</TD
+><TD
+>&#13; <TT
+CLASS="filename"
+>filename</TT
+>
+ </TD
+></TR
+><TR
+><TD
+>Command to be typed</TD
+><TD
+>&#13; <B
+CLASS="command"
+>command</B
+>
+ </TD
+></TR
+><TR
+><TD
+>Application name</TD
+><TD
+>&#13; <SPAN
+CLASS="application"
+>application</SPAN
+>
+ </TD
+></TR
+><TR
+><TD
+>&#13; Normal user's prompt under bash shell</TD
+><TD
+>bash$</TD
+></TR
+><TR
+><TD
+>&#13; Root user's prompt under bash shell</TD
+><TD
+>bash#</TD
+></TR
+><TR
+><TD
+>&#13; Normal user's prompt under tcsh shell</TD
+><TD
+>tcsh$</TD
+></TR
+><TR
+><TD
+>Environment variables</TD
+><TD
+>&#13; <CODE
+CLASS="envar"
+>VARIABLE</CODE
+>
+ </TD
+></TR
+><TR
+><TD
+>Term found in the glossary</TD
+><TD
+>&#13; <A
+HREF="glossary.html#gloss-bugzilla"
+><I
+CLASS="glossterm"
+>Bugzilla</I
+></A
+>
+ </TD
+></TR
+><TR
+><TD
+>Code example</TD
+><TD
+>&#13; <TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+><CODE
+CLASS="sgmltag"
+>&#60;para&#62;</CODE
+>
+Beginning and end of paragraph
+<CODE
+CLASS="sgmltag"
+>&#60;/para&#62;</CODE
+></PRE
+></FONT
+></TD
+></TR
+></TABLE
+>
+ </TD
+></TR
+></TBODY
+></TABLE
+><P
+></P
+></DIV
+><P
+>
+ This documentation is maintained in DocBook 4.1.2 XML format.
+ Changes are best submitted as plain text or XML diffs, attached
+ to a bug filed in the <A
+HREF="https://bugzilla.mozilla.org/enter_bug.cgi?product=Bugzilla&#38;component=Documentation"
+TARGET="_top"
+>Bugzilla Documentation</A
+> component.
+ </P
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="credits.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="installing-bugzilla.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Credits</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="about.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Installing Bugzilla</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/copyright.html b/docs/en/html/copyright.html
new file mode 100644
index 000000000..b9676abc0
--- /dev/null
+++ b/docs/en/html/copyright.html
@@ -0,0 +1,171 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Copyright Information</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="About This Guide"
+HREF="about.html"><LINK
+REL="PREVIOUS"
+TITLE="About This Guide"
+HREF="about.html"><LINK
+REL="NEXT"
+TITLE="Disclaimer"
+HREF="disclaimer.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="about.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 1. About This Guide</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="disclaimer.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="copyright"
+>1.1. Copyright Information</A
+></H1
+><P
+>This document is copyright (c) 2000-2011 by the various
+ Bugzilla contributors who wrote it.</P
+><A
+NAME="AEN26"
+></A
+><BLOCKQUOTE
+CLASS="BLOCKQUOTE"
+><P
+>&#13; Permission is granted to copy, distribute and/or modify this
+ document under the terms of the GNU Free Documentation
+ License, Version 1.1 or any later version published by the
+ Free Software Foundation; with no Invariant Sections, no
+ Front-Cover Texts, and with no Back-Cover Texts. A copy of
+ the license is included in <A
+HREF="gfdl.html"
+>Appendix D</A
+>.
+ </P
+></BLOCKQUOTE
+><P
+>&#13; If you have any questions regarding this document, its
+ copyright, or publishing this document in non-electronic form,
+ please contact the Bugzilla Team.
+ </P
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="about.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="disclaimer.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>About This Guide</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="about.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Disclaimer</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/credits.html b/docs/en/html/credits.html
new file mode 100644
index 000000000..c6bf14353
--- /dev/null
+++ b/docs/en/html/credits.html
@@ -0,0 +1,273 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Credits</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="About This Guide"
+HREF="about.html"><LINK
+REL="PREVIOUS"
+TITLE="New Versions"
+HREF="newversions.html"><LINK
+REL="NEXT"
+TITLE="Document Conventions"
+HREF="conventions.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="newversions.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 1. About This Guide</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="conventions.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="credits"
+>1.4. Credits</A
+></H1
+><P
+>&#13; The people listed below have made enormous contributions to the
+ creation of this Guide, through their writing, dedicated hacking efforts,
+ numerous e-mail and IRC support sessions, and overall excellent
+ contribution to the Bugzilla community:
+ </P
+><P
+></P
+><DIV
+CLASS="variablelist"
+><DL
+><DT
+>Matthew P. Barnson <CODE
+CLASS="email"
+>&#60;<A
+HREF="mailto:mbarnson@sisna.com"
+>mbarnson@sisna.com</A
+>&#62;</CODE
+></DT
+><DD
+><P
+>for the Herculean task of pulling together the Bugzilla Guide
+ and shepherding it to 2.14.
+ </P
+></DD
+><DT
+>Terry Weissman <CODE
+CLASS="email"
+>&#60;<A
+HREF="mailto:terry@mozilla.org"
+>terry@mozilla.org</A
+>&#62;</CODE
+></DT
+><DD
+><P
+>for initially writing Bugzilla and creating the README upon
+ which the UNIX installation documentation is largely based.
+ </P
+></DD
+><DT
+>Tara Hernandez <CODE
+CLASS="email"
+>&#60;<A
+HREF="mailto:tara@tequilarists.org"
+>tara@tequilarists.org</A
+>&#62;</CODE
+></DT
+><DD
+><P
+>for keeping Bugzilla development going strong after Terry left
+ mozilla.org and for running landfill.
+ </P
+></DD
+><DT
+>Dave Lawrence <CODE
+CLASS="email"
+>&#60;<A
+HREF="mailto:dkl@redhat.com"
+>dkl@redhat.com</A
+>&#62;</CODE
+></DT
+><DD
+><P
+>for providing insight into the key differences between Red
+ Hat's customized Bugzilla.
+ </P
+></DD
+><DT
+>Dawn Endico <CODE
+CLASS="email"
+>&#60;<A
+HREF="mailto:endico@mozilla.org"
+>endico@mozilla.org</A
+>&#62;</CODE
+></DT
+><DD
+><P
+>for being a hacker extraordinaire and putting up with Matthew's
+ incessant questions and arguments on irc.mozilla.org in #mozwebtools
+ </P
+></DD
+><DT
+>Jacob Steenhagen <CODE
+CLASS="email"
+>&#60;<A
+HREF="mailto:jake@bugzilla.org"
+>jake@bugzilla.org</A
+>&#62;</CODE
+></DT
+><DD
+><P
+>for taking over documentation during the 2.17 development
+ period.
+ </P
+></DD
+><DT
+>Dave Miller <CODE
+CLASS="email"
+>&#60;<A
+HREF="mailto:justdave@bugzilla.org"
+>justdave@bugzilla.org</A
+>&#62;</CODE
+></DT
+><DD
+><P
+>for taking over as project lead when Tara stepped down and
+ continually pushing for the documentation to be the best it can be.
+ </P
+></DD
+></DL
+></DIV
+><P
+>&#13; Thanks also go to the following people for significant contributions
+ to this documentation:
+ Kevin Brannen, Vlad Dascalu, Ben FrantzDale, Eric Hanson, Zach Lipton, Gervase Markham, Andrew Pearson, Joe Robins, Spencer Smith, Ron Teitelbaum, Shane Travis, Martin Wulffeld.
+ </P
+><P
+>&#13; Also, thanks are due to the members of the
+ <A
+HREF="news://news.mozilla.org/mozilla.support.bugzilla"
+TARGET="_top"
+>&#13; mozilla.support.bugzilla</A
+>
+ newsgroup (and its predecessor, netscape.public.mozilla.webtools).
+ Without your discussions, insight, suggestions, and patches,
+ this could never have happened.
+ </P
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="newversions.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="conventions.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>New Versions</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="about.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Document Conventions</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/cust-change-permissions.html b/docs/en/html/cust-change-permissions.html
new file mode 100644
index 000000000..ade718ca5
--- /dev/null
+++ b/docs/en/html/cust-change-permissions.html
@@ -0,0 +1,360 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Customizing Who Can Change What</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Customizing Bugzilla"
+HREF="customization.html"><LINK
+REL="PREVIOUS"
+TITLE="Template Customization"
+HREF="cust-templates.html"><LINK
+REL="NEXT"
+TITLE="Integrating Bugzilla with Third-Party Tools"
+HREF="integration.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="cust-templates.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 6. Customizing Bugzilla</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="integration.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="cust-change-permissions"
+>6.4. Customizing Who Can Change What</A
+></H1
+><DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; This feature should be considered experimental; the Bugzilla code you
+ will be changing is not stable, and could change or move between
+ versions. Be aware that if you make modifications as outlined here,
+ you may have
+ to re-make them or port them if Bugzilla changes internally between
+ versions, and you upgrade.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; Companies often have rules about which employees, or classes of employees,
+ are allowed to change certain things in the bug system. For example,
+ only the bug's designated QA Contact may be allowed to VERIFY the bug.
+ Bugzilla has been
+ designed to make it easy for you to write your own custom rules to define
+ who is allowed to make what sorts of value transition.
+ </P
+><P
+>&#13; By default, assignees, QA owners and users
+ with <EM
+>editbugs</EM
+> privileges can edit all fields of bugs,
+ except group restrictions (unless they are members of the groups they
+ are trying to change). Bug reporters also have the ability to edit some
+ fields, but in a more restrictive manner. Other users, without
+ <EM
+>editbugs</EM
+> privileges, can not edit
+ bugs, except to comment and add themselves to the CC list.
+ </P
+><P
+>&#13; For maximum flexibility, customizing this means editing Bugzilla's Perl
+ code. This gives the administrator complete control over exactly who is
+ allowed to do what. The relevant method is called
+ <TT
+CLASS="filename"
+>check_can_change_field()</TT
+>,
+ and is found in <TT
+CLASS="filename"
+>Bug.pm</TT
+> in your
+ Bugzilla/ directory. If you open that file and search for
+ <SPAN
+CLASS="QUOTE"
+>"sub check_can_change_field"</SPAN
+>, you'll find it.
+ </P
+><P
+>&#13; This function has been carefully commented to allow you to see exactly
+ how it works, and give you an idea of how to make changes to it.
+ Certain marked sections should not be changed - these are
+ the <SPAN
+CLASS="QUOTE"
+>"plumbing"</SPAN
+> which makes the rest of the function work.
+ In between those sections, you'll find snippets of code like:
+ <TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+> # Allow the assignee to change anything.
+ if ($ownerid eq $whoid) {
+ return 1;
+ }</PRE
+></FONT
+></TD
+></TR
+></TABLE
+>
+ It's fairly obvious what this piece of code does.
+ </P
+><P
+>&#13; So, how does one go about changing this function? Well, simple changes
+ can be made just by removing pieces - for example, if you wanted to
+ prevent any user adding a comment to a bug, just remove the lines marked
+ <SPAN
+CLASS="QUOTE"
+>"Allow anyone to change comments."</SPAN
+> If you don't want the
+ Reporter to have any special rights on bugs they have filed, just
+ remove the entire section that deals with the Reporter.
+ </P
+><P
+>&#13; More complex customizations are not much harder. Basically, you add
+ a check in the right place in the function, i.e. after all the variables
+ you are using have been set up. So, don't look at $ownerid before
+ $ownerid has been obtained from the database. You can either add a
+ positive check, which returns 1 (allow) if certain conditions are true,
+ or a negative check, which returns 0 (deny.) E.g.:
+ <TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+> if ($field eq "qacontact") {
+ if (Bugzilla-&#62;user-&#62;in_group("quality_assurance")) {
+ return 1;
+ }
+ else {
+ return 0;
+ }
+ }</PRE
+></FONT
+></TD
+></TR
+></TABLE
+>
+ This says that only users in the group "quality_assurance" can change
+ the QA Contact field of a bug.
+ </P
+><P
+>&#13; Getting more weird:
+ <TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+> if (($field eq "priority") &#38;&#38;
+ (Bugzilla-&#62;user-&#62;email =~ /.*\@example\.com$/))
+ {
+ if ($oldvalue eq "P1") {
+ return 1;
+ }
+ else {
+ return 0;
+ }
+ }</PRE
+></FONT
+></TD
+></TR
+></TABLE
+>
+ This says that if the user is trying to change the priority field,
+ and their email address is @example.com, they can only do so if the
+ old value of the field was "P1". Not very useful, but illustrative.
+ </P
+><DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; If you are modifying <TT
+CLASS="filename"
+>process_bug.cgi</TT
+> in any
+ way, do not change the code that is bounded by DO_NOT_CHANGE blocks.
+ Doing so could compromise security, or cause your installation to
+ stop working entirely.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; For a list of possible field names, look at the bugs table in the
+ database. If you need help writing custom rules for your organization,
+ ask in the newsgroup.
+ </P
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="cust-templates.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="integration.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Template Customization</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="customization.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Integrating Bugzilla with Third-Party Tools</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/cust-skins.html b/docs/en/html/cust-skins.html
new file mode 100644
index 000000000..1c8f4dc70
--- /dev/null
+++ b/docs/en/html/cust-skins.html
@@ -0,0 +1,188 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Custom Skins</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Customizing Bugzilla"
+HREF="customization.html"><LINK
+REL="PREVIOUS"
+TITLE="Bugzilla Extensions"
+HREF="extensions.html"><LINK
+REL="NEXT"
+TITLE="Template Customization"
+HREF="cust-templates.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="extensions.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 6. Customizing Bugzilla</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="cust-templates.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="cust-skins"
+>6.2. Custom Skins</A
+></H1
+><P
+>&#13; Bugzilla allows you to have multiple skins. These are custom CSS and possibly
+ also custom images for Bugzilla. To create a new custom skin, you have two
+ choices:
+ <P
+></P
+><UL
+><LI
+><P
+>&#13; Make a single CSS file, and put it in the
+ <TT
+CLASS="filename"
+>skins/contrib</TT
+> directory.
+ </P
+></LI
+><LI
+><P
+>&#13; Make a directory that contains all the same CSS file
+ names as <TT
+CLASS="filename"
+>skins/standard/</TT
+>, and put
+ your directory in <TT
+CLASS="filename"
+>skins/contrib/</TT
+>.
+ </P
+></LI
+></UL
+>
+ </P
+><P
+>&#13; After you put the file or the directory there, make sure to run checksetup.pl
+ so that it can reset the file permissions correctly.
+ </P
+><P
+>&#13; After you have installed the new skin, it will show up as an option in the
+ user's General Preferences. If you would like to force a particular skin on all
+ users, just select it in the Default Preferences and then uncheck "Enabled" on
+ the preference.
+ </P
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="extensions.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="cust-templates.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Bugzilla Extensions</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="customization.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Template Customization</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/cust-templates.html b/docs/en/html/cust-templates.html
new file mode 100644
index 000000000..e7a19e148
--- /dev/null
+++ b/docs/en/html/cust-templates.html
@@ -0,0 +1,916 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Template Customization</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Customizing Bugzilla"
+HREF="customization.html"><LINK
+REL="PREVIOUS"
+TITLE="Custom Skins"
+HREF="cust-skins.html"><LINK
+REL="NEXT"
+TITLE="Customizing Who Can Change What"
+HREF="cust-change-permissions.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="cust-skins.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 6. Customizing Bugzilla</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="cust-change-permissions.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="cust-templates"
+>6.3. Template Customization</A
+></H1
+><P
+>&#13; Administrators can configure the look and feel of Bugzilla without
+ having to edit Perl files or face the nightmare of massive merge
+ conflicts when they upgrade to a newer version in the future.
+ </P
+><P
+>&#13; Templatization also makes localized versions of Bugzilla possible,
+ for the first time. It's possible to have Bugzilla's UI language
+ determined by the user's browser. More information is available in
+ <A
+HREF="cust-templates.html#template-http-accept"
+>Section 6.3.6</A
+>.
+ </P
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="template-directory"
+>6.3.1. Template Directory Structure</A
+></H2
+><P
+>&#13; The template directory structure starts with top level directory
+ named <TT
+CLASS="filename"
+>template</TT
+>, which contains a directory
+ for each installed localization. The next level defines the
+ language used in the templates. Bugzilla comes with English
+ templates, so the directory name is <TT
+CLASS="filename"
+>en</TT
+>,
+ and we will discuss <TT
+CLASS="filename"
+>template/en</TT
+> throughout
+ the documentation. Below <TT
+CLASS="filename"
+>template/en</TT
+> is the
+ <TT
+CLASS="filename"
+>default</TT
+> directory, which contains all the
+ standard templates shipped with Bugzilla.
+ </P
+><DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; A directory <TT
+CLASS="filename"
+>data/templates</TT
+> also exists;
+ this is where Template Toolkit puts the compiled versions of
+ the templates from either the default or custom directories.
+ <EM
+>Do not</EM
+> directly edit the files in this
+ directory, or all your changes will be lost the next time
+ Template Toolkit recompiles the templates.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="template-method"
+>6.3.2. Choosing a Customization Method</A
+></H2
+><P
+>&#13; If you want to edit Bugzilla's templates, the first decision
+ you must make is how you want to go about doing so. There are two
+ choices, and which you use depends mainly on the scope of your
+ modifications, and the method you plan to use to upgrade Bugzilla.
+ </P
+><P
+>&#13; The first method of making customizations is to directly edit the
+ templates found in <TT
+CLASS="filename"
+>template/en/default</TT
+>.
+ This is probably the best way to go about it if you are going to
+ be upgrading Bugzilla through CVS, because if you then execute
+ a <B
+CLASS="command"
+>cvs update</B
+>, any changes you have made will
+ be merged automagically with the updated versions.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; If you use this method, and CVS conflicts occur during an
+ update, the conflicted templates (and possibly other parts
+ of your installation) will not work until they are resolved.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; The second method is to copy the templates to be modified
+ into a mirrored directory structure under
+ <TT
+CLASS="filename"
+>template/en/custom</TT
+>. Templates in this
+ directory structure automatically override any identically-named
+ and identically-located templates in the
+ <TT
+CLASS="filename"
+>default</TT
+> directory.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; The <TT
+CLASS="filename"
+>custom</TT
+> directory does not exist
+ at first and must be created if you want to use it.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; The second method of customization should be used if you
+ use the overwriting method of upgrade, because otherwise
+ your changes will be lost. This method may also be better if
+ you are using the CVS method of upgrading and are going to make major
+ changes, because it is guaranteed that the contents of this directory
+ will not be touched during an upgrade, and you can then decide whether
+ to continue using your own templates, or make the effort to merge your
+ changes into the new versions by hand.
+ </P
+><P
+>&#13; Using this method, your installation may break if incompatible
+ changes are made to the template interface. Such changes should
+ be documented in the release notes, provided you are using a
+ stable release of Bugzilla. If you use using unstable code, you will
+ need to deal with this one yourself, although if possible the changes
+ will be mentioned before they occur in the deprecations section of the
+ previous stable release's release notes.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Regardless of which method you choose, it is recommended that
+ you run <B
+CLASS="command"
+>./checksetup.pl</B
+> after
+ editing any templates in the <TT
+CLASS="filename"
+>template/en/default</TT
+>
+ directory, and after creating or editing any templates in the
+ <TT
+CLASS="filename"
+>custom</TT
+> directory.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; It is <EM
+>required</EM
+> that you run
+ <B
+CLASS="command"
+>./checksetup.pl</B
+> after creating a new
+ template in the <TT
+CLASS="filename"
+>custom</TT
+> directory. Failure
+ to do so will raise an incomprehensible error message.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="template-edit"
+>6.3.3. How To Edit Templates</A
+></H2
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; If you are making template changes that you intend on submitting back
+ for inclusion in standard Bugzilla, you should read the relevant
+ sections of the
+ <A
+HREF="http://www.bugzilla.org/docs/developer.html"
+TARGET="_top"
+>Developers'
+ Guide</A
+>.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; The syntax of the Template Toolkit language is beyond the scope of
+ this guide. It's reasonably easy to pick up by looking at the current
+ templates; or, you can read the manual, available on the
+ <A
+HREF="http://www.template-toolkit.org"
+TARGET="_top"
+>Template Toolkit home
+ page</A
+>.
+ </P
+><P
+>&#13; One thing you should take particular care about is the need
+ to properly HTML filter data that has been passed into the template.
+ This means that if the data can possibly contain special HTML characters
+ such as &#60;, and the data was not intended to be HTML, they need to be
+ converted to entity form, i.e. &#38;lt;. You use the 'html' filter in the
+ Template Toolkit to do this. If you forget, you may open up
+ your installation to cross-site scripting attacks.
+ </P
+><P
+>&#13; Also note that Bugzilla adds a few filters of its own, that are not
+ in standard Template Toolkit. In particular, the 'url_quote' filter
+ can convert characters that are illegal or have special meaning in URLs,
+ such as &#38;, to the encoded form, i.e. %26. This actually encodes most
+ characters (but not the common ones such as letters and numbers and so
+ on), including the HTML-special characters, so there's never a need to
+ HTML filter afterwards.
+ </P
+><P
+>&#13; Editing templates is a good way of doing a <SPAN
+CLASS="QUOTE"
+>"poor man's custom
+ fields"</SPAN
+>.
+ For example, if you don't use the Status Whiteboard, but want to have
+ a free-form text entry box for <SPAN
+CLASS="QUOTE"
+>"Build Identifier"</SPAN
+>,
+ then you can just
+ edit the templates to change the field labels. It's still be called
+ status_whiteboard internally, but your users don't need to know that.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="template-formats"
+>6.3.4. Template Formats and Types</A
+></H2
+><P
+>&#13; Some CGI's have the ability to use more than one template. For example,
+ <TT
+CLASS="filename"
+>buglist.cgi</TT
+> can output itself as RDF, or as two
+ formats of HTML (complex and simple). The mechanism that provides this
+ feature is extensible.
+ </P
+><P
+>&#13; Bugzilla can support different types of output, which again can have
+ multiple formats. In order to request a certain type, you can append
+ the &#38;ctype=&#60;contenttype&#62; (such as rdf or html) to the
+ <TT
+CLASS="filename"
+>&#60;cginame&#62;.cgi</TT
+> URL. If you would like to
+ retrieve a certain format, you can use the &#38;format=&#60;format&#62;
+ (such as simple or complex) in the URL.
+ </P
+><P
+>&#13; To see if a CGI supports multiple output formats and types, grep the
+ CGI for <SPAN
+CLASS="QUOTE"
+>"get_format"</SPAN
+>. If it's not present, adding
+ multiple format/type support isn't too hard - see how it's done in
+ other CGIs, e.g. config.cgi.
+ </P
+><P
+>&#13; To make a new format template for a CGI which supports this,
+ open a current template for
+ that CGI and take note of the INTERFACE comment (if present.) This
+ comment defines what variables are passed into this template. If
+ there isn't one, I'm afraid you'll have to read the template and
+ the code to find out what information you get.
+ </P
+><P
+>&#13; Write your template in whatever markup or text style is appropriate.
+ </P
+><P
+>&#13; You now need to decide what content type you want your template
+ served as. The content types are defined in the
+ <TT
+CLASS="filename"
+>Bugzilla/Constants.pm</TT
+> file in the
+ <TT
+CLASS="filename"
+>contenttypes</TT
+>
+ constant. If your content type is not there, add it. Remember
+ the three- or four-letter tag assigned to your content type.
+ This tag will be part of the template filename.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; After adding or changing a content type, it's suitable to edit
+ <TT
+CLASS="filename"
+>Bugzilla/Constants.pm</TT
+> in order to reflect
+ the changes. Also, the file should be kept up to date after an
+ upgrade if content types have been customized in the past.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; Save the template as <TT
+CLASS="filename"
+>&#60;stubname&#62;-&#60;formatname&#62;.&#60;contenttypetag&#62;.tmpl</TT
+>.
+ Try out the template by calling the CGI as
+ <TT
+CLASS="filename"
+>&#60;cginame&#62;.cgi?format=&#60;formatname&#62;&#38;ctype=&#60;type&#62;</TT
+> .
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="template-specific"
+>6.3.5. Particular Templates</A
+></H2
+><P
+>&#13; There are a few templates you may be particularly interested in
+ customizing for your installation.
+ </P
+><P
+>&#13; <B
+CLASS="command"
+>index.html.tmpl</B
+>:
+ This is the Bugzilla front page.
+ </P
+><P
+>&#13; <B
+CLASS="command"
+>global/header.html.tmpl</B
+>:
+ This defines the header that goes on all Bugzilla pages.
+ The header includes the banner, which is what appears to users
+ and is probably what you want to edit instead. However the
+ header also includes the HTML HEAD section, so you could for
+ example add a stylesheet or META tag by editing the header.
+ </P
+><P
+>&#13; <B
+CLASS="command"
+>global/banner.html.tmpl</B
+>:
+ This contains the <SPAN
+CLASS="QUOTE"
+>"banner"</SPAN
+>, the part of the header
+ that appears
+ at the top of all Bugzilla pages. The default banner is reasonably
+ barren, so you'll probably want to customize this to give your
+ installation a distinctive look and feel. It is recommended you
+ preserve the Bugzilla version number in some form so the version
+ you are running can be determined, and users know what docs to read.
+ </P
+><P
+>&#13; <B
+CLASS="command"
+>global/footer.html.tmpl</B
+>:
+ This defines the footer that goes on all Bugzilla pages. Editing
+ this is another way to quickly get a distinctive look and feel for
+ your Bugzilla installation.
+ </P
+><P
+>&#13; <B
+CLASS="command"
+>global/variables.none.tmpl</B
+>:
+ This defines a list of terms that may be changed in order to
+ <SPAN
+CLASS="QUOTE"
+>"brand"</SPAN
+> the Bugzilla instance In this way, terms
+ like <SPAN
+CLASS="QUOTE"
+>"bugs"</SPAN
+> can be replaced with <SPAN
+CLASS="QUOTE"
+>"issues"</SPAN
+>
+ across the whole Bugzilla installation. The name
+ <SPAN
+CLASS="QUOTE"
+>"Bugzilla"</SPAN
+> and other words can be customized as well.
+ </P
+><P
+>&#13; <B
+CLASS="command"
+>list/table.html.tmpl</B
+>:
+ This template controls the appearance of the bug lists created
+ by Bugzilla. Editing this template allows per-column control of
+ the width and title of a column, the maximum display length of
+ each entry, and the wrap behaviour of long entries.
+ For long bug lists, Bugzilla inserts a 'break' every 100 bugs by
+ default; this behaviour is also controlled by this template, and
+ that value can be modified here.
+ </P
+><P
+>&#13; <B
+CLASS="command"
+>bug/create/user-message.html.tmpl</B
+>:
+ This is a message that appears near the top of the bug reporting page.
+ By modifying this, you can tell your users how they should report
+ bugs.
+ </P
+><P
+>&#13; <B
+CLASS="command"
+>bug/process/midair.html.tmpl</B
+>:
+ This is the page used if two people submit simultaneous changes to the
+ same bug. The second person to submit their changes will get this page
+ to tell them what the first person did, and ask if they wish to
+ overwrite those changes or go back and revisit the bug. The default
+ title and header on this page read "Mid-air collision detected!" If
+ you work in the aviation industry, or other environment where this
+ might be found offensive (yes, we have true stories of this happening)
+ you'll want to change this to something more appropriate for your
+ environment.
+ </P
+><P
+>&#13; <B
+CLASS="command"
+>bug/create/create.html.tmpl</B
+> and
+ <B
+CLASS="command"
+>bug/create/comment.txt.tmpl</B
+>:
+ You may not wish to go to the effort of creating custom fields in
+ Bugzilla, yet you want to make sure that each bug report contains
+ a number of pieces of important information for which there is not
+ a special field. The bug entry system has been designed in an
+ extensible fashion to enable you to add arbitrary HTML widgets,
+ such as drop-down lists or textboxes, to the bug entry page
+ and have their values appear formatted in the initial comment.
+ A hidden field that indicates the format should be added inside
+ the form in order to make the template functional. Its value should
+ be the suffix of the template filename. For example, if the file
+ is called <TT
+CLASS="filename"
+>create-cust.html.tmpl</TT
+>, then
+ <TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#60;input type="hidden" name="format" value="cust"&#62;</PRE
+></FONT
+></TD
+></TR
+></TABLE
+>
+ should be used inside the form.
+ </P
+><P
+>
+ An example of this is the mozilla.org
+ <A
+HREF="http://landfill.bugzilla.org/bugzilla-tip/enter_bug.cgi?product=WorldControl&#38;format=guided"
+TARGET="_top"
+>guided
+ bug submission form</A
+>. The code for this comes with the Bugzilla
+ distribution as an example for you to copy. It can be found in the
+ files
+ <TT
+CLASS="filename"
+>create-guided.html.tmpl</TT
+> and
+ <TT
+CLASS="filename"
+>comment-guided.html.tmpl</TT
+>.
+ </P
+><P
+>&#13; So to use this feature, create a custom template for
+ <TT
+CLASS="filename"
+>enter_bug.cgi</TT
+>. The default template, on which you
+ could base it, is
+ <TT
+CLASS="filename"
+>custom/bug/create/create.html.tmpl</TT
+>.
+ Call it <TT
+CLASS="filename"
+>create-&#60;formatname&#62;.html.tmpl</TT
+>, and
+ in it, add widgets for each piece of information you'd like
+ collected - such as a build number, or set of steps to reproduce.
+ </P
+><P
+>&#13; Then, create a template like
+ <TT
+CLASS="filename"
+>custom/bug/create/comment.txt.tmpl</TT
+>, and call it
+ <TT
+CLASS="filename"
+>comment-&#60;formatname&#62;.txt.tmpl</TT
+>. This
+ template should reference the form fields you have created using
+ the syntax <TT
+CLASS="filename"
+>[% form.&#60;fieldname&#62; %]</TT
+>. When a
+ bug report is
+ submitted, the initial comment attached to the bug report will be
+ formatted according to the layout of this template.
+ </P
+><P
+>&#13; For example, if your custom enter_bug template had a field
+ <TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#60;input type="text" name="buildid" size="30"&#62;</PRE
+></FONT
+></TD
+></TR
+></TABLE
+>
+ and then your comment.txt.tmpl had
+ <TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>BuildID: [% form.buildid %]</PRE
+></FONT
+></TD
+></TR
+></TABLE
+>
+ then something like
+ <TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>BuildID: 20020303</PRE
+></FONT
+></TD
+></TR
+></TABLE
+>
+ would appear in the initial comment.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="template-http-accept"
+>6.3.6. Configuring Bugzilla to Detect the User's Language</A
+></H2
+><P
+>Bugzilla honours the user's Accept: HTTP header. You can install
+ templates in other languages, and Bugzilla will pick the most appropriate
+ according to a priority order defined by you. Many
+ language templates can be obtained from <A
+HREF="http://www.bugzilla.org/download.html#localizations"
+TARGET="_top"
+>http://www.bugzilla.org/download.html#localizations</A
+>. Instructions
+ for submitting new languages are also available from that location.
+ </P
+></DIV
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="cust-skins.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="cust-change-permissions.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Custom Skins</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="customization.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Customizing Who Can Change What</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/custom-fields.html b/docs/en/html/custom-fields.html
new file mode 100644
index 000000000..a926ca6ad
--- /dev/null
+++ b/docs/en/html/custom-fields.html
@@ -0,0 +1,379 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Custom Fields</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Administering Bugzilla"
+HREF="administration.html"><LINK
+REL="PREVIOUS"
+TITLE="Keywords"
+HREF="keywords.html"><LINK
+REL="NEXT"
+TITLE="Legal Values"
+HREF="edit-values.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="keywords.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 3. Administering Bugzilla</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="edit-values.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="custom-fields"
+>3.10. Custom Fields</A
+></H1
+><P
+>&#13; The release of Bugzilla 3.0 added the ability to create Custom Fields.
+ Custom Fields are treated like any other field - they can be set in bugs
+ and used for search queries. Administrators should keep in mind that
+ adding too many fields can make the user interface more complicated and
+ harder to use. Custom Fields should be added only when necessary and with
+ careful consideration.
+ </P
+><DIV
+CLASS="tip"
+><P
+></P
+><TABLE
+CLASS="tip"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/tip.gif"
+HSPACE="5"
+ALT="Tip"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Before adding a Custom Field, make sure that Bugzilla can not already
+ do the desired behavior. Many Bugzilla options are not enabled by
+ default, and many times Administrators find that simply enabling
+ certain options that already exist is sufficient.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; Administrators can manage Custom Fields using the
+ <SPAN
+CLASS="QUOTE"
+>"Custom Fields"</SPAN
+> link on the Administration page. The Custom
+ Fields administration page displays a list of Custom Fields, if any exist,
+ and a link to "Add a new custom field".
+ </P
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="add-custom-fields"
+>3.10.1. Adding Custom Fields</A
+></H2
+><P
+>&#13; To add a new Custom Field, click the "Add a new custom field" link. This
+ page displays several options for the new field, described below.
+ </P
+><P
+>&#13; The following attributes must be set for each new custom field:
+ <P
+></P
+><UL
+><LI
+><P
+>&#13; <EM
+>Name:</EM
+>
+ The name of the field in the database, used internally. This name
+ MUST begin with <SPAN
+CLASS="QUOTE"
+>"cf_"</SPAN
+> to prevent confusion with
+ standard fields. If this string is omitted, it will
+ be automatically added to the name entered.
+ </P
+></LI
+><LI
+><P
+>&#13; <EM
+>Description:</EM
+>
+ A brief string which is used as the label for this Custom Field.
+ That is the string that users will see, and should be
+ short and explicit.
+ </P
+></LI
+><LI
+><P
+>&#13; <EM
+>Type:</EM
+>
+ The type of field to create. There are
+ several types available:
+ <P
+></P
+><TABLE
+BORDER="0"
+><TBODY
+><TR
+><TD
+>&#13; Large Text Box: A multiple line box for entering free text.
+ </TD
+></TR
+><TR
+><TD
+>&#13; Free Text: A single line box for entering free text.
+ </TD
+></TR
+><TR
+><TD
+>&#13; Multiple-Selection Box: A list box where multiple options
+ can be selected. After creating this field, it must be edited
+ to add the selection options. See
+ <A
+HREF="edit-values.html#edit-values-list"
+>Section 3.11.1</A
+> for information about
+ editing legal values.
+ </TD
+></TR
+><TR
+><TD
+>&#13; Drop Down: A list box where only one option can be selected.
+ After creating this field, it must be edited to add the
+ selection options. See
+ <A
+HREF="edit-values.html#edit-values-list"
+>Section 3.11.1</A
+> for information about
+ editing legal values.
+ </TD
+></TR
+><TR
+><TD
+>&#13; Date/Time: A date field. This field appears with a
+ calendar widget for choosing the date.
+ </TD
+></TR
+></TBODY
+></TABLE
+><P
+></P
+>
+ </P
+></LI
+><LI
+><P
+>&#13; <EM
+>Sortkey:</EM
+>
+ Integer that determines in which order Custom Fields are
+ displayed in the User Interface, especially when viewing a bug.
+ Fields with lower values are displayed first.
+ </P
+></LI
+><LI
+><P
+>&#13; <EM
+>Can be set on bug creation:</EM
+>
+ Boolean that determines whether this field can be set on
+ bug creation. If not selected, then a bug must be created
+ before this field can be set. See <A
+HREF="bugreports.html"
+>Section 5.6</A
+>
+ for information about filing bugs.
+ </P
+></LI
+><LI
+><P
+>&#13; <EM
+>Displayed in bugmail for new bugs:</EM
+>
+ Boolean that determines whether the value set on this field
+ should appear in bugmail when the bug is filed. This attribute
+ has no effect if the field cannot be set on bug creation.
+ </P
+></LI
+><LI
+><P
+>&#13; <EM
+>Is obsolete:</EM
+>
+ Boolean that determines whether this field should
+ be displayed at all. Obsolete Custom Fields are hidden.
+ </P
+></LI
+></UL
+>
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="edit-custom-fields"
+>3.10.2. Editing Custom Fields</A
+></H2
+><P
+>&#13; As soon as a Custom Field is created, its name and type cannot be
+ changed. If this field is a drop down menu, its legal values can
+ be set as described in <A
+HREF="edit-values.html#edit-values-list"
+>Section 3.11.1</A
+>. All
+ other attributes can be edited as described above.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="delete-custom-fields"
+>3.10.3. Deleting Custom Fields</A
+></H2
+><P
+>&#13; Only custom fields which are marked as obsolete, and which never
+ have been used, can be deleted completely (else the integrity
+ of the bug history would be compromised). For custom fields marked
+ as obsolete, a "Delete" link will appear in the <SPAN
+CLASS="QUOTE"
+>"Action"</SPAN
+>
+ column. If the custom field has been used in the past, the deletion
+ will be rejected. But marking the field as obsolete is sufficient
+ to hide it from the user interface entirely.
+ </P
+></DIV
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="keywords.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="edit-values.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Keywords</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="administration.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Legal Values</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/customization.html b/docs/en/html/customization.html
new file mode 100644
index 000000000..6e02dad6b
--- /dev/null
+++ b/docs/en/html/customization.html
@@ -0,0 +1,206 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Customizing Bugzilla</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="PREVIOUS"
+TITLE="Whining"
+HREF="whining.html"><LINK
+REL="NEXT"
+TITLE="Bugzilla Extensions"
+HREF="extensions.html"></HEAD
+><BODY
+CLASS="chapter"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="whining.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+></TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="extensions.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="chapter"
+><H1
+><A
+NAME="customization"
+></A
+>Chapter 6. Customizing Bugzilla</H1
+><DIV
+CLASS="TOC"
+><DL
+><DT
+><B
+>Table of Contents</B
+></DT
+><DT
+>6.1. <A
+HREF="extensions.html"
+>Bugzilla Extensions</A
+></DT
+><DT
+>6.2. <A
+HREF="cust-skins.html"
+>Custom Skins</A
+></DT
+><DT
+>6.3. <A
+HREF="cust-templates.html"
+>Template Customization</A
+></DT
+><DD
+><DL
+><DT
+>6.3.1. <A
+HREF="cust-templates.html#template-directory"
+>Template Directory Structure</A
+></DT
+><DT
+>6.3.2. <A
+HREF="cust-templates.html#template-method"
+>Choosing a Customization Method</A
+></DT
+><DT
+>6.3.3. <A
+HREF="cust-templates.html#template-edit"
+>How To Edit Templates</A
+></DT
+><DT
+>6.3.4. <A
+HREF="cust-templates.html#template-formats"
+>Template Formats and Types</A
+></DT
+><DT
+>6.3.5. <A
+HREF="cust-templates.html#template-specific"
+>Particular Templates</A
+></DT
+><DT
+>6.3.6. <A
+HREF="cust-templates.html#template-http-accept"
+>Configuring Bugzilla to Detect the User's Language</A
+></DT
+></DL
+></DD
+><DT
+>6.4. <A
+HREF="cust-change-permissions.html"
+>Customizing Who Can Change What</A
+></DT
+><DT
+>6.5. <A
+HREF="integration.html"
+>Integrating Bugzilla with Third-Party Tools</A
+></DT
+></DL
+></DIV
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="whining.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="extensions.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Whining</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+>&nbsp;</TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Bugzilla Extensions</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/disclaimer.html b/docs/en/html/disclaimer.html
new file mode 100644
index 000000000..3ccd3ec20
--- /dev/null
+++ b/docs/en/html/disclaimer.html
@@ -0,0 +1,172 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Disclaimer</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="About This Guide"
+HREF="about.html"><LINK
+REL="PREVIOUS"
+TITLE="Copyright Information"
+HREF="copyright.html"><LINK
+REL="NEXT"
+TITLE="New Versions"
+HREF="newversions.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="copyright.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 1. About This Guide</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="newversions.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="disclaimer"
+>1.2. Disclaimer</A
+></H1
+><P
+>&#13; No liability for the contents of this document can be accepted.
+ Follow the instructions herein at your own risk.
+ This document may contain errors
+ and inaccuracies that may damage your system, cause your partner
+ to leave you, your boss to fire you, your cats to
+ pee on your furniture and clothing, and global thermonuclear
+ war. Proceed with caution.
+ </P
+><P
+>&#13; Naming of particular products or brands should not be seen as
+ endorsements, with the exception of the term "GNU/Linux". We
+ wholeheartedly endorse the use of GNU/Linux; it is an extremely
+ versatile, stable,
+ and robust operating system that offers an ideal operating
+ environment for Bugzilla.
+ </P
+><P
+>&#13; Although the Bugzilla development team has taken great care to
+ ensure that all exploitable bugs have been fixed, security holes surely
+ exist in any piece of code. Great care should be taken both in
+ the installation and usage of this software. The Bugzilla development
+ team members assume no liability for your use of Bugzilla. You have
+ the source code, and are responsible for auditing it yourself to ensure
+ your security needs are met.
+ </P
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="copyright.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="newversions.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Copyright Information</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="about.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>New Versions</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/edit-values.html b/docs/en/html/edit-values.html
new file mode 100644
index 000000000..fa0cfa7a3
--- /dev/null
+++ b/docs/en/html/edit-values.html
@@ -0,0 +1,212 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Legal Values</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Administering Bugzilla"
+HREF="administration.html"><LINK
+REL="PREVIOUS"
+TITLE="Custom Fields"
+HREF="custom-fields.html"><LINK
+REL="NEXT"
+TITLE="Bug Status Workflow"
+HREF="bug_status_workflow.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="custom-fields.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 3. Administering Bugzilla</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="bug_status_workflow.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="edit-values"
+>3.11. Legal Values</A
+></H1
+><P
+>&#13; Since Bugzilla 2.20 RC1, legal values for Operating Systems, platforms,
+ bug priorities and severities can be edited from the User Interface
+ directly. This means that it is no longer required to manually edit
+ <TT
+CLASS="filename"
+>localconfig</TT
+>. Starting with Bugzilla 2.23.3,
+ the list of valid resolutions can be customized from the same interface.
+ Since Bugzilla 3.1.1 the list of valid bug statuses can be customized
+ as well.
+ </P
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="edit-values-list"
+>3.11.1. Viewing/Editing legal values</A
+></H2
+><P
+>&#13; Editing legal values requires <SPAN
+CLASS="QUOTE"
+>"admin"</SPAN
+> privileges.
+ Select "Legal Values" from the Administration page. A list of all
+ fields, both system fields and Custom Fields, for which legal values
+ can be edited appears. Click a field name to edit its legal values.
+ </P
+><P
+>&#13; There is no limit to how many values a field can have, but each value
+ must be unique to that field. The sortkey is important to display these
+ values in the desired order.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="edit-values-delete"
+>3.11.2. Deleting legal values</A
+></H2
+><P
+>&#13; Legal values from Custom Fields can be deleted, but only if the
+ following two conditions are respected:
+ </P
+><P
+></P
+><OL
+TYPE="1"
+><LI
+><P
+>The value is not used by default for the field.</P
+></LI
+><LI
+><P
+>No bug is currently using this value.</P
+></LI
+></OL
+><P
+>&#13; If any of these conditions is not respected, the value cannot be deleted.
+ The only way to delete these values is to reassign bugs to another value
+ and to set another value as default for the field.
+ </P
+></DIV
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="custom-fields.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="bug_status_workflow.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Custom Fields</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="administration.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Bug Status Workflow</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/extensions.html b/docs/en/html/extensions.html
new file mode 100644
index 000000000..b01e9fc16
--- /dev/null
+++ b/docs/en/html/extensions.html
@@ -0,0 +1,161 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Bugzilla Extensions</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Customizing Bugzilla"
+HREF="customization.html"><LINK
+REL="PREVIOUS"
+TITLE="Customizing Bugzilla"
+HREF="customization.html"><LINK
+REL="NEXT"
+TITLE="Custom Skins"
+HREF="cust-skins.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="customization.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 6. Customizing Bugzilla</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="cust-skins.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="extensions"
+>6.1. Bugzilla Extensions</A
+></H1
+><P
+>&#13; One of the best ways to customize Bugzilla is by writing a Bugzilla
+ Extension. Bugzilla Extensions let you modify both the code and
+ UI of Bugzilla in a way that can be distributed to other Bugzilla
+ users and ported forward to future versions of Bugzilla with minimal
+ effort.
+ </P
+><P
+>&#13; See the <A
+HREF="api/Bugzilla/Extension.html"
+TARGET="_top"
+>Bugzilla Extension
+ documentation</A
+> for information on how to write an Extension.
+ </P
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="customization.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="cust-skins.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Customizing Bugzilla</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="customization.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Custom Skins</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/extraconfig.html b/docs/en/html/extraconfig.html
new file mode 100644
index 000000000..89522834b
--- /dev/null
+++ b/docs/en/html/extraconfig.html
@@ -0,0 +1,487 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Optional Additional Configuration</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Installing Bugzilla"
+HREF="installing-bugzilla.html"><LINK
+REL="PREVIOUS"
+TITLE="Configuration"
+HREF="configuration.html"><LINK
+REL="NEXT"
+TITLE="Multiple Bugzilla databases with a single installation"
+HREF="multiple-bz-dbs.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="configuration.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 2. Installing Bugzilla</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="multiple-bz-dbs.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="extraconfig"
+>2.3. Optional Additional Configuration</A
+></H1
+><P
+>&#13; Bugzilla has a number of optional features. This section describes how
+ to configure or enable them.
+ </P
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="AEN688"
+>2.3.1. Bug Graphs</A
+></H2
+><P
+>If you have installed the necessary Perl modules you
+ can start collecting statistics for the nifty Bugzilla
+ graphs.</P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="screen"
+><SAMP
+CLASS="prompt"
+>bash#</SAMP
+> <B
+CLASS="command"
+>crontab -e</B
+></PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>&#13; This should bring up the crontab file in your editor.
+ Add a cron entry like this to run
+ <TT
+CLASS="filename"
+>collectstats.pl</TT
+>
+ daily at 5 after midnight:
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>5 0 * * * cd &#60;your-bugzilla-directory&#62; ; ./collectstats.pl</PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>&#13; After two days have passed you'll be able to view bug graphs from
+ the Reports page.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Windows does not have 'cron', but it does have the Task
+ Scheduler, which performs the same duties. There are also
+ third-party tools that can be used to implement cron, such as
+ <A
+HREF="http://www.nncron.ru/"
+TARGET="_top"
+>nncron</A
+>.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="installation-whining-cron"
+>2.3.2. The Whining Cron</A
+></H2
+><P
+>What good are
+ bugs if they're not annoying? To help make them more so you
+ can set up Bugzilla's automatic whining system to complain at engineers
+ which leave their bugs in the CONFIRMED state without triaging them.
+ </P
+><P
+>&#13; This can be done by adding the following command as a daily
+ crontab entry, in the same manner as explained above for bug
+ graphs. This example runs it at 12.55am.
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>55 0 * * * cd &#60;your-bugzilla-directory&#62; ; ./whineatnews.pl</PRE
+></FONT
+></TD
+></TR
+></TABLE
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Windows does not have 'cron', but it does have the Task
+ Scheduler, which performs the same duties. There are also
+ third-party tools that can be used to implement cron, such as
+ <A
+HREF="http://www.nncron.ru/"
+TARGET="_top"
+>nncron</A
+>.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="installation-whining"
+>2.3.3. Whining</A
+></H2
+><P
+>&#13; As of Bugzilla 2.20, users can configure Bugzilla to regularly annoy
+ them at regular intervals, by having Bugzilla execute saved searches
+ at certain times and emailing the results to the user. This is known
+ as "Whining". The process of configuring Whining is described
+ in <A
+HREF="whining.html"
+>Section 5.13</A
+>, but for it to work a Perl script must be
+ executed at regular intervals.
+ </P
+><P
+>&#13; This can be done by adding the following command as a daily
+ crontab entry, in the same manner as explained above for bug
+ graphs. This example runs it every 15 minutes.
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>*/15 * * * * cd &#60;your-bugzilla-directory&#62; ; ./whine.pl</PRE
+></FONT
+></TD
+></TR
+></TABLE
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Whines can be executed as often as every 15 minutes, so if you specify
+ longer intervals between executions of whine.pl, some users may not
+ be whined at as often as they would expect. Depending on the person,
+ this can either be a very Good Thing or a very Bad Thing.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Windows does not have 'cron', but it does have the Task
+ Scheduler, which performs the same duties. There are also
+ third-party tools that can be used to implement cron, such as
+ <A
+HREF="http://www.nncron.ru/"
+TARGET="_top"
+>nncron</A
+>.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="apache-addtype"
+>2.3.4. Serving Alternate Formats with the right MIME type</A
+></H2
+><P
+>&#13; Some Bugzilla pages have alternate formats, other than just plain
+ <ACRONYM
+CLASS="acronym"
+>HTML</ACRONYM
+>. In particular, a few Bugzilla pages can
+ output their contents as either <ACRONYM
+CLASS="acronym"
+>XUL</ACRONYM
+> (a special
+ Mozilla format, that looks like a program <ACRONYM
+CLASS="acronym"
+>GUI</ACRONYM
+>)
+ or <ACRONYM
+CLASS="acronym"
+>RDF</ACRONYM
+> (a type of structured <ACRONYM
+CLASS="acronym"
+>XML</ACRONYM
+>
+ that can be read by various programs).
+ </P
+><P
+>&#13; In order for your users to see these pages correctly, Apache must
+ send them with the right <ACRONYM
+CLASS="acronym"
+>MIME</ACRONYM
+> type. To do this,
+ add the following lines to your Apache configuration, either in the
+ <SAMP
+CLASS="computeroutput"
+>&#60;VirtualHost&#62;</SAMP
+> section for your
+ Bugzilla, or in the <SAMP
+CLASS="computeroutput"
+>&#60;Directory&#62;</SAMP
+>
+ section for your Bugzilla:
+ </P
+><P
+>&#13; <TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="screen"
+>AddType application/vnd.mozilla.xul+xml .xul
+AddType application/rdf+xml .rdf</PRE
+></FONT
+></TD
+></TR
+></TABLE
+>
+ </P
+></DIV
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="configuration.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="multiple-bz-dbs.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Configuration</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="installing-bugzilla.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Multiple Bugzilla databases with a single installation</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/flags-overview.html b/docs/en/html/flags-overview.html
new file mode 100644
index 000000000..cffcd595d
--- /dev/null
+++ b/docs/en/html/flags-overview.html
@@ -0,0 +1,981 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Flags</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Administering Bugzilla"
+HREF="administration.html"><LINK
+REL="PREVIOUS"
+TITLE="Milestones"
+HREF="milestones.html"><LINK
+REL="NEXT"
+TITLE="Keywords"
+HREF="keywords.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="milestones.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 3. Administering Bugzilla</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="keywords.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="flags-overview"
+>3.8. Flags</A
+></H1
+><P
+>&#13; Flags are a way to attach a specific status to a bug or attachment,
+ either <SPAN
+CLASS="QUOTE"
+>"+"</SPAN
+> or <SPAN
+CLASS="QUOTE"
+>"-"</SPAN
+>. The meaning of these symbols depends on the text
+ the flag itself, but contextually they could mean pass/fail,
+ accept/reject, approved/denied, or even a simple yes/no. If your site
+ allows requestable flags, then users may set a flag to <SPAN
+CLASS="QUOTE"
+>"?"</SPAN
+> as a
+ request to another user that they look at the bug/attachment, and set
+ the flag to its correct status.
+ </P
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="flags-simpleexample"
+>3.8.1. A Simple Example</A
+></H2
+><P
+>&#13; A developer might want to ask their manager,
+ <SPAN
+CLASS="QUOTE"
+>"Should we fix this bug before we release version 2.0?"</SPAN
+>
+ They might want to do this for a <EM
+>lot</EM
+> of bugs,
+ so it would be nice to streamline the process...
+ </P
+><P
+>&#13; In Bugzilla, it would work this way:
+ <P
+></P
+><OL
+TYPE="1"
+><LI
+><P
+>&#13; The Bugzilla administrator creates a flag type called
+ <SPAN
+CLASS="QUOTE"
+>"blocking2.0"</SPAN
+> that shows up on all bugs in
+ your product.
+ </P
+><P
+>&#13; It shows up on the <SPAN
+CLASS="QUOTE"
+>"Show Bug"</SPAN
+> screen
+ as the text <SPAN
+CLASS="QUOTE"
+>"blocking2.0"</SPAN
+> with a drop-down box next
+ to it. The drop-down box contains four values: an empty space,
+ <SPAN
+CLASS="QUOTE"
+>"?"</SPAN
+>, <SPAN
+CLASS="QUOTE"
+>"-"</SPAN
+>, and <SPAN
+CLASS="QUOTE"
+>"+"</SPAN
+>.
+ </P
+></LI
+><LI
+><P
+>The developer sets the flag to <SPAN
+CLASS="QUOTE"
+>"?"</SPAN
+>.</P
+></LI
+><LI
+><P
+>&#13; The manager sees the <SAMP
+CLASS="computeroutput"
+>blocking2.0</SAMP
+>
+ flag with a <SPAN
+CLASS="QUOTE"
+>"?"</SPAN
+> value.
+ </P
+></LI
+><LI
+><P
+>&#13; If the manager thinks the feature should go into the product
+ before version 2.0 can be released, he sets the flag to
+ <SPAN
+CLASS="QUOTE"
+>"+"</SPAN
+>. Otherwise, he sets it to <SPAN
+CLASS="QUOTE"
+>"-"</SPAN
+>.
+ </P
+></LI
+><LI
+><P
+>&#13; Now, every Bugzilla user who looks at the bug knows whether or
+ not the bug needs to be fixed before release of version 2.0.
+ </P
+></LI
+></OL
+>
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="flags-about"
+>3.8.2. About Flags</A
+></H2
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="flag-values"
+>3.8.2.1. Values</A
+></H3
+><P
+>&#13; Flags can have three values:
+ <P
+></P
+><DIV
+CLASS="variablelist"
+><DL
+><DT
+><SAMP
+CLASS="computeroutput"
+>?</SAMP
+></DT
+><DD
+><P
+>&#13; A user is requesting that a status be set. (Think of it as 'A question is being asked'.)
+ </P
+></DD
+><DT
+><SAMP
+CLASS="computeroutput"
+>-</SAMP
+></DT
+><DD
+><P
+>&#13; The status has been set negatively. (The question has been answered <SPAN
+CLASS="QUOTE"
+>"no"</SPAN
+>.)
+ </P
+></DD
+><DT
+><SAMP
+CLASS="computeroutput"
+>+</SAMP
+></DT
+><DD
+><P
+>&#13; The status has been set positively.
+ (The question has been answered <SPAN
+CLASS="QUOTE"
+>"yes"</SPAN
+>.)
+ </P
+></DD
+></DL
+></DIV
+>
+ </P
+><P
+>&#13; Actually, there's a fourth value a flag can have --
+ <SPAN
+CLASS="QUOTE"
+>"unset"</SPAN
+> -- which shows up as a blank space. This
+ just means that nobody has expressed an opinion (or asked
+ someone else to express an opinion) about this bug or attachment.
+ </P
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="flag-askto"
+>3.8.3. Using flag requests</A
+></H2
+><P
+>&#13; If a flag has been defined as 'requestable', and a user has enough privileges
+ to request it (see below), the user can set the flag's status to <SPAN
+CLASS="QUOTE"
+>"?"</SPAN
+>.
+ This status indicates that someone (a.k.a. <SPAN
+CLASS="QUOTE"
+>"the requester"</SPAN
+>) is asking
+ someone else to set the flag to either <SPAN
+CLASS="QUOTE"
+>"+"</SPAN
+> or <SPAN
+CLASS="QUOTE"
+>"-"</SPAN
+>.
+ </P
+><P
+>&#13; If a flag has been defined as 'specifically requestable',
+ a text box will appear next to the flag into which the requester may
+ enter a Bugzilla username. That named person (a.k.a. <SPAN
+CLASS="QUOTE"
+>"the requestee"</SPAN
+>)
+ will receive an email notifying them of the request, and pointing them
+ to the bug/attachment in question.
+ </P
+><P
+>&#13; If a flag has <EM
+>not</EM
+> been defined as 'specifically requestable',
+ then no such text-box will appear. A request to set this flag cannot be made of
+ any specific individual, but must be asked <SPAN
+CLASS="QUOTE"
+>"to the wind"</SPAN
+>.
+ A requester may <SPAN
+CLASS="QUOTE"
+>"ask the wind"</SPAN
+> on any flag simply by leaving the text-box blank.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="flag-types"
+>3.8.4. Two Types of Flags</A
+></H2
+><P
+>&#13; Flags can go in two places: on an attachment, or on a bug.
+ </P
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="flag-type-attachment"
+>3.8.4.1. Attachment Flags</A
+></H3
+><P
+>&#13; Attachment flags are used to ask a question about a specific
+ attachment on a bug.
+ </P
+><P
+>&#13; Many Bugzilla installations use this to
+ request that one developer <SPAN
+CLASS="QUOTE"
+>"review"</SPAN
+> another
+ developer's code before they check it in. They attach the code to
+ a bug report, and then set a flag on that attachment called
+ <SPAN
+CLASS="QUOTE"
+>"review"</SPAN
+> to
+ <SAMP
+CLASS="computeroutput"
+>review?boss@domain.com</SAMP
+>.
+ boss@domain.com is then notified by email that
+ he has to check out that attachment and approve it or deny it.
+ </P
+><P
+>&#13; For a Bugzilla user, attachment flags show up in three places:
+ <P
+></P
+><OL
+TYPE="1"
+><LI
+><P
+>&#13; On the list of attachments in the <SPAN
+CLASS="QUOTE"
+>"Show Bug"</SPAN
+>
+ screen, you can see the current state of any flags that
+ have been set to ?, +, or -. You can see who asked about
+ the flag (the requester), and who is being asked (the
+ requestee).
+ </P
+></LI
+><LI
+><P
+>&#13; When you <SPAN
+CLASS="QUOTE"
+>"Edit"</SPAN
+> an attachment, you can
+ see any settable flag, along with any flags that have
+ already been set. This <SPAN
+CLASS="QUOTE"
+>"Edit Attachment"</SPAN
+>
+ screen is where you set flags to ?, -, +, or unset them.
+ </P
+></LI
+><LI
+><P
+>&#13; Requests are listed in the <SPAN
+CLASS="QUOTE"
+>"Request Queue"</SPAN
+>, which
+ is accessible from the <SPAN
+CLASS="QUOTE"
+>"My Requests"</SPAN
+> link (if you are
+ logged in) or <SPAN
+CLASS="QUOTE"
+>"Requests"</SPAN
+> link (if you are logged out)
+ visible in the footer of all pages.
+ </P
+></LI
+></OL
+>
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="flag-type-bug"
+>3.8.4.2. Bug Flags</A
+></H3
+><P
+>&#13; Bug flags are used to set a status on the bug itself. You can
+ see Bug Flags in the <SPAN
+CLASS="QUOTE"
+>"Show Bug"</SPAN
+> and <SPAN
+CLASS="QUOTE"
+>"Requests"</SPAN
+>
+ screens, as described above.
+ </P
+><P
+>&#13; Only users with enough privileges (see below) may set flags on bugs.
+ This doesn't necessarily include the assignee, reporter, or users with the
+ <SAMP
+CLASS="computeroutput"
+>editbugs</SAMP
+> permission.
+ </P
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="flags-admin"
+>3.8.5. Administering Flags</A
+></H2
+><P
+>&#13; If you have the <SPAN
+CLASS="QUOTE"
+>"editcomponents"</SPAN
+> permission, you can
+ edit Flag Types from the main administration page. Clicking the
+ <SPAN
+CLASS="QUOTE"
+>"Flags"</SPAN
+> link will bring you to the <SPAN
+CLASS="QUOTE"
+>"Administer
+ Flag Types"</SPAN
+> page. Here, you can select whether you want
+ to create (or edit) a Bug flag, or an Attachment flag.
+ </P
+><P
+>&#13; No matter which you choose, the interface is the same, so we'll
+ just go over it once.
+ </P
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="flags-edit"
+>3.8.5.1. Editing a Flag</A
+></H3
+><P
+>&#13; To edit a flag's properties, just click the flag's name.
+ That will take you to the same
+ form as described below (<A
+HREF="flags-overview.html#flags-create"
+>Section 3.8.5.2</A
+>).
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="flags-create"
+>3.8.5.2. Creating a Flag</A
+></H3
+><P
+>&#13; When you click on the <SPAN
+CLASS="QUOTE"
+>"Create a Flag Type for..."</SPAN
+>
+ link, you will be presented with a form. Here is what the fields in
+ the form mean:
+ </P
+><DIV
+CLASS="section"
+><H4
+CLASS="section"
+><A
+NAME="flags-create-field-name"
+>3.8.5.2.1. Name</A
+></H4
+><P
+>&#13; This is the name of the flag. This will be displayed
+ to Bugzilla users who are looking at or setting the flag.
+ The name may contain any valid Unicode characters except commas
+ and spaces.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H4
+CLASS="section"
+><A
+NAME="flags-create-field-description"
+>3.8.5.2.2. Description</A
+></H4
+><P
+>&#13; The description describes the flag in more detail. It is visible
+ in a tooltip when hovering over a flag either in the <SPAN
+CLASS="QUOTE"
+>"Show Bug"</SPAN
+>
+ or <SPAN
+CLASS="QUOTE"
+>"Edit Attachment"</SPAN
+> pages. This field can be as
+ long as you like, and can contain any character you want.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H4
+CLASS="section"
+><A
+NAME="flags-create-field-category"
+>3.8.5.2.3. Category</A
+></H4
+><P
+>&#13; Default behaviour for a newly-created flag is to appear on
+ products and all components, which is why <SPAN
+CLASS="QUOTE"
+>"__Any__:__Any__"</SPAN
+>
+ is already entered in the <SPAN
+CLASS="QUOTE"
+>"Inclusions"</SPAN
+> box.
+ If this is not your desired behaviour, you must either set some
+ exclusions (for products on which you don't want the flag to appear),
+ or you must remove <SPAN
+CLASS="QUOTE"
+>"__Any__:__Any__"</SPAN
+> from the Inclusions box
+ and define products/components specifically for this flag.
+ </P
+><P
+>&#13; To create an Inclusion, select a Product from the top drop-down box.
+ You may also select a specific component from the bottom drop-down box.
+ (Setting <SPAN
+CLASS="QUOTE"
+>"__Any__"</SPAN
+> for Product translates to,
+ <SPAN
+CLASS="QUOTE"
+>"all the products in this Bugzilla"</SPAN
+>.
+ Selecting <SPAN
+CLASS="QUOTE"
+>"__Any__"</SPAN
+> in the Component field means
+ <SPAN
+CLASS="QUOTE"
+>"all components in the selected product."</SPAN
+>)
+ Selections made, press <SPAN
+CLASS="QUOTE"
+>"Include"</SPAN
+>, and your
+ Product/Component pairing will show up in the <SPAN
+CLASS="QUOTE"
+>"Inclusions"</SPAN
+> box on the right.
+ </P
+><P
+>&#13; To create an Exclusion, the process is the same; select a Product from the
+ top drop-down box, select a specific component if you want one, and press
+ <SPAN
+CLASS="QUOTE"
+>"Exclude"</SPAN
+>. The Product/Component pairing will show up in the
+ <SPAN
+CLASS="QUOTE"
+>"Exclusions"</SPAN
+> box on the right.
+ </P
+><P
+>&#13; This flag <EM
+>will</EM
+> and <EM
+>can</EM
+> be set for any
+ products/components that appearing in the <SPAN
+CLASS="QUOTE"
+>"Inclusions"</SPAN
+> box
+ (or which fall under the appropriate <SPAN
+CLASS="QUOTE"
+>"__Any__"</SPAN
+>).
+ This flag <EM
+>will not</EM
+> appear (and therefore cannot be set) on
+ any products appearing in the <SPAN
+CLASS="QUOTE"
+>"Exclusions"</SPAN
+> box.
+ <EM
+> IMPORTANT: Exclusions override inclusions.</EM
+>
+ </P
+><P
+>&#13; You may select a Product without selecting a specific Component,
+ but you can't select a Component without a Product, or to select a
+ Component that does not belong to the named Product. If you do so,
+ Bugzilla will display an error message, even if all your products
+ have a component by that name.
+ </P
+><P
+><EM
+>Example:</EM
+> Let's say you have a product called
+ <SPAN
+CLASS="QUOTE"
+>"Jet Plane"</SPAN
+> that has thousands of components. You want
+ to be able to ask if a problem should be fixed in the next model of
+ plane you release. We'll call the flag <SPAN
+CLASS="QUOTE"
+>"fixInNext"</SPAN
+>.
+ But, there's one component in <SPAN
+CLASS="QUOTE"
+>"Jet Plane,"</SPAN
+>
+ called <SPAN
+CLASS="QUOTE"
+>"Pilot."</SPAN
+> It doesn't make sense to release a
+ new pilot, so you don't want to have the flag show up in that component.
+ So, you include <SPAN
+CLASS="QUOTE"
+>"Jet Plane:__Any__"</SPAN
+> and you exclude
+ <SPAN
+CLASS="QUOTE"
+>"Jet Plane:Pilot"</SPAN
+>.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H4
+CLASS="section"
+><A
+NAME="flags-create-field-sortkey"
+>3.8.5.2.4. Sort Key</A
+></H4
+><P
+>&#13; Flags normally show up in alphabetical order. If you want them to
+ show up in a different order, you can use this key set the order on each flag.
+ Flags with a lower sort key will appear before flags with a higher
+ sort key. Flags that have the same sort key will be sorted alphabetically,
+ but they will still be after flags with a lower sort key, and before flags
+ with a higher sort key.
+ </P
+><P
+>&#13; <EM
+>Example:</EM
+> I have AFlag (Sort Key 100), BFlag (Sort Key 10),
+ CFlag (Sort Key 10), and DFlag (Sort Key 1). These show up in
+ the order: DFlag, BFlag, CFlag, AFlag.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H4
+CLASS="section"
+><A
+NAME="flags-create-field-active"
+>3.8.5.2.5. Active</A
+></H4
+><P
+>&#13; Sometimes, you might want to keep old flag information in the
+ Bugzilla database, but stop users from setting any new flags of this type.
+ To do this, uncheck <SPAN
+CLASS="QUOTE"
+>"active"</SPAN
+>. Deactivated
+ flags will still show up in the UI if they are ?, +, or -, but they
+ may only be cleared (unset), and cannot be changed to a new value.
+ Once a deactivated flag is cleared, it will completely disappear from a
+ bug/attachment, and cannot be set again.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H4
+CLASS="section"
+><A
+NAME="flags-create-field-requestable"
+>3.8.5.2.6. Requestable</A
+></H4
+><P
+>&#13; New flags are, by default, <SPAN
+CLASS="QUOTE"
+>"requestable"</SPAN
+>, meaning that they
+ offer users the <SPAN
+CLASS="QUOTE"
+>"?"</SPAN
+> option, as well as <SPAN
+CLASS="QUOTE"
+>"+"</SPAN
+>
+ and <SPAN
+CLASS="QUOTE"
+>"-"</SPAN
+>.
+ To remove the ? option, uncheck <SPAN
+CLASS="QUOTE"
+>"requestable"</SPAN
+>.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H4
+CLASS="section"
+><A
+NAME="flags-create-field-specific"
+>3.8.5.2.7. Specifically Requestable</A
+></H4
+><P
+>&#13; By default this box is checked for new flags, meaning that users may make
+ flag requests of specific individuals. Unchecking this box will remove the
+ text box next to a flag; if it is still requestable, then requests may
+ only be made <SPAN
+CLASS="QUOTE"
+>"to the wind."</SPAN
+> Removing this after specific
+ requests have been made will not remove those requests; that data will
+ stay in the database (though it will no longer appear to the user).
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H4
+CLASS="section"
+><A
+NAME="flags-create-field-multiplicable"
+>3.8.5.2.8. Multiplicable</A
+></H4
+><P
+>&#13; Any flag with <SPAN
+CLASS="QUOTE"
+>"Multiplicable"</SPAN
+> set (default for new flags is 'on')
+ may be set more than once. After being set once, an unset flag
+ of the same type will appear below it with <SPAN
+CLASS="QUOTE"
+>"addl."</SPAN
+> (short for
+ <SPAN
+CLASS="QUOTE"
+>"additional"</SPAN
+>) before the name. There is no limit to the number of
+ times a Multiplicable flags may be set on the same bug/attachment.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H4
+CLASS="section"
+><A
+NAME="flags-create-field-cclist"
+>3.8.5.2.9. CC List</A
+></H4
+><P
+>&#13; If you want certain users to be notified every time this flag is
+ set to ?, -, +, or unset, add them here. This is a comma-separated
+ list of email addresses that need not be restricted to Bugzilla usernames.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H4
+CLASS="section"
+><A
+NAME="flags-create-grant-group"
+>3.8.5.2.10. Grant Group</A
+></H4
+><P
+>&#13; When this field is set to some given group, only users in the group
+ can set the flag to <SPAN
+CLASS="QUOTE"
+>"+"</SPAN
+> and <SPAN
+CLASS="QUOTE"
+>"-"</SPAN
+>. This
+ field does not affect who can request or cancel the flag. For that,
+ see the <SPAN
+CLASS="QUOTE"
+>"Request Group"</SPAN
+> field below. If this field
+ is left blank, all users can set or delete this flag. This field is
+ useful for restricting which users can approve or reject requests.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H4
+CLASS="section"
+><A
+NAME="flags-create-request-group"
+>3.8.5.2.11. Request Group</A
+></H4
+><P
+>&#13; When this field is set to some given group, only users in the group
+ can request or cancel this flag. Note that this field has no effect
+ if the <SPAN
+CLASS="QUOTE"
+>"grant group"</SPAN
+> field is empty. You can set the
+ value of this field to a different group, but both fields have to be
+ set to a group for this field to have an effect.
+ </P
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="flags-delete"
+>3.8.5.3. Deleting a Flag</A
+></H3
+><P
+>&#13; When you are at the <SPAN
+CLASS="QUOTE"
+>"Administer Flag Types"</SPAN
+> screen,
+ you will be presented with a list of Bug flags and a list of Attachment
+ Flags.
+ </P
+><P
+>&#13; To delete a flag, click on the <SPAN
+CLASS="QUOTE"
+>"Delete"</SPAN
+> link next to
+ the flag description.
+ </P
+><DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Once you delete a flag, it is <EM
+>gone</EM
+> from
+ your Bugzilla. All the data for that flag will be deleted.
+ Everywhere that flag was set, it will disappear,
+ and you cannot get that data back. If you want to keep flag data,
+ but don't want anybody to set any new flags or change current flags,
+ unset <SPAN
+CLASS="QUOTE"
+>"active"</SPAN
+> in the flag Edit form.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+></DIV
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="milestones.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="keywords.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Milestones</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="administration.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Keywords</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/flags.html b/docs/en/html/flags.html
new file mode 100644
index 000000000..6cb85de55
--- /dev/null
+++ b/docs/en/html/flags.html
@@ -0,0 +1,194 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Flags</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Using Bugzilla"
+HREF="using.html"><LINK
+REL="PREVIOUS"
+TITLE="Reports and Charts"
+HREF="reporting.html"><LINK
+REL="NEXT"
+TITLE="Whining"
+HREF="whining.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="reporting.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 5. Using Bugzilla</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="whining.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="flags"
+>5.12. Flags</A
+></H1
+><P
+>&#13; A flag is a kind of status that can be set on bugs or attachments
+ to indicate that the bugs/attachments are in a certain state.
+ Each installation can define its own set of flags that can be set
+ on bugs or attachments.
+ </P
+><P
+>&#13; If your installation has defined a flag, you can set or unset that flag,
+ and if your administrator has enabled requesting of flags, you can submit
+ a request for another user to set the flag.
+ </P
+><P
+>&#13; To set a flag, select either "+" or "-" from the drop-down menu next to
+ the name of the flag in the "Flags" list. The meaning of these values are
+ flag-specific and thus cannot be described in this documentation,
+ but by way of example, setting a flag named "review" to "+" may indicate
+ that the bug/attachment has passed review, while setting it to "-"
+ may indicate that the bug/attachment has failed review.
+ </P
+><P
+>&#13; To unset a flag, click its drop-down menu and select the blank value.
+ Note that marking an attachment as obsolete automatically cancels all
+ pending requests for the attachment.
+ </P
+><P
+>&#13; If your administrator has enabled requests for a flag, request a flag
+ by selecting "?" from the drop-down menu and then entering the username
+ of the user you want to set the flag in the text field next to the menu.
+ </P
+><P
+>&#13; A set flag appears in bug reports and on "edit attachment" pages with the
+ abbreviated username of the user who set the flag prepended to the
+ flag name. For example, if Jack sets a "review" flag to "+", it appears
+ as Jack: review [ + ]
+ </P
+><P
+>&#13; A requested flag appears with the user who requested the flag prepended
+ to the flag name and the user who has been requested to set the flag
+ appended to the flag name within parentheses. For example, if Jack
+ asks Jill for review, it appears as Jack: review [ ? ] (Jill).
+ </P
+><P
+>&#13; You can browse through open requests made of you and by you by selecting
+ 'My Requests' from the footer. You can also look at open requests limited
+ by other requesters, requestees, products, components, and flag names from
+ this page. Note that you can use '-' for requestee to specify flags with
+ 'no requestee' set.
+ </P
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="reporting.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="whining.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Reports and Charts</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="using.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Whining</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/general-advice.html b/docs/en/html/general-advice.html
new file mode 100644
index 000000000..648e2f36b
--- /dev/null
+++ b/docs/en/html/general-advice.html
@@ -0,0 +1,202 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>General Advice</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Troubleshooting"
+HREF="troubleshooting.html"><LINK
+REL="PREVIOUS"
+TITLE="Troubleshooting"
+HREF="troubleshooting.html"><LINK
+REL="NEXT"
+TITLE="The Apache web server is not serving Bugzilla pages"
+HREF="trbl-testserver.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="troubleshooting.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Appendix A. Troubleshooting</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="trbl-testserver.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="general-advice"
+>A.1. General Advice</A
+></H1
+><P
+>If you can't get <TT
+CLASS="filename"
+>checksetup.pl</TT
+> to run to
+ completion, it normally explains what's wrong and how to fix it.
+ If you can't work it out, or if it's being uncommunicative, post
+ the errors in the
+ <A
+HREF="news://news.mozilla.org/mozilla.support.bugzilla"
+TARGET="_top"
+>mozilla.support.bugzilla</A
+>
+ newsgroup.
+ </P
+><P
+>If you have made it all the way through
+ <A
+HREF="installation.html"
+>Section 2.1</A
+> (Installation) and
+ <A
+HREF="configuration.html"
+>Section 2.2</A
+> (Configuration) but accessing the Bugzilla
+ URL doesn't work, the first thing to do is to check your web server error
+ log. For Apache, this is often located at
+ <TT
+CLASS="filename"
+>/etc/logs/httpd/error_log</TT
+>. The error messages
+ you see may be self-explanatory enough to enable you to diagnose and
+ fix the problem. If not, see below for some commonly-encountered
+ errors. If that doesn't help, post the errors to the newsgroup.
+ </P
+><P
+>&#13; Bugzilla can also log all user-based errors (and many code-based errors)
+ that occur, without polluting the web server's error log. To enable
+ Bugzilla error logging, create a file that Bugzilla can write to, named
+ <TT
+CLASS="filename"
+>errorlog</TT
+>, in the Bugzilla <TT
+CLASS="filename"
+>data</TT
+>
+ directory. Errors will be logged as they occur, and will include the type
+ of the error, the IP address and username (if available) of the user who
+ triggered the error, and the values of all environment variables; if a
+ form was being submitted, the data in the form will also be included.
+ To disable error logging, delete or rename the
+ <TT
+CLASS="filename"
+>errorlog</TT
+> file.
+ </P
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="troubleshooting.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="trbl-testserver.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Troubleshooting</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="troubleshooting.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>The Apache web server is not serving Bugzilla pages</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/gfdl-0.html b/docs/en/html/gfdl-0.html
new file mode 100644
index 000000000..a53ec5ee3
--- /dev/null
+++ b/docs/en/html/gfdl-0.html
@@ -0,0 +1,167 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Preamble</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="GNU Free Documentation License"
+HREF="gfdl.html"><LINK
+REL="PREVIOUS"
+TITLE="GNU Free Documentation License"
+HREF="gfdl.html"><LINK
+REL="NEXT"
+TITLE="Applicability and Definition"
+HREF="gfdl-1.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="gfdl.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Appendix D. GNU Free Documentation License</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="gfdl-1.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="gfdl-0"
+>0. Preamble</A
+></H1
+><P
+>The purpose of this License is to make a manual, textbook, or other
+ written document "free" in the sense of freedom: to assure everyone the
+ effective freedom to copy and redistribute it, with or without modifying
+ it, either commercially or noncommercially. Secondarily, this License
+ preserves for the author and publisher a way to get credit for their
+ work, while not being considered responsible for modifications made by
+ others.</P
+><P
+>This License is a kind of "copyleft", which means that derivative
+ works of the document must themselves be free in the same sense. It
+ complements the GNU General Public License, which is a copyleft license
+ designed for free software.</P
+><P
+>We have designed this License in order to use it for manuals for
+ free software, because free software needs free documentation: a free
+ program should come with manuals providing the same freedoms that the
+ software does. But this License is not limited to software manuals; it
+ can be used for any textual work, regardless of subject matter or whether
+ it is published as a printed book. We recommend this License principally
+ for works whose purpose is instruction or reference.</P
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="gfdl.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="gfdl-1.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>GNU Free Documentation License</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="gfdl.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Applicability and Definition</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/gfdl-1.html b/docs/en/html/gfdl-1.html
new file mode 100644
index 000000000..45c1cbacc
--- /dev/null
+++ b/docs/en/html/gfdl-1.html
@@ -0,0 +1,202 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Applicability and Definition</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="GNU Free Documentation License"
+HREF="gfdl.html"><LINK
+REL="PREVIOUS"
+TITLE="Preamble"
+HREF="gfdl-0.html"><LINK
+REL="NEXT"
+TITLE="Verbatim Copying"
+HREF="gfdl-2.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="gfdl-0.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Appendix D. GNU Free Documentation License</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="gfdl-2.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="gfdl-1"
+>1. Applicability and Definition</A
+></H1
+><P
+>This License applies to any manual or other work that contains a
+ notice placed by the copyright holder saying it can be distributed under
+ the terms of this License. The "Document", below, refers to any such
+ manual or work. Any member of the public is a licensee, and is addressed
+ as "you".</P
+><P
+>A "Modified Version" of the Document means any work containing the
+ Document or a portion of it, either copied verbatim, or with
+ modifications and/or translated into another language.</P
+><P
+>A "Secondary Section" is a named appendix or a front-matter section
+ of the Document that deals exclusively with the relationship of the
+ publishers or authors of the Document to the Document's overall subject
+ (or to related matters) and contains nothing that could fall directly
+ within that overall subject. (For example, if the Document is in part a
+ textbook of mathematics, a Secondary Section may not explain any
+ mathematics.) The relationship could be a matter of historical connection
+ with the subject or with related matters, or of legal, commercial,
+ philosophical, ethical or political position regarding them.</P
+><P
+>The "Invariant Sections" are certain Secondary Sections whose
+ titles are designated, as being those of Invariant Sections, in the
+ notice that says that the Document is released under this License.</P
+><P
+>The "Cover Texts" are certain short passages of text that are
+ listed, as Front-Cover Texts or Back-Cover Texts, in the notice that says
+ that the Document is released under this License.</P
+><P
+>A "Transparent" copy of the Document means a machine-readable copy,
+ represented in a format whose specification is available to the general
+ public, whose contents can be viewed and edited directly and
+ straightforwardly with generic text editors or (for images composed of
+ pixels) generic paint programs or (for drawings) some widely available
+ drawing editor, and that is suitable for input to text formatters or for
+ automatic translation to a variety of formats suitable for input to text
+ formatters. A copy made in an otherwise Transparent file format whose
+ markup has been designed to thwart or discourage subsequent modification
+ by readers is not Transparent. A copy that is not "Transparent" is called
+ "Opaque".</P
+><P
+>Examples of suitable formats for Transparent copies include plain
+ ASCII without markup, Texinfo input format, LaTeX input format, SGML or
+ XML using a publicly available DTD, and standard-conforming simple HTML
+ designed for human modification. Opaque formats include PostScript, PDF,
+ proprietary formats that can be read and edited only by proprietary word
+ processors, SGML or XML for which the DTD and/or processing tools are not
+ generally available, and the machine-generated HTML produced by some word
+ processors for output purposes only.</P
+><P
+>The "Title Page" means, for a printed book, the title page itself,
+ plus such following pages as are needed to hold, legibly, the material
+ this License requires to appear in the title page. For works in formats
+ which do not have any title page as such, "Title Page" means the text
+ near the most prominent appearance of the work's title, preceding the
+ beginning of the body of the text.</P
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="gfdl-0.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="gfdl-2.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Preamble</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="gfdl.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Verbatim Copying</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/gfdl-10.html b/docs/en/html/gfdl-10.html
new file mode 100644
index 000000000..4c82f6ec3
--- /dev/null
+++ b/docs/en/html/gfdl-10.html
@@ -0,0 +1,165 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Future Revisions of this License</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="GNU Free Documentation License"
+HREF="gfdl.html"><LINK
+REL="PREVIOUS"
+TITLE="Termination"
+HREF="gfdl-9.html"><LINK
+REL="NEXT"
+TITLE="How to use this License for your documents"
+HREF="gfdl-howto.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="gfdl-9.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Appendix D. GNU Free Documentation License</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="gfdl-howto.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="gfdl-10"
+>10. Future Revisions of this License</A
+></H1
+><P
+>The Free Software Foundation may publish new, revised versions of
+ the GNU Free Documentation License from time to time. Such new versions
+ will be similar in spirit to the present version, but may differ in
+ detail to address new problems or concerns. See
+ <A
+HREF="http://www.gnu.org/copyleft/"
+TARGET="_top"
+>http://www.gnu.org/copyleft/</A
+>.</P
+><P
+>Each version of the License is given a distinguishing version
+ number. If the Document specifies that a particular numbered version of
+ this License "or any later version" applies to it, you have the option of
+ following the terms and conditions either of that specified version or of
+ any later version that has been published (not as a draft) by the Free
+ Software Foundation. If the Document does not specify a version number of
+ this License, you may choose any version ever published (not as a draft)
+ by the Free Software Foundation.</P
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="gfdl-9.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="gfdl-howto.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Termination</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="gfdl.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>How to use this License for your documents</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/gfdl-2.html b/docs/en/html/gfdl-2.html
new file mode 100644
index 000000000..064da138e
--- /dev/null
+++ b/docs/en/html/gfdl-2.html
@@ -0,0 +1,159 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Verbatim Copying</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="GNU Free Documentation License"
+HREF="gfdl.html"><LINK
+REL="PREVIOUS"
+TITLE="Applicability and Definition"
+HREF="gfdl-1.html"><LINK
+REL="NEXT"
+TITLE="Copying in Quantity"
+HREF="gfdl-3.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="gfdl-1.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Appendix D. GNU Free Documentation License</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="gfdl-3.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="gfdl-2"
+>2. Verbatim Copying</A
+></H1
+><P
+>You may copy and distribute the Document in any medium, either
+ commercially or noncommercially, provided that this License, the
+ copyright notices, and the license notice saying this License applies to
+ the Document are reproduced in all copies, and that you add no other
+ conditions whatsoever to those of this License. You may not use technical
+ measures to obstruct or control the reading or further copying of the
+ copies you make or distribute. However, you may accept compensation in
+ exchange for copies. If you distribute a large enough number of copies
+ you must also follow the conditions in section 3.</P
+><P
+>You may also lend copies, under the same conditions stated above,
+ and you may publicly display copies.</P
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="gfdl-1.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="gfdl-3.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Applicability and Definition</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="gfdl.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Copying in Quantity</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/gfdl-3.html b/docs/en/html/gfdl-3.html
new file mode 100644
index 000000000..187521ce2
--- /dev/null
+++ b/docs/en/html/gfdl-3.html
@@ -0,0 +1,181 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Copying in Quantity</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="GNU Free Documentation License"
+HREF="gfdl.html"><LINK
+REL="PREVIOUS"
+TITLE="Verbatim Copying"
+HREF="gfdl-2.html"><LINK
+REL="NEXT"
+TITLE="Modifications"
+HREF="gfdl-4.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="gfdl-2.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Appendix D. GNU Free Documentation License</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="gfdl-4.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="gfdl-3"
+>3. Copying in Quantity</A
+></H1
+><P
+>If you publish printed copies of the Document numbering more than
+ 100, and the Document's license notice requires Cover Texts, you must
+ enclose the copies in covers that carry, clearly and legibly, all these
+ Cover Texts: Front-Cover Texts on the front cover, and Back-Cover Texts
+ on the back cover. Both covers must also clearly and legibly identify you
+ as the publisher of these copies. The front cover must present the full
+ title with all words of the title equally prominent and visible. You may
+ add other material on the covers in addition. Copying with changes
+ limited to the covers, as long as they preserve the title of the Document
+ and satisfy these conditions, can be treated as verbatim copying in other
+ respects.</P
+><P
+>If the required texts for either cover are too voluminous to fit
+ legibly, you should put the first ones listed (as many as fit reasonably)
+ on the actual cover, and continue the rest onto adjacent pages.</P
+><P
+>If you publish or distribute Opaque copies of the Document
+ numbering more than 100, you must either include a machine-readable
+ Transparent copy along with each Opaque copy, or state in or with each
+ Opaque copy a publicly-accessible computer-network location containing a
+ complete Transparent copy of the Document, free of added material, which
+ the general network-using public has access to download anonymously at no
+ charge using public-standard network protocols. If you use the latter
+ option, you must take reasonably prudent steps, when you begin
+ distribution of Opaque copies in quantity, to ensure that this
+ Transparent copy will remain thus accessible at the stated location until
+ at least one year after the last time you distribute an Opaque copy
+ (directly or through your agents or retailers) of that edition to the
+ public.</P
+><P
+>It is requested, but not required, that you contact the authors of
+ the Document well before redistributing any large number of copies, to
+ give them a chance to provide you with an updated version of the
+ Document.</P
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="gfdl-2.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="gfdl-4.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Verbatim Copying</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="gfdl.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Modifications</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/gfdl-4.html b/docs/en/html/gfdl-4.html
new file mode 100644
index 000000000..e78008ac8
--- /dev/null
+++ b/docs/en/html/gfdl-4.html
@@ -0,0 +1,275 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Modifications</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="GNU Free Documentation License"
+HREF="gfdl.html"><LINK
+REL="PREVIOUS"
+TITLE="Copying in Quantity"
+HREF="gfdl-3.html"><LINK
+REL="NEXT"
+TITLE="Combining Documents"
+HREF="gfdl-5.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="gfdl-3.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Appendix D. GNU Free Documentation License</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="gfdl-5.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="gfdl-4"
+>4. Modifications</A
+></H1
+><P
+>You may copy and distribute a Modified Version of the Document
+ under the conditions of sections 2 and 3 above, provided that you release
+ the Modified Version under precisely this License, with the Modified
+ Version filling the role of the Document, thus licensing distribution and
+ modification of the Modified Version to whoever possesses a copy of it.
+ In addition, you must do these things in the Modified Version:</P
+><P
+></P
+><OL
+TYPE="A"
+><LI
+><P
+>Use in the Title Page (and on the covers, if any) a title
+ distinct from that of the Document, and from those of previous
+ versions (which should, if there were any, be listed in the History
+ section of the Document). You may use the same title as a previous
+ version if the original publisher of that version gives
+ permission.</P
+></LI
+><LI
+><P
+>List on the Title Page, as authors, one or more persons or
+ entities responsible for authorship of the modifications in the
+ Modified Version, together with at least five of the principal
+ authors of the Document (all of its principal authors, if it has less
+ than five).</P
+></LI
+><LI
+><P
+>State on the Title page the name of the publisher of the
+ Modified Version, as the publisher.</P
+></LI
+><LI
+><P
+>Preserve all the copyright notices of the Document.</P
+></LI
+><LI
+><P
+>Add an appropriate copyright notice for your modifications
+ adjacent to the other copyright notices.</P
+></LI
+><LI
+><P
+>Include, immediately after the copyright notices, a license
+ notice giving the public permission to use the Modified Version under
+ the terms of this License, in the form shown in the Addendum
+ below.</P
+></LI
+><LI
+><P
+>Preserve in that license notice the full lists of Invariant
+ Sections and required Cover Texts given in the Document's license
+ notice.</P
+></LI
+><LI
+><P
+>Include an unaltered copy of this License.</P
+></LI
+><LI
+><P
+>Preserve the section entitled "History", and its title, and add
+ to it an item stating at least the title, year, new authors, and
+ publisher of the Modified Version as given on the Title Page. If
+ there is no section entitled "History" in the Document, create one
+ stating the title, year, authors, and publisher of the Document as
+ given on its Title Page, then add an item describing the Modified
+ Version as stated in the previous sentence.</P
+></LI
+><LI
+><P
+>Preserve the network location, if any, given in the Document
+ for public access to a Transparent copy of the Document, and likewise
+ the network locations given in the Document for previous versions it
+ was based on. These may be placed in the "History" section. You may
+ omit a network location for a work that was published at least four
+ years before the Document itself, or if the original publisher of the
+ version it refers to gives permission.</P
+></LI
+><LI
+><P
+>In any section entitled "Acknowledgements" or "Dedications",
+ preserve the section's title, and preserve in the section all the
+ substance and tone of each of the contributor acknowledgements and/or
+ dedications given therein.</P
+></LI
+><LI
+><P
+>Preserve all the Invariant Sections of the Document, unaltered
+ in their text and in their titles. Section numbers or the equivalent
+ are not considered part of the section titles.</P
+></LI
+><LI
+><P
+>Delete any section entitled "Endorsements". Such a section may
+ not be included in the Modified Version.</P
+></LI
+><LI
+><P
+>Do not retitle any existing section as "Endorsements" or to
+ conflict in title with any Invariant Section.</P
+></LI
+></OL
+><P
+>If the Modified Version includes new front-matter sections or
+ appendices that qualify as Secondary Sections and contain no material
+ copied from the Document, you may at your option designate some or all of
+ these sections as invariant. To do this, add their titles to the list of
+ Invariant Sections in the Modified Version's license notice. These titles
+ must be distinct from any other section titles.</P
+><P
+>You may add a section entitled "Endorsements", provided it contains
+ nothing but endorsements of your Modified Version by various parties--for
+ example, statements of peer review or that the text has been approved by
+ an organization as the authoritative definition of a standard.</P
+><P
+>You may add a passage of up to five words as a Front-Cover Text,
+ and a passage of up to 25 words as a Back-Cover Text, to the end of the
+ list of Cover Texts in the Modified Version. Only one passage of
+ Front-Cover Text and one of Back-Cover Text may be added by (or through
+ arrangements made by) any one entity. If the Document already includes a
+ cover text for the same cover, previously added by you or by arrangement
+ made by the same entity you are acting on behalf of, you may not add
+ another; but you may replace the old one, on explicit permission from the
+ previous publisher that added the old one.</P
+><P
+>The author(s) and publisher(s) of the Document do not by this
+ License give permission to use their names for publicity for or to assert
+ or imply endorsement of any Modified Version.</P
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="gfdl-3.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="gfdl-5.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Copying in Quantity</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="gfdl.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Combining Documents</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/gfdl-5.html b/docs/en/html/gfdl-5.html
new file mode 100644
index 000000000..59e621921
--- /dev/null
+++ b/docs/en/html/gfdl-5.html
@@ -0,0 +1,168 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Combining Documents</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="GNU Free Documentation License"
+HREF="gfdl.html"><LINK
+REL="PREVIOUS"
+TITLE="Modifications"
+HREF="gfdl-4.html"><LINK
+REL="NEXT"
+TITLE="Collections of Documents"
+HREF="gfdl-6.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="gfdl-4.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Appendix D. GNU Free Documentation License</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="gfdl-6.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="gfdl-5"
+>5. Combining Documents</A
+></H1
+><P
+>You may combine the Document with other documents released under
+ this License, under the terms defined in section 4 above for modified
+ versions, provided that you include in the combination all of the
+ Invariant Sections of all of the original documents, unmodified, and list
+ them all as Invariant Sections of your combined work in its license
+ notice.</P
+><P
+>The combined work need only contain one copy of this License, and
+ multiple identical Invariant Sections may be replaced with a single copy.
+ If there are multiple Invariant Sections with the same name but different
+ contents, make the title of each such section unique by adding at the end
+ of it, in parentheses, the name of the original author or publisher of
+ that section if known, or else a unique number. Make the same adjustment
+ to the section titles in the list of Invariant Sections in the license
+ notice of the combined work.</P
+><P
+>In the combination, you must combine any sections entitled
+ "History" in the various original documents, forming one section entitled
+ "History"; likewise combine any sections entitled "Acknowledgements", and
+ any sections entitled "Dedications". You must delete all sections
+ entitled "Endorsements."</P
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="gfdl-4.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="gfdl-6.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Modifications</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="gfdl.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Collections of Documents</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/gfdl-6.html b/docs/en/html/gfdl-6.html
new file mode 100644
index 000000000..852deb49e
--- /dev/null
+++ b/docs/en/html/gfdl-6.html
@@ -0,0 +1,158 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Collections of Documents</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="GNU Free Documentation License"
+HREF="gfdl.html"><LINK
+REL="PREVIOUS"
+TITLE="Combining Documents"
+HREF="gfdl-5.html"><LINK
+REL="NEXT"
+TITLE="Aggregation with Independent Works"
+HREF="gfdl-7.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="gfdl-5.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Appendix D. GNU Free Documentation License</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="gfdl-7.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="gfdl-6"
+>6. Collections of Documents</A
+></H1
+><P
+>You may make a collection consisting of the Document and other
+ documents released under this License, and replace the individual copies
+ of this License in the various documents with a single copy that is
+ included in the collection, provided that you follow the rules of this
+ License for verbatim copying of each of the documents in all other
+ respects.</P
+><P
+>You may extract a single document from such a collection, and
+ distribute it individually under this License, provided you insert a copy
+ of this License into the extracted document, and follow this License in
+ all other respects regarding verbatim copying of that document.</P
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="gfdl-5.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="gfdl-7.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Combining Documents</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="gfdl.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Aggregation with Independent Works</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/gfdl-7.html b/docs/en/html/gfdl-7.html
new file mode 100644
index 000000000..83fc0c9b0
--- /dev/null
+++ b/docs/en/html/gfdl-7.html
@@ -0,0 +1,161 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Aggregation with Independent Works</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="GNU Free Documentation License"
+HREF="gfdl.html"><LINK
+REL="PREVIOUS"
+TITLE="Collections of Documents"
+HREF="gfdl-6.html"><LINK
+REL="NEXT"
+TITLE="Translation"
+HREF="gfdl-8.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="gfdl-6.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Appendix D. GNU Free Documentation License</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="gfdl-8.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="gfdl-7"
+>7. Aggregation with Independent Works</A
+></H1
+><P
+>A compilation of the Document or its derivatives with other
+ separate and independent documents or works, in or on a volume of a
+ storage or distribution medium, does not as a whole count as a Modified
+ Version of the Document, provided no compilation copyright is claimed for
+ the compilation. Such a compilation is called an "aggregate", and this
+ License does not apply to the other self-contained works thus compiled
+ with the Document, on account of their being thus compiled, if they are
+ not themselves derivative works of the Document.</P
+><P
+>If the Cover Text requirement of section 3 is applicable to these
+ copies of the Document, then if the Document is less than one quarter of
+ the entire aggregate, the Document's Cover Texts may be placed on covers
+ that surround only the Document within the aggregate. Otherwise they must
+ appear on covers around the whole aggregate.</P
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="gfdl-6.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="gfdl-8.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Collections of Documents</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="gfdl.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Translation</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/gfdl-8.html b/docs/en/html/gfdl-8.html
new file mode 100644
index 000000000..c15ffc6c2
--- /dev/null
+++ b/docs/en/html/gfdl-8.html
@@ -0,0 +1,157 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Translation</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="GNU Free Documentation License"
+HREF="gfdl.html"><LINK
+REL="PREVIOUS"
+TITLE="Aggregation with Independent Works"
+HREF="gfdl-7.html"><LINK
+REL="NEXT"
+TITLE="Termination"
+HREF="gfdl-9.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="gfdl-7.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Appendix D. GNU Free Documentation License</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="gfdl-9.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="gfdl-8"
+>8. Translation</A
+></H1
+><P
+>Translation is considered a kind of modification, so you may
+ distribute translations of the Document under the terms of section 4.
+ Replacing Invariant Sections with translations requires special
+ permission from their copyright holders, but you may include translations
+ of some or all Invariant Sections in addition to the original versions of
+ these Invariant Sections. You may include a translation of this License
+ provided that you also include the original English version of this
+ License. In case of a disagreement between the translation and the
+ original English version of this License, the original English version
+ will prevail.</P
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="gfdl-7.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="gfdl-9.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Aggregation with Independent Works</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="gfdl.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Termination</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/gfdl-9.html b/docs/en/html/gfdl-9.html
new file mode 100644
index 000000000..6fe5b9c53
--- /dev/null
+++ b/docs/en/html/gfdl-9.html
@@ -0,0 +1,154 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Termination</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="GNU Free Documentation License"
+HREF="gfdl.html"><LINK
+REL="PREVIOUS"
+TITLE="Translation"
+HREF="gfdl-8.html"><LINK
+REL="NEXT"
+TITLE="Future Revisions of this License"
+HREF="gfdl-10.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="gfdl-8.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Appendix D. GNU Free Documentation License</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="gfdl-10.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="gfdl-9"
+>9. Termination</A
+></H1
+><P
+>You may not copy, modify, sublicense, or distribute the Document
+ except as expressly provided for under this License. Any other attempt to
+ copy, modify, sublicense or distribute the Document is void, and will
+ automatically terminate your rights under this License. However, parties
+ who have received copies, or rights, from you under this License will not
+ have their licenses terminated so long as such parties remain in full
+ compliance.</P
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="gfdl-8.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="gfdl-10.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Translation</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="gfdl.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Future Revisions of this License</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/gfdl-howto.html b/docs/en/html/gfdl-howto.html
new file mode 100644
index 000000000..e5bf8334c
--- /dev/null
+++ b/docs/en/html/gfdl-howto.html
@@ -0,0 +1,174 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>How to use this License for your documents</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="GNU Free Documentation License"
+HREF="gfdl.html"><LINK
+REL="PREVIOUS"
+TITLE="Future Revisions of this License"
+HREF="gfdl-10.html"><LINK
+REL="NEXT"
+TITLE="Glossary"
+HREF="glossary.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="gfdl-10.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Appendix D. GNU Free Documentation License</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="glossary.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="gfdl-howto"
+>How to use this License for your documents</A
+></H1
+><P
+>To use this License in a document you have written, include a copy
+ of the License in the document and put the following copyright and
+ license notices just after the title page:</P
+><A
+NAME="AEN3345"
+></A
+><BLOCKQUOTE
+CLASS="BLOCKQUOTE"
+><P
+>Copyright (c) YEAR YOUR NAME. Permission is granted to copy,
+ distribute and/or modify this document under the terms of the GNU Free
+ Documentation License, Version 1.1 or any later version published by
+ the Free Software Foundation; with the Invariant Sections being LIST
+ THEIR TITLES, with the Front-Cover Texts being LIST, and with the
+ Back-Cover Texts being LIST. A copy of the license is included in the
+ section entitled "GNU Free Documentation License".</P
+></BLOCKQUOTE
+><P
+>If you have no Invariant Sections, write "with no Invariant
+ Sections" instead of saying which ones are invariant. If you have no
+ Front-Cover Texts, write "no Front-Cover Texts" instead of "Front-Cover
+ Texts being LIST"; likewise for Back-Cover Texts.</P
+><P
+>If your document contains nontrivial examples of program code, we
+ recommend releasing these examples in parallel under your choice of free
+ software license, such as the GNU General Public License, to permit their
+ use in free software.</P
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="gfdl-10.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="glossary.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Future Revisions of this License</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="gfdl.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Glossary</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/gfdl.html b/docs/en/html/gfdl.html
new file mode 100644
index 000000000..5db1c83d4
--- /dev/null
+++ b/docs/en/html/gfdl.html
@@ -0,0 +1,220 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>GNU Free Documentation License</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="PREVIOUS"
+TITLE="Optional Modules"
+HREF="modules-manual-optional.html"><LINK
+REL="NEXT"
+TITLE="Preamble"
+HREF="gfdl-0.html"></HEAD
+><BODY
+CLASS="appendix"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="modules-manual-optional.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+></TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="gfdl-0.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="appendix"
+><H1
+><A
+NAME="gfdl"
+></A
+>Appendix D. GNU Free Documentation License</H1
+><DIV
+CLASS="TOC"
+><DL
+><DT
+><B
+>Table of Contents</B
+></DT
+><DT
+>0. <A
+HREF="gfdl-0.html"
+>Preamble</A
+></DT
+><DT
+>1. <A
+HREF="gfdl-1.html"
+>Applicability and Definition</A
+></DT
+><DT
+>2. <A
+HREF="gfdl-2.html"
+>Verbatim Copying</A
+></DT
+><DT
+>3. <A
+HREF="gfdl-3.html"
+>Copying in Quantity</A
+></DT
+><DT
+>4. <A
+HREF="gfdl-4.html"
+>Modifications</A
+></DT
+><DT
+>5. <A
+HREF="gfdl-5.html"
+>Combining Documents</A
+></DT
+><DT
+>6. <A
+HREF="gfdl-6.html"
+>Collections of Documents</A
+></DT
+><DT
+>7. <A
+HREF="gfdl-7.html"
+>Aggregation with Independent Works</A
+></DT
+><DT
+>8. <A
+HREF="gfdl-8.html"
+>Translation</A
+></DT
+><DT
+>9. <A
+HREF="gfdl-9.html"
+>Termination</A
+></DT
+><DT
+>10. <A
+HREF="gfdl-10.html"
+>Future Revisions of this License</A
+></DT
+><DT
+><A
+HREF="gfdl-howto.html"
+>How to use this License for your documents</A
+></DT
+></DL
+></DIV
+><P
+>Version 1.1, March 2000</P
+><A
+NAME="AEN3255"
+></A
+><BLOCKQUOTE
+CLASS="BLOCKQUOTE"
+><P
+>Copyright (C) 2000 Free Software Foundation, Inc. 59 Temple Place,
+ Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and
+ distribute verbatim copies of this license document, but changing it is
+ not allowed.</P
+></BLOCKQUOTE
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="modules-manual-optional.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="gfdl-0.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Optional Modules</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+>&nbsp;</TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Preamble</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/glossary.html b/docs/en/html/glossary.html
new file mode 100644
index 000000000..ad06e44a4
--- /dev/null
+++ b/docs/en/html/glossary.html
@@ -0,0 +1,1081 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Glossary</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="PREVIOUS"
+TITLE="How to use this License for your documents"
+HREF="gfdl-howto.html"></HEAD
+><BODY
+CLASS="glossary"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="gfdl-howto.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+></TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+>&nbsp;</TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="GLOSSARY"
+><H1
+><A
+NAME="glossary"
+></A
+>Glossary</H1
+><DIV
+CLASS="glossdiv"
+><H1
+CLASS="glossdiv"
+><A
+NAME="AEN3350"
+>0-9, high ascii</A
+></H1
+><DL
+><DT
+><A
+NAME="gloss-htaccess"
+></A
+><B
+>.htaccess</B
+></DT
+><DD
+><P
+>Apache web server, and other NCSA-compliant web servers,
+ observe the convention of using files in directories called
+ <TT
+CLASS="filename"
+>.htaccess</TT
+>
+
+ to restrict access to certain files. In Bugzilla, they are used
+ to keep secret files which would otherwise
+ compromise your installation - e.g. the
+ <TT
+CLASS="filename"
+>localconfig</TT
+>
+ file contains the password to your database.
+ curious.</P
+></DD
+></DL
+></DIV
+><DIV
+CLASS="glossdiv"
+><H1
+CLASS="glossdiv"
+><A
+NAME="gloss-a"
+>A</A
+></H1
+><DL
+><DT
+><A
+NAME="gloss-apache"
+></A
+><B
+>Apache</B
+></DT
+><DD
+><P
+>In this context, Apache is the web server most commonly used
+ for serving up Bugzilla
+ pages. Contrary to popular belief, the apache web server has nothing
+ to do with the ancient and noble Native American tribe, but instead
+ derived its name from the fact that it was
+ <SPAN
+CLASS="QUOTE"
+>"a patchy"</SPAN
+>
+ version of the original
+ <ACRONYM
+CLASS="acronym"
+>NCSA</ACRONYM
+>
+ world-wide-web server.</P
+><P
+></P
+><DIV
+CLASS="variablelist"
+><P
+><B
+>Useful Directives when configuring Bugzilla</B
+></P
+><DL
+><DT
+><SAMP
+CLASS="computeroutput"
+><A
+HREF="http://httpd.apache.org/docs-2.0/mod/core.html#addhandler"
+TARGET="_top"
+>AddHandler</A
+></SAMP
+></DT
+><DD
+><P
+>Tell Apache that it's OK to run CGI scripts.</P
+></DD
+><DT
+><SAMP
+CLASS="computeroutput"
+><A
+HREF="http://httpd.apache.org/docs-2.0/mod/core.html#allowoverride"
+TARGET="_top"
+>AllowOverride</A
+></SAMP
+>, <SAMP
+CLASS="computeroutput"
+><A
+HREF="http://httpd.apache.org/docs-2.0/mod/core.html#options"
+TARGET="_top"
+>Options</A
+></SAMP
+></DT
+><DD
+><P
+>These directives are used to tell Apache many things about
+ the directory they apply to. For Bugzilla's purposes, we need
+ them to allow script execution and <TT
+CLASS="filename"
+>.htaccess</TT
+>
+ overrides.
+ </P
+></DD
+><DT
+><SAMP
+CLASS="computeroutput"
+><A
+HREF="http://httpd.apache.org/docs-2.0/mod/mod_dir.html#directoryindex"
+TARGET="_top"
+>DirectoryIndex</A
+></SAMP
+></DT
+><DD
+><P
+>Used to tell Apache what files are indexes. If you can
+ not add <TT
+CLASS="filename"
+>index.cgi</TT
+> to the list of valid files,
+ you'll need to set <SAMP
+CLASS="computeroutput"
+>$index_html</SAMP
+> to
+ 1 in <TT
+CLASS="filename"
+>localconfig</TT
+> so
+ <B
+CLASS="command"
+>./checksetup.pl</B
+> will create an
+ <TT
+CLASS="filename"
+>index.html</TT
+> that redirects to
+ <TT
+CLASS="filename"
+>index.cgi</TT
+>.
+ </P
+></DD
+><DT
+><SAMP
+CLASS="computeroutput"
+><A
+HREF="http://httpd.apache.org/docs-2.0/mod/core.html#scriptinterpretersource"
+TARGET="_top"
+>ScriptInterpreterSource</A
+></SAMP
+></DT
+><DD
+><P
+>Used when running Apache on windows so the shebang line
+ doesn't have to be changed in every Bugzilla script.
+ </P
+></DD
+></DL
+></DIV
+><P
+>For more information about how to configure Apache for Bugzilla,
+ see <A
+HREF="configuration.html#http-apache"
+>Section 2.2.4.1</A
+>.
+ </P
+></DD
+></DL
+></DIV
+><DIV
+CLASS="glossdiv"
+><H1
+CLASS="glossdiv"
+><A
+NAME="gloss-b"
+>B</A
+></H1
+><DL
+><DT
+><B
+>Bug</B
+></DT
+><DD
+><P
+>A
+ <SPAN
+CLASS="QUOTE"
+>"bug"</SPAN
+>
+
+ in Bugzilla refers to an issue entered into the database which has an
+ associated number, assignments, comments, etc. Some also refer to a
+ <SPAN
+CLASS="QUOTE"
+>"tickets"</SPAN
+>
+ or
+ <SPAN
+CLASS="QUOTE"
+>"issues"</SPAN
+>;
+ in the context of Bugzilla, they are synonymous.</P
+></DD
+><DT
+><B
+>Bug Number</B
+></DT
+><DD
+><P
+>Each Bugzilla bug is assigned a number that uniquely identifies
+ that bug. The bug associated with a bug number can be pulled up via a
+ query, or easily from the very front page by typing the number in the
+ "Find" box.</P
+></DD
+><DT
+><A
+NAME="gloss-bugzilla"
+></A
+><B
+>Bugzilla</B
+></DT
+><DD
+><P
+>Bugzilla is the world-leading free software bug tracking system.
+ </P
+></DD
+></DL
+></DIV
+><DIV
+CLASS="glossdiv"
+><H1
+CLASS="glossdiv"
+><A
+NAME="gloss-c"
+>C</A
+></H1
+><DL
+><DT
+><A
+NAME="gloss-cgi"
+></A
+><B
+>Common Gateway Interface</B
+></DT
+> (CGI)<DD
+><P
+><ACRONYM
+CLASS="acronym"
+>CGI</ACRONYM
+> is an acronym for Common Gateway Interface. This is
+ a standard for interfacing an external application with a web server. Bugzilla
+ is an example of a <ACRONYM
+CLASS="acronym"
+>CGI</ACRONYM
+> application.
+ </P
+></DD
+><DT
+><A
+NAME="gloss-component"
+></A
+><B
+>Component</B
+></DT
+><DD
+><P
+>A Component is a subsection of a Product. It should be a narrow
+ category, tailored to your organization. All Products must contain at
+ least one Component (and, as a matter of fact, creating a Product
+ with no Components will create an error in Bugzilla).</P
+></DD
+><DT
+><A
+NAME="gloss-cpan"
+></A
+><B
+>Comprehensive Perl Archive Network</B
+></DT
+> (CPAN)<DD
+><P
+>&#13; <ACRONYM
+CLASS="acronym"
+>CPAN</ACRONYM
+>
+
+ stands for the
+ <SPAN
+CLASS="QUOTE"
+>"Comprehensive Perl Archive Network"</SPAN
+>.
+ CPAN maintains a large number of extremely useful
+ <I
+CLASS="glossterm"
+>Perl</I
+>
+ modules - encapsulated chunks of code for performing a
+ particular task.</P
+></DD
+><DT
+><A
+NAME="gloss-contrib"
+></A
+><B
+><TT
+CLASS="filename"
+>contrib</TT
+></B
+></DT
+><DD
+><P
+>The <TT
+CLASS="filename"
+>contrib</TT
+> directory is
+ a location to put scripts that have been contributed to Bugzilla but
+ are not a part of the official distribution. These scripts are written
+ by third parties and may be in languages other than perl. For those
+ that are in perl, there may be additional modules or other requirements
+ than those of the official distribution.
+ <DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>Scripts in the <TT
+CLASS="filename"
+>contrib</TT
+>
+ directory are not officially supported by the Bugzilla team and may
+ break in between versions.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+>
+ </P
+></DD
+></DL
+></DIV
+><DIV
+CLASS="glossdiv"
+><H1
+CLASS="glossdiv"
+><A
+NAME="gloss-d"
+>D</A
+></H1
+><DL
+><DT
+><A
+NAME="gloss-daemon"
+></A
+><B
+>daemon</B
+></DT
+><DD
+><P
+>A daemon is a computer program which runs in the background. In
+ general, most daemons are started at boot time via System V init
+ scripts, or through RC scripts on BSD-based systems.
+ <I
+CLASS="glossterm"
+>mysqld</I
+>,
+ the MySQL server, and
+ <I
+CLASS="glossterm"
+>apache</I
+>,
+ a web server, are generally run as daemons.</P
+></DD
+><DT
+><A
+NAME="gloss-dos"
+></A
+><B
+>DOS Attack</B
+></DT
+><DD
+><P
+>A DOS, or Denial of Service attack, is when a user attempts to
+ deny access to a web server by repeatedly accessing a page or sending
+ malformed requests to a webserver. A D-DOS, or
+ Distributed Denial of Service attack, is when these requests come
+ from multiple sources at the same time. Unfortunately, these are much
+ more difficult to defend against.
+ </P
+></DD
+></DL
+></DIV
+><DIV
+CLASS="glossdiv"
+><H1
+CLASS="glossdiv"
+><A
+NAME="gloss-g"
+>G</A
+></H1
+><DL
+><DT
+><A
+NAME="gloss-groups"
+></A
+><B
+>Groups</B
+></DT
+><DD
+><P
+>The word
+ <SPAN
+CLASS="QUOTE"
+>"Groups"</SPAN
+>
+
+ has a very special meaning to Bugzilla. Bugzilla's main security
+ mechanism comes by placing users in groups, and assigning those
+ groups certain privileges to view bugs in particular
+ <I
+CLASS="glossterm"
+>Products</I
+>
+ in the
+ <I
+CLASS="glossterm"
+>Bugzilla</I
+>
+ database.</P
+></DD
+></DL
+></DIV
+><DIV
+CLASS="glossdiv"
+><H1
+CLASS="glossdiv"
+><A
+NAME="gloss-j"
+>J</A
+></H1
+><DL
+><DT
+><A
+NAME="gloss-javascript"
+></A
+><B
+>JavaScript</B
+></DT
+><DD
+><P
+>JavaScript is cool, we should talk about it.
+ </P
+></DD
+></DL
+></DIV
+><DIV
+CLASS="glossdiv"
+><H1
+CLASS="glossdiv"
+><A
+NAME="gloss-m"
+>M</A
+></H1
+><DL
+><DT
+><A
+NAME="gloss-mta"
+></A
+><B
+>Message Transport Agent</B
+></DT
+> (MTA)<DD
+><P
+>A Message Transport Agent is used to control the flow of email on a system.
+ The <A
+HREF="http://search.cpan.org/dist/Email-Send/lib/Email/Send.pm"
+TARGET="_top"
+>Email::Send</A
+>
+ Perl module, which Bugzilla uses to send email, can be configured to
+ use many different underlying implementations for actually sending the
+ mail using the <CODE
+CLASS="option"
+>mail_delivery_method</CODE
+> parameter.
+ </P
+></DD
+><DT
+><A
+NAME="gloss-mysql"
+></A
+><B
+>MySQL</B
+></DT
+><DD
+><P
+>MySQL is currently the required
+ <A
+HREF="glossary.html#gloss-rdbms"
+><I
+CLASS="glossterm"
+>RDBMS</I
+></A
+> for Bugzilla. MySQL
+ can be downloaded from <A
+HREF="http://www.mysql.com"
+TARGET="_top"
+>http://www.mysql.com</A
+>. While you
+ should familiarize yourself with all of the documentation, some high
+ points are:
+ </P
+><P
+></P
+><DIV
+CLASS="variablelist"
+><DL
+><DT
+><A
+HREF="http://www.mysql.com/doc/en/Backup.html"
+TARGET="_top"
+>Backup</A
+></DT
+><DD
+><P
+>Methods for backing up your Bugzilla database.
+ </P
+></DD
+><DT
+><A
+HREF="http://www.mysql.com/doc/en/Option_files.html"
+TARGET="_top"
+>Option Files</A
+></DT
+><DD
+><P
+>Information about how to configure MySQL using
+ <TT
+CLASS="filename"
+>my.cnf</TT
+>.
+ </P
+></DD
+><DT
+><A
+HREF="http://www.mysql.com/doc/en/Privilege_system.html"
+TARGET="_top"
+>Privilege System</A
+></DT
+><DD
+><P
+>Information about how to protect your MySQL server.
+ </P
+></DD
+></DL
+></DIV
+></DD
+></DL
+></DIV
+><DIV
+CLASS="glossdiv"
+><H1
+CLASS="glossdiv"
+><A
+NAME="gloss-p"
+>P</A
+></H1
+><DL
+><DT
+><A
+NAME="gloss-ppm"
+></A
+><B
+>Perl Package Manager</B
+></DT
+> (PPM)<DD
+><P
+><A
+HREF="http://aspn.activestate.com/ASPN/Downloads/ActivePerl/PPM/"
+TARGET="_top"
+>http://aspn.activestate.com/ASPN/Downloads/ActivePerl/PPM/</A
+>
+ </P
+></DD
+><DT
+><B
+>Product</B
+></DT
+><DD
+><P
+>A Product is a broad category of types of bugs, normally
+ representing a single piece of software or entity. In general,
+ there are several Components to a Product. A Product may define a
+ group (used for security) for all bugs entered into
+ its Components.</P
+></DD
+><DT
+><B
+>Perl</B
+></DT
+><DD
+><P
+>First written by Larry Wall, Perl is a remarkable program
+ language. It has the benefits of the flexibility of an interpreted
+ scripting language (such as shell script), combined with the speed
+ and power of a compiled language, such as C.
+ <I
+CLASS="glossterm"
+>Bugzilla</I
+>
+
+ is maintained in Perl.</P
+></DD
+></DL
+></DIV
+><DIV
+CLASS="glossdiv"
+><H1
+CLASS="glossdiv"
+><A
+NAME="gloss-q"
+>Q</A
+></H1
+><DL
+><DT
+><B
+>QA</B
+></DT
+><DD
+><P
+>&#13; <SPAN
+CLASS="QUOTE"
+>"QA"</SPAN
+>,
+ <SPAN
+CLASS="QUOTE"
+>"Q/A"</SPAN
+>, and
+ <SPAN
+CLASS="QUOTE"
+>"Q.A."</SPAN
+>
+ are short for
+ <SPAN
+CLASS="QUOTE"
+>"Quality Assurance"</SPAN
+>.
+ In most large software development organizations, there is a team
+ devoted to ensuring the product meets minimum standards before
+ shipping. This team will also generally want to track the progress of
+ bugs over their life cycle, thus the need for the
+ <SPAN
+CLASS="QUOTE"
+>"QA Contact"</SPAN
+>
+
+ field in a bug.</P
+></DD
+></DL
+></DIV
+><DIV
+CLASS="glossdiv"
+><H1
+CLASS="glossdiv"
+><A
+NAME="gloss-r"
+>R</A
+></H1
+><DL
+><DT
+><A
+NAME="gloss-rdbms"
+></A
+><B
+>Relational DataBase Management System</B
+></DT
+> (RDBMS)<DD
+><P
+>A relational database management system is a database system
+ that stores information in tables that are related to each other.
+ </P
+></DD
+><DT
+><A
+NAME="gloss-regexp"
+></A
+><B
+>Regular Expression</B
+></DT
+> (regexp)<DD
+><P
+>A regular expression is an expression used for pattern matching.
+ <A
+HREF="http://perldoc.com/perl5.6/pod/perlre.html#Regular-Expressions"
+TARGET="_top"
+>Documentation</A
+>
+ </P
+></DD
+></DL
+></DIV
+><DIV
+CLASS="glossdiv"
+><H1
+CLASS="glossdiv"
+><A
+NAME="gloss-s"
+>S</A
+></H1
+><DL
+><DT
+><A
+NAME="gloss-service"
+></A
+><B
+>Service</B
+></DT
+><DD
+><P
+>In Windows NT environment, a boot-time background application
+ is referred to as a service. These are generally managed through the
+ control panel while logged in as an account with
+ <SPAN
+CLASS="QUOTE"
+>"Administrator"</SPAN
+> level capabilities. For more
+ information, consult your Windows manual or the MSKB.
+ </P
+></DD
+><DT
+><B
+>&#13; <ACRONYM
+CLASS="acronym"
+>SGML</ACRONYM
+>
+ </B
+></DT
+><DD
+><P
+>&#13; <ACRONYM
+CLASS="acronym"
+>SGML</ACRONYM
+>
+
+ stands for
+ <SPAN
+CLASS="QUOTE"
+>"Standard Generalized Markup Language"</SPAN
+>.
+ Created in the 1980's to provide an extensible means to maintain
+ documentation based upon content instead of presentation,
+ <ACRONYM
+CLASS="acronym"
+>SGML</ACRONYM
+>
+
+ has withstood the test of time as a robust, powerful language.
+ <I
+CLASS="glossterm"
+>&#13; <ACRONYM
+CLASS="acronym"
+>XML</ACRONYM
+>
+ </I
+>
+
+ is the
+ <SPAN
+CLASS="QUOTE"
+>"baby brother"</SPAN
+>
+
+ of SGML; any valid
+ <ACRONYM
+CLASS="acronym"
+>XML</ACRONYM
+>
+
+ document it, by definition, a valid
+ <ACRONYM
+CLASS="acronym"
+>SGML</ACRONYM
+>
+
+ document. The document you are reading is written and maintained in
+ <ACRONYM
+CLASS="acronym"
+>SGML</ACRONYM
+>,
+ and is also valid
+ <ACRONYM
+CLASS="acronym"
+>XML</ACRONYM
+>
+
+ if you modify the Document Type Definition.</P
+></DD
+></DL
+></DIV
+><DIV
+CLASS="glossdiv"
+><H1
+CLASS="glossdiv"
+><A
+NAME="gloss-t"
+>T</A
+></H1
+><DL
+><DT
+><A
+NAME="gloss-target-milestone"
+></A
+><B
+>Target Milestone</B
+></DT
+><DD
+><P
+>Target Milestones are Product goals. They are configurable on a
+ per-Product basis. Most software development houses have a concept of
+
+ <SPAN
+CLASS="QUOTE"
+>"milestones"</SPAN
+>
+
+ where the people funding a project expect certain functionality on
+ certain dates. Bugzilla facilitates meeting these milestones by
+ giving you the ability to declare by which milestone a bug will be
+ fixed, or an enhancement will be implemented.</P
+></DD
+><DT
+><A
+NAME="gloss-tcl"
+></A
+><B
+>Tool Command Language</B
+></DT
+> (TCL)<DD
+><P
+>TCL is an open source scripting language available for Windows,
+ Macintosh, and Unix based systems. Bugzilla 1.0 was written in TCL but
+ never released. The first release of Bugzilla was 2.0, which was when
+ it was ported to perl.
+ </P
+></DD
+></DL
+></DIV
+><DIV
+CLASS="glossdiv"
+><H1
+CLASS="glossdiv"
+><A
+NAME="gloss-z"
+>Z</A
+></H1
+><DL
+><DT
+><A
+NAME="gloss-zarro"
+></A
+><B
+>Zarro Boogs Found</B
+></DT
+><DD
+><P
+>This is just a goofy way of saying that there were no bugs
+ found matching your query. When asked to explain this message,
+ Terry had the following to say:
+ </P
+><A
+NAME="AEN3591"
+></A
+><TABLE
+BORDER="0"
+WIDTH="100%"
+CELLSPACING="0"
+CELLPADDING="0"
+CLASS="BLOCKQUOTE"
+><TR
+><TD
+WIDTH="10%"
+VALIGN="TOP"
+>&nbsp;</TD
+><TD
+VALIGN="TOP"
+><P
+>I've been asked to explain this ... way back when, when
+ Netscape released version 4.0 of its browser, we had a release
+ party. Naturally, there had been a big push to try and fix every
+ known bug before the release. Naturally, that hadn't actually
+ happened. (This is not unique to Netscape or to 4.0; the same thing
+ has happened with every software project I've ever seen.) Anyway,
+ at the release party, T-shirts were handed out that said something
+ like "Netscape 4.0: Zarro Boogs". Just like the software, the
+ T-shirt had no known bugs. Uh-huh.
+ </P
+><P
+>So, when you query for a list of bugs, and it gets no results,
+ you can think of this as a friendly reminder. Of *course* there are
+ bugs matching your query, they just aren't in the bugsystem yet...
+ </P
+></TD
+><TD
+WIDTH="10%"
+VALIGN="TOP"
+>&nbsp;</TD
+></TR
+><TR
+><TD
+COLSPAN="2"
+ALIGN="RIGHT"
+VALIGN="TOP"
+>--<SPAN
+CLASS="attribution"
+>Terry Weissman</SPAN
+></TD
+><TD
+WIDTH="10%"
+>&nbsp;</TD
+></TR
+></TABLE
+></DD
+></DL
+></DIV
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="gfdl-howto.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>&nbsp;</TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>How to use this License for your documents</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+>&nbsp;</TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>&nbsp;</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/groups.html b/docs/en/html/groups.html
new file mode 100644
index 000000000..dd25828ce
--- /dev/null
+++ b/docs/en/html/groups.html
@@ -0,0 +1,587 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Groups and Group Security</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Administering Bugzilla"
+HREF="administration.html"><LINK
+REL="PREVIOUS"
+TITLE="Quips"
+HREF="quips.html"><LINK
+REL="NEXT"
+TITLE="Checking and Maintaining Database Integrity"
+HREF="sanitycheck.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="quips.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 3. Administering Bugzilla</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="sanitycheck.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="groups"
+>3.15. Groups and Group Security</A
+></H1
+><P
+>&#13; Groups allow for separating bugs into logical divisions.
+ Groups are typically used
+ to isolate bugs that should only be seen by certain people. For
+ example, a company might create a different group for each one of its customers
+ or partners. Group permissions could be set so that each partner or customer would
+ only have access to their own bugs. Or, groups might be used to create
+ variable access controls for different departments within an organization.
+ Another common use of groups is to associate groups with products,
+ creating isolation and access control on a per-product basis.
+ </P
+><P
+>&#13; Groups and group behaviors are controlled in several places:
+ </P
+><P
+></P
+><OL
+TYPE="1"
+><LI
+><P
+>&#13; The group configuration page. To view or edit existing groups, or to
+ create new groups, access the "Groups" link from the "Administration"
+ page. This section of the manual deals primarily with the aspect of
+ group controls accessed on this page.
+ </P
+></LI
+><LI
+><P
+>&#13; Global configuration parameters. Bugzilla has several parameters
+ that control the overall default group behavior and restriction
+ levels. For more information on the parameters that control
+ group behavior globally, see <A
+HREF="parameters.html#param-group-security"
+>Section 3.1.9</A
+>.
+ </P
+></LI
+><LI
+><P
+>&#13; Product association with groups. Most of the functionality of groups
+ and group security is controlled at the product level. Some aspects
+ of group access controls for products are discussed in this section,
+ but for more detail see <A
+HREF="products.html#product-group-controls"
+>Section 3.4.4</A
+>.
+ </P
+></LI
+><LI
+><P
+>&#13; Group access for users. See <A
+HREF="groups.html#users-and-groups"
+>Section 3.15.3</A
+> for
+ details on how users are assigned group access.
+ </P
+></LI
+></OL
+><P
+>&#13; Group permissions are such that if a bug belongs to a group, only members
+ of that group can see the bug. If a bug is in more than one group, only
+ members of <EM
+>all</EM
+> the groups that the bug is in can see
+ the bug. For information on granting read-only access to certain people and
+ full edit access to others, see <A
+HREF="products.html#product-group-controls"
+>Section 3.4.4</A
+>.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; By default, bugs can also be seen by the Assignee, the Reporter, and
+ by everyone on the CC List, regardless of whether or not the bug would
+ typically be viewable by them. Visibility to the Reporter and CC List can
+ be overridden (on a per-bug basis) by bringing up the bug, finding the
+ section that starts with <SPAN
+CLASS="QUOTE"
+>"Users in the roles selected below..."</SPAN
+>
+ and un-checking the box next to either 'Reporter' or 'CC List' (or both).
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="create-groups"
+>3.15.1. Creating Groups</A
+></H2
+><P
+>&#13; To create a new group, follow the steps below:
+ </P
+><P
+></P
+><OL
+TYPE="1"
+><LI
+><P
+>&#13; Select the <SPAN
+CLASS="QUOTE"
+>"Administration"</SPAN
+> link in the page footer,
+ and then select the <SPAN
+CLASS="QUOTE"
+>"Groups"</SPAN
+> link from the
+ Administration page.
+ </P
+></LI
+><LI
+><P
+>&#13; A table of all the existing groups is displayed. Below the table is a
+ description of all the fields. To create a new group, select the
+ <SPAN
+CLASS="QUOTE"
+>"Add Group"</SPAN
+> link under the table of existing groups.
+ </P
+></LI
+><LI
+><P
+>&#13; There are five fields to fill out. These fields are documented below
+ the form. Choose a name and description for the group. Decide whether
+ this group should be used for bugs (in all likelihood this should be
+ selected). Optionally, choose a regular expression that will
+ automatically add any matching users to the group, and choose an
+ icon that will help identify user comments for the group. The regular
+ expression can be useful, for example, to automatically put all users
+ from the same company into one group (if the group is for a specific
+ customer or partner).
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; If <SPAN
+CLASS="QUOTE"
+>"User RegExp"</SPAN
+> is filled out, users whose email
+ addresses match the regular expression will automatically be
+ members of the group as long as their email addresses continue
+ to match the regular expression. If their email address changes
+ and no longer matches the regular expression, they will be removed
+ from the group. Versions 2.16 and older of Bugzilla did not automatically
+ remove users who's email addresses no longer matched the RegExp.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; If specifying a domain in the regular expression, end
+ the regexp with a "$". Otherwise, when granting access to
+ "@mycompany\.com", access will also be granted to
+ 'badperson@mycompany.com.cracker.net'. Use the syntax,
+ '@mycompany\.com$' for the regular expression.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></LI
+><LI
+><P
+>&#13; After the new group is created, it can be edited for additional options.
+ The "Edit Group" page allows for specifying other groups that should be included
+ in this group and which groups should be permitted to add and delete
+ users from this group. For more details, see <A
+HREF="groups.html#edit-groups"
+>Section 3.15.2</A
+>.
+ </P
+></LI
+></OL
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="edit-groups"
+>3.15.2. Editing Groups and Assigning Group Permissions</A
+></H2
+><P
+>&#13; To access the "Edit Groups" page, select the
+ <SPAN
+CLASS="QUOTE"
+>"Administration"</SPAN
+> link in the page footer,
+ and then select the <SPAN
+CLASS="QUOTE"
+>"Groups"</SPAN
+> link from the Administration page.
+ A table of all the existing groups is displayed. Click on a group name
+ you wish to edit or control permissions for.
+ </P
+><P
+>&#13; The "Edit Groups" page contains the same five fields present when
+ creating a new group. Below that are two additional sections, "Group
+ Permissions," and "Mass Remove". The "Mass Remove" option simply removes
+ all users from the group who match the regular expression entered. The
+ "Group Permissions" section requires further explanation.
+ </P
+><P
+>&#13; The "Group Permissions" section on the "Edit Groups" page contains four sets
+ of permissions that control the relationship of this group to other
+ groups. If the 'usevisibilitygroups' parameter is in use (see
+ <A
+HREF="parameters.html"
+>Section 3.1</A
+>) two additional sets of permissions are displayed.
+ Each set consists of two select boxes. On the left, a select box
+ with a list of all existing groups. On the right, a select box listing
+ all groups currently selected for this permission setting (this box will
+ be empty for new groups). The way these controls allow groups to relate
+ to one another is called <EM
+>inheritance</EM
+>.
+ Each of the six permissions is described below.
+ </P
+><P
+></P
+><DIV
+CLASS="variablelist"
+><DL
+><DT
+><EM
+>Groups That Are a Member of This Group</EM
+></DT
+><DD
+><P
+>
+ Members of any groups selected here will automatically have
+ membership in this group. In other words, members of any selected
+ group will inherit membership in this group.
+ </P
+></DD
+><DT
+><EM
+>Groups That This Group Is a Member Of</EM
+></DT
+><DD
+><P
+>&#13; Members of this group will inherit membership to any group
+ selected here. For example, suppose the group being edited is
+ an Admin group. If there are two products (Product1 and Product2)
+ and each product has its
+ own group (Group1 and Group2), and the Admin group
+ should have access to both products,
+ simply select both Group1 and Group2 here.
+ </P
+></DD
+><DT
+><EM
+>Groups That Can Grant Membership in This Group</EM
+></DT
+><DD
+><P
+>&#13; The members of any group selected here will be able add users
+ to this group, even if they themselves are not in this group.
+ </P
+></DD
+><DT
+><EM
+>Groups That This Group Can Grant Membership In</EM
+></DT
+><DD
+><P
+>&#13; Members of this group can add users to any group selected here,
+ even if they themselves are not in the selected groups.
+ </P
+></DD
+><DT
+><EM
+>Groups That Can See This Group</EM
+></DT
+><DD
+><P
+>&#13; Members of any selected group can see the users in this group.
+ This setting is only visible if the 'usevisibilitygroups' parameter
+ is enabled on the Bugzilla Configuration page. See
+ <A
+HREF="parameters.html"
+>Section 3.1</A
+> for information on configuring Bugzilla.
+ </P
+></DD
+><DT
+><EM
+>Groups That This Group Can See</EM
+></DT
+><DD
+><P
+>&#13; Members of this group can see members in any of the selected groups.
+ This setting is only visible if the 'usevisibilitygroups' parameter
+ is enabled on the the Bugzilla Configuration page. See
+ <A
+HREF="parameters.html"
+>Section 3.1</A
+> for information on configuring Bugzilla.
+ </P
+></DD
+></DL
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="users-and-groups"
+>3.15.3. Assigning Users to Groups</A
+></H2
+><P
+>&#13; A User can become a member of a group in several ways:
+ </P
+><P
+></P
+><OL
+TYPE="1"
+><LI
+><P
+>&#13; The user can be explicitly placed in the group by editing
+ the user's profile. This can be done by accessing the "Users" page
+ from the "Administration" page. Use the search form to find the user
+ you want to edit group membership for, and click on their email
+ address in the search results to edit their profile. The profile
+ page lists all the groups, and indicates if the user is a member of
+ the group either directly or indirectly. More information on indirect
+ group membership is below. For more details on User administration,
+ see <A
+HREF="useradmin.html"
+>Section 3.2</A
+>.
+ </P
+></LI
+><LI
+><P
+>&#13; The group can include another group of which the user is
+ a member. This is indicated by square brackets around the checkbox
+ next to the group name in the user's profile.
+ See <A
+HREF="groups.html#edit-groups"
+>Section 3.15.2</A
+> for details on group inheritance.
+ </P
+></LI
+><LI
+><P
+>&#13; The user's email address can match the regular expression
+ that has been specified to automatically grant membership to
+ the group. This is indicated by "*" around the check box by the
+ group name in the user's profile.
+ See <A
+HREF="groups.html#create-groups"
+>Section 3.15.1</A
+> for details on
+ the regular expression option when creating groups.
+ </P
+></LI
+></OL
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="AEN2110"
+>3.15.4. Assigning Group Controls to Products</A
+></H2
+><P
+>&#13; The primary functionality of groups is derived from the relationship of
+ groups to products. The concepts around segregating access to bugs with
+ product group controls can be confusing. For details and examples on this
+ topic, see <A
+HREF="products.html#product-group-controls"
+>Section 3.4.4</A
+>.
+ </P
+></DIV
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="quips.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="sanitycheck.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Quips</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="administration.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Checking and Maintaining Database Integrity</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/hintsandtips.html b/docs/en/html/hintsandtips.html
new file mode 100644
index 000000000..0f14ada0d
--- /dev/null
+++ b/docs/en/html/hintsandtips.html
@@ -0,0 +1,280 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Hints and Tips</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Using Bugzilla"
+HREF="using.html"><LINK
+REL="PREVIOUS"
+TITLE="Attachments"
+HREF="attachments.html"><LINK
+REL="NEXT"
+TITLE="Time Tracking Information"
+HREF="timetracking.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="attachments.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 5. Using Bugzilla</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="timetracking.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="hintsandtips"
+>5.8. Hints and Tips</A
+></H1
+><P
+>This section distills some Bugzilla tips and best practices
+ that have been developed.</P
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="AEN2591"
+>5.8.1. Autolinkification</A
+></H2
+><P
+>Bugzilla comments are plain text - so typing &#60;U&#62; will
+ produce less-than, U, greater-than rather than underlined text.
+ However, Bugzilla will automatically make hyperlinks out of certain
+ sorts of text in comments. For example, the text
+ "http://www.bugzilla.org" will be turned into a link:
+ <A
+HREF="http://www.bugzilla.org"
+TARGET="_top"
+>http://www.bugzilla.org</A
+>.
+ Other strings which get linkified in the obvious manner are:
+ <P
+></P
+><TABLE
+BORDER="0"
+><TBODY
+><TR
+><TD
+>bug 12345</TD
+></TR
+><TR
+><TD
+>comment 7</TD
+></TR
+><TR
+><TD
+>bug 23456, comment 53</TD
+></TR
+><TR
+><TD
+>attachment 4321</TD
+></TR
+><TR
+><TD
+>mailto:george@example.com</TD
+></TR
+><TR
+><TD
+>george@example.com</TD
+></TR
+><TR
+><TD
+>ftp://ftp.mozilla.org</TD
+></TR
+><TR
+><TD
+>Most other sorts of URL</TD
+></TR
+></TBODY
+></TABLE
+><P
+></P
+>
+ </P
+><P
+>A corollary here is that if you type a bug number in a comment,
+ you should put the word "bug" before it, so it gets autolinkified
+ for the convenience of others.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="commenting"
+>5.8.2. Comments</A
+></H2
+><P
+>If you are changing the fields on a bug, only comment if
+ either you have something pertinent to say, or Bugzilla requires it.
+ Otherwise, you may spam people unnecessarily with bug mail.
+ To take an example: a user can set up their account to filter out messages
+ where someone just adds themselves to the CC field of a bug
+ (which happens a lot.) If you come along, add yourself to the CC field,
+ and add a comment saying "Adding self to CC", then that person
+ gets a pointless piece of mail they would otherwise have avoided.
+ </P
+><P
+>&#13; Don't use sigs in comments. Signing your name ("Bill") is acceptable,
+ if you do it out of habit, but full mail/news-style
+ four line ASCII art creations are not.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="comment-wrapping"
+>5.8.3. Server-Side Comment Wrapping</A
+></H2
+><P
+>&#13; Bugzilla stores comments unwrapped and wraps them at display time. This
+ ensures proper wrapping in all browsers. Lines beginning with the "&#62;"
+ character are assumed to be quotes, and are not wrapped.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="dependencytree"
+>5.8.4. Dependency Tree</A
+></H2
+><P
+>&#13; On the <SPAN
+CLASS="QUOTE"
+>"Dependency tree"</SPAN
+> page linked from each bug
+ page, you can see the dependency relationship from the bug as a
+ tree structure.
+ </P
+><P
+>&#13; You can change how much depth to show, and you can hide resolved bugs
+ from this page. You can also collaps/expand dependencies for
+ each bug on the tree view, using the [-]/[+] buttons that appear
+ before its summary. This option is not available for terminal
+ bugs in the tree (that don't have further dependencies).
+ </P
+></DIV
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="attachments.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="timetracking.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Attachments</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="using.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Time Tracking Information</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/index.html b/docs/en/html/index.html
new file mode 100644
index 000000000..ebce424f7
--- /dev/null
+++ b/docs/en/html/index.html
@@ -0,0 +1,657 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>The Bugzilla Guide - 4.0
+ Release</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="NEXT"
+TITLE="About This Guide"
+HREF="about.html"><META
+NAME="KEYWORD"
+CONTENT="Bugzilla"><META
+NAME="KEYWORD"
+CONTENT="Guide"><META
+NAME="KEYWORD"
+CONTENT="installation"><META
+NAME="KEYWORD"
+CONTENT="FAQ"><META
+NAME="KEYWORD"
+CONTENT="administration"><META
+NAME="KEYWORD"
+CONTENT="integration"><META
+NAME="KEYWORD"
+CONTENT="MySQL"><META
+NAME="KEYWORD"
+CONTENT="Mozilla"><META
+NAME="KEYWORD"
+CONTENT="webtools"></HEAD
+><BODY
+CLASS="book"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="BOOK"
+><A
+NAME="index"
+></A
+><DIV
+CLASS="TITLEPAGE"
+><H1
+CLASS="title"
+><A
+NAME="AEN2"
+>The Bugzilla Guide - 4.0
+ Release</A
+></H1
+><H3
+CLASS="corpauthor"
+>The Bugzilla Team</H3
+><P
+CLASS="pubdate"
+>2011-02-15<BR></P
+><DIV
+><DIV
+CLASS="abstract"
+><P
+></P
+><A
+NAME="AEN7"
+></A
+><P
+>&#13; This is the documentation for Bugzilla, a
+ bug-tracking system from mozilla.org.
+ Bugzilla is an enterprise-class piece of software
+ that tracks millions of bugs and issues for hundreds of
+ organizations around the world.
+ </P
+><P
+>&#13; The most current version of this document can always be found on the
+ <A
+HREF="http://www.bugzilla.org/docs/"
+TARGET="_top"
+>Bugzilla
+ Documentation Page</A
+>.
+ </P
+><P
+></P
+></DIV
+></DIV
+><HR></DIV
+><DIV
+CLASS="TOC"
+><DL
+><DT
+><B
+>Table of Contents</B
+></DT
+><DT
+>1. <A
+HREF="about.html"
+>About This Guide</A
+></DT
+><DD
+><DL
+><DT
+>1.1. <A
+HREF="copyright.html"
+>Copyright Information</A
+></DT
+><DT
+>1.2. <A
+HREF="disclaimer.html"
+>Disclaimer</A
+></DT
+><DT
+>1.3. <A
+HREF="newversions.html"
+>New Versions</A
+></DT
+><DT
+>1.4. <A
+HREF="credits.html"
+>Credits</A
+></DT
+><DT
+>1.5. <A
+HREF="conventions.html"
+>Document Conventions</A
+></DT
+></DL
+></DD
+><DT
+>2. <A
+HREF="installing-bugzilla.html"
+>Installing Bugzilla</A
+></DT
+><DD
+><DL
+><DT
+>2.1. <A
+HREF="installation.html"
+>Installation</A
+></DT
+><DT
+>2.2. <A
+HREF="configuration.html"
+>Configuration</A
+></DT
+><DT
+>2.3. <A
+HREF="extraconfig.html"
+>Optional Additional Configuration</A
+></DT
+><DT
+>2.4. <A
+HREF="multiple-bz-dbs.html"
+>Multiple Bugzilla databases with a single installation</A
+></DT
+><DT
+>2.5. <A
+HREF="os-specific.html"
+>OS-Specific Installation Notes</A
+></DT
+><DT
+>2.6. <A
+HREF="nonroot.html"
+>UNIX (non-root) Installation Notes</A
+></DT
+><DT
+>2.7. <A
+HREF="upgrade.html"
+>Upgrading to New Releases</A
+></DT
+></DL
+></DD
+><DT
+>3. <A
+HREF="administration.html"
+>Administering Bugzilla</A
+></DT
+><DD
+><DL
+><DT
+>3.1. <A
+HREF="parameters.html"
+>Bugzilla Configuration</A
+></DT
+><DT
+>3.2. <A
+HREF="useradmin.html"
+>User Administration</A
+></DT
+><DT
+>3.3. <A
+HREF="classifications.html"
+>Classifications</A
+></DT
+><DT
+>3.4. <A
+HREF="products.html"
+>Products</A
+></DT
+><DT
+>3.5. <A
+HREF="components.html"
+>Components</A
+></DT
+><DT
+>3.6. <A
+HREF="versions.html"
+>Versions</A
+></DT
+><DT
+>3.7. <A
+HREF="milestones.html"
+>Milestones</A
+></DT
+><DT
+>3.8. <A
+HREF="flags-overview.html"
+>Flags</A
+></DT
+><DT
+>3.9. <A
+HREF="keywords.html"
+>Keywords</A
+></DT
+><DT
+>3.10. <A
+HREF="custom-fields.html"
+>Custom Fields</A
+></DT
+><DT
+>3.11. <A
+HREF="edit-values.html"
+>Legal Values</A
+></DT
+><DT
+>3.12. <A
+HREF="bug_status_workflow.html"
+>Bug Status Workflow</A
+></DT
+><DT
+>3.13. <A
+HREF="voting.html"
+>Voting</A
+></DT
+><DT
+>3.14. <A
+HREF="quips.html"
+>Quips</A
+></DT
+><DT
+>3.15. <A
+HREF="groups.html"
+>Groups and Group Security</A
+></DT
+><DT
+>3.16. <A
+HREF="sanitycheck.html"
+>Checking and Maintaining Database Integrity</A
+></DT
+></DL
+></DD
+><DT
+>4. <A
+HREF="security.html"
+>Bugzilla Security</A
+></DT
+><DD
+><DL
+><DT
+>4.1. <A
+HREF="security-os.html"
+>Operating System</A
+></DT
+><DT
+>4.2. <A
+HREF="security-webserver.html"
+>Web server</A
+></DT
+><DT
+>4.3. <A
+HREF="security-bugzilla.html"
+>Bugzilla</A
+></DT
+></DL
+></DD
+><DT
+>5. <A
+HREF="using.html"
+>Using Bugzilla</A
+></DT
+><DD
+><DL
+><DT
+>5.1. <A
+HREF="using-intro.html"
+>Introduction</A
+></DT
+><DT
+>5.2. <A
+HREF="myaccount.html"
+>Create a Bugzilla Account</A
+></DT
+><DT
+>5.3. <A
+HREF="bug_page.html"
+>Anatomy of a Bug</A
+></DT
+><DT
+>5.4. <A
+HREF="lifecycle.html"
+>Life Cycle of a Bug</A
+></DT
+><DT
+>5.5. <A
+HREF="query.html"
+>Searching for Bugs</A
+></DT
+><DT
+>5.6. <A
+HREF="bugreports.html"
+>Filing Bugs</A
+></DT
+><DT
+>5.7. <A
+HREF="attachments.html"
+>Attachments</A
+></DT
+><DT
+>5.8. <A
+HREF="hintsandtips.html"
+>Hints and Tips</A
+></DT
+><DT
+>5.9. <A
+HREF="timetracking.html"
+>Time Tracking Information</A
+></DT
+><DT
+>5.10. <A
+HREF="userpreferences.html"
+>User Preferences</A
+></DT
+><DT
+>5.11. <A
+HREF="reporting.html"
+>Reports and Charts</A
+></DT
+><DT
+>5.12. <A
+HREF="flags.html"
+>Flags</A
+></DT
+><DT
+>5.13. <A
+HREF="whining.html"
+>Whining</A
+></DT
+></DL
+></DD
+><DT
+>6. <A
+HREF="customization.html"
+>Customizing Bugzilla</A
+></DT
+><DD
+><DL
+><DT
+>6.1. <A
+HREF="extensions.html"
+>Bugzilla Extensions</A
+></DT
+><DT
+>6.2. <A
+HREF="cust-skins.html"
+>Custom Skins</A
+></DT
+><DT
+>6.3. <A
+HREF="cust-templates.html"
+>Template Customization</A
+></DT
+><DT
+>6.4. <A
+HREF="cust-change-permissions.html"
+>Customizing Who Can Change What</A
+></DT
+><DT
+>6.5. <A
+HREF="integration.html"
+>Integrating Bugzilla with Third-Party Tools</A
+></DT
+></DL
+></DD
+><DT
+>A. <A
+HREF="troubleshooting.html"
+>Troubleshooting</A
+></DT
+><DD
+><DL
+><DT
+>A.1. <A
+HREF="general-advice.html"
+>General Advice</A
+></DT
+><DT
+>A.2. <A
+HREF="trbl-testserver.html"
+>The Apache web server is not serving Bugzilla pages</A
+></DT
+><DT
+>A.3. <A
+HREF="trbl-perlmodule.html"
+>I installed a Perl module, but
+ <TT
+CLASS="filename"
+>checksetup.pl</TT
+> claims it's not installed!</A
+></DT
+><DT
+>A.4. <A
+HREF="trbl-dbdsponge.html"
+>DBD::Sponge::db prepare failed</A
+></DT
+><DT
+>A.5. <A
+HREF="paranoid-security.html"
+>cannot chdir(/var/spool/mqueue)</A
+></DT
+><DT
+>A.6. <A
+HREF="trbl-relogin-everyone.html"
+>Everybody is constantly being forced to relogin</A
+></DT
+><DT
+>A.7. <A
+HREF="trbl-index.html"
+><TT
+CLASS="filename"
+>index.cgi</TT
+> doesn't show up unless specified in the URL</A
+></DT
+><DT
+>A.8. <A
+HREF="trbl-passwd-encryption.html"
+>checksetup.pl reports "Client does not support authentication protocol
+ requested by server..."</A
+></DT
+></DL
+></DD
+><DT
+>B. <A
+HREF="patches.html"
+>Contrib</A
+></DT
+><DD
+><DL
+><DT
+>B.1. <A
+HREF="cmdline.html"
+>Command-line Search Interface</A
+></DT
+><DT
+>B.2. <A
+HREF="cmdline-bugmail.html"
+>Command-line 'Send Unsent Bug-mail' tool</A
+></DT
+></DL
+></DD
+><DT
+>C. <A
+HREF="install-perlmodules-manual.html"
+>Manual Installation of Perl Modules</A
+></DT
+><DD
+><DL
+><DT
+>C.1. <A
+HREF="modules-manual-instructions.html"
+>Instructions</A
+></DT
+><DT
+>C.2. <A
+HREF="modules-manual-download.html"
+>Download Locations</A
+></DT
+><DT
+>C.3. <A
+HREF="modules-manual-optional.html"
+>Optional Modules</A
+></DT
+></DL
+></DD
+><DT
+>D. <A
+HREF="gfdl.html"
+>GNU Free Documentation License</A
+></DT
+><DD
+><DL
+><DT
+>0. <A
+HREF="gfdl-0.html"
+>Preamble</A
+></DT
+><DT
+>1. <A
+HREF="gfdl-1.html"
+>Applicability and Definition</A
+></DT
+><DT
+>2. <A
+HREF="gfdl-2.html"
+>Verbatim Copying</A
+></DT
+><DT
+>3. <A
+HREF="gfdl-3.html"
+>Copying in Quantity</A
+></DT
+><DT
+>4. <A
+HREF="gfdl-4.html"
+>Modifications</A
+></DT
+><DT
+>5. <A
+HREF="gfdl-5.html"
+>Combining Documents</A
+></DT
+><DT
+>6. <A
+HREF="gfdl-6.html"
+>Collections of Documents</A
+></DT
+><DT
+>7. <A
+HREF="gfdl-7.html"
+>Aggregation with Independent Works</A
+></DT
+><DT
+>8. <A
+HREF="gfdl-8.html"
+>Translation</A
+></DT
+><DT
+>9. <A
+HREF="gfdl-9.html"
+>Termination</A
+></DT
+><DT
+>10. <A
+HREF="gfdl-10.html"
+>Future Revisions of this License</A
+></DT
+><DT
+><A
+HREF="gfdl-howto.html"
+>How to use this License for your documents</A
+></DT
+></DL
+></DD
+><DT
+><A
+HREF="glossary.html"
+>Glossary</A
+></DT
+></DL
+></DIV
+><DIV
+CLASS="LOT"
+><DL
+CLASS="LOT"
+><DT
+><B
+>List of Figures</B
+></DT
+><DT
+>5-1. <A
+HREF="lifecycle.html#lifecycle-image"
+>Lifecycle of a Bugzilla Bug</A
+></DT
+></DL
+></DIV
+><DIV
+CLASS="LOT"
+><DL
+CLASS="LOT"
+><DT
+><B
+>List of Examples</B
+></DT
+><DT
+>A-1. <A
+HREF="trbl-relogin-everyone.html#trbl-relogin-everyone-share"
+>Examples of urlbase/cookiepath pairs for sharing login cookies</A
+></DT
+><DT
+>A-2. <A
+HREF="trbl-relogin-everyone.html#trbl-relogin-everyone-restrict"
+>Examples of urlbase/cookiepath pairs to restrict the login cookie</A
+></DT
+></DL
+></DIV
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>&nbsp;</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+>&nbsp;</TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="about.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>&nbsp;</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+>&nbsp;</TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>About This Guide</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/install-perlmodules-manual.html b/docs/en/html/install-perlmodules-manual.html
new file mode 100644
index 000000000..26f5b7ec6
--- /dev/null
+++ b/docs/en/html/install-perlmodules-manual.html
@@ -0,0 +1,162 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Manual Installation of Perl Modules</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="PREVIOUS"
+TITLE="Command-line 'Send Unsent Bug-mail' tool"
+HREF="cmdline-bugmail.html"><LINK
+REL="NEXT"
+TITLE="Instructions"
+HREF="modules-manual-instructions.html"></HEAD
+><BODY
+CLASS="appendix"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="cmdline-bugmail.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+></TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="modules-manual-instructions.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="appendix"
+><H1
+><A
+NAME="install-perlmodules-manual"
+></A
+>Appendix C. Manual Installation of Perl Modules</H1
+><DIV
+CLASS="TOC"
+><DL
+><DT
+><B
+>Table of Contents</B
+></DT
+><DT
+>C.1. <A
+HREF="modules-manual-instructions.html"
+>Instructions</A
+></DT
+><DT
+>C.2. <A
+HREF="modules-manual-download.html"
+>Download Locations</A
+></DT
+><DT
+>C.3. <A
+HREF="modules-manual-optional.html"
+>Optional Modules</A
+></DT
+></DL
+></DIV
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="cmdline-bugmail.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="modules-manual-instructions.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Command-line 'Send Unsent Bug-mail' tool</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+>&nbsp;</TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Instructions</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/installation.html b/docs/en/html/installation.html
new file mode 100644
index 000000000..61331c816
--- /dev/null
+++ b/docs/en/html/installation.html
@@ -0,0 +1,1136 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Installation</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Installing Bugzilla"
+HREF="installing-bugzilla.html"><LINK
+REL="PREVIOUS"
+TITLE="Installing Bugzilla"
+HREF="installing-bugzilla.html"><LINK
+REL="NEXT"
+TITLE="Configuration"
+HREF="configuration.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="installing-bugzilla.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 2. Installing Bugzilla</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="configuration.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="installation"
+>2.1. Installation</A
+></H1
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>If you just want to <EM
+>use</EM
+> Bugzilla,
+ you do not need to install it. None of this chapter is relevant to
+ you. Ask your Bugzilla administrator for the URL to access it from
+ your web browser.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>The Bugzilla server software is usually installed on Linux or
+ Solaris.
+ If you are installing on another OS, check <A
+HREF="os-specific.html"
+>Section 2.5</A
+>
+ before you start your installation to see if there are any special
+ instructions.
+ </P
+><P
+>This guide assumes that you have administrative access to the
+ Bugzilla machine. It not possible to
+ install and run Bugzilla itself without administrative access except
+ in the very unlikely event that every single prerequisite is
+ already installed.
+ </P
+><DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>The installation process may make your machine insecure for
+ short periods of time. Make sure there is a firewall between you
+ and the Internet.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; You are strongly recommended to make a backup of your system
+ before installing Bugzilla (and at regular intervals thereafter :-).
+ </P
+><P
+>In outline, the installation proceeds as follows:
+ </P
+><DIV
+CLASS="procedure"
+><OL
+TYPE="1"
+><LI
+CLASS="step"
+><P
+><A
+HREF="installation.html#install-perl"
+>Install Perl</A
+>
+ (5.8.1 or above)
+ </P
+></LI
+><LI
+CLASS="step"
+><P
+><A
+HREF="installation.html#install-database"
+>Install a Database Engine</A
+>
+ </P
+></LI
+><LI
+CLASS="step"
+><P
+><A
+HREF="installation.html#install-webserver"
+>Install a Webserver</A
+>
+ </P
+></LI
+><LI
+CLASS="step"
+><P
+><A
+HREF="installation.html#install-bzfiles"
+>Install Bugzilla</A
+>
+ </P
+></LI
+><LI
+CLASS="step"
+><P
+><A
+HREF="installation.html#install-perlmodules"
+>Install Perl modules</A
+>
+ </P
+></LI
+><LI
+CLASS="step"
+><P
+>&#13; <A
+HREF="installation.html#install-MTA"
+>Install a Mail Transfer Agent</A
+>
+ (Sendmail 8.7 or above, or an MTA that is Sendmail-compatible with at least this version)
+ </P
+></LI
+><LI
+CLASS="step"
+><P
+>Configure all of the above.
+ </P
+></LI
+></OL
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="install-perl"
+>2.1.1. Perl</A
+></H2
+><P
+>Installed Version Test: <TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>perl -v</PRE
+></FONT
+></TD
+></TR
+></TABLE
+></P
+><P
+>Any machine that doesn't have Perl on it is a sad machine indeed.
+ If you don't have it and your OS doesn't provide official packages,
+ visit <A
+HREF="http://www.perl.org"
+TARGET="_top"
+>http://www.perl.org</A
+>.
+ Although Bugzilla runs with Perl 5.8.1,
+ it's a good idea to be using the latest stable version.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="install-database"
+>2.1.2. Database Engine</A
+></H2
+><P
+>&#13; Bugzilla supports MySQL, PostgreSQL and Oracle as database servers.
+ You only require one of these systems to make use of Bugzilla.
+ </P
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="install-mysql"
+>2.1.2.1. MySQL</A
+></H3
+><P
+>Installed Version Test: <TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>mysql -V</PRE
+></FONT
+></TD
+></TR
+></TABLE
+></P
+><P
+>&#13; If you don't have it and your OS doesn't provide official packages,
+ visit <A
+HREF="http://www.mysql.com"
+TARGET="_top"
+>http://www.mysql.com</A
+>. You need MySQL version
+ 4.1.2 or higher.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+> Many of the binary
+ versions of MySQL store their data files in
+ <TT
+CLASS="filename"
+>/var</TT
+>.
+ On some Unix systems, this is part of a smaller root partition,
+ and may not have room for your bug database. To change the data
+ directory, you have to build MySQL from source yourself, and
+ set it as an option to <TT
+CLASS="filename"
+>configure</TT
+>.</P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>If you install from something other than a packaging/installation
+ system, such as .rpm (Redhat Package), .deb (Debian Package), .exe
+ (Windows Executable), or .msi (Microsoft Installer), make sure the MySQL
+ server is started when the machine boots.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="install-pg"
+>2.1.2.2. PostgreSQL</A
+></H3
+><P
+>Installed Version Test: <TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>psql -V</PRE
+></FONT
+></TD
+></TR
+></TABLE
+></P
+><P
+>&#13; If you don't have it and your OS doesn't provide official packages,
+ visit <A
+HREF="http://www.postgresql.org/"
+TARGET="_top"
+>http://www.postgresql.org/</A
+>. You need PostgreSQL
+ version 8.00.0000 or higher.
+ </P
+><P
+>If you install from something other than a packaging/installation
+ system, such as .rpm (Redhat Package), .deb (Debian Package), .exe
+ (Windows Executable), or .msi (Microsoft Installer), make sure the
+ PostgreSQL server is started when the machine boots.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="install-oracle"
+>2.1.2.3. Oracle</A
+></H3
+><P
+>&#13; Installed Version Test: <TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>select * from v$version</PRE
+></FONT
+></TD
+></TR
+></TABLE
+>
+ (you first have to log in into your DB)
+ </P
+><P
+>&#13; If you don't have it and your OS doesn't provide official packages,
+ visit <A
+HREF="http://www.oracle.com/"
+TARGET="_top"
+>http://www.oracle.com/</A
+>. You need Oracle
+ version 10.02.0 or higher.
+ </P
+><P
+>&#13; If you install from something other than a packaging/installation
+ system, such as .rpm (Redhat Package), .deb (Debian Package), .exe
+ (Windows Executable), or .msi (Microsoft Installer), make sure the
+ Oracle server is started when the machine boots.
+ </P
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="install-webserver"
+>2.1.3. Web Server</A
+></H2
+><P
+>Installed Version Test: view the default welcome page at
+ http://&#60;your-machine&#62;/</P
+><P
+>You have freedom of choice here, pretty much any web server that
+ is capable of running <A
+HREF="glossary.html#gloss-cgi"
+><I
+CLASS="glossterm"
+>CGI</I
+></A
+>
+ scripts will work.
+ However, we strongly recommend using the Apache web server
+ (either 1.3.x or 2.x), and
+ the installation instructions usually assume you are
+ using it. If you have got Bugzilla working using another web server,
+ please share your experiences with us by filing a bug in <A
+HREF="https://bugzilla.mozilla.org/enter_bug.cgi?product=Bugzilla&#38;component=Documentation"
+TARGET="_top"
+>Bugzilla Documentation</A
+>.
+ </P
+><P
+>&#13; If you don't have Apache and your OS doesn't provide official packages,
+ visit <A
+HREF="http://httpd.apache.org/"
+TARGET="_top"
+>http://httpd.apache.org/</A
+>.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="install-bzfiles"
+>2.1.4. Bugzilla</A
+></H2
+><P
+>&#13; <A
+HREF="http://www.bugzilla.org/download/"
+TARGET="_top"
+>Download a Bugzilla tarball</A
+>
+ (or check it out from CVS) and place
+ it in a suitable directory, accessible by the default web server user
+ (probably <SPAN
+CLASS="QUOTE"
+>"apache"</SPAN
+> or <SPAN
+CLASS="QUOTE"
+>"www"</SPAN
+>).
+ Good locations are either directly in the web server's document directories or
+ in <TT
+CLASS="filename"
+>/usr/local</TT
+> with a symbolic link to the web server's
+ document directories or an alias in the web server's configuration.
+ </P
+><DIV
+CLASS="caution"
+><P
+></P
+><TABLE
+CLASS="caution"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/caution.gif"
+HSPACE="5"
+ALT="Caution"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>The default Bugzilla distribution is NOT designed to be placed
+ in a <TT
+CLASS="filename"
+>cgi-bin</TT
+> directory. This
+ includes any directory which is configured using the
+ <CODE
+CLASS="option"
+>ScriptAlias</CODE
+> directive of Apache.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>Once all the files are in a web accessible directory, make that
+ directory writable by your web server's user. This is a temporary step
+ until you run the
+ <TT
+CLASS="filename"
+>checksetup.pl</TT
+>
+ script, which locks down your installation.</P
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="install-perlmodules"
+>2.1.5. Perl Modules</A
+></H2
+><P
+>Bugzilla's installation process is based
+ on a script called <TT
+CLASS="filename"
+>checksetup.pl</TT
+>.
+ The first thing it checks is whether you have appropriate
+ versions of all the required
+ Perl modules. The aim of this section is to pass this check.
+ When it passes, proceed to <A
+HREF="configuration.html"
+>Section 2.2</A
+>.
+ </P
+><P
+>&#13; At this point, you need to <TT
+CLASS="filename"
+>su</TT
+> to root. You should
+ remain as root until the end of the install. To check you have the
+ required modules, run:
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="screen"
+><SAMP
+CLASS="prompt"
+>bash#</SAMP
+> ./checksetup.pl --check-modules</PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>&#13; <TT
+CLASS="filename"
+>checksetup.pl</TT
+> will print out a list of the
+ required and optional Perl modules, together with the versions
+ (if any) installed on your machine.
+ The list of required modules is reasonably long; however, you
+ may already have several of them installed.
+ </P
+><P
+>&#13; The preferred way to install missing Perl modules is to use the package
+ manager provided by your operating system (e.g <SPAN
+CLASS="QUOTE"
+>"rpm"</SPAN
+> or
+ <SPAN
+CLASS="QUOTE"
+>"yum"</SPAN
+> on Linux distros, or <SPAN
+CLASS="QUOTE"
+>"ppm"</SPAN
+> on Windows
+ if using ActivePerl, see <A
+HREF="os-specific.html#win32-perl-modules"
+>Section 2.5.1.2</A
+>).
+ If some Perl modules are still missing or are too old, then we recommend
+ using the <TT
+CLASS="filename"
+>install-module.pl</TT
+> script (doesn't work
+ with ActivePerl on Windows). If for some reason you really need to
+ install the Perl modules manually, see
+ <A
+HREF="install-perlmodules-manual.html"
+>Appendix C</A
+>. For instance, on Unix,
+ you invoke <TT
+CLASS="filename"
+>install-module.pl</TT
+> as follows:
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="screen"
+><SAMP
+CLASS="prompt"
+>bash#</SAMP
+> perl install-module.pl &#60;modulename&#62;</PRE
+></FONT
+></TD
+></TR
+></TABLE
+><DIV
+CLASS="tip"
+><P
+></P
+><TABLE
+CLASS="tip"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/tip.gif"
+HSPACE="5"
+ALT="Tip"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>Many people complain that Perl modules will not install for
+ them. Most times, the error messages complain that they are missing a
+ file in
+ <SPAN
+CLASS="QUOTE"
+>"@INC"</SPAN
+>.
+ Virtually every time, this error is due to permissions being set too
+ restrictively for you to compile Perl modules or not having the
+ necessary Perl development libraries installed on your system.
+ Consult your local UNIX systems administrator for help solving these
+ permissions issues; if you
+ <EM
+>are</EM
+>
+ the local UNIX sysadmin, please consult the newsgroup/mailing list
+ for further assistance or hire someone to help you out.</P
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>If you are using a package-based system, and attempting to install the
+ Perl modules from CPAN, you may need to install the "development" packages for
+ MySQL and GD before attempting to install the related Perl modules. The names of
+ these packages will vary depending on the specific distribution you are using,
+ but are often called <TT
+CLASS="filename"
+>&#60;packagename&#62;-devel</TT
+>.</P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; Here is a complete list of modules and their minimum versions.
+ Some modules have special installation notes, which follow.
+ </P
+><P
+>Required Perl modules:
+ <P
+></P
+><OL
+TYPE="1"
+><LI
+><P
+>&#13; CGI (3.51)
+ </P
+></LI
+><LI
+><P
+>&#13; Date::Format (2.21)
+ </P
+></LI
+><LI
+><P
+>&#13; DateTime (0.28)
+ </P
+></LI
+><LI
+><P
+>&#13; DateTime::TimeZone (0.71)
+ </P
+></LI
+><LI
+><P
+>&#13; DBI (1.41)
+ </P
+></LI
+><LI
+><P
+>&#13; DBD::mysql (4.00) if using MySQL
+ </P
+></LI
+><LI
+><P
+>&#13; DBD::Pg (1.45) if using PostgreSQL
+ </P
+></LI
+><LI
+><P
+>&#13; DBD::Oracle (1.19) if using Oracle
+ </P
+></LI
+><LI
+><P
+>&#13; Digest::SHA (any)
+ </P
+></LI
+><LI
+><P
+>&#13; Email::Send (2.00)
+ </P
+></LI
+><LI
+><P
+>&#13; Email::MIME (1.904)
+ </P
+></LI
+><LI
+><P
+>&#13; Template (2.22)
+ </P
+></LI
+><LI
+><P
+>&#13; URI (any)
+ </P
+></LI
+></OL
+>
+
+ Optional Perl modules:
+ <P
+></P
+><OL
+TYPE="1"
+><LI
+><P
+>&#13; GD (1.20) for bug charting
+ </P
+></LI
+><LI
+><P
+>&#13; Template::Plugin::GD::Image
+ (any) for Graphical Reports
+ </P
+></LI
+><LI
+><P
+>&#13; Chart::Lines (2.1) for bug charting
+ </P
+></LI
+><LI
+><P
+>&#13; GD::Graph (any) for bug charting
+ </P
+></LI
+><LI
+><P
+>&#13; GD::Text (any) for bug charting
+ </P
+></LI
+><LI
+><P
+>&#13; XML::Twig (any) for bug import/export
+ </P
+></LI
+><LI
+><P
+>&#13; MIME::Parser (5.406) for bug import/export
+ </P
+></LI
+><LI
+><P
+>&#13; LWP::UserAgent
+ (any) for Automatic Update Notifications
+ </P
+></LI
+><LI
+><P
+>&#13; PatchReader (0.9.4) for pretty HTML view of patches
+ </P
+></LI
+><LI
+><P
+>&#13; Net::LDAP
+ (any) for LDAP Authentication
+ </P
+></LI
+><LI
+><P
+>&#13; Authen::SASL
+ (any) for SASL Authentication
+ </P
+></LI
+><LI
+><P
+>&#13; Authen::Radius
+ (any) for RADIUS Authentication
+ </P
+></LI
+><LI
+><P
+>&#13; SOAP::Lite (0.712) for the web service interface
+ </P
+></LI
+><LI
+><P
+>&#13; JSON::RPC
+ (any) for the JSON-RPC interface
+ </P
+></LI
+><LI
+><P
+>&#13; Test::Taint
+ (any) for the web service interface
+ </P
+></LI
+><LI
+><P
+>&#13; HTML::Parser
+ (3.40) for More HTML in Product/Group Descriptions
+ </P
+></LI
+><LI
+><P
+>&#13; HTML::Scrubber
+ (any) for More HTML in Product/Group Descriptions
+ </P
+></LI
+><LI
+><P
+>&#13; Email::MIME::Attachment::Stripper
+ (any) for Inbound Email
+ </P
+></LI
+><LI
+><P
+>&#13; Email::Reply
+ (any) for Inbound Email
+ </P
+></LI
+><LI
+><P
+>&#13; TheSchwartz
+ (any) for Mail Queueing
+ </P
+></LI
+><LI
+><P
+>&#13; Daemon::Generic
+ (any) for Mail Queueing
+ </P
+></LI
+><LI
+><P
+>&#13; mod_perl2
+ (1.999022) for mod_perl
+ </P
+></LI
+></OL
+>
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="install-MTA"
+>2.1.6. Mail Transfer Agent (MTA)</A
+></H2
+><P
+>&#13; Bugzilla is dependent on the availability of an e-mail system for its
+ user authentication and for other tasks.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; This is not entirely true. It is possible to completely disable
+ email sending, or to have Bugzilla store email messages in a
+ file instead of sending them. However, this is mainly intended
+ for testing, as disabling or diverting email on a production
+ machine would mean that users could miss important events (such
+ as bug changes or the creation of new accounts).
+ </P
+><P
+>&#13; For more information, see the <SPAN
+CLASS="QUOTE"
+>"mail_delivery_method"</SPAN
+> parameter
+ in <A
+HREF="parameters.html"
+>Section 3.1</A
+>.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; On Linux, any Sendmail-compatible MTA (Mail Transfer Agent) will
+ suffice. Sendmail, Postfix, qmail and Exim are examples of common
+ MTAs. Sendmail is the original Unix MTA, but the others are easier to
+ configure, and therefore many people replace Sendmail with Postfix or
+ Exim. They are drop-in replacements, so Bugzilla will not
+ distinguish between them.
+ </P
+><P
+>&#13; If you are using Sendmail, version 8.7 or higher is required.
+ If you are using a Sendmail-compatible MTA, it must be congruent with
+ at least version 8.7 of Sendmail.
+ </P
+><P
+>&#13; Consult the manual for the specific MTA you choose for detailed
+ installation instructions. Each of these programs will have their own
+ configuration files where you must configure certain parameters to
+ ensure that the mail is delivered properly. They are implemented
+ as services, and you should ensure that the MTA is in the auto-start
+ list of services for the machine.
+ </P
+><P
+>&#13; If a simple mail sent with the command-line 'mail' program
+ succeeds, then Bugzilla should also be fine.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="using-mod_perl-with-bugzilla"
+>2.1.7. Installing Bugzilla on mod_perl</A
+></H2
+><P
+>It is now possible to run the Bugzilla software under <TT
+CLASS="literal"
+>mod_perl</TT
+> on
+ Apache. <TT
+CLASS="literal"
+>mod_perl</TT
+> has some additional requirements to that of running
+ Bugzilla under <TT
+CLASS="literal"
+>mod_cgi</TT
+> (the standard and previous way).</P
+><P
+>Bugzilla requires <TT
+CLASS="literal"
+>mod_perl</TT
+> to be installed, which can be
+ obtained from <A
+HREF="http://perl.apache.org"
+TARGET="_top"
+>http://perl.apache.org</A
+> - Bugzilla requires
+ version 1.999022 (AKA 2.0.0-RC5) to be installed.</P
+></DIV
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="installing-bugzilla.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="configuration.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Installing Bugzilla</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="installing-bugzilla.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Configuration</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/installing-bugzilla.html b/docs/en/html/installing-bugzilla.html
new file mode 100644
index 000000000..b33f0407d
--- /dev/null
+++ b/docs/en/html/installing-bugzilla.html
@@ -0,0 +1,354 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Installing Bugzilla</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="PREVIOUS"
+TITLE="Document Conventions"
+HREF="conventions.html"><LINK
+REL="NEXT"
+TITLE="Installation"
+HREF="installation.html"></HEAD
+><BODY
+CLASS="chapter"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="conventions.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+></TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="installation.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="chapter"
+><H1
+><A
+NAME="installing-bugzilla"
+></A
+>Chapter 2. Installing Bugzilla</H1
+><DIV
+CLASS="TOC"
+><DL
+><DT
+><B
+>Table of Contents</B
+></DT
+><DT
+>2.1. <A
+HREF="installation.html"
+>Installation</A
+></DT
+><DD
+><DL
+><DT
+>2.1.1. <A
+HREF="installation.html#install-perl"
+>Perl</A
+></DT
+><DT
+>2.1.2. <A
+HREF="installation.html#install-database"
+>Database Engine</A
+></DT
+><DT
+>2.1.3. <A
+HREF="installation.html#install-webserver"
+>Web Server</A
+></DT
+><DT
+>2.1.4. <A
+HREF="installation.html#install-bzfiles"
+>Bugzilla</A
+></DT
+><DT
+>2.1.5. <A
+HREF="installation.html#install-perlmodules"
+>Perl Modules</A
+></DT
+><DT
+>2.1.6. <A
+HREF="installation.html#install-MTA"
+>Mail Transfer Agent (MTA)</A
+></DT
+><DT
+>2.1.7. <A
+HREF="installation.html#using-mod_perl-with-bugzilla"
+>Installing Bugzilla on mod_perl</A
+></DT
+></DL
+></DD
+><DT
+>2.2. <A
+HREF="configuration.html"
+>Configuration</A
+></DT
+><DD
+><DL
+><DT
+>2.2.1. <A
+HREF="configuration.html#localconfig"
+>localconfig</A
+></DT
+><DT
+>2.2.2. <A
+HREF="configuration.html#database-engine"
+>Database Server</A
+></DT
+><DT
+>2.2.3. <A
+HREF="configuration.html#AEN537"
+>checksetup.pl</A
+></DT
+><DT
+>2.2.4. <A
+HREF="configuration.html#http"
+>Web server</A
+></DT
+><DT
+>2.2.5. <A
+HREF="configuration.html#install-config-bugzilla"
+>Bugzilla</A
+></DT
+></DL
+></DD
+><DT
+>2.3. <A
+HREF="extraconfig.html"
+>Optional Additional Configuration</A
+></DT
+><DD
+><DL
+><DT
+>2.3.1. <A
+HREF="extraconfig.html#AEN688"
+>Bug Graphs</A
+></DT
+><DT
+>2.3.2. <A
+HREF="extraconfig.html#installation-whining-cron"
+>The Whining Cron</A
+></DT
+><DT
+>2.3.3. <A
+HREF="extraconfig.html#installation-whining"
+>Whining</A
+></DT
+><DT
+>2.3.4. <A
+HREF="extraconfig.html#apache-addtype"
+>Serving Alternate Formats with the right MIME type</A
+></DT
+></DL
+></DD
+><DT
+>2.4. <A
+HREF="multiple-bz-dbs.html"
+>Multiple Bugzilla databases with a single installation</A
+></DT
+><DT
+>2.5. <A
+HREF="os-specific.html"
+>OS-Specific Installation Notes</A
+></DT
+><DD
+><DL
+><DT
+>2.5.1. <A
+HREF="os-specific.html#os-win32"
+>Microsoft Windows</A
+></DT
+><DT
+>2.5.2. <A
+HREF="os-specific.html#os-macosx"
+><SPAN
+CLASS="productname"
+>Mac OS X</SPAN
+></A
+></DT
+><DT
+>2.5.3. <A
+HREF="os-specific.html#os-linux"
+>Linux Distributions</A
+></DT
+></DL
+></DD
+><DT
+>2.6. <A
+HREF="nonroot.html"
+>UNIX (non-root) Installation Notes</A
+></DT
+><DD
+><DL
+><DT
+>2.6.1. <A
+HREF="nonroot.html#AEN851"
+>Introduction</A
+></DT
+><DT
+>2.6.2. <A
+HREF="nonroot.html#AEN855"
+>MySQL</A
+></DT
+><DT
+>2.6.3. <A
+HREF="nonroot.html#AEN890"
+>Perl</A
+></DT
+><DT
+>2.6.4. <A
+HREF="nonroot.html#install-perlmodules-nonroot"
+>Perl Modules</A
+></DT
+><DT
+>2.6.5. <A
+HREF="nonroot.html#AEN912"
+>HTTP Server</A
+></DT
+><DT
+>2.6.6. <A
+HREF="nonroot.html#AEN924"
+>Bugzilla</A
+></DT
+></DL
+></DD
+><DT
+>2.7. <A
+HREF="upgrade.html"
+>Upgrading to New Releases</A
+></DT
+><DD
+><DL
+><DT
+>2.7.1. <A
+HREF="upgrade.html#upgrade-before"
+>Before You Upgrade</A
+></DT
+><DT
+>2.7.2. <A
+HREF="upgrade.html#upgrade-files"
+>Getting The New Bugzilla</A
+></DT
+><DT
+>2.7.3. <A
+HREF="upgrade.html#upgrade-completion"
+>Completing Your Upgrade</A
+></DT
+><DT
+>2.7.4. <A
+HREF="upgrade.html#upgrade-notifications"
+>Automatic Notifications of New Releases</A
+></DT
+></DL
+></DD
+></DL
+></DIV
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="conventions.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="installation.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Document Conventions</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+>&nbsp;</TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Installation</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/integration.html b/docs/en/html/integration.html
new file mode 100644
index 000000000..6afea4daa
--- /dev/null
+++ b/docs/en/html/integration.html
@@ -0,0 +1,157 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Integrating Bugzilla with Third-Party Tools</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Customizing Bugzilla"
+HREF="customization.html"><LINK
+REL="PREVIOUS"
+TITLE="Customizing Who Can Change What"
+HREF="cust-change-permissions.html"><LINK
+REL="NEXT"
+TITLE="Troubleshooting"
+HREF="troubleshooting.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="cust-change-permissions.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 6. Customizing Bugzilla</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="troubleshooting.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="integration"
+>6.5. Integrating Bugzilla with Third-Party Tools</A
+></H1
+><P
+>&#13; Many utilities and applications can integrate with Bugzilla,
+ either on the client- or server-side. None of them are maintained
+ by the Bugzilla community, nor are they tested during our
+ QA tests, so use them at your own risk. They are listed at
+ <A
+HREF="https://wiki.mozilla.org/Bugzilla:Addons"
+TARGET="_top"
+>https://wiki.mozilla.org/Bugzilla:Addons</A
+>.
+ </P
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="cust-change-permissions.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="troubleshooting.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Customizing Who Can Change What</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="customization.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Troubleshooting</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/keywords.html b/docs/en/html/keywords.html
new file mode 100644
index 000000000..8b19cbd3c
--- /dev/null
+++ b/docs/en/html/keywords.html
@@ -0,0 +1,168 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Keywords</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Administering Bugzilla"
+HREF="administration.html"><LINK
+REL="PREVIOUS"
+TITLE="Flags"
+HREF="flags-overview.html"><LINK
+REL="NEXT"
+TITLE="Custom Fields"
+HREF="custom-fields.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="flags-overview.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 3. Administering Bugzilla</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="custom-fields.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="keywords"
+>3.9. Keywords</A
+></H1
+><P
+>&#13; The administrator can define keywords which can be used to tag and
+ categorise bugs. For example, the keyword "regression" is commonly used.
+ A company might have a policy stating all regressions
+ must be fixed by the next release - this keyword can make tracking those
+ bugs much easier.
+ </P
+><P
+>&#13; Keywords are global, rather than per-product. If the administrator changes
+ a keyword currently applied to any bugs, the keyword cache must be rebuilt
+ using the <A
+HREF="sanitycheck.html"
+>Section 3.16</A
+> script. Currently keywords can not
+ be marked obsolete to prevent future usage.
+ </P
+><P
+>&#13; Keywords can be created, edited or deleted by clicking the "Keywords"
+ link in the admin page. There are two fields for each keyword - the keyword
+ itself and a brief description. Once created, keywords can be selected
+ and applied to individual bugs in that bug's "Details" section.
+ </P
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="flags-overview.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="custom-fields.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Flags</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="administration.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Custom Fields</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/lifecycle.html b/docs/en/html/lifecycle.html
new file mode 100644
index 000000000..af0dda2c1
--- /dev/null
+++ b/docs/en/html/lifecycle.html
@@ -0,0 +1,186 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Life Cycle of a Bug</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Using Bugzilla"
+HREF="using.html"><LINK
+REL="PREVIOUS"
+TITLE="Anatomy of a Bug"
+HREF="bug_page.html"><LINK
+REL="NEXT"
+TITLE="Searching for Bugs"
+HREF="query.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="bug_page.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 5. Using Bugzilla</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="query.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="lifecycle"
+>5.4. Life Cycle of a Bug</A
+></H1
+><P
+>&#13; The life cycle of a bug, also known as workflow, is customizable to match
+ the needs of your organization, see <A
+HREF="bug_status_workflow.html"
+>Section 3.12</A
+>.
+ <A
+HREF="lifecycle.html#lifecycle-image"
+>Figure 5-1</A
+> contains a graphical representation of
+ the default workflow using the default bug statuses. If you wish to
+ customize this image for your site, the
+ <A
+HREF="../images/bzLifecycle.xml"
+TARGET="_top"
+>diagram file</A
+>
+ is available in <A
+HREF="http://www.gnome.org/projects/dia"
+TARGET="_top"
+>Dia's</A
+>
+ native XML format.
+ </P
+><DIV
+CLASS="figure"
+><A
+NAME="lifecycle-image"
+></A
+><P
+><B
+>Figure 5-1. Lifecycle of a Bugzilla Bug</B
+></P
+><DIV
+CLASS="mediaobject"
+><P
+><IMG
+SRC="../images/bzLifecycle.png"></P
+></DIV
+></DIV
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="bug_page.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="query.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Anatomy of a Bug</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="using.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Searching for Bugs</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/milestones.html b/docs/en/html/milestones.html
new file mode 100644
index 000000000..8b5f4be3e
--- /dev/null
+++ b/docs/en/html/milestones.html
@@ -0,0 +1,203 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Milestones</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Administering Bugzilla"
+HREF="administration.html"><LINK
+REL="PREVIOUS"
+TITLE="Versions"
+HREF="versions.html"><LINK
+REL="NEXT"
+TITLE="Flags"
+HREF="flags-overview.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="versions.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 3. Administering Bugzilla</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="flags-overview.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="milestones"
+>3.7. Milestones</A
+></H1
+><P
+>Milestones are "targets" that you plan to get a bug fixed by. For
+ example, you have a bug that you plan to fix for your 3.0 release, it
+ would be assigned the milestone of 3.0.</P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>Milestone options will only appear for a Product if you turned
+ on the "usetargetmilestone" parameter in the "Bug Fields" tab of the
+ "Parameters" page.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>To create new Milestones, and set Default Milestones:</P
+><P
+></P
+><OL
+TYPE="1"
+><LI
+><P
+>Select "Edit milestones" from the "Edit product" page.</P
+></LI
+><LI
+><P
+>Select "Add" in the bottom right corner.</P
+></LI
+><LI
+><P
+>Enter the name of the Milestone in the "Milestone" field. You
+ can optionally set the "sortkey", which is a positive or negative
+ number (-32768 to 32767) that defines where in the list this particular
+ milestone appears. This is because milestones often do not
+ occur in alphanumeric order For example, "Future" might be
+ after "Release 1.2". Select "Add".</P
+></LI
+></OL
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="versions.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="flags-overview.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Versions</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="administration.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Flags</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/modules-manual-download.html b/docs/en/html/modules-manual-download.html
new file mode 100644
index 000000000..0bbee4fd1
--- /dev/null
+++ b/docs/en/html/modules-manual-download.html
@@ -0,0 +1,366 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Download Locations</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Manual Installation of Perl Modules"
+HREF="install-perlmodules-manual.html"><LINK
+REL="PREVIOUS"
+TITLE="Instructions"
+HREF="modules-manual-instructions.html"><LINK
+REL="NEXT"
+TITLE="Optional Modules"
+HREF="modules-manual-optional.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="modules-manual-instructions.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Appendix C. Manual Installation of Perl Modules</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="modules-manual-optional.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="modules-manual-download"
+>C.2. Download Locations</A
+></H1
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Running Bugzilla on Windows requires the use of ActiveState
+ Perl 5.8.1 or higher. Many modules already exist in the core
+ distribution of ActiveState Perl. Additional modules can be downloaded
+ from <A
+HREF="http://theoryx5.uwinnipeg.ca/ppms/"
+TARGET="_top"
+>http://theoryx5.uwinnipeg.ca/ppms/</A
+> if you use
+ Perl 5.8.x or from <A
+HREF="http://cpan.uwinnipeg.ca/PPMPackages/10xx/"
+TARGET="_top"
+>http://cpan.uwinnipeg.ca/PPMPackages/10xx/</A
+>
+ if you use Perl 5.10.x.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; CGI:
+ <P
+CLASS="literallayout"
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CPAN&nbsp;Download&nbsp;Page:&nbsp;<A
+HREF="http://search.cpan.org/dist/CGI.pm/"
+TARGET="_top"
+>http://search.cpan.org/dist/CGI.pm/</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Documentation:&nbsp;<A
+HREF="http://perldoc.perl.org/CGI.html"
+TARGET="_top"
+>http://perldoc.perl.org/CGI.html</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</P
+>
+ </P
+><P
+>&#13; Data-Dumper:
+ <P
+CLASS="literallayout"
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CPAN&nbsp;Download&nbsp;Page:&nbsp;<A
+HREF="http://search.cpan.org/dist/Data-Dumper/"
+TARGET="_top"
+>http://search.cpan.org/dist/Data-Dumper/</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Documentation:&nbsp;<A
+HREF="http://search.cpan.org/dist/Data-Dumper/Dumper.pm"
+TARGET="_top"
+>http://search.cpan.org/dist/Data-Dumper/Dumper.pm</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</P
+>
+ </P
+><P
+>&#13; Date::Format (part of TimeDate):
+ <P
+CLASS="literallayout"
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CPAN&nbsp;Download&nbsp;Page:&nbsp;<A
+HREF="http://search.cpan.org/dist/TimeDate/"
+TARGET="_top"
+>http://search.cpan.org/dist/TimeDate/</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Documentation:&nbsp;<A
+HREF="http://search.cpan.org/dist/TimeDate/lib/Date/Format.pm"
+TARGET="_top"
+>http://search.cpan.org/dist/TimeDate/lib/Date/Format.pm</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</P
+>
+ </P
+><P
+>&#13; DBI:
+ <P
+CLASS="literallayout"
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CPAN&nbsp;Download&nbsp;Page:&nbsp;<A
+HREF="http://search.cpan.org/dist/DBI/"
+TARGET="_top"
+>http://search.cpan.org/dist/DBI/</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Documentation:&nbsp;<A
+HREF="http://dbi.perl.org/docs/"
+TARGET="_top"
+>http://dbi.perl.org/docs/</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</P
+>
+ </P
+><P
+>&#13; DBD::mysql:
+ <P
+CLASS="literallayout"
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CPAN&nbsp;Download&nbsp;Page:&nbsp;<A
+HREF="http://search.cpan.org/dist/DBD-mysql/"
+TARGET="_top"
+>http://search.cpan.org/dist/DBD-mysql/</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Documentation:&nbsp;<A
+HREF="http://search.cpan.org/dist/DBD-mysql/lib/DBD/mysql.pm"
+TARGET="_top"
+>http://search.cpan.org/dist/DBD-mysql/lib/DBD/mysql.pm</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</P
+>
+ </P
+><P
+>&#13; DBD::Pg:
+ <P
+CLASS="literallayout"
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CPAN&nbsp;Download&nbsp;Page:&nbsp;<A
+HREF="http://search.cpan.org/dist/DBD-Pg/"
+TARGET="_top"
+>http://search.cpan.org/dist/DBD-Pg/</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Documentation:&nbsp;<A
+HREF="http://search.cpan.org/dist/DBD-Pg/Pg.pm"
+TARGET="_top"
+>http://search.cpan.org/dist/DBD-Pg/Pg.pm</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</P
+>
+ </P
+><P
+>&#13; Template-Toolkit:
+ <P
+CLASS="literallayout"
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CPAN&nbsp;Download&nbsp;Page:&nbsp;<A
+HREF="http://search.cpan.org/dist/Template-Toolkit/"
+TARGET="_top"
+>http://search.cpan.org/dist/Template-Toolkit/</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Documentation:&nbsp;<A
+HREF="http://www.template-toolkit.org/docs.html"
+TARGET="_top"
+>http://www.template-toolkit.org/docs.html</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</P
+>
+ </P
+><P
+>&#13; GD:
+ <P
+CLASS="literallayout"
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CPAN&nbsp;Download&nbsp;Page:&nbsp;<A
+HREF="http://search.cpan.org/dist/GD/"
+TARGET="_top"
+>http://search.cpan.org/dist/GD/</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Documentation:&nbsp;<A
+HREF="http://search.cpan.org/dist/GD/GD.pm"
+TARGET="_top"
+>http://search.cpan.org/dist/GD/GD.pm</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</P
+>
+ </P
+><P
+>&#13; Template::Plugin::GD:
+ <P
+CLASS="literallayout"
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CPAN&nbsp;Download&nbsp;Page:&nbsp;<A
+HREF="http://search.cpan.org/dist/Template-GD/"
+TARGET="_top"
+>http://search.cpan.org/dist/Template-GD/</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Documentation:&nbsp;<A
+HREF="http://www.template-toolkit.org/docs/aqua/Modules/index.html"
+TARGET="_top"
+>http://www.template-toolkit.org/docs/aqua/Modules/index.html</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</P
+>
+ </P
+><P
+>&#13; MIME::Parser (part of MIME-tools):
+ <P
+CLASS="literallayout"
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CPAN&nbsp;Download&nbsp;Page:&nbsp;<A
+HREF="http://search.cpan.org/dist/MIME-tools/"
+TARGET="_top"
+>http://search.cpan.org/dist/MIME-tools/</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Documentation:&nbsp;<A
+HREF="http://search.cpan.org/dist/MIME-tools/lib/MIME/Parser.pm"
+TARGET="_top"
+>http://search.cpan.org/dist/MIME-tools/lib/MIME/Parser.pm</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</P
+>
+ </P
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="modules-manual-instructions.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="modules-manual-optional.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Instructions</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="install-perlmodules-manual.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Optional Modules</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/modules-manual-instructions.html b/docs/en/html/modules-manual-instructions.html
new file mode 100644
index 000000000..f94df354e
--- /dev/null
+++ b/docs/en/html/modules-manual-instructions.html
@@ -0,0 +1,239 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Instructions</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Manual Installation of Perl Modules"
+HREF="install-perlmodules-manual.html"><LINK
+REL="PREVIOUS"
+TITLE="Manual Installation of Perl Modules"
+HREF="install-perlmodules-manual.html"><LINK
+REL="NEXT"
+TITLE="Download Locations"
+HREF="modules-manual-download.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="install-perlmodules-manual.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Appendix C. Manual Installation of Perl Modules</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="modules-manual-download.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="modules-manual-instructions"
+>C.1. Instructions</A
+></H1
+><P
+>&#13; If you need to install Perl modules manually, here's how it's done.
+ Download the module using the link given in the next section, and then
+ apply this magic incantation, as root:
+ </P
+><P
+>
+ <TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="screen"
+><SAMP
+CLASS="prompt"
+>bash#</SAMP
+> tar -xzvf &#60;module&#62;.tar.gz
+<SAMP
+CLASS="prompt"
+>bash#</SAMP
+> cd &#60;module&#62;
+<SAMP
+CLASS="prompt"
+>bash#</SAMP
+> perl Makefile.PL
+<SAMP
+CLASS="prompt"
+>bash#</SAMP
+> make
+<SAMP
+CLASS="prompt"
+>bash#</SAMP
+> make test
+<SAMP
+CLASS="prompt"
+>bash#</SAMP
+> make install</PRE
+></FONT
+></TD
+></TR
+></TABLE
+>
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; In order to compile source code under Windows you will need to obtain
+ a 'make' utility. The <B
+CLASS="command"
+>nmake</B
+> utility provided with
+ Microsoft Visual C++ may be used. As an alternative, there is a
+ utility called <B
+CLASS="command"
+>dmake</B
+> available from CPAN which is
+ written entirely in Perl.
+ </P
+><P
+>&#13; As described in <A
+HREF="modules-manual-download.html"
+>Section C.2</A
+>, however, most
+ packages already exist and are available from ActiveState or theory58S.
+ We highly recommend that you install them using the ppm GUI available with
+ ActiveState and to add the theory58S repository to your list of repositories.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="install-perlmodules-manual.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="modules-manual-download.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Manual Installation of Perl Modules</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="install-perlmodules-manual.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Download Locations</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/modules-manual-optional.html b/docs/en/html/modules-manual-optional.html
new file mode 100644
index 000000000..5c00cf0f5
--- /dev/null
+++ b/docs/en/html/modules-manual-optional.html
@@ -0,0 +1,236 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Optional Modules</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Manual Installation of Perl Modules"
+HREF="install-perlmodules-manual.html"><LINK
+REL="PREVIOUS"
+TITLE="Download Locations"
+HREF="modules-manual-download.html"><LINK
+REL="NEXT"
+TITLE="GNU Free Documentation License"
+HREF="gfdl.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="modules-manual-download.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Appendix C. Manual Installation of Perl Modules</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="gfdl.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="modules-manual-optional"
+>C.3. Optional Modules</A
+></H1
+><P
+>&#13; Chart::Lines:
+ <P
+CLASS="literallayout"
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CPAN&nbsp;Download&nbsp;Page:&nbsp;<A
+HREF="http://search.cpan.org/dist/Chart/"
+TARGET="_top"
+>http://search.cpan.org/dist/Chart/</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Documentation:&nbsp;<A
+HREF="http://search.cpan.org/dist/Chart/Chart.pod"
+TARGET="_top"
+>http://search.cpan.org/dist/Chart/Chart.pod</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</P
+>
+ </P
+><P
+>&#13; GD::Graph:
+ <P
+CLASS="literallayout"
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CPAN&nbsp;Download&nbsp;Page:&nbsp;<A
+HREF="http://search.cpan.org/dist/GDGraph/"
+TARGET="_top"
+>http://search.cpan.org/dist/GDGraph/</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Documentation:&nbsp;<A
+HREF="http://search.cpan.org/dist/GDGraph/Graph.pm"
+TARGET="_top"
+>http://search.cpan.org/dist/GDGraph/Graph.pm</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</P
+>
+ </P
+><P
+>&#13; GD::Text::Align (part of GD::Text::Util):
+ <P
+CLASS="literallayout"
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CPAN&nbsp;Download&nbsp;Page:&nbsp;<A
+HREF="http://search.cpan.org/dist/GDTextUtil/"
+TARGET="_top"
+>http://search.cpan.org/dist/GDTextUtil/</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Documentation:&nbsp;<A
+HREF="http://search.cpan.org/dist/GDTextUtil/Text/Align.pm"
+TARGET="_top"
+>http://search.cpan.org/dist/GDTextUtil/Text/Align.pm</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</P
+>
+ </P
+><P
+>&#13; XML::Twig:
+ <P
+CLASS="literallayout"
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CPAN&nbsp;Download&nbsp;Page:&nbsp;<A
+HREF="http://search.cpan.org/dist/XML-Twig/"
+TARGET="_top"
+>http://search.cpan.org/dist/XML-Twig/</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Documentation:&nbsp;<A
+HREF="http://standards.ieee.org/resources/spasystem/twig/twig_stable.html"
+TARGET="_top"
+>http://standards.ieee.org/resources/spasystem/twig/twig_stable.html</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</P
+>
+ </P
+><P
+>&#13; PatchReader:
+ <P
+CLASS="literallayout"
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CPAN&nbsp;Download&nbsp;Page:&nbsp;<A
+HREF="http://search.cpan.org/author/JKEISER/PatchReader/"
+TARGET="_top"
+>http://search.cpan.org/author/JKEISER/PatchReader/</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Documentation:&nbsp;<A
+HREF="http://www.johnkeiser.com/mozilla/Patch_Viewer.html"
+TARGET="_top"
+>http://www.johnkeiser.com/mozilla/Patch_Viewer.html</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</P
+>
+ </P
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="modules-manual-download.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="gfdl.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Download Locations</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="install-perlmodules-manual.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>GNU Free Documentation License</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/multiple-bz-dbs.html b/docs/en/html/multiple-bz-dbs.html
new file mode 100644
index 000000000..c2d9e5302
--- /dev/null
+++ b/docs/en/html/multiple-bz-dbs.html
@@ -0,0 +1,227 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Multiple Bugzilla databases with a single installation</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Installing Bugzilla"
+HREF="installing-bugzilla.html"><LINK
+REL="PREVIOUS"
+TITLE="Optional Additional Configuration"
+HREF="extraconfig.html"><LINK
+REL="NEXT"
+TITLE="OS-Specific Installation Notes"
+HREF="os-specific.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="extraconfig.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 2. Installing Bugzilla</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="os-specific.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="multiple-bz-dbs"
+>2.4. Multiple Bugzilla databases with a single installation</A
+></H1
+><P
+>The previous instructions referred to a standard installation, with
+ one unique Bugzilla database. However, you may want to host several
+ distinct installations, without having several copies of the code. This is
+ possible by using the PROJECT environment variable. When accessed,
+ Bugzilla checks for the existence of this variable, and if present, uses
+ its value to check for an alternative configuration file named
+ <TT
+CLASS="filename"
+>localconfig.&#60;PROJECT&#62;</TT
+> in the same location as
+ the default one (<TT
+CLASS="filename"
+>localconfig</TT
+>). It also checks for
+ customized templates in a directory named
+ <TT
+CLASS="filename"
+>&#60;PROJECT&#62;</TT
+> in the same location as the
+ default one (<TT
+CLASS="filename"
+>template/&#60;langcode&#62;</TT
+>). By default
+ this is <TT
+CLASS="filename"
+>template/en/default</TT
+> so PROJECT's templates
+ would be located at <TT
+CLASS="filename"
+>template/en/PROJECT</TT
+>.</P
+><P
+>To set up an alternate installation, just export PROJECT=foo before
+ running <B
+CLASS="command"
+>checksetup.pl</B
+> for the first time. It will
+ result in a file called <TT
+CLASS="filename"
+>localconfig.foo</TT
+> instead of
+ <TT
+CLASS="filename"
+>localconfig</TT
+>. Edit this file as described above, with
+ reference to a new database, and re-run <B
+CLASS="command"
+>checksetup.pl</B
+>
+ to populate it. That's all.</P
+><P
+>Now you have to configure the web server to pass this environment
+ variable when accessed via an alternate URL, such as virtual host for
+ instance. The following is an example of how you could do it in Apache,
+ other Webservers may differ.
+<TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#13;&#60;VirtualHost 212.85.153.228:80&#62;
+ ServerName foo.bar.baz
+ SetEnv PROJECT foo
+ Alias /bugzilla /var/www/bugzilla
+&#60;/VirtualHost&#62;
+</PRE
+></FONT
+></TD
+></TR
+></TABLE
+>
+ </P
+><P
+>Don't forget to also export this variable before accessing Bugzilla
+ by other means, such as cron tasks for instance.</P
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="extraconfig.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="os-specific.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Optional Additional Configuration</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="installing-bugzilla.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>OS-Specific Installation Notes</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/myaccount.html b/docs/en/html/myaccount.html
new file mode 100644
index 000000000..51b3e022d
--- /dev/null
+++ b/docs/en/html/myaccount.html
@@ -0,0 +1,291 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Create a Bugzilla Account</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Using Bugzilla"
+HREF="using.html"><LINK
+REL="PREVIOUS"
+TITLE="Introduction"
+HREF="using-intro.html"><LINK
+REL="NEXT"
+TITLE="Anatomy of a Bug"
+HREF="bug_page.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="using-intro.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 5. Using Bugzilla</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="bug_page.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="myaccount"
+>5.2. Create a Bugzilla Account</A
+></H1
+><P
+>If you want to use Bugzilla, first you need to create an account.
+ Consult with the administrator responsible for your installation of
+ Bugzilla for the URL you should use to access it. If you're
+ test-driving Bugzilla, use this URL:
+ <A
+HREF="http://landfill.bugzilla.org/bugzilla-4.0-branch/"
+TARGET="_top"
+>http://landfill.bugzilla.org/bugzilla-4.0-branch/</A
+>.
+ </P
+><P
+></P
+><OL
+TYPE="1"
+><LI
+><P
+>&#13; On the home page <TT
+CLASS="filename"
+>index.cgi</TT
+>, click the
+ <SPAN
+CLASS="QUOTE"
+>"Open a new Bugzilla account"</SPAN
+> link, or the
+ <SPAN
+CLASS="QUOTE"
+>"New Account"</SPAN
+> link available in the footer of pages.
+ Now enter your email address, then click the <SPAN
+CLASS="QUOTE"
+>"Send"</SPAN
+>
+ button.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; If none of these links is available, this means that the
+ administrator of the installation has disabled self-registration.
+ This means that only an administrator can create accounts
+ for other users. One reason could be that this installation is
+ private.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Also, if only some users are allowed to create an account on
+ the installation, you may see these links but your registration
+ may fail if your email address doesn't match the ones accepted
+ by the installation. This is another way to restrict who can
+ access and edit bugs in this installation.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></LI
+><LI
+><P
+>&#13; Within moments, and if your registration is accepted, you should
+ receive an email to the address you provided, which contains your
+ login name (generally the same as the email address), and two URLs
+ with a token (a random string generated by the installation) to
+ confirm, respectively cancel, your registration. This is a way to
+ prevent users from abusing the generation of user accounts, for
+ instance by entering inexistent email addresses, or email addresses
+ which do not belong to them.
+ </P
+></LI
+><LI
+><P
+>&#13; By default, you have 3 days to confirm your registration. Past this
+ timeframe, the token is invalidated and the registration is
+ automatically canceled. You can also cancel this registration sooner
+ by using the appropriate URL in the email you got.
+ </P
+></LI
+><LI
+><P
+>&#13; If you confirm your registration, Bugzilla will ask you your real name
+ (optional, but recommended) and your password, which must be between
+ 3 and 16 characters long.
+ </P
+></LI
+><LI
+><P
+>&#13; Now all you need to do is to click the <SPAN
+CLASS="QUOTE"
+>"Log In"</SPAN
+>
+ link in the footer at the bottom of the page in your browser,
+ enter your email address and password you just chose into the
+ login form, and click the <SPAN
+CLASS="QUOTE"
+>"Log in"</SPAN
+> button.
+ </P
+></LI
+></OL
+><P
+>&#13; You are now logged in. Bugzilla uses cookies to remember you are
+ logged in so, unless you have cookies disabled or your IP address changes,
+ you should not have to log in again during your session.
+ </P
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="using-intro.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="bug_page.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Introduction</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="using.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Anatomy of a Bug</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/newversions.html b/docs/en/html/newversions.html
new file mode 100644
index 000000000..a8a1c0639
--- /dev/null
+++ b/docs/en/html/newversions.html
@@ -0,0 +1,175 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>New Versions</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="About This Guide"
+HREF="about.html"><LINK
+REL="PREVIOUS"
+TITLE="Disclaimer"
+HREF="disclaimer.html"><LINK
+REL="NEXT"
+TITLE="Credits"
+HREF="credits.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="disclaimer.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 1. About This Guide</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="credits.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="newversions"
+>1.3. New Versions</A
+></H1
+><P
+>&#13; This is the 4.0 version of The Bugzilla Guide. It is so named
+ to match the current version of Bugzilla.
+ </P
+><P
+>&#13; The latest version of this guide can always be found at <A
+HREF="http://www.bugzilla.org/docs/"
+TARGET="_top"
+>http://www.bugzilla.org/docs/</A
+>. However, you should read
+ the version which came with the Bugzilla release you are using.
+ </P
+><P
+>
+ In addition, there are Bugzilla template localization projects in
+ <A
+HREF="http://www.bugzilla.org/download/#localizations"
+TARGET="_top"
+>several languages</A
+>.
+ They may have translated documentation available. If you would like to
+ volunteer to translate the Guide into additional languages, please visit the
+ <A
+HREF="https://wiki.mozilla.org/Bugzilla:L10n"
+TARGET="_top"
+>Bugzilla L10n team</A
+>
+ page.
+ </P
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="disclaimer.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="credits.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Disclaimer</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="about.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Credits</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/nonroot.html b/docs/en/html/nonroot.html
new file mode 100644
index 000000000..d7f52d4b9
--- /dev/null
+++ b/docs/en/html/nonroot.html
@@ -0,0 +1,745 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>UNIX (non-root) Installation Notes</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Installing Bugzilla"
+HREF="installing-bugzilla.html"><LINK
+REL="PREVIOUS"
+TITLE="OS-Specific Installation Notes"
+HREF="os-specific.html"><LINK
+REL="NEXT"
+TITLE="Upgrading to New Releases"
+HREF="upgrade.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="os-specific.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 2. Installing Bugzilla</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="upgrade.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="nonroot"
+>2.6. UNIX (non-root) Installation Notes</A
+></H1
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="AEN851"
+>2.6.1. Introduction</A
+></H2
+><P
+>If you are running a *NIX OS as non-root, either due
+ to lack of access (web hosts, for example) or for security
+ reasons, this will detail how to install Bugzilla on such
+ a setup. It is recommended that you read through the
+ <A
+HREF="installation.html"
+>Section 2.1</A
+>
+ first to get an idea on the installation steps required.
+ (These notes will reference to steps in that guide.)</P
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="AEN855"
+>2.6.2. MySQL</A
+></H2
+><P
+>You may have MySQL installed as root. If you're
+ setting up an account with a web host, a MySQL account
+ needs to be set up for you. From there, you can create
+ the bugs account, or use the account given to you.</P
+><DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>You may have problems trying to set up
+ <B
+CLASS="command"
+>GRANT</B
+> permissions to the database.
+ If you're using a web host, chances are that you have a
+ separate database which is already locked down (or one big
+ database with limited/no access to the other areas), but you
+ may want to ask your system administrator what the security
+ settings are set to, and/or run the <B
+CLASS="command"
+>GRANT</B
+>
+ command for you.</P
+><P
+>Also, you will probably not be able to change the MySQL
+ root user password (for obvious reasons), so skip that
+ step.</P
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="AEN863"
+>2.6.2.1. Running MySQL as Non-Root</A
+></H3
+><DIV
+CLASS="section"
+><H4
+CLASS="section"
+><A
+NAME="AEN865"
+>2.6.2.1.1. The Custom Configuration Method</A
+></H4
+><P
+>Create a file .my.cnf in your
+ home directory (using /home/foo in this example)
+ as follows....</P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#13;[mysqld]
+datadir=/home/foo/mymysql
+socket=/home/foo/mymysql/thesock
+port=8081
+
+[mysql]
+socket=/home/foo/mymysql/thesock
+port=8081
+
+[mysql.server]
+user=mysql
+basedir=/var/lib
+
+[safe_mysqld]
+err-log=/home/foo/mymysql/the.log
+pid-file=/home/foo/mymysql/the.pid
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="section"
+><H4
+CLASS="section"
+><A
+NAME="AEN869"
+>2.6.2.1.2. The Custom Built Method</A
+></H4
+><P
+>You can install MySQL as a not-root, if you really need to.
+ Build it with PREFIX set to <TT
+CLASS="filename"
+>/home/foo/mysql</TT
+>,
+ or use pre-installed executables, specifying that you want
+ to put all of the data files in <TT
+CLASS="filename"
+>/home/foo/mysql/data</TT
+>.
+ If there is another MySQL server running on the system that you
+ do not own, use the -P option to specify a TCP port that is not
+ in use.</P
+></DIV
+><DIV
+CLASS="section"
+><H4
+CLASS="section"
+><A
+NAME="AEN874"
+>2.6.2.1.3. Starting the Server</A
+></H4
+><P
+>After your mysqld program is built and any .my.cnf file is
+ in place, you must initialize the databases (ONCE).</P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="screen"
+>&#13; <SAMP
+CLASS="prompt"
+>bash$</SAMP
+>
+ <B
+CLASS="command"
+>mysql_install_db</B
+>
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>Then start the daemon with</P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="screen"
+>&#13; <SAMP
+CLASS="prompt"
+>bash$</SAMP
+>
+ <B
+CLASS="command"
+>safe_mysql &#38;</B
+>
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>After you start mysqld the first time, you then connect to
+ it as "root" and <B
+CLASS="command"
+>GRANT</B
+> permissions to other
+ users. (Again, the MySQL root account has nothing to do with
+ the *NIX root account.)</P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>You will need to start the daemons yourself. You can either
+ ask your system administrator to add them to system startup files, or
+ add a crontab entry that runs a script to check on these daemons
+ and restart them if needed.</P
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>Do NOT run daemons or other services on a server without first
+ consulting your system administrator! Daemons use up system resources
+ and running one may be in violation of your terms of service for any
+ machine on which you are a user!</P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="AEN890"
+>2.6.3. Perl</A
+></H2
+><P
+>&#13; On the extremely rare chance that you don't have Perl on
+ the machine, you will have to build the sources
+ yourself. The following commands should get your system
+ installed with your own personal version of Perl:
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="screen"
+>&#13; <SAMP
+CLASS="prompt"
+>bash$</SAMP
+>
+ <B
+CLASS="command"
+>wget http://perl.org/CPAN/src/stable.tar.gz</B
+>
+ <SAMP
+CLASS="prompt"
+>bash$</SAMP
+>
+ <B
+CLASS="command"
+>tar zvxf stable.tar.gz</B
+>
+ <SAMP
+CLASS="prompt"
+>bash$</SAMP
+>
+ <B
+CLASS="command"
+>cd perl-5.8.1</B
+> (or whatever the version of Perl is called)
+ <SAMP
+CLASS="prompt"
+>bash$</SAMP
+>
+ <B
+CLASS="command"
+>sh Configure -de -Dprefix=/home/foo/perl</B
+>
+ <SAMP
+CLASS="prompt"
+>bash$</SAMP
+>
+ <B
+CLASS="command"
+>make &#38;&#38; make test &#38;&#38; make install</B
+>
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>&#13; Once you have Perl installed into a directory (probably
+ in <TT
+CLASS="filename"
+>~/perl/bin</TT
+>), you will need to
+ install the Perl Modules, described below.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="install-perlmodules-nonroot"
+>2.6.4. Perl Modules</A
+></H2
+><P
+>&#13; Installing the Perl modules as a non-root user is accomplished by
+ running the <TT
+CLASS="filename"
+>install-module.pl</TT
+>
+ script. For more details on this script, see
+ <A
+HREF="api/install-module.html"
+TARGET="_top"
+><TT
+CLASS="filename"
+>install-module.pl</TT
+>
+ documentation</A
+>
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="AEN912"
+>2.6.5. HTTP Server</A
+></H2
+><P
+>Ideally, this also needs to be installed as root and
+ run under a special web server account. As long as
+ the web server will allow the running of *.cgi files outside of a
+ cgi-bin, and a way of denying web access to certain files (such as a
+ .htaccess file), you should be good in this department.</P
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="AEN915"
+>2.6.5.1. Running Apache as Non-Root</A
+></H3
+><P
+>You can run Apache as a non-root user, but the port will need
+ to be set to one above 1024. If you type <B
+CLASS="command"
+>httpd -V</B
+>,
+ you will get a list of the variables that your system copy of httpd
+ uses. One of those, namely HTTPD_ROOT, tells you where that
+ installation looks for its config information.</P
+><P
+>From there, you can copy the config files to your own home
+ directory to start editing. When you edit those and then use the -d
+ option to override the HTTPD_ROOT compiled into the web server, you
+ get control of your own customized web server.</P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>You will need to start the daemons yourself. You can either
+ ask your system administrator to add them to system startup files, or
+ add a crontab entry that runs a script to check on these daemons
+ and restart them if needed.</P
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>Do NOT run daemons or other services on a server without first
+ consulting your system administrator! Daemons use up system resources
+ and running one may be in violation of your terms of service for any
+ machine on which you are a user!</P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="AEN924"
+>2.6.6. Bugzilla</A
+></H2
+><P
+>&#13; When you run <B
+CLASS="command"
+>./checksetup.pl</B
+> to create
+ the <TT
+CLASS="filename"
+>localconfig</TT
+> file, it will list the Perl
+ modules it finds. If one is missing, go back and double-check the
+ module installation from <A
+HREF="nonroot.html#install-perlmodules-nonroot"
+>Section 2.6.4</A
+>,
+ then delete the <TT
+CLASS="filename"
+>localconfig</TT
+> file and try again.
+ </P
+><DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>One option in <TT
+CLASS="filename"
+>localconfig</TT
+> you
+ might have problems with is the web server group. If you can't
+ successfully browse to the <TT
+CLASS="filename"
+>index.cgi</TT
+> (like
+ a Forbidden error), you may have to relax your permissions,
+ and blank out the web server group. Of course, this may pose
+ as a security risk. Having a properly jailed shell and/or
+ limited access to shell accounts may lessen the security risk,
+ but use at your own risk.</P
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="suexec"
+>2.6.6.1. suexec or shared hosting</A
+></H3
+><P
+>If you are running on a system that uses suexec (most shared
+ hosting environments do this), you will need to set the
+ <EM
+>webservergroup</EM
+> value in <TT
+CLASS="filename"
+>localconfig</TT
+>
+ to match <EM
+>your</EM
+> primary group, rather than the one
+ the web server runs under. You will need to run the following
+ shell commands after running <B
+CLASS="command"
+>./checksetup.pl</B
+>,
+ every time you run it (or modify <TT
+CLASS="filename"
+>checksetup.pl</TT
+>
+ to do them for you via the system() command).
+ <TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+> for i in docs graphs images js skins; do find $i -type d -exec chmod o+rx {} \; ; done
+ for i in jpg gif css js png html rdf xul; do find . -name \*.$i -exec chmod o+r {} \; ; done
+ find . -name .htaccess -exec chmod o+r {} \;
+ chmod o+x . data data/webdot</PRE
+></FONT
+></TD
+></TR
+></TABLE
+>
+ Pay particular attention to the number of semicolons and dots.
+ They are all important. A future version of Bugzilla will
+ hopefully be able to do this for you out of the box.</P
+></DIV
+></DIV
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="os-specific.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="upgrade.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>OS-Specific Installation Notes</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="installing-bugzilla.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Upgrading to New Releases</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/os-specific.html b/docs/en/html/os-specific.html
new file mode 100644
index 000000000..0f92c09ed
--- /dev/null
+++ b/docs/en/html/os-specific.html
@@ -0,0 +1,827 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>OS-Specific Installation Notes</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Installing Bugzilla"
+HREF="installing-bugzilla.html"><LINK
+REL="PREVIOUS"
+TITLE="Multiple Bugzilla databases with a single installation"
+HREF="multiple-bz-dbs.html"><LINK
+REL="NEXT"
+TITLE="UNIX (non-root) Installation Notes"
+HREF="nonroot.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="multiple-bz-dbs.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 2. Installing Bugzilla</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="nonroot.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="os-specific"
+>2.5. OS-Specific Installation Notes</A
+></H1
+><P
+>Many aspects of the Bugzilla installation can be affected by the
+ operating system you choose to install it on. Sometimes it can be made
+ easier and others more difficult. This section will attempt to help you
+ understand both the difficulties of running on specific operating systems
+ and the utilities available to make it easier.
+ </P
+><P
+>If you have anything to add or notes for an operating system not
+ covered, please file a bug in <A
+HREF="https://bugzilla.mozilla.org/enter_bug.cgi?product=Bugzilla&#38;component=Documentation"
+TARGET="_top"
+>Bugzilla Documentation</A
+>.
+ </P
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="os-win32"
+>2.5.1. Microsoft Windows</A
+></H2
+><P
+>&#13; Making Bugzilla work on Windows is more difficult than making it
+ work on Unix. For that reason, we still recommend doing so on a Unix
+ based system such as GNU/Linux. That said, if you do want to get
+ Bugzilla running on Windows, you will need to make the following
+ adjustments. A detailed step-by-step
+ <A
+HREF="https://wiki.mozilla.org/Bugzilla:Win32Install"
+TARGET="_top"
+>&#13; installation guide for Windows</A
+> is also available
+ if you need more help with your installation.
+ </P
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="win32-perl"
+>2.5.1.1. Win32 Perl</A
+></H3
+><P
+>&#13; Perl for Windows can be obtained from
+ <A
+HREF="http://www.activestate.com/"
+TARGET="_top"
+>ActiveState</A
+>.
+ You should be able to find a compiled binary at <A
+HREF="http://aspn.activestate.com/ASPN/Downloads/ActivePerl/"
+TARGET="_top"
+>http://aspn.activestate.com/ASPN/Downloads/ActivePerl/</A
+>.
+ The following instructions assume that you are using version
+ 5.8.1 of ActiveState.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; These instructions are for 32-bit versions of Windows. If you are
+ using a 64-bit version of Windows, you will need to install 32-bit
+ Perl in order to install the 32-bit modules as described below.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="win32-perl-modules"
+>2.5.1.2. Perl Modules on Win32</A
+></H3
+><P
+>&#13; Bugzilla on Windows requires the same perl modules found in
+ <A
+HREF="installation.html#install-perlmodules"
+>Section 2.1.5</A
+>. The main difference is that
+ windows uses <A
+HREF="glossary.html#gloss-ppm"
+><I
+CLASS="glossterm"
+>PPM</I
+></A
+> instead
+ of CPAN. ActiveState provides a GUI to manage Perl modules. We highly
+ recommend that you use it. If you prefer to use ppm from the
+ command-line, type:
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#13;C:\perl&#62; <B
+CLASS="command"
+>ppm install &#60;module name&#62;</B
+>
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>&#13; The best source for the Windows PPM modules needed for Bugzilla
+ is probably the theory58S website, which you can add to your list
+ of repositories as follows (for Perl 5.8.x):
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#13;<B
+CLASS="command"
+>ppm repo add theory58S http://theoryx5.uwinnipeg.ca/ppms/</B
+>
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>&#13; If you are using Perl 5.10.x, you cannot use the same PPM modules as Perl
+ 5.8.x as they are incompatible. In this case, you should add the following
+ repository:
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#13;<B
+CLASS="command"
+>ppm repo add theory58S http://cpan.uwinnipeg.ca/PPMPackages/10xx/</B
+>
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; In versions prior to 5.8.8 build 819 of PPM the command is
+ <TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#13;<B
+CLASS="command"
+>ppm repository add theory58S http://theoryx5.uwinnipeg.ca/ppms/</B
+>
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+>
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; The PPM repository stores modules in 'packages' that may have
+ a slightly different name than the module. If retrieving these
+ modules from there, you will need to pay attention to the information
+ provided when you run <B
+CLASS="command"
+>checksetup.pl</B
+> as it will
+ tell you what package you'll need to install.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="tip"
+><P
+></P
+><TABLE
+CLASS="tip"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/tip.gif"
+HSPACE="5"
+ALT="Tip"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; If you are behind a corporate firewall, you will need to let the
+ ActiveState PPM utility know how to get through it to access
+ the repositories by setting the HTTP_proxy system environmental
+ variable. For more information on setting that variable, see
+ the ActiveState documentation.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="win32-http"
+>2.5.1.3. Serving the web pages</A
+></H3
+><P
+>&#13; As is the case on Unix based systems, any web server should
+ be able to handle Bugzilla; however, the Bugzilla Team still
+ recommends Apache whenever asked. No matter what web server
+ you choose, be sure to pay attention to the security notes
+ in <A
+HREF="security-webserver.html#security-webserver-access"
+>Section 4.2.1</A
+>. More
+ information on configuring specific web servers can be found
+ in <A
+HREF="configuration.html#http"
+>Section 2.2.4</A
+>.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; The web server looks at <TT
+CLASS="filename"
+>/usr/bin/perl</TT
+> to
+ call Perl. If you are using Apache on windows, you can set the
+ <A
+HREF="http://httpd.apache.org/docs-2.0/mod/core.html#scriptinterpretersource"
+TARGET="_top"
+>ScriptInterpreterSource</A
+>
+ directive in your Apache config file to make it look at the
+ right place: insert the line
+ <TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>ScriptInterpreterSource Registry-Strict</PRE
+></FONT
+></TD
+></TR
+></TABLE
+>
+ into your <TT
+CLASS="filename"
+>httpd.conf</TT
+> file, and create the key
+ <TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>HKEY_CLASSES_ROOT\.cgi\Shell\ExecCGI\Command</PRE
+></FONT
+></TD
+></TR
+></TABLE
+>
+ with <CODE
+CLASS="option"
+>C:\Perl\bin\perl.exe -T</CODE
+> as value (adapt to your
+ path if needed) in the registry. When this is done, restart Apache.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="win32-email"
+>2.5.1.4. Sending Email</A
+></H3
+><P
+>&#13; To enable Bugzilla to send email on Windows, the server running the
+ Bugzilla code must be able to connect to, or act as, an SMTP server.
+ </P
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="os-macosx"
+>2.5.2. <SPAN
+CLASS="productname"
+>Mac OS X</SPAN
+></A
+></H2
+><P
+>Making Bugzilla work on Mac OS X requires the following
+ adjustments.</P
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="macosx-sendmail"
+>2.5.2.1. Sendmail</A
+></H3
+><P
+>In Mac OS X 10.3 and later,
+ <A
+HREF="http://www.postfix.org/"
+TARGET="_top"
+>Postfix</A
+>
+ is used as the built-in email server. Postfix provides an executable
+ that mimics sendmail enough to fool Bugzilla, as long as Bugzilla can
+ find it. Bugzilla is able to find the fake sendmail executable without
+ any assistance.</P
+></DIV
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="macosx-libraries"
+>2.5.2.2. Libraries &#38; Perl Modules on Mac OS X</A
+></H3
+><P
+>Apple does not include the GD library with Mac OS X. Bugzilla
+ needs this for bug graphs.</P
+><P
+>You can use DarwinPorts (<A
+HREF="http://darwinports.com/"
+TARGET="_top"
+>http://darwinports.com/</A
+>)
+ or Fink (<A
+HREF="http://sourceforge.net/projects/fink/"
+TARGET="_top"
+>http://sourceforge.net/projects/fink/</A
+>), both
+ of which are similar in nature to the CPAN installer, but install
+ common unix programs.</P
+><P
+>Follow the instructions for setting up DarwinPorts or Fink.
+ Once you have one installed, you'll want to use it to install the
+ <TT
+CLASS="filename"
+>gd2</TT
+> package.
+ </P
+><P
+>Fink will prompt you for a number of dependencies, type 'y' and hit
+ enter to install all of the dependencies and then watch it work. You will
+ then be able to use <A
+HREF="glossary.html#gloss-cpan"
+><I
+CLASS="glossterm"
+>CPAN</I
+></A
+> to
+ install the GD Perl module.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>To prevent creating conflicts with the software that Apple
+ installs by default, Fink creates its own directory tree at
+ <TT
+CLASS="filename"
+>/sw</TT
+> where it installs most of
+ the software that it installs. This means your libraries and headers
+ will be at <TT
+CLASS="filename"
+>/sw/lib</TT
+> and
+ <TT
+CLASS="filename"
+>/sw/include</TT
+> instead of
+ <TT
+CLASS="filename"
+>/usr/lib</TT
+> and
+ <TT
+CLASS="filename"
+>/usr/include</TT
+>. When the
+ Perl module config script asks where your <TT
+CLASS="filename"
+>libgd</TT
+>
+ is, be sure to tell it
+ <TT
+CLASS="filename"
+>/sw/lib</TT
+>.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>Also available via DarwinPorts and Fink is
+ <TT
+CLASS="filename"
+>expat</TT
+>. After installing the expat package, you
+ will be able to install XML::Parser using CPAN. If you use fink, there
+ is one caveat. Unlike recent versions of
+ the GD module, XML::Parser doesn't prompt for the location of the
+ required libraries. When using CPAN, you will need to use the following
+ command sequence:
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="screen"
+>&#13;# perl -MCPAN -e'look XML::Parser' <A
+NAME="macosx-look"
+><IMG
+SRC="../images/callouts/1.gif"
+HSPACE="0"
+VSPACE="0"
+BORDER="0"
+ALT="(1)"></A
+>
+# perl Makefile.PL EXPATLIBPATH=/sw/lib EXPATINCPATH=/sw/include
+# make; make test; make install <A
+NAME="macosx-make"
+><IMG
+SRC="../images/callouts/2.gif"
+HSPACE="0"
+VSPACE="0"
+BORDER="0"
+ALT="(2)"></A
+>
+# exit <A
+NAME="macosx-exit"
+><IMG
+SRC="../images/callouts/3.gif"
+HSPACE="0"
+VSPACE="0"
+BORDER="0"
+ALT="(3)"></A
+>
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><DIV
+CLASS="calloutlist"
+><DL
+COMPACT="COMPACT"
+><DT
+><A
+HREF="os-specific.html#macosx-look"
+><IMG
+SRC="../images/callouts/1.gif"
+HSPACE="0"
+VSPACE="0"
+BORDER="0"
+ALT="(1)"></A
+><A
+HREF="os-specific.html#macosx-exit"
+><IMG
+SRC="../images/callouts/3.gif"
+HSPACE="0"
+VSPACE="0"
+BORDER="0"
+ALT="(3)"></A
+></DT
+><DD
+>The look command will download the module and spawn a
+ new shell with the extracted files as the current working directory.
+ The exit command will return you to your original shell.
+ </DD
+><DT
+><A
+HREF="os-specific.html#macosx-make"
+><IMG
+SRC="../images/callouts/2.gif"
+HSPACE="0"
+VSPACE="0"
+BORDER="0"
+ALT="(2)"></A
+></DT
+><DD
+>You should watch the output from these make commands,
+ especially <SPAN
+CLASS="QUOTE"
+>"make test"</SPAN
+> as errors may prevent
+ XML::Parser from functioning correctly with Bugzilla.
+ </DD
+></DL
+></DIV
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="os-linux"
+>2.5.3. Linux Distributions</A
+></H2
+><P
+>Many Linux distributions include Bugzilla and its
+ dependencies in their native package management systems.
+ Installing Bugzilla with root access on any Linux system
+ should be as simple as finding the Bugzilla package in the
+ package management application and installing it using the
+ normal command syntax. Several distributions also perform
+ the proper web server configuration automatically on installation.
+ </P
+><P
+>Please consult the documentation of your Linux
+ distribution for instructions on how to install packages,
+ or for specific instructions on installing Bugzilla with
+ native package management tools. There is also a
+ <A
+HREF="http://wiki.mozilla.org/Bugzilla:Linux_Distro_Installation"
+TARGET="_top"
+>&#13; Bugzilla Wiki Page</A
+> for distro-specific installation
+ notes.
+ </P
+></DIV
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="multiple-bz-dbs.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="nonroot.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Multiple Bugzilla databases with a single installation</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="installing-bugzilla.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>UNIX (non-root) Installation Notes</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/parameters.html b/docs/en/html/parameters.html
new file mode 100644
index 000000000..d86c11168
--- /dev/null
+++ b/docs/en/html/parameters.html
@@ -0,0 +1,1343 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Bugzilla Configuration</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Administering Bugzilla"
+HREF="administration.html"><LINK
+REL="PREVIOUS"
+TITLE="Administering Bugzilla"
+HREF="administration.html"><LINK
+REL="NEXT"
+TITLE="User Administration"
+HREF="useradmin.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="administration.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 3. Administering Bugzilla</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="useradmin.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="parameters"
+>3.1. Bugzilla Configuration</A
+></H1
+><P
+>&#13; Bugzilla is configured by changing various parameters, accessed
+ from the "Parameters" link in the Administration page (the
+ Administration page can be found by clicking the "Administration"
+ link in the footer). The parameters are divided into several categories,
+ accessed via the menu on the left. Following is a description of the
+ different categories and important parameters within those categories.
+ </P
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="param-requiredsettings"
+>3.1.1. Required Settings</A
+></H2
+><P
+>&#13; The core required parameters for any Bugzilla installation are set
+ here. These parameters must be set before a new Bugzilla installation
+ can be used. Administrators should review this list before
+ deploying a new Bugzilla installation.
+ </P
+><P
+></P
+><DIV
+CLASS="variablelist"
+><DL
+><DT
+>maintainer</DT
+><DD
+><P
+>
+ Email address of the person
+ responsible for maintaining this Bugzilla installation.
+ The address need not be that of a valid Bugzilla account.
+ </P
+></DD
+><DT
+>urlbase</DT
+><DD
+><P
+>&#13; Defines the fully qualified domain name and web
+ server path to this Bugzilla installation.
+ </P
+><P
+>&#13; For example, if the Bugzilla query page is
+ <TT
+CLASS="filename"
+>http://www.foo.com/bugzilla/query.cgi</TT
+>,
+ the <SPAN
+CLASS="QUOTE"
+>"urlbase"</SPAN
+> should be set
+ to <TT
+CLASS="filename"
+>http://www.foo.com/bugzilla/</TT
+>.
+ </P
+></DD
+><DT
+>docs_urlbase</DT
+><DD
+><P
+>&#13; Defines path to the Bugzilla documentation. This can be a fully
+ qualified domain name, or a path relative to "urlbase".
+ </P
+><P
+>&#13; For example, if the "Bugzilla Configuration" page
+ of the documentation is
+ <TT
+CLASS="filename"
+>http://www.foo.com/bugzilla/docs/html/parameters.html</TT
+>,
+ set the <SPAN
+CLASS="QUOTE"
+>"docs_urlbase"</SPAN
+>
+ to <TT
+CLASS="filename"
+>http://www.foo.com/bugzilla/docs/html/</TT
+>.
+ </P
+></DD
+><DT
+>sslbase</DT
+><DD
+><P
+>&#13; Defines the fully qualified domain name and web
+ server path for HTTPS (SSL) connections to this Bugzilla installation.
+ </P
+><P
+>&#13; For example, if the Bugzilla main page is
+ <TT
+CLASS="filename"
+>https://www.foo.com/bugzilla/index.cgi</TT
+>,
+ the <SPAN
+CLASS="QUOTE"
+>"sslbase"</SPAN
+> should be set
+ to <TT
+CLASS="filename"
+>https://www.foo.com/bugzilla/</TT
+>.
+ </P
+></DD
+><DT
+>ssl_redirect</DT
+><DD
+><P
+>&#13; If enabled, Bugzilla will force HTTPS (SSL) connections, by
+ automatically redirecting any users who try to use a non-SSL
+ connection.
+ </P
+></DD
+><DT
+>cookiedomain</DT
+><DD
+><P
+>&#13; Defines the domain for Bugzilla cookies. This is typically left blank.
+ If there are multiple hostnames that point to the same webserver, which
+ require the same cookie, then this parameter can be utilized. For
+ example, If your website is at
+ <TT
+CLASS="filename"
+>https://www.foo.com/</TT
+>, setting this to
+ <TT
+CLASS="filename"
+>.foo.com/</TT
+> will also allow
+ <TT
+CLASS="filename"
+>bar.foo.com/</TT
+> to access Bugzilla cookies.
+ </P
+></DD
+><DT
+>cookiepath</DT
+><DD
+><P
+>&#13; Defines a path, relative to the web server root, that Bugzilla
+ cookies will be restricted to. For example, if the
+ <B
+CLASS="command"
+>urlbase</B
+> is set to
+ <TT
+CLASS="filename"
+>http://www.foo.com/bugzilla/</TT
+>, the
+ <B
+CLASS="command"
+>cookiepath</B
+> should be set to
+ <TT
+CLASS="filename"
+>/bugzilla/</TT
+>. Setting it to "/" will allow all sites
+ served by this web server or virtual host to read Bugzilla cookies.
+ </P
+></DD
+><DT
+>utf8</DT
+><DD
+><P
+>&#13; Determines whether to use UTF-8 (Unicode) encoding for all text in
+ Bugzilla. New installations should set this to true to avoid character
+ encoding problems. Existing databases should set this to true only
+ after the data has been converted from existing legacy character
+ encoding to UTF-8, using the
+ <TT
+CLASS="filename"
+>contrib/recode.pl</TT
+> script.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; If you turn this parameter from "off" to "on", you must re-run
+ <TT
+CLASS="filename"
+>checksetup.pl</TT
+> immediately afterward.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DD
+><DT
+>shutdownhtml</DT
+><DD
+><P
+>&#13; If there is any text in this field, this Bugzilla installation will
+ be completely disabled and this text will appear instead of all
+ Bugzilla pages for all users, including Admins. Used in the event
+ of site maintenance or outage situations.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Although regular log-in capability is disabled while
+ <B
+CLASS="command"
+>shutdownhtml</B
+>
+ is enabled, safeguards are in place to protect the unfortunate
+ admin who loses connection to Bugzilla. Should this happen to you,
+ go directly to the <TT
+CLASS="filename"
+>editparams.cgi</TT
+> (by typing
+ the URL in manually, if necessary). Doing this will prompt you to
+ log in, and your name/password will be accepted here (but nowhere
+ else).
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DD
+><DT
+>announcehtml</DT
+><DD
+><P
+>&#13; Any text in this field will be displayed at the top of every HTML
+ page in this Bugzilla installation. The text is not wrapped in any
+ tags. For best results, wrap the text in a <SPAN
+CLASS="QUOTE"
+>"&#60;div&#62;"</SPAN
+>
+ tag. Any style attributes from the CSS can be applied. For example,
+ to make the text green inside of a red box, add <SPAN
+CLASS="QUOTE"
+>"id=message"</SPAN
+>
+ to the <SPAN
+CLASS="QUOTE"
+>"&#60;div&#62;"</SPAN
+> tag.
+ </P
+></DD
+><DT
+>proxy_url</DT
+><DD
+><P
+>&#13; If this Bugzilla installation is behind a proxy, enter the proxy
+ information here to enable Bugzilla to access the Internet. Bugzilla
+ requires Internet access to utilize the
+ <B
+CLASS="command"
+>upgrade_notification</B
+> parameter (below). If the
+ proxy requires authentication, use the syntax:
+ <TT
+CLASS="filename"
+>http://user:pass@proxy_url/</TT
+>.
+ </P
+></DD
+><DT
+>upgrade_notification</DT
+><DD
+><P
+>&#13; Enable or disable a notification on the homepage of this Bugzilla
+ installation when a newer version of Bugzilla is available. This
+ notification is only visible to administrators. Choose "disabled",
+ to turn off the notification. Otherwise, choose which version of
+ Bugzilla you want to be notified about: "development_snapshot" is the
+ latest release on the trunk; "latest_stable_release" is the most
+ recent release available on the most recent stable branch;
+ "stable_branch_release" the most recent release on the branch
+ this installation is based on.
+ </P
+></DD
+></DL
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="param-admin-policies"
+>3.1.2. Administrative Policies</A
+></H2
+><P
+>&#13; This page contains parameters for basic administrative functions.
+ Options include whether to allow the deletion of bugs and users,
+ and whether to allow users to change their email address.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="param-user-authentication"
+>3.1.3. User Authentication</A
+></H2
+><P
+>&#13; This page contains the settings that control how this Bugzilla
+ installation will do its authentication. Choose what authentication
+ mechanism to use (the Bugzilla database, or an external source such
+ as LDAP), and set basic behavioral parameters. For example, choose
+ whether to require users to login to browse bugs, the management
+ of authentication cookies, and the regular expression used to
+ validate email addresses. Some parameters are highlighted below.
+ </P
+><P
+></P
+><DIV
+CLASS="variablelist"
+><DL
+><DT
+>emailregexp</DT
+><DD
+><P
+>&#13; Defines the regular expression used to validate email addresses
+ used for login names. The default attempts to match fully
+ qualified email addresses (i.e. 'user@example.com'). Some
+ Bugzilla installations allow only local user names (i.e 'user'
+ instead of 'user@example.com'). In that case, the
+ <B
+CLASS="command"
+>emailsuffix</B
+> parameter should be used to define
+ the email domain.
+ </P
+></DD
+><DT
+>emailsuffix</DT
+><DD
+><P
+>&#13; This string is appended to login names when actually sending
+ email to a user. For example,
+ If <B
+CLASS="command"
+>emailregexp</B
+> has been set to allow
+ local usernames,
+ then this parameter would contain the email domain for all users
+ (i.e. '@example.com').
+ </P
+></DD
+></DL
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="param-attachments"
+>3.1.4. Attachments</A
+></H2
+><P
+>&#13; This page allows for setting restrictions and other parameters
+ regarding attachments to bugs. For example, control size limitations
+ and whether to allow pointing to external files via a URI.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="param-bug-change-policies"
+>3.1.5. Bug Change Policies</A
+></H2
+><P
+>&#13; Set policy on default behavior for bug change events. For example,
+ choose which status to set a bug to when it is marked as a duplicate,
+ and choose whether to allow bug reporters to set the priority or
+ target milestone. Also allows for configuration of what changes
+ should require the user to make a comment, described below.
+ </P
+><P
+></P
+><DIV
+CLASS="variablelist"
+><DL
+><DT
+>commenton*</DT
+><DD
+><P
+>&#13; All these fields allow you to dictate what changes can pass
+ without comment, and which must have a comment from the
+ person who changed them. Often, administrators will allow
+ users to add themselves to the CC list, accept bugs, or
+ change the Status Whiteboard without adding a comment as to
+ their reasons for the change, yet require that most other
+ changes come with an explanation.
+ </P
+><P
+>&#13; Set the "commenton" options according to your site policy. It
+ is a wise idea to require comments when users resolve, reassign, or
+ reopen bugs at the very least.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; It is generally far better to require a developer comment
+ when resolving bugs than not. Few things are more annoying to bug
+ database users than having a developer mark a bug "fixed" without
+ any comment as to what the fix was (or even that it was truly
+ fixed!)
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DD
+><DT
+>noresolveonopenblockers</DT
+><DD
+><P
+>&#13; This option will prevent users from resolving bugs as FIXED if
+ they have unresolved dependencies. Only the FIXED resolution
+ is affected. Users will be still able to resolve bugs to
+ resolutions other than FIXED if they have unresolved dependent
+ bugs.
+ </P
+></DD
+></DL
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="param-bugfields"
+>3.1.6. Bug Fields</A
+></H2
+><P
+>&#13; The parameters in this section determine the default settings of
+ several Bugzilla fields for new bugs, and also control whether
+ certain fields are used. For example, choose whether to use the
+ "target milestone" field or the "status whiteboard" field.
+ </P
+><P
+></P
+><DIV
+CLASS="variablelist"
+><DL
+><DT
+>useqacontact</DT
+><DD
+><P
+>&#13; This allows you to define an email address for each component,
+ in addition to that of the default assignee, who will be sent
+ carbon copies of incoming bugs.
+ </P
+></DD
+><DT
+>usestatuswhiteboard</DT
+><DD
+><P
+>&#13; This defines whether you wish to have a free-form, overwritable field
+ associated with each bug. The advantage of the Status Whiteboard is
+ that it can be deleted or modified with ease, and provides an
+ easily-searchable field for indexing some bugs that have some trait
+ in common.
+ </P
+></DD
+></DL
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="param-bugmoving"
+>3.1.7. Bug Moving</A
+></H2
+><P
+>&#13; This page controls whether this Bugzilla installation allows certain
+ users to move bugs to an external database. If bug moving is enabled,
+ there are a number of parameters that control bug moving behaviors.
+ For example, choose which users are allowed to move bugs, the location
+ of the external database, and the default product and component that
+ bugs moved <EM
+>from</EM
+> other bug databases to this
+ Bugzilla installation are assigned to.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="param-dependency-graphs"
+>3.1.8. Dependency Graphs</A
+></H2
+><P
+>&#13; This page has one parameter that sets the location of a Web Dot
+ server, or of the Web Dot binary on the local system, that is used
+ to generate dependency graphs. Web Dot is a CGI program that creates
+ images from <TT
+CLASS="filename"
+>.dot</TT
+> graphic description files. If
+ no Web Dot server or binary is specified, then dependency graphs will
+ be disabled.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="param-group-security"
+>3.1.9. Group Security</A
+></H2
+><P
+>&#13; Bugzilla allows for the creation of different groups, with the
+ ability to restrict the visibility of bugs in a group to a set of
+ specific users. Specific products can also be associated with
+ groups, and users restricted to only see products in their groups.
+ Several parameters are described in more detail below. Most of the
+ configuration of groups and their relationship to products is done
+ on the "Groups" and "Product" pages of the "Administration" area.
+ The options on this page control global default behavior.
+ For more information on Groups and Group Security, see
+ <A
+HREF="groups.html"
+>Section 3.15</A
+>
+ </P
+><P
+></P
+><DIV
+CLASS="variablelist"
+><DL
+><DT
+>makeproductgroups</DT
+><DD
+><P
+>&#13; Determines whether or not to automatically create groups
+ when new products are created. If this is on, the groups will be
+ used for querying bugs.
+ </P
+></DD
+><DT
+>usevisibilitygroups</DT
+><DD
+><P
+>&#13; If selected, user visibility will be restricted to members of
+ groups, as selected in the group configuration settings.
+ Each user-defined group can be allowed to see members of selected
+ other groups.
+ For details on configuring groups (including the visibility
+ restrictions) see <A
+HREF="groups.html#edit-groups"
+>Section 3.15.2</A
+>.
+ </P
+></DD
+><DT
+>querysharegroup</DT
+><DD
+><P
+>&#13; The name of the group of users who are allowed to share saved
+ searches with one another. For more information on using
+ saved searches, see <A
+HREF="userpreferences.html#savedsearches"
+>Saved Searches</A
+>.
+ </P
+></DD
+></DL
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="bzldap"
+>3.1.10. LDAP Authentication</A
+></H2
+><P
+>LDAP authentication is a module for Bugzilla's plugin
+ authentication architecture. This page contains all the parameters
+ necessary to configure Bugzilla for use with LDAP authentication.
+ </P
+><P
+>&#13; The existing authentication
+ scheme for Bugzilla uses email addresses as the primary user ID, and a
+ password to authenticate that user. All places within Bugzilla that
+ require a user ID (e.g assigning a bug) use the email
+ address. The LDAP authentication builds on top of this scheme, rather
+ than replacing it. The initial log-in is done with a username and
+ password for the LDAP directory. Bugzilla tries to bind to LDAP using
+ those credentials and, if successful, tries to map this account to a
+ Bugzilla account. If an LDAP mail attribute is defined, the value of this
+ attribute is used, otherwise the "emailsuffix" parameter is appended to LDAP
+ username to form a full email address. If an account for this address
+ already exists in the Bugzilla installation, it will log in to that account.
+ If no account for that email address exists, one is created at the time
+ of login. (In this case, Bugzilla will attempt to use the "displayName"
+ or "cn" attribute to determine the user's full name.) After
+ authentication, all other user-related tasks are still handled by email
+ address, not LDAP username. For example, bugs are still assigned by
+ email address and users are still queried by email address.
+ </P
+><DIV
+CLASS="caution"
+><P
+></P
+><TABLE
+CLASS="caution"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/caution.gif"
+HSPACE="5"
+ALT="Caution"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>Because the Bugzilla account is not created until the first time
+ a user logs in, a user who has not yet logged is unknown to Bugzilla.
+ This means they cannot be used as an assignee or QA contact (default or
+ otherwise), added to any CC list, or any other such operation. One
+ possible workaround is the <TT
+CLASS="filename"
+>bugzilla_ldapsync.rb</TT
+>
+ script in the
+ <A
+HREF="glossary.html#gloss-contrib"
+><I
+CLASS="glossterm"
+>&#13; <TT
+CLASS="filename"
+>contrib</TT
+></I
+></A
+>
+ directory. Another possible solution is fixing
+ <A
+HREF="https://bugzilla.mozilla.org/show_bug.cgi?id=201069"
+TARGET="_top"
+>bug
+ 201069</A
+>.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>Parameters required to use LDAP Authentication:</P
+><P
+></P
+><DIV
+CLASS="variablelist"
+><DL
+><DT
+><A
+NAME="param-user_verify_class_for_ldap"
+></A
+>user_verify_class</DT
+><DD
+><P
+>If you want to list <SPAN
+CLASS="QUOTE"
+>"LDAP"</SPAN
+> here,
+ make sure to have set up the other parameters listed below.
+ Unless you have other (working) authentication methods listed as
+ well, you may otherwise not be able to log back in to Bugzilla once
+ you log out.
+ If this happens to you, you will need to manually edit
+ <TT
+CLASS="filename"
+>data/params</TT
+> and set user_verify_class to
+ <SPAN
+CLASS="QUOTE"
+>"DB"</SPAN
+>.
+ </P
+></DD
+><DT
+><A
+NAME="param-LDAPserver"
+></A
+>LDAPserver</DT
+><DD
+><P
+>This parameter should be set to the name (and optionally the
+ port) of your LDAP server. If no port is specified, it assumes
+ the default LDAP port of 389.
+ </P
+><P
+>For example: <SPAN
+CLASS="QUOTE"
+>"ldap.company.com"</SPAN
+>
+ or <SPAN
+CLASS="QUOTE"
+>"ldap.company.com:3268"</SPAN
+>
+ </P
+><P
+>You can also specify a LDAP URI, so as to use other
+ protocols, such as LDAPS or LDAPI. If port was not specified in
+ the URI, the default is either 389 or 636 for 'LDAP' and 'LDAPS'
+ schemes respectively.
+ </P
+><DIV
+CLASS="tip"
+><P
+></P
+><TABLE
+CLASS="tip"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/tip.gif"
+HSPACE="5"
+ALT="Tip"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; In order to use SSL with LDAP, specify a URI with "ldaps://".
+ This will force the use of SSL over port 636.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>For example, normal LDAP:
+ <SPAN
+CLASS="QUOTE"
+>"ldap://ldap.company.com"</SPAN
+>, LDAP over SSL:
+ <SPAN
+CLASS="QUOTE"
+>"ldaps://ldap.company.com"</SPAN
+> or LDAP over a UNIX
+ domain socket <SPAN
+CLASS="QUOTE"
+>"ldapi://%2fvar%2flib%2fldap_sock"</SPAN
+>.
+ </P
+></DD
+><DT
+><A
+NAME="param-LDAPbinddn"
+></A
+>LDAPbinddn [Optional]</DT
+><DD
+><P
+>Some LDAP servers will not allow an anonymous bind to search
+ the directory. If this is the case with your configuration you
+ should set the LDAPbinddn parameter to the user account Bugzilla
+ should use instead of the anonymous bind.
+ </P
+><P
+>Ex. <SPAN
+CLASS="QUOTE"
+>"cn=default,cn=user:password"</SPAN
+></P
+></DD
+><DT
+><A
+NAME="param-LDAPBaseDN"
+></A
+>LDAPBaseDN</DT
+><DD
+><P
+>The LDAPBaseDN parameter should be set to the location in
+ your LDAP tree that you would like to search for email addresses.
+ Your uids should be unique under the DN specified here.
+ </P
+><P
+>Ex. <SPAN
+CLASS="QUOTE"
+>"ou=People,o=Company"</SPAN
+></P
+></DD
+><DT
+><A
+NAME="param-LDAPuidattribute"
+></A
+>LDAPuidattribute</DT
+><DD
+><P
+>The LDAPuidattribute parameter should be set to the attribute
+ which contains the unique UID of your users. The value retrieved
+ from this attribute will be used when attempting to bind as the
+ user to confirm their password.
+ </P
+><P
+>Ex. <SPAN
+CLASS="QUOTE"
+>"uid"</SPAN
+></P
+></DD
+><DT
+><A
+NAME="param-LDAPmailattribute"
+></A
+>LDAPmailattribute</DT
+><DD
+><P
+>The LDAPmailattribute parameter should be the name of the
+ attribute which contains the email address your users will enter
+ into the Bugzilla login boxes.
+ </P
+><P
+>Ex. <SPAN
+CLASS="QUOTE"
+>"mail"</SPAN
+></P
+></DD
+></DL
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="bzradius"
+>3.1.11. RADIUS Authentication</A
+></H2
+><P
+>&#13; RADIUS authentication is a module for Bugzilla's plugin
+ authentication architecture. This page contains all the parameters
+ necessary for configuring Bugzilla to use RADIUS authentication.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Most caveats that apply to LDAP authentication apply to RADIUS
+ authentication as well. See <A
+HREF="parameters.html#bzldap"
+>Section 3.1.10</A
+> for details.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>Parameters required to use RADIUS Authentication:</P
+><P
+></P
+><DIV
+CLASS="variablelist"
+><DL
+><DT
+><A
+NAME="param-user_verify_class_for_radius"
+></A
+>user_verify_class</DT
+><DD
+><P
+>If you want to list <SPAN
+CLASS="QUOTE"
+>"RADIUS"</SPAN
+> here,
+ make sure to have set up the other parameters listed below.
+ Unless you have other (working) authentication methods listed as
+ well, you may otherwise not be able to log back in to Bugzilla once
+ you log out.
+ If this happens to you, you will need to manually edit
+ <TT
+CLASS="filename"
+>data/params</TT
+> and set user_verify_class to
+ <SPAN
+CLASS="QUOTE"
+>"DB"</SPAN
+>.
+ </P
+></DD
+><DT
+><A
+NAME="param-RADIUS_server"
+></A
+>RADIUS_server</DT
+><DD
+><P
+>This parameter should be set to the name (and optionally the
+ port) of your RADIUS server.
+ </P
+></DD
+><DT
+><A
+NAME="param-RADIUS_secret"
+></A
+>RADIUS_secret</DT
+><DD
+><P
+>This parameter should be set to the RADIUS server's secret.
+ </P
+></DD
+><DT
+><A
+NAME="param-RADIUS_email_suffix"
+></A
+>RADIUS_email_suffix</DT
+><DD
+><P
+>Bugzilla needs an e-mail address for each user account.
+ Therefore, it needs to determine the e-mail address corresponding
+ to a RADIUS user.
+ Bugzilla offers only a simple way to do this: it can concatenate
+ a suffix to the RADIUS user name to convert it into an e-mail
+ address.
+ You can specify this suffix in the RADIUS_email_suffix parameter.
+ </P
+><P
+>If this simple solution does not work for you, you'll
+ probably need to modify
+ <TT
+CLASS="filename"
+>Bugzilla/Auth/Verify/RADIUS.pm</TT
+> to match your
+ requirements.
+ </P
+></DD
+></DL
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="param-email"
+>3.1.12. Email</A
+></H2
+><P
+>&#13; This page contains all of the parameters for configuring how
+ Bugzilla deals with the email notifications it sends. See below
+ for a summary of important options.
+ </P
+><P
+></P
+><DIV
+CLASS="variablelist"
+><DL
+><DT
+>mail_delivery_method</DT
+><DD
+><P
+>&#13; This is used to specify how email is sent, or if it is sent at
+ all. There are several options included for different MTAs,
+ along with two additional options that disable email sending.
+ "Test" does not send mail, but instead saves it in
+ <TT
+CLASS="filename"
+>data/mailer.testfile</TT
+> for later review.
+ "None" disables email sending entirely.
+ </P
+></DD
+><DT
+>mailfrom</DT
+><DD
+><P
+>&#13; This is the email address that will appear in the "From" field
+ of all emails sent by this Bugzilla installation. Some email
+ servers require mail to be from a valid email address, therefore
+ it is recommended to choose a valid email address here.
+ </P
+></DD
+><DT
+>whinedays</DT
+><DD
+><P
+>&#13; Set this to the number of days you want to let bugs go
+ in the CONFIRMED state before notifying people they have
+ untouched new bugs. If you do not plan to use this feature, simply
+ do not set up the whining cron job described in the installation
+ instructions, or set this value to "0" (never whine).
+ </P
+></DD
+><DT
+>globalwatcher</DT
+><DD
+><P
+>&#13; This allows you to define specific users who will
+ receive notification each time a new bug in entered, or when
+ an existing bug changes, according to the normal groupset
+ permissions. It may be useful for sending notifications to a
+ mailing-list, for instance.
+ </P
+></DD
+></DL
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="param-patchviewer"
+>3.1.13. Patch Viewer</A
+></H2
+><P
+>&#13; This page contains configuration parameters for the CVS server,
+ Bonsai server and LXR server that Bugzilla will use to enable the
+ features of the Patch Viewer. Bonsai is a tool that enables queries
+ to a CVS tree. LXR is a tool that can cross reference and index source
+ code.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="param-querydefaults"
+>3.1.14. Query Defaults</A
+></H2
+><P
+>&#13; This page controls the default behavior of Bugzilla in regards to
+ several aspects of querying bugs. Options include what the default
+ query options are, what the "My Bugs" page returns, whether users
+ can freely add bugs to the quip list, and how many duplicate bugs are
+ needed to add a bug to the "most frequently reported" list.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="param-shadowdatabase"
+>3.1.15. Shadow Database</A
+></H2
+><P
+>&#13; This page controls whether a shadow database is used, and all the
+ parameters associated with the shadow database. Versions of Bugzilla
+ prior to 3.2 used the MyISAM table type, which supports
+ only table-level write locking. With MyISAM, any time someone is making a change to
+ a bug, the entire table is locked until the write operation is complete.
+ Locking for write also blocks reads until the write is complete.
+ </P
+><P
+>&#13; The <SPAN
+CLASS="QUOTE"
+>"shadowdb"</SPAN
+> parameter was designed to get around
+ this limitation. While only a single user is allowed to write to
+ a table at a time, reads can continue unimpeded on a read-only
+ shadow copy of the database.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; As of version 3.2, Bugzilla no longer uses the MyISAM table type.
+ Instead, InnoDB is used, which can do transaction-based locking.
+ Therefore, the limitations the Shadow Database feature was designed
+ to workaround no longer exist.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="admin-usermatching"
+>3.1.16. User Matching</A
+></H2
+><P
+>&#13; The settings on this page control how users are selected and queried
+ when adding a user to a bug. For example, users need to be selected
+ when choosing who the bug is assigned to, adding to the CC list or
+ selecting a QA contact. With the "usemenuforusers" parameter, it is
+ possible to configure Bugzilla to
+ display a list of users in the fields instead of an empty text field.
+ This should only be used in Bugzilla installations with a small number
+ of users. If users are selected via a text box, this page also
+ contains parameters for how user names can be queried and matched
+ when entered.
+ </P
+></DIV
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="administration.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="useradmin.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Administering Bugzilla</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="administration.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>User Administration</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/paranoid-security.html b/docs/en/html/paranoid-security.html
new file mode 100644
index 000000000..1f6bc984b
--- /dev/null
+++ b/docs/en/html/paranoid-security.html
@@ -0,0 +1,196 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>cannot chdir(/var/spool/mqueue)</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Troubleshooting"
+HREF="troubleshooting.html"><LINK
+REL="PREVIOUS"
+TITLE="DBD::Sponge::db prepare failed"
+HREF="trbl-dbdsponge.html"><LINK
+REL="NEXT"
+TITLE="Everybody is constantly being forced to relogin"
+HREF="trbl-relogin-everyone.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="trbl-dbdsponge.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Appendix A. Troubleshooting</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="trbl-relogin-everyone.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="paranoid-security"
+>A.5. cannot chdir(/var/spool/mqueue)</A
+></H1
+><P
+>If you are installing Bugzilla on SuSE Linux, or some other
+ distributions with <SPAN
+CLASS="QUOTE"
+>"paranoid"</SPAN
+> security options, it is
+ possible that the checksetup.pl script may fail with the error:
+<TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>cannot chdir(/var/spool/mqueue): Permission denied
+</PRE
+></FONT
+></TD
+></TR
+></TABLE
+>
+ </P
+><P
+>This is because your <TT
+CLASS="filename"
+>/var/spool/mqueue</TT
+>
+ directory has a mode of <SAMP
+CLASS="computeroutput"
+>drwx------</SAMP
+>.
+ Type <B
+CLASS="command"
+>chmod 755 <TT
+CLASS="filename"
+>/var/spool/mqueue</TT
+></B
+>
+ as root to fix this problem. This will allow any process running on your
+ machine the ability to <EM
+>read</EM
+> the
+ <TT
+CLASS="filename"
+>/var/spool/mqueue</TT
+> directory.
+ </P
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="trbl-dbdsponge.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="trbl-relogin-everyone.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>DBD::Sponge::db prepare failed</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="troubleshooting.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Everybody is constantly being forced to relogin</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/patches.html b/docs/en/html/patches.html
new file mode 100644
index 000000000..8afe9f68c
--- /dev/null
+++ b/docs/en/html/patches.html
@@ -0,0 +1,169 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Contrib</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="PREVIOUS"
+TITLE='
+ checksetup.pl reports "Client does not support authentication protocol
+ requested by server..."
+ '
+HREF="trbl-passwd-encryption.html"><LINK
+REL="NEXT"
+TITLE="Command-line Search Interface"
+HREF="cmdline.html"></HEAD
+><BODY
+CLASS="appendix"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="trbl-passwd-encryption.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+></TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="cmdline.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="appendix"
+><H1
+><A
+NAME="patches"
+></A
+>Appendix B. Contrib</H1
+><DIV
+CLASS="TOC"
+><DL
+><DT
+><B
+>Table of Contents</B
+></DT
+><DT
+>B.1. <A
+HREF="cmdline.html"
+>Command-line Search Interface</A
+></DT
+><DT
+>B.2. <A
+HREF="cmdline-bugmail.html"
+>Command-line 'Send Unsent Bug-mail' tool</A
+></DT
+></DL
+></DIV
+><P
+>&#13; There are a number of unofficial Bugzilla add-ons in the
+ <TT
+CLASS="filename"
+>$BUGZILLA_ROOT/contrib/</TT
+>
+ directory. This section documents them.
+ </P
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="trbl-passwd-encryption.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="cmdline.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>checksetup.pl reports "Client does not support authentication protocol
+ requested by server..."</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+>&nbsp;</TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Command-line Search Interface</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/products.html b/docs/en/html/products.html
new file mode 100644
index 000000000..b8af97253
--- /dev/null
+++ b/docs/en/html/products.html
@@ -0,0 +1,817 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Products</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Administering Bugzilla"
+HREF="administration.html"><LINK
+REL="PREVIOUS"
+TITLE="Classifications"
+HREF="classifications.html"><LINK
+REL="NEXT"
+TITLE="Components"
+HREF="components.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="classifications.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 3. Administering Bugzilla</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="components.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="products"
+>3.4. Products</A
+></H1
+><P
+>&#13; <A
+HREF="glossary.html#gloss-product"
+><I
+CLASS="glossterm"
+>&#13; Products</I
+></A
+> typically represent real-world
+ shipping products. Products can be given
+ <A
+HREF="classifications.html"
+>Classifications</A
+>.
+ For example, if a company makes computer games,
+ they could have a classification of "Games", and a separate
+ product for each game. This company might also have a
+ <SPAN
+CLASS="QUOTE"
+>"Common"</SPAN
+> product for units of technology used
+ in multiple games, and perhaps a few special products that
+ represent items that are not actually shipping products
+ (for example, "Website", or "Administration").
+ </P
+><P
+>&#13; Many of Bugzilla's settings are configurable on a per-product
+ basis. The number of <SPAN
+CLASS="QUOTE"
+>"votes"</SPAN
+> available to
+ users is set per-product, as is the number of votes
+ required to move a bug automatically from the UNCONFIRMED
+ status to the CONFIRMED status.
+ </P
+><P
+>&#13; When creating or editing products the following options are
+ available:
+ </P
+><P
+></P
+><DIV
+CLASS="variablelist"
+><DL
+><DT
+>Product</DT
+><DD
+><P
+>
+ The name of the product
+ </P
+></DD
+><DT
+>Description</DT
+><DD
+><P
+>
+ A brief description of the product
+ </P
+></DD
+><DT
+>Default milestone</DT
+><DD
+><P
+>
+ Select the default milestone for this product.
+ </P
+></DD
+><DT
+>Closed for bug entry</DT
+><DD
+><P
+>
+ Select this box to prevent new bugs from being
+ entered against this product.
+ </P
+></DD
+><DT
+>Maximum votes per person</DT
+><DD
+><P
+>
+ Maximum votes a user is allowed to give for this
+ product
+ </P
+></DD
+><DT
+>Maximum votes a person can put on a single bug</DT
+><DD
+><P
+>
+ Maximum votes a user is allowed to give for this
+ product in a single bug
+ </P
+></DD
+><DT
+>Confirmation threshold</DT
+><DD
+><P
+>
+ Number of votes needed to automatically remove any
+ bug against this product from the UNCONFIRMED state
+ </P
+></DD
+><DT
+>Version</DT
+><DD
+><P
+>
+ Specify which version of the product bugs will be
+ entered against.
+ </P
+></DD
+><DT
+>Create chart datasets for this product</DT
+><DD
+><P
+>
+ Select to make chart datasets available for this product.
+ </P
+></DD
+></DL
+></DIV
+><P
+>&#13; When editing a product there is also a link to edit Group Access Controls,
+ see <A
+HREF="products.html#product-group-controls"
+>Section 3.4.4</A
+>.
+ </P
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="create-product"
+>3.4.1. Creating New Products</A
+></H2
+><P
+>&#13; To create a new product:
+ </P
+><P
+></P
+><OL
+TYPE="1"
+><LI
+><P
+>
+ Select <SPAN
+CLASS="QUOTE"
+>"Administration"</SPAN
+> from the footer and then
+ choose <SPAN
+CLASS="QUOTE"
+>"Products"</SPAN
+> from the main administration page.
+ </P
+></LI
+><LI
+><P
+>&#13; Select the <SPAN
+CLASS="QUOTE"
+>"Add"</SPAN
+> link in the bottom right.
+ </P
+></LI
+><LI
+><P
+>&#13; Enter the name of the product and a description. The
+ Description field may contain HTML.
+ </P
+></LI
+><LI
+><P
+>&#13; When the product is created, Bugzilla will give a message
+ stating that a component must be created before any bugs can
+ be entered against the new product. Follow the link to create
+ a new component. See <A
+HREF="components.html"
+>Components</A
+> for more
+ information.
+ </P
+></LI
+></OL
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="edit-products"
+>3.4.2. Editing Products</A
+></H2
+><P
+>&#13; To edit an existing product, click the "Products" link from the
+ "Administration" page. If the 'useclassification' parameter is
+ turned on, a table of existing classifications is displayed,
+ including an "Unclassified" category. The table indicates how many products
+ are in each classification. Click on the classification name to see its
+ products. If the 'useclassification' parameter is not in use, the table
+ lists all products directly. The product table summarizes the information
+ about the product defined
+ when the product was created. Click on the product name to edit these
+ properties, and to access links to other product attributes such as the
+ product's components, versions, milestones, and group access controls.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="comps-vers-miles-products"
+>3.4.3. Adding or Editing Components, Versions and Target Milestones</A
+></H2
+><P
+>&#13; To edit existing, or add new, Components, Versions or Target Milestones
+ to a Product, select the "Edit Components", "Edit Versions" or "Edit
+ Milestones" links from the "Edit Product" page. A table of existing
+ Components, Versions or Milestones is displayed. Click on a item name
+ to edit the properties of that item. Below the table is a link to add
+ a new Component, Version or Milestone.
+ </P
+><P
+>&#13; For more information on components, see <A
+HREF="components.html"
+>Components</A
+>.
+ </P
+><P
+>&#13; For more information on versions, see <A
+HREF="versions.html"
+>Section 3.6</A
+>.
+ </P
+><P
+>&#13; For more information on milestones, see <A
+HREF="milestones.html"
+>Section 3.7</A
+>.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="product-group-controls"
+>3.4.4. Assigning Group Controls to Products</A
+></H2
+><P
+>
+ On the <SPAN
+CLASS="QUOTE"
+>"Edit Product"</SPAN
+> page, there is a link called
+ <SPAN
+CLASS="QUOTE"
+>"Edit Group Access Controls"</SPAN
+>. The settings on this page
+ control the relationship of the groups to the product being edited.
+ </P
+><P
+>&#13; Group Access Controls are an important aspect of using groups for
+ isolating products and restricting access to bugs filed against those
+ products. For more information on groups, including how to create, edit
+ add users to, and alter permission of, see <A
+HREF="groups.html"
+>Section 3.15</A
+>.
+ </P
+><P
+>&#13; After selecting the "Edit Group Access Controls" link from the "Edit
+ Product" page, a table containing all user-defined groups for this
+ Bugzilla installation is displayed. The system groups that are created
+ when Bugzilla is installed are not applicable to Group Access Controls.
+ Below is description of what each of these fields means.
+ </P
+><P
+>&#13; Groups may be applicable (e.g bugs in this product can be associated
+ with this group) , default (e.g. bugs in this product are in this group
+ by default), and mandatory (e.g. bugs in this product must be associated
+ with this group) for each product. Groups can also control access
+ to bugs for a given product, or be used to make bugs for a product
+ totally read-only unless the group restrictions are met. The best way to
+ understand these relationships is by example. See
+ <A
+HREF="products.html#group-control-examples"
+>Section 3.4.4.1</A
+> for examples of
+ product and group relationships.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Products and Groups are not limited to a one-to-one relationship.
+ Multiple groups can be associated with the same product, and groups
+ can be associated with more than one product.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; If any group has <EM
+>Entry</EM
+> selected, then the
+ product will restrict bug entry to only those users
+ who are members of <EM
+>all</EM
+> the groups with
+ <EM
+>Entry</EM
+> selected.
+ </P
+><P
+>&#13; If any group has <EM
+>Canedit</EM
+> selected,
+ then the product will be read-only for any users
+ who are not members of <EM
+>all</EM
+> of the groups with
+ <EM
+>Canedit</EM
+> selected. <EM
+>Only</EM
+> users who
+ are members of all the <EM
+>Canedit</EM
+> groups
+ will be able to edit bugs for this product. This is an additional
+ restriction that enables finer-grained control over products rather
+ than just all-or-nothing access levels.
+ </P
+><P
+>&#13; The following settings let you
+ choose privileges on a <EM
+>per-product basis</EM
+>.
+ This is a convenient way to give privileges to
+ some users for some products only, without having
+ to give them global privileges which would affect
+ all products.
+ </P
+><P
+>&#13; Any group having <EM
+>editcomponents</EM
+>
+ selected allows users who are in this group to edit all
+ aspects of this product, including components, milestones
+ and versions.
+ </P
+><P
+>&#13; Any group having <EM
+>canconfirm</EM
+> selected
+ allows users who are in this group to confirm bugs
+ in this product.
+ </P
+><P
+>&#13; Any group having <EM
+>editbugs</EM
+> selected allows
+ users who are in this group to edit all fields of
+ bugs in this product.
+ </P
+><P
+>&#13; The <EM
+>MemberControl</EM
+> and
+ <EM
+>OtherControl</EM
+> are used in tandem to determine which
+ bugs will be placed in this group. The only allowable combinations of
+ these two parameters are listed in a table on the "Edit Group Access Controls"
+ page. Consult this table for details on how these fields can be used.
+ Examples of different uses are described below.
+ </P
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="group-control-examples"
+>3.4.4.1. Common Applications of Group Controls</A
+></H3
+><P
+>&#13; The use of groups is best explained by providing examples that illustrate
+ configurations for common use cases. The examples follow a common syntax:
+ <EM
+>Group: Entry, MemberControl, OtherControl, CanEdit,
+ EditComponents, CanConfirm, EditBugs</EM
+>. Where "Group" is the name
+ of the group being edited for this product. The other fields all
+ correspond to the table on the "Edit Group Access Controls" page. If any
+ of these options are not listed, it means they are not checked.
+ </P
+><P
+>&#13; Basic Product/Group Restriction
+ </P
+><P
+>&#13; Suppose there is a product called "Bar". The
+ "Bar" product can only have bugs entered against it by users in the
+ group "Foo". Additionally, bugs filed against product "Bar" must stay
+ restricted to users to "Foo" at all times. Furthermore, only members
+ of group "Foo" can edit bugs filed against product "Bar", even if other
+ users could see the bug. This arrangement would achieved by the
+ following:
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#13;Product Bar:
+foo: ENTRY, MANDATORY/MANDATORY, CANEDIT
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>&#13; Perhaps such strict restrictions are not needed for product "Bar". A
+ more lenient way to configure product "Bar" and group "Foo" would be:
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#13;Product Bar:
+foo: ENTRY, SHOWN/SHOWN, EDITCOMPONENTS, CANCONFIRM, EDITBUGS
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>&#13; The above indicates that for product "Bar", members of group "Foo" can
+ enter bugs. Any one with permission to edit a bug against product "Bar"
+ can put the bug
+ in group "Foo", even if they themselves are not in "Foo". Anyone in group
+ "Foo" can edit all aspects of the components of product "Bar", can confirm
+ bugs against product "Bar", and can edit all fields of any bug against
+ product "Bar".
+ </P
+><P
+>&#13; General User Access With Security Group
+ </P
+><P
+>&#13; To permit any user to file bugs against "Product A",
+ and to permit any user to submit those bugs into a
+ group called "Security":
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>
+Product A:
+security: SHOWN/SHOWN
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>&#13; General User Access With A Security Product
+ </P
+><P
+>&#13; To permit any user to file bugs against product called "Security"
+ while keeping those bugs from becoming visible to anyone
+ outside the group "SecurityWorkers" (unless a member of the
+ "SecurityWorkers" group removes that restriction):
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>
+Product Security:
+securityworkers: DEFAULT/MANDATORY
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>&#13; Product Isolation With a Common Group
+ </P
+><P
+>&#13; To permit users of "Product A" to access the bugs for
+ "Product A", users of "Product B" to access the bugs for
+ "Product B", and support staff, who are members of the "Support
+ Group" to access both, three groups are needed:
+ </P
+><P
+></P
+><OL
+TYPE="1"
+><LI
+><P
+>Support Group: Contains members of the support staff.</P
+></LI
+><LI
+><P
+>AccessA Group: Contains users of product A and the Support group.</P
+></LI
+><LI
+><P
+>AccessB Group: Contains users of product B and the Support group.</P
+></LI
+></OL
+><P
+>&#13; Once these three groups are defined, the product group controls
+ can be set to:
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#13;Product A:
+AccessA: ENTRY, MANDATORY/MANDATORY
+Product B:
+AccessB: ENTRY, MANDATORY/MANDATORY
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>&#13; Perhaps the "Support Group" wants more control. For example,
+ the "Support Group" could be permitted to make bugs inaccessible to
+ users of both groups "AccessA" and "AccessB".
+ Then, the "Support Group" could be permitted to publish
+ bugs relevant to all users in a third product (let's call it
+ "Product Common") that is read-only
+ to anyone outside the "Support Group". In this way the "Support Group"
+ could control bugs that should be seen by both groups.
+ That configuration would be:
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#13;Product A:
+AccessA: ENTRY, MANDATORY/MANDATORY
+Support: SHOWN/NA
+Product B:
+AccessB: ENTRY, MANDATORY/MANDATORY
+Support: SHOWN/NA
+Product Common:
+Support: ENTRY, DEFAULT/MANDATORY, CANEDIT
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>&#13; Make a Product Read Only
+ </P
+><P
+>&#13; Sometimes a product is retired and should no longer have
+ new bugs filed against it (for example, an older version of a software
+ product that is no longer supported). A product can be made read-only
+ by creating a group called "readonly" and adding products to the
+ group as needed:
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#13;Product A:
+ReadOnly: ENTRY, NA/NA, CANEDIT
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; For more information on Groups outside of how they relate to products
+ see <A
+HREF="groups.html"
+>Section 3.15</A
+>.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+></DIV
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="classifications.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="components.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Classifications</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="administration.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Components</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/query.html b/docs/en/html/query.html
new file mode 100644
index 000000000..e556362ce
--- /dev/null
+++ b/docs/en/html/query.html
@@ -0,0 +1,625 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Searching for Bugs</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Using Bugzilla"
+HREF="using.html"><LINK
+REL="PREVIOUS"
+TITLE="Life Cycle of a Bug"
+HREF="lifecycle.html"><LINK
+REL="NEXT"
+TITLE="Filing Bugs"
+HREF="bugreports.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="lifecycle.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 5. Using Bugzilla</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="bugreports.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="query"
+>5.5. Searching for Bugs</A
+></H1
+><P
+>The Bugzilla Search page is the interface where you can find
+ any bug report, comment, or patch currently in the Bugzilla system. You
+ can play with it here:
+ <A
+HREF="http://landfill.bugzilla.org/bugzilla-4.0-branch/query.cgi"
+TARGET="_top"
+>http://landfill.bugzilla.org/bugzilla-4.0-branch/query.cgi</A
+>.</P
+><P
+>The Search page has controls for selecting different possible
+ values for all of the fields in a bug, as described above. For some
+ fields, multiple values can be selected. In those cases, Bugzilla
+ returns bugs where the content of the field matches any one of the selected
+ values. If none is selected, then the field can take any value.</P
+><P
+>&#13; After a search is run, you can save it as a Saved Search, which
+ will appear in the page footer. If you are in the group defined
+ by the "querysharegroup" parameter, you may share your queries
+ with other users, see <A
+HREF="userpreferences.html#savedsearches"
+>Saved Searches</A
+> for more details.
+ </P
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="boolean"
+>5.5.1. Boolean Charts</A
+></H2
+><P
+>&#13; Highly advanced querying is done using Boolean Charts.
+ </P
+><P
+>&#13; The boolean charts further restrict the set of results
+ returned by a query. It is possible to search for bugs
+ based on elaborate combinations of criteria.
+ </P
+><P
+>&#13; The simplest boolean searches have only one term. These searches
+ permit the selected left <EM
+>field</EM
+>
+ to be compared using a
+ selectable <EM
+>operator</EM
+> to a
+ specified <EM
+>value.</EM
+>
+ Using the "And," "Or," and "Add Another Boolean Chart" buttons,
+ additional terms can be included in the query, further
+ altering the list of bugs returned by the query.
+ </P
+><P
+>&#13; There are three fields in each row of a boolean search.
+ </P
+><P
+></P
+><UL
+><LI
+><P
+>&#13; <EM
+>Field:</EM
+>
+ the items being searched
+ </P
+></LI
+><LI
+><P
+>&#13; <EM
+>Operator:</EM
+>
+ the comparison operator
+ </P
+></LI
+><LI
+><P
+>&#13; <EM
+>Value:</EM
+>
+ the value to which the field is being compared
+ </P
+></LI
+></UL
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="pronouns"
+>5.5.1.1. Pronoun Substitution</A
+></H3
+><P
+>&#13; Sometimes, a query needs to compare a user-related field
+ (such as ReportedBy) with a role-specific user (such as the
+ user running the query or the user to whom each bug is assigned).
+ When the operator is either "equals" or "notequals", the value
+ can be "%reporter%", "%assignee%", "%qacontact%", or "%user%".
+ The user pronoun
+ refers to the user who is executing the query or, in the case
+ of whining reports, the user who will be the recipient
+ of the report. The reporter, assignee, and qacontact
+ pronouns refer to the corresponding fields in the bug.
+ </P
+><P
+>&#13; Boolean charts also let you type a group name in any user-related
+ field if the operator is either "equals", "notequals" or "anyexact".
+ This will let you query for any member belonging (or not) to the
+ specified group. The group name must be entered following the
+ "%group.foo%" syntax, where "foo" is the group name.
+ So if you are looking for bugs reported by any user being in the
+ "editbugs" group, then you can type "%group.editbugs%".
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="negation"
+>5.5.1.2. Negation</A
+></H3
+><P
+>&#13; At first glance, negation seems redundant. Rather than
+ searching for
+ <A
+NAME="AEN2438"
+></A
+><BLOCKQUOTE
+CLASS="BLOCKQUOTE"
+><P
+>&#13; NOT("summary" "contains the string" "foo"),
+ </P
+></BLOCKQUOTE
+>
+ one could search for
+ <A
+NAME="AEN2440"
+></A
+><BLOCKQUOTE
+CLASS="BLOCKQUOTE"
+><P
+>&#13; ("summary" "does not contain the string" "foo").
+ </P
+></BLOCKQUOTE
+>
+ However, the search
+ <A
+NAME="AEN2442"
+></A
+><BLOCKQUOTE
+CLASS="BLOCKQUOTE"
+><P
+>&#13; ("CC" "does not contain the string" "@mozilla.org")
+ </P
+></BLOCKQUOTE
+>
+ would find every bug where anyone on the CC list did not contain
+ "@mozilla.org" while
+ <A
+NAME="AEN2444"
+></A
+><BLOCKQUOTE
+CLASS="BLOCKQUOTE"
+><P
+>&#13; NOT("CC" "contains the string" "@mozilla.org")
+ </P
+></BLOCKQUOTE
+>
+ would find every bug where there was nobody on the CC list who
+ did contain the string. Similarly, the use of negation also permits
+ complex expressions to be built using terms OR'd together and then
+ negated. Negation permits queries such as
+ <A
+NAME="AEN2446"
+></A
+><BLOCKQUOTE
+CLASS="BLOCKQUOTE"
+><P
+>&#13; NOT(("product" "equals" "update") OR
+ ("component" "equals" "Documentation"))
+ </P
+></BLOCKQUOTE
+>
+ to find bugs that are neither
+ in the update product or in the documentation component or
+ <A
+NAME="AEN2448"
+></A
+><BLOCKQUOTE
+CLASS="BLOCKQUOTE"
+><P
+>&#13; NOT(("commenter" "equals" "%assignee%") OR
+ ("component" "equals" "Documentation"))
+ </P
+></BLOCKQUOTE
+>
+ to find non-documentation
+ bugs on which the assignee has never commented.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="multiplecharts"
+>5.5.1.3. Multiple Charts</A
+></H3
+><P
+>&#13; The terms within a single row of a boolean chart are all
+ constraints on a single piece of data. If you are looking for
+ a bug that has two different people cc'd on it, then you need
+ to use two boolean charts. A search for
+ <A
+NAME="AEN2453"
+></A
+><BLOCKQUOTE
+CLASS="BLOCKQUOTE"
+><P
+>&#13; ("cc" "contains the string" "foo@") AND
+ ("cc" "contains the string" "@mozilla.org")
+ </P
+></BLOCKQUOTE
+>
+ would return only bugs with "foo@mozilla.org" on the cc list.
+ If you wanted bugs where there is someone on the cc list
+ containing "foo@" and someone else containing "@mozilla.org",
+ then you would need two boolean charts.
+ <A
+NAME="AEN2455"
+></A
+><BLOCKQUOTE
+CLASS="BLOCKQUOTE"
+><P
+>&#13; First chart: ("cc" "contains the string" "foo@")
+ </P
+><P
+>&#13; Second chart: ("cc" "contains the string" "@mozilla.org")
+ </P
+></BLOCKQUOTE
+>
+ The bugs listed will be only the bugs where ALL the charts are true.
+ </P
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="quicksearch"
+>5.5.2. Quicksearch</A
+></H2
+><P
+>&#13; Quicksearch is a single-text-box query tool which uses
+ metacharacters to indicate what is to be searched. For example, typing
+ "<TT
+CLASS="literal"
+>foo|bar</TT
+>"
+ into Quicksearch would search for "foo" or "bar" in the
+ summary and status whiteboard of a bug; adding
+ "<TT
+CLASS="literal"
+>:BazProduct</TT
+>" would
+ search only in that product.
+ You can use it to find a bug by its number or its alias, too.
+ </P
+><P
+>&#13; You'll find the Quicksearch box in Bugzilla's footer area.
+ On Bugzilla's front page, there is an additional
+ <A
+HREF="../../page.cgi?id=quicksearch.html"
+TARGET="_top"
+>Help</A
+>
+ link which details how to use it.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="casesensitivity"
+>5.5.3. Case Sensitivity in Searches</A
+></H2
+><P
+>&#13; Bugzilla queries are case-insensitive and accent-insensitive, when
+ used with either MySQL or Oracle databases. When using Bugzilla with
+ PostgreSQL, however, some queries are case-sensitive. This is due to
+ the way PostgreSQL handles case and accent sensitivity.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="list"
+>5.5.4. Bug Lists</A
+></H2
+><P
+>If you run a search, a list of matching bugs will be returned.
+ </P
+><P
+>The format of the list is configurable. For example, it can be
+ sorted by clicking the column headings. Other useful features can be
+ accessed using the links at the bottom of the list:
+ <P
+></P
+><TABLE
+BORDER="0"
+><TBODY
+><TR
+><TD
+>&#13; <EM
+>Long Format:</EM
+>
+
+ this gives you a large page with a non-editable summary of the fields
+ of each bug.</TD
+></TR
+><TR
+><TD
+>&#13; <EM
+>XML:</EM
+>
+
+ get the buglist in the XML format.</TD
+></TR
+><TR
+><TD
+>&#13; <EM
+>CSV:</EM
+>
+
+ get the buglist as comma-separated values, for import into e.g.
+ a spreadsheet.</TD
+></TR
+><TR
+><TD
+>&#13; <EM
+>Feed:</EM
+>
+
+ get the buglist as an Atom feed. Copy this link into your
+ favorite feed reader. If you are using Firefox, you can also
+ save the list as a live bookmark by clicking the live bookmark
+ icon in the status bar. To limit the number of bugs in the feed,
+ add a limit=n parameter to the URL.</TD
+></TR
+><TR
+><TD
+>&#13; <EM
+>iCalendar:</EM
+>
+
+ Get the buglist as an iCalendar file. Each bug is represented as a
+ to-do item in the imported calendar.</TD
+></TR
+><TR
+><TD
+>&#13; <EM
+>Change Columns:</EM
+>
+
+ change the bug attributes which appear in the list.</TD
+></TR
+><TR
+><TD
+>&#13; <EM
+>Change several bugs at once:</EM
+>
+
+ If your account is sufficiently empowered, and more than one bug
+ appear in the bug list, this link is displayed which lets you make
+ the same change to all the bugs in the list - for example, changing
+ their assignee.</TD
+></TR
+><TR
+><TD
+>&#13; <EM
+>Send mail to bug assignees:</EM
+>
+
+ If more than one bug appear in the bug list and there are at least
+ two distinct bug assignees, this links is displayed which lets you
+ easily send a mail to the assignees of all bugs on the list.</TD
+></TR
+><TR
+><TD
+>&#13; <EM
+>Edit Search:</EM
+>
+
+ If you didn't get exactly the results you were looking for, you can
+ return to the Query page through this link and make small revisions
+ to the query you just made so you get more accurate results.</TD
+></TR
+><TR
+><TD
+>&#13; <EM
+>Remember Search As:</EM
+>
+
+ You can give a search a name and remember it; a link will appear
+ in your page footer giving you quick access to run it again later.
+ </TD
+></TR
+></TBODY
+></TABLE
+><P
+></P
+>
+ </P
+><P
+>&#13; If you would like to access the bug list from another program
+ it is often useful to have the list returned in something other
+ than HTML. By adding the ctype=type parameter into the bug list URL
+ you can specify several alternate formats. Besides the types described
+ above, the following formats are also supported: ECMAScript, also known
+ as JavaScript (ctype=js), and Resource Description Framework RDF/XML
+ (ctype=rdf).
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="individual-buglists"
+>5.5.5. Adding/removing tags to/from bugs</A
+></H2
+><P
+>&#13; You can add and remove tags from individual bugs, which let you find and
+ manage them more easily. Creating a new tag automatically generates a saved
+ search - whose name is the name of the tag - which lists bugs with this tag.
+ This saved search will be displayed in the footer of pages by default, as
+ all other saved searches. The main difference between tags and normal saved
+ searches is that saved searches, as described in the previous section, are
+ stored in the form of a list of matching criteria, while the saved search
+ generated by tags is a list of bug numbers. Consequently, you can easily
+ edit this list by either adding or removing tags from bugs. To enable this
+ feature, you have to turn on the <SPAN
+CLASS="QUOTE"
+>"Enable tags for bugs"</SPAN
+> user
+ preference, see <A
+HREF="userpreferences.html"
+>Section 5.10</A
+>. This feature is disabled
+ by default.
+ </P
+><P
+>&#13; This feature is useful when you want to keep track of several bugs, but
+ for different reasons. Instead of adding yourself to the CC list of all
+ these bugs and mixing all these reasons, you can now store these bugs in
+ separate lists, e.g. <SPAN
+CLASS="QUOTE"
+>"Keep in mind"</SPAN
+>, <SPAN
+CLASS="QUOTE"
+>"Interesting bugs"</SPAN
+>,
+ or <SPAN
+CLASS="QUOTE"
+>"Triage"</SPAN
+>. One big advantage of this way to manage bugs
+ is that you can easily add or remove bugs one by one, which is not
+ possible to do with saved searches without having to edit the search
+ criteria again.
+ </P
+></DIV
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="lifecycle.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="bugreports.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Life Cycle of a Bug</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="using.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Filing Bugs</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/quips.html b/docs/en/html/quips.html
new file mode 100644
index 000000000..84ad571f6
--- /dev/null
+++ b/docs/en/html/quips.html
@@ -0,0 +1,184 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Quips</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Administering Bugzilla"
+HREF="administration.html"><LINK
+REL="PREVIOUS"
+TITLE="Voting"
+HREF="voting.html"><LINK
+REL="NEXT"
+TITLE="Groups and Group Security"
+HREF="groups.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="voting.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 3. Administering Bugzilla</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="groups.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="quips"
+>3.14. Quips</A
+></H1
+><P
+>&#13; Quips are small text messages that can be configured to appear
+ next to search results. A Bugzilla installation can have its own specific
+ quips. Whenever a quip needs to be displayed, a random selection
+ is made from the pool of already existing quips.
+ </P
+><P
+>&#13; Quips are controlled by the <EM
+>enablequips</EM
+> parameter.
+ It has several possible values: on, approved, frozen or off.
+ In order to enable quips approval you need to set this parameter
+ to "approved". In this way, users are free to submit quips for
+ addition but an administrator must explicitly approve them before
+ they are actually used.
+ </P
+><P
+>&#13; In order to see the user interface for the quips, it is enough to click
+ on a quip when it is displayed together with the search results. Or
+ it can be seen directly in the browser by visiting the quips.cgi URL
+ (prefixed with the usual web location of the Bugzilla installation).
+ Once the quip interface is displayed, it is enough to click the
+ "view and edit the whole quip list" in order to see the administration
+ page. A page with all the quips available in the database will
+ be displayed.
+ </P
+><P
+>&#13; Next to each tip there is a checkbox, under the
+ "Approved" column. Quips who have this checkbox checked are
+ already approved and will appear next to the search results.
+ The ones that have it unchecked are still preserved in the
+ database but they will not appear on search results pages.
+ User submitted quips have initially the checkbox unchecked.
+ </P
+><P
+>&#13; Also, there is a delete link next to each quip,
+ which can be used in order to permanently delete a quip.
+ </P
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="voting.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="groups.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Voting</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="administration.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Groups and Group Security</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/reporting.html b/docs/en/html/reporting.html
new file mode 100644
index 000000000..b000b0a3a
--- /dev/null
+++ b/docs/en/html/reporting.html
@@ -0,0 +1,319 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Reports and Charts</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Using Bugzilla"
+HREF="using.html"><LINK
+REL="PREVIOUS"
+TITLE="User Preferences"
+HREF="userpreferences.html"><LINK
+REL="NEXT"
+TITLE="Flags"
+HREF="flags.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="userpreferences.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 5. Using Bugzilla</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="flags.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="reporting"
+>5.11. Reports and Charts</A
+></H1
+><P
+>As well as the standard buglist, Bugzilla has two more ways of
+ viewing sets of bugs. These are the reports (which give different
+ views of the current state of the database) and charts (which plot
+ the changes in particular sets of bugs over time.)</P
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="reports"
+>5.11.1. Reports</A
+></H2
+><P
+>&#13; A report is a view of the current state of the bug database.
+ </P
+><P
+>&#13; You can run either an HTML-table-based report, or a graphical
+ line/pie/bar-chart-based one. The two have different pages to
+ define them, but are close cousins - once you've defined and
+ viewed a report, you can switch between any of the different
+ views of the data at will.
+ </P
+><P
+>&#13; Both report types are based on the idea of defining a set of bugs
+ using the standard search interface, and then choosing some
+ aspect of that set to plot on the horizontal and/or vertical axes.
+ You can also get a form of 3-dimensional report by choosing to have
+ multiple images or tables.
+ </P
+><P
+>&#13; So, for example, you could use the search form to choose "all
+ bugs in the WorldControl product", and then plot their severity
+ against their component to see which component had had the largest
+ number of bad bugs reported against it.
+ </P
+><P
+>&#13; Once you've defined your parameters and hit "Generate Report",
+ you can switch between HTML, CSV, Bar, Line and Pie. (Note: Pie
+ is only available if you didn't define a vertical axis, as pie
+ charts don't have one.) The other controls are fairly self-explanatory;
+ you can change the size of the image if you find text is overwriting
+ other text, or the bars are too thin to see.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="charts"
+>5.11.2. Charts</A
+></H2
+><P
+>&#13; A chart is a view of the state of the bug database over time.
+ </P
+><P
+>&#13; Bugzilla currently has two charting systems - Old Charts and New
+ Charts. Old Charts have been part of Bugzilla for a long time; they
+ chart each status and resolution for each product, and that's all.
+ They are deprecated, and going away soon - we won't say any more
+ about them.
+ New Charts are the future - they allow you to chart anything you
+ can define as a search.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Both charting forms require the administrator to set up the
+ data-gathering script. If you can't see any charts, ask them whether
+ they have done so.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; An individual line on a chart is called a data set.
+ All data sets are organised into categories and subcategories. The
+ data sets that Bugzilla defines automatically use the Product name
+ as a Category and Component names as Subcategories, but there is no
+ need for you to follow that naming scheme with your own charts if
+ you don't want to.
+ </P
+><P
+>&#13; Data sets may be public or private. Everyone sees public data sets in
+ the list, but only their creator sees private data sets. Only
+ administrators can make data sets public.
+ No two data sets, even two private ones, can have the same set of
+ category, subcategory and name. So if you are creating private data
+ sets, one idea is to have the Category be your username.
+ </P
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="AEN2788"
+>5.11.2.1. Creating Charts</A
+></H3
+><P
+>&#13; You create a chart by selecting a number of data sets from the
+ list, and pressing Add To List for each. In the List Of Data Sets
+ To Plot, you can define the label that data set will have in the
+ chart's legend, and also ask Bugzilla to Sum a number of data sets
+ (e.g. you could Sum data sets representing RESOLVED, VERIFIED and
+ CLOSED in a particular product to get a data set representing all
+ the resolved bugs in that product.)
+ </P
+><P
+>&#13; If you've erroneously added a data set to the list, select it
+ using the checkbox and click Remove. Once you add more than one
+ data set, a "Grand Total" line
+ automatically appears at the bottom of the list. If you don't want
+ this, simply remove it as you would remove any other line.
+ </P
+><P
+>&#13; You may also choose to plot only over a certain date range, and
+ to cumulate the results - that is, to plot each one using the
+ previous one as a baseline, so the top line gives a sum of all
+ the data sets. It's easier to try than to explain :-)
+ </P
+><P
+>&#13; Once a data set is in the list, one can also perform certain
+ actions on it. For example, one can edit the
+ data set's parameters (name, frequency etc.) if it's one you
+ created or if you are an administrator.
+ </P
+><P
+>&#13; Once you are happy, click Chart This List to see the chart.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="charts-new-series"
+>5.11.2.2. Creating New Data Sets</A
+></H3
+><P
+>&#13; You may also create new data sets of your own. To do this,
+ click the "create a new data set" link on the Create Chart page.
+ This takes you to a search-like interface where you can define
+ the search that Bugzilla will plot. At the bottom of the page,
+ you choose the category, sub-category and name of your new
+ data set.
+ </P
+><P
+>&#13; If you have sufficient permissions, you can make the data set public,
+ and reduce the frequency of data collection to less than the default
+ seven days.
+ </P
+></DIV
+></DIV
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="userpreferences.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="flags.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>User Preferences</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="using.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Flags</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/sanitycheck.html b/docs/en/html/sanitycheck.html
new file mode 100644
index 000000000..9fa65b824
--- /dev/null
+++ b/docs/en/html/sanitycheck.html
@@ -0,0 +1,206 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Checking and Maintaining Database Integrity</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Administering Bugzilla"
+HREF="administration.html"><LINK
+REL="PREVIOUS"
+TITLE="Groups and Group Security"
+HREF="groups.html"><LINK
+REL="NEXT"
+TITLE="Bugzilla Security"
+HREF="security.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="groups.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 3. Administering Bugzilla</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="security.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="sanitycheck"
+>3.16. Checking and Maintaining Database Integrity</A
+></H1
+><P
+>&#13; Over time it is possible for the Bugzilla database to become corrupt
+ or to have anomalies.
+ This could happen through normal usage of Bugzilla, manual database
+ administration outside of the Bugzilla user interface, or from some
+ other unexpected event. Bugzilla includes a "Sanity Check" script that
+ can perform several basic database checks, and repair certain problems or
+ inconsistencies.
+ </P
+><P
+>&#13; To run the "Sanity Check" script, log in as an Administrator and click the
+ "Sanity Check" link in the admin page. Any problems that are found will be
+ displayed in red letters. If the script is capable of fixing a problem,
+ it will present a link to initiate the fix. If the script can not
+ fix the problem it will require manual database administration or recovery.
+ </P
+><P
+>&#13; The "Sanity Check" script can also be run from the command line via the perl
+ script <TT
+CLASS="filename"
+>sanitycheck.pl</TT
+>. The script can also be run as
+ a <B
+CLASS="command"
+>cron</B
+> job. Results will be delivered by email.
+ </P
+><P
+>&#13; The "Sanity Check" script should be run on a regular basis as a matter of
+ best practice.
+ </P
+><DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; The "Sanity Check" script is no substitute for a competent database
+ administrator. It is only designed to check and repair basic database
+ problems.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="groups.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="security.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Groups and Group Security</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="administration.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Bugzilla Security</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/security-bugzilla.html b/docs/en/html/security-bugzilla.html
new file mode 100644
index 000000000..90e134dae
--- /dev/null
+++ b/docs/en/html/security-bugzilla.html
@@ -0,0 +1,184 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Bugzilla</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Bugzilla Security"
+HREF="security.html"><LINK
+REL="PREVIOUS"
+TITLE="Web server"
+HREF="security-webserver.html"><LINK
+REL="NEXT"
+TITLE="Using Bugzilla"
+HREF="using.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="security-webserver.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 4. Bugzilla Security</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="using.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="security-bugzilla"
+>4.3. Bugzilla</A
+></H1
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="security-bugzilla-charset"
+>4.3.1. Prevent users injecting malicious Javascript</A
+></H2
+><P
+>If you installed Bugzilla version 2.22 or later from scratch,
+ then the <EM
+>utf8</EM
+> parameter is switched on by default.
+ This makes Bugzilla explicitly set the character encoding, following
+ <A
+HREF="http://www.cert.org/tech_tips/malicious_code_mitigation.html#3"
+TARGET="_top"
+>a
+ CERT advisory</A
+> recommending exactly this.
+ The following therefore does not apply to you; just keep
+ <EM
+>utf8</EM
+> turned on.
+ </P
+><P
+>If you've upgraded from an older version, then it may be possible
+ for a Bugzilla user to take advantage of character set encoding
+ ambiguities to inject HTML into Bugzilla comments.
+ This could include malicious scripts.
+ This is because due to internationalization concerns, we are unable to
+ turn the <EM
+>utf8</EM
+> parameter on by default for upgraded
+ installations.
+ Turning it on manually will prevent this problem.
+ </P
+></DIV
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="security-webserver.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="using.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Web server</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="security.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Using Bugzilla</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/security-os.html b/docs/en/html/security-os.html
new file mode 100644
index 000000000..75948a7a8
--- /dev/null
+++ b/docs/en/html/security-os.html
@@ -0,0 +1,292 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Operating System</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Bugzilla Security"
+HREF="security.html"><LINK
+REL="PREVIOUS"
+TITLE="Bugzilla Security"
+HREF="security.html"><LINK
+REL="NEXT"
+TITLE="Web server"
+HREF="security-webserver.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="security.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 4. Bugzilla Security</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="security-webserver.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="security-os"
+>4.1. Operating System</A
+></H1
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="security-os-ports"
+>4.1.1. TCP/IP Ports</A
+></H2
+><P
+>The TCP/IP standard defines more than 65,000 ports for sending
+ and receiving traffic. Of those, Bugzilla needs exactly one to operate
+ (different configurations and options may require up to 3). You should
+ audit your server and make sure that you aren't listening on any ports
+ you don't need to be. It's also highly recommended that the server
+ Bugzilla resides on, along with any other machines you administer, be
+ placed behind some kind of firewall.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="security-os-accounts"
+>4.1.2. System User Accounts</A
+></H2
+><P
+>Many <A
+HREF="glossary.html#gloss-daemon"
+><I
+CLASS="glossterm"
+>daemons</I
+></A
+>, such
+ as Apache's <TT
+CLASS="filename"
+>httpd</TT
+> or MySQL's
+ <TT
+CLASS="filename"
+>mysqld</TT
+>, run as either <SPAN
+CLASS="QUOTE"
+>"root"</SPAN
+> or
+ <SPAN
+CLASS="QUOTE"
+>"nobody"</SPAN
+>. This is even worse on Windows machines where the
+ majority of <A
+HREF="glossary.html#gloss-service"
+><I
+CLASS="glossterm"
+>services</I
+></A
+>
+ run as <SPAN
+CLASS="QUOTE"
+>"SYSTEM"</SPAN
+>. While running as <SPAN
+CLASS="QUOTE"
+>"root"</SPAN
+> or
+ <SPAN
+CLASS="QUOTE"
+>"SYSTEM"</SPAN
+> introduces obvious security concerns, the
+ problems introduced by running everything as <SPAN
+CLASS="QUOTE"
+>"nobody"</SPAN
+> may
+ not be so obvious. Basically, if you run every daemon as
+ <SPAN
+CLASS="QUOTE"
+>"nobody"</SPAN
+> and one of them gets compromised it can
+ compromise every other daemon running as <SPAN
+CLASS="QUOTE"
+>"nobody"</SPAN
+> on your
+ machine. For this reason, it is recommended that you create a user
+ account for each daemon.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>You will need to set the <CODE
+CLASS="option"
+>webservergroup</CODE
+> option
+ in <TT
+CLASS="filename"
+>localconfig</TT
+> to the group your web server runs
+ as. This will allow <TT
+CLASS="filename"
+>./checksetup.pl</TT
+> to set file
+ permissions on Unix systems so that nothing is world-writable.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="security-os-chroot"
+>4.1.3. The <TT
+CLASS="filename"
+>chroot</TT
+> Jail</A
+></H2
+><P
+>&#13; If your system supports it, you may wish to consider running
+ Bugzilla inside of a <TT
+CLASS="filename"
+>chroot</TT
+> jail. This option
+ provides unprecedented security by restricting anything running
+ inside the jail from accessing any information outside of it. If you
+ wish to use this option, please consult the documentation that came
+ with your system.
+ </P
+></DIV
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="security.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="security-webserver.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Bugzilla Security</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="security.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Web server</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/security-webserver.html b/docs/en/html/security-webserver.html
new file mode 100644
index 000000000..e5e61e48a
--- /dev/null
+++ b/docs/en/html/security-webserver.html
@@ -0,0 +1,414 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Web server</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Bugzilla Security"
+HREF="security.html"><LINK
+REL="PREVIOUS"
+TITLE="Operating System"
+HREF="security-os.html"><LINK
+REL="NEXT"
+TITLE="Bugzilla"
+HREF="security-bugzilla.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="security-os.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 4. Bugzilla Security</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="security-bugzilla.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="security-webserver"
+>4.2. Web server</A
+></H1
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="security-webserver-access"
+>4.2.1. Disabling Remote Access to Bugzilla Configuration Files</A
+></H2
+><P
+>&#13; There are many files that are placed in the Bugzilla directory
+ area that should not be accessible from the web server. Because of the way
+ Bugzilla is currently layed out, the list of what should and should not
+ be accessible is rather complicated. A quick way is to run
+ <TT
+CLASS="filename"
+>testserver.pl</TT
+> to check if your web server serves
+ Bugzilla files as expected. If not, you may want to follow the few
+ steps below.
+ </P
+><DIV
+CLASS="tip"
+><P
+></P
+><TABLE
+CLASS="tip"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/tip.gif"
+HSPACE="5"
+ALT="Tip"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>Bugzilla ships with the ability to create
+ <A
+HREF="glossary.html#gloss-htaccess"
+><I
+CLASS="glossterm"
+><TT
+CLASS="filename"
+>.htaccess</TT
+></I
+></A
+>
+ files that enforce these rules. Instructions for enabling these
+ directives in Apache can be found in <A
+HREF="configuration.html#http-apache"
+>Section 2.2.4.1</A
+>
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+></P
+><UL
+COMPACT="COMPACT"
+><LI
+><P
+>In the main Bugzilla directory, you should:</P
+><P
+></P
+><UL
+COMPACT="COMPACT"
+><LI
+><P
+>Block:
+ <TT
+CLASS="filename"
+>*.pl</TT
+>, <TT
+CLASS="filename"
+>*localconfig*</TT
+>
+ </P
+></LI
+></UL
+></LI
+><LI
+><P
+>In <TT
+CLASS="filename"
+>data</TT
+>:</P
+><P
+></P
+><UL
+COMPACT="COMPACT"
+><LI
+><P
+>Block everything</P
+></LI
+></UL
+></LI
+><LI
+><P
+>In <TT
+CLASS="filename"
+>data/webdot</TT
+>:</P
+><P
+></P
+><UL
+COMPACT="COMPACT"
+><LI
+><P
+>If you use a remote webdot server:</P
+><P
+></P
+><UL
+COMPACT="COMPACT"
+><LI
+><P
+>Block everything</P
+></LI
+><LI
+><P
+>But allow
+ <TT
+CLASS="filename"
+>*.dot</TT
+>
+ only for the remote webdot server</P
+></LI
+></UL
+></LI
+><LI
+><P
+>Otherwise, if you use a local GraphViz:</P
+><P
+></P
+><UL
+COMPACT="COMPACT"
+><LI
+><P
+>Block everything</P
+></LI
+><LI
+><P
+>But allow:
+ <TT
+CLASS="filename"
+>*.png</TT
+>, <TT
+CLASS="filename"
+>*.gif</TT
+>, <TT
+CLASS="filename"
+>*.jpg</TT
+>, <TT
+CLASS="filename"
+>*.map</TT
+>
+ </P
+></LI
+></UL
+></LI
+><LI
+><P
+>And if you don't use any dot:</P
+><P
+></P
+><UL
+COMPACT="COMPACT"
+><LI
+><P
+>Block everything</P
+></LI
+></UL
+></LI
+></UL
+></LI
+><LI
+><P
+>In <TT
+CLASS="filename"
+>Bugzilla</TT
+>:</P
+><P
+></P
+><UL
+COMPACT="COMPACT"
+><LI
+><P
+>Block everything</P
+></LI
+></UL
+></LI
+><LI
+><P
+>In <TT
+CLASS="filename"
+>template</TT
+>:</P
+><P
+></P
+><UL
+COMPACT="COMPACT"
+><LI
+><P
+>Block everything</P
+></LI
+></UL
+></LI
+></UL
+><P
+>Be sure to test that data that should not be accessed remotely is
+ properly blocked. Of particular interest is the localconfig file which
+ contains your database password. Also, be aware that many editors
+ create temporary and backup files in the working directory and that
+ those should also not be accessible. For more information, see
+ <A
+HREF="http://bugzilla.mozilla.org/show_bug.cgi?id=186383"
+TARGET="_top"
+>bug 186383</A
+>
+ or
+ <A
+HREF="http://online.securityfocus.com/bid/6501"
+TARGET="_top"
+>Bugtraq ID 6501</A
+>.
+ To test, simply run <TT
+CLASS="filename"
+>testserver.pl</TT
+>, as said above.
+ </P
+><DIV
+CLASS="tip"
+><P
+></P
+><TABLE
+CLASS="tip"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/tip.gif"
+HSPACE="5"
+ALT="Tip"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>Be sure to check <A
+HREF="configuration.html#http"
+>Section 2.2.4</A
+> for instructions
+ specific to the web server you use.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="security-os.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="security-bugzilla.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Operating System</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="security.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Bugzilla</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/security.html b/docs/en/html/security.html
new file mode 100644
index 000000000..9600e003a
--- /dev/null
+++ b/docs/en/html/security.html
@@ -0,0 +1,201 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Bugzilla Security</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="PREVIOUS"
+TITLE="Checking and Maintaining Database Integrity"
+HREF="sanitycheck.html"><LINK
+REL="NEXT"
+TITLE="Operating System"
+HREF="security-os.html"></HEAD
+><BODY
+CLASS="chapter"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="sanitycheck.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+></TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="security-os.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="chapter"
+><H1
+><A
+NAME="security"
+></A
+>Chapter 4. Bugzilla Security</H1
+><DIV
+CLASS="TOC"
+><DL
+><DT
+><B
+>Table of Contents</B
+></DT
+><DT
+>4.1. <A
+HREF="security-os.html"
+>Operating System</A
+></DT
+><DD
+><DL
+><DT
+>4.1.1. <A
+HREF="security-os.html#security-os-ports"
+>TCP/IP Ports</A
+></DT
+><DT
+>4.1.2. <A
+HREF="security-os.html#security-os-accounts"
+>System User Accounts</A
+></DT
+><DT
+>4.1.3. <A
+HREF="security-os.html#security-os-chroot"
+>The <TT
+CLASS="filename"
+>chroot</TT
+> Jail</A
+></DT
+></DL
+></DD
+><DT
+>4.2. <A
+HREF="security-webserver.html"
+>Web server</A
+></DT
+><DT
+>4.3. <A
+HREF="security-bugzilla.html"
+>Bugzilla</A
+></DT
+></DL
+></DIV
+><P
+>While some of the items in this chapter are related to the operating
+ system Bugzilla is running on or some of the support software required to
+ run Bugzilla, it is all related to protecting your data. This is not
+ intended to be a comprehensive guide to securing Linux, Apache, MySQL, or
+ any other piece of software mentioned. There is no substitute for active
+ administration and monitoring of a machine. The key to good security is
+ actually right in the middle of the word: <EM
+>U R It</EM
+>.
+ </P
+><P
+>While programmers in general always strive to write secure code,
+ accidents can and do happen. The best approach to security is to always
+ assume that the program you are working with isn't 100% secure and restrict
+ its access to other parts of your machine as much as possible.
+ </P
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="sanitycheck.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="security-os.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Checking and Maintaining Database Integrity</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+>&nbsp;</TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Operating System</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/timetracking.html b/docs/en/html/timetracking.html
new file mode 100644
index 000000000..af398e789
--- /dev/null
+++ b/docs/en/html/timetracking.html
@@ -0,0 +1,179 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Time Tracking Information</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Using Bugzilla"
+HREF="using.html"><LINK
+REL="PREVIOUS"
+TITLE="Hints and Tips"
+HREF="hintsandtips.html"><LINK
+REL="NEXT"
+TITLE="User Preferences"
+HREF="userpreferences.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="hintsandtips.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 5. Using Bugzilla</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="userpreferences.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="timetracking"
+>5.9. Time Tracking Information</A
+></H1
+><P
+>&#13; Users who belong to the group specified by the <SPAN
+CLASS="QUOTE"
+>"timetrackinggroup"</SPAN
+>
+ parameter have access to time-related fields. Developers can see
+ deadlines and estimated times to fix bugs, and can provide time spent
+ on these bugs.
+ </P
+><P
+>&#13; At any time, a summary of the time spent by developers on bugs is
+ accessible either from bug lists when clicking the <SPAN
+CLASS="QUOTE"
+>"Time Summary"</SPAN
+>
+ button or from individual bugs when clicking the <SPAN
+CLASS="QUOTE"
+>"Summarize time"</SPAN
+>
+ link in the time tracking table. The <TT
+CLASS="filename"
+>summarize_time.cgi</TT
+>
+ page lets you view this information either per developer or per bug,
+ and can be split on a month basis to have greater details on how time
+ is spent by developers.
+ </P
+><P
+>&#13; As soon as a bug is marked as RESOLVED, the remaining time expected
+ to fix the bug is set to zero. This lets QA people set it again for
+ their own usage, and it will be set to zero again when the bug will
+ be marked as CLOSED.
+ </P
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="hintsandtips.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="userpreferences.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Hints and Tips</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="using.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>User Preferences</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/trbl-dbdsponge.html b/docs/en/html/trbl-dbdsponge.html
new file mode 100644
index 000000000..675046729
--- /dev/null
+++ b/docs/en/html/trbl-dbdsponge.html
@@ -0,0 +1,226 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>DBD::Sponge::db prepare failed</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Troubleshooting"
+HREF="troubleshooting.html"><LINK
+REL="PREVIOUS"
+TITLE="I installed a Perl module, but
+ checksetup.pl claims it's not installed!"
+HREF="trbl-perlmodule.html"><LINK
+REL="NEXT"
+TITLE="cannot chdir(/var/spool/mqueue)"
+HREF="paranoid-security.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="trbl-perlmodule.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Appendix A. Troubleshooting</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="paranoid-security.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="trbl-dbdSponge"
+>A.4. DBD::Sponge::db prepare failed</A
+></H1
+><P
+>The following error message may appear due to a bug in DBD::mysql
+ (over which the Bugzilla team have no control):
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+> DBD::Sponge::db prepare failed: Cannot determine NUM_OF_FIELDS at D:/Perl/site/lib/DBD/mysql.pm line 248.
+ SV = NULL(0x0) at 0x20fc444
+ REFCNT = 1
+ FLAGS = (PADBUSY,PADMY)
+</PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>To fix this, go to
+ <TT
+CLASS="filename"
+>&#60;path-to-perl&#62;/lib/DBD/sponge.pm</TT
+>
+ in your Perl installation and replace
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+> my $numFields;
+ if ($attribs-&#62;{'NUM_OF_FIELDS'}) {
+ $numFields = $attribs-&#62;{'NUM_OF_FIELDS'};
+ } elsif ($attribs-&#62;{'NAME'}) {
+ $numFields = @{$attribs-&#62;{NAME}};
+</PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>with</P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+> my $numFields;
+ if ($attribs-&#62;{'NUM_OF_FIELDS'}) {
+ $numFields = $attribs-&#62;{'NUM_OF_FIELDS'};
+ } elsif ($attribs-&#62;{'NAMES'}) {
+ $numFields = @{$attribs-&#62;{NAMES}};
+</PRE
+></FONT
+></TD
+></TR
+></TABLE
+><P
+>(note the S added to NAME.)</P
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="trbl-perlmodule.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="paranoid-security.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>I installed a Perl module, but
+ <TT
+CLASS="filename"
+>checksetup.pl</TT
+> claims it's not installed!</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="troubleshooting.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>cannot chdir(/var/spool/mqueue)</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/trbl-index.html b/docs/en/html/trbl-index.html
new file mode 100644
index 000000000..3c942faa1
--- /dev/null
+++ b/docs/en/html/trbl-index.html
@@ -0,0 +1,172 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>index.cgi doesn't show up unless specified in the URL</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Troubleshooting"
+HREF="troubleshooting.html"><LINK
+REL="PREVIOUS"
+TITLE="Everybody is constantly being forced to relogin"
+HREF="trbl-relogin-everyone.html"><LINK
+REL="NEXT"
+TITLE='
+ checksetup.pl reports "Client does not support authentication protocol
+ requested by server..."
+ '
+HREF="trbl-passwd-encryption.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="trbl-relogin-everyone.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Appendix A. Troubleshooting</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="trbl-passwd-encryption.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="trbl-index"
+>A.7. <TT
+CLASS="filename"
+>index.cgi</TT
+> doesn't show up unless specified in the URL</A
+></H1
+><P
+>&#13; You probably need to set up your web server in such a way that it
+ will serve the index.cgi page as an index page.
+ </P
+><P
+>&#13; If you are using Apache, you can do this by adding
+ <TT
+CLASS="filename"
+>index.cgi</TT
+> to the end of the
+ <SAMP
+CLASS="computeroutput"
+>DirectoryIndex</SAMP
+> line
+ as mentioned in <A
+HREF="configuration.html#http-apache"
+>Section 2.2.4.1</A
+>.
+ </P
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="trbl-relogin-everyone.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="trbl-passwd-encryption.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Everybody is constantly being forced to relogin</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="troubleshooting.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>checksetup.pl reports "Client does not support authentication protocol
+ requested by server..."</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/trbl-passwd-encryption.html b/docs/en/html/trbl-passwd-encryption.html
new file mode 100644
index 000000000..69f882be8
--- /dev/null
+++ b/docs/en/html/trbl-passwd-encryption.html
@@ -0,0 +1,177 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>&#13; checksetup.pl reports "Client does not support authentication protocol
+ requested by server..."
+ </TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Troubleshooting"
+HREF="troubleshooting.html"><LINK
+REL="PREVIOUS"
+TITLE="index.cgi doesn't show up unless specified in the URL"
+HREF="trbl-index.html"><LINK
+REL="NEXT"
+TITLE="Contrib"
+HREF="patches.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="trbl-index.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Appendix A. Troubleshooting</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="patches.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="trbl-passwd-encryption"
+>A.8. checksetup.pl reports "Client does not support authentication protocol
+ requested by server..."</A
+></H1
+><P
+>&#13; This error is occurring because you are using the new password
+ encryption that comes with MySQL 4.1, while your
+ <TT
+CLASS="filename"
+>DBD::mysql</TT
+> module was compiled against an
+ older version of MySQL. If you recompile <TT
+CLASS="filename"
+>DBD::mysql</TT
+>
+ against the current MySQL libraries (or just obtain a newer version
+ of this module) then the error may go away.
+ </P
+><P
+>&#13; If that does not fix the problem, or if you cannot recompile the
+ existing module (e.g. you're running Windows) and/or don't want to
+ replace it (e.g. you want to keep using a packaged version), then a
+ workaround is available from the MySQL docs:
+ <A
+HREF="http://dev.mysql.com/doc/mysql/en/Old_client.html"
+TARGET="_top"
+>http://dev.mysql.com/doc/mysql/en/Old_client.html</A
+>
+ </P
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="trbl-index.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="patches.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><TT
+CLASS="filename"
+>index.cgi</TT
+> doesn't show up unless specified in the URL</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="troubleshooting.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Contrib</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/trbl-perlmodule.html b/docs/en/html/trbl-perlmodule.html
new file mode 100644
index 000000000..2a924df85
--- /dev/null
+++ b/docs/en/html/trbl-perlmodule.html
@@ -0,0 +1,177 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>I installed a Perl module, but
+ checksetup.pl claims it's not installed!</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Troubleshooting"
+HREF="troubleshooting.html"><LINK
+REL="PREVIOUS"
+TITLE="The Apache web server is not serving Bugzilla pages"
+HREF="trbl-testserver.html"><LINK
+REL="NEXT"
+TITLE="DBD::Sponge::db prepare failed"
+HREF="trbl-dbdsponge.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="trbl-testserver.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Appendix A. Troubleshooting</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="trbl-dbdsponge.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="trbl-perlmodule"
+>A.3. I installed a Perl module, but
+ <TT
+CLASS="filename"
+>checksetup.pl</TT
+> claims it's not installed!</A
+></H1
+><P
+>This could be caused by one of two things:</P
+><P
+></P
+><OL
+TYPE="1"
+><LI
+><P
+>You have two versions of Perl on your machine. You are installing
+ modules into one, and Bugzilla is using the other. Rerun the CPAN
+ commands (or manual compile) using the full path to Perl from the
+ top of <TT
+CLASS="filename"
+>checksetup.pl</TT
+>. This will make sure you
+ are installing the modules in the right place.
+ </P
+></LI
+><LI
+><P
+>The permissions on your library directories are set incorrectly.
+ They must, at the very least, be readable by the web server user or
+ group. It is recommended that they be world readable.
+ </P
+></LI
+></OL
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="trbl-testserver.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="trbl-dbdsponge.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>The Apache web server is not serving Bugzilla pages</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="troubleshooting.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>DBD::Sponge::db prepare failed</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/trbl-relogin-everyone.html b/docs/en/html/trbl-relogin-everyone.html
new file mode 100644
index 000000000..8409b23dd
--- /dev/null
+++ b/docs/en/html/trbl-relogin-everyone.html
@@ -0,0 +1,268 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Everybody is constantly being forced to relogin</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Troubleshooting"
+HREF="troubleshooting.html"><LINK
+REL="PREVIOUS"
+TITLE="cannot chdir(/var/spool/mqueue)"
+HREF="paranoid-security.html"><LINK
+REL="NEXT"
+TITLE="index.cgi doesn't show up unless specified in the URL"
+HREF="trbl-index.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="paranoid-security.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Appendix A. Troubleshooting</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="trbl-index.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="trbl-relogin-everyone"
+>A.6. Everybody is constantly being forced to relogin</A
+></H1
+><P
+>The most-likely cause is that the <SPAN
+CLASS="QUOTE"
+>"cookiepath"</SPAN
+> parameter
+ is not set correctly in the Bugzilla configuration. You can change this (if
+ you're a Bugzilla administrator) from the editparams.cgi page via the web interface.
+ </P
+><P
+>The value of the cookiepath parameter should be the actual directory
+ containing your Bugzilla installation, <EM
+>as seen by the end-user's
+ web browser</EM
+>. Leading and trailing slashes are mandatory. You can
+ also set the cookiepath to any directory which is a parent of the Bugzilla
+ directory (such as '/', the root directory). But you can't put something
+ that isn't at least a partial match or it won't work. What you're actually
+ doing is restricting the end-user's browser to sending the cookies back only
+ to that directory.
+ </P
+><P
+>How do you know if you want your specific Bugzilla directory or the
+ whole site?
+ </P
+><P
+>If you have only one Bugzilla running on the server, and you don't
+ mind having other applications on the same server with it being able to see
+ the cookies (you might be doing this on purpose if you have other things on
+ your site that share authentication with Bugzilla), then you'll want to have
+ the cookiepath set to "/", or to a sufficiently-high enough directory that
+ all of the involved apps can see the cookies.
+ </P
+><DIV
+CLASS="example"
+><A
+NAME="trbl-relogin-everyone-share"
+></A
+><P
+><B
+>Example A-1. Examples of urlbase/cookiepath pairs for sharing login cookies</B
+></P
+><A
+NAME="AEN3090"
+></A
+><BLOCKQUOTE
+CLASS="BLOCKQUOTE"
+><P
+CLASS="literallayout"
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;urlbase&nbsp;is&nbsp;<A
+HREF="http://bugzilla.mozilla.org/"
+TARGET="_top"
+>http://bugzilla.mozilla.org/</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cookiepath&nbsp;is&nbsp;/<br>
+<br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;urlbase&nbsp;is&nbsp;<A
+HREF="http://tools.mysite.tld/bugzilla/"
+TARGET="_top"
+>http://tools.mysite.tld/bugzilla/</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;but&nbsp;you&nbsp;have&nbsp;http://tools.mysite.tld/someotherapp/&nbsp;which&nbsp;shares<br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;authentication&nbsp;with&nbsp;your&nbsp;Bugzilla<br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cookiepath&nbsp;is&nbsp;/<br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</P
+></BLOCKQUOTE
+></DIV
+><P
+>On the other hand, if you have more than one Bugzilla running on the
+ server (some people do - we do on landfill) then you need to have the
+ cookiepath restricted enough so that the different Bugzillas don't
+ confuse their cookies with one another.
+ </P
+><DIV
+CLASS="example"
+><A
+NAME="trbl-relogin-everyone-restrict"
+></A
+><P
+><B
+>Example A-2. Examples of urlbase/cookiepath pairs to restrict the login cookie</B
+></P
+><A
+NAME="AEN3097"
+></A
+><BLOCKQUOTE
+CLASS="BLOCKQUOTE"
+><P
+CLASS="literallayout"
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;urlbase&nbsp;is&nbsp;<A
+HREF="http://landfill.bugzilla.org/bugzilla-tip/"
+TARGET="_top"
+>http://landfill.bugzilla.org/bugzilla-tip/</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cookiepath&nbsp;is&nbsp;/bugzilla-tip/<br>
+<br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;urlbase&nbsp;is&nbsp;<A
+HREF="http://landfill.bugzilla.org/bugzilla-2.16-branch/"
+TARGET="_top"
+>http://landfill.bugzilla.org/bugzilla-2.16-branch/</A
+><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cookiepath&nbsp;is&nbsp;/bugzilla-2.16-branch/<br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</P
+></BLOCKQUOTE
+></DIV
+><P
+>If you had cookiepath set to <SPAN
+CLASS="QUOTE"
+>"/"</SPAN
+> at any point in the
+ past and need to set it to something more restrictive
+ (i.e. <SPAN
+CLASS="QUOTE"
+>"/bugzilla/"</SPAN
+>), you can safely do this without
+ requiring users to delete their Bugzilla-related cookies in their
+ browser (this is true starting with Bugzilla 2.18 and Bugzilla 2.16.5).
+ </P
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="paranoid-security.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="trbl-index.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>cannot chdir(/var/spool/mqueue)</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="troubleshooting.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><TT
+CLASS="filename"
+>index.cgi</TT
+> doesn't show up unless specified in the URL</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/trbl-testserver.html b/docs/en/html/trbl-testserver.html
new file mode 100644
index 000000000..c6b7af0bd
--- /dev/null
+++ b/docs/en/html/trbl-testserver.html
@@ -0,0 +1,186 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>The Apache web server is not serving Bugzilla pages</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Troubleshooting"
+HREF="troubleshooting.html"><LINK
+REL="PREVIOUS"
+TITLE="General Advice"
+HREF="general-advice.html"><LINK
+REL="NEXT"
+TITLE="I installed a Perl module, but
+ checksetup.pl claims it's not installed!"
+HREF="trbl-perlmodule.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="general-advice.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Appendix A. Troubleshooting</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="trbl-perlmodule.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="trbl-testserver"
+>A.2. The Apache web server is not serving Bugzilla pages</A
+></H1
+><P
+>After you have run <B
+CLASS="command"
+>checksetup.pl</B
+> twice,
+ run <B
+CLASS="command"
+>testserver.pl http://yoursite.yourdomain/yoururl</B
+>
+ to confirm that your web server is configured properly for
+ Bugzilla.
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#13;<SAMP
+CLASS="prompt"
+>bash$</SAMP
+> ./testserver.pl http://landfill.bugzilla.org/bugzilla-tip
+TEST-OK Webserver is running under group id in $webservergroup.
+TEST-OK Got ant picture.
+TEST-OK Webserver is executing CGIs.
+TEST-OK Webserver is preventing fetch of http://landfill.bugzilla.org/bugzilla-tip/localconfig.
+</PRE
+></FONT
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="general-advice.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="trbl-perlmodule.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>General Advice</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="troubleshooting.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>I installed a Perl module, but
+ <TT
+CLASS="filename"
+>checksetup.pl</TT
+> claims it's not installed!</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/troubleshooting.html b/docs/en/html/troubleshooting.html
new file mode 100644
index 000000000..57c862ee0
--- /dev/null
+++ b/docs/en/html/troubleshooting.html
@@ -0,0 +1,200 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Troubleshooting</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="PREVIOUS"
+TITLE="Integrating Bugzilla with Third-Party Tools"
+HREF="integration.html"><LINK
+REL="NEXT"
+TITLE="General Advice"
+HREF="general-advice.html"></HEAD
+><BODY
+CLASS="appendix"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="integration.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+></TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="general-advice.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="appendix"
+><H1
+><A
+NAME="troubleshooting"
+></A
+>Appendix A. Troubleshooting</H1
+><DIV
+CLASS="TOC"
+><DL
+><DT
+><B
+>Table of Contents</B
+></DT
+><DT
+>A.1. <A
+HREF="general-advice.html"
+>General Advice</A
+></DT
+><DT
+>A.2. <A
+HREF="trbl-testserver.html"
+>The Apache web server is not serving Bugzilla pages</A
+></DT
+><DT
+>A.3. <A
+HREF="trbl-perlmodule.html"
+>I installed a Perl module, but
+ <TT
+CLASS="filename"
+>checksetup.pl</TT
+> claims it's not installed!</A
+></DT
+><DT
+>A.4. <A
+HREF="trbl-dbdsponge.html"
+>DBD::Sponge::db prepare failed</A
+></DT
+><DT
+>A.5. <A
+HREF="paranoid-security.html"
+>cannot chdir(/var/spool/mqueue)</A
+></DT
+><DT
+>A.6. <A
+HREF="trbl-relogin-everyone.html"
+>Everybody is constantly being forced to relogin</A
+></DT
+><DT
+>A.7. <A
+HREF="trbl-index.html"
+><TT
+CLASS="filename"
+>index.cgi</TT
+> doesn't show up unless specified in the URL</A
+></DT
+><DT
+>A.8. <A
+HREF="trbl-passwd-encryption.html"
+>checksetup.pl reports "Client does not support authentication protocol
+ requested by server..."</A
+></DT
+></DL
+></DIV
+><P
+>This section gives solutions to common Bugzilla installation
+ problems. If none of the section headings seems to match your
+ problem, read the general advice.
+ </P
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="integration.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="general-advice.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Integrating Bugzilla with Third-Party Tools</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+>&nbsp;</TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>General Advice</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/upgrade.html b/docs/en/html/upgrade.html
new file mode 100644
index 000000000..82b6b9e87
--- /dev/null
+++ b/docs/en/html/upgrade.html
@@ -0,0 +1,941 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Upgrading to New Releases</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Installing Bugzilla"
+HREF="installing-bugzilla.html"><LINK
+REL="PREVIOUS"
+TITLE="UNIX (non-root) Installation Notes"
+HREF="nonroot.html"><LINK
+REL="NEXT"
+TITLE="Administering Bugzilla"
+HREF="administration.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="nonroot.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 2. Installing Bugzilla</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="administration.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="upgrade"
+>2.7. Upgrading to New Releases</A
+></H1
+><P
+>Upgrading to new Bugzilla releases is very simple. There is
+ a script included with Bugzilla that will automatically
+ do all of the database migration for you.</P
+><P
+>The following sections explain how to upgrade from one
+ version of Bugzilla to another. Whether you are upgrading
+ from one bug-fix version to another (such as 3.0.1 to 3.0.2)
+ or from one major version to another (such as from 3.0 to 3.2),
+ the instructions are always the same.</P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Any examples in the following sections are written as though the
+ user were updating to version 2.22.1, but the procedures are the
+ same no matter what version you're updating to. Also, in the
+ examples, the user's Bugzilla installation is found at
+ <TT
+CLASS="filename"
+>/var/www/html/bugzilla</TT
+>. If that is not the
+ same as the location of your Bugzilla installation, simply
+ substitute the proper paths where appropriate.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="upgrade-before"
+>2.7.1. Before You Upgrade</A
+></H2
+><P
+>Before you start your upgrade, there are a few important
+ steps to take:</P
+><P
+></P
+><OL
+TYPE="1"
+><LI
+><P
+>&#13; Read the <A
+HREF="http://www.bugzilla.org/releases/"
+TARGET="_top"
+>Release
+ Notes</A
+> of the version you're upgrading to,
+ particularly the "Notes for Upgraders" section.
+ </P
+></LI
+><LI
+><P
+>&#13; View the Sanity Check (<A
+HREF="sanitycheck.html"
+>Section 3.16</A
+>) page
+ on your installation before upgrading. Attempt to fix all warnings
+ that the page produces before you go any further, or you may
+ experience problems during your upgrade.
+ </P
+></LI
+><LI
+><P
+>&#13; Shut down your Bugzilla installation by putting some HTML or
+ text in the shutdownhtml parameter
+ (see <A
+HREF="parameters.html"
+>Section 3.1</A
+>).
+ </P
+></LI
+><LI
+><P
+>&#13; Make a backup of the Bugzilla database.
+ <EM
+>THIS IS VERY IMPORTANT</EM
+>. If
+ anything goes wrong during the upgrade, your installation
+ can be corrupted beyond recovery. Having a backup keeps you safe.
+ </P
+><DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Upgrading is a one-way process. You cannot "downgrade" an
+ upgraded Bugzilla. If you wish to revert to the old Bugzilla
+ version for any reason, you will have to restore your database
+ from this backup.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>Here are some sample commands you could use to backup
+ your database, depending on what database system you're
+ using. You may have to modify these commands for your
+ particular setup.</P
+><P
+></P
+><DIV
+CLASS="variablelist"
+><DL
+><DT
+>MySQL:</DT
+><DD
+><P
+>&#13; <B
+CLASS="command"
+>mysqldump --opt -u bugs -p bugs &#62; bugs.sql</B
+>
+ </P
+></DD
+><DT
+>PostgreSQL:</DT
+><DD
+><P
+>&#13; <B
+CLASS="command"
+>pg_dump --no-privileges --no-owner -h localhost -U bugs
+ &#62; bugs.sql</B
+>
+ </P
+></DD
+></DL
+></DIV
+></LI
+></OL
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="upgrade-files"
+>2.7.2. Getting The New Bugzilla</A
+></H2
+><P
+>There are three ways to get the new version of Bugzilla.
+ We'll list them here briefly and then explain them
+ more later.</P
+><P
+></P
+><DIV
+CLASS="variablelist"
+><DL
+><DT
+>CVS (<A
+HREF="upgrade.html#upgrade-cvs"
+>Section 2.7.2.2</A
+>)</DT
+><DD
+><P
+>&#13; If have <B
+CLASS="command"
+>cvs</B
+> installed on your machine
+ and you have Internet access, this is the easiest way to
+ upgrade, particularly if you have made modifications
+ to the code or templates of Bugzilla.
+ </P
+></DD
+><DT
+>Download the tarball (<A
+HREF="upgrade.html#upgrade-tarball"
+>Section 2.7.2.3</A
+>)</DT
+><DD
+><P
+>&#13; This is a very simple way to upgrade, and good if you
+ haven't made many (or any) modifications to the code or
+ templates of your Bugzilla.
+ </P
+></DD
+><DT
+>Patches (<A
+HREF="upgrade.html#upgrade-patches"
+>Section 2.7.2.4</A
+>)</DT
+><DD
+><P
+>&#13; If you have made modifications to your Bugzilla, and
+ you don't have Internet access or you don't want to use
+ cvs, then this is the best way to upgrade.
+ </P
+><P
+>&#13; You can only do minor upgrades (such as 3.0 to 3.0.1 or
+ 3.0.1 to 3.0.2) with patches.
+ </P
+></DD
+></DL
+></DIV
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="upgrade-modified"
+>2.7.2.1. If you have modified your Bugzilla</A
+></H3
+><P
+>&#13; If you have modified the code or templates of your Bugzilla,
+ then upgrading requires a bit more thought and effort.
+ A discussion of the various methods of updating compared with
+ degree and methods of local customization can be found in
+ <A
+HREF="cust-templates.html#template-method"
+>Section 6.3.2</A
+>.
+ </P
+><P
+>&#13; The larger the jump you are trying to make, the more difficult it
+ is going to be to upgrade if you have made local customizations.
+ Upgrading from 3.0 to 3.0.1 should be fairly painless even if
+ you are heavily customized, but going from 2.18 to 3.0 is going
+ to mean a fair bit of work re-writing your local changes to use
+ the new files, logic, templates, etc. If you have done no local
+ changes at all, however, then upgrading should be approximately
+ the same amount of work regardless of how long it has been since
+ your version was released.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="upgrade-cvs"
+>2.7.2.2. Upgrading using CVS</A
+></H3
+><P
+>&#13; This requires that you have cvs installed (most Unix machines do),
+ and requires that you are able to access cvs-mirror.mozilla.org
+ on port 2401, which may not be an option if you are behind a
+ highly restrictive firewall or don't have Internet access.
+ </P
+><P
+>&#13; The following shows the sequence of commands needed to update a
+ Bugzilla installation via CVS, and a typical series of results.
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#13;bash$ <B
+CLASS="command"
+>cd /var/www/html/bugzilla</B
+>
+bash$ <B
+CLASS="command"
+>cvs login</B
+>
+Logging in to :pserver:anonymous@cvs-mirror.mozilla.org:2401/cvsroot
+CVS password: <EM
+>('anonymous', or just leave it blank)</EM
+>
+bash$ <B
+CLASS="command"
+>cvs -q update -r BUGZILLA-2_22_1 -dP</B
+>
+P checksetup.pl
+P collectstats.pl
+P docs/rel_notes.txt
+P template/en/default/list/quips.html.tmpl
+<EM
+>(etc.)</EM
+>
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><DIV
+CLASS="caution"
+><P
+></P
+><TABLE
+CLASS="caution"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/caution.gif"
+HSPACE="5"
+ALT="Caution"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; If a line in the output from <B
+CLASS="command"
+>cvs update</B
+> begins
+ with a <SAMP
+CLASS="computeroutput"
+>C</SAMP
+>, then that represents a
+ file with local changes that CVS was unable to properly merge. You
+ need to resolve these conflicts manually before Bugzilla (or at
+ least the portion using that file) will be usable.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="upgrade-tarball"
+>2.7.2.3. Upgrading using the tarball</A
+></H3
+><P
+>&#13; If you are unable (or unwilling) to use CVS, another option that's
+ always available is to obtain the latest tarball from the <A
+HREF="http://www.bugzilla.org/download/"
+TARGET="_top"
+>Download Page</A
+> and
+ create a new Bugzilla installation from that.
+ </P
+><P
+>&#13; This sequence of commands shows how to get the tarball from the
+ command-line; it is also possible to download it from the site
+ directly in a web browser. If you go that route, save the file
+ to the <TT
+CLASS="filename"
+>/var/www/html</TT
+>
+ directory (or its equivalent, if you use something else) and
+ omit the first three lines of the example.
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#13;bash$ <B
+CLASS="command"
+>cd /var/www/html</B
+>
+bash$ <B
+CLASS="command"
+>wget http://ftp.mozilla.org/pub/mozilla.org/webtools/bugzilla-2.22.1.tar.gz</B
+>
+<EM
+>(Output omitted)</EM
+>
+bash$ <B
+CLASS="command"
+>tar xzvf bugzilla-2.22.1.tar.gz</B
+>
+bugzilla-2.22.1/
+bugzilla-2.22.1/.cvsignore
+<EM
+>(Output truncated)</EM
+>
+bash$ <B
+CLASS="command"
+>cd bugzilla-2.22.1</B
+>
+bash$ <B
+CLASS="command"
+>cp ../bugzilla/localconfig* .</B
+>
+bash$ <B
+CLASS="command"
+>cp -r ../bugzilla/data .</B
+>
+bash$ <B
+CLASS="command"
+>cd ..</B
+>
+bash$ <B
+CLASS="command"
+>mv bugzilla bugzilla.old</B
+>
+bash$ <B
+CLASS="command"
+>mv bugzilla-2.22.1 bugzilla</B
+>
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; The <B
+CLASS="command"
+>cp</B
+> commands both end with periods which
+ is a very important detail--it means that the destination
+ directory is the current working directory.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; This upgrade method will give you a clean install of Bugzilla.
+ That's fine if you don't have any local customizations that you
+ want to maintain. If you do have customizations, then you will
+ need to reapply them by hand to the appropriate files.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="upgrade-patches"
+>2.7.2.4. Upgrading using patches</A
+></H3
+><P
+>&#13; A patch is a collection of all the bug fixes that have been made
+ since the last bug-fix release.
+ </P
+><P
+>&#13; If you are doing a bug-fix upgrade&#8212;that is, one where only the
+ last number of the revision changes, such as from 2.22 to
+ 2.22.1&#8212;then you have the option of obtaining and applying a
+ patch file from the <A
+HREF="http://www.bugzilla.org/download/"
+TARGET="_top"
+>Download Page</A
+>.
+ </P
+><P
+>&#13; As above, this example starts with obtaining the file via the
+ command line. If you have already downloaded it, you can omit the
+ first two commands.
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#13;bash$ <B
+CLASS="command"
+>cd /var/www/html/bugzilla</B
+>
+bash$ <B
+CLASS="command"
+>wget http://ftp.mozilla.org/pub/mozilla.org/webtools/bugzilla-2.22-to-2.22.1.diff.gz</B
+>
+<EM
+>(Output omitted)</EM
+>
+bash$ <B
+CLASS="command"
+>gunzip bugzilla-2.22-to-2.22.1.diff.gz</B
+>
+bash$ <B
+CLASS="command"
+>patch -p1 &#60; bugzilla-2.22-to-2.22.1.diff</B
+>
+patching file checksetup.pl
+patching file collectstats.pl
+<EM
+>(etc.)</EM
+>
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Be aware that upgrading from a patch file does not change the
+ entries in your <TT
+CLASS="filename"
+>CVS</TT
+> directory.
+ This could make it more difficult to upgrade using CVS
+ (<A
+HREF="upgrade.html#upgrade-cvs"
+>Section 2.7.2.2</A
+>) in the future.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="upgrade-completion"
+>2.7.3. Completing Your Upgrade</A
+></H2
+><P
+>&#13; Now that you have the new Bugzilla code, there are a few final
+ steps to complete your upgrade.
+ </P
+><P
+></P
+><OL
+TYPE="1"
+><LI
+><P
+>&#13; If your new Bugzilla installation is in a different
+ directory or on a different machine than your old Bugzilla
+ installation, make sure that you have copied the
+ <TT
+CLASS="filename"
+>data</TT
+> directory and the
+ <TT
+CLASS="filename"
+>localconfig</TT
+> file from your old Bugzilla
+ installation. (If you followed the tarball instructions
+ above, this has already happened.)
+ </P
+></LI
+><LI
+><P
+>&#13; If this is a major update, check that the configuration
+ (<A
+HREF="configuration.html"
+>Section 2.2</A
+>) for your new Bugzilla is
+ up-to-date. Sometimes the configuration requirements change
+ between major versions.
+ </P
+></LI
+><LI
+><P
+>&#13; If you didn't do it as part of the above configuration step,
+ now you need to run <B
+CLASS="command"
+>checksetup.pl</B
+>, which
+ will do everything required to convert your existing database
+ and settings for the new version:
+ </P
+><TABLE
+BORDER="0"
+BGCOLOR="#E0E0E0"
+WIDTH="100%"
+><TR
+><TD
+><FONT
+COLOR="#000000"
+><PRE
+CLASS="programlisting"
+>&#13;bash$ <B
+CLASS="command"
+>cd /var/www/html/bugzilla</B
+>
+bash$ <B
+CLASS="command"
+>./checksetup.pl</B
+>
+ </PRE
+></FONT
+></TD
+></TR
+></TABLE
+><DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; The period at the beginning of the command
+ <B
+CLASS="command"
+>./checksetup.pl</B
+> is important and can not
+ be omitted.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="caution"
+><P
+></P
+><TABLE
+CLASS="caution"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/caution.gif"
+HSPACE="5"
+ALT="Caution"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; If this is a major upgrade (say, 2.22 to 3.0 or similar),
+ running <B
+CLASS="command"
+>checksetup.pl</B
+> on a large
+ installation (75,000 or more bugs) can take a long time,
+ possibly several hours.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></LI
+><LI
+><P
+>&#13; Clear any HTML or text that you put into the shutdownhtml
+ parameter, to re-activate Bugzilla.
+ </P
+></LI
+><LI
+><P
+>&#13; View the Sanity Check (<A
+HREF="sanitycheck.html"
+>Section 3.16</A
+>) page in your
+ upgraded Bugzilla.
+ </P
+><P
+>&#13; It is recommended that, if possible, you fix any problems
+ you see, immediately. Failure to do this may mean that Bugzilla
+ will not work correctly. Be aware that if the sanity check page
+ contains more errors after an upgrade, it doesn't necessarily
+ mean there are more errors in your database than there were
+ before, as additional tests are added to the sanity check over
+ time, and it is possible that those errors weren't being
+ checked for in the old version.
+ </P
+></LI
+></OL
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="upgrade-notifications"
+>2.7.4. Automatic Notifications of New Releases</A
+></H2
+><P
+>&#13; Bugzilla 3.0 introduced the ability to automatically notify
+ administrators when new releases are available, based on the
+ <TT
+CLASS="literal"
+>upgrade_notification</TT
+> parameter, see
+ <A
+HREF="parameters.html"
+>Section 3.1</A
+>. Administrators will see these
+ notifications when they access the <TT
+CLASS="filename"
+>index.cgi</TT
+>
+ page, i.e. generally when logging in. Bugzilla will check once per
+ day for new releases, unless the parameter is set to
+ <SPAN
+CLASS="QUOTE"
+>"disabled"</SPAN
+>. If you are behind a proxy, you may have to set
+ the <TT
+CLASS="literal"
+>proxy_url</TT
+> parameter accordingly. If the proxy
+ requires authentication, use the
+ <TT
+CLASS="literal"
+>http://user:pass@proxy_url/</TT
+> syntax.
+ </P
+></DIV
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="nonroot.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="administration.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>UNIX (non-root) Installation Notes</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="installing-bugzilla.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Administering Bugzilla</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/useradmin.html b/docs/en/html/useradmin.html
new file mode 100644
index 000000000..92c078df7
--- /dev/null
+++ b/docs/en/html/useradmin.html
@@ -0,0 +1,718 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>User Administration</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Administering Bugzilla"
+HREF="administration.html"><LINK
+REL="PREVIOUS"
+TITLE="Bugzilla Configuration"
+HREF="parameters.html"><LINK
+REL="NEXT"
+TITLE="Classifications"
+HREF="classifications.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="parameters.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 3. Administering Bugzilla</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="classifications.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="useradmin"
+>3.2. User Administration</A
+></H1
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="defaultuser"
+>3.2.1. Creating the Default User</A
+></H2
+><P
+>When you first run checksetup.pl after installing Bugzilla, it
+ will prompt you for the administrative username (email address) and
+ password for this "super user". If for some reason you delete
+ the "super user" account, re-running checksetup.pl will again prompt
+ you for this username and password.</P
+><DIV
+CLASS="tip"
+><P
+></P
+><TABLE
+CLASS="tip"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/tip.gif"
+HSPACE="5"
+ALT="Tip"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>If you wish to add more administrative users, add them to
+ the "admin" group and, optionally, edit the tweakparams, editusers,
+ creategroups, editcomponents, and editkeywords groups to add the
+ entire admin group to those groups (which is the case by default).
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="manageusers"
+>3.2.2. Managing Other Users</A
+></H2
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="user-account-search"
+>3.2.2.1. Searching for existing users</A
+></H3
+><P
+>&#13; If you have <SPAN
+CLASS="QUOTE"
+>"editusers"</SPAN
+> privileges or if you are allowed
+ to grant privileges for some groups, the <SPAN
+CLASS="QUOTE"
+>"Users"</SPAN
+> link
+ will appear in the Administration page.
+ </P
+><P
+>&#13; The first screen is a search form to search for existing user
+ accounts. You can run searches based either on the user ID, real
+ name or login name (i.e. the email address, or just the first part
+ of the email address if the "emailsuffix" parameter is set).
+ The search can be conducted
+ in different ways using the listbox to the right of the text entry
+ box. You can match by case-insensitive substring (the default),
+ regular expression, a <EM
+>reverse</EM
+> regular expression
+ match (which finds every user name which does NOT match the regular
+ expression), or the exact string if you know exactly who you are
+ looking for. The search can be restricted to users who are in a
+ specific group. By default, the restriction is turned off.
+ </P
+><P
+>&#13; The search returns a list of
+ users matching your criteria. User properties can be edited by clicking
+ the login name. The Account History of a user can be viewed by clicking
+ the "View" link in the Account History column. The Account History
+ displays changes that have been made to the user account, the time of
+ the change and the user who made the change. For example, the Account
+ History page will display details of when a user was added or removed
+ from a group.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="createnewusers"
+>3.2.2.2. Creating new users</A
+></H3
+><DIV
+CLASS="section"
+><H4
+CLASS="section"
+><A
+NAME="self-registration"
+>3.2.2.2.1. Self-registration</A
+></H4
+><P
+>&#13; By default, users can create their own user accounts by clicking the
+ <SPAN
+CLASS="QUOTE"
+>"New Account"</SPAN
+> link at the bottom of each page (assuming
+ they aren't logged in as someone else already). If you want to disable
+ this self-registration, or if you want to restrict who can create his
+ own user account, you have to edit the <SPAN
+CLASS="QUOTE"
+>"createemailregexp"</SPAN
+>
+ parameter in the <SPAN
+CLASS="QUOTE"
+>"Configuration"</SPAN
+> page, see
+ <A
+HREF="parameters.html"
+>Section 3.1</A
+>.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H4
+CLASS="section"
+><A
+NAME="user-account-creation"
+>3.2.2.2.2. Accounts created by an administrator</A
+></H4
+><P
+>&#13; Users with <SPAN
+CLASS="QUOTE"
+>"editusers"</SPAN
+> privileges, such as administrators,
+ can create user accounts for other users:
+ </P
+><P
+></P
+><OL
+TYPE="1"
+><LI
+><P
+>After logging in, click the "Users" link at the footer of
+ the query page, and then click "Add a new user".</P
+></LI
+><LI
+><P
+>Fill out the form presented. This page is self-explanatory.
+ When done, click "Submit".</P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>Adding a user this way will <EM
+>not</EM
+>
+ send an email informing them of their username and password.
+ While useful for creating dummy accounts (watchers which
+ shuttle mail to another system, for instance, or email
+ addresses which are a mailing list), in general it is
+ preferable to log out and use the <SPAN
+CLASS="QUOTE"
+>"New Account"</SPAN
+>
+ button to create users, as it will pre-populate all the
+ required fields and also notify the user of her account name
+ and password.</P
+></TD
+></TR
+></TABLE
+></DIV
+></LI
+></OL
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="modifyusers"
+>3.2.2.3. Modifying Users</A
+></H3
+><P
+>Once you have found your user, you can change the following
+ fields:</P
+><P
+></P
+><UL
+><LI
+><P
+>&#13; <EM
+>Login Name</EM
+>:
+ This is generally the user's full email address. However, if you
+ have are using the <SPAN
+CLASS="QUOTE"
+>"emailsuffix"</SPAN
+> parameter, this may
+ just be the user's login name. Note that users can now change their
+ login names themselves (to any valid email address).
+ </P
+></LI
+><LI
+><P
+>&#13; <EM
+>Real Name</EM
+>: The user's real name. Note that
+ Bugzilla does not require this to create an account.</P
+></LI
+><LI
+><P
+>&#13; <EM
+>Password</EM
+>:
+ You can change the user's password here. Users can automatically
+ request a new password, so you shouldn't need to do this often.
+ If you want to disable an account, see Disable Text below.
+ </P
+></LI
+><LI
+><P
+>&#13; <EM
+>Bugmail Disabled</EM
+>:
+ Mark this checkbox to disable bugmail and whinemail completely
+ for this account. This checkbox replaces the data/nomail file
+ which existed in older versions of Bugzilla.
+ </P
+></LI
+><LI
+><P
+>&#13; <EM
+>Disable Text</EM
+>:
+ If you type anything in this box, including just a space, the
+ user is prevented from logging in, or making any changes to
+ bugs via the web interface.
+ The HTML you type in this box is presented to the user when
+ they attempt to perform these actions, and should explain
+ why the account was disabled.
+ </P
+><P
+>&#13; Users with disabled accounts will continue to receive
+ mail from Bugzilla; furthermore, they will not be able
+ to log in themselves to change their own preferences and
+ stop it. If you want an account (disabled or active) to
+ stop receiving mail, simply check the
+ <SPAN
+CLASS="QUOTE"
+>"Bugmail Disabled"</SPAN
+> checkbox above.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Even users whose accounts have been disabled can still
+ submit bugs via the e-mail gateway, if one exists.
+ The e-mail gateway should <EM
+>not</EM
+> be
+ enabled for secure installations of Bugzilla.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Don't disable all the administrator accounts!
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></LI
+><LI
+><P
+>&#13; <EM
+>&#60;groupname&#62;</EM
+>:
+ If you have created some groups, e.g. "securitysensitive", then
+ checkboxes will appear here to allow you to add users to, or
+ remove them from, these groups. The first checkbox gives the
+ user the ability to add and remove other users as members of
+ this group. The second checkbox adds the user himself as a member
+ of the group.
+ </P
+></LI
+><LI
+><P
+>&#13; <EM
+>canconfirm</EM
+>:
+ This field is only used if you have enabled the "unconfirmed"
+ status. If you enable this for a user,
+ that user can then move bugs from "Unconfirmed" to a "Confirmed"
+ status (e.g.: "New" status).</P
+></LI
+><LI
+><P
+>&#13; <EM
+>creategroups</EM
+>:
+ This option will allow a user to create and destroy groups in
+ Bugzilla.</P
+></LI
+><LI
+><P
+>&#13; <EM
+>editbugs</EM
+>:
+ Unless a user has this bit set, they can only edit those bugs
+ for which they are the assignee or the reporter. Even if this
+ option is unchecked, users can still add comments to bugs.
+ </P
+></LI
+><LI
+><P
+>&#13; <EM
+>editcomponents</EM
+>:
+ This flag allows a user to create new products and components,
+ as well as modify and destroy those that have no bugs associated
+ with them. If a product or component has bugs associated with it,
+ those bugs must be moved to a different product or component
+ before Bugzilla will allow them to be destroyed.
+ </P
+></LI
+><LI
+><P
+>&#13; <EM
+>editkeywords</EM
+>:
+ If you use Bugzilla's keyword functionality, enabling this
+ feature allows a user to create and destroy keywords. As always,
+ the keywords for existing bugs containing the keyword the user
+ wishes to destroy must be changed before Bugzilla will allow it
+ to die.</P
+></LI
+><LI
+><P
+>&#13; <EM
+>editusers</EM
+>:
+ This flag allows a user to do what you're doing right now: edit
+ other users. This will allow those with the right to do so to
+ remove administrator privileges from other users or grant them to
+ themselves. Enable with care.</P
+></LI
+><LI
+><P
+>&#13; <EM
+>tweakparams</EM
+>:
+ This flag allows a user to change Bugzilla's Params
+ (using <TT
+CLASS="filename"
+>editparams.cgi</TT
+>.)</P
+></LI
+><LI
+><P
+>
+ <EM
+>&#60;productname&#62;</EM
+>:
+ This allows an administrator to specify the products
+ in which a user can see bugs. If you turn on the
+ <SPAN
+CLASS="QUOTE"
+>"makeproductgroups"</SPAN
+> parameter in
+ the Group Security Panel in the Parameters page,
+ then Bugzilla creates one group per product (at the time you create
+ the product), and this group has exactly the same name as the
+ product itself. Note that for products that already exist when
+ the parameter is turned on, the corresponding group will not be
+ created. The user must still have the <SPAN
+CLASS="QUOTE"
+>"editbugs"</SPAN
+>
+ privilege to edit bugs in these products.</P
+></LI
+></UL
+></DIV
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="user-account-deletion"
+>3.2.2.4. Deleting Users</A
+></H3
+><P
+>&#13; If the <SPAN
+CLASS="QUOTE"
+>"allowuserdeletion"</SPAN
+> parameter is turned on, see
+ <A
+HREF="parameters.html"
+>Section 3.1</A
+>, then you can also delete user accounts.
+ Note that this is most of the time not the best thing to do. If only
+ a warning in a yellow box is displayed, then the deletion is safe.
+ If a warning is also displayed in a red box, then you should NOT try
+ to delete the user account, else you will get referential integrity
+ problems in your database, which can lead to unexpected behavior,
+ such as bugs not appearing in bug lists anymore, or data displaying
+ incorrectly. You have been warned!
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H3
+CLASS="section"
+><A
+NAME="impersonatingusers"
+>3.2.2.5. Impersonating Users</A
+></H3
+><P
+>&#13; There may be times when an administrator would like to do something as
+ another user. The <B
+CLASS="command"
+>sudo</B
+> feature may be used to do
+ this.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; To use the sudo feature, you must be in the
+ <EM
+>bz_sudoers</EM
+> group. By default, all
+ administrators are in this group.</P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; If you have access to this feature, you may start a session by
+ going to the Edit Users page, Searching for a user and clicking on
+ their login. You should see a link below their login name titled
+ "Impersonate this user". Click on the link. This will take you
+ to a page where you will see a description of the feature and
+ instructions for using it. After reading the text, simply
+ enter the login of the user you would like to impersonate, provide
+ a short message explaining why you are doing this, and press the
+ button.</P
+><P
+>&#13; As long as you are using this feature, everything you do will be done
+ as if you were logged in as the user you are impersonating.</P
+><DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; The user you are impersonating will not be told about what you are
+ doing. If you do anything that results in mail being sent, that
+ mail will appear to be from the user you are impersonating. You
+ should be extremely careful while using this feature.</P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+></DIV
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="parameters.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="classifications.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Bugzilla Configuration</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="administration.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Classifications</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/userpreferences.html b/docs/en/html/userpreferences.html
new file mode 100644
index 000000000..cdfc85239
--- /dev/null
+++ b/docs/en/html/userpreferences.html
@@ -0,0 +1,720 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>User Preferences</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Using Bugzilla"
+HREF="using.html"><LINK
+REL="PREVIOUS"
+TITLE="Time Tracking Information"
+HREF="timetracking.html"><LINK
+REL="NEXT"
+TITLE="Reports and Charts"
+HREF="reporting.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="timetracking.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 5. Using Bugzilla</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="reporting.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="userpreferences"
+>5.10. User Preferences</A
+></H1
+><P
+>&#13; Once logged in, you can customize various aspects of
+ Bugzilla via the "Preferences" link in the page footer.
+ The preferences are split into five tabs:</P
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="generalpreferences"
+>5.10.1. General Preferences</A
+></H2
+><P
+>&#13; This tab allows you to change several default settings of Bugzilla.
+ </P
+><P
+></P
+><UL
+COMPACT="COMPACT"
+><LI
+><P
+>&#13; Bugzilla's general appearance (skin) - select which skin to use.
+ Bugzilla supports adding custom skins.
+ </P
+></LI
+><LI
+><P
+>&#13; Quote the associated comment when you click on its reply link - sets
+ the behavior of the comment "Reply" link. Options include quoting the
+ full comment, just reference the comment number, or turn the link off.
+ </P
+></LI
+><LI
+><P
+>&#13; Language used in email - select which language email will be sent in,
+ from the list of available languages.
+ </P
+></LI
+><LI
+><P
+>&#13; After changing a bug - This controls what page is displayed after
+ changes to a bug are submitted. The options include to show the bug
+ just modified, to show the next bug in your list, or to do nothing.
+ </P
+></LI
+><LI
+><P
+>&#13; Enable tags for bugs - turn bug tagging on or off.
+ </P
+></LI
+><LI
+><P
+>&#13; Zoom textareas large when in use (requires JavaScript) - enable or
+ disable the automatic expanding of text areas when text is being
+ entered into them.
+ </P
+></LI
+><LI
+><P
+>&#13; Field separator character for CSV files -
+ Select between a comma and semi-colon for exported CSV bug lists.
+ </P
+></LI
+><LI
+><P
+>&#13; Automatically add me to the CC list of bugs I change - set default
+ behavior of CC list. Options include "Always", "Never", and "Only
+ if I have no role on them".
+ </P
+></LI
+><LI
+><P
+>&#13; When viewing a bug, show comments in this order -
+ controls the order of comments. Options include "Oldest
+ to Newest", "Newest to Oldest" and "Newest to Oldest, but keep the
+ bug description at the top".
+ </P
+></LI
+><LI
+><P
+>&#13; Show a quip at the top of each bug list - controls
+ whether a quip will be shown on the Bug list page.
+ </P
+></LI
+></UL
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="emailpreferences"
+>5.10.2. Email Preferences</A
+></H2
+><P
+>&#13; This tab allows you to enable or disable email notification on
+ specific events.
+ </P
+><P
+>&#13; In general, users have almost complete control over how much (or
+ how little) email Bugzilla sends them. If you want to receive the
+ maximum amount of email possible, click the <SPAN
+CLASS="QUOTE"
+>"Enable All
+ Mail"</SPAN
+> button. If you don't want to receive any email from
+ Bugzilla at all, click the <SPAN
+CLASS="QUOTE"
+>"Disable All Mail"</SPAN
+> button.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; A Bugzilla administrator can stop a user from receiving
+ bugmail by clicking the <SPAN
+CLASS="QUOTE"
+>"Bugmail Disabled"</SPAN
+> checkbox
+ when editing the user account. This is a drastic step
+ best taken only for disabled accounts, as it overrides
+ the user's individual mail preferences.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; There are two global options -- <SPAN
+CLASS="QUOTE"
+>"Email me when someone
+ asks me to set a flag"</SPAN
+> and <SPAN
+CLASS="QUOTE"
+>"Email me when someone
+ sets a flag I asked for"</SPAN
+>. These define how you want to
+ receive bugmail with regards to flags. Their use is quite
+ straightforward; enable the checkboxes if you want Bugzilla to
+ send you mail under either of the above conditions.
+ </P
+><P
+>&#13; If you'd like to set your bugmail to something besides
+ 'Completely ON' and 'Completely OFF', the
+ <SPAN
+CLASS="QUOTE"
+>"Field/recipient specific options"</SPAN
+> table
+ allows you to do just that. The rows of the table
+ define events that can happen to a bug -- things like
+ attachments being added, new comments being made, the
+ priority changing, etc. The columns in the table define
+ your relationship with the bug:
+ </P
+><P
+></P
+><UL
+COMPACT="COMPACT"
+><LI
+><P
+>&#13; Reporter - Where you are the person who initially
+ reported the bug. Your name/account appears in the
+ <SPAN
+CLASS="QUOTE"
+>"Reporter:"</SPAN
+> field.
+ </P
+></LI
+><LI
+><P
+>&#13; Assignee - Where you are the person who has been
+ designated as the one responsible for the bug. Your
+ name/account appears in the <SPAN
+CLASS="QUOTE"
+>"Assigned To:"</SPAN
+>
+ field of the bug.
+ </P
+></LI
+><LI
+><P
+>&#13; QA Contact - You are one of the designated
+ QA Contacts for the bug. Your account appears in the
+ <SPAN
+CLASS="QUOTE"
+>"QA Contact:"</SPAN
+> text-box of the bug.
+ </P
+></LI
+><LI
+><P
+>&#13; CC - You are on the list CC List for the bug.
+ Your account appears in the <SPAN
+CLASS="QUOTE"
+>"CC:"</SPAN
+> text box
+ of the bug.
+ </P
+></LI
+><LI
+><P
+>&#13; Voter - You have placed one or more votes for the bug.
+ Your account appears only if someone clicks on the
+ <SPAN
+CLASS="QUOTE"
+>"Show votes for this bug"</SPAN
+> link on the bug.
+ </P
+></LI
+></UL
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Some columns may not be visible for your installation, depending
+ on your site's configuration.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; To fine-tune your bugmail, decide the events for which you want
+ to receive bugmail; then decide if you want to receive it all
+ the time (enable the checkbox for every column), or only when
+ you have a certain relationship with a bug (enable the checkbox
+ only for those columns). For example: if you didn't want to
+ receive mail when someone added themselves to the CC list, you
+ could uncheck all the boxes in the <SPAN
+CLASS="QUOTE"
+>"CC Field Changes"</SPAN
+>
+ line. As another example, if you never wanted to receive email
+ on bugs you reported unless the bug was resolved, you would
+ un-check all boxes in the <SPAN
+CLASS="QUOTE"
+>"Reporter"</SPAN
+> column
+ except for the one on the <SPAN
+CLASS="QUOTE"
+>"The bug is resolved or
+ verified"</SPAN
+> row.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Bugzilla adds the <SPAN
+CLASS="QUOTE"
+>"X-Bugzilla-Reason"</SPAN
+> header to
+ all bugmail it sends, describing the recipient's relationship
+ (AssignedTo, Reporter, QAContact, CC, or Voter) to the bug.
+ This header can be used to do further client-side filtering.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; Bugzilla has a feature called <SPAN
+CLASS="QUOTE"
+>"Users Watching"</SPAN
+>.
+ When you enter one or more comma-delineated user accounts (usually email
+ addresses) into the text entry box, you will receive a copy of all the
+ bugmail those users are sent (security settings permitting).
+ This powerful functionality enables seamless transitions as developers
+ change projects or users go on holiday.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; The ability to watch other users may not be available in all
+ Bugzilla installations. If you don't see this feature, and feel
+ that you need it, speak to your administrator.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; Each user listed in the <SPAN
+CLASS="QUOTE"
+>"Users watching you"</SPAN
+> field
+ has you listed in their <SPAN
+CLASS="QUOTE"
+>"Users to watch"</SPAN
+> list
+ and can get bugmail according to your relationship to the bug and
+ their <SPAN
+CLASS="QUOTE"
+>"Field/recipient specific options"</SPAN
+> setting.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="savedsearches"
+>5.10.3. Saved Searches</A
+></H2
+><P
+>&#13; On this tab you can view and run any Saved Searches that you have
+ created, and also any Saved Searches that other members of the group
+ defined in the "querysharegroup" parameter have shared.
+ Saved Searches can be added to the page footer from this screen.
+ If somebody is sharing a Search with a group she or he is allowed to
+ <A
+HREF="groups.html"
+>assign users to</A
+>, the sharer may opt to have
+ the Search show up in the footer of the group's direct members by default.
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="accountpreferences"
+>5.10.4. Name and Password</A
+></H2
+><P
+>On this tab, you can change your basic account information,
+ including your password, email address and real name. For security
+ reasons, in order to change anything on this page you must type your
+ <EM
+>current</EM
+> password into the <SPAN
+CLASS="QUOTE"
+>"Password"</SPAN
+>
+ field at the top of the page.
+ If you attempt to change your email address, a confirmation
+ email is sent to both the old and new addresses, with a link to use to
+ confirm the change. This helps to prevent account hijacking.</P
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="permissionsettings"
+>5.10.5. Permissions</A
+></H2
+><P
+>&#13; This is a purely informative page which outlines your current
+ permissions on this installation of Bugzilla.
+ </P
+><P
+>&#13; A complete list of permissions is below. Only users with
+ <EM
+>editusers</EM
+> privileges can change the permissions
+ of other users.
+ </P
+><P
+></P
+><DIV
+CLASS="variablelist"
+><DL
+><DT
+>admin</DT
+><DD
+><P
+>
+ Indicates user is an Administrator.
+ </P
+></DD
+><DT
+>bz_canusewhineatothers</DT
+><DD
+><P
+>
+ Indicates user can configure whine reports for other users.
+ </P
+></DD
+><DT
+>bz_canusewhines</DT
+><DD
+><P
+>
+ Indicates user can configure whine reports for self.
+ </P
+></DD
+><DT
+>bz_sudoers</DT
+><DD
+><P
+>
+ Indicates user can perform actions as other users.
+ </P
+></DD
+><DT
+>bz_sudo_protect</DT
+><DD
+><P
+>
+ Indicates user can not be impersonated by other users.
+ </P
+></DD
+><DT
+>canconfirm</DT
+><DD
+><P
+>
+ Indicates user can confirm a bug or mark it a duplicate.
+ </P
+></DD
+><DT
+>creategroups</DT
+><DD
+><P
+>
+ Indicates user can create and destroy groups.
+ </P
+></DD
+><DT
+>editbugs</DT
+><DD
+><P
+>
+ Indicates user can edit all bug fields.
+ </P
+></DD
+><DT
+>editclassifications</DT
+><DD
+><P
+>
+ Indicates user can create, destroy, and edit classifications.
+ </P
+></DD
+><DT
+>editcomponents</DT
+><DD
+><P
+>
+ Indicates user can create, destroy, and edit components.
+ </P
+></DD
+><DT
+>editkeywords</DT
+><DD
+><P
+>
+ Indicates user can create, destroy, and edit keywords.
+ </P
+></DD
+><DT
+>editusers</DT
+><DD
+><P
+>
+ Indicates user can edit or disable users.
+ </P
+></DD
+><DT
+>tweakparams</DT
+><DD
+><P
+>
+ Indicates user can change Parameters.
+ </P
+></DD
+></DL
+></DIV
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; For more information on how permissions work in Bugzilla (i.e. who can
+ change what), see <A
+HREF="cust-change-permissions.html"
+>Section 6.4</A
+>.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="timetracking.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="reporting.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Time Tracking Information</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="using.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Reports and Charts</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/using-intro.html b/docs/en/html/using-intro.html
new file mode 100644
index 000000000..fe894d01f
--- /dev/null
+++ b/docs/en/html/using-intro.html
@@ -0,0 +1,167 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Introduction</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Using Bugzilla"
+HREF="using.html"><LINK
+REL="PREVIOUS"
+TITLE="Using Bugzilla"
+HREF="using.html"><LINK
+REL="NEXT"
+TITLE="Create a Bugzilla Account"
+HREF="myaccount.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="using.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 5. Using Bugzilla</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="myaccount.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="using-intro"
+>5.1. Introduction</A
+></H1
+><P
+>This section contains information for end-users of Bugzilla. There
+ is a Bugzilla test installation, called
+ <A
+HREF="http://landfill.bugzilla.org/"
+TARGET="_top"
+>Landfill</A
+>, which you are
+ welcome to play with (if it's up). However, not all of the Bugzilla
+ installations there will necessarily have all Bugzilla features enabled,
+ and different installations run different versions, so some things may not
+ quite work as this document describes.</P
+><P
+>&#13; Frequently Asked Questions (FAQ) are available and answered on
+ <A
+HREF="http://wiki.mozilla.org/Bugzilla:FAQ"
+TARGET="_top"
+>wiki.mozilla.org</A
+>.
+ They may cover some questions you have which are left unanswered.
+ </P
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="using.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="myaccount.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Using Bugzilla</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="using.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Create a Bugzilla Account</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/using.html b/docs/en/html/using.html
new file mode 100644
index 000000000..060a32581
--- /dev/null
+++ b/docs/en/html/using.html
@@ -0,0 +1,346 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Using Bugzilla</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="PREVIOUS"
+TITLE="Bugzilla"
+HREF="security-bugzilla.html"><LINK
+REL="NEXT"
+TITLE="Introduction"
+HREF="using-intro.html"></HEAD
+><BODY
+CLASS="chapter"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="security-bugzilla.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+></TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="using-intro.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="chapter"
+><H1
+><A
+NAME="using"
+></A
+>Chapter 5. Using Bugzilla</H1
+><DIV
+CLASS="TOC"
+><DL
+><DT
+><B
+>Table of Contents</B
+></DT
+><DT
+>5.1. <A
+HREF="using-intro.html"
+>Introduction</A
+></DT
+><DT
+>5.2. <A
+HREF="myaccount.html"
+>Create a Bugzilla Account</A
+></DT
+><DT
+>5.3. <A
+HREF="bug_page.html"
+>Anatomy of a Bug</A
+></DT
+><DT
+>5.4. <A
+HREF="lifecycle.html"
+>Life Cycle of a Bug</A
+></DT
+><DT
+>5.5. <A
+HREF="query.html"
+>Searching for Bugs</A
+></DT
+><DD
+><DL
+><DT
+>5.5.1. <A
+HREF="query.html#boolean"
+>Boolean Charts</A
+></DT
+><DT
+>5.5.2. <A
+HREF="query.html#quicksearch"
+>Quicksearch</A
+></DT
+><DT
+>5.5.3. <A
+HREF="query.html#casesensitivity"
+>Case Sensitivity in Searches</A
+></DT
+><DT
+>5.5.4. <A
+HREF="query.html#list"
+>Bug Lists</A
+></DT
+><DT
+>5.5.5. <A
+HREF="query.html#individual-buglists"
+>Adding/removing tags to/from bugs</A
+></DT
+></DL
+></DD
+><DT
+>5.6. <A
+HREF="bugreports.html"
+>Filing Bugs</A
+></DT
+><DD
+><DL
+><DT
+>5.6.1. <A
+HREF="bugreports.html#fillingbugs"
+>Reporting a New Bug</A
+></DT
+><DT
+>5.6.2. <A
+HREF="bugreports.html#cloningbugs"
+>Clone an Existing Bug</A
+></DT
+></DL
+></DD
+><DT
+>5.7. <A
+HREF="attachments.html"
+>Attachments</A
+></DT
+><DT
+>5.8. <A
+HREF="hintsandtips.html"
+>Hints and Tips</A
+></DT
+><DD
+><DL
+><DT
+>5.8.1. <A
+HREF="hintsandtips.html#AEN2591"
+>Autolinkification</A
+></DT
+><DT
+>5.8.2. <A
+HREF="hintsandtips.html#commenting"
+>Comments</A
+></DT
+><DT
+>5.8.3. <A
+HREF="hintsandtips.html#comment-wrapping"
+>Server-Side Comment Wrapping</A
+></DT
+><DT
+>5.8.4. <A
+HREF="hintsandtips.html#dependencytree"
+>Dependency Tree</A
+></DT
+></DL
+></DD
+><DT
+>5.9. <A
+HREF="timetracking.html"
+>Time Tracking Information</A
+></DT
+><DT
+>5.10. <A
+HREF="userpreferences.html"
+>User Preferences</A
+></DT
+><DD
+><DL
+><DT
+>5.10.1. <A
+HREF="userpreferences.html#generalpreferences"
+>General Preferences</A
+></DT
+><DT
+>5.10.2. <A
+HREF="userpreferences.html#emailpreferences"
+>Email Preferences</A
+></DT
+><DT
+>5.10.3. <A
+HREF="userpreferences.html#savedsearches"
+>Saved Searches</A
+></DT
+><DT
+>5.10.4. <A
+HREF="userpreferences.html#accountpreferences"
+>Name and Password</A
+></DT
+><DT
+>5.10.5. <A
+HREF="userpreferences.html#permissionsettings"
+>Permissions</A
+></DT
+></DL
+></DD
+><DT
+>5.11. <A
+HREF="reporting.html"
+>Reports and Charts</A
+></DT
+><DD
+><DL
+><DT
+>5.11.1. <A
+HREF="reporting.html#reports"
+>Reports</A
+></DT
+><DT
+>5.11.2. <A
+HREF="reporting.html#charts"
+>Charts</A
+></DT
+></DL
+></DD
+><DT
+>5.12. <A
+HREF="flags.html"
+>Flags</A
+></DT
+><DT
+>5.13. <A
+HREF="whining.html"
+>Whining</A
+></DT
+><DD
+><DL
+><DT
+>5.13.1. <A
+HREF="whining.html#whining-overview"
+>The Event</A
+></DT
+><DT
+>5.13.2. <A
+HREF="whining.html#whining-schedule"
+>Whining Schedule</A
+></DT
+><DT
+>5.13.3. <A
+HREF="whining.html#whining-query"
+>Whining Searches</A
+></DT
+><DT
+>5.13.4. <A
+HREF="whining.html#AEN2848"
+>Saving Your Changes</A
+></DT
+></DL
+></DD
+></DL
+></DIV
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="security-bugzilla.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="using-intro.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Bugzilla</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+>&nbsp;</TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Introduction</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/versions.html b/docs/en/html/versions.html
new file mode 100644
index 000000000..b2f5c11ab
--- /dev/null
+++ b/docs/en/html/versions.html
@@ -0,0 +1,173 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Versions</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Administering Bugzilla"
+HREF="administration.html"><LINK
+REL="PREVIOUS"
+TITLE="Components"
+HREF="components.html"><LINK
+REL="NEXT"
+TITLE="Milestones"
+HREF="milestones.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="components.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 3. Administering Bugzilla</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="milestones.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="versions"
+>3.6. Versions</A
+></H1
+><P
+>Versions are the revisions of the product, such as "Flinders
+ 3.1", "Flinders 95", and "Flinders 2000". Version is not a multi-select
+ field; the usual practice is to select the earliest version known to have
+ the bug.
+ </P
+><P
+>To create and edit Versions:</P
+><P
+></P
+><OL
+TYPE="1"
+><LI
+><P
+>From the "Edit product" screen, select "Edit Versions"</P
+></LI
+><LI
+><P
+>You will notice that the product already has the default
+ version "undefined". Click the "Add" link in the bottom right.</P
+></LI
+><LI
+><P
+>Enter the name of the Version. This field takes text only.
+ Then click the "Add" button.</P
+></LI
+></OL
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="components.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="milestones.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Components</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="administration.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Milestones</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/voting.html b/docs/en/html/voting.html
new file mode 100644
index 000000000..dc743db74
--- /dev/null
+++ b/docs/en/html/voting.html
@@ -0,0 +1,199 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Voting</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Administering Bugzilla"
+HREF="administration.html"><LINK
+REL="PREVIOUS"
+TITLE="Bug Status Workflow"
+HREF="bug_status_workflow.html"><LINK
+REL="NEXT"
+TITLE="Quips"
+HREF="quips.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="bug_status_workflow.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 3. Administering Bugzilla</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="quips.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="voting"
+>3.13. Voting</A
+></H1
+><P
+>Voting allows users to be given a pot of votes which they can allocate
+ to bugs, to indicate that they'd like them fixed.
+ This allows developers to gauge
+ user need for a particular enhancement or bugfix. By allowing bugs with
+ a certain number of votes to automatically move from "UNCONFIRMED" to
+ "CONFIRMED", users of the bug system can help high-priority bugs garner
+ attention so they don't sit for a long time awaiting triage.</P
+><P
+>To modify Voting settings:</P
+><P
+></P
+><OL
+TYPE="1"
+><LI
+><P
+>Navigate to the "Edit product" screen for the Product you
+ wish to modify</P
+></LI
+><LI
+><P
+><EM
+>Maximum Votes per person</EM
+>:
+ Setting this field to "0" disables voting.</P
+></LI
+><LI
+><P
+><EM
+>Maximum Votes a person can put on a single
+ bug</EM
+>:
+ It should probably be some number lower than the
+ "Maximum votes per person". Don't set this field to "0" if
+ "Maximum votes per person" is non-zero; that doesn't make
+ any sense.</P
+></LI
+><LI
+><P
+><EM
+>Number of votes a bug in this product needs to
+ automatically get out of the UNCONFIRMED state</EM
+>:
+ Setting this field to "0" disables the automatic move of
+ bugs from UNCONFIRMED to CONFIRMED.
+ </P
+></LI
+><LI
+><P
+>Once you have adjusted the values to your preference, click
+ "Update".</P
+></LI
+></OL
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="bug_status_workflow.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="quips.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Bug Status Workflow</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="administration.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Quips</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/html/whining.html b/docs/en/html/whining.html
new file mode 100644
index 000000000..cc6567066
--- /dev/null
+++ b/docs/en/html/whining.html
@@ -0,0 +1,530 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML
+><HEAD
+><TITLE
+>Whining</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
+REL="HOME"
+TITLE="The Bugzilla Guide - 4.0
+ Release"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Using Bugzilla"
+HREF="using.html"><LINK
+REL="PREVIOUS"
+TITLE="Flags"
+HREF="flags.html"><LINK
+REL="NEXT"
+TITLE="Customizing Bugzilla"
+HREF="customization.html"></HEAD
+><BODY
+CLASS="section"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>The Bugzilla Guide - 4.0
+ Release</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="flags.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 5. Using Bugzilla</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="customization.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="section"
+><H1
+CLASS="section"
+><A
+NAME="whining"
+>5.13. Whining</A
+></H1
+><P
+>&#13; Whining is a feature in Bugzilla that can regularly annoy users at
+ specified times. Using this feature, users can execute saved searches
+ at specific times (i.e. the 15th of the month at midnight) or at
+ regular intervals (i.e. every 15 minutes on Sundays). The results of the
+ searches are sent to the user, either as a single email or as one email
+ per bug, along with some descriptive text.
+ </P
+><DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Throughout this section it will be assumed that all users are members
+ of the bz_canusewhines group, membership in which is required in order
+ to use the Whining system. You can easily make all users members of
+ the bz_canusewhines group by setting the User RegExp to ".*" (without
+ the quotes).
+ </P
+><P
+>&#13; Also worth noting is the bz_canusewhineatothers group. Members of this
+ group can create whines for any user or group in Bugzilla using a
+ extended form of the whining interface. Features only available to
+ members of the bz_canusewhineatothers group will be noted in the
+ appropriate places.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; For whining to work, a special Perl script must be executed at regular
+ intervals. More information on this is available in
+ <A
+HREF="extraconfig.html#installation-whining"
+>Section 2.3.3</A
+>.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; This section does not cover the whineatnews.pl script. See
+ <A
+HREF="extraconfig.html#installation-whining-cron"
+>Section 2.3.2</A
+> for more information on
+ The Whining Cron.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="whining-overview"
+>5.13.1. The Event</A
+></H2
+><P
+>&#13; The whining system defines an "Event" as one or more queries being
+ executed at regular intervals, with the results of said queries (if
+ there are any) being emailed to the user. Events are created by
+ clicking on the "Add new event" button.
+ </P
+><P
+>&#13; Once a new event is created, the first thing to set is the "Email
+ subject line". The contents of this field will be used in the subject
+ line of every email generated by this event. In addition to setting a
+ subject, space is provided to enter some descriptive text that will be
+ included at the top of each message (to help you in understanding why
+ you received the email in the first place).
+ </P
+><P
+>&#13; The next step is to specify when the Event is to be run (the Schedule)
+ and what searches are to be performed (the Searches).
+ </P
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="whining-schedule"
+>5.13.2. Whining Schedule</A
+></H2
+><P
+>&#13; Each whining event is associated with zero or more schedules. A
+ schedule is used to specify when the query (specified below) is to be
+ run. A new event starts out with no schedules (which means it will
+ never run, as it is not scheduled to run). To add a schedule, press
+ the "Add a new schedule" button.
+ </P
+><P
+>&#13; Each schedule includes an interval, which you use to tell Bugzilla
+ when the event should be run. An event can be run on certain days of
+ the week, certain days of the month, during weekdays (defined as
+ Monday through Friday), or every day.
+ </P
+><DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Be careful if you set your event to run on the 29th, 30th, or 31st of
+ the month, as your event may not run exactly when expected. If you
+ want your event to run on the last day of the month, select "Last day
+ of the month" as the interval.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; Once you have specified the day(s) on which the event is to be run, you
+ should now specify the time at which the event is to be run. You can
+ have the event run at a certain hour on the specified day(s), or
+ every hour, half-hour, or quarter-hour on the specified day(s).
+ </P
+><P
+>&#13; If a single schedule does not execute an event as many times as you
+ would want, you can create another schedule for the same event. For
+ example, if you want to run an event on days whose numbers are
+ divisible by seven, you would need to add four schedules to the event,
+ setting the schedules to run on the 7th, 14th, 21st, and 28th (one day
+ per schedule) at whatever time (or times) you choose.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; If you are a member of the bz_canusewhineatothers group, then you
+ will be presented with another option: "Mail to". Using this you
+ can control who will receive the emails generated by this event. You
+ can choose to send the emails to a single user (identified by email
+ address) or a single group (identified by group name). To send to
+ multiple users or groups, create a new schedule for each additional
+ user/group.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="whining-query"
+>5.13.3. Whining Searches</A
+></H2
+><P
+>&#13; Each whining event is associated with zero or more searches. A search
+ is any saved search to be run as part of the specified schedule (see
+ above). You start out without any searches associated with the event
+ (which means that the event will not run, as there will never be any
+ results to return). To add a search, press the "Include search" button.
+ </P
+><P
+>&#13; The first field to examine in your newly added search is the Sort field.
+ Searches are run, and results included, in the order specified by the
+ Sort field. Searches with smaller Sort values will run before searches
+ with bigger Sort values.
+ </P
+><P
+>&#13; The next field to examine is the Search field. This is where you
+ choose the actual search that is to be run. Instead of defining search
+ parameters here, you are asked to choose from the list of saved
+ searches (the same list that appears at the bottom of every Bugzilla
+ page). You are only allowed to choose from searches that you have
+ saved yourself (the default saved search, "My Bugs", is not a valid
+ choice). If you do not have any saved searches, you can take this
+ opportunity to create one (see <A
+HREF="query.html#list"
+>Section 5.5.4</A
+>).
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; When running queries, the whining system acts as if you are the user
+ executing the query. This means that the whining system will ignore
+ bugs that match your query, but that you can not access.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+><P
+>&#13; Once you have chosen the saved search to be executed, give the query a
+ descriptive title. This title will appear in the email, above the
+ results of the query. If you choose "One message per bug", the query
+ title will appear at the top of each email that contains a bug matching
+ your query.
+ </P
+><P
+>&#13; Finally, decide if the results of the query should be sent in a single
+ email, or if each bug should appear in its own email.
+ </P
+><DIV
+CLASS="warning"
+><P
+></P
+><TABLE
+CLASS="warning"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/warning.gif"
+HSPACE="5"
+ALT="Warning"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; Think carefully before checking the "One message per bug" box. If
+ you create a query that matches thousands of bugs, you will receive
+ thousands of emails!
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+><DIV
+CLASS="section"
+><H2
+CLASS="section"
+><A
+NAME="AEN2848"
+>5.13.4. Saving Your Changes</A
+></H2
+><P
+>&#13; Once you have defined at least one schedule, and created at least one
+ query, go ahead and "Update/Commit". This will save your Event and make
+ it available for immediate execution.
+ </P
+><DIV
+CLASS="note"
+><P
+></P
+><TABLE
+CLASS="note"
+WIDTH="100%"
+BORDER="0"
+><TR
+><TD
+WIDTH="25"
+ALIGN="CENTER"
+VALIGN="TOP"
+><IMG
+SRC="../images/note.gif"
+HSPACE="5"
+ALT="Note"></TD
+><TD
+ALIGN="LEFT"
+VALIGN="TOP"
+><P
+>&#13; If you ever feel like deleting your event, you may do so using the
+ "Remove Event" button in the upper-right corner of each Event. You
+ can also modify an existing event, so long as you "Update/Commit"
+ after completing your modifications.
+ </P
+></TD
+></TR
+></TABLE
+></DIV
+></DIV
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="flags.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="customization.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Flags</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="using.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Customizing Bugzilla</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/docs/en/images/bzLifecycle.png b/docs/en/images/bzLifecycle.png
new file mode 100644
index 000000000..c38b57318
--- /dev/null
+++ b/docs/en/images/bzLifecycle.png
Binary files differ
diff --git a/docs/en/images/bzLifecycle.xml b/docs/en/images/bzLifecycle.xml
new file mode 100644
index 000000000..92ea28155
--- /dev/null
+++ b/docs/en/images/bzLifecycle.xml
@@ -0,0 +1,1724 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<dia:diagram xmlns:dia="http://www.lysator.liu.se/~alla/dia/">
+ <dia:diagramdata>
+ <dia:attribute name="background">
+ <dia:color val="#ffffff"/>
+ </dia:attribute>
+ <dia:attribute name="pagebreak">
+ <dia:color val="#000099"/>
+ </dia:attribute>
+ <dia:attribute name="paper">
+ <dia:composite type="paper">
+ <dia:attribute name="name">
+ <dia:string>#A4#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="tmargin">
+ <dia:real val="2.8222000598907471"/>
+ </dia:attribute>
+ <dia:attribute name="bmargin">
+ <dia:real val="2.8222000598907471"/>
+ </dia:attribute>
+ <dia:attribute name="lmargin">
+ <dia:real val="2.8222000598907471"/>
+ </dia:attribute>
+ <dia:attribute name="rmargin">
+ <dia:real val="2.8222000598907471"/>
+ </dia:attribute>
+ <dia:attribute name="is_portrait">
+ <dia:boolean val="true"/>
+ </dia:attribute>
+ <dia:attribute name="scaling">
+ <dia:real val="1"/>
+ </dia:attribute>
+ <dia:attribute name="fitto">
+ <dia:boolean val="false"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="grid">
+ <dia:composite type="grid">
+ <dia:attribute name="width_x">
+ <dia:real val="1"/>
+ </dia:attribute>
+ <dia:attribute name="width_y">
+ <dia:real val="1"/>
+ </dia:attribute>
+ <dia:attribute name="visible_x">
+ <dia:int val="1"/>
+ </dia:attribute>
+ <dia:attribute name="visible_y">
+ <dia:int val="1"/>
+ </dia:attribute>
+ <dia:composite type="color"/>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#d8e5e5"/>
+ </dia:attribute>
+ <dia:attribute name="guides">
+ <dia:composite type="guides">
+ <dia:attribute name="hguides"/>
+ <dia:attribute name="vguides"/>
+ </dia:composite>
+ </dia:attribute>
+ </dia:diagramdata>
+ <dia:layer name="Background" visible="true" active="true">
+ <dia:object type="Standard - Line" version="0" id="O0">
+ <dia:attribute name="obj_pos">
+ <dia:point val="25.05,2.6"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="24.6882,2.55;25.4118,5.0618"/>
+ </dia:attribute>
+ <dia:attribute name="conn_endpoints">
+ <dia:point val="25.05,2.6"/>
+ <dia:point val="25.05,4.95"/>
+ </dia:attribute>
+ <dia:attribute name="numcp">
+ <dia:int val="2"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow">
+ <dia:enum val="22"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_length">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_width">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:connections>
+ <dia:connection handle="1" to="O4" connection="2"/>
+ </dia:connections>
+ </dia:object>
+ <dia:object type="Standard - Line" version="0" id="O1">
+ <dia:attribute name="obj_pos">
+ <dia:point val="16,8.9"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="15.6382,8.85;16.3618,12.1118"/>
+ </dia:attribute>
+ <dia:attribute name="conn_endpoints">
+ <dia:point val="16,8.9"/>
+ <dia:point val="16,12"/>
+ </dia:attribute>
+ <dia:attribute name="numcp">
+ <dia:int val="1"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow">
+ <dia:enum val="22"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_length">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_width">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:connections>
+ <dia:connection handle="0" to="O5" connection="13"/>
+ <dia:connection handle="1" to="O6" connection="2"/>
+ </dia:connections>
+ </dia:object>
+ <dia:object type="Standard - Line" version="0" id="O2">
+ <dia:attribute name="obj_pos">
+ <dia:point val="16,14"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="15.6382,13.95;16.3618,17.1118"/>
+ </dia:attribute>
+ <dia:attribute name="conn_endpoints">
+ <dia:point val="16,14"/>
+ <dia:point val="16,17"/>
+ </dia:attribute>
+ <dia:attribute name="numcp">
+ <dia:int val="1"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow">
+ <dia:enum val="22"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_length">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_width">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:connections>
+ <dia:connection handle="0" to="O6" connection="13"/>
+ <dia:connection handle="1" to="O7" connection="2"/>
+ </dia:connections>
+ </dia:object>
+ <dia:object type="Standard - BezierLine" version="0" id="O3">
+ <dia:attribute name="obj_pos">
+ <dia:point val="16,18.9"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="15.6382,18.85;16.3618,22.2"/>
+ </dia:attribute>
+ <dia:attribute name="bez_points">
+ <dia:point val="16,18.9"/>
+ <dia:point val="16,22"/>
+ <dia:point val="16,19.2"/>
+ <dia:point val="16,22.2"/>
+ </dia:attribute>
+ <dia:attribute name="corner_types">
+ <dia:enum val="0"/>
+ <dia:enum val="0"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow">
+ <dia:enum val="22"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_length">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_width">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:connections>
+ <dia:connection handle="0" to="O7" connection="13"/>
+ <dia:connection handle="3" to="O8" connection="2"/>
+ </dia:connections>
+ </dia:object>
+ <dia:object type="Flowchart - Box" version="0" id="O4">
+ <dia:attribute name="obj_pos">
+ <dia:point val="21.81,4.95"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="21.76,4.9;28.34,7"/>
+ </dia:attribute>
+ <dia:attribute name="elem_corner">
+ <dia:point val="21.81,4.95"/>
+ </dia:attribute>
+ <dia:attribute name="elem_width">
+ <dia:real val="6.480000001490116"/>
+ </dia:attribute>
+ <dia:attribute name="elem_height">
+ <dia:real val="2"/>
+ </dia:attribute>
+ <dia:attribute name="border_width">
+ <dia:real val="0.10000000149011612"/>
+ </dia:attribute>
+ <dia:attribute name="show_background">
+ <dia:boolean val="true"/>
+ </dia:attribute>
+ <dia:attribute name="corner_radius">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:attribute name="padding">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>#UNCONFIRMED#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="80" name="Helvetica-Bold"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.80000000000000004"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="25.05,6.145"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="1"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Flowchart - Box" version="0" id="O5">
+ <dia:attribute name="obj_pos">
+ <dia:point val="13,7"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="12.95,6.95;19.05,8.95"/>
+ </dia:attribute>
+ <dia:attribute name="elem_corner">
+ <dia:point val="13,7"/>
+ </dia:attribute>
+ <dia:attribute name="elem_width">
+ <dia:real val="6"/>
+ </dia:attribute>
+ <dia:attribute name="elem_height">
+ <dia:real val="1.9000000000000001"/>
+ </dia:attribute>
+ <dia:attribute name="show_background">
+ <dia:boolean val="true"/>
+ </dia:attribute>
+ <dia:attribute name="corner_radius">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:attribute name="padding">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>#CONFIRMED#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="80" name="Helvetica-Bold"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.80000000000000004"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="16,8.145"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="1"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Flowchart - Box" version="0" id="O6">
+ <dia:attribute name="obj_pos">
+ <dia:point val="12.9625,12"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="12.9125,11.95;19.0875,14.05"/>
+ </dia:attribute>
+ <dia:attribute name="elem_corner">
+ <dia:point val="12.9625,12"/>
+ </dia:attribute>
+ <dia:attribute name="elem_width">
+ <dia:real val="6.0749999999999993"/>
+ </dia:attribute>
+ <dia:attribute name="elem_height">
+ <dia:real val="2"/>
+ </dia:attribute>
+ <dia:attribute name="show_background">
+ <dia:boolean val="true"/>
+ </dia:attribute>
+ <dia:attribute name="corner_radius">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:attribute name="padding">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>#IN_PROGRESS#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="80" name="Helvetica-Bold"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.80000000000000004"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="16,13.195"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="1"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Flowchart - Box" version="0" id="O7">
+ <dia:attribute name="obj_pos">
+ <dia:point val="13,17"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="12.95,16.95;19.05,18.95"/>
+ </dia:attribute>
+ <dia:attribute name="elem_corner">
+ <dia:point val="13,17"/>
+ </dia:attribute>
+ <dia:attribute name="elem_width">
+ <dia:real val="6"/>
+ </dia:attribute>
+ <dia:attribute name="elem_height">
+ <dia:real val="1.9000000000000001"/>
+ </dia:attribute>
+ <dia:attribute name="inner_color">
+ <dia:color val="#bfbfbf"/>
+ </dia:attribute>
+ <dia:attribute name="show_background">
+ <dia:boolean val="true"/>
+ </dia:attribute>
+ <dia:attribute name="corner_radius">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:attribute name="padding">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>#RESOLVED#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="80" name="Helvetica-Bold"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.80000000000000004"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="16,18.145"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="1"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Flowchart - Box" version="0" id="O8">
+ <dia:attribute name="obj_pos">
+ <dia:point val="13,22.2"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="12.95,22.15;19.05,24.15"/>
+ </dia:attribute>
+ <dia:attribute name="elem_corner">
+ <dia:point val="13,22.2"/>
+ </dia:attribute>
+ <dia:attribute name="elem_width">
+ <dia:real val="6"/>
+ </dia:attribute>
+ <dia:attribute name="elem_height">
+ <dia:real val="1.9000000000000001"/>
+ </dia:attribute>
+ <dia:attribute name="inner_color">
+ <dia:color val="#bfbfbf"/>
+ </dia:attribute>
+ <dia:attribute name="show_background">
+ <dia:boolean val="true"/>
+ </dia:attribute>
+ <dia:attribute name="corner_radius">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:attribute name="padding">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>#VERIFIED#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="80" name="Helvetica-Bold"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.80000000000000004"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="16,23.345"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="1"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Standard - Text" version="1" id="O9">
+ <dia:attribute name="obj_pos">
+ <dia:point val="17.75,5.0625"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="17.75,4.6175;21.6275,5.775"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>#Bug determined
+to be present#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="0" name="Helvetica"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.59999999999999998"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="17.75,5.0625"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="0"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Standard - Text" version="1" id="O10">
+ <dia:attribute name="obj_pos">
+ <dia:point val="16.3365,10.4"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="16.3365,9.955;21.394,11.1125"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>#Developer is working
+on the bug#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="0" name="Helvetica"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.59999999999999998"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="16.3365,10.4"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="0"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Flowchart - Box" version="0" id="O11">
+ <dia:attribute name="obj_pos">
+ <dia:point val="-1.03711,14.8573"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="-1.08711,14.8073;4.71289,20.1073"/>
+ </dia:attribute>
+ <dia:attribute name="elem_corner">
+ <dia:point val="-1.03711,14.8573"/>
+ </dia:attribute>
+ <dia:attribute name="elem_width">
+ <dia:real val="5.6999999999999993"/>
+ </dia:attribute>
+ <dia:attribute name="elem_height">
+ <dia:real val="5.1999999999999993"/>
+ </dia:attribute>
+ <dia:attribute name="inner_color">
+ <dia:color val="#bfbfbf"/>
+ </dia:attribute>
+ <dia:attribute name="show_background">
+ <dia:boolean val="true"/>
+ </dia:attribute>
+ <dia:attribute name="corner_radius">
+ <dia:real val="0.10000000000000001"/>
+ </dia:attribute>
+ <dia:attribute name="padding">
+ <dia:real val="0.14999999999999999"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>#Possible resolutions:
+ FIXED
+ DUPLICATE
+ WONTFIX
+ WORKSFORME
+ INVALID#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="0" name="Helvetica"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.59999999999999998"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="-0.93711,16.1023"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="0"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Standard - Line" version="0" id="O12">
+ <dia:attribute name="obj_pos">
+ <dia:point val="4.66289,18.7573"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="4.63788,18.7286;13.1714,18.7823"/>
+ </dia:attribute>
+ <dia:attribute name="conn_endpoints">
+ <dia:point val="4.66289,18.7573"/>
+ <dia:point val="13.1464,18.7536"/>
+ </dia:attribute>
+ <dia:attribute name="numcp">
+ <dia:int val="1"/>
+ </dia:attribute>
+ <dia:attribute name="line_width">
+ <dia:real val="0.050000000000000003"/>
+ </dia:attribute>
+ <dia:attribute name="line_style">
+ <dia:enum val="4"/>
+ </dia:attribute>
+ <dia:connections>
+ <dia:connection handle="0" to="O11" connection="10"/>
+ <dia:connection handle="1" to="O7" connection="11"/>
+ </dia:connections>
+ </dia:object>
+ <dia:object type="Standard - Text" version="1" id="O13">
+ <dia:attribute name="obj_pos">
+ <dia:point val="16.4,15.6"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="16.4,15.155;19.8425,15.7125"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>#Fix checked in#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="0" name="Helvetica"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.59999999999999998"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="16.4,15.6"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="0"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Standard - Text" version="1" id="O14">
+ <dia:attribute name="obj_pos">
+ <dia:point val="8.2865,16.95"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="8.2865,16.505;12.294,17.6625"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>#QA not satisfied
+with the solution#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="0" name="Helvetica"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.59999999999999998"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="8.2865,16.95"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="0"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Standard - Text" version="1" id="O15">
+ <dia:attribute name="obj_pos">
+ <dia:point val="16.2865,20.5"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="16.2865,20.055;20.6865,21.2125"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>#QA verifies that
+the solution works#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="0" name="Helvetica"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.59999999999999998"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="16.2865,20.5"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="0"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Standard - Text" version="1" id="O16">
+ <dia:attribute name="obj_pos">
+ <dia:point val="6.4365,22.7"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="6.4365,22.255;12.494,22.8125"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>#Fix turns out to be wrong#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="0" name="Helvetica"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.59999999999999998"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="6.4365,22.7"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="0"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Standard - Text" version="1" id="O17">
+ <dia:attribute name="obj_pos">
+ <dia:point val="22.8,22.15"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="22.8,21.705;27.85,22.8625"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>#Bug is reopened,
+was never confirmed#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="0" name="Helvetica"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.59999999999999998"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="22.8,22.15"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="0"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Standard - Text" version="1" id="O18">
+ <dia:attribute name="obj_pos">
+ <dia:point val="16,7.95"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="16,7.355;16,8.1"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="0" name="Helvetica"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.80000000000000004"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="16,7.95"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="0"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
+ <dia:connections>
+ <dia:connection handle="0" to="O5" connection="16"/>
+ </dia:connections>
+ </dia:object>
+ <dia:object type="Standard - Text" version="1" id="O19">
+ <dia:attribute name="obj_pos">
+ <dia:point val="16.9,10.5125"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="16.9,9.9175;16.9,10.6625"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="0" name="Helvetica"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.80000000000000004"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="16.9,10.5125"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="0"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Standard - Text" version="1" id="O20">
+ <dia:attribute name="obj_pos">
+ <dia:point val="19.55,15.6125"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="19.55,15.0175;19.55,15.7625"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="0" name="Helvetica"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.80000000000000004"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="19.55,15.6125"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="0"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Standard - Text" version="1" id="O21">
+ <dia:attribute name="obj_pos">
+ <dia:point val="20.3,20.8625"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="20.3,20.2675;20.3,21.0125"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="0" name="Helvetica"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.80000000000000004"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="20.3,20.8625"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="0"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Standard - Text" version="1" id="O22">
+ <dia:attribute name="obj_pos">
+ <dia:point val="20.2,20.8625"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="20.2,20.2675;20.2,21.0125"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="0" name="Helvetica"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.80000000000000004"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="20.2,20.8625"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="0"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Standard - Text" version="1" id="O23">
+ <dia:attribute name="obj_pos">
+ <dia:point val="19.75,21.0625"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="19.75,20.4675;19.75,21.2125"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="0" name="Helvetica"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.80000000000000004"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="19.75,21.0625"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="0"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Standard - Text" version="1" id="O24">
+ <dia:attribute name="obj_pos">
+ <dia:point val="12.8,1.5125"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="12.8,0.9175;12.8,1.6625"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="0" name="Helvetica"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.80000000000000004"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="12.8,1.5125"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="0"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Standard - Text" version="1" id="O25">
+ <dia:attribute name="obj_pos">
+ <dia:point val="13.7,15.7625"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="13.7,15.1675;13.7,15.9125"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="0" name="Helvetica"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.80000000000000004"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="13.7,15.7625"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="0"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Standard - Text" version="1" id="O26">
+ <dia:attribute name="obj_pos">
+ <dia:point val="9.65,11.7125"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="9.65,11.2675;13.555,12.4247"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>#Developer stops
+work on bug#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="0" name="Helvetica"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.59972222571740919"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="9.65,11.7125"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="0"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Standard - Text" version="1" id="O27">
+ <dia:attribute name="obj_pos">
+ <dia:point val="20.55,19.0625"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="20.55,18.6175;26.5875,19.7747"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>#Bug is not fixable
+(e.g because it is invalid)#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="0" name="Helvetica"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.59972222571740919"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="20.55,19.0625"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="0"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Standard - ZigZagLine" version="1" id="O28">
+ <dia:attribute name="obj_pos">
+ <dia:point val="12.9625,13"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="9.3,8.0632;13.1118,13.05"/>
+ </dia:attribute>
+ <dia:attribute name="orth_points">
+ <dia:point val="12.9625,13"/>
+ <dia:point val="9.35,13"/>
+ <dia:point val="9.35,8.425"/>
+ <dia:point val="13,8.425"/>
+ </dia:attribute>
+ <dia:attribute name="orth_orient">
+ <dia:enum val="0"/>
+ <dia:enum val="1"/>
+ <dia:enum val="0"/>
+ </dia:attribute>
+ <dia:attribute name="autorouting">
+ <dia:boolean val="false"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow">
+ <dia:enum val="22"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_length">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_width">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:connections>
+ <dia:connection handle="0" to="O6" connection="7"/>
+ <dia:connection handle="1" to="O5" connection="9"/>
+ </dia:connections>
+ </dia:object>
+ <dia:object type="Standard - ZigZagLine" version="1" id="O29">
+ <dia:attribute name="obj_pos">
+ <dia:point val="13,17.95"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="7.65,7.5882;13.1118,18"/>
+ </dia:attribute>
+ <dia:attribute name="orth_points">
+ <dia:point val="13,17.95"/>
+ <dia:point val="7.7,17.95"/>
+ <dia:point val="7.7,7.95"/>
+ <dia:point val="13,7.95"/>
+ </dia:attribute>
+ <dia:attribute name="orth_orient">
+ <dia:enum val="0"/>
+ <dia:enum val="1"/>
+ <dia:enum val="0"/>
+ </dia:attribute>
+ <dia:attribute name="autorouting">
+ <dia:boolean val="false"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow">
+ <dia:enum val="22"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_length">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_width">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:connections>
+ <dia:connection handle="0" to="O7" connection="7"/>
+ <dia:connection handle="1" to="O5" connection="7"/>
+ </dia:connections>
+ </dia:object>
+ <dia:object type="Standard - ZigZagLine" version="1" id="O30">
+ <dia:attribute name="obj_pos">
+ <dia:point val="13,23.15"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="5.9,7.1132;13.1118,23.2"/>
+ </dia:attribute>
+ <dia:attribute name="orth_points">
+ <dia:point val="13,23.15"/>
+ <dia:point val="5.95,23.15"/>
+ <dia:point val="5.95,7.475"/>
+ <dia:point val="13,7.475"/>
+ </dia:attribute>
+ <dia:attribute name="orth_orient">
+ <dia:enum val="0"/>
+ <dia:enum val="1"/>
+ <dia:enum val="0"/>
+ </dia:attribute>
+ <dia:attribute name="autorouting">
+ <dia:boolean val="false"/>
+ </dia:attribute>
+ <dia:attribute name="line_style">
+ <dia:enum val="4"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow">
+ <dia:enum val="22"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_length">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_width">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:connections>
+ <dia:connection handle="0" to="O8" connection="7"/>
+ <dia:connection handle="1" to="O5" connection="5"/>
+ </dia:connections>
+ </dia:object>
+ <dia:object type="Standard - ZigZagLine" version="1" id="O31">
+ <dia:attribute name="obj_pos">
+ <dia:point val="19.0498,7.95"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="18.8882,7.9;22.7,17.8368"/>
+ </dia:attribute>
+ <dia:attribute name="orth_points">
+ <dia:point val="19.0498,7.95"/>
+ <dia:point val="22.65,7.95"/>
+ <dia:point val="22.65,17.475"/>
+ <dia:point val="19,17.475"/>
+ </dia:attribute>
+ <dia:attribute name="orth_orient">
+ <dia:enum val="0"/>
+ <dia:enum val="1"/>
+ <dia:enum val="0"/>
+ </dia:attribute>
+ <dia:attribute name="autorouting">
+ <dia:boolean val="false"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow">
+ <dia:enum val="22"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_length">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_width">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:connections>
+ <dia:connection handle="0" to="O5" connection="16"/>
+ <dia:connection handle="1" to="O7" connection="6"/>
+ </dia:connections>
+ </dia:object>
+ <dia:object type="Standard - ZigZagLine" version="1" id="O32">
+ <dia:attribute name="obj_pos">
+ <dia:point val="26.67,6.95"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="18.8882,6.9;26.72,18.7868"/>
+ </dia:attribute>
+ <dia:attribute name="orth_points">
+ <dia:point val="26.67,6.95"/>
+ <dia:point val="26.67,18.425"/>
+ <dia:point val="19,18.425"/>
+ </dia:attribute>
+ <dia:attribute name="orth_orient">
+ <dia:enum val="1"/>
+ <dia:enum val="0"/>
+ </dia:attribute>
+ <dia:attribute name="autorouting">
+ <dia:boolean val="true"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow">
+ <dia:enum val="22"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_length">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_width">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:connections>
+ <dia:connection handle="0" to="O4" connection="14"/>
+ <dia:connection handle="1" to="O7" connection="10"/>
+ </dia:connections>
+ </dia:object>
+ <dia:object type="Standard - ZigZagLine" version="1" id="O33">
+ <dia:attribute name="obj_pos">
+ <dia:point val="23.43,6.95"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="18.9257,6.9;23.48,13.3618"/>
+ </dia:attribute>
+ <dia:attribute name="orth_points">
+ <dia:point val="23.43,6.95"/>
+ <dia:point val="23.43,13"/>
+ <dia:point val="19.0375,13"/>
+ </dia:attribute>
+ <dia:attribute name="orth_orient">
+ <dia:enum val="1"/>
+ <dia:enum val="0"/>
+ </dia:attribute>
+ <dia:attribute name="autorouting">
+ <dia:boolean val="true"/>
+ </dia:attribute>
+ <dia:attribute name="line_style">
+ <dia:enum val="4"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow">
+ <dia:enum val="22"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_length">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_width">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:connections>
+ <dia:connection handle="0" to="O4" connection="12"/>
+ <dia:connection handle="1" to="O6" connection="8"/>
+ </dia:connections>
+ </dia:object>
+ <dia:object type="Standard - ZigZagLine" version="1" id="O34">
+ <dia:attribute name="obj_pos">
+ <dia:point val="19,17.95"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="18.95,6.8382;25.4118,18"/>
+ </dia:attribute>
+ <dia:attribute name="orth_points">
+ <dia:point val="19,17.95"/>
+ <dia:point val="25.05,17.95"/>
+ <dia:point val="25.05,6.95"/>
+ </dia:attribute>
+ <dia:attribute name="orth_orient">
+ <dia:enum val="0"/>
+ <dia:enum val="1"/>
+ </dia:attribute>
+ <dia:attribute name="autorouting">
+ <dia:boolean val="true"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow">
+ <dia:enum val="22"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_length">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_width">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:connections>
+ <dia:connection handle="0" to="O7" connection="8"/>
+ <dia:connection handle="1" to="O4" connection="13"/>
+ </dia:connections>
+ </dia:object>
+ <dia:object type="Standard - ZigZagLine" version="1" id="O35">
+ <dia:attribute name="obj_pos">
+ <dia:point val="19,23.15"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="19,6.69175;28.5054,23.2"/>
+ </dia:attribute>
+ <dia:attribute name="orth_points">
+ <dia:point val="19,23.15"/>
+ <dia:point val="19,23.15"/>
+ <dia:point val="28.1436,23.15"/>
+ <dia:point val="28.1436,6.80355"/>
+ </dia:attribute>
+ <dia:attribute name="orth_orient">
+ <dia:enum val="1"/>
+ <dia:enum val="0"/>
+ <dia:enum val="1"/>
+ </dia:attribute>
+ <dia:attribute name="autorouting">
+ <dia:boolean val="false"/>
+ </dia:attribute>
+ <dia:attribute name="line_style">
+ <dia:enum val="4"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow">
+ <dia:enum val="22"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_length">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_width">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:connections>
+ <dia:connection handle="0" to="O8" connection="8"/>
+ <dia:connection handle="1" to="O4" connection="15"/>
+ </dia:connections>
+ </dia:object>
+ <dia:object type="Standard - Line" version="0" id="O36">
+ <dia:attribute name="obj_pos">
+ <dia:point val="16,0.65"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="15.6382,0.6;16.3618,7.1118"/>
+ </dia:attribute>
+ <dia:attribute name="conn_endpoints">
+ <dia:point val="16,0.65"/>
+ <dia:point val="16,7"/>
+ </dia:attribute>
+ <dia:attribute name="numcp">
+ <dia:int val="1"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow">
+ <dia:enum val="22"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_length">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_width">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:connections>
+ <dia:connection handle="1" to="O5" connection="2"/>
+ </dia:connections>
+ </dia:object>
+ <dia:object type="Standard - ZigZagLine" version="1" id="O37">
+ <dia:attribute name="obj_pos">
+ <dia:point val="21.81,5.95"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="17.1382,5.9;21.86,7.1118"/>
+ </dia:attribute>
+ <dia:attribute name="orth_points">
+ <dia:point val="21.81,5.95"/>
+ <dia:point val="17.5,5.95"/>
+ <dia:point val="17.5,7"/>
+ </dia:attribute>
+ <dia:attribute name="orth_orient">
+ <dia:enum val="0"/>
+ <dia:enum val="1"/>
+ </dia:attribute>
+ <dia:attribute name="autorouting">
+ <dia:boolean val="true"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow">
+ <dia:enum val="22"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_length">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_width">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:connections>
+ <dia:connection handle="0" to="O4" connection="7"/>
+ <dia:connection handle="1" to="O5" connection="3"/>
+ </dia:connections>
+ </dia:object>
+ <dia:object type="Standard - Text" version="1" id="O38">
+ <dia:attribute name="obj_pos">
+ <dia:point val="20.65,1.05"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="20.65,0.605;28.575,2.36194"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>#Bug is filed by a non-empowered
+user in a product where the
+UNCONFIRMED state is enabled#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="0" name="Helvetica"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.59972222571740919"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="20.65,1.05"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="0"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Standard - Text" version="1" id="O39">
+ <dia:attribute name="obj_pos">
+ <dia:point val="26.65,1.5"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="26.65,0.905;26.65,1.65"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="0" name="Helvetica"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.80000000000000004"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="26.65,1.5"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="0"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Standard - Text" version="1" id="O40">
+ <dia:attribute name="obj_pos">
+ <dia:point val="21.2,5.2"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="21.2,4.605;21.2,5.35"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="0" name="Helvetica"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.80000000000000004"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="21.2,5.2"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="0"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Standard - Text" version="1" id="O41">
+ <dia:attribute name="obj_pos">
+ <dia:point val="12.95,10.85"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="12.95,10.255;12.95,11"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="0" name="Helvetica"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.80000000000000004"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="12.95,10.85"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="0"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Standard - Text" version="1" id="O42">
+ <dia:attribute name="obj_pos">
+ <dia:point val="18.95,15.45"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="18.95,14.855;18.95,15.6"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="0" name="Helvetica"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.80000000000000004"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="18.95,15.45"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="0"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Standard - Text" version="1" id="O43">
+ <dia:attribute name="obj_pos">
+ <dia:point val="21,15.55"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="21,14.955;21,15.7"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="0" name="Helvetica"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.80000000000000004"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="21,15.55"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="0"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Standard - Text" version="1" id="O44">
+ <dia:attribute name="obj_pos">
+ <dia:point val="13.6,20.7"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="13.6,20.105;13.6,20.85"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="0" name="Helvetica"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.80000000000000004"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="13.6,20.7"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="0"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Standard - Text" version="1" id="O45">
+ <dia:attribute name="obj_pos">
+ <dia:point val="24.35,9.7"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="24.35,9.105;24.35,9.85"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="0" name="Helvetica"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.80000000000000004"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="24.35,9.7"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="0"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Standard - Text" version="1" id="O46">
+ <dia:attribute name="obj_pos">
+ <dia:point val="23.85,9.95"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="23.85,9.355;23.85,10.1"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="0" name="Helvetica"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.80000000000000004"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="23.85,9.95"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="0"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
+ </dia:object>
+ </dia:layer>
+</dia:diagram>
diff --git a/docs/en/images/callouts/1.gif b/docs/en/images/callouts/1.gif
new file mode 100644
index 000000000..79fd388a8
--- /dev/null
+++ b/docs/en/images/callouts/1.gif
Binary files differ
diff --git a/docs/en/images/callouts/2.gif b/docs/en/images/callouts/2.gif
new file mode 100644
index 000000000..b9be050e4
--- /dev/null
+++ b/docs/en/images/callouts/2.gif
Binary files differ
diff --git a/docs/en/images/callouts/3.gif b/docs/en/images/callouts/3.gif
new file mode 100644
index 000000000..73635e3f7
--- /dev/null
+++ b/docs/en/images/callouts/3.gif
Binary files differ
diff --git a/docs/en/images/caution.gif b/docs/en/images/caution.gif
new file mode 100644
index 000000000..a48223013
--- /dev/null
+++ b/docs/en/images/caution.gif
Binary files differ
diff --git a/docs/en/images/note.gif b/docs/en/images/note.gif
new file mode 100644
index 000000000..613bc7f70
--- /dev/null
+++ b/docs/en/images/note.gif
Binary files differ
diff --git a/docs/en/images/tip.gif b/docs/en/images/tip.gif
new file mode 100644
index 000000000..c8d5ae9bd
--- /dev/null
+++ b/docs/en/images/tip.gif
Binary files differ
diff --git a/docs/en/images/warning.gif b/docs/en/images/warning.gif
new file mode 100644
index 000000000..693ffc3e8
--- /dev/null
+++ b/docs/en/images/warning.gif
Binary files differ
diff --git a/docs/en/pdf/Bugzilla-Guide.pdf b/docs/en/pdf/Bugzilla-Guide.pdf
new file mode 100644
index 000000000..0564b3ddc
--- /dev/null
+++ b/docs/en/pdf/Bugzilla-Guide.pdf
@@ -0,0 +1,30483 @@
+%PDF-1.4
+1 0 obj
+<< /S /GoTo /D (1.0) >>
+endobj
+4 0 obj
+(The Bugzilla Guide 4.0 Release)
+endobj
+5 0 obj
+<< /S /GoTo /D (2.0) >>
+endobj
+8 0 obj
+(Table of Contents)
+endobj
+9 0 obj
+<< /S /GoTo /D (3.0) >>
+endobj
+12 0 obj
+(List of Figures)
+endobj
+13 0 obj
+<< /S /GoTo /D (4.0) >>
+endobj
+16 0 obj
+(Chapter 1. About This Guide)
+endobj
+17 0 obj
+<< /S /GoTo /D (4.1.1) >>
+endobj
+20 0 obj
+(1.1. Copyright Information)
+endobj
+21 0 obj
+<< /S /GoTo /D (4.2.1) >>
+endobj
+24 0 obj
+(1.2. Disclaimer)
+endobj
+25 0 obj
+<< /S /GoTo /D (4.3.1) >>
+endobj
+28 0 obj
+(1.3. New Versions)
+endobj
+29 0 obj
+<< /S /GoTo /D (4.4.1) >>
+endobj
+32 0 obj
+(1.4. Credits)
+endobj
+33 0 obj
+<< /S /GoTo /D (4.5.1) >>
+endobj
+36 0 obj
+(1.5. Document Conventions)
+endobj
+37 0 obj
+<< /S /GoTo /D (5.0) >>
+endobj
+40 0 obj
+(Chapter 2. Installing Bugzilla)
+endobj
+41 0 obj
+<< /S /GoTo /D (5.6.1) >>
+endobj
+44 0 obj
+(2.1. Installation)
+endobj
+45 0 obj
+<< /S /GoTo /D (5.6.1.2) >>
+endobj
+48 0 obj
+(2.1.1. Perl)
+endobj
+49 0 obj
+<< /S /GoTo /D (5.6.2.2) >>
+endobj
+52 0 obj
+(2.1.2. Database Engine)
+endobj
+53 0 obj
+<< /S /GoTo /D (5.6.2.1.3) >>
+endobj
+56 0 obj
+(2.1.2.1. MySQL)
+endobj
+57 0 obj
+<< /S /GoTo /D (5.6.2.2.3) >>
+endobj
+60 0 obj
+(2.1.2.2. PostgreSQL)
+endobj
+61 0 obj
+<< /S /GoTo /D (5.6.2.3.3) >>
+endobj
+64 0 obj
+(2.1.2.3. Oracle)
+endobj
+65 0 obj
+<< /S /GoTo /D (5.6.3.2) >>
+endobj
+68 0 obj
+(2.1.3. Web Server)
+endobj
+69 0 obj
+<< /S /GoTo /D (5.6.4.2) >>
+endobj
+72 0 obj
+(2.1.4. Bugzilla)
+endobj
+73 0 obj
+<< /S /GoTo /D (5.6.5.2) >>
+endobj
+76 0 obj
+(2.1.5. Perl Modules)
+endobj
+77 0 obj
+<< /S /GoTo /D (5.6.6.2) >>
+endobj
+80 0 obj
+(2.1.6. Mail Transfer Agent \(MTA\))
+endobj
+81 0 obj
+<< /S /GoTo /D (5.6.7.2) >>
+endobj
+84 0 obj
+(2.1.7. Installing Bugzilla on modperl)
+endobj
+85 0 obj
+<< /S /GoTo /D (5.7.1) >>
+endobj
+88 0 obj
+(2.2. Configuration)
+endobj
+89 0 obj
+<< /S /GoTo /D (5.7.8.2) >>
+endobj
+92 0 obj
+(2.2.1. localconfig)
+endobj
+93 0 obj
+<< /S /GoTo /D (5.7.9.2) >>
+endobj
+96 0 obj
+(2.2.2. Database Server)
+endobj
+97 0 obj
+<< /S /GoTo /D (5.7.9.4.3) >>
+endobj
+100 0 obj
+(2.2.2.1. Bugzilla Database Schema)
+endobj
+101 0 obj
+<< /S /GoTo /D (5.7.9.5.3) >>
+endobj
+104 0 obj
+(2.2.2.2. MySQL)
+endobj
+105 0 obj
+<< /S /GoTo /D (5.7.9.5.1.4) >>
+endobj
+108 0 obj
+(2.2.2.2.1. Allow large attachments and many comments)
+endobj
+109 0 obj
+<< /S /GoTo /D (5.7.9.5.2.4) >>
+endobj
+112 0 obj
+(2.2.2.2.2. Allow small words in fulltext indexes)
+endobj
+113 0 obj
+<< /S /GoTo /D (5.7.9.5.3.4) >>
+endobj
+116 0 obj
+(2.2.2.2.3. Add a user to MySQL)
+endobj
+117 0 obj
+<< /S /GoTo /D (5.7.9.5.4.4) >>
+endobj
+120 0 obj
+(2.2.2.2.4. Permit attachments table to grow beyond 4GB)
+endobj
+121 0 obj
+<< /S /GoTo /D (5.7.9.6.3) >>
+endobj
+124 0 obj
+(2.2.2.3. PostgreSQL)
+endobj
+125 0 obj
+<< /S /GoTo /D (5.7.9.6.5.4) >>
+endobj
+128 0 obj
+(2.2.2.3.1. Add a User to PostgreSQL)
+endobj
+129 0 obj
+<< /S /GoTo /D (5.7.9.6.6.4) >>
+endobj
+132 0 obj
+(2.2.2.3.2. Configure PostgreSQL)
+endobj
+133 0 obj
+<< /S /GoTo /D (5.7.9.7.3) >>
+endobj
+136 0 obj
+(2.2.2.4. Oracle)
+endobj
+137 0 obj
+<< /S /GoTo /D (5.7.9.7.7.4) >>
+endobj
+140 0 obj
+(2.2.2.4.1. Create a New Tablespace)
+endobj
+141 0 obj
+<< /S /GoTo /D (5.7.9.7.8.4) >>
+endobj
+144 0 obj
+(2.2.2.4.2. Add a User to Oracle)
+endobj
+145 0 obj
+<< /S /GoTo /D (5.7.9.7.9.4) >>
+endobj
+148 0 obj
+(2.2.2.4.3. Configure the Web Server)
+endobj
+149 0 obj
+<< /S /GoTo /D (5.7.10.2) >>
+endobj
+152 0 obj
+(2.2.3. checksetup.pl)
+endobj
+153 0 obj
+<< /S /GoTo /D (5.7.11.2) >>
+endobj
+156 0 obj
+(2.2.4. Web server)
+endobj
+157 0 obj
+<< /S /GoTo /D (5.7.11.8.3) >>
+endobj
+160 0 obj
+(2.2.4.1. Bugzilla using Apache)
+endobj
+161 0 obj
+<< /S /GoTo /D (5.7.11.8.10.4) >>
+endobj
+164 0 obj
+(2.2.4.1.1. Apache httpd with modcgi)
+endobj
+165 0 obj
+<< /S /GoTo /D (5.7.11.8.11.4) >>
+endobj
+168 0 obj
+(2.2.4.1.2. Apache httpd with modperl)
+endobj
+169 0 obj
+<< /S /GoTo /D (5.7.11.9.3) >>
+endobj
+172 0 obj
+(2.2.4.2. Microsoft Internet Information Services)
+endobj
+173 0 obj
+<< /S /GoTo /D (5.7.12.2) >>
+endobj
+176 0 obj
+(2.2.5. Bugzilla)
+endobj
+177 0 obj
+<< /S /GoTo /D (5.8.1) >>
+endobj
+180 0 obj
+(2.3. Optional Additional Configuration)
+endobj
+181 0 obj
+<< /S /GoTo /D (5.8.13.2) >>
+endobj
+184 0 obj
+(2.3.1. Bug Graphs)
+endobj
+185 0 obj
+<< /S /GoTo /D (5.8.14.2) >>
+endobj
+188 0 obj
+(2.3.2. The Whining Cron)
+endobj
+189 0 obj
+<< /S /GoTo /D (5.8.15.2) >>
+endobj
+192 0 obj
+(2.3.3. Whining)
+endobj
+193 0 obj
+<< /S /GoTo /D (5.8.16.2) >>
+endobj
+196 0 obj
+(2.3.4. Serving Alternate Formats with the right MIME type)
+endobj
+197 0 obj
+<< /S /GoTo /D (5.9.1) >>
+endobj
+200 0 obj
+(2.4. Multiple Bugzilla databases with a single installation)
+endobj
+201 0 obj
+<< /S /GoTo /D (5.10.1) >>
+endobj
+204 0 obj
+(2.5. OSSpecific Installation Notes)
+endobj
+205 0 obj
+<< /S /GoTo /D (5.10.17.2) >>
+endobj
+208 0 obj
+(2.5.1. Microsoft Windows)
+endobj
+209 0 obj
+<< /S /GoTo /D (5.10.17.10.3) >>
+endobj
+212 0 obj
+(2.5.1.1. Win32 Perl)
+endobj
+213 0 obj
+<< /S /GoTo /D (5.10.17.11.3) >>
+endobj
+216 0 obj
+(2.5.1.2. Perl Modules on Win32)
+endobj
+217 0 obj
+<< /S /GoTo /D (5.10.17.12.3) >>
+endobj
+220 0 obj
+(2.5.1.3. Serving the web pages)
+endobj
+221 0 obj
+<< /S /GoTo /D (5.10.17.13.3) >>
+endobj
+224 0 obj
+(2.5.1.4. Sending Email)
+endobj
+225 0 obj
+<< /S /GoTo /D (5.10.18.2) >>
+endobj
+228 0 obj
+(2.5.2. Mac OS X)
+endobj
+229 0 obj
+<< /S /GoTo /D (5.10.18.14.3) >>
+endobj
+232 0 obj
+(2.5.2.1. Sendmail)
+endobj
+233 0 obj
+<< /S /GoTo /D (5.10.18.15.3) >>
+endobj
+236 0 obj
+(2.5.2.2. Libraries \046 Perl Modules on Mac OS X)
+endobj
+237 0 obj
+<< /S /GoTo /D (5.10.19.2) >>
+endobj
+240 0 obj
+(2.5.3. Linux Distributions)
+endobj
+241 0 obj
+<< /S /GoTo /D (5.11.1) >>
+endobj
+244 0 obj
+(2.6. UNIX \(nonroot\) Installation Notes)
+endobj
+245 0 obj
+<< /S /GoTo /D (5.11.20.2) >>
+endobj
+248 0 obj
+(2.6.1. Introduction)
+endobj
+249 0 obj
+<< /S /GoTo /D (5.11.21.2) >>
+endobj
+252 0 obj
+(2.6.2. MySQL)
+endobj
+253 0 obj
+<< /S /GoTo /D (5.11.21.16.3) >>
+endobj
+256 0 obj
+(2.6.2.1. Running MySQL as NonRoot)
+endobj
+257 0 obj
+<< /S /GoTo /D (5.11.21.16.12.4) >>
+endobj
+260 0 obj
+(2.6.2.1.1. The Custom Configuration Method)
+endobj
+261 0 obj
+<< /S /GoTo /D (5.11.21.16.13.4) >>
+endobj
+264 0 obj
+(2.6.2.1.2. The Custom Built Method)
+endobj
+265 0 obj
+<< /S /GoTo /D (5.11.21.16.14.4) >>
+endobj
+268 0 obj
+(2.6.2.1.3. Starting the Server)
+endobj
+269 0 obj
+<< /S /GoTo /D (5.11.22.2) >>
+endobj
+272 0 obj
+(2.6.3. Perl)
+endobj
+273 0 obj
+<< /S /GoTo /D (5.11.23.2) >>
+endobj
+276 0 obj
+(2.6.4. Perl Modules)
+endobj
+277 0 obj
+<< /S /GoTo /D (5.11.24.2) >>
+endobj
+280 0 obj
+(2.6.5. HTTP Server)
+endobj
+281 0 obj
+<< /S /GoTo /D (5.11.24.17.3) >>
+endobj
+284 0 obj
+(2.6.5.1. Running Apache as NonRoot)
+endobj
+285 0 obj
+<< /S /GoTo /D (5.11.25.2) >>
+endobj
+288 0 obj
+(2.6.6. Bugzilla)
+endobj
+289 0 obj
+<< /S /GoTo /D (5.11.25.18.3) >>
+endobj
+292 0 obj
+(2.6.6.1. suexec or shared hosting)
+endobj
+293 0 obj
+<< /S /GoTo /D (5.12.1) >>
+endobj
+296 0 obj
+(2.7. Upgrading to New Releases)
+endobj
+297 0 obj
+<< /S /GoTo /D (5.12.26.2) >>
+endobj
+300 0 obj
+(2.7.1. Before You Upgrade)
+endobj
+301 0 obj
+<< /S /GoTo /D (5.12.27.2) >>
+endobj
+304 0 obj
+(2.7.2. Getting The New Bugzilla)
+endobj
+305 0 obj
+<< /S /GoTo /D (5.12.27.19.3) >>
+endobj
+308 0 obj
+(2.7.2.1. If you have modified your Bugzilla)
+endobj
+309 0 obj
+<< /S /GoTo /D (5.12.27.20.3) >>
+endobj
+312 0 obj
+(2.7.2.2. Upgrading using CVS)
+endobj
+313 0 obj
+<< /S /GoTo /D (5.12.27.21.3) >>
+endobj
+316 0 obj
+(2.7.2.3. Upgrading using the tarball)
+endobj
+317 0 obj
+<< /S /GoTo /D (5.12.27.22.3) >>
+endobj
+320 0 obj
+(2.7.2.4. Upgrading using patches)
+endobj
+321 0 obj
+<< /S /GoTo /D (5.12.28.2) >>
+endobj
+324 0 obj
+(2.7.3. Completing Your Upgrade)
+endobj
+325 0 obj
+<< /S /GoTo /D (5.12.29.2) >>
+endobj
+328 0 obj
+(2.7.4. Automatic Notifications of New Releases)
+endobj
+329 0 obj
+<< /S /GoTo /D (6.0) >>
+endobj
+332 0 obj
+(Chapter 3. Administering Bugzilla)
+endobj
+333 0 obj
+<< /S /GoTo /D (6.13.1) >>
+endobj
+336 0 obj
+(3.1. Bugzilla Configuration)
+endobj
+337 0 obj
+<< /S /GoTo /D (6.13.30.2) >>
+endobj
+340 0 obj
+(3.1.1. Required Settings)
+endobj
+341 0 obj
+<< /S /GoTo /D (6.13.31.2) >>
+endobj
+344 0 obj
+(3.1.2. Administrative Policies)
+endobj
+345 0 obj
+<< /S /GoTo /D (6.13.32.2) >>
+endobj
+348 0 obj
+(3.1.3. User Authentication)
+endobj
+349 0 obj
+<< /S /GoTo /D (6.13.33.2) >>
+endobj
+352 0 obj
+(3.1.4. Attachments)
+endobj
+353 0 obj
+<< /S /GoTo /D (6.13.34.2) >>
+endobj
+356 0 obj
+(3.1.5. Bug Change Policies)
+endobj
+357 0 obj
+<< /S /GoTo /D (6.13.35.2) >>
+endobj
+360 0 obj
+(3.1.6. Bug Fields)
+endobj
+361 0 obj
+<< /S /GoTo /D (6.13.36.2) >>
+endobj
+364 0 obj
+(3.1.7. Bug Moving)
+endobj
+365 0 obj
+<< /S /GoTo /D (6.13.37.2) >>
+endobj
+368 0 obj
+(3.1.8. Dependency Graphs)
+endobj
+369 0 obj
+<< /S /GoTo /D (6.13.38.2) >>
+endobj
+372 0 obj
+(3.1.9. Group Security)
+endobj
+373 0 obj
+<< /S /GoTo /D (6.13.39.2) >>
+endobj
+376 0 obj
+(3.1.10. LDAP Authentication)
+endobj
+377 0 obj
+<< /S /GoTo /D (6.13.40.2) >>
+endobj
+380 0 obj
+(3.1.11. RADIUS Authentication)
+endobj
+381 0 obj
+<< /S /GoTo /D (6.13.41.2) >>
+endobj
+384 0 obj
+(3.1.12. Email)
+endobj
+385 0 obj
+<< /S /GoTo /D (6.13.42.2) >>
+endobj
+388 0 obj
+(3.1.13. Patch Viewer)
+endobj
+389 0 obj
+<< /S /GoTo /D (6.13.43.2) >>
+endobj
+392 0 obj
+(3.1.14. Query Defaults)
+endobj
+393 0 obj
+<< /S /GoTo /D (6.13.44.2) >>
+endobj
+396 0 obj
+(3.1.15. Shadow Database)
+endobj
+397 0 obj
+<< /S /GoTo /D (6.13.45.2) >>
+endobj
+400 0 obj
+(3.1.16. User Matching)
+endobj
+401 0 obj
+<< /S /GoTo /D (6.14.1) >>
+endobj
+404 0 obj
+(3.2. User Administration)
+endobj
+405 0 obj
+<< /S /GoTo /D (6.14.46.2) >>
+endobj
+408 0 obj
+(3.2.1. Creating the Default User)
+endobj
+409 0 obj
+<< /S /GoTo /D (6.14.47.2) >>
+endobj
+412 0 obj
+(3.2.2. Managing Other Users)
+endobj
+413 0 obj
+<< /S /GoTo /D (6.14.47.23.3) >>
+endobj
+416 0 obj
+(3.2.2.1. Searching for existing users)
+endobj
+417 0 obj
+<< /S /GoTo /D (6.14.47.24.3) >>
+endobj
+420 0 obj
+(3.2.2.2. Creating new users)
+endobj
+421 0 obj
+<< /S /GoTo /D (6.14.47.24.15.4) >>
+endobj
+424 0 obj
+(3.2.2.2.1. Selfregistration)
+endobj
+425 0 obj
+<< /S /GoTo /D (6.14.47.24.16.4) >>
+endobj
+428 0 obj
+(3.2.2.2.2. Accounts created by an administrator)
+endobj
+429 0 obj
+<< /S /GoTo /D (6.14.47.25.3) >>
+endobj
+432 0 obj
+(3.2.2.3. Modifying Users)
+endobj
+433 0 obj
+<< /S /GoTo /D (6.14.47.26.3) >>
+endobj
+436 0 obj
+(3.2.2.4. Deleting Users)
+endobj
+437 0 obj
+<< /S /GoTo /D (6.14.47.27.3) >>
+endobj
+440 0 obj
+(3.2.2.5. Impersonating Users)
+endobj
+441 0 obj
+<< /S /GoTo /D (6.15.1) >>
+endobj
+444 0 obj
+(3.3. Classifications)
+endobj
+445 0 obj
+<< /S /GoTo /D (6.16.1) >>
+endobj
+448 0 obj
+(3.4. Products)
+endobj
+449 0 obj
+<< /S /GoTo /D (6.16.48.2) >>
+endobj
+452 0 obj
+(3.4.1. Creating New Products)
+endobj
+453 0 obj
+<< /S /GoTo /D (6.16.49.2) >>
+endobj
+456 0 obj
+(3.4.2. Editing Products)
+endobj
+457 0 obj
+<< /S /GoTo /D (6.16.50.2) >>
+endobj
+460 0 obj
+(3.4.3. Adding or Editing Components, Versions and Target Milestones)
+endobj
+461 0 obj
+<< /S /GoTo /D (6.16.51.2) >>
+endobj
+464 0 obj
+(3.4.4. Assigning Group Controls to Products)
+endobj
+465 0 obj
+<< /S /GoTo /D (6.16.51.28.3) >>
+endobj
+468 0 obj
+(3.4.4.1. Common Applications of Group Controls)
+endobj
+469 0 obj
+<< /S /GoTo /D (6.17.1) >>
+endobj
+472 0 obj
+(3.5. Components)
+endobj
+473 0 obj
+<< /S /GoTo /D (6.18.1) >>
+endobj
+476 0 obj
+(3.6. Versions)
+endobj
+477 0 obj
+<< /S /GoTo /D (6.19.1) >>
+endobj
+480 0 obj
+(3.7. Milestones)
+endobj
+481 0 obj
+<< /S /GoTo /D (6.20.1) >>
+endobj
+484 0 obj
+(3.8. Flags)
+endobj
+485 0 obj
+<< /S /GoTo /D (6.20.52.2) >>
+endobj
+488 0 obj
+(3.8.1. A Simple Example)
+endobj
+489 0 obj
+<< /S /GoTo /D (6.20.53.2) >>
+endobj
+492 0 obj
+(3.8.2. About Flags)
+endobj
+493 0 obj
+<< /S /GoTo /D (6.20.53.29.3) >>
+endobj
+496 0 obj
+(3.8.2.1. Values)
+endobj
+497 0 obj
+<< /S /GoTo /D (6.20.54.2) >>
+endobj
+500 0 obj
+(3.8.3. Using flag requests)
+endobj
+501 0 obj
+<< /S /GoTo /D (6.20.55.2) >>
+endobj
+504 0 obj
+(3.8.4. Two Types of Flags)
+endobj
+505 0 obj
+<< /S /GoTo /D (6.20.55.30.3) >>
+endobj
+508 0 obj
+(3.8.4.1. Attachment Flags)
+endobj
+509 0 obj
+<< /S /GoTo /D (6.20.55.31.3) >>
+endobj
+512 0 obj
+(3.8.4.2. Bug Flags)
+endobj
+513 0 obj
+<< /S /GoTo /D (6.20.56.2) >>
+endobj
+516 0 obj
+(3.8.5. Administering Flags)
+endobj
+517 0 obj
+<< /S /GoTo /D (6.20.56.32.3) >>
+endobj
+520 0 obj
+(3.8.5.1. Editing a Flag)
+endobj
+521 0 obj
+<< /S /GoTo /D (6.20.56.33.3) >>
+endobj
+524 0 obj
+(3.8.5.2. Creating a Flag)
+endobj
+525 0 obj
+<< /S /GoTo /D (6.20.56.33.17.4) >>
+endobj
+528 0 obj
+(3.8.5.2.1. Name)
+endobj
+529 0 obj
+<< /S /GoTo /D (6.20.56.33.18.4) >>
+endobj
+532 0 obj
+(3.8.5.2.2. Description)
+endobj
+533 0 obj
+<< /S /GoTo /D (6.20.56.33.19.4) >>
+endobj
+536 0 obj
+(3.8.5.2.3. Category)
+endobj
+537 0 obj
+<< /S /GoTo /D (6.20.56.33.20.4) >>
+endobj
+540 0 obj
+(3.8.5.2.4. Sort Key)
+endobj
+541 0 obj
+<< /S /GoTo /D (6.20.56.33.21.4) >>
+endobj
+544 0 obj
+(3.8.5.2.5. Active)
+endobj
+545 0 obj
+<< /S /GoTo /D (6.20.56.33.22.4) >>
+endobj
+548 0 obj
+(3.8.5.2.6. Requestable)
+endobj
+549 0 obj
+<< /S /GoTo /D (6.20.56.33.23.4) >>
+endobj
+552 0 obj
+(3.8.5.2.7. Specifically Requestable)
+endobj
+553 0 obj
+<< /S /GoTo /D (6.20.56.33.24.4) >>
+endobj
+556 0 obj
+(3.8.5.2.8. Multiplicable)
+endobj
+557 0 obj
+<< /S /GoTo /D (6.20.56.33.25.4) >>
+endobj
+560 0 obj
+(3.8.5.2.9. CC List)
+endobj
+561 0 obj
+<< /S /GoTo /D (6.20.56.33.26.4) >>
+endobj
+564 0 obj
+(3.8.5.2.10. Grant Group)
+endobj
+565 0 obj
+<< /S /GoTo /D (6.20.56.33.27.4) >>
+endobj
+568 0 obj
+(3.8.5.2.11. Request Group)
+endobj
+569 0 obj
+<< /S /GoTo /D (6.20.56.34.3) >>
+endobj
+572 0 obj
+(3.8.5.3. Deleting a Flag)
+endobj
+573 0 obj
+<< /S /GoTo /D (6.21.1) >>
+endobj
+576 0 obj
+(3.9. Keywords)
+endobj
+577 0 obj
+<< /S /GoTo /D (6.22.1) >>
+endobj
+580 0 obj
+(3.10. Custom Fields)
+endobj
+581 0 obj
+<< /S /GoTo /D (6.22.57.2) >>
+endobj
+584 0 obj
+(3.10.1. Adding Custom Fields)
+endobj
+585 0 obj
+<< /S /GoTo /D (6.22.58.2) >>
+endobj
+588 0 obj
+(3.10.2. Editing Custom Fields)
+endobj
+589 0 obj
+<< /S /GoTo /D (6.22.59.2) >>
+endobj
+592 0 obj
+(3.10.3. Deleting Custom Fields)
+endobj
+593 0 obj
+<< /S /GoTo /D (6.23.1) >>
+endobj
+596 0 obj
+(3.11. Legal Values)
+endobj
+597 0 obj
+<< /S /GoTo /D (6.23.60.2) >>
+endobj
+600 0 obj
+(3.11.1. Viewing/Editing legal values)
+endobj
+601 0 obj
+<< /S /GoTo /D (6.23.61.2) >>
+endobj
+604 0 obj
+(3.11.2. Deleting legal values)
+endobj
+605 0 obj
+<< /S /GoTo /D (6.24.1) >>
+endobj
+608 0 obj
+(3.12. Bug Status Workflow)
+endobj
+609 0 obj
+<< /S /GoTo /D (6.25.1) >>
+endobj
+612 0 obj
+(3.13. Voting)
+endobj
+613 0 obj
+<< /S /GoTo /D (6.26.1) >>
+endobj
+616 0 obj
+(3.14. Quips)
+endobj
+617 0 obj
+<< /S /GoTo /D (6.27.1) >>
+endobj
+620 0 obj
+(3.15. Groups and Group Security)
+endobj
+621 0 obj
+<< /S /GoTo /D (6.27.62.2) >>
+endobj
+624 0 obj
+(3.15.1. Creating Groups)
+endobj
+625 0 obj
+<< /S /GoTo /D (6.27.63.2) >>
+endobj
+628 0 obj
+(3.15.2. Editing Groups and Assigning Group Permissions)
+endobj
+629 0 obj
+<< /S /GoTo /D (6.27.64.2) >>
+endobj
+632 0 obj
+(3.15.3. Assigning Users to Groups)
+endobj
+633 0 obj
+<< /S /GoTo /D (6.27.65.2) >>
+endobj
+636 0 obj
+(3.15.4. Assigning Group Controls to Products)
+endobj
+637 0 obj
+<< /S /GoTo /D (6.28.1) >>
+endobj
+640 0 obj
+(3.16. Checking and Maintaining Database Integrity)
+endobj
+641 0 obj
+<< /S /GoTo /D (7.0) >>
+endobj
+644 0 obj
+(Chapter 4. Bugzilla Security)
+endobj
+645 0 obj
+<< /S /GoTo /D (7.29.1) >>
+endobj
+648 0 obj
+(4.1. Operating System)
+endobj
+649 0 obj
+<< /S /GoTo /D (7.29.66.2) >>
+endobj
+652 0 obj
+(4.1.1. TCP/IP Ports)
+endobj
+653 0 obj
+<< /S /GoTo /D (7.29.67.2) >>
+endobj
+656 0 obj
+(4.1.2. System User Accounts)
+endobj
+657 0 obj
+<< /S /GoTo /D (7.29.68.2) >>
+endobj
+660 0 obj
+(4.1.3. The chroot Jail)
+endobj
+661 0 obj
+<< /S /GoTo /D (7.30.1) >>
+endobj
+664 0 obj
+(4.2. Web server)
+endobj
+665 0 obj
+<< /S /GoTo /D (7.30.69.2) >>
+endobj
+668 0 obj
+(4.2.1. Disabling Remote Access to Bugzilla Configuration Files)
+endobj
+669 0 obj
+<< /S /GoTo /D (7.31.1) >>
+endobj
+672 0 obj
+(4.3. Bugzilla)
+endobj
+673 0 obj
+<< /S /GoTo /D (7.31.70.2) >>
+endobj
+676 0 obj
+(4.3.1. Prevent users injecting malicious Javascript)
+endobj
+677 0 obj
+<< /S /GoTo /D (8.0) >>
+endobj
+680 0 obj
+(Chapter 5. Using Bugzilla)
+endobj
+681 0 obj
+<< /S /GoTo /D (8.32.1) >>
+endobj
+684 0 obj
+(5.1. Introduction)
+endobj
+685 0 obj
+<< /S /GoTo /D (8.33.1) >>
+endobj
+688 0 obj
+(5.2. Create a Bugzilla Account)
+endobj
+689 0 obj
+<< /S /GoTo /D (8.34.1) >>
+endobj
+692 0 obj
+(5.3. Anatomy of a Bug)
+endobj
+693 0 obj
+<< /S /GoTo /D (8.35.1) >>
+endobj
+696 0 obj
+(5.4. Life Cycle of a Bug)
+endobj
+697 0 obj
+<< /S /GoTo /D (8.36.1) >>
+endobj
+700 0 obj
+(5.5. Searching for Bugs)
+endobj
+701 0 obj
+<< /S /GoTo /D (8.36.71.2) >>
+endobj
+704 0 obj
+(5.5.1. Boolean Charts)
+endobj
+705 0 obj
+<< /S /GoTo /D (8.36.71.35.3) >>
+endobj
+708 0 obj
+(5.5.1.1. Pronoun Substitution)
+endobj
+709 0 obj
+<< /S /GoTo /D (8.36.71.36.3) >>
+endobj
+712 0 obj
+(5.5.1.2. Negation)
+endobj
+713 0 obj
+<< /S /GoTo /D (8.36.71.37.3) >>
+endobj
+716 0 obj
+(5.5.1.3. Multiple Charts)
+endobj
+717 0 obj
+<< /S /GoTo /D (8.36.72.2) >>
+endobj
+720 0 obj
+(5.5.2. Quicksearch)
+endobj
+721 0 obj
+<< /S /GoTo /D (8.36.73.2) >>
+endobj
+724 0 obj
+(5.5.3. Case Sensitivity in Searches)
+endobj
+725 0 obj
+<< /S /GoTo /D (8.36.74.2) >>
+endobj
+728 0 obj
+(5.5.4. Bug Lists)
+endobj
+729 0 obj
+<< /S /GoTo /D (8.36.75.2) >>
+endobj
+732 0 obj
+(5.5.5. Adding/removing tags to/from bugs)
+endobj
+733 0 obj
+<< /S /GoTo /D (8.37.1) >>
+endobj
+736 0 obj
+(5.6. Filing Bugs)
+endobj
+737 0 obj
+<< /S /GoTo /D (8.37.76.2) >>
+endobj
+740 0 obj
+(5.6.1. Reporting a New Bug)
+endobj
+741 0 obj
+<< /S /GoTo /D (8.37.77.2) >>
+endobj
+744 0 obj
+(5.6.2. Clone an Existing Bug)
+endobj
+745 0 obj
+<< /S /GoTo /D (8.38.1) >>
+endobj
+748 0 obj
+(5.7. Attachments)
+endobj
+749 0 obj
+<< /S /GoTo /D (8.38.78.2) >>
+endobj
+752 0 obj
+(5.7.1. Patch Viewer)
+endobj
+753 0 obj
+<< /S /GoTo /D (8.38.78.38.3) >>
+endobj
+756 0 obj
+(5.7.1.1. Viewing Patches in Patch Viewer)
+endobj
+757 0 obj
+<< /S /GoTo /D (8.38.78.39.3) >>
+endobj
+760 0 obj
+(5.7.1.2. Seeing the Difference Between Two Patches)
+endobj
+761 0 obj
+<< /S /GoTo /D (8.38.78.40.3) >>
+endobj
+764 0 obj
+(5.7.1.3. Getting More Context in a Patch)
+endobj
+765 0 obj
+<< /S /GoTo /D (8.38.78.41.3) >>
+endobj
+768 0 obj
+(5.7.1.4. Collapsing and Expanding Sections of a Patch)
+endobj
+769 0 obj
+<< /S /GoTo /D (8.38.78.42.3) >>
+endobj
+772 0 obj
+(5.7.1.5. Linking to a Section of a Patch)
+endobj
+773 0 obj
+<< /S /GoTo /D (8.38.78.43.3) >>
+endobj
+776 0 obj
+(5.7.1.6. Going to Bonsai and LXR)
+endobj
+777 0 obj
+<< /S /GoTo /D (8.38.78.44.3) >>
+endobj
+780 0 obj
+(5.7.1.7. Creating a Unified Diff)
+endobj
+781 0 obj
+<< /S /GoTo /D (8.39.1) >>
+endobj
+784 0 obj
+(5.8. Hints and Tips)
+endobj
+785 0 obj
+<< /S /GoTo /D (8.39.79.2) >>
+endobj
+788 0 obj
+(5.8.1. Autolinkification)
+endobj
+789 0 obj
+<< /S /GoTo /D (8.39.80.2) >>
+endobj
+792 0 obj
+(5.8.2. Comments)
+endobj
+793 0 obj
+<< /S /GoTo /D (8.39.81.2) >>
+endobj
+796 0 obj
+(5.8.3. ServerSide Comment Wrapping)
+endobj
+797 0 obj
+<< /S /GoTo /D (8.39.82.2) >>
+endobj
+800 0 obj
+(5.8.4. Dependency Tree)
+endobj
+801 0 obj
+<< /S /GoTo /D (8.40.1) >>
+endobj
+804 0 obj
+(5.9. Time Tracking Information)
+endobj
+805 0 obj
+<< /S /GoTo /D (8.41.1) >>
+endobj
+808 0 obj
+(5.10. User Preferences)
+endobj
+809 0 obj
+<< /S /GoTo /D (8.41.83.2) >>
+endobj
+812 0 obj
+(5.10.1. General Preferences)
+endobj
+813 0 obj
+<< /S /GoTo /D (8.41.84.2) >>
+endobj
+816 0 obj
+(5.10.2. Email Preferences)
+endobj
+817 0 obj
+<< /S /GoTo /D (8.41.85.2) >>
+endobj
+820 0 obj
+(5.10.3. Saved Searches)
+endobj
+821 0 obj
+<< /S /GoTo /D (8.41.86.2) >>
+endobj
+824 0 obj
+(5.10.4. Name and Password)
+endobj
+825 0 obj
+<< /S /GoTo /D (8.41.87.2) >>
+endobj
+828 0 obj
+(5.10.5. Permissions)
+endobj
+829 0 obj
+<< /S /GoTo /D (8.42.1) >>
+endobj
+832 0 obj
+(5.11. Reports and Charts)
+endobj
+833 0 obj
+<< /S /GoTo /D (8.42.88.2) >>
+endobj
+836 0 obj
+(5.11.1. Reports)
+endobj
+837 0 obj
+<< /S /GoTo /D (8.42.89.2) >>
+endobj
+840 0 obj
+(5.11.2. Charts)
+endobj
+841 0 obj
+<< /S /GoTo /D (8.42.89.45.3) >>
+endobj
+844 0 obj
+(5.11.2.1. Creating Charts)
+endobj
+845 0 obj
+<< /S /GoTo /D (8.42.89.46.3) >>
+endobj
+848 0 obj
+(5.11.2.2. Creating New Data Sets)
+endobj
+849 0 obj
+<< /S /GoTo /D (8.43.1) >>
+endobj
+852 0 obj
+(5.12. Flags)
+endobj
+853 0 obj
+<< /S /GoTo /D (8.44.1) >>
+endobj
+856 0 obj
+(5.13. Whining)
+endobj
+857 0 obj
+<< /S /GoTo /D (8.44.90.2) >>
+endobj
+860 0 obj
+(5.13.1. The Event)
+endobj
+861 0 obj
+<< /S /GoTo /D (8.44.91.2) >>
+endobj
+864 0 obj
+(5.13.2. Whining Schedule)
+endobj
+865 0 obj
+<< /S /GoTo /D (8.44.92.2) >>
+endobj
+868 0 obj
+(5.13.3. Whining Searches)
+endobj
+869 0 obj
+<< /S /GoTo /D (8.44.93.2) >>
+endobj
+872 0 obj
+(5.13.4. Saving Your Changes)
+endobj
+873 0 obj
+<< /S /GoTo /D (9.0) >>
+endobj
+876 0 obj
+(Chapter 6. Customizing Bugzilla)
+endobj
+877 0 obj
+<< /S /GoTo /D (9.45.1) >>
+endobj
+880 0 obj
+(6.1. Bugzilla Extensions)
+endobj
+881 0 obj
+<< /S /GoTo /D (9.46.1) >>
+endobj
+884 0 obj
+(6.2. Custom Skins)
+endobj
+885 0 obj
+<< /S /GoTo /D (9.47.1) >>
+endobj
+888 0 obj
+(6.3. Template Customization)
+endobj
+889 0 obj
+<< /S /GoTo /D (9.47.94.2) >>
+endobj
+892 0 obj
+(6.3.1. Template Directory Structure)
+endobj
+893 0 obj
+<< /S /GoTo /D (9.47.95.2) >>
+endobj
+896 0 obj
+(6.3.2. Choosing a Customization Method)
+endobj
+897 0 obj
+<< /S /GoTo /D (9.47.96.2) >>
+endobj
+900 0 obj
+(6.3.3. How To Edit Templates)
+endobj
+901 0 obj
+<< /S /GoTo /D (9.47.97.2) >>
+endobj
+904 0 obj
+(6.3.4. Template Formats and Types)
+endobj
+905 0 obj
+<< /S /GoTo /D (9.47.98.2) >>
+endobj
+908 0 obj
+(6.3.5. Particular Templates)
+endobj
+909 0 obj
+<< /S /GoTo /D (9.47.99.2) >>
+endobj
+912 0 obj
+(6.3.6. Configuring Bugzilla to Detect the User's Language)
+endobj
+913 0 obj
+<< /S /GoTo /D (9.48.1) >>
+endobj
+916 0 obj
+(6.4. Customizing Who Can Change What)
+endobj
+917 0 obj
+<< /S /GoTo /D (9.49.1) >>
+endobj
+920 0 obj
+(6.5. Integrating Bugzilla with ThirdParty Tools)
+endobj
+921 0 obj
+<< /S /GoTo /D (10.0) >>
+endobj
+924 0 obj
+(Appendix A. Troubleshooting)
+endobj
+925 0 obj
+<< /S /GoTo /D (10.50.1) >>
+endobj
+928 0 obj
+(A.1. General Advice)
+endobj
+929 0 obj
+<< /S /GoTo /D (10.51.1) >>
+endobj
+932 0 obj
+(A.2. The Apache web server is not serving Bugzilla pages)
+endobj
+933 0 obj
+<< /S /GoTo /D (10.52.1) >>
+endobj
+936 0 obj
+(A.3. I installed a Perl module, but checksetup.pl claims it's not installed!)
+endobj
+937 0 obj
+<< /S /GoTo /D (10.53.1) >>
+endobj
+940 0 obj
+(A.4. DBD::Sponge::db prepare failed)
+endobj
+941 0 obj
+<< /S /GoTo /D (10.54.1) >>
+endobj
+944 0 obj
+(A.5. cannot chdir\(/var/spool/mqueue\))
+endobj
+945 0 obj
+<< /S /GoTo /D (10.55.1) >>
+endobj
+948 0 obj
+(A.6. Everybody is constantly being forced to relogin)
+endobj
+949 0 obj
+<< /S /GoTo /D (10.56.1) >>
+endobj
+952 0 obj
+(A.7. index.cgi doesn't show up unless specified in the URL)
+endobj
+953 0 obj
+<< /S /GoTo /D (10.57.1) >>
+endobj
+956 0 obj
+(A.8. checksetup.pl reports "Client does not support authentication protocol requested by server...")
+endobj
+957 0 obj
+<< /S /GoTo /D (11.0) >>
+endobj
+960 0 obj
+(Appendix B. Contrib)
+endobj
+961 0 obj
+<< /S /GoTo /D (11.58.1) >>
+endobj
+964 0 obj
+(B.1. Commandline Search Interface)
+endobj
+965 0 obj
+<< /S /GoTo /D (11.59.1) >>
+endobj
+968 0 obj
+(B.2. Commandline 'Send Unsent Bugmail' tool)
+endobj
+969 0 obj
+<< /S /GoTo /D (12.0) >>
+endobj
+972 0 obj
+(Appendix C. Manual Installation of Perl Modules)
+endobj
+973 0 obj
+<< /S /GoTo /D (12.60.1) >>
+endobj
+976 0 obj
+(C.1. Instructions)
+endobj
+977 0 obj
+<< /S /GoTo /D (12.61.1) >>
+endobj
+980 0 obj
+(C.2. Download Locations)
+endobj
+981 0 obj
+<< /S /GoTo /D (12.62.1) >>
+endobj
+984 0 obj
+(C.3. Optional Modules)
+endobj
+985 0 obj
+<< /S /GoTo /D (13.0) >>
+endobj
+988 0 obj
+(Appendix D. GNU Free Documentation License)
+endobj
+989 0 obj
+<< /S /GoTo /D (13.63.1) >>
+endobj
+992 0 obj
+(0. Preamble)
+endobj
+993 0 obj
+<< /S /GoTo /D (13.64.1) >>
+endobj
+996 0 obj
+(1. Applicability and Definition)
+endobj
+997 0 obj
+<< /S /GoTo /D (13.65.1) >>
+endobj
+1000 0 obj
+(2. Verbatim Copying)
+endobj
+1001 0 obj
+<< /S /GoTo /D (13.66.1) >>
+endobj
+1004 0 obj
+(3. Copying in Quantity)
+endobj
+1005 0 obj
+<< /S /GoTo /D (13.67.1) >>
+endobj
+1008 0 obj
+(4. Modifications)
+endobj
+1009 0 obj
+<< /S /GoTo /D (13.68.1) >>
+endobj
+1012 0 obj
+(5. Combining Documents)
+endobj
+1013 0 obj
+<< /S /GoTo /D (13.69.1) >>
+endobj
+1016 0 obj
+(6. Collections of Documents)
+endobj
+1017 0 obj
+<< /S /GoTo /D (13.70.1) >>
+endobj
+1020 0 obj
+(7. Aggregation with Independent Works)
+endobj
+1021 0 obj
+<< /S /GoTo /D (13.71.1) >>
+endobj
+1024 0 obj
+(8. Translation)
+endobj
+1025 0 obj
+<< /S /GoTo /D (13.72.1) >>
+endobj
+1028 0 obj
+(9. Termination)
+endobj
+1029 0 obj
+<< /S /GoTo /D (13.73.1) >>
+endobj
+1032 0 obj
+(10. Future Revisions of this License)
+endobj
+1033 0 obj
+<< /S /GoTo /D (13.74.1) >>
+endobj
+1036 0 obj
+(How to use this License for your documents)
+endobj
+1037 0 obj
+<< /S /GoTo /D (14.0) >>
+endobj
+1040 0 obj
+(Glossary)
+endobj
+1041 0 obj
+<< /S /GoTo /D (15.0) >>
+endobj
+1044 0 obj
+(09, high ascii)
+endobj
+1045 0 obj
+<< /S /GoTo /D (15.74.100.2) >>
+endobj
+1048 0 obj
+(.htaccess)
+endobj
+1049 0 obj
+<< /S /GoTo /D (16.0) >>
+endobj
+1052 0 obj
+(A)
+endobj
+1053 0 obj
+<< /S /GoTo /D (16.74.101.2) >>
+endobj
+1056 0 obj
+(Apache)
+endobj
+1057 0 obj
+<< /S /GoTo /D (16.74.101.47.3) >>
+endobj
+1060 0 obj
+(Useful Directives when configuring Bugzilla)
+endobj
+1061 0 obj
+<< /S /GoTo /D (17.0) >>
+endobj
+1064 0 obj
+(B)
+endobj
+1065 0 obj
+<< /S /GoTo /D (17.74.102.2) >>
+endobj
+1068 0 obj
+(Bug)
+endobj
+1069 0 obj
+<< /S /GoTo /D (17.74.103.2) >>
+endobj
+1072 0 obj
+(Bug Number)
+endobj
+1073 0 obj
+<< /S /GoTo /D (17.74.104.2) >>
+endobj
+1076 0 obj
+(Bugzilla)
+endobj
+1077 0 obj
+<< /S /GoTo /D (18.0) >>
+endobj
+1080 0 obj
+(C)
+endobj
+1081 0 obj
+<< /S /GoTo /D (18.74.105.2) >>
+endobj
+1084 0 obj
+(Common Gateway Interface)
+endobj
+1085 0 obj
+<< /S /GoTo /D (18.74.106.2) >>
+endobj
+1088 0 obj
+(Component)
+endobj
+1089 0 obj
+<< /S /GoTo /D (18.74.107.2) >>
+endobj
+1092 0 obj
+(Comprehensive Perl Archive Network)
+endobj
+1093 0 obj
+<< /S /GoTo /D (18.74.108.2) >>
+endobj
+1096 0 obj
+(contrib)
+endobj
+1097 0 obj
+<< /S /GoTo /D (19.0) >>
+endobj
+1100 0 obj
+(D)
+endobj
+1101 0 obj
+<< /S /GoTo /D (19.74.109.2) >>
+endobj
+1104 0 obj
+(daemon)
+endobj
+1105 0 obj
+<< /S /GoTo /D (19.74.110.2) >>
+endobj
+1108 0 obj
+(DOS Attack)
+endobj
+1109 0 obj
+<< /S /GoTo /D (20.0) >>
+endobj
+1112 0 obj
+(G)
+endobj
+1113 0 obj
+<< /S /GoTo /D (20.74.111.2) >>
+endobj
+1116 0 obj
+(Groups)
+endobj
+1117 0 obj
+<< /S /GoTo /D (21.0) >>
+endobj
+1120 0 obj
+(J)
+endobj
+1121 0 obj
+<< /S /GoTo /D (21.74.112.2) >>
+endobj
+1124 0 obj
+(JavaScript)
+endobj
+1125 0 obj
+<< /S /GoTo /D (22.0) >>
+endobj
+1128 0 obj
+(M)
+endobj
+1129 0 obj
+<< /S /GoTo /D (22.74.113.2) >>
+endobj
+1132 0 obj
+(Message Transport Agent)
+endobj
+1133 0 obj
+<< /S /GoTo /D (22.74.114.2) >>
+endobj
+1136 0 obj
+(MySQL)
+endobj
+1137 0 obj
+<< /S /GoTo /D (23.0) >>
+endobj
+1140 0 obj
+(P)
+endobj
+1141 0 obj
+<< /S /GoTo /D (23.74.115.2) >>
+endobj
+1144 0 obj
+(Perl Package Manager)
+endobj
+1145 0 obj
+<< /S /GoTo /D (23.74.116.2) >>
+endobj
+1148 0 obj
+(Product)
+endobj
+1149 0 obj
+<< /S /GoTo /D (23.74.117.2) >>
+endobj
+1152 0 obj
+(Perl)
+endobj
+1153 0 obj
+<< /S /GoTo /D (24.0) >>
+endobj
+1156 0 obj
+(Q)
+endobj
+1157 0 obj
+<< /S /GoTo /D (24.74.118.2) >>
+endobj
+1160 0 obj
+(QA)
+endobj
+1161 0 obj
+<< /S /GoTo /D (25.0) >>
+endobj
+1164 0 obj
+(R)
+endobj
+1165 0 obj
+<< /S /GoTo /D (25.74.119.2) >>
+endobj
+1168 0 obj
+(Relational DataBase Management System)
+endobj
+1169 0 obj
+<< /S /GoTo /D (25.74.120.2) >>
+endobj
+1172 0 obj
+(Regular Expression)
+endobj
+1173 0 obj
+<< /S /GoTo /D (26.0) >>
+endobj
+1176 0 obj
+(S)
+endobj
+1177 0 obj
+<< /S /GoTo /D (26.74.121.2) >>
+endobj
+1180 0 obj
+(Service)
+endobj
+1181 0 obj
+<< /S /GoTo /D (26.74.122.2) >>
+endobj
+1184 0 obj
+(SGML )
+endobj
+1185 0 obj
+<< /S /GoTo /D (27.0) >>
+endobj
+1188 0 obj
+(T)
+endobj
+1189 0 obj
+<< /S /GoTo /D (27.74.123.2) >>
+endobj
+1192 0 obj
+(Target Milestone)
+endobj
+1193 0 obj
+<< /S /GoTo /D (27.74.124.2) >>
+endobj
+1196 0 obj
+(Tool Command Language)
+endobj
+1197 0 obj
+<< /S /GoTo /D (28.0) >>
+endobj
+1200 0 obj
+(Z)
+endobj
+1201 0 obj
+<< /S /GoTo /D (28.74.125.2) >>
+endobj
+1204 0 obj
+(Zarro Boogs Found)
+endobj
+1205 0 obj
+<< /S /GoTo /D [1206 0 R /Fit ] >>
+endobj
+1208 0 obj <<
+/Length 184
+/Filter /FlateDecode
+>>
+stream
+xڍP
+07C%$G&*I\z- "7w=2Л1.HmEAv$e*g<dŚ E!0%JkdfHN(^ކyiko~Dy57yG&gZA1(΄_]tV\ H}<
+endobj
+1206 0 obj <<
+/Type /Page
+/Contents 1208 0 R
+/Resources 1207 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 1215 0 R
+>> endobj
+1209 0 obj <<
+/D [1206 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+1210 0 obj <<
+/D [1206 0 R /XYZ 71.731 718.3063 null]
+>> endobj
+1211 0 obj <<
+/D [1206 0 R /XYZ 71.731 718.3063 null]
+>> endobj
+2 0 obj <<
+/D [1206 0 R /XYZ 494.9815 700.222 null]
+>> endobj
+1207 0 obj <<
+/Font << /F23 1214 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+1218 0 obj <<
+/Length 513
+/Filter /FlateDecode
+>>
+stream
+xڍTM0Wp4R1z\]UŭCPl(;&PqhTP\d
+3YLz> 6x*{~cYTJnH<+꓊Vi_q~mp$~15*iT?piQp ;8/qI>Jo.+qB rd
+endobj
+1217 0 obj <<
+/Type /Page
+/Contents 1218 0 R
+/Resources 1216 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 1215 0 R
+>> endobj
+1219 0 obj <<
+/D [1217 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+1216 0 obj <<
+/Font << /F23 1214 0 R /F27 1222 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+1225 0 obj <<
+/Length 58200
+/Filter /FlateDecode
+>>
+stream
+xڔ]cW~{}
+^ 'D&R/ 9,{/<%zA$[ѧI8Ys!t~iXU߼l~pzo_oN/op9>op?7>9.7WQ/]_;|pz~~O}_r9?}3:'oC/t<lۿ_w?zx&/;+{xBoV|<MoO#;vlӈ~=\4~i—|v^>FWw9_[4⾺ooO#^ڏ}<Moӈ};/cO#;qj#mt=xeu?g>FWwG oamsG޾kߞFW [zuu߾iamsG޾{KߞFWwⷧqj^B|x_xm_Gzx?ߟEq9{:/>&zY~i}u߾oO#;ݼ4?o~i—|vNcO#;_ۏ}<۷r9i}9z<|\ڏ}<M[4⾺^ۏ}<e{oO#|W _VwymEO#;?ڏ}<۷s~?ӈsmvNcO۷s<?O#;ڏ}<ǹoO#~i—|x}m?4⾺_˱oO#;\.~{q_}_oӄ/;ۏ}<۷}J-~{q_ox}mӈscv^>ڏ}<Mڏ}<ӈ~=c=>//I(= ooO#;\=۷ŨoO#_ۏ}<MoN0Ҷ#o_~i}u߾3=>O/a}sG>N=ⷧz8}ׇyf.=&{Y~i}u߾跇yfǷ=>_tj?4}50Ҷ#oKKwzm_k4ޮ_\ӄ/;p|xq_/cO#;\꿦xeu߾ⷧ|vNcO#;?4߾K _Vwyyi?4⾺_c i}u^c=>ǯ/ӄ/;vn?4⾺oӈ};&ӈscv xeu߾[|vKӈ};/~{q_}^?M_Wwzi}u^?.-~{q_/~{q_}oozeu߾K4⾺oӈ};SߞFW9XO۷slFDO#;XO#;q?ӈsr8_=Mi}uo큎FWwi|<m>_浽d/;՜=WsnߟEqqݾKߞFW{9?穧 _Vw{iJ`<mȗ飽h~x?m<>^/jtxkCFWww9zq_7amyi|jN]FWw9XO#;\4i&|YOk4⾺_k{nb=>?o=ӄ/;\=۷nb=۷s>i})~yپS4};@GO#;:zq_c=>Ǐ/k{ _Vwnb=[{|v.~{q_}ozeu߾@GO#;M|vNc=>۷slk&|YoM_K//sB\U,H},C}~m>{jt7驯њ۟g
+Wp_XC+
+Wp_XCXCƗU5tb *:\}b X
+
+/kp
+Wp_XC+
+Wp_XCXCƗU5tb *:\}b X
+
+/kp
+Wp_XCXCƗU5tb *:\}b X
+
+/kp
+Wp_XC+
+Wp_XC+
+Wp_XC"Bk
+Wp_XC+
+mW[
+p_T-B[ՖW-U-eR
+mW[
+p_T-B[g[**:\m)}R
+mW[
+p_T϶TƗUhKuR*:\m)}R
+m
+m/Жp
+mW[
+p_T-B[g[*jnKuhKmЖhUhKuRjnKURMhKuR`*:\m)}5:{MhKUlKe{YT-Tgж mV[
+l_T϶TƗܖі6-jKЖp
+mW[
+p_T-B[ՖW-U-eR
+mW[
+p_T-B[g[**:\m)}R
+mW[
+p_T϶TƗUhKuR*:\m)}R
+mkKE|]T-B[ՖW-jKЖٖ
+mW[
+p_T-B[ՖW-U-eR
+mW[
+p_T-B[g[**:\m)}RྚR=R@&*zB[ՖWc[om)-jKЖٖR=R@&:Zm)}RྚRT}Rؾ
+mW[
+p_mm)mR=R^V-jKЖp
+m
+m/Жp
+v&|kiX{"ឆU'iX{""dzV>.Ba"yVa>.B`g'9U'iXex" U')ؙI-Bva݉"wV>.Bjaډ"Dv
+v&vkiXu"aUV'iXu"1)"dtV>.B@aω"sV>.B4ٻdN`}r9 XNd!ӰDq9 @Nd!S3X[YFQ>AAÉ"pV!>.N*=47Q}\M*{Ŝin
+tnjiXEn"7:6}؃M*lEL$sΦQG&{iPel U&)ؙI-Ba"kV>.Ba"Dj
+v&jkiXi"aU&iXi"1)"dhV>.Ba"gV>.Bt`gr&Ul&iXef"U`&)ؙI-BVa"eV9>.BJa"DdK!ӰDq1 lLd!Ӱ
+Dqb1;S1E4"1}\@L*EH40}\(L$Lbmr0 Ld!ӰDq0
+Dqb.;S.Ŝqiq\T>.Banb- }܃\K*E42-}\̉F>A@g%YU%diXX")U%a)ؙ`I-B~a_"WVٕ>.Bra\"V
+vVkYiXEV"U^%ViXU"Qf*Eȩ4b*}\J*EH4*}\xJtJbm) hJd!ҰʥDqR) PJd!R3X[<J*E4(}\$J*EL$!U%HܵF(O.?]pO92@y%P/okx^>n_aCv'~5~z׷W 4⾺_KߞFW:%>&|Yotj?4⾺o~i}u߾c=>ߎk _Vwzi}u;ڏ}<#:vv
+yW p_S+B{W!T3eO
+W p_ (mBP=SP^V!
+B9
+# mBW!w9#mB,W!F9UJGe7!
+Hpe
+EJULJezYT+,B\ÕW!1L
+Wp
+p_T+;BzW!@U3Ae2T
+1W
+p_$U+JBg**:\*}"UL
+W
+p_`UdUƗUVuU*ī:\*}V
+!
+)/p
+]p
+W$ p_PVTVƗ՜6!fpų
+yW` p_͑-mR[ؾ
+
+ɭ/9#mB|W!p
+9W p_(W+BW!U3ѕe2]P
+W p_dW+Bg+*:\/}"^
+)W p_WϤWƗUzu^*Ľ:\y/}_
+
+/p
+W p_X+BW! V3 e`@
+W& p_TX+B0g2,*d:\0}a|
+ WD p_XϔXƗUȉub*D:\Y1}5:{ŀMULe{YX+4XGrc
+yW p_Y+sB;W!xwɳ=p
+Epe
+݂W
+݂W
+݂W
+݂/-pu
+݂W
+݂W
+݂/[٣[
+݂W
+݂W
+݂W
+݂/-pu
+݂W
+݂W
+݂W
+݂W
+~cf깇]p^^4⾺o=ⷧΗcO۷zm?4⾺oⷧ|vN~{q_}_o~i—|v/>FWwy9xq_Tamq<3Oӈm>m7sy?hKy;\OWs~k#m;͜[4ھߒL%>BOj =u=9T#DSᾚBO%5Db=uBO@/9T#DSᾚCO%>BOj=uBO/9T#DSᾚCO%>BOj=uBO/9T#DSᾚCO%>BOj=uBO/9T#DSᾚCO%>BOj=uBO/9T#DSᾚCO%>BOj=uBO/9T#DSᾚCO%>BOj=uBO/9T#DSᾚCO%>BOj=uBO/9T#DSᾚCO%>BOj=U =e|]͡'}5J|z*z"Wsz|YҾz*z"WsGp_'}3Jzz*z"Wc詴o'm3:Z'z*z"Wc詴o'm3Jzzp
+qW\
+p_T+.B\g\**ĥ:\q)}R
+qW\
+p_TϸTƗUKuR*ĥ:\q)}5ǥ:{ĥMKUKe{YT+.Tgж qV\
+l_Tť"Tgж qV\
+l_T+.Te+.i߄TG+.B\Wc\oq)!.U3.eR
+qW\
+p_T+.B\g\**ĥ:\q)}R
+qW\
+p_TϸTƗUKuR*ĥ:\q)}R
+q
+q/pť
+qW\
+p_T+.B\W!.wqpť
+hۄTG+.B\Ws\ʴoB\W!.K9.#.mB\g\**ĥ:\q)}5ǥ:{ĥMKuR`*ĥ*|ƥ2Tgж qV\
+l_T+.B\g\**ĥ:\q)}R
+qW\
+p_TϸTƗUKuR*ĥ:\q)}R
+q
+q/pť
+qW\
+p_TϸTƗUKuR*ĥ:\q)}R
+q
+q/pť
+h_TϸTƗ6!.ъKpť
+qW\
+p_T+.B\W!.U3.eR
+qW\
+p_T+.B\g\**ĥ:\q)}R
+qW\
+p_Tť"B\W!.Kpť
+qW\
+p_T+.B\W!.U3.eRťF\B\*^?-7?۾>>;wF`|ǵ(}t1WO?u7,5Nw tz}R p_SB۩vWTeN
+mW p_SB۩g)*:\m'}NྚN=N@&*zB۩vWs۩G hۄSGB۩N_Ws۩G hۄSGB۩vWs۩vʴoB۩vWj;ѷvv
+mW p_SB۩vWTeN
+mW p_SB۩g)*:\m'}N
+mW p_S϶SƗUh;uN*:\m'}N
+m
+m/vp
+m
+m/٣mB۩vWj;Tj;e7j;vp
+mW p_SB۩g)*:\m'}N
+mW p_S϶SƗUh;uN*:\m'}N
+m
+m/vp
+mW p_SB۩g)*:\m'}N
+mW p_S϶SƗUh;uN*:\m'}N
+m
+m/vp
+mW p_m
+mW p_SB۩vWTeN
+mW p_SB۩N_Wj;vp
+m(NBtB^/o;]޶o?xlN}_ӟ~˳:7iR{~|yn }}5wnEۯye;\w*wUpp_n?*wUppWn~ƗUppWnu~ϻ/pWnu~np_
+w_Vnu~np_>
+w>3~np_>
+w}}+|g|Y>
+w}};\w*W
+w}};\w*wUopWnu~np_
+w_V~g}m;Zw`*w|_ٺϴo~Gnl_>ྚ;{mp_n?*w|nhۄ>ؾ
+w>3w@&wUpp_n?*wUppWn~ƗUppWnu~ϻ/pWnu~np_
+w_Vnu~np_>
+w>3~np_>
+w}}n?*wUppWn~ƗUppWnu~ϻ/pWnu~np_
+w_Vnu~np_w=Mۯye;\wj>pWn~Ɨ|nhۄ>ؾ
+w}}5W3pѺWnuw@&W
+w}};\w*wUۯye;\w*wUpp_n?*wUppWn#~np_>
+w}}+|g|Y>
+w}};\w*W
+w}};\w*wUۯyeñy{/4_^\=^5ߘylW??7|!}m&^P=Y9 *Oɱׇy:Z}}>}
+}L/'p
+}W
+}W
+}W
+}D|]>AO
+}W
+}W
+}W
+}W
+}
+}/'p
+}W
+}W
+}
+}/'7]
+_V်u@ up_5
+>3up_5
+j}+|Pg|Y5
+j};\Ԁ*PW<
+j};\Ԁ*PwU8y@e;\Ԁ*PwU8pPp@w_W်u@ up_5
+>3@&PwU8pPu@i߄5ؾ
+j}5Pw86်uU8pP5ж j}+|Pg|Yԝ=M8hPp@:W်uƗU8pPp@:W်u@ u/p@:W်u@ up_
+_V်u@ up_5
+>3up_5
+j}+|Pg|Y5
+j};\Ԁ*P7uU8pPp@:W်u@ u/p@:W်u@ up_
+_V်u@ up_5
+>3up_5ྚ;{Pmp@]:*Pwx@ѷj်u@ u/5ж j};\Ԁj>lPg7်u@ up_ԝ=M8y@e;\Ԁ*PwU8pPp@]:*PwU8pPp@:W်uƗU8pPp@:W်u@ uPG|]5
+j};\Ԁ*PW<
+j};\Ԁ*PwU8y@e;\Ԁ*PwU8pPp@]:*P^hP߿P8/Po;y>}O|?~Y|퉏ϟuuGƽ?|xwvOݿ{_wy&6wgW@,fX}tm,eaۄ?Ⱦg6b_?>{G7W,&?>˶ JVw!˶ ՅFх(&$*YŅ,&*Y,&*Y,&y원YdUlXd%lXdlPWhWoBZUVȲmBWUȲmBTTȲmBQgP!ʾ 9JVM!˶ -JVJ!˶ !JVG!˶ F(&$*Y,&*Y,&*Y,&y원MdUlLd%lLdlPKhKoB*UJȲmB'IȲmB$HȲmB! I^6!Pɪ#d6PJ#d6!P"d63eI
+E>.BrCȰmB BȲmB+][ B媂aۄB%+e
+>.Bqg ¾ JV ˶<}\AyaۄA#A}3*xU ö JV ˶ F(&$*Y,& *Yy,& *Ym,& y 원5dU l4d% l4d lP3h3oBʠU2ȲmBǠ1ȲmBĠ0ȲmBg ʾ JV ˶ JV ˶ JV ˶ ՂFт(&$ *Ył,&
+*Y,&
+*Y,&
+
+.Te2JAmDAm>Am<QMHTYMTYMTYM(4 D7!GPɪd6EPJd6!DPd6B3Be߄A%@eۄ@%+?e
+>.Byqgx ¾ فJVu ˶{Kda@vmj<cQ͜Q"t*W &D*Y,f. 4ѵE TMh TY"TwF"원dldldlPhoBFUȲmBCȲmB@ȲmB=g< ʾ JV9 ˶ ݀JV6 ˶ рJV3 ˶ ŀB $yل\@%eۄV@%+eۄP@%eۄJ@#H@}B@m
+q<'~+:? =oO#;V_F6wҷ՝0|*\OhDgж %Vl_EFEe+Hi߄$EGIBÕ
+MQ3Ne>
+Wp_HERBg"**:\
+}j\
+Wp_fEhEƗUVt*+:\
+}z
+
+/p5,
+ɋWp_E+{BU
+G hۄFG+BU
+WAp_FψFƗUht:*4:\) }b
+=
+A/p55
+W}p_F
+-
+1/p8
+)Wp_G+BУU
+eWp_GBߣg#*$>:\}*
+Wp_G>"B
+Wp_H+B UWR3e 9dQ5AF , ǎʂ޲ ??ҟxN`|\O{mzy'8{x5q=ΧZ4~>\/-~{q_kߞFWw_F6w
+(>LG:3%>3jΔ:CmLI پ3>3_Wcuom3WgJzTg\)Q!WcuGuhՙ}5WgJ|TgT)suUzYՙ}5WgJ|Tg\)Q!WsuU|Yՙ}5WgJ|Tg\)Q!WsuU|Yՙ}5WgJ|Tg\)Q!WsuU|Yՙ}5WgJ|Tg\)Q!WsuU|Yՙ}5WgJ|Tg\)Q!WsuU|Yՙ}5WgJ|Tg\)Q!WsuU|Yՙ}5WgJ|Tg\)Q!WsuU|Yՙ}5WgJ|Tg\)Q!Wsugu&jΔ:S:CL ᾚ3 jΔ:CmLI پ3%>3jt
+m8ؾ
+qWp_qV"Ӿ qVl_8E+N8EG
+}8
+qWp_8E8EƗUSt*):\q
+}8
+q
+q/p)
+}>_V!NS
+qWp_8E+NB
+qWp_8E+NB_W!NS
+m8ؾ
+qWp_8E8EƗUSt*):\q
+}8
+q
+q/p)
+}>_V!NS
+}8
+q
+q/p)
+}>_V!NS
+}5):
+q
+q/9N#NmB
+qWp_8E+NBg"*):\q
+}8
+qWp_8E8EƗUSt*):\q
+}8
+qSD|]8E+NB
+qWp_8E+NB
+qWp_8E+NBg"*)bTS 8 8Ex<Nۏh!N1q[w˷|ӯ嗯ӗ~ח?gwZ{8}|wWqѼw➼P
+Wq8}:\Wq*\u
+Wq8}:\Wq*\U:{\mpѺW*u櫸U\}:ZWq`*\u
+Wq_V*uU\*p_U
+Wq>2U\*p_U
+Wq8}*|^e|YU
+Wq8}:\Wq*\U
+Wq8}:\Wq*\u
+Wq>2U\*p_U
+Wq8}*|^e|YU
+Wq8}:\Wq*\U
+Wq8}:\WqjqmU\Eϫl/pẊWU\G߮
+Wq>2櫸Wq@&\uUp]*ui߄Uؾ
+Wq8}5_u6*U\Up]pẊW*uU\ϫ/pẊW*uU\*p_
+Wq_V*uU\*p_U
+Wq ~wu:\Wq*\u
+A_V<uyP<p_y
+A>σ2yP<p_y
+A }΃<(*u΃
+A }5U΃2p:W<uA@&U<
+A }5u86<uyP/<yж A }΃:\A*U<
+A }΃:\A*u΃
+A }΃:\A*u΃
+A }΃*|e|Yy
+A }΃:\A*U<
+A }΃:\A*u΃
+A_V<uyP<p_y
+A>σ2yP<p_y
+A }΃*|e|YpҞ ?
+/_?ѯ?y7-4o鿾_퇟>~YE?~g<
+Wkp_PPƗ65j p
+Wkp_ͭ!mZCؾ
+
+/5٣5mBkW5j 
+Wkp_P5BkW5T5eZC
+Wkp_P5Bkgk(*:\!}ZC
+Wkp_PPƗUh uZC*:\!}ZC
+
+/p
+Wkp_P5BkW5T5eZC
+Wkp_P5Bkgk(*:\!}ZC
+Wkp_PPƗUh uZC*:\!}5:{Mh Ul e{YP5PGZC
+Wkp_P5BkW5wp
+/Q䧷oOV%GΟ4R$hܽcH
+$MT#Y"G*W &dG
+:e37G*x$GUo$ö JVl$˶S# Htm:#Hm"#Hm3F*EuEdElduElPdEEliYoBOɲmBLɲmBIɲmBFgE$ʾ JVB$˶ JV?$˶ JV<$˶ F(&tC*Yِ,&DC*Y͐,&C*Y,&ByB
+dBl
+duBlP dEBliYoBɲmBɲmBɲmBg$ʾ MJV$˶ AJV$˶ 5JV $˶ )F%(&t@*Y,&D@*Y ,&@*Y,&?
+eGmGmGm<Q">*W &>*Y,f|4*ѵEh|TM|TY\"=w="de=l9QE(zTMy4yD7sˣG#\u<2lPdE<lhYoBȲmBȲmBȲmBg#ʾ ͎JV#˶ JV#˶ JV#˶ F(&t:*Y,&D:*Y,&:*Y,&9y9d9ldu9lPdE9lhY䈲oBȲmBȲmBȲmB*I^6QJpd6!Qod6QɊod6!ȳe߄F%+eۄF%eۄF%+eۄF#F}ZFmBFm*Fm< QMkTYMkTY\֨"d5wV5"Ԩd%5l1QFVQid6!ȳe
+>.BDrȰmBAȲm|F][vF*aۄpF%eՌ
+ь>.B2qg1#¾ JV.#˶ JV+#˶ JV(#˶ F(&42*Y,&2*Y},&1*Yq,&1y1Ũde1lŨd51lPĨd1l(FMhaTRYMaT:YM`T"YMH`4,`D7Q_d6!~Qj_d6|Q
+_d6!{ȳze߄E%+yeۄE%weۄE%+veۄE#E}:e.یۄ卋~9Qb<6*[/_?~߿;^zq8?u<\?rW'nUZppo
+_Vᔽu5{p_E;
+7>3Y{p_i;
+v}+|g|Y׍;
+W3w};\*ܺW<v
+{w}.;\'*wU{yeN;\*\wU8p]p_>*wUpp ẆWA|ƗU8pp:W0um|G|]}<
+y};\W*W<O;{mp-:W`u1|};Zw`*\wN|<zhۄ^VᄾuC+g@&w.UyLe5w6ᢾuRQ}p_
+_Vᴾu[u}p_ׅ=
+7>3™}p_Kש=
+k{}+|g|Y=
+W{};\*W<
+{}.;\'*wUïyeN;\*\wU8p]pwG_W,ue~4p_u>
+>3‰~Fp_+י>
+K}}n+|g|Ys׽>
+}};\W*W<
+}};\j>qm E#l/pW%GN~\c5?о
+>37@&\wU8p]uܟi߄}?ؾ
+}5w6οU8pp:Wuϣ/pWup_
+/
+D%ߙDj$:$@mL$:2 p;2NLN͙DV&΄LS+
+D'W&rB&Qw&
+D'W&rB&ɕIܩItre w*d|gY>Itre w*d\ȝ
+D'W&rB&Qw&
+D'W&rB&ɕIܩItre w*d|gY>Itre w*d\ȝ
+D'W&rB&ȯ2(T$:2 ;2NLNL+
+D'W&rB&ɕIܩItre w*d|gY>Itre w*d\ȝ
+D'W&rB&Qw&
+D'W&rB&ɕIܩItre w*d*IL+
+=LBOLbdwo|{i_ng/'s>'o{g A6RfHn6C;5ͰT͐mXo36C;5Ͱܩm|͐No3z!5Ͱܩm|͐No3,m$wj~a' A>m|͐No3,m$wj~a)_o3$S ;fAo3,m$wj~a)_o3$S Kz!ɝf6Cj~a)_o3$S Kz!ɝfX I6N|P Kz!ɝfX I6RfHrvr䃚fXWOrLLN͙~)_>ɝ3NL䃚3R2};5g|e$wjKIԜWgLLN͙~)_>ɝ3R2};5g\>5fژ9/+'Ss_WOrLsL̙~^>3R2};5fژ9=9/+'Sc_/>3R2}r;5g\>5fژ9/+'Ss_WOrL+LLN͙~)_>ɝ3R2};5g\>5g|e$wjKIԜܩ9A>9/+'Ss_WOrLLN͙~'WA͙~)_>ɝ3R2};5g|e$wj;2}jKIԜܩ9/+'Ss_w哚3R2};5g|e$wjKIԜwre Ԝܩ9/+'Ss_WOrL+LLN͙~)_>ɝ3R2};5g\>5g|e$wjKIԘKOjcLS+LLNM~~I3R2}R;5g\>5fژ9/+'Ss_WOrLsL̙~^>3R2};5fژ9=9/+'Ss_WOrLLN͙~'WA͙~)_>ɝ3R2};5g|e$wj;2}jKIԜܩ9/+'Ss_w哚3R2};5g|e$wjKIԜwre Ԝܩ9/+'Ss_WOrL+LLN͙~)_>ɝ3\W>}s>P'3ۯrڙO|ߟ??=r'9+S>|qRG nBG\wG
+q'WG rBGܩwru w*tĕ|wY>wru w*tĝ\1ȝ
+q'WG rBG\wG
+q'WG rBGܩwru w*tĕ|wY>wru w*tĝ\1ȝ
+q'WG rBG\wG
+q'WG rBGܩwru w*tĕ|wY>wru w*tĝ\1ȝ
+q'WG rBG\wG
+q'WG rBGܩwru w*tč#I#S#AT;:b;:J;,wژwjuv*tĝ\1ȝ;ʭ8 qVG nBGܩ#ܫ#1#Ի#A#SsGܹWG jcBGܩ۩Wg掸sƄS#S#AT+|P#AT;:b;:NN8:NN#S#AT+|P#AT;:b;:NN8:NN#S#AT+|P#AT;:b;:NN_uQ>wru w*tĝ\1ȝ
+q'WG rBG\wG
+q'WG rBGܩwru w*tĕ|wY>wru w*tĝ\1ȝ
+q'WG rBG\wG
+q'WG rBGܩ#ܫ#1#Ի#A#ScGܩ/1?#AT+|PsGܹWG jcBGܩ۩wru wj+:v&tĝZ1
+q'WG r掸sƄR8:NN#S#AT+|P#AT;:b;:NN8:NN#S#ATUG
+q'WG rBGܩwru w*tĕ|wY>wru w*tĝ\1ȝ
+q'WG rBG\wG
+q'WG rBGܩ
+ow:?vGpwepz}Wt}ߧ c>K{:?}q>Q_FSo;srWn< 9z9
+9z9ȝsީ/9G"g{;sr{;sr>A'>ȝ
+|d>A'>ȝ
+\ w*trܩOP>A*trܩOɵO
+|d>A'>ȝ
+\ w*trܩOP>A*trܩOɵO
+|d>A'>ȝ
+\ w*trܩOP>A*trܩOɵO
+|d>A'>ȝ
+\ w*trܩOȯ |Rak
+\ wj''jg>A>
+\ wj'k
+|d>A'>ȝ
+\ w*trܩOP>A*trܩOɵO
+|d>A'>ȝ
+\ wj'k
+\ w*trܩOP>A*trܩOɵO
+|d>A'>ȝ
+\ w*¾' ?P'Ǘ^_vrx}}z1Pck`?˯/?G>SOoQzes\)8Xzd*f 6&,$tj-$۩ɵ
+ \ w*,$tr-$ܩɵ
+ \ w*,$T򽐐
+ \ w*,$tr-$ܩɵ
+ \ w*,$T򽐐
+ \ w*,$tr-$ܩɵ
+ \ w*,$T򽐐
+ \ w*,$tr-$ܩɵ
+ \ w*,$4򫅄(TXHZH
+ \ w*,$tr-$ܩɵ
+ \ w*,$T򽐐
+ \ w*,$tr-$ܩɵ
+ \ wj^HZHjgBBB
+ \ wj^Hk!ƄJ{Pa!k!NN;:@TXH{!!:@TXHZH
+ \ w*,$T򽐐
+ \ w*,$tr-$ܩɵ
+ !ot<^Ho_H8=H >e_~r_w~_~K /͡t<vǮ?a6ϧO?'d6 ?Kޏڗs<LbcfXj#3aTⵅQy5Ƅʬl6fhĚ^#
+,E2&RTfmRd1a2k" sYklLآh{"(*ڡޏDXbcEeE63O4bODa|j{" Yl<;QD~$DݓQLڛfc浉J&#&*&ؘ3јD4;3LT10Qy50Ƅyʬul6&lK4=-΄aʬ]l6&JTfJd1aR2kQ" {yID3aL2kK" KYClLZfc†DcLڏfczDexD6#*#٘јlD4;F#*6#٘Q5Ƅʬl6&lE4=΄ʬl6&DTfDd1a"2k!" j"!*!٘ Q5 ƄYʬUl6&lB4= ΄Aʬ=l6&ATfAd1a
+2k " ;y@D3a2k" YlLZfcCcL~}fcCeC63O>TxC;*٘q^f*VטИC4;3<TPy5Ƅiʬel6fuhĚu^#¨CզC*٘yΡ5H-ƻؙ0PƄʬl6&L8Tf-8d1a1hv&7Tfm7d1a2k! Y lLlh{! Y{ lLXkkfcTCeRC6v
+LC2&4Tfm4d1a2k! Y lLfh{! Y lLXeefc$Ce"C6cfgCeC6*٘0p{9]aȟf0\} 緧ׇg~Bpz_S{xaǿ鿾w_||<~?z3U?Z?|[>@lA+nS!o[@T(\:;"J+,T\:B;RNNڥ+wS!xxA楓+zS!{^@T(_:;F~UDBܩtr50 w*T0\ ȝ
+!L%%Ljna:a@mLa:zp;N$NQLV΄.S+S!jc@\tǀژT]dB#ɀܩ9ܫ1JeTe*e|Ps/ӹW0jcB2ө̀۩Ptre3 w*3|3Y>tr3 w*3\ ȝ
+M'WBrBDSwE
+M'WHrBJҀܩPtr4 w*5|5Y>trE5 w*d5\] ȝ
+eM'WZrB\Sw]
+}M'W`rBb؀ܩPtre6 w*6|6Y>tr6 w*6\ ȝ
+M'WrrBtȯ(Tn:;қNN+S!A+S!p@T(q:R;bJk,Tq:;N&N*+S!̩A6+S!s@\tژT]dBܩ1ԗVPtb:v*;|;Y>+1!vT(w:;5;[NV;NNSsӹWjcBSwɓ݃
+-O'WrBܩPtr%= w*D=|W=Y>tr= w*=\mȝ
+uO'WrBSw
+O'WrBܩPtr> w*>Iާ+S!j~@T~:;ŸJ˟,Th:;NN+S!A+S!j@TB@VtR>(:osE闟_{ݯ__×_~GgoǙ_ݭLh_?]] O>MS ,|׹ pZpv*\ur]ܩpW\*\ur]ܩpur\'ȝ
+p|_e\'ȝ
+p\p w*\ur]ܩpW\*\ur]ܩpur\'ȝ
+p|_e\'ȝ
+p\p w*\ur]ܩpW\*\ur]ܩpur\'ȝ
+p.'.:.@T亀SN J/|PN N 8;.:.@T..:.@T亀SN J/|PN N 8;5_uujc\p=pur N}G"\ub]کpW\jƄ N 8p;.:.@|Wu΄ N 8p;.:.@|׹ pz_e\'ȝ
+p\p w*\ur]ܩpW\*\ur]ܩpur\'ȝ
+p|_e\'ȝ
+p\p w*\ur]ܩpȯ.|RN N 8;.:.@T..:.@T亀SN J/|PN N 8;.QVwh]] ~p|9<u;y zNGĶO{|~9ߟr-rw3>|=h$x/Oܩ7O_FSo_ϯaTspr~ir |j4ʝzCtژ7C_FS?_;hO\O|Ro?S~Ƽa>d[it;9>i;s{)ݗIq(w ʿ=ϵӤ~i;sy<(w gp_헧QOtj4ӹ?_zN!?t׾FSo?V~yN\p<ܽ>_zzC~:<<_zN!:=?_zN!9<>=_i;sX$O =rސ:k_Oܩ7/Oܩ˟?[{`]&ސ:c4ʝzC~ݷ_zN!(׏ܩc\׏1;5~׏ܩc|~Nͯz5~׏ܩc|~Nͯ+c$wj~X'@>c|~Nͯ+c$wj~X)_#S*~XOj~X)_#SJzɝ_?VHN׏|PJc6f~X^#SJzɝ_?ֹ@R^?Fn׏1;5~t_^?Fjc׏uj~ ܃_?VH}_!1J^+vj__}_!1J^+vj_)k|N+\+ <R
+ɝWJ_!SJ)_+$wj__RWH<R
+ɝWJ_!SJ'
+55BrRWH<R
+ɝW:W@>y|;55BrRWH<5A+|ܩy|;55BrJW|RJ)_+$wj_)k|N+|ܩy|k|䃚WJ_!SJ)_+$wj_)k|N+\+ <R
+ɝWJ_!SJ)_+$wj__RWH<R
+ɝWJe|+Z+<R
+ɝWJ
+?J)^+vj__}_!1J^+vj_)k|N+{ڙy|Tr;55Br}_!1J
+55BrRWH<R
+ɝW:W@>y|;55BrRWH<5A+|ܩy|;55BrJW|RJ)_+$wj_)k|N+|ܩy|k|䃚WJ_!SJ)_+$wj_)k|N+\+ <R
+ɝWJ_!SJW]7W'7ENoxٟ緧oKk ?h1=>r /o/?~_wwWa~Ƿ|B}'(ex w*dx\ȝ
+^'WrBWw
+^'WrBɕܩurex w*dx|gxY>urex w*dx\ȝ
+^'WrBWw
+^'WrBɕܩurex w*dx|gxY>urex w*dx\ȝ
+^'WrBȯ2(T:2<;2N N +S!ë;A^^ ^VnBɕܩ9ëڙujexv*dx\ȝ3ν2<P2J3T:2<;5gx{ex6&dxZ
+^%^j:@mL:2<p;2N N /2N N +S!@T*|P!@T:2<;2N N /2N N +S!@T*|P!@T:2<;2N N /2N N +S!@TU
+^'WrBɕܩurex w*dx|gxY>urex w*dx\ȝ
+^'WrBWw
+^'WrBɕܩurex w*dx|gxY>urex w*dx\ȝ3ν2<P2J3T:2<;5fx#2N N /5gx{ex6&dxZ
+^'Wr r+jgBש۩urex wj:@mL*{P!@T:2<;2N N /2N N +S!@T*|P!@T:2<;2N N _exQ>urex w*dx\ȝ
+^'WrBWw
+^'WrBɕܩurex w*dx|gxY>urex w*dx\ȝ
+^ĺ >?P'߁N;Ro 9^o
+}:T~?''ƄCwjNC7CGCw~hPݩ~hp;~h;5rYLx?tTx?t'A~N}y?4?Еx:~h;~h;~h;]|PН\SН\SН\SЕ|:~h;~h;~h;]|PakNaNa
+;):)@T{"):)@T
+;):)@T{"):)@T
+;):)@ThWQ>0L5Lr0E'0ȝ
+\ w* ST=L僚):
+;5STn Sd3aSkNaNa
+;5St5Ljc0E=0L5Lraν)@mL
+|Sd0E'0ȝ
+\ w* Str Sܩ0LQ0E* Str Sܩ0L5Lr0E'0ȝ
+|Sd0E'0ȝ
+\ w* Str Sܩ0LQ0E* Str Sܩ0L5Lr0E'0ȝ
+j"'):)@T
+;):)@T{"):)@T
+;5St5Ljc0E=0L5LraN}
+p;):)@<LQ5L΄aNa
+p;):)@<Lѹ0 zSd0E'0ȝ
+\ w* Str Sܩ0LQ0E* Str Sܩ0L5Lr0E'0ȝ
+|Sd0E'0ȝ
+\ w* Str Sܩ0Lȯ)|RakNaNa
+;):)@T{"):)@T
+;)¢B;Lh S\4L>>Lr8hb=)/ƿ_T?y!|vslwR?N?tH?<)S!JATH;j;J,TH;j;NN+S!;A+S!JATH;j;J,TH;j;NN+S!;A+S!JATH;j;J,TH;j;NN+S!;A+S!JATH;j;J,TH;j;NN+S!nWIuO*$՝\I5ȝ
+Iu'WR rBRɕTܩTWTg椺sƄS+S!JAԜTWn%YLH;jp;NNIu^I5 IuIuv*$՝\I5ȝνjPNN:5'՝{%ՠ6&$՝ZI5
+Iu'WR rBR]wR
+Iu'WR rBRɕTܩTwr% w*$Օ|'Y>Twr% w*$՝\I5ȝ
+Iu'WR rBR]wR
+Iu'WR rBRɕTܩTwr% w*$Օ|'Y>Twr% w*$՝\I5ȝ
+Iu'WR rBR]wR
+Iu'WR rBRɕTܩTwr% w*$Ս*I+S!JATH;j;J,TH;j;NN+S!;A+S!JATH;j;J,TH;j;NNIu^I5 IuIuv*$՝\I5ȝN}IAIu'VR jBR]wR僚νjPNN+SsR]Tg3!JTH;j;5'՝{%ՠ6&$Օz'=Twr% w*$՝\I5ȝ
+Iu'WR rBR]wR
+Iu'WR rBRɕTܩTwr% w*$Օ|'Y>Twr% w*$՝\I5ȝ
+Iu'WR rBRȯ(TH;j;NN+S!;A+S!JATH;j;J,TH;j;NN:mR h%(%귿Gv*Izl%էǒ_lz8}eӯ/7L,G\}y|9^wϟ~O䗧Q-t(w tQmͯo}=7(w v?jc02w~yN;hO\O|Ro?k0y|?=v Gs|(wGk+aR;txz}iit;p|ym4ʝzCrxoϢ#*{$O:O@T??SakNJO|PakNN;O:O@T?{$O:O@T??SakNJO|PakNN;O:O@T?{$O:O@T??SakNJO|PakNN;O:O@T?{$O:O@T??SakNJO|PakNN;O:O@T?iW'Q>05rI' ȝ
+'\' w*̟T=僚O:?1aSkNN;5ϟTn͟d3aSkNN;5ϟt5jcI'=05rνO@mL?Ԛ?Sa,<ҹ 'Z'v*̟tr͟ܩ0RI*̟tr͟ܩ05rI' ȝ
+'|ϟdI' ȝ
+'\' w*̟tr͟ܩ0RI*̟tr͟ܩ05rI' ȝ
+'|ϟdI' ȝ
+'\' w*̟tr͟ܩ0RI*̟tr͟ܩ05rI' ȝ
+'j$'O:O@T??SakNJO|PakNN;O:O@T?{$O:O@T??SakNJO|PakNN;5ϟt5jcI'=05rN}?G"̟tb͟ک0RIj?kƄNp;O:O@<R5΄Np;O:O@<ҹ 'zϟdI' ȝ
+'\' w*̟tr͟ܩ0RI*̟tr͟ܩ05rI' ȝ
+'|ϟdI' ȝ
+'\' w*̟tr͟ܩ0ȯO|RakNN;O:O@T?{$O:O@T??SakNJO|PakNN;O~G;h͟\͟ϟid=O.'͟|/?ݯ_߿?ϗg~?˿{truC t;nh*AN;nh:nh@T亡Sᆦ,T亡SᆦNN;nh*AN;nh:nh@T亡Sᆦ,T亡SᆦNN;nh MO*trЀܩpCuCr M' ȝ
+74|d M' ȝ
+74\74 w*trЀܩpCS M*trЀܩpCuCr M' ȝ
+74|d M' ȝ
+74\74 wjƄJoh{PᆦN74rCDĺSᆦ,|Cӹ 74Z74v*trЀܩr& 74Z74v*trЀܩsPnh*AN;nh:nh@T亡Sᆦ,T亡SᆦNN;nh*AN;nh:nh@T亡Sᆦ_D M' ȝ
+74\74 w*trЀܩpCS M*trЀܩpCuCr M' ȝ
+74|d M' ȝ
+74\74 w*ē>к@&|oS+tCs÷%~|=yuͺ__~\___w~e\\_/~wOK~v>|íә(`EO>OX1aQRETXZSX^b6&,uj-۩(VXj^kQ ƄENE1p;:@TX{Q,:@TX了SNkJ!|PNkNkH;!;!AT2!;!AT了SNkJ!|PNkNkH;!;!AT2!;!AT了SNkF~u 
+א\א w*\Cvr]Cܩp u r5d%אY>p u r5d'5$ȝ
+א\א w*\CV}
+א\א w*\Cvr]Cܩp u r5d%אY>p u r5d'5$ȝ!;1RkT了S5d\C#!;!AT25_Cvu jc5d5$
+א\א wjܺjg5d5$
+א\א wjƄkJ!{PNkNkH;!;!AT2!;!AT了SNkJ!|PNkNkH;!;!ATlWאQ>p u r5d'5$ȝ
+א\א w*\CV}
+א\א w*\Cvr]Cܩp u r5d%אY>p u r5d'5$ȝ
+!?5dD5t אO=]!/אo/_ww߿Kt}_/ӧ_2o?q|9=vr8Q>1wa^Owo}"<r~.8^S$驕_FSoȟw׾FSo_O~yN\x<I>7kQO~i;߭(wO~i s<(w sw~i;S_FS??ߝ}j4txx~h4ʝzCx<[i;y|(wO$O~i;ӹn4ʝzC~_Zi;ss~i ps_Q]QX헧Qϯ/o{ˍ\O|Ro?V~yN!?tOV~yN!?tϨ_FS?$OUzƼa>hwFSo?;<rިϯaR;ty՗Նvr wj.N;*NAmL(N+.N{P8*NA\vUژPvjv*|Y>8ܫ818*NT(N;S;J,T(N;S;NNⴓ8S88Aⴓ8S8*NAT(N;S;J,T(N;S;NNⴓ8S88Aⴓ8S8*NAT(N;S;J,T(N;S;NNⴓ8S8mWiO*\)ȝ
+i'Wq
+rBqUܩPV]fBqUܩPvr w*\)ȝ
+i%i*\)ȝ
+i'Wq
+rBqUܩPV]fBqUܩPvr wj.N;*NAmL(N+.N{P8*NAXvKq
+D(N;SP;J,\vUژPvjv*\)ȝʭ4 iVq
+nBqUܩ8ܫ818Ի8Aⴓ8S8*NAT(N;S;J,T(N;S;NNⴓ8S88Aⴓ8S8*NAT(N;S;F~UFBqUܩPvr w*\)ȝ
+i%i*\)ȝ
+i'Wq
+rBqUܩPV]fBqUܩPvr w*lS@8@8 Ǘ^x|=wz:O?c9=wp|~~}4o?giz:U]KN_sLǷ_ϭ>|y~>'(wx8$/Oܩ7
+Nmq)_m1ɝN䃚Rb;5ť|$wjnKjIwr ܩ-.-&Ss[\W[Lr涸-涸Nmq)_m1ɝRb;5ŝ\m15ť|$wjnKjIܩ-jA>-.-&Ss[\W[Lr涸Nmq'W[ Amq)_m1ɝRb;5ť|$wjn+n|Rs[\W[Lr涸Nmq)_m1ɝN}iIm۩-.-&Sc[ܹW[ jg涸TNmq)_m1ɝ}iImwjܩ-.ݗmq^m1N}iIm۩-.-&Ss[|Ps[\W[Lr涸Nmq)_m1ɝN䃚Rb;5ť|$wjnKjIwr ܩ-.-&Ss[\W[Lr涸-涸Nmq)_m1ɝRb;5ŝ\m15ť|$wjnKjIܩ--Imq)_m1ɝRb;5ť|$wjn;bjnKjIܩ-.-&Ss[|Ps[\W[Lr涸Nmq)_m1ɝN䃚Rb;5ť|$wjlK-&1s[ܩ{Ps[\W[LrT$Hmq)^m1N}iIm۩-.-&Sc[ܹW[ jg涸TNmq)_m1ɝ}iImwjܩ-.-&Ss[\W[Lr涸-涸Nmq)_m1ɝRb;5ŝ\m15ť|$wjnKjIܩ--Imq)_m1ɝRb;5ť|$wjn;bjnKjIܩ-.-&Ss[|Ps[\W[Lr涸Nmq`˶>(p~Y?:ej/1w?̂Z6ʻp+)>Aϫ>|gߟ}"<rސ칓+S!䊩AT+|P!䊩AT;bj;bNN:bNN+S!䊩AT+|P!䊩AT;bj;bNN:bNN+S!䊩AT+|P!䊩AT;bj;bNN:bNN+S!䊩AT+|P!䊩AT;bj;bNN_Q>Swr w*ԝ\15ȝ
+1u'WL rBL]wL僚cνbjPbNN+SsL]Sg3!ԊT;bj;5ԝ{Ԡ6&ԕz=Swr wj;AmL;bjp;bJc,SwSژSwjv*ԝ\15ȝ
+1u%1u*ԝ\15ȝ
+1u'WL rBLSܩSWSgBLSܩSwr w*ԝ\15ȝ
+1u%1u*ԝ\15ȝ
+1u'WL rBLSܩSWSgBLSܩSwr w*ԝ\15ȝ
+1u%1u*ԝ\15ȝ
+1u'WL rBLSܩS7:'bNN+S!䊩AT+|P!䊩AT;bj;bNN:bNN+S!䊩AT+|P!䊩AT;bj;5ԝ{Ԡ6&ԕz=Swr wj;%G"ԝX15
+1u%1uj;AmL;bjp;bNN1uVL΄S+S!䊩ASwSژSWSgBLSܩSwr w*ԝ\15ȝ
+1u%1u*ԝ\15ȝ
+1u'WL rBLSܩSWSgBLSܩSwr w*ԝ\15ȝ
+1u#|R!䊩AT;bj;bNN:bNN+S!䊩AT+|P!䊩AT;bj;bX
+uN%uNjs:s@mLs:p;N:NuNV΄:SSs@\tUژPT]dBUܩܫ1ԪsTs*s|PsӹWjcBөU۩Ptr9 w*9|9Y>Ptr9 w*9\uȝ
+uN'WrBSw
+uN'WrBUܩPtr9 w*9|9Y>Ptr9 w*9\uȝ
+uN'WrBSw
+uN'WrBUܩPtr9 w*9|9Y>Ptr9 w*9\uȝ
+uN'WrBȯ(Ts:;N:N:SΩA:Ss@Ts:;J,Ts:;N:N:SΩA:Ss@\tUژPT]dBUܩԗ:Ptb9v*9|9Y>ܫ1ԪsTs:;59[uNV;N:N:SsӹWjcBSw݃
+uN'WrBUܩPtr9 w*9|9Y>Ptr9 w*9\uȝ
+uN'WrBSw
+uN'WrBUܩPtr9 w*9ΉI:Ss@Ts:;J,Ts:;N:N:SΩA:Ss@Tsb=9V󡁹]O79_N?BuR?ί_//=h{ +nO~(s~cAxc'(1N7U$eB$ܩ$ur5I w*4I\Mȝ
+MR%MR*4I\Mȝ
+MR'WrB$ܩ$U$eB$ܩ$ur5I w*4I\Mȝ
+MR#j|RIj@Th:$;N& N&&)57I{5I6&4IZM
+MR'Wr&rIjgBԩ$۩$ur5I wjn:j@mLh*n{PIj@$u$ژ$uj5Iv*4I|7IY>IܫI1IjTh:$;J,Th:$;N& N&ISIIA&ISIj@Th:$;J,Th:$;N& N&ISIIA&ISIj@Th:$;J,Th:$;N& N&ISIjWMRO*4I\Mȝ
+MR'WrB$ܩ$U$eB$ܩ$ur5I w*4I\Mȝ
+MR%MR*4I\Mȝ
+MR'WrB$ܩ$U$eB$ܩ$ur5I wjn:j@mLh*n{PIj@$uKDh:$P;J,$u$ژ$uj5Iv*4I\Mȝʭ&) MRVnB$ܩIܫI1IԻIA&ISIj@Th:$;J,Th:$;N& N&ISIIA&ISIj@Th:$;F~$EB$ܩ$ur5I w*4I\Mȝ
+MR%MR*4I\Mȝ
+MR'WrB$ܩ$U$eB$ܩ$ur5I w*4I$@IPo7IM|z&i=5I?QM0OvpOt|z}U 㷿(W}Jrx{}"<r~.?w|=MA!?_Zi;p~i;\헧Q۟f*zzC~<_zN!?tN׾FSo?]_FSod$O'N~yN!_h'W]
+rB]UܩPV]fB]UܩPvrե w*ԥ\u)ȝ
+ui%ui*ԥ\u)ȝ
+ui'W]
+rB]UܩPV]fB]UܩPvrե w*ԥ\u)ȝ
+ui#K|R.KATK;R;NN45ץ{ե6&ԥZu)
+ui'W]
+r溴r.jgB]کU۩Pvrե wjK;KAmLK+K{P.KA\vUژPvjեv*ԥ|ץY>.ܫ.1.ԪKTK;R;J,TK;R;NN.S..A.S.KATK;R;J,TK;R;NN.S..A.S.KATK;R;J,TK;R;NN.S.mWuiO*ԥ\u)ȝ
+ui'W]
+rB]UܩPV]fB]UܩPvrե w*ԥ\u)ȝ
+ui%ui*ԥ\u)ȝ
+ui'W]
+rB]UܩPV]fB]UܩPvrե wjK;KAmLK+K{P.KAXvK]
+DK;RP;J,\vUژPvjեv*ԥ\u)ȝʭ4 uiV]
+nB]Uܩ.ܫ.1.Ի.A.S.KATK;R;J,TK;R;NN.S..A.S.KATK;R;F~UFB]UܩPvrե w*ԥ\u)ȝ
+ui%ui*ԥ\u)ȝ
+ui'W]
+rB]UܩPV]fB]UܩPvrե w*ԥlR@.p4|>_o:ե]?/yzK\O/aӶ'ϿB߄O>c>_>A4_&1oOY~$ОW}yB;@0l?Kޏ^&1o?sU_&1?7?R؈/ς׈7K#^ؘ7O}-0yrxjosp|+0xy|i<y=Lfc0o]/٘7}m_&1o,=&SmW0yüF0*5٘Q5ƄQƼ75ٙQ5Ƅ9ʬ5l6&liTfMid1aH1hv&hTfhd1aB2kA# YlLh{;# YlLͨZfcfFedF63ˈfgZFeXF62*2٘Q5ƄƼ72ٙQ5Ƅyʬul6&lcTfMcd1a1]hv&bTfbd1a2k# {YslL(W[<Q5Ƅʬl6&l`TfM`d1a
+2k " ;Y3lLh{" YlLZfcɂv!pno'o-l~zj>*ďo] ww|*>㮦?dȦ?@Sc9ݩ/4?!ĻA~+S!jAT;2j;BJK,Th;bj;rNN+S!仪A+S!jAT;j;J ,Th;"k;2NNҺ+S!件A޺+S!jAT;k;J,Th;k;NN+S!®A+S!jAT;rl;J,Th;l;N.N2+S!nWuvO*ٝ\6ȝ
+v'W rBɕiܩjW]jgVsXƄ\SSJAmWnUYL;mp;NvNv^6 wwv*4ܝ\7ȝ3ν:nPJNN;5ܝ{ݠ6&$ݝZM7
+Uw'W rB]wٝ
+mw'W rBwܩPxwr% w*Dޕ|WY>ywr w*ޝ\7ȝ
+w'W rB]w
+w'W rB}ܩP~wr w*ߕ|Y>wr w*$\ 8ȝ
+x'WrB^w
+-x'W rBՃܩPwr% w*D
+I.+ S! jAT;p;J ,Th;"q;2NNNR+S!A^+S!jAT;q;J,Th;q;N~Ny^ 9 yyv*t\!9ȝSN}iA5y'VNjBP^wQ僚νrPNN+-Ss\^Ug3/
+TH;s;5W{e6&z=Кwr w*\9ȝ
+y'WrrBt^wu
+y'WxrBz՞ܩPwr w*|Y>РwrE w*d\:ȝ
+%z'WrBȯj(T;t;N&N*+KS!LLA6+NS!OAT(;u;"J+,T;Bu;RNVNZ=&]hO7ozzjb|ן_x?w??/w߿go?]?/^n|(.!(1}qQ
+Gq|e棸ν@mL8:S((NJ|P((NN8;:@T8(.:@T8:S((NJ|P((NN8;:@T8(.:@T8:S((NJ|P((NN8;:@T8kWGqQ>purQ\'Qȝ
+Gq\Gq w*U}
+Gq\Gq w*urŁܩpurQ\%GqY>purQ\'Qȝ
+Gq\Gq w*U}
+Gq\Gq w*urŁܩ(s8P*>AN8;5uQ?((NJ|PQ\^Gq6&ujŁ۩pur棸ʭv&ujŁ۩pur棸ν@mL8(.:@T8:S((NJ|P((NN8;:@T8(.:@T8:S((NF~u
+Gq\Gq w*urŁܩpurQ\%GqY>purQ\'Qȝ
+Gq\Gq w*U}
+Gq\Gq w*urŁܩp8@(Y8 O&y;z|8ӧO=ϧo9^x^ϿxxSoׯ7?mQDendstream
+endobj
+1224 0 obj <<
+/Type /Page
+/Contents 1225 0 R
+/Resources 1223 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 1215 0 R
+/Annots [ 1227 0 R 1231 0 R 1232 0 R 1233 0 R 1234 0 R 1235 0 R 1236 0 R 1237 0 R 1238 0 R 1239 0 R 1240 0 R 1241 0 R 1242 0 R 1243 0 R 1244 0 R 1245 0 R 1246 0 R 1247 0 R 1248 0 R 1249 0 R 1250 0 R 1251 0 R 1252 0 R 1253 0 R 1254 0 R 1255 0 R 1256 0 R 1257 0 R 1258 0 R 1259 0 R 1260 0 R 1261 0 R 1262 0 R 1263 0 R 1264 0 R 1265 0 R 1266 0 R 1267 0 R 1268 0 R 1269 0 R 1270 0 R 1271 0 R 1272 0 R 1273 0 R 1274 0 R 1275 0 R 1276 0 R 1277 0 R 1278 0 R 1279 0 R 1280 0 R 1281 0 R 1282 0 R 1283 0 R 1284 0 R 1285 0 R 1286 0 R 1287 0 R 1288 0 R 1289 0 R 1290 0 R 1291 0 R 1292 0 R 1293 0 R 1294 0 R 1295 0 R 1296 0 R 1297 0 R 1298 0 R 1299 0 R 1300 0 R 1301 0 R 1302 0 R 1303 0 R 1304 0 R 1305 0 R 1306 0 R 1307 0 R 1308 0 R 1309 0 R 1310 0 R 1311 0 R 1312 0 R 1313 0 R 1314 0 R 1315 0 R 1316 0 R 1317 0 R 1318 0 R 1319 0 R ]
+>> endobj
+1227 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [70.7348 678.8395 159.0925 687.8158]
+/Subtype /Link
+/A << /S /GoTo /D (about) >>
+>> endobj
+1231 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [532.005 678.8395 538.9788 687.8158]
+/Subtype /Link
+/A << /S /GoTo /D (about) >>
+>> endobj
+1232 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 661.4597 204.4624 672.3637]
+/Subtype /Link
+/A << /S /GoTo /D (copyright) >>
+>> endobj
+1233 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [532.005 661.4597 538.9788 672.3637]
+/Subtype /Link
+/A << /S /GoTo /D (copyright) >>
+>> endobj
+1234 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 650.5656 157.7875 659.4122]
+/Subtype /Link
+/A << /S /GoTo /D (disclaimer) >>
+>> endobj
+1235 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [532.005 650.5656 538.9788 659.4122]
+/Subtype /Link
+/A << /S /GoTo /D (disclaimer) >>
+>> endobj
+1236 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 637.6142 169.4341 646.4608]
+/Subtype /Link
+/A << /S /GoTo /D (newversions) >>
+>> endobj
+1237 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [532.005 637.6142 538.9788 646.4608]
+/Subtype /Link
+/A << /S /GoTo /D (newversions) >>
+>> endobj
+1238 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 624.6627 142.8538 633.5094]
+/Subtype /Link
+/A << /S /GoTo /D (credits) >>
+>> endobj
+1239 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [532.005 624.6627 538.9788 633.5094]
+/Subtype /Link
+/A << /S /GoTo /D (credits) >>
+>> endobj
+1240 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 611.7113 207.8898 620.5579]
+/Subtype /Link
+/A << /S /GoTo /D (conventions) >>
+>> endobj
+1241 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [532.005 611.7113 538.9788 620.5579]
+/Subtype /Link
+/A << /S /GoTo /D (conventions) >>
+>> endobj
+1242 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [70.7348 594.446 160.477 605.3251]
+/Subtype /Link
+/A << /S /GoTo /D (installing-bugzilla) >>
+>> endobj
+1243 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [532.005 594.446 538.9788 605.3251]
+/Subtype /Link
+/A << /S /GoTo /D (installing-bugzilla) >>
+>> endobj
+1244 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 581.0264 158.9033 589.873]
+/Subtype /Link
+/A << /S /GoTo /D (installation) >>
+>> endobj
+1245 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [532.005 581.0264 538.9788 589.873]
+/Subtype /Link
+/A << /S /GoTo /D (installation) >>
+>> endobj
+1246 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 568.0749 161.5041 576.9216]
+/Subtype /Link
+/A << /S /GoTo /D (install-perl) >>
+>> endobj
+1247 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [532.005 568.0749 538.9788 576.9216]
+/Subtype /Link
+/A << /S /GoTo /D (install-perl) >>
+>> endobj
+1248 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 553.0662 212.6818 563.9701]
+/Subtype /Link
+/A << /S /GoTo /D (install-database) >>
+>> endobj
+1249 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [532.005 553.0662 538.9788 563.9701]
+/Subtype /Link
+/A << /S /GoTo /D (install-database) >>
+>> endobj
+1250 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [142.4658 540.1148 209.4942 551.0187]
+/Subtype /Link
+/A << /S /GoTo /D (install-mysql) >>
+>> endobj
+1251 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [532.005 540.1148 538.9788 551.0187]
+/Subtype /Link
+/A << /S /GoTo /D (install-mysql) >>
+>> endobj
+1252 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [142.4658 527.1634 225.5437 538.0673]
+/Subtype /Link
+/A << /S /GoTo /D (install-pg) >>
+>> endobj
+1253 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [532.005 527.1634 538.9788 538.0673]
+/Subtype /Link
+/A << /S /GoTo /D (install-pg) >>
+>> endobj
+1254 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [142.4658 516.2692 203.387 525.1159]
+/Subtype /Link
+/A << /S /GoTo /D (install-oracle) >>
+>> endobj
+1255 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [532.005 516.2692 538.9788 525.1159]
+/Subtype /Link
+/A << /S /GoTo /D (install-oracle) >>
+>> endobj
+1256 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 503.3178 191.8105 512.1644]
+/Subtype /Link
+/A << /S /GoTo /D (install-webserver) >>
+>> endobj
+1257 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [532.005 503.3178 538.9788 512.1644]
+/Subtype /Link
+/A << /S /GoTo /D (install-webserver) >>
+>> endobj
+1258 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 488.3091 179.2174 499.213]
+/Subtype /Link
+/A << /S /GoTo /D (install-bzfiles) >>
+>> endobj
+1259 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [532.005 488.3091 538.9788 499.213]
+/Subtype /Link
+/A << /S /GoTo /D (install-bzfiles) >>
+>> endobj
+1260 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 477.4149 198.8636 486.2616]
+/Subtype /Link
+/A << /S /GoTo /D (install-perlmodules) >>
+>> endobj
+1261 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [532.005 477.4149 538.9788 486.2616]
+/Subtype /Link
+/A << /S /GoTo /D (install-perlmodules) >>
+>> endobj
+1262 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 462.4062 257.3339 473.3101]
+/Subtype /Link
+/A << /S /GoTo /D (install-MTA) >>
+>> endobj
+1263 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [532.005 462.4062 538.9788 473.3101]
+/Subtype /Link
+/A << /S /GoTo /D (install-MTA) >>
+>> endobj
+1264 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 449.4548 272.4767 460.3587]
+/Subtype /Link
+/A << /S /GoTo /D (using-mod_perl-with-bugzilla) >>
+>> endobj
+1265 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [532.005 449.4548 538.9788 460.3587]
+/Subtype /Link
+/A << /S /GoTo /D (using-mod_perl-with-bugzilla) >>
+>> endobj
+1266 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 436.5033 169.4242 447.4073]
+/Subtype /Link
+/A << /S /GoTo /D (configuration) >>
+>> endobj
+1267 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [532.005 436.5033 538.9788 447.4073]
+/Subtype /Link
+/A << /S /GoTo /D (configuration) >>
+>> endobj
+1268 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 423.5519 189.7281 434.4558]
+/Subtype /Link
+/A << /S /GoTo /D (localconfig) >>
+>> endobj
+1269 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [532.005 423.5519 538.9788 434.4558]
+/Subtype /Link
+/A << /S /GoTo /D (localconfig) >>
+>> endobj
+1270 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 412.6577 210.3107 421.5044]
+/Subtype /Link
+/A << /S /GoTo /D (database-engine) >>
+>> endobj
+1271 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 412.6577 538.9788 421.5044]
+/Subtype /Link
+/A << /S /GoTo /D (database-engine) >>
+>> endobj
+1272 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [142.4658 397.649 283.635 408.553]
+/Subtype /Link
+/A << /S /GoTo /D (database-schema) >>
+>> endobj
+1273 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 397.649 538.9788 408.553]
+/Subtype /Link
+/A << /S /GoTo /D (database-schema) >>
+>> endobj
+1274 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [142.4658 384.6976 209.4942 395.6015]
+/Subtype /Link
+/A << /S /GoTo /D (mysql) >>
+>> endobj
+1275 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 384.6976 538.9788 395.6015]
+/Subtype /Link
+/A << /S /GoTo /D (mysql) >>
+>> endobj
+1276 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [142.4658 371.7462 225.5437 382.6501]
+/Subtype /Link
+/A << /S /GoTo /D (postgresql) >>
+>> endobj
+1277 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 371.7462 538.9788 382.6501]
+/Subtype /Link
+/A << /S /GoTo /D (postgresql) >>
+>> endobj
+1278 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [142.4658 360.852 203.387 369.6987]
+/Subtype /Link
+/A << /S /GoTo /D (oracle) >>
+>> endobj
+1279 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 360.852 538.9788 369.6987]
+/Subtype /Link
+/A << /S /GoTo /D (oracle) >>
+>> endobj
+1280 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 345.8433 199.9596 356.7472]
+/Subtype /Link
+/A << /S /GoTo /D (536) >>
+>> endobj
+1281 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 345.8433 538.9788 356.7472]
+/Subtype /Link
+/A << /S /GoTo /D (536) >>
+>> endobj
+1282 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 334.9492 190.1467 343.7958]
+/Subtype /Link
+/A << /S /GoTo /D (http) >>
+>> endobj
+1283 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 334.9492 538.9788 343.7958]
+/Subtype /Link
+/A << /S /GoTo /D (http) >>
+>> endobj
+1284 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [142.4658 319.9404 267.5956 330.8444]
+/Subtype /Link
+/A << /S /GoTo /D (http-apache) >>
+>> endobj
+1285 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 319.9404 538.9788 330.8444]
+/Subtype /Link
+/A << /S /GoTo /D (http-apache) >>
+>> endobj
+1286 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [142.4658 309.0463 335.9283 317.8929]
+/Subtype /Link
+/A << /S /GoTo /D (http-iis) >>
+>> endobj
+1287 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 309.0463 538.9788 317.8929]
+/Subtype /Link
+/A << /S /GoTo /D (http-iis) >>
+>> endobj
+1288 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 294.0376 179.2174 304.9415]
+/Subtype /Link
+/A << /S /GoTo /D (install-config-bugzilla) >>
+>> endobj
+1289 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 294.0376 538.9788 304.9415]
+/Subtype /Link
+/A << /S /GoTo /D (install-config-bugzilla) >>
+>> endobj
+1290 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 281.0861 251.894 291.9901]
+/Subtype /Link
+/A << /S /GoTo /D (extraconfig) >>
+>> endobj
+1291 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 281.0861 538.9788 291.9901]
+/Subtype /Link
+/A << /S /GoTo /D (extraconfig) >>
+>> endobj
+1292 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 268.1347 193.3247 279.0386]
+/Subtype /Link
+/A << /S /GoTo /D (687) >>
+>> endobj
+1293 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 268.1347 538.9788 279.0386]
+/Subtype /Link
+/A << /S /GoTo /D (687) >>
+>> endobj
+1294 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 255.1833 220.7218 266.0872]
+/Subtype /Link
+/A << /S /GoTo /D (installation-whining-cron) >>
+>> endobj
+1295 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 255.1833 538.9788 266.0872]
+/Subtype /Link
+/A << /S /GoTo /D (installation-whining-cron) >>
+>> endobj
+1296 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 242.2318 180.3235 253.1358]
+/Subtype /Link
+/A << /S /GoTo /D (installation-whining) >>
+>> endobj
+1297 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 242.2318 538.9788 253.1358]
+/Subtype /Link
+/A << /S /GoTo /D (installation-whining) >>
+>> endobj
+1298 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 229.2804 356.441 240.1843]
+/Subtype /Link
+/A << /S /GoTo /D (apache-addtype) >>
+>> endobj
+1299 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 229.2804 538.9788 240.1843]
+/Subtype /Link
+/A << /S /GoTo /D (apache-addtype) >>
+>> endobj
+1300 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 216.329 325.4962 227.2329]
+/Subtype /Link
+/A << /S /GoTo /D (multiple-bz-dbs) >>
+>> endobj
+1301 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 216.329 538.9788 227.2329]
+/Subtype /Link
+/A << /S /GoTo /D (multiple-bz-dbs) >>
+>> endobj
+1302 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 203.3775 235.2761 214.2815]
+/Subtype /Link
+/A << /S /GoTo /D (os-specific) >>
+>> endobj
+1303 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 203.3775 538.9788 214.2815]
+/Subtype /Link
+/A << /S /GoTo /D (os-specific) >>
+>> endobj
+1304 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 192.3638 224.7763 201.33]
+/Subtype /Link
+/A << /S /GoTo /D (os-win32) >>
+>> endobj
+1305 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 192.3638 538.9788 201.33]
+/Subtype /Link
+/A << /S /GoTo /D (os-win32) >>
+>> endobj
+1306 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [142.4658 179.4124 222.0968 188.3786]
+/Subtype /Link
+/A << /S /GoTo /D (win32-perl) >>
+>> endobj
+1307 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 179.4124 538.9788 188.3786]
+/Subtype /Link
+/A << /S /GoTo /D (win32-perl) >>
+>> endobj
+1308 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [142.4658 166.5805 271.9096 175.4272]
+/Subtype /Link
+/A << /S /GoTo /D (win32-perl-modules) >>
+>> endobj
+1309 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 166.5805 538.9788 175.4272]
+/Subtype /Link
+/A << /S /GoTo /D (win32-perl-modules) >>
+>> endobj
+1310 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [142.4658 151.5718 266.7589 162.4758]
+/Subtype /Link
+/A << /S /GoTo /D (win32-http) >>
+>> endobj
+1311 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 151.5718 538.9788 162.4758]
+/Subtype /Link
+/A << /S /GoTo /D (win32-http) >>
+>> endobj
+1312 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [142.4658 138.6204 235.7852 149.5243]
+/Subtype /Link
+/A << /S /GoTo /D (win32-email) >>
+>> endobj
+1313 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 138.6204 538.9788 149.5243]
+/Subtype /Link
+/A << /S /GoTo /D (win32-email) >>
+>> endobj
+1314 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 127.7262 188.0644 136.5729]
+/Subtype /Link
+/A << /S /GoTo /D (os-macosx) >>
+>> endobj
+1315 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 127.7262 538.9788 136.5729]
+/Subtype /Link
+/A << /S /GoTo /D (os-macosx) >>
+>> endobj
+1316 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [142.4658 114.7748 214.4752 123.6215]
+/Subtype /Link
+/A << /S /GoTo /D (macosx-sendmail) >>
+>> endobj
+1317 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 114.7748 538.9788 123.6215]
+/Subtype /Link
+/A << /S /GoTo /D (macosx-sendmail) >>
+>> endobj
+1318 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [142.4658 101.8234 336.4965 110.67]
+/Subtype /Link
+/A << /S /GoTo /D (macosx-libraries) >>
+>> endobj
+1319 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 101.8234 538.9788 110.67]
+/Subtype /Link
+/A << /S /GoTo /D (macosx-libraries) >>
+>> endobj
+1226 0 obj <<
+/D [1224 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+6 0 obj <<
+/D [1224 0 R /XYZ 244.3315 703.236 null]
+>> endobj
+1223 0 obj <<
+/Font << /F23 1214 0 R /F32 1230 0 R /F27 1222 0 R /F33 1322 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+1370 0 obj <<
+/Length 57774
+/Filter /FlateDecode
+>>
+stream
+xڔ_}{
+\(wKIr2E9aKHD@hw]=]'Ouz򛌍XT
+4=5:\4\K+QK#Ss-G-pO͵4=5:\4\K+QK#Ss-G-pO͵4=5:\4\K+QK#Ss-G-pO͵4=5:\4\K+QK#Ss-G-pO͵4=5*|2ZZZរki%>ji{j᪥ZZijiD[fV⣖FZZgZОki%=jid{jVڷZіkiZKjVڷZіki%=jid{juji/VڷZіki%=jid{j᪥ZZZរki%>ji{j᪥ZZZរki%>ji{j᪥ZZZរki%>ji{j᪥ZZZរki%>ji{j᪥ZZZរki%>ji{jV᳖55J|\K+QK#Ss-G-pO͵W- %5J|\K+QK#Ss-G-pO͵W- %5J|\K+QK#Ss-G-pO͵W- %5J|\K+QK#Sc-o4-3:Z4\K+QK#SS-kiL̵4=5:\4XK+[-h̵4=5J|XKQK3s-G-lO͵4=5JVK#2s-UK{I͵4=5J|\K+QK#Ss-UK|I͵4=5J|\K+QK#Ss-UK|I͵4=5J|\K+QK#Ss-g--kjV⣖FZZZរkiZKjV⣖FZZZរkiZKjV⣖FZZJ4@>kiݽo/g6_:.M=6iݨ}}qw޿]!wt&E9s>n@/y`?enȗtnh{ӀGv#m'sx9i=uh;=>FD{|ܽ^xmO\xqO/+,7ڻO>&zIo5.d=5,˒ /K&S˒;\/K|I/K.d=5,˒ /K&S˒;\/K|I/K.d=5,˒ /K&S˒;\/K|I/K.d=5,ĵJ*EkpOU
+_RaõJ*EkpOU*
+>W)2*EkpOU*
+U
+=V)*|Rd|IU*
+U
+=V):\{*RT\
+U
+=V):\{*RtV)
+_R*EgU
+-V):Z`{*RtV)
+>W)2U@[&RtV)TXpR
+_RaõJ*EkpOU*
+>W)2*EkpOU*
+U
+=V)n"k*RtV)
+_RaõJ*EkpOͫ=V)LXJ%V):\{j\*aJ*EU/y*Ж U
+=V):\{j^lRd3aJ*EkpOͫ=V)LXJ%V):\{*RtV)
+U
+=V):\{*RT\
+U
+=V):\{*RtV)
+*|Ja!RORhuwhb<6V)U oU5Gq.m'܇B\j<@+>&|Iⷧ ew|ڟZ4➺_v?uH[
+~w|<M
+W:{,
++=5.tmyRsy%K*,tW
++_RayõJkypO
+
++>W2JkypO
+
++=W*|.d|I
+
++=W:\+{*,T\^
++=W:\+{*,tW
+
++>W2+@[&,tWTX^p-
++=5/tX^2ayJTX^p-
+Ж +=W*|.d|I+=WLX^h-Z^SayJƗTX^p-
++_RayõJkypO
+
++>W2JkypO
+
++=W*|.d|I
+
++=W:\+{*,4JTX^p-
++_RayõJkypO
+
++>W2JkypO
+W:{,
++_RJg-W:Z+`{*,tW
+W:{,
++=W:\+{*,tW
+»X;\bSX;{h˄wV|kTxk]{j~kgwm.֎ֻXTxk߽5kj~kgwm.֎ֻXTxk]{j~ke]LxkG]`{*.V=5obc"X3»X;\bS]w.׻XTxkwf|Iwv
+»X;\bS]w.
+b
+bppOwv
+»X;\bS]>Ś%z+
+bppOwv
+»X+|5K*.V=z+
+bppOwV|kƗTxk]{*.V=z+
+b.֌/.׻XTxk]{*.V=Z]_R]w.׻XTxk]{*|" OkpOMר
+>]=}*|Nd|I>=}Lh 0SOek'Ӟ C?=~:\c?{jeOEɟl/0ZSOg-:Z?`{*T:{,
+@_Raõ 6PkpOy>
+ A>'2HPk%pOP
+SA =ւ*|e|Ib
+A =f:\A{*,U
+A =:\B{*Lu6
+B_RaTõ*®PkXpOi׶
+B>2PkapO
+3C!=*|N e|I
+{C!=5Ou2auPTp-q{oC
+ D>'2+D@[&uT"pmy5GiτA"؞
+DQ"=5u%2a4QT'pO(SaõQJQϙ/0TZ*Sa5V\QkpOŢ
+E_RaõZnQkpOv
+E ~7_5:\ F{*luF
+ =6*|n4d|IF
+ =6:\ {*l4Th
+ =6:\ {*l4t6
+ _Raõ
+ ~56:\ {*l4t6
+ =6*|n4d|IF
+ =6:\ {*l4Th
+ =6:\ {*l4t6
+ =6:\ {*l4Th
+ =6:\ {*l4t6
+ =6*|n4d|IF
+ =6:\ {*l4Th
+ =6:\ {*l4t6
+ _Raõ
+ >72FCkpOF
+ qZh46> _.3m4Fm_~y4p{?Ǘ4ϫf|4554ti
+3 ^Ra5
+3 >g2LCkpOL
+3 =f*|4d|IL
+3 =f:\3 {*4Ti
+3 =f:\3 {*4tf
+3 =f:\3 {*4tf
+3 >g2LCkpOL
+3 =f*|4d|IL
+3 =f:\3 {*4Ti
+3 =f:\3 {*4tf
+3 =f*|4d|IL
+3 =f:\3 {*4Ti
+3 =f:\3 {*4tf
+3 >g2LCkpOL
+3 =f*|4d|IL
+3 =f:\3 {*44LCTip4
+3 _Ra5
+>3@[&܏wTpݏ~u?iτ8؞
+q=5ߏw2~xTpݏ~8Ж q=+|ޏg|I=ǁLhݏp?S~xƗTpݏp?S~u?x/p?S~u?x~pO
+_R~u?x~pO8
+>3x~pO8
+q=+|ޏg|I8
+q=;\{*܏7xTpݏp?S~u?x/p?S~u?x~pO
+_R~u?x~pO8
+>3x~pO8;{܏mp?^~<K*܏wx?ѷq?&x~hO
+_Rxgq-;Z`{*܏w|?^ٺϴgxG~lO8;{܏mp?^~<K*܏wTpݏp?S~xƗTpݏp?S~u?x/p?S~u?x~pO
+q=;\{*܏wTy?%;\{*܏wTpݏp?^~<K*܏wTpݏp?Λq@~Õvhy3uƧ O ~X^woѸrz5rx;s>_ix%u?.sߞFS7ijӈ{~ٝ_0ҖXˡd/qw>|<n/Km?enȷlxy'ٞ_Y_S;KN-3;/$S;;{h/,N=5; Β~y';;X/zI/,N=5; /$S;;\/|I/,N=5; /$S;;\/|I/,N=5; /$S;;\/|I/,N=5; /$S;;\/|I/,N=5; /$S;;\/|I/,N=5; /$S;;\/|I/,N=5; /$S;;\/|I/,N=5; +{j_955_!SJ=5_!SJk|%5m|h+%=W<Rc|pO+=W<Rc|lO+%>W8Rڷ-3tW^RJ=5m|h+%=W<_|I+}_!2JI=5_!SJk|%5_!SJ=5_!SJk|%5_!SJ=5_!SJk|%5_!SJ=5_!SJk|%5_!SJ=5_!SJk|%5_!SJ=5_!SJ񕌯y|
+រWJ|y|
+រW:\+/y|
+រWJ|y|
+រW:\+/y|
+រWJ|y|
+រW:\+/y|
+រWJ|q|o+D[f_h+{j_)?&+D{j_p
+ٞWJ|q|
+ОWJzy|
+WJ6Be
+Kj_)1B+{j_)1B
+Kj_)1B+{j_)1B
+Kj_)1B+{j_)1B
++_SJ=5_!SJ=5tW
+
+YUYUńʃ*{*TU*|VU2BUUUS᪪
+
+U
+U/٣eBUUUS᪪
+*=*@[&TU:ZU=*>*_RsUGUh˄JGBUUUSRᳪ%*
+
+UWUpOJBUgU%K*TU:\U=*
+
+UWUpOJϪJƗTt*{*TU:\U=*
+
+U
+U/PUpUU
+
+UWUpOJBUgU%K*TU:\U=*
+
+UWUpOJϪJƗTt*{*TU:\U=*
+
+U
+U/PUpUU
+UWUpOUUU
+UWUpOJBUUUSRᳪ%*
+
+UWUpOJBUgU%K*TU:\U=*
+
+UWUpOJUU"BUUUS᪪
+UWUpOJBU%t(ڪ
+|QUP UEwr﷗׿$~SyD3zR/?~?wo?ݧ>_Нv~1_UQ%'lއ>
+
+ppOuއ:\S}x>߇%އz
+ppOuއ*|/K*><=އz
+ppOU|^ƗTx^}x{*><=އz
+ë>/>
+%WpOqd Bɸg8K*;\%c=J1
+%WpOqߕ#BɸU2Sd*P2pT(W,g|I%%c-J1؞
+%WpO%V8Ӟ %VlOqd qg1Ж %㊞%l/P2p\2Q22d*P2Y2KƝ=J@[&;Z%c=J1
+%
+%/P2pT(wJƀ{*;\%c=J>K_Rd*P2pT(wJƀ{*+|3BɸU2Sd*P2pT(W,g|Iqd BɸU2Sd*P2Y2
+%WpOqd BɸU2Sdw%㈯P2pT(wJƀ{*;\%c=J>K_Rd*P2pT(wJƀ{*+|3BɸU2Sd*P2pT(W,g|Iqd BɸU2SsɸGh˄qEϒqT(wJƀ{j,wd D(wJ@{*+|3qg1Ж %VlOqd qediτqGd BɸU2SsɸGh˄qEϒqT(wJƀ{*;\%c=J1
+%
+%/P2pT(wJƀ{*;\%c=J>K_Rd*P2pT(wJƀ{*d5J1
+%WpOqd Bɸg8K*;\%c=J1
+%WpOqϒqƗT(wJƀ{*;\%c=Jơۖ.v8|Ot|}x^?_p#ͧ˧_}}ٽ~m일֋͊o{CIߌ/PpU|Tv*{*T|;\_=*>+_RPpU|Tv*{*T|+|V|3BŷUSPpU|TVf|Io BŷUSPY
+WpOo BŷUS[%*/
+WpOo Bŷg7K*T|;\_=*/
+WpOoU|#BŷUSPpU|TVf|I_-*/؞
+WpOV7Ӟ ߎVlOo og/Ж ߊl/PpU|\Q2ѪPY+=*@[&T|;Z_=*/
+
+ߌ/PpU|Tv*{*T|;\_=*>+_RPpU|Tv*{*T|+|V|3BŷUSPpU|TVf|Io BŷUSPY
+WpOo BŷUSw߈PpU|Tv*{*T|;\_=*>+_RPpU|Tv*{*T|+|V|3BŷUSPpU|TVf|Io BŷUSsŷGh˄oEϊoTv*{jv Dv*@{*T|+|V|3og/Ж ߎVlOo oeiτoG BŷUSsŷGh˄oEϊoTv*{*T|;\_=*/
+
+ߌ/PpU|Tv*{*T|;\_=*>+_RPpU|Tv*{*T|5*/
+WpOo Bŷg7K*T|;\_=*/
+WpOoϊoƗTv*{*T|;\_=*V|mOW|Or~Sw<6*[?}E?=x~X{wa-ϗ{~z/#|TS*Pp
+EWpO"^BUSW᳈%x"
+EWpO"^Bg/K*:\E<=x"
+EWpO"^"BUS*Pp
+EWpOEV/Ӟ EVlO"^"^g"Ж EEl/Pp
+E
+E/Pp
+EWpO"^BUSwEPp
+E
+E/Pp
+EWpO"^Bg/K*:\E<=x"
+EWpO"^"^ƗT(ux{*:\E<=x%"އy>s&N2Fxӷ[+_?~z:|%W.Q_w:>mUn^"/1ܼ>@pO7L{&ܼvn^Typݼ浳+Ж 7=o^kpO7=n^LyhݼpZ5Kjyq
+ekGlO+
+7>o^3kpO+
+7W=n^+|޼f|I+
+7W=n^;\7{*ܼVy
+7W=n^;\7{*ܼvn^Tyy%n^;\7{*ܼvn^TypݼpZ5K*ܼvn^TypݼpySn^#kpO+
+7W=n^+|޼f|I+
+7W=n^;\7{*ܼVy
+7W=n^;\7{*ܼvn^Tyy%n^;\7{*ܼvn^|h˄׊7^Ru
+ƛ׎ݼ1n^;X7@{*ܼVyo^;{ܼmpѺySu
+k=n^;Z7`{*ܼvn^|h˄׊7^Ru
+kpO+
+7>o^3kpO+
+7W=n^+|޼f|I+
+7W=n^;\7{*ܼ6kTypݼpySu
+kϛ׌/pySu
+kpO
+7_Ru
+kpOxvݼ7.KkD{:p:7o??{wlnbO>?z
+>3
+~=+|g|I
+~=;\{* W
+~=;\{* wTo0Sa5
+_R
+eW pOee$-H=H^R*#٣eBUFST᳌%5:{L(#uH`{*:\e$=H>H_R*#PFp
+eW pO2RBUFST᳌%H2
+eW pO2RBH_S*#PFp
+eW pO2R2Rg2Ж eel/PFp
+eW pOeV)Ӟ eV lO2R2Rg2Ж eel/PFp
+eW pO2R2RƗT(#uH{*:\e$=H2
+e
+e/PFp
+/.#=@i<5HG,#}m~Q;a| 7|\[ͤXXuƒ {*4*|62BcX
+WcpOBBƗXX
+WcpO͍- ؞
+
+/٣
+WcpOB
+WcpOB
+WcpOBBƗTh,t {*4:\= 
+
+/Xp5
+WcpOB
+WcpOB
+WcpOBBƗTh,t {*4:\=57:{4Lh,Tl,d{IB
+WcpOB
+WcpOB
+រ)%>){j.t)/R⣜BrJr
+រ)%>){j.T,d|M=5SJ|S\N)QN!Ss9UN|IҾS\N)QN!Ss9G9pO=3SJzS\N)QN!Sc9o-3S:Z\N)QN!Sc9o-3SJzS\NpS
+រ)r
+Kj.(R⣜BrJr
+រ)r
+Kj.(R⣜BrJr
+រ)r
+Kj.(R⣜BrJi)D[f.t)`/R⣜BrJIS)%<)D{j.t)/Rڷr
+і)%=)d{j.(٣grJIr
+ٞ)%>){j,BerJG)%>){j.(R⣜BrJ)%>){j.(R⣜BrJ)%>){j.(R⣜BrJrJ\N)QN!Ss9G9pO=5S:\\N)QN!Ss9G9pO=5S:\\N)QN!Ss9G9pOԡ()n}nS'NǷ{)^?>nبi>}Q%|?}_+C͜wO^.?h<wO_#zï`\>^-~{qO_|w@ߞFS7ⷧsط_x%utoӈ{~<گ}<n?H4➺Q{q~i— ;O>FS7іpu
+
+/٣eBSWe3pu
+WpO^Bg/K*t:\<=:x
+WpO^^ƗTu:x{*t:\<=:x
+
+/pu
+WpO^Bg/K*t:\<=:x
+WpO^^ƗTu:x{*t:\<=:x
+
+/pu
+WpOu
+WpO^BSW᳃%:x
+WpO^Bg/K*t:\<=:x
+WpO^u"BSpu
+WpO^B/ĺ|s O/?@o<6:xQۗ×OϿ/?_O~O?@ٯyO@j|zݙo3_|L8hp:Soe7Ӟ g3_=|;\g{j>q e™oE3l/p:Sog3_-|;Zg`{*V<|;{mp:S̷u ™o3ߌ/p:S̷u ™opO3
+g_R̷u ™opO3י/
+g>|3™opO3י/
+g3_=|+|f|I3י/
+g3_=|;\g{*V<
+g3_=|;\g{*v|T8m3߈p:S̷u ™opO3
+g_R̷u ™opO3י/
+g>|3™opO3י/
+g3_=|+|f|I3י/
+g3_=5v82̷oT8p̷og
+g>|33g@[&v|T8p̷uiτ3ߎ֙/؞
+g3_=5v82̷oT8pp:S̷u ™o3ߌ/p:S̷u ™opO3
+g_R̷u ™opO3י/
+g ~w5|;\g{*v|T8pp[7K*v|T8pp:S̷oƗT8pp:S7\gƙ:>mOt^Nvutxy;=^'}v{h<S??O۟ǟ~?>}zR=/DViW|{dܐ_vǧPɷI ;qߟ%ccu;\W=37|>_xdܐ{ig^ϧ=Le>v@No p y;ng^s&27dn$[cy<]:,w/G<&27_xdܐ/(n~*0!_/{l6*0ɖ!_"Cm&2x,0Ȟ!_&cmI 3ٟo{<Lenןol~\~a=sC>//=&27\~a-sC_}{dxIx`#WF3ńf2᭄f2ᕄ7f2}<_GeτVEe˄7V^De˄VBe˄w6|a=^@Xz`->Xz`-^=Xz`-;QLx`%띃YLx`%녃YLx`%mYLx`#W F3E f2- f27 f2|z$/rJֻlfJ֋lZJ[lNF[#Q̼4Rch$afr2a˄JH-34Ek.Rɰe´H%kY$˖wE*x̊dc"4g¢H%kP$˖D*xdc"lTD2l0$sG$ʞWD*xdc"LTD2lRɚɲexH#({&,TCl0RZ ɲefH%k2$˖ !<B왰R ɲeTH%k)$˖ ;!,[&4gBH%k $˖ u,[&lTAl0 s$ʞ Q,[&LTAlRɚɲeH#-({&,T@l0RZɲeH%k$˖  |%?*YYLd-d2a5e˄яFQLXd ~d2ae˄JG->y|D3a壒5e˄JG-=*YYLheτeJְG-f=*YY̼Qc#aУqG=<*YcY8Q%~ȃJՌGv-F<ynxD3G~LzG-;*YY<툮%jGj#Ö Ŏ,[f1בݏ0Ѹs#ž K,[&tTV:lQɚȲe@G#}({&sT9l0QZȲe.G%k#˖ <79왰QȲeG%k#˖ [),[& qGLXdpd2ae˄JF-7ynoD3ay5e˄ٍJF-67*YYLh乷eτJF-6*YKYLHF4ce~b=q _>wϗzlV??DO5zkEt#W=_,{ۼkO=+;t|TZSsm[ϴgBsU]Sjѷ;^Þ%Z;
+EWpO.{Bg=K*4;\v=JV;
+WpOj{n{ƗThw{*;\ w=:;
+5
+=/tpUT(w{*;\w=*>;_R᪽P|p5Tw{*+|3BUSjЃpTWg|I6|B!ՈS*Pً
+W5pOr|B?US"wВpT(w{*t;\ey=>_RscGeh˄|G5BoUSsu՝ϴgB{US@jC٣DeBg>K*4;\Uz=5;{遶Lw
+`{*T+|v3V}gZ=Ж VlOn}\Bg>K*4;\{=J=
+=WpO}Ϯ}ƗThw{*;\{=:=
+
+/мpUT(w{*;\|=*>;_RPp5Tw{*+|3B#USjpT7]7?k*;\|=
+>
+WIpO~Ϟ~ƗThw{*;\m}=>
+
+/pT(w{*t;\}=>_RPpQ2_ѳ˟%:? }k1:R?О
+
+/٣eBS*_g3ѪPp5Q2_ѳ%?
+WpOBg?K*;\=
+
+;
+
+
+/!puTw:{*t;\r=:>;_RC!puTw:{*t+|v3B!SC!puTWg|IyCB!SC!!
+WpOyCB!SC^C%:9
+WpOyCBg<K*t;\r=:9
+WpOyu#B!SC!puTWg|Ir-:9؞
+WpOV<Ӟ VlOyCyg9Ж l/!pu!!2Cꐃ!!;=:@[&t;Zr=:9
+
+/!puTw:{*t;\r=:>;_RC!puTw:{*t+|v3B!SC!puTWg|IyCB!SC!!
+WpOyCB!SCw򈯩!puTw:{*t;\r=:>;_RC!puTw:{*t+|v3B!SC!puTWg|IyCB!SsGh˄yEyTw:{jwCDw:@{*t+|v3yg9Ж VlOyCyeCiτyGCB!SsGh˄yEyTw:{*t;\r=:9
+
+/!puTw:{*t;\r=:>;_RC!puTw:{*tC5:9
+WpOyCBg<K*t;\r=:9
+WpOyyƗTw:{*t;\r=:uv萇OtO!a|9S|<6:[?_^8n>~6JvwݞeQ>+(U>3BUS|PpU>T|V|f|Ig BUS|PY
+WpOg BUSY%*'
+WpOg Bg3K*T>;\O=*'
+WpOggƗT|v*{*T>;\O=*'
+|F|Mg BUS|PY+=*@[&T>;ZO=*'+g=*'؞
+WpO͕O-*=+^R|٣ eB峣USY%5W>;{T>L|v*`{*T>;\O=*>+_R|PpU>T|v*{*T>+|V>3BUS|PpU>T|V|f|Ig BUS|PY
+WpOg BUSY%*'
+WpOg B*_S|PpU>T|v*{*T>+|V>3BUS|PpU>T|V|f|Ig BUS|PY
+WpOg gg'Ж ϊl/PpU>X[P`U>T|V|f|I͕O-*'؞
+WpO͕V3Ӟ ώVlOg gg'Ж ϊl/PpU>T|v*{*T>;\O=*>+_R|PpU>T|v*{*T>+|V>3BUS|PpU>T|6]3k*T>;\O=*'
+WpOggƗT|v*{*T>;\O=*'
+
+ό/PpU>T|v*{*T>c-|v3|y|~:W>ب|o/o_/?/?~y}siz~>}sz{拃"@y<oH /%Sd;\/|I/-"Y=5HNjd "b=5;\b*.*&SsGpOͭb=5;\b*.*&SsGpOͭb=5;\b*.*&SsGpOͭb=5;\b*.*&SsGpOͭb=5;\b*.*&SsGpOͭb=5;\b*.*&SsGpOͭb=5+|3VqV1រ[%>Zń{jnhUjVqiZD[fnhU\ULVqgV1О[%=Zd{jnhU\ڷV1і[V1KjnhU\ڷV1і[%=Zd{jnAsye翢!9hDCYae+#OT -g}pߵjF~OU :VҾTD\KzUd\K|Uń\;<U1\K|Uń\K|Uń\K|Uń\;<U1\K|Uń\K|Uń\K|Uń\;<U1\K|Uń\K|Uń\K|Uń\;<U1\K|Uń\K|Uń\K|Uń\;<U1\K|Uń\K|Uń\K|Uń\+|W3~UWULxUWULxUWULxUS~UWULxUWULxUWULxUS~UWULxUWULxUWULxUS~UWULxUWULxU/U1ѵ9WŎNU :W_U1:UŒ^~q%b{ucu}͹*չ* ձ*v͹*չ* ձ**&6ѩ>VX*&WX*&WX*&Wᩊ?VX*&WX*&WX*&Wᩊ?VX*&WX*&WX*&WX*su%b{u%b{u%b{ucu%b{u%b{u%b{ucu%b{u%b{u
+VV@Ͻ/woWAU>"ٟo?|/!A}}{ώOyػ;Ǐ}4ڽtoT4 ۟s#]7ןӳ/FWo?g_>x~߿hI/&7oȟ4ڽzz;_FWo/wO+_q'O}_.O _FWooO#ޫp9 xa3~a0
+!*pxC
+!*P> 1*pxC
+!C
+!*pxC
+!*pxC
+!*P> 1*pxC
+! ~ubU8 
+߇!fXa{C8<!
+߇!fXa{C8<!
+!*pxC
+!*pxC
+!*P> 1*pxC
+!*P> 1*pxC
+!C
+!*pxC
+=ӧ_z|w{CMy:<y/
+yÓޫ:<y/
+yw;*O xB佀*O xBޫ
+yÓޫ:<y/
+yÓޫ*|3~B佀*O xB佀*
+y:<y/
+yÓޫ:<y/
+yw;*O xB佀*O xBޫ
+yÓޫ:<y/
+yÓޫ*s^'W!ux^{^'W!U{gX^gڄ{U{^^e'ބ{U{^^gڄW;c^'W+6!ut^{^wƏ9u{M{^{U{?V!ux^{^'W!ux^{^wƏU{^{U{^W;c^'W!ux^{^'W!U{gX{U{^{U{?V!ux^{^'W!ux^{^_#~B佀*O xB佀*
+y:<y/
+yÓޫ:<y/
+yw;*O xB佀*O xBޫ
+yÓޫ:<y/:^y/е yw;*O xy/y/!up^{^wƏ9u{M{^{9UvޙM{^{9u{M{>V!ux^{^'W!ux^{^wƏU{^{U{^W;c^'W!ux^{^'W!5U;*O xB佀*O xBޫ
+yÓޫ:<y/
+yÓޫ*|3~B佀*O xB;.Zyﻄv{'}+w{Ԋ{:~=˗op+wx/Qw/=o}|޼!{~~n%_>Lrmސ,_<<گakƫ_ oG.|x)g}x}y<<=Kak_$׻@>8Ypk{^>Lpmސk|0ɵyC{QRxzQׇz۽/&6oUJ|ڼ!CJuYMa}Zc{k(9w5f6᪆sTckNj(95f6ឆ9QM8ҘڄKJ!YM8\јڄF'4F7ဆs?ckg(93f6ts9ckfyބJ͌YM̘ڄsJεYMa}*c{e(9w2f6Js$ckNd(92f6>yQM8ƘڄJaYM8\ŘڄF'1F7 scka(90f6s ck` $pCɹ1˵ 0\pBɹ~1˵ /O_ro·/^EzڄJŋYޅsbtk](7.f6҅sbks>subv/ #ܛpBɹo1˵9_P:n1ᴅrsbkZyޜZ(xݴpB9h1õ ,k\p(&PrXrm %,&Pr.Xrm
+#ܛpBɹ]1˵ +\pBɹZ1˵ 7+OVro
+%^,&\PrUrm©
+%R,&ܩ0>S1ʽ G*\pB9P1˵ )\p4(&PrRrmU
+%(,&Pr.Rrm=
+!_cQ(9(f6sbkP(9W(f6 QM8@ܟڄJYM8=\ڄFg'F7脒ssbk.N(9'f6܄smbknMyބCJΝYM2ڜOL(x]p_¸&PrnKrm%{9,1଄RsUbvknJyޜJ(xݓpMB9&1õ $K\ "PnnHpm %,|>Bz_ݧ#F7ps7bkF(9G#f6ds1bkEyބcJέYMڄ3JΕYMa}"b{D(9!f6:sbkNC(9!f6.BL G!\pB91˵ k\p (&Pr@rm%,&Pr.@rm#ܛpAɹ0˵ \paX>O>\_Vx}>?͇}/?듼\|Hw?ύ9Gp0#~ 3]$
+ |@9P0ӽ '
+`*\)px
+
+߇ fX{8<
+߇fXSϭ{8<
+s^C%{n!ˆpṇ
+G*EP>0|6е `*Hpx.$
+w)
+[
+*\Spx)
+3~Y
+
+*Wpx+
+K *ZP>0*[px-
+w/
+ *\_px/
+fX M{28<g
+|е w;
+*\wpx;
+S*{px=
+*~0?Ws^  {@8<W
+U?V!,vxb{b',W!,vxb{bxƏU^ U^X;,cb',W!,vxb{b',W!,VgX U^ U?V!,vxb{b',W!,vxb{bxƏU^ U^X;,cb',W!,vxb{b',W!,6UX<*OX xBXŀ*OX xBX:^a1е aݫ;<a1:NX<ӽ aݫ;<a1:^a1е awX<*OX xaWX tmBX`*
+asX];:a1ؽ
+aޫ+|3~BXŀ*OX xBXŀ*
+a;<a1
+aޫ;<a1
+awX<*OX xBXŀ*OX xBX
+aޫ;<a1
+aޫ+|3~BXŀ*OX xBXŀ*
+G\ U^ U?V!,vxb{b',W!,vxb{bxƏU^ U^X;,cb',W!,vxb{ubkbExU^bG_b/BX@*
+asX];:a1ؽ
+aޫsX3ݛ;:a1ؽ
+aޫsX]+zų}BXŀ*OX xBXŀ*
+a;<a1
+aޫ;<a1
+awX<*OX xBXŀ*OX xBXlxU^ U^X;,cb',W!,vxb{b',W!,VgX U^x`]X wOܿ<SX>w+"o_~7{+*_nUW_?*hz9:\yL&y9ؽ
+"wx^Ex"wU
+fX
+fX
+*\ px.
+/:_ p
+*\ P0*\ px.
+/
+*\ px.
+*\ px.
+*\ P0*\ px.
+ ~u1`U\
+fX
+/:_ p
+|1@ٹ0ӽ `*\ px.
+/
+*\ px.
+*\ px.
+zޫz:<'
+zޫz*|3~B𴞀*O xB𴞀*
+߭z:<'
+zޫz:<'
+w9*O xB𴞀*O xBz
+zޫz:<'
+zޫzsZOWtxZO{ZOWTn=gX[Ogڄi=Uh=^[Oeބi=Uh=^[OgڄSѻcZOW٫6ttZO{ZOsƏչtj=Mh=^i=Uh=[?VtxZO{ZOWtxZO{ZOsƏUh=^i=Uh=^ScZOWtxZO{ZOWTn=gXi=Uh=^i=Uh=[?VtxZO{ZOWtxZO{ZO_#~B𴞀*O xB𴞀*
+߭z:<'
+zޫz:<'
+w9*O xB𴞀*O xBz
+zޫz:<':^'е w9*O x/'tpZO{ZOsƏչtj=Mh=^i=չTvZϙMh=^i=չtj=Mh=[>VtxZO{ZOWtxZO{ZOsƏUh=^i=Uh=^ScZOWtxZO{ZOW4U9*O xB𴞀*O xBz
+zޫz:<'
+zޫz*|3~B𴞀*O xB9dZ绞sx_z>|g>z/JǗ6ϯ/}/JTcsv \I|U\I|U\I|U\9<\I|U\I|U\I|U\9<\I|U\I|U\I|U\9<\I|U\I|U\I|U\9<\I|U\I|U\I|U\)|W3~՟WGx՟WGx՟WGx՟S~՟/ѵ9W^ٽ:W_:V^н9W^ٽ:W_:VҾTD\9:\I|UXIR]s'UݫsTc'KGtm՟WGv՟WGx՟S~՟WGx՟WGx՟WGx՟S~՟WGx՟WGx՟WGx՟S~՟WGx՟WGx՟WGx՟S~՟WGx՟WGx՟WGx՟S~՟WGx՟WGx՟WGx՟w7\I|U\I|U\I|U\9<\I|U\I|U\I|U\9<\I|U\I|U\I|U\9<\I|U\I|UXIR]sT`s'UޫS'O«#W
+'g3*qxN
+'g3*Q>93*qxN
+'gO
+'g3*qxN
+'g3*qxN
+'gfX3{N8<'g
+'g3*Q>93*qxN
+'gO
+'g3*qxN
+'g3|r е 'gO
+'g3xrї3
+'gfX3{N8<'g
+'g3*qxN
+'g3*qxN
+'gƳ) <:933O\S_řϗ3/?~7?~×緿o?}~;[قlsg >xlؽ
+g |@9[0ӽ g `*-px
+g fX{8<g
+g fX{8<g
+g *-P>[0|lе g `*-px
+g 
+g *-px
+g *-px
+g *-P>[0*-px
+g 
+g *-px
+g fX{8<g
+g |lе g 
+g *-px
+g *-px
+g *-0ق?Wls
+<_u ȩ篟x?xWwxy{u}~w=?*O=xB=*O=xB=]
+SޫP;<<
+SޫP+|3~B=*O=xB=*
+P;<<
+SޫP;<<
+w=?*O=xB=*O=xB=]
+SޫP;<<
+SޫP+|3~B=*O=xB=*
+P;<<
+SޫP;<<
+?Wwxy{yWwxy{yz~Əչw灮Mz^zչWvMz^zչw灮M>Vwxy{u睽ykyGWWgXygzڄzѩUz^z^ỞcyWwxy{yWWgXzUz^zU?Vwxy{yWwxy{yz~ƏUz^zUz^z^ỞcyWwxy{yW7U=?*O=xB=*O=xB=]
+SޫP;<<
+SޫP+|3~B=*O=xB=*
+P;<<
+Sޫs=U]P+z}B=X;R~yWWgXygzڄzѩUz^yeބzѩUz^ygzڄz^ѻcyWwxy{yWWgXzUz^zU?Vwxy{yWwxy{y_#~B=*O=xB=*
+P;<<
+SޫP;<<
+w=?*O=xB=*cyxUϿ+ԇz~x__Ͽ~tOX/zÿ_~󟗄뷳}}oz%_o;z'=$<ДTޫT;<I5
+IÓTޫT+|'3~BR$Հ*$OR xBR$Հ*$
+IT;<I5
+IÓTޫT;<I5
+IwR=*$OR xBR$Հ*$OR xBRT
+IÓTޫT;<I5
+IÓTޫT+|'3~BR$Հ*$OR xBR$Հ*$
+IT;<I5
+IÓTޫT;<I5
+IwR=*$OR xBR$Հ*$OR xBRmzUH^IUH^Z;cuNjkjG'W!vxj{uNz{jG'W!vxj{uNjkjEzUH^jgڄIUH?V+6!vtj{j'W!VNgXIUH^IUH?V!vxj{j'W!vxj{jzƏUH^IUH^Z;cj'W!vxj{j'W!VNgXIUH^IUH ~T
+IÓTޫT;<I5
+IÓTޫT+|'3~BR$Հ*$OR xBR$Հ*$
+IT;<I5
+IÓTޫT;<I5
+IwR=*$OR xBR$ՀT;{%@&$ՊIlT;<I5:&Վ$
+IÓTޫT;<I5
+IwR=*$OR xBR$Հ*$OR xBRT
+IÓTޫT;<I5
+IX
+rY~{|y"qwݫ.r].rG_EExw8?*O8xB8*O8xB8
+ޫ;<<
+ޫ+|3~B8*O8xB8*
+;<<
+ޫ;<<
+w8?*O8xB8*O8xB8
+ޫ;<<
+ޫ+|3~B8*O8xB8*
+;<<
+ޫ;<<
+?W!wxy{y'W!wxy{yp~Ə9w
+灮Mp^p 9WvMp^p 9w
+灮M>V!wxy{u睽ykyG'W!WgXygpڄp Up^p^;cy'W!wxy{y'W!WgXp Up^p U?V!wxy{y'W!wxy{yp~ƏUp^p Up^p^;cy'W!wxy{y'W!7U8?*O8xB8*O8xB8
+ޫ;<<
+ޫ+|3~B8*O8xB8*
+;<<
+ޫs8]+z}B8;~y'W!WgXygpڄp Up^ye'ބp Up^ygpڄp^;cy'W!wxy{y'W!WgXp Up^p U?V!wxy{y'W!wxy{y_#~B8*O8xB8*
+;<<
+ޫ;<<
+w8?*O8xB8*CmyxοӇp~x_~Sۯ,vo,/%~?>X>=8_~?O~Ww3ݿ˭א}{x]{w=yq>/FW?#^4 s#]7ןӳ/FWo?g_>x޸Vp\ބ ΅{.D8<"
+" *\px.D
+" *\px.D
+" *\P1*\px.D
+"/D
+" *\px.D
+" *\px.D
+" *\P1*\px.D
+" ~u!bU\
+"fX υ{.D8<"
+"fX υ{.D8<"
+" *\px.D
+" *\px.D
+" *\P1*\px.D
+" *\P1*\px.D
+"/D
+" *\px.D
+SޫP:<)
+wi:\:{@&Ni
+vBi𔦀\*;L&Ni
+vBi𔦀\:{@&ޥlP:<):^)е SݫP*|3~ΥWi
+tmBi蔦`*Oi
+xBi]
+SޫP:<)
+SޫP*|3~Bi𔦀*Oi
+xBi𔦀*
+ߥP:<)
+SޫP:<)
+wi:*Oi
+xBi𔦀*Oi
+xBi]
+SޫP:<)
+SޫP4sJS4W4uxJS{JS4W4U.MgX)MU(M^)MU(MK?V4uxJS{JS4W4uxJS{JStƏU(M^)Mչ4u*MM(MK>V4uxJS{u,M})M~SݫP*|3~ΥWi
+tmBi蔦`*Oi
+xΥStoBi蔦`*Oi
+xΥWi
+tmBi]
+SޫP:<)
+SޫP*|3~Bi𔦀*Oi
+xBi𔦀*
+ߥP:<)
+SޫP:<)
+J?W4uxJS{JS4W4uxJS{JStƏU(M^)MU(M^T4cJS4W4uxJS{Jӱt)<*M՜Ci:<e//_<|^>Otp1~?uo/OwOozoˍt㿗]\ʁh*fXr)
+S
+S
+S
+w90\8{@&N9
+P8<
+S
+w90*O9
+S
+S
+P8<
+S
+ʁ?Wpx{
+w90\8{@&N9
+S
+w90*O9
+S
+S
+|V*_3>~uW
+Q~uΌ9*:yu
+qx^x«s_3*:yu
+qx^x«s
+܌s
+ޫ)|p3~B
+܌9<
+ޫ9<
+w
+ޫ9<
+ޫ)|p3~B
+F\
+܌s
+ޫs
+ޫs
+܌9<
+ޫ9<
+w
+txZC{ZC5W5txZC{ZCpƏUh ^i Uh ^P5cZC5W5txZC{ZC5W5Tn gXi Uh ^i Uh [?V5txZC{ZC5W5txZC{ZC_#~Bk𴆀*OkxBk𴆀*
+߭sk]::!ؽ
+ޫsk촆3ݛ::!ؽ
+ޫsk]*z}Bk𴆀:{@&NkvBk:^!е ݫ:<!
+wk8*OkxBk𴆀*OkxBk
+ޫ:<!
+ޫ*|3~Bk𴆀*OkxBk𴆀*
+߭:<!
+ޫ:<!
+wk8*OkxBk𴆀*OkxBkhpUh ^i Uh ^P5cZC5W5txZC{ZC5W5Tn gXi Uh ^i Uh [?V5txZC{ZC5W٫565Tn gXi ձ5t5"NktBk:^!е ݫ:<!:Nk8ӽ ݫ:<!:^!е wk8*OkxBk𴆀*OkxBk
+ޫ:<!
+ޫ*|3~Bk𴆀*OkxBk𴆀*j G\i Uh ^i Uh [?V5txZC{ZC5W5txZC{ZCpƏUh ^i Uh >ζ@5~O[቎ ?=}}EO۟ y=v~/7߯+7{SrÇ~4zs3
+_&Xd˧
+A^KUK?V!/uxR{R'/W!/uxR{RtƏUK^KUK^T;/cR'/W!/uxR{R'/W!/UKgXKUK^KUK?V!/uxR{R'/W!/uxR{R_#~B^䥀*O^
+xB^䥀*
+ys^앗]::y)ؽ
+yÓޫs^3ݛ::y)ؽ
+yÓޫs^앗]*z祳}B^䥀ꜗ:{@&䥎N^
+vB^:^y)е yݫ:<y)
+yw^:*O^
+xB^䥀*O^
+xB^
+yÓޫ:<y)
+yÓޫ*|3~B^䥀*O^
+xB^䥀*
+y:<y)
+yÓޫ:<y)
+yw^:*O^
+xB^䥀*O^
+xB^jtUK^KUK^T;/cR'/W!/uxR{R'/W!/UKgXKUK^KUK?V!/uxR{R'/W+/6!/UKgXK1/u%/"N^
+tB^:^y)е yݫ:<y):N^:ӽ yݫ:<y):^y)е yw^:*O^
+xB^䥀*O^
+xB^
+yÓޫ:<y)
+yÓޫ*|3~B^䥀*O^
+xB^䥀*KG\KUK^KUK?V!/uxR{R'/W!/uxR{RtƏUK^KUK&@+/~yDUoԐO%/?5oQo\W~1л~ ^)
+,Wrx
+,{
+,WR.fX)
+,{
+,Wrx
+,{
+,_X#~BX*XOxBX*X
+֌sU`]P`9:ؽ
+S`ޫsX3ݛP`9:ؽ
+S`ޫsU`]P`)zX}BX\`9{X@&XNvB]`:X^е S`ݫP`9<
+w5*XOxBX*XOxB]`
+S`ޫP`9<
+S`ޫP`)|X3~BX*XOxBX*X
+֌P`9<
+S`ޫP`9<
+w5*XOxBX*XOxBekU( ^)
+,Wrx
+,{
+,WR.fX)
+,{
+,W٫6R.fX)
+S`ޫP`9<
+S`ޫP`)|X3~BX*XOxBX*X*F\)
+,{
+,Wrx
+,{
+,kƏU( ^)
+wofX7{8<wo
+wo:߽q{tmG ؽ
+wo7|Fٹ{3ӽ wo7`*ܽqx
+wo7*ܽqx
+wo7*ܽQ{3*ܽqx
+wo
+wo7*ܽqx
+wo7*ܽqx
+wo7*ܽqx
+wo7*ܽQ{3*ܽqx
+wo ~ufU{ܽWs^7{(|߽c8<wo
+wofX7{8<wo
+endobj
+1369 0 obj <<
+/Type /Page
+/Contents 1370 0 R
+/Resources 1368 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 1215 0 R
+/Annots [ 1372 0 R 1373 0 R 1374 0 R 1375 0 R 1376 0 R 1377 0 R 1378 0 R 1379 0 R 1380 0 R 1381 0 R 1382 0 R 1383 0 R 1384 0 R 1385 0 R 1386 0 R 1387 0 R 1388 0 R 1389 0 R 1390 0 R 1391 0 R 1392 0 R 1393 0 R 1394 0 R 1395 0 R 1396 0 R 1397 0 R 1398 0 R 1399 0 R 1400 0 R 1401 0 R 1402 0 R 1403 0 R 1404 0 R 1405 0 R 1406 0 R 1407 0 R 1408 0 R 1409 0 R 1410 0 R 1411 0 R 1412 0 R 1413 0 R 1414 0 R 1415 0 R 1416 0 R 1417 0 R 1418 0 R 1419 0 R 1420 0 R 1421 0 R 1422 0 R 1423 0 R 1424 0 R 1425 0 R 1426 0 R 1427 0 R 1428 0 R 1429 0 R 1430 0 R 1431 0 R 1432 0 R 1433 0 R 1434 0 R 1435 0 R 1436 0 R 1437 0 R 1438 0 R 1439 0 R 1440 0 R 1441 0 R 1442 0 R 1443 0 R 1444 0 R 1445 0 R 1446 0 R 1447 0 R 1448 0 R 1449 0 R 1450 0 R 1451 0 R 1452 0 R 1453 0 R 1454 0 R 1455 0 R 1456 0 R 1457 0 R 1458 0 R 1459 0 R 1460 0 R 1461 0 R 1462 0 R 1463 0 R 1464 0 R 1465 0 R ]
+>> endobj
+1372 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 707.2479 223.5807 716.0945]
+/Subtype /Link
+/A << /S /GoTo /D (os-linux) >>
+>> endobj
+1373 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 707.2479 538.9788 716.0945]
+/Subtype /Link
+/A << /S /GoTo /D (os-linux) >>
+>> endobj
+1374 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 692.6128 255.4605 703.1431]
+/Subtype /Link
+/A << /S /GoTo /D (nonroot) >>
+>> endobj
+1375 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 692.6128 538.9788 703.1431]
+/Subtype /Link
+/A << /S /GoTo /D (nonroot) >>
+>> endobj
+1376 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 681.345 194.7094 690.1917]
+/Subtype /Link
+/A << /S /GoTo /D (850) >>
+>> endobj
+1377 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 681.345 538.9788 690.1917]
+/Subtype /Link
+/A << /S /GoTo /D (850) >>
+>> endobj
+1378 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 666.3363 178.1119 677.2402]
+/Subtype /Link
+/A << /S /GoTo /D (854) >>
+>> endobj
+1379 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 666.3363 538.9788 677.2402]
+/Subtype /Link
+/A << /S /GoTo /D (854) >>
+>> endobj
+1380 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [142.4658 653.3849 299.4363 664.2888]
+/Subtype /Link
+/A << /S /GoTo /D (862) >>
+>> endobj
+1381 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 653.3849 538.9788 664.2888]
+/Subtype /Link
+/A << /S /GoTo /D (862) >>
+>> endobj
+1382 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 642.4907 161.5041 651.3374]
+/Subtype /Link
+/A << /S /GoTo /D (889) >>
+>> endobj
+1383 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 642.4907 538.9788 651.3374]
+/Subtype /Link
+/A << /S /GoTo /D (889) >>
+>> endobj
+1384 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 629.5393 198.8636 638.3859]
+/Subtype /Link
+/A << /S /GoTo /D (install-perlmodules-nonroot) >>
+>> endobj
+1385 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 629.5393 538.9788 638.3859]
+/Subtype /Link
+/A << /S /GoTo /D (install-perlmodules-nonroot) >>
+>> endobj
+1386 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 616.5879 198.7046 625.4345]
+/Subtype /Link
+/A << /S /GoTo /D (911) >>
+>> endobj
+1387 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 616.5879 538.9788 625.4345]
+/Subtype /Link
+/A << /S /GoTo /D (911) >>
+>> endobj
+1388 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [142.4658 601.5791 297.2047 612.4831]
+/Subtype /Link
+/A << /S /GoTo /D (914) >>
+>> endobj
+1389 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 601.5791 538.9788 612.4831]
+/Subtype /Link
+/A << /S /GoTo /D (914) >>
+>> endobj
+1390 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 588.6277 179.2174 599.5316]
+/Subtype /Link
+/A << /S /GoTo /D (923) >>
+>> endobj
+1391 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 588.6277 538.9788 599.5316]
+/Subtype /Link
+/A << /S /GoTo /D (923) >>
+>> endobj
+1392 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [142.4658 575.6763 274.7589 586.5802]
+/Subtype /Link
+/A << /S /GoTo /D (suexec) >>
+>> endobj
+1393 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 575.6763 538.9788 586.5802]
+/Subtype /Link
+/A << /S /GoTo /D (suexec) >>
+>> endobj
+1394 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 562.7248 225.3239 573.6288]
+/Subtype /Link
+/A << /S /GoTo /D (upgrade) >>
+>> endobj
+1395 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 562.7248 538.9788 573.6288]
+/Subtype /Link
+/A << /S /GoTo /D (upgrade) >>
+>> endobj
+1396 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 549.7734 227.9049 560.6774]
+/Subtype /Link
+/A << /S /GoTo /D (upgrade-before) >>
+>> endobj
+1397 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 549.7734 538.9788 560.6774]
+/Subtype /Link
+/A << /S /GoTo /D (upgrade-before) >>
+>> endobj
+1398 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 536.822 250.629 547.7259]
+/Subtype /Link
+/A << /S /GoTo /D (upgrade-files) >>
+>> endobj
+1399 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 536.822 538.9788 547.7259]
+/Subtype /Link
+/A << /S /GoTo /D (upgrade-files) >>
+>> endobj
+1400 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [142.4658 523.8706 316.7811 534.7745]
+/Subtype /Link
+/A << /S /GoTo /D (upgrade-modified) >>
+>> endobj
+1401 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 523.8706 538.9788 534.7745]
+/Subtype /Link
+/A << /S /GoTo /D (upgrade-modified) >>
+>> endobj
+1402 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [142.4658 510.9191 265.3942 521.8231]
+/Subtype /Link
+/A << /S /GoTo /D (upgrade-cvs) >>
+>> endobj
+1403 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 510.9191 538.9788 521.8231]
+/Subtype /Link
+/A << /S /GoTo /D (upgrade-cvs) >>
+>> endobj
+1404 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [142.4658 497.9677 286.1359 508.8716]
+/Subtype /Link
+/A << /S /GoTo /D (upgrade-tarball) >>
+>> endobj
+1405 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 497.9677 538.9788 508.8716]
+/Subtype /Link
+/A << /S /GoTo /D (upgrade-tarball) >>
+>> endobj
+1406 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [142.4658 485.0163 275.8946 495.9202]
+/Subtype /Link
+/A << /S /GoTo /D (upgrade-patches) >>
+>> endobj
+1407 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 485.0163 538.9788 495.9202]
+/Subtype /Link
+/A << /S /GoTo /D (upgrade-patches) >>
+>> endobj
+1408 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 472.0648 251.1673 482.9688]
+/Subtype /Link
+/A << /S /GoTo /D (upgrade-completion) >>
+>> endobj
+1409 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 472.0648 538.9788 482.9688]
+/Subtype /Link
+/A << /S /GoTo /D (upgrade-completion) >>
+>> endobj
+1410 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 461.1707 310.6729 470.0173]
+/Subtype /Link
+/A << /S /GoTo /D (upgrade-notifications) >>
+>> endobj
+1411 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 461.1707 538.9788 470.0173]
+/Subtype /Link
+/A << /S /GoTo /D (upgrade-notifications) >>
+>> endobj
+1412 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [70.7348 443.9054 181.498 454.7845]
+/Subtype /Link
+/A << /S /GoTo /D (administration) >>
+>> endobj
+1413 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 443.9054 538.9788 454.7845]
+/Subtype /Link
+/A << /S /GoTo /D (administration) >>
+>> endobj
+1414 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 428.4285 205.6777 439.3324]
+/Subtype /Link
+/A << /S /GoTo /D (parameters) >>
+>> endobj
+1415 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 428.4285 538.9788 439.3324]
+/Subtype /Link
+/A << /S /GoTo /D (parameters) >>
+>> endobj
+1416 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 415.477 216.577 426.381]
+/Subtype /Link
+/A << /S /GoTo /D (param-requiredsettings) >>
+>> endobj
+1417 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 415.477 538.9788 426.381]
+/Subtype /Link
+/A << /S /GoTo /D (param-requiredsettings) >>
+>> endobj
+1418 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 404.5829 238.8728 413.4295]
+/Subtype /Link
+/A << /S /GoTo /D (param-admin-policies) >>
+>> endobj
+1419 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 404.5829 538.9788 413.4295]
+/Subtype /Link
+/A << /S /GoTo /D (param-admin-policies) >>
+>> endobj
+1420 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 391.6315 225.9716 400.4781]
+/Subtype /Link
+/A << /S /GoTo /D (param-user-authentication) >>
+>> endobj
+1421 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 391.6315 538.9788 400.4781]
+/Subtype /Link
+/A << /S /GoTo /D (param-user-authentication) >>
+>> endobj
+1422 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 378.68 195.815 387.5267]
+/Subtype /Link
+/A << /S /GoTo /D (param-attachments) >>
+>> endobj
+1423 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 378.68 538.9788 387.5267]
+/Subtype /Link
+/A << /S /GoTo /D (param-attachments) >>
+>> endobj
+1424 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 363.6713 229.0303 374.5752]
+/Subtype /Link
+/A << /S /GoTo /D (param-bug-change-policies) >>
+>> endobj
+1425 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 363.6713 538.9788 374.5752]
+/Subtype /Link
+/A << /S /GoTo /D (param-bug-change-policies) >>
+>> endobj
+1426 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 350.7199 188.9111 361.6238]
+/Subtype /Link
+/A << /S /GoTo /D (param-bugfields) >>
+>> endobj
+1427 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 350.7199 538.9788 361.6238]
+/Subtype /Link
+/A << /S /GoTo /D (param-bugfields) >>
+>> endobj
+1428 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 337.7684 195.955 348.6724]
+/Subtype /Link
+/A << /S /GoTo /D (param-bugmoving) >>
+>> endobj
+1429 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 337.7684 538.9788 348.6724]
+/Subtype /Link
+/A << /S /GoTo /D (param-bugmoving) >>
+>> endobj
+1430 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 324.817 226.3608 335.721]
+/Subtype /Link
+/A << /S /GoTo /D (param-dependency-graphs) >>
+>> endobj
+1431 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 324.817 538.9788 335.721]
+/Subtype /Link
+/A << /S /GoTo /D (param-dependency-graphs) >>
+>> endobj
+1432 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 311.8656 206.6047 322.7695]
+/Subtype /Link
+/A << /S /GoTo /D (param-group-security) >>
+>> endobj
+1433 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 311.8656 538.9788 322.7695]
+/Subtype /Link
+/A << /S /GoTo /D (param-group-security) >>
+>> endobj
+1434 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 300.9714 237.7576 309.8181]
+/Subtype /Link
+/A << /S /GoTo /D (bzldap) >>
+>> endobj
+1435 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 300.9714 538.9788 309.8181]
+/Subtype /Link
+/A << /S /GoTo /D (bzldap) >>
+>> endobj
+1436 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 288.02 249.2244 296.8667]
+/Subtype /Link
+/A << /S /GoTo /D (bzradius) >>
+>> endobj
+1437 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 288.02 538.9788 296.8667]
+/Subtype /Link
+/A << /S /GoTo /D (bzradius) >>
+>> endobj
+1438 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 275.0686 174.2362 283.9152]
+/Subtype /Link
+/A << /S /GoTo /D (param-email) >>
+>> endobj
+1439 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 275.0686 538.9788 283.9152]
+/Subtype /Link
+/A << /S /GoTo /D (param-email) >>
+>> endobj
+1440 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 262.1171 203.3868 270.9638]
+/Subtype /Link
+/A << /S /GoTo /D (param-patchviewer) >>
+>> endobj
+1441 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 262.1171 538.9788 270.9638]
+/Subtype /Link
+/A << /S /GoTo /D (param-patchviewer) >>
+>> endobj
+1442 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 247.1084 211.4763 258.0124]
+/Subtype /Link
+/A << /S /GoTo /D (param-querydefaults) >>
+>> endobj
+1443 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 247.1084 538.9788 258.0124]
+/Subtype /Link
+/A << /S /GoTo /D (param-querydefaults) >>
+>> endobj
+1444 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 236.2143 221.2895 245.0609]
+/Subtype /Link
+/A << /S /GoTo /D (param-shadowdatabase) >>
+>> endobj
+1445 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 236.2143 538.9788 245.0609]
+/Subtype /Link
+/A << /S /GoTo /D (param-shadowdatabase) >>
+>> endobj
+1446 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 221.2056 209.9221 232.1095]
+/Subtype /Link
+/A << /S /GoTo /D (admin-usermatching) >>
+>> endobj
+1447 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 221.2056 538.9788 232.1095]
+/Subtype /Link
+/A << /S /GoTo /D (admin-usermatching) >>
+>> endobj
+1448 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 210.1918 195.705 219.1581]
+/Subtype /Link
+/A << /S /GoTo /D (useradmin) >>
+>> endobj
+1449 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 210.1918 538.9788 219.1581]
+/Subtype /Link
+/A << /S /GoTo /D (useradmin) >>
+>> endobj
+1450 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 195.3027 247.9988 206.2066]
+/Subtype /Link
+/A << /S /GoTo /D (defaultuser) >>
+>> endobj
+1451 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 195.3027 538.9788 206.2066]
+/Subtype /Link
+/A << /S /GoTo /D (defaultuser) >>
+>> endobj
+1452 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 182.3513 236.2033 193.2552]
+/Subtype /Link
+/A << /S /GoTo /D (manageusers) >>
+>> endobj
+1453 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 182.3513 538.9788 193.2552]
+/Subtype /Link
+/A << /S /GoTo /D (manageusers) >>
+>> endobj
+1454 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [142.4658 169.3998 287.6402 180.3038]
+/Subtype /Link
+/A << /S /GoTo /D (user-account-search) >>
+>> endobj
+1455 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 169.3998 538.9788 180.3038]
+/Subtype /Link
+/A << /S /GoTo /D (user-account-search) >>
+>> endobj
+1456 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [142.4658 156.4484 252.9506 167.3523]
+/Subtype /Link
+/A << /S /GoTo /D (createnewusers) >>
+>> endobj
+1457 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 156.4484 538.9788 167.3523]
+/Subtype /Link
+/A << /S /GoTo /D (createnewusers) >>
+>> endobj
+1458 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [142.4658 143.497 244.6319 154.4009]
+/Subtype /Link
+/A << /S /GoTo /D (modifyusers) >>
+>> endobj
+1459 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 143.497 538.9788 154.4009]
+/Subtype /Link
+/A << /S /GoTo /D (modifyusers) >>
+>> endobj
+1460 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [142.4658 130.5455 236.323 141.4495]
+/Subtype /Link
+/A << /S /GoTo /D (user-account-deletion) >>
+>> endobj
+1461 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 130.5455 538.9788 141.4495]
+/Subtype /Link
+/A << /S /GoTo /D (user-account-deletion) >>
+>> endobj
+1462 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [142.4658 117.5941 259.5657 128.498]
+/Subtype /Link
+/A << /S /GoTo /D (impersonatingusers) >>
+>> endobj
+1463 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 117.5941 538.9788 128.498]
+/Subtype /Link
+/A << /S /GoTo /D (impersonatingusers) >>
+>> endobj
+1464 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 106.7 172.1932 115.5466]
+/Subtype /Link
+/A << /S /GoTo /D (classifications) >>
+>> endobj
+1465 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 106.7 538.9788 115.5466]
+/Subtype /Link
+/A << /S /GoTo /D (classifications) >>
+>> endobj
+1371 0 obj <<
+/D [1369 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+1368 0 obj <<
+/Font << /F27 1222 0 R /F32 1230 0 R /F33 1322 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+1515 0 obj <<
+/Length 59695
+/Filter /FlateDecode
+>>
+stream
+xڔMdWz~$`c(j6lK46fUC>u'n.ŕbU\yfaw;-7#uw˿yW?p~8./O=.p?N?ݿc_t?oOwgη|ayump/ ˋs^ᮼaw>/_PiK~Zi=u·%ӈ{<9~ӈ{kz؝m /iw_)FS7to8nz4_yҷ}&n9ӷ}FS7_O#tm_O#tm /tm |y:Cӈ{~~?e~Lq쒺v)}i=u_~wH[憼<9~h{K||9eN^S7ќ~iܐ'sRz=nˣY~I4?,fb&37{FS7p8n5~˿Eckxn .\  |y.#ӈ{<1~ӈ{k<)}iK<)}i=u_᜾4➺/Og9zqO}㒾4%u?.S|=nvKqqOi=5~YI /tPtqOszo8n9}=Ư9ҷ}&n9Ōӈ{<1}i=uΏSm{O>N^R7|;o8nrH4➺/Or4/O|Mq𒺁/OtKqqOK)FS7_O#tm /iڗ4➺_v>}i=u_i=%~w!!N^S7\_O#t.C |!BS_>N^R7餿 yy4Kh{5~ӈ{kzhܐOi=un)ӈ{~~?eO˓ď{&nˣ|FS7ѤQa-sC^pM4ڞ?/xLq𒺁/ yy2;FS78i=5~sq𒺁v%}i=ukӈ{<KqqO}_so8MxIsS|=n9ӷ}FS7۾FS_>N^R7>N#ewy?&ӈ{~m_O#~wӷ}&n&i=u_圾4➺/O&i=5XPӄ |y:kӈ{<Cz/E |y:[ԗyߝmbn~zqO/qI4➺v>~ӈ{k<kzM /t.mbFS7ӂN#tNm_O#tb&n9?i=u_>Sӈ{~m_O#~w&n^+i=uN״ӈ{<qi|-GsN9:MvIGsJ9:nˣ9׳L`roziO}_rHӄ |y.a-sC쎏h{~i=5~?D{|ݮbFS7\^N#hH[k<SzX.hi/G |y46N#pm_O#~wx&n&i=uӈ{~]ombFS_\ӂN^R7\҂N#tmbFS7N۾FS_tmbn9FS7ӂN#mwy\}=tt𒺁w[zX _ӂN#t.m_O#tmb&n9FS7:n9h'.n܇t>Wt_g~{ߝOo^g:<~.ԝ8ccVk~?77϶|l-gun|zAM7>C|4> ԾIO=o|fԾIO=o|h|}3O=o|h|}3GpO'%o|h|}3GpO!>{jp5>/}3GpO!>{j $Sg xI!>{j $Sg'7>#|6>{O=o|h|}3GpO'%m|$2gH'ٞ7>C|4> Զ٣ gϐO=o|h|m3'і7>3ZOKj $SghO-o|h|}3^D[f $Sg'7>3\OKj $Sg'7>C|4> Ծj|^Rg'7>C|4> ԾI W7>C|4> ԾIO=o|fԾIO=o|h|}3O=o|h|}3GpO>=^Sg'7>C|4> ԾI W7>C|4> ԾIO=o|fԾIO=o|h|}3O=o|h|m3'і7>3ZOKj $SgH7> 7>Cx4>Ծj|^RghO-o|h|}3GpOm=@{f $Sg'6>C{m|m}3O=o|h|}3GpO'%o|h|}3GpO!>{jp5>/}3GpO!>{j $SggԾIO=o|h|}3O=o|h|}3GpO'%o|h|}3GpO]-1l| Z}xAf{Ey{y86ǵ?< aw}7s3x9,؇eɯ^Ned{je!>,pO,9߿,%g,Yr/K&S/KeɄ{je!>,pO,9eɀ˒C||Y2Yr/K&S/KqUTGxI|:Bu>US:PY
+ WupO|:Bu>US::%<
+ WupO|:Bu>guK*T3\y=<
+ WupO||Tg{*T3\y=<
+/PpUTg{*T3\y= To
+ WupO|:Bu>US::%gmPhUTg{j_lU{3:Ѫ΃PpUԾ:٣:eBu>guK*T3\y=gmPhUTGxI=@[&T3Zy=<
+/PpUTg{*T3\y=>=^R:PpUTg{*T#|V{Bu>US:PpUTGxI|:Bu>US:PY
+ WupO|:Bu>US:OPpUTg{*T3\y=>=^R:PpUTg{*T#|V{Bu>US:PpUTGxI|:Bu>US|f<Ж .PpUԶ:kuP`UTGxI=@[&T3Zy=<W#[Lg`{*T3\y=gmPY
+ WupO|:Bu>US::%<
+ WupO|:Bu>guK*T3\y=<
+ WupO|?U[Bu>US:PpUTGxI|:Bu>US:PY
+ WupO|:Bum{gyxA:|%_q86:?ooo~wO_?G?wOg_>}D|z=? o>0}QCaԋ}‡Q
+FE0K*|0
+pO2\FaT(=>aTT0*a
+Fe>Sè ׇQ{*|è/aT(=>p}Fe0
+h˄"z~%>p}Feaè ևQ@{*|è/Q=>2èևQ`{*|0
+pO?l}Ӟ Fe>Sè ׇQ{jaTfL0*Q]Rè ׇQ{*|0
+pO2\FaTz‡Q
+Fe>SèFxI2\FaT(=>p}‡Q aTT0*a
+Fe>Sè ׇQ{*|è/aT(=>p}‡Q
+pO:_aTQr(<>~Oԯ_~Ӧ/kmtg_]g=4FtF\2a(h˖ {F9^L3dme’Q"!V~(NJQ~L ՄQ[& Ez2xQZ"LEz2a(5[˖ُE,ݏXs="YkElVQCELX)J9RʞOEX(ݏO'a˄qH6Q/[&,%&jeτYH*Q/[&lE&z2a(G˖ kD<LjZ3a(D˖ ;D^L!dmeQ"VLdeP$kz-"YCl:st=&"YCl7ɚe˄HP/[&, %jeτHP/[&l E&z2a`(/˖ BtK&d]e˄ܑܽlp;u-.q'ʞ 7#Y{2v$v/[&ݎd]e˄ۉ<mg­H֥^Lɺ˖ #Y׵{2v"ϻڭpS;uQ-iGniew#x\ݏpA;qLɺ˖^͎fv~ȃ{ّkٽkp);VFv ٽ1cGnce]HU^"vvZ"Ž\]a˄+ؑؽlׯ{c"\Ny=n^G.^eµH֭^Lsɺr˖ y޷neτֽ֑lp:uӺ-YGYe%Dw[3u$u/[&\dݮe˄Ցսlp:UwrɄ[ՑKսlp:u-SGSeeDw[3&u$"u/[&\dݢe˄;ԑ+Խlp:VL=ɺ<˖ W#Y7{2UtjƭW]~9ߟ5GȗwϧJ˿ܯ;1w;_/?_2o}\9+O/^>?1~獥oJW;K/^Nwi lOkK{K{*\yuK*\^p^SR
+W2\w
+w2\
+ן2\
+TT= E(=BeBp*u/p!*u#
+pO;QKQ{*\p݋SfTϫQ=^RrTv
+2\
+lOSS{j{*u}=.PenPp*u
+pOQeGeMWze m*=Se.Tp*u
+pO[U>UxIUU{*ܭp]SzU~
+7"|^
+2\
+pO;WKW{*\pݻSUϫW=^RU
+2\
+4=--h{j=h= h˄B]h]R2\__рLN֗h>Kjh=Lh 04Sah(PTp 04Sah(54Pϡ/04Sah(54PkhpOCC=^Rah(54PkhpO 
+CC>zPkhpO 
+CC!="| xI 
+CC!=2\CC{* %PTp 04Sah(54Pϡ/04Sah(54PkhpOCC=^Rah(54PkhpO 
+CC>zPkhpO  e2ah(PoTp PFCC
+CC>zCC=Lh 04SPO{& eTp Pf!-"z vI 
+CC!=2\CC{* E
+CC!=2\CC{* e
+CC>zPkhpO 
+CC!="| xI 
+CC!=hh^z~AP_tq}h wOeop3Qr9o}|=_? o_}xur-_ӄ {~Ği=u·%ӈ{<ۏ3|=nL<v9}iK>62\Z{*,ke
+Ze-=_챬e²VFkY lOeZj/keX2aY+²VkY pO헵"[Z=홰ZSaY+õZ.k1"x.ktIe ײ
+Ze-=2\Z{*,kE\
+Ze-=2\Z{*,ke
+Z>z²VkY pOe ײ
+Ze-=iYk*,ke
+Ze-=_챬e²VDe.ZSeZ@[&,keTX֊%_챬e²VFkY lOe ײ
+Z>z²VkY pOe ײ
+Ze-="|.kxIe ײ
+Ze-=2\Z{*,kE\
+Ze-=2\Z{*,ke
+Ze-="|.kxIe ײ
+Ze-=2\Z{*,kE\
+Ze-=2\Z{*,ke
+Ze-=2\Z{*,ke
+K>'zRkb pO 
+K%=&"|N,xI 
+K%=X1eRDω.0XSK@[&L,e&TXJ𧉥Rf%-&2ZK`{*L,e&
+K>'zRkb pO 
+K%=&"|N,xI 
+K%=&2\K{*L,EX
+K%=&2\K{*L,e&
+K%=&"|N,xI'2{L,m0њXSab)5KLXhM,0XSK@[&L,EX
+K%=X1eRFkb lOK=^RK@[&L,e&TXpM,0sbK*L,e&
+K>'zRkb pO 
+K%=&ibk*L,e&
+K%=&"|N,xI 
+K%=&2\K{*L,EX
+K%=&2\K{*L,e&
+K>'zRkb pO 
+KHP:/hL,=nbyEebO,]反{?SKubOS]W:ws~<]֕Nvu:A}/_ |=χ8MxIOi=u_e!FS7۾FS_霎 /tm |y:Sӈ{p#S ׄ%p 1Fn!>&~-DŽOe&
+ WpONuS B:թSSS%:N5
+ WpONuS B:gK*t3\j=:N5
+ WpONuNuTTg:Հ{*t3\j=:N5
+TxMNuS B:թSSTЩ٩w3{tLTg:`{*t3\j=TG:=ЩhuTTg:Հ{jߩѩ2SѳS%:N5w3{tLTg:`{*t#|v{j-:N5؞
+ WpONuNuTTg:Հ{*t3\j=:N5
+/ЩpuTTg:Հ{*t3\j=:>;=^RSTЩpuTTg:Հ{*t#|v{B:թSSTЩpuTTGTxINuS B:թSSTЩNNuTTg:Հ{*t3\j=:N5
+/ЩpuTTg:Հ{*t3\j=:>;=^RSTЩpuTTg:Հ{*t#|v{B:թSST}:Gh˄NuDNuoTTg:Հ{j۩S DTg:@{*t#|v{j-:N5؞
+ WpO;ՑNuO{&t3Zj=:N5w3{tLTGTvINuS B:թSSTЩ٩
+ WpONuS B:թSSS%:N5
+ WpONuS B::-^SSTЩpuTTg:Հ{*t#|v{B:թSSTЩpuTTGTxINuS B:թSS6N5ѩ~~A]yES~S7R}߾?O7ܟvXG˷t\~km|Kݾ)շޯ>]}Az~z^ ]η^O_<-2\hea-sC^zK4ڞ/p4➺ˣy$-D{|]qh{~]HqqOk=č1!sK* Sd)
+>)z0EkpOa 0
+a
+=)"|SxIa 0
+a
+=)2\{* SD
+a
+=)2\{* Sd)
+>)z=)Lh S0L
+-)"zSvIa 0Sd
+-)2Z`{* Sd)
+ 4L5)2\{* Sd)
+-)2Z`{* Sd)
+=)2\{j?Lch˄a]Ra"5L0EkpOa 0
+>)z0EkpOa 0
+a
+=)"|SxIa 0
+a
+=)2\{* S$0ETp S
+"[#=0R)
+"xtI H
+#=F
+2\#{*D)
+#=F
+2\#{*dF
+
+2\#{*dF
+
+
+#>G
+zHAk
+#=F
+ik*dF
+
+2\#{j?R)iτH؞
+#=)1R
+T)9R%)1R
+#>G
+zHAk
+#=F
+"|xI H
+#=F
+2\#{*D)
+#=F
+2\#{*dF
+
+2\#{*dF
+
+
+ZHAk
+#=F
+"|xI H
+#=F
+2\#{*D)
+#=F
+2\#{*dF
+
+2\#{*dF
+
+zHAk
+2z)
+T)9R%)1R
+z3a 5R
+2{
+
+#=F
+2\#{*dF
+
+2\#{*dF
+
+
+ WpO^u^uTUgzՀ{*3\j=z^5
+/ЫpTUgzՀ{*3\j=z>{=^RWUЫpTUgzՀ{*#|{B:իSWUЫpTUGUxI^uW B:իSWUЫ٫
+ WpO^uW B:իSWW%z^5
+ WpO^uW B:z-^SWUЫpTUgzՀ{*#|{j-z^5؞
+ WpO{Ց^uO{&3Zj=z^53{LUGUvI^uW j-z^5؞
+/}:Gh˄^uFW B:իSWW%z^5
+ WpO^uW B:gK*3\j=z^5
+ WpO^u^uTUgzՀ{*3\j=z^5
+/ЫpTUgzՀ{*3\j=z>{=^RWUЫpTUgzՀ{*W5z^5
+ WpO^uW B:gK*3\j=z^5
+ WpO^u^uTUgzՀ{*3\j=z^5
+/ЫpTUgzՀ{j߫ѫ2WѳW%z^53zU1z^5О
+/}:Gh˄^uFW B:իS^udWӞ VlO^uW j-z={ս]RWUЫpTUgzՀ{*#|{B:իSWUЫpTUGUxI^uW B:իSWUЫN^uTUgzՀ{*3\j=z^5
+/ЫpTUgzՀ{*3\j=z>{=^RWUЫpTU W /h_P׫n^U/FcWkw=>~zѲ^~3z$4_ojY>T(:g΀{*#|{B9UtS*:PtpT(:G,:xIs B9UtS*:PtYt
+E WpOs B9UtS%3
+E WpOs B9gѹK*3\Eg=3
+E WpOsϢsT(:g΀{*3\Eg=3
+EE/PtpT(:g΀{*3\Eg= Ttn
+E WpOs B9UtS%/:g(:mPthT(:g΀{j_tl{3*:PtpԾ٣ eB9gѹK*3\Eg=/:g(:mPthT(:G,:xIΙ=@[&3ZEg=3
+EE/PtpT(:g΀{*3\Eg=>=^R*:PtpT(:g΀{*#|{B9UtS*:PtpT(:G,:xIs B9UtS*:PtYt
+E WpOs B9UtSOEPtpT(:g΀{*3\Eg=>=^R*:PtpT(:g΀{*#|{B9UtS*:PtpT(:G,:xIs B9UtSsf3Ж E爞E.PtpԶkPt`T(:G,:xIΙ=@[&3ZEg=3#[EL(:g`{*3\Eg=/:g(:mPtYt
+E WpOs B9UtS%3
+E WpOs B9gѹK*3\Eg=3
+E WpOs?[B9UtS*:PtpT(:G,:xIs B9UtS*:PtYt
+E WpOs BѹfEgxAs?=~mM[t>v}λx9tj֜k|_>+4It\~q|lgݯ}ߋzB/USPp
+u WpO_B/US%~
+u WpO_B/gݯK*2\u?=~
+u WpO_?ZB/USPp
+u WpO~=~@[&2Zu?=~>~=^R_fЖ uVlO_B/gݯK*2\u?=~
+u WpO_Ϻ_Te~{*2\u?=~
+uu/Pp
+u WpO_Ϻ_Te~{*2\u?=~
+uu/Pp
+u WpO~=~@[&"zzB/USPp
+u WpO_B/USOuPp
+ךV=5xIkp}_+
+ךV=5}{*|_kk
+ךV=5}{*|_k B;gK*3\n=
+7
+ WpOwwT(pg
+܀{*3\n=
+7
+/PpT(pg
+܀{*3\n=
+> =^R*pPpT(pg
+܀{*#|{B;US*pPpT(p'Sk*3\n=
+7
+ WpOwwԾ٣ eB;US*p};UiτwF B;USwf7Ж .PpԾ٣ eB;US%/pg(pmPhT(pg
+܀{*#|{B;US*pPpT(pG,pxIw B;US*pPY
+ WpOw B;US%
+7
+ WpOw B;gK*3\n=
+7
+ WpOw?[B;US*pPpT(pG,pxIw B;US*pPY
+ WpOw B;US%
+7
+ WpO ܙ=
+@[&#z{B;USwFn?&B;US%/pg(pmPhT(pg
+܀{j_l{3*pPpԾ٣ eB;gK*3\n=
+7
+ WpOwwT(pg
+܀{*3\n=
+7
+/PpT(pg
+܀{*3\n=
+ Tn
+ WpOw B;US%
+7
+ WpOw B;gK*3\n=
+7
+z +|ΧOw~_~7z
+e|_ן?ϿU0^~+[U+~hj^v jjń{j_+pՊ/}8GpOk!>jń{j_+Q+&SZqV xIk!>jń{j_+Q+&SZqZ1׊3\bKj_+Q+&SZqZ1׊C|Ԋ ԾV^RZqZ1׊C|Ԋ ԾVVL W׊C|Ԋ ԾVVLb=gjŀԾVVLb=}8U+b=}8GpOk>k=^SZqZ1׊C|Ԋ ԾVVL W֊C{m}8GlOk!>jń{j[+Q+3ZqHZ1ٞ׊C|Ԋ ԶVkhkZ1%m8Z1і׊CzԊԾV^RZqhb-}8GpOkZ1%}8GpOk!>jń{j_+pՊ/}8GpOk!>jń{j_+Q+&SZqV xIk!>jń{j_+Q+&SZqZ1׊3\bKj_+Q+&SZqZ1׊C|Ԋ ԾV^RZqZ1׊C|Ԋ ԾVVL}8GpOk!>jń{j_+Q+&SZqV xIk!>jń{j_+Q+&SZqZ1׊3\bKj_+Q+&SZqZ1׊C|Ԋ ԾV^RZqZ1׊C|Ԋ ԶVkhkZ1%]8kLk!<jD{j_+pՊ/m8Z1і׊CzԊԾVVLb=}8GpOmkš֊̾VѪ]RZqZ1׊C|Ԋ ԾVVL W׊C|Ԋ ԾVVLb=gjŀԾVVLb=}8gkj_+Q+&SZqZ1׊C|Ԋ ԾV^RZqZ1׊C|Ԋ ԾVVL W׊C|Ԋ ԾVVLՊ>Vܽ?m:5KŇV?EfV:>ow_[Ypm_,>uW=
++
+/Ppp\T(f
+{*\3\W=
+> =^R*Ppp\T(f
+{*\#|\{B5UpS*Ppp\T(F,xIk
+B5UpS*PpYp
+ WpOk
+B5UpS%
++
+ WpOk
+B5
+-^S*Ppp\T(f
+{*\#|\{W-
++؞
+ WpO kO{&\3ZW=
++\3{\L(F,vIk
+W-
++؞
+/}5Gh˄kF
+B5UpS%
++
+ WpOk
+B5gK*\3\W=
++
+ WpOkςkT(f
+{*\3\W=
++
+/Ppp\T(f
+{*\3\W=
+> =^R*Ppp\T(f
+{*\5
++
+ WpOk
+B5gK*\3\W=
++
+ WpOkςkT(f
+{*\3\W=
++
+/Ppp\T(f
+{j_pQp2ѳ%
++\3z-1
++О
+/}5Gh˄kF
+B5UpSkdӞ ׌VlOk
+W-
+= ]R*Ppp\T(f
+{*\#|\{B5UpS*Ppp\T(F,xIk
+B5UpS*PpMkT(f
+{*\3\W=
++
+/Ppp\T(f
+{*\3\W=
+> =^R*Ppp\T(6̴
+/h\_PWpm^..~:^qj\k~Cp|Lh.͆z=<_%z'
+= WpOg B3gϳK*<3\=O=z'
+= WpOgϞgTyfz{*<3\=O=z'
+==/p<Tyfz{*<3\=O=z>{=^Ryp<Tyfz{*<#|<{B3Syp<Ty&Sϳk*<3\=O=z'
+= WpOgϞgԾ٣ eB3Sy}3iτgF B3Sgf'Ж =ψ=.p<Ծ٣ eB3S%yfymh<Tyfz{*<#|<{B3Syp<TyFyxIg B3Sy
+= WpOg B3S%z'
+= WpOg B3gϳK*<3\=O=z'
+= WpOg?<[B3Syp<TyFyxIg B3Sy
+= WpOg B3S%z'
+= WpO{=z@[&<#z<{B3S۞gF=O?&B3S%yfymh<Tyfz{jl<{3yp<Ծ٣ eB3gϳK*<3\=O=z'
+= WpOgϞgTyfz{*<3\=O=z'
+==/p<Tyfz{*<3\=O=z l
+= WpOg B3S%z'
+= WpOg B3gϳK*<3\=O=z'
+=Ϧ< z+=c
+= WpOgϞgTyfz{*<3\=O=z'
+==/p<Tyfz{*<3\=O=z>{=^Ryp<Tyfz{*<#|<{B3Syp<TyFyxIg B3SyLgTyfz{*<3\=O=z'
+==/}3Gh˄gF B3SgdӞ =όVlOg ==O-z={]Ry}3Gh˄gF B3gϳKj2yp<TyFyxIg B3Sy
+= WpOg B3S%z'
+= WpOg B3gϳK*<3\=O=z'
+= WpOgϞgTyfz{*<3\=O=z'
+=yxMg B3Sy
+= WpOg B3S%z'
+= WpOg B3gϳK*<3\=O=z'<3{<LyFyvIg =ό^{
+==/p<Tyfz{*<3\=O=z>{=^Ryp<Tyfz{*<5z'
+= WpOg B3gϳK*<3\=O=z'
+= WpOgϞgTyfz{*<3\=O=zM-1y =t9n<W", v=;]O/mөcxo?}w|~[ '}k<ǯ?n_}x#/FS7x7^i=5~=,4%u?9ӈ{5}i=ukmy:[ӄ |y:{ӈ{ۏ3|=n9>}=9ӷ}&n9ӷ}FS7>N#}w{\fF2_ˏx?8MvIO픾4➺_wklF27\{FS_ˣ99u𚺁/VyfF27^Oh=_O#ho!a=sC>{i=uGӈ{~]Y?&fOq蒺/zHqqOryys |y6ciy:Sӄ |y:Sӈ{<9}i=uPFSaf*TTpL03ᚙSaf*53Tϙ/03ᚙSaf*53Tkf
+pO3S=^Raf*53Tkf
+pO 
+3S>gzTkf
+pO 
+3S)=f"|LxI 
+3S)=f2\3S{*LE
+3S)=f2\3S{*Lef
+pO3S=^R3S@[&LefTpLTdkf=f2Z3S`{*Lef
+pOg2{Lm03њSaf*T~f*Ж 3S)=f2\3S{*LE
+3S)=f2\3S{*Lef
+pOj
+3S)=f2\3S{*Lef
+3S)=uf
+03Saf*T~f*Ж 3S)=f2\3S{j?3ٚiτ؞
+3S)=13eTDϙ.03ᚙSaf*53Tkf
+pO3S=^Raf*53Tkf
+pO 
+3S>gzTkf
+pO 
+3S)=fifk*Lef
+0=`"|xI
+0=`2\0{* D
+0=`2\0{* d`
+0 4
+0=l g
+0>`z
+0=`"|xI
+0=`2\0{* D
+0=`2\0{* d`
+0>`z
+0=`"|xI
+0=`2\0{* D
+0=`2\0{j?
+0=`:\0{* TH
+0=`:\0{* 4
+%
+WpOeeGj.\v*\mPh.T(\v
+{j.\V
+L(\v
+`{*.;\K=5.;{.L(\V.\f{BUSsᲳWh˄eGp Bw2#5.;{.L(\v
+`{*.;\K=
+ Pp.T(\v
+{*.;\K=
+ Pp.T(\v
+{*.;\K=
+ Pp.T(\v
+{*.;\K=
+ Pp.T(\v
+{*.;\K=
+ Pp.T(\v
+{*.;\K=
+ ~ULep BUSp*\P]Hep BUSp*\P]Hep BUSp*\P]Hep BUSsᲳWh˄eEeG*.;\K=5.;R}".;XK=
+ p٫p eBᲣUSp*\pY*\f3p*\Pp.\U2pYѻp
+WpOep BUSpYp
+WpOep BUSpYp
+WpOep BUSpWˈT(\v
+{*.;\K=
+%
+
+߅ˌT(\v
+{*.;\K=
+%
+
+߅ˌT(\v
+{*.;\K=
+.JF
+<ޝ..
+?󗿰qy? 6.gs>G(4. ܸ,ո$SsWpO͍W%{jn\j\qYqIeq H͍_K=57.K|5. ܸ,ո$Ssո|e%រ%{jn\j\qj\>RsWpO͍_K=57.K|5. ܸp5.qYqIe%រ%{jn\Vn\f|e%រ%{jn\j\qj\>Rc㲴/K-37.Kz5.ܸ,ո$Sc㲳Wh̍˒^K=57.K|5. ظ,Kh̍ˎV쑚%{jl\qIeeI%ٞ%#56.KҸ$2s㲤WlO͍_K=57.;\KGjn\j\qYqIe%រ%#57.K|5. ܸ,ո$SsWpO͍W%{jn\j\qYqIeq H͍_K=57.K|5. ܸ,ո$Ssո|e%រ%{jn\j\qYq%{jn\j\qYqIeq H͍_K=57.K|5. ܸ,ո$Ssո|e%រ%{jn\j\qj\>RsWpO͍_K=56.KҸ$2s㲣ո{e%រ%qIܸ,ո$Ssո|ei_D[fn\j\qYqIeg%О%d{jn\j\qYڗ%і%#57.K|5. ܸ,ո$SsWpO͍W%{jn\j\qYqIeq H͍_K=57.K|5. ܸ,ո$Ssw2357.K|5. ܸ,ո$SsWpO͍W%{jn\j\qYqIeq H͍_K=57.K|5. ܸLqIܻpq>__15.O_T|x8?˗ު\x?.^P<!
+
+ÌT(v{*;\C=!
+
+ÌT(v{*;\C=!
+
+ÌT(v{*;\C=!
+
+ÌT(v{*;\C=!
+
+ÌT(v{*;\C=!
+*F|BU<Sx*P<pT(V.f|ag!Ж ÎVlOaxaexiτaGxBU<SsWh˄aEaG*;\C=5;{L(v`{*+|3>RsWh˄aGxBU<SxXx
+WpOaxBU<SxXx
+WpOaxBU<SxXx
+WpOaxBU<SxXx
+WpOaxBU<SxXx
+WpOaxBU<SxWÈT(v{*;\C=!
+
+ÌT(v{*;\C=!
+
+ÌT(v{*;\C=!
+
+ÌT(v{*;\C=5;{L(V.f{BU<Sc/C'BU<SxXx񑚋@[&;ZC=!a=!؞
+WpO^C-P<pT(v{*;\C=P<pT(v{*;\C=P<pT(v{*;\C= ~U<LaxBU<Sx*P<]<HaxBU<Sx*P<]<HaxBU<Sxqm>*^T< "ğǏ?zLO]jK??}h^7ʜ秛/y<Oܻ=~>i=t8D/O#7C_FS?_οǾ&|Ͽ]c_O#7oӈ{ 6a-cxoiG tx~>?4z:?7P/O׀ο?kp= L57SH[ znh{ 9>?ӈ{߾D{ txz}ii=t8?4z9<?">cx8>ߵ?4#~<zqO/oᗧwp_/O#otjiG ۹??4z?v}=Ͽ<ޘc9^zz?_zqO
+
++8=Vp*|d|
+NkpO
+
++8=Vp*|d|
+NkpO
+
++8=Vp*|d|
+NkpO
+
++8=Vp*|d|
+NkpO
+
++8=Vp*|d|
+NkpO
+
++8=Vpj'3Vp:\+8{*tVp
+NkpO+8L{&tVpTXp
+Ж +8Wp=Raõ^+8@[&tVpTX𽂓Wp:{
+NTXp
+NTXp
+NTXp
+NTXp
+NTXp
+N_D|
+NkpO
+
++8=Vp*|d|
+NkpO
+
++8=Vp*|d|
+NkpO
+
++8=Vp*|d|
+NkpO
+Wp:{
+a
+NԼkh˄
+؞
++8=5TVp2홰ZSaõ^+8@[&T^H
+
++8=Vp:\+8{*T^H
+
++8=Vp:\+8{*T^H
+
++8=Vp:\+8{*4
+Ng*tVp
+NG*tVp
+NG*tVp
+e˄"P%e˄P!_<2Tj
+O-
+<N-3wXZ"Tw*W͝ [&v*Y,[f.Td}"4vwv"PשdulթdUulPԩdtli]҉gBEɲeB?UɲeB9ɲeB3w1'ʞ JV+'˖ JV%'˖ JV'˖ mB*$ydBɲeBUɲeB ɲeBw'ʞ JV&˖ ݛJV&˖ śJV&˖ Fޥ({&Tn*Y,[&m*Yu,[&mn?ڮM4jsiRE*ڬ;hڬTTmOvkNo_B\}}{ߍn~\wn׻
+WopOffG*7;\M='
+WpOggG*9;\MN=2'
+uWpOFgJgG*:;\N=zb'
+WpOvg_;#>Sjxp<Tyvz{*4=+|W=3>RsٳWh˄gG BSs볲U̴gBS*٫ eBw4#J({@[&TA;Z]P=ڠ٫
+eB'U
+S ] Hrh
+B?US"]Hh)
+BWUS.]Hh5
+BoUS:]HiA
+BU"SF$]%H2iM
+BU(SR*mZig*K;\R=r)
+WpOiiG*L;\-S=z)
+UWpOiiG*N;\S=:)
+WpOiiG*O;\S=*+:@[&P+zP=Rjї2*H]IHͥ^T-zb*؞
+W7pOV=5Ӟ ՎVClOj
+jg*Ж MՊUlT(vڪ{*U;\U=**
+
+ߵՌT(v{*tW;\U=*
+
+֌T(vZ{*X;\EV=.+
+mF|BhS*PkpZThVf|BnS*Pqpu\ThVf|BѵtS*Pw}̮
+h^?PjOENog*|{zGN^Wo4]_~ۿ߿}˯ݝ~ҹq&v֕>r(^@Ͽ{?8З3~yqO.߽a=|:<<i=t[gFSo/GY'~&zϿc_O#77_◧wp_/O#7jӄp
+= *|d|^@[&t TX/p
+=5tZ/
+ 2>RzAg- :Z`{*t
+= :\{*t
+= :\{*t
+= :\{*t
+= :\{*t
+= :\{*t
+^Z/
+^Z/
+= :\{*T^/Hz
+= :\{*T^/Hz
+= bþ[/ ?z#_| z^O_oWӍK!ܻOy41x<~XiG txiӈ{ 9zqO;w41t<^zz?^^Z4z:zqO^i=cy|niG yxii=~/O#7o_FS?_οǾ&|Ͽ]c_O#7oӈ{ 6oTF2?_g&{OS3_O#7Sm{i˼!34ڞzcayxsӀeתЖ ^U/=V:\^{j^lze3aիªWk pO^}Y}"zU^HUת
+^U/=V:\^{*zU^HUת
+^U/=V:\^{*zU^HUת
+^U/=V:\^{*zU^HUת
+^U/=V:\^{*zU^HUת
+^U/=V:\^{*zU^HUת
+^U/=V:\^{*zU^HUת
+^U/=V:\^{*zU^HUת
+^U/=V:\^{*z5ժWg*zuV
+߫^ZSaիõªWk pOU
+߫^ZSaիõªWk pOU
+߫^ZSaիõªWk pOU
+߫^ZSaիõªWk pOU
+߫^ZSaիõªWk pOUZLUת
+^U/=V:\^{*zU^HUת
+^U/=V:\^{*zU^HUת
+^U/=V:\^{*zU^HUת
+^U/=5zuZ2aիWG*zuV
+߫^yիתЖ ^U/=V:\^{j^lze3aիªWk pOͫ^VLX
+^U/=V:\^{*zuV
+^U/=V:\^{*zuV
+^U/=V:\^{*zuV
+cb11=5U20&SaL5&1>*xez˜XkL pO1ט
+cb11=*|e|˜XkL pO1ט
+cb11=*|e|˜XkL pO1ט
+cb11=*|e|˜XkL pO1ט
+cb11=*|e|˜XkL pO1ט
+cb11=*|e|˜XkL pO1ט
+cb11=*|e|˜XkL pO1ט
+cb11=*|e|˜XkL pO1ט
+cb11=jL,3:\cb{*u
+cb11=*|e|˜XkL pO1ט
+cb11=*|e|˜XkL pO1ט
+cb11=*|e|˜XkL pO1ט:{m0&V{L,#:\cb{j˘aL5&˜X1<&kL h˄1֘؞
+cb11=5U20&SaL5&1^cb@[&UH1ט
+cb11=:\cb{*UH1ט
+cb11=:\cb{*UH1ט
+cb11=:\cb{*5՘Xg*u
+5ߌ\-U%SsͷW͗pO5_5_=5|;\5_Gj[Ko/រk/#5|K|| \-U%SsͷW͗pO5Wk%j{j[ڗ/іk/#5|K|| T-o5_'o /ўk/#5|KR%2sͷW͗lO5_5_=5|;{|\-U%SsͷW͗pO5Ҿ|\h|[Ko/រk%j{jvj\-U%SsͷW͗pO5_5_=5|;\5_Gj[Ko/រkk[Ko/រk%j{jvj\-U%SsͷW͗pO5_5_=5|;\5_Gj[KonV5_@}ǧ5rx8}{폳?|x|=o=o7_1}/x1ɏrUՋs>Eqn(EE=RK|(pO/.h=5( SA^A
+ WpOyAB SA^A
+ WpOyAB SA^A
+ WpOyAB SA^A
+ WpOyAB SA^A
+ WpOyAB SA^A
+ WpOyAB SA^A
+ WpOyAB SAW Thw{*4;\ r=9
+
+ 2Aj p5 l53 h5Thw{jnwjm HyAyg9Ж VlOyyGjnwjm h5Thw{*4+|73>RAj p5Thw{*4+|73>RAj p5Thw{*4+|73>RAj p5Thw{*4+|73>RAj p5Thw{*4+|73>RAj p5Thw{*4A
+ WpOyAB SA^A
+ WpOyAB SA^A
+ WpOyAB SA^A
+ WpOyAyg9Ж lThw{jlwADhw@{*4+|73>RsWh˄yGAB Ss ϴgB SAjA٫AeBw<#9
+ WpOyABw<#9
+ WpOyABw<#9
+ WpOyAB p5Thw{*4;\ r= p5Thw{*4;\ r= p5Thw{*4ckZ 5 HS_ϣ>og?~_?_~ק@xSyJh~t$a˄JօP-*YAYL8j}eτˠJaP-΂*YWAYL de2 =P=*Y@YL8d]e2ue˄#F7@QL
+'˖ 78,[&4򾿉gM%&˖ 7˛,[&TnlptSW77IpqS:ɲe¹M%&˖ 6S,[&4򾳉g•M%&˖ '6 ,[&Tklp\&ʞ 5Ú,[&TjlpSs=ɟf]\}5?_~Osg˻ԬtQs:|x/~/- ys ӭ4}"¾(+g|¾rk_pO}׾2
+}e=+|+g|¾rk_pO}׾2
+}e=+|+g|¾rk_pO}׾2
+}e=+|+g|¾rk_pO}׾2
+}e=j_93;\ʀ{*+wTWp+\{_9#5+wW2a_ ¾rk_pOʕ}L{&+wTWp+y_׾2Ж =Ra_õ }^@[&+wTW𽯜;{+mWSa_õ ¾r}匏ThNuS{*:\)=S
+
+ThQujT{*:\M*=T2
+u
+}ThTu*U{*:\*=zUb
+ݪThWuU{*:\ +=:V
+5
+=ThZuV{*:\m+=V
+\E|BUSxj^нp
+u
+}U2jmp
+_
+W pOWWG*4:\/=_
+W pO
+XXG*:\50=`&
+]PWj`V=ܮOwqw_~vOeN*?L-~*"]U_E>PS᪺uU Uuꌏ|Uh˄U5؞
+Wj=5_UW3pUѺS᪺uU ^W@[&\UWHU5;{]UmpUѺS᪺UuGjuU eUuGlOU5
+W3>R᪺uU UupOU5
+W3>R᪺uU UupOU5
+W3>R᪺uU UupOU5
+W3>R᪺uU UupOU5
+W3>R᪺uU UupOU5
+W ~uU
+Wj=;\WՀ{*\UwT}U
+Wj=;\WՀ{*\UwT}U
+Wj=;\WՀ{*\UwT}U
+Wj=;\WՀ{juU eUuElTp]U񪺣/W
+W3>RUugj-;ZW`{*\Uw|U]ٺδgUuGlOU5;{]UmpU]:#;\WՀ{*\UwTp]UpU]:#;\WՀ{*\UwTp]UpU]:#;\WՀ{*\UwTp]UpUWWpUẪS᪺uU UupO
+WpUẪS᪺uU UupO
+WpUẪS᪺uU Uu8mz:|(rzx;tU}Ϸ$~zO?zLO]n./~:?}r_~~??a3Wl_^[{Z{/ne_Յ>PS᲻u ewpO
+ߗe7Ж n=.;\݀{jl]vg3᲻u ewpO͗ݝ.L}ٝ
+n=5_vw2᲻u ew|h˄e7؞
+n=.+|_vg|ewpOe7
+n=.+|_vg|ewpOe7
+n=.+|_vg|ewpOe7
+n=.+|_vg|ewpOe7
+n=.+|_vg|ewpOe7
+n=.;3.;\݀{*\vw.Tp]vp];#.;\݀{*\vw.Tp]vp];#.;\݀{*\vw.Tp]vp];#.;\݀{*\vw.|h˄ޗpSewG_.~n=.+|_vg|^@[&\vw.Tp]vuٝiτe7؞
+n=5_vw2᲻ewG*\vw.Tp]vpS᲻ewG*\vw.Tp]vpS᲻ewG*\vw.Tp]vpS᲻.#>S᲻u ewpOe7
+/3>R᲻u ewpOe7
+/3>R᲻u ewpOxa]vZ(]vOt燧?dgpS |';gdZ3~~;Wrx||:<~3?{i^~ӈ{pz}ӄ~yqO?NO}=_/OO41~_sc_O>RoKc_O#7o◧sZ/O#/]c_O>Roswli=rw[H[prd}>?4z?Sm{i˼!34ڞ!~_O7vF2osK_FSo__FS?Ə_]a=|:<4ڞz:_^۟zqOg~1{<zz?^|=ϿǷ?ӈ{ y엧ԏs:?4#~ܟzqO;LJǾFSo}_FS?Ǿ&|OǗǾFSoOǾFSo/ǧ~yqO<v_K|=MH;?i=~^zqO;~yqO:v}=MH;cw1i=~ݷ?4z9<i=crjiG txx~hi=xoy<i=~<>?ӈ{ӄszni=~??4z?v/-~yqO=v^zz?ӈ{ zӈ{ pz9?ӈ{W;OySaõSk pO
+;Oy穳Ж ;O'=v:\;O{jyl<e3a穣Sk pO;OvLy
+;O'=5<uy2a穣SԼk h˄؞
+;O'=v*|<e|Sk pO
+;O'=v*|<e|Sk pO
+;O'=v*|<e|Sk pO
+;O'=v*|<e|Sk pO
+;O'=v*|<e|Sk pO
+;O'=vj)3v:\;O{*<uv
+;O'=5<uy2a穢SG*<uv
+;Ow2>RaõSk pO
+;Ow2>RaõSk pOP<Z;OSDoo}5ڡoSߖ>/uo<UȇSOȻ@s>5>5-3t@y @JBe5^k d{j^@JBe5^k d{j^)B5^k @{f^)B5_k {jZ)ok Ok 5Gj^)B5_k {j^)B5#5Z!SH5=5Z!SHk @J|y រ@J|y õHk %@ԼRk pOk %@ԼZ|5_k {j^)B5_k {j^p
+ =5o4T62홰h
+ =6*|o4d|FCkpOF
+ =6*|o4d|FCkpOF
+ =6*|o4d|FCkpOF
+ =6*|o4d|FCkpOF
+ =6*|o4d|FCkpOF
+ =6*|o4d|FCkpOF
+ =6*|o4d|FCkpOF
+ =6j!36:\ {*l4t6
+ =6*|o4d|FCkpOF
+ =6*|o4d|FCkpOF
+ =6*|o4d|FCkpOF7:{m4
+ =5o4T62홰h
+ =6:\ {*l4ThHF
+ =6:\ {*l4ThHF
+ =6:\ {*l44FCg*l4t6
+MWpOii
+BӴ4SiZi
+MWpOii
+BӴ4SiZi
+MWpOii
+BӴ4SiZi
+MWpOii
+BӴ4SiZi
+MWpOii
+BӴ4SiZi
+MWpOii
+BӴ4SiWMӈThv{*4M;\MS=)
+M
+Mӌ442ij4p5M4l5M34h5MThv{jnvjm44Hii
+ig)Ж MӎVlOiiGjnvjm4h5MThv{*4M+|7M3>Rij4p5MThv{*4M+|7M3>Rij4p5MThv{*4M+|7M3>Rij4p5MThv{*4M+|7M3>Rij4p5MThv{*4M+|7M3>Rij4p5MThv{*4Mi
+MWpOii
+BӴ4SiZi
+MWpOii
+BӴ4SiZi
+MWpOii
+BӴ4SiZi
+MWpOii
+ig)Ж MӊMlThv{jlvi
+Dhv@{*4M+|7M3>RsӴWh˄iGi
+BӴ4SsӴ4ʹgBӴ4Siji٫i
+eBӴw4#)
+MWpOii
+BӴw4#)
+MWpOii
+BӴw4#)
+MWpOii
+BӴ4p5MThv{*4M;\MS=4p5MThv{*4M;\MS=4p5MThv{*4Mc'kZMo|=<}+zl5MOm:=O//o?˯cv$tקc?}&织o>5[T9|zg=^z3_ї;>^\ΙzTxs΀{*޹zg=^\zTxstBUSt_t
+WpO}tBUSt_t
+WpO}tBUSt_t
+WpO}tBUSt_t
+WpO}tBUSt_t
+WpO}tBUSt_t
+WpO}tBUStWT(wJ{*;\{=J=
+
+ߥ\U2t*݃Pp\l3PhT(wJ{j.w*mP]H}t}g=Ж VlO}}Gj.w*mPhT(wJ{*+|3>Rt*PpT(wJ{*+|3>Rt*PpT(wJ{*+|3>Rt*PpT(wJ{*+|3>Rt*PpT(wJ{*+|3>Rt*PpT(wJ{*t
+WpO}tBUSt_t
+WpO}tBUSt_t
+WpO}tBUSt_t
+WpO}t}g=Ж ޥlT(wJ{j,wtD(wJ@{*+|3>Rs龳Wh˄}GtBUSs龲UϴgB龣USt*t٫teB龢w>#J=
+WpO}tBw>#J=
+WpO}tBw>#J=
+WpO}tBJPpT(wJ{*;\{=JKPpT(wJ{*;\{=JKPpT(wJ{*cۻ+ZJ}C׿==|#?h1=r^߿?/~|ӯohMt'y||9}qܻ;~翧9^߾L|=MH//-~yqO?NO<_/OO41|<>?4#~<?4z?vNoᗧsZ/O#/]c_O>Roswli=rx~ۚ0Җz<ܷܿ?4#~:<?ڟzqO?jH[ y|旧ԏfw_6|Ͽ0ҖyC>f[4ڞz?j4zc2Tٚ[ʴgRGkn lO:2D[=
+sK%=:\sK{*-u
+sK%=:\sK{*-u
+sK%=:\sK{*-u
+sK%=:\sK{*-u
+sK%=:\sK{*-u
+sK%=:\sK{*-u
+sK%=:\sK{*-u
+sK%=:\sK{*-u
+sK%=5-U20њ[San5湥^sK@[&-U[H:{-m0њ[SanRGj[5eRGkn lO
+sK2>Ran5Rkn pO
+sK2>Ran5Rkn pO
+sK2>Ran5Rkn pO
+sK2>Ran5Rkn pO
+sK2>Ran5Rkn pO
+sK ~5
+sK%=:\sK{*-u
+sK%=:\sK{*-u
+sK%=:\sK{*-u
+sK%=:\sK{j[5eRE﹥lT[p-qn/sK
+sK2>RRg%-:ZsK`{*-u
+sK0[San5Rkn pO
+sK0[San5Rj'(zϧKك[K_ߟ/O_oO?|/ӧ>
+ sO4?u\}7>PfpOo+|
+߬wYS7{*|fpOo+|
+߬wYS7{*|fpOoTf:
+߬wYS7{*|^Tf:
+߬wYS7{*|^Tf:
+߬wYS7{*|^Tf:
+߬wYS7락Y2߬g{7ozG_Y}"|fhOo+|񑚿Y:Ж ߬wYS7{jfz=Yh}7ozgoցLf7zu=Yp}7oTf7zu=Yp}7oTf7zu=Yp}7oTfYLo;\߬zu=Yp}7YHo;\߬zu=Yp}7YHo;\߬zu=Y_߬Z߬_o'_d*t{jxz꧃}M/7xx:ުW櫋^_PϽm ??@74#~:޿z_FSoO/O#7b
+p
+pOͯl*Ӟ h
+lOUu^WUuuU
+p
+pOUU~]UG**=^Wz]
+p
+pOUU~]UG**=^Wz]
+p
+pOUU~]UG**=^Wz]
+p
+pOUU~]UG**=^Wz]
+p
+pOUU~]UG**=^Wz]
+p
+pOUU~]UG**=^Wz]
+p
+pOUU~]UG**=^Wz]
+p
+pOU5">SuUU
+h˄Uu^W:\S*[ʴg:ZSuUUuU^We*z*#^Wz]_WuU@[&*=^WUuUuU^We:ZSuUU
+߯HUu^W:\SuUU
+߯HUu^W:\SuUU
+߯HUu^W:\Z{*juF
+ZQ-=F:\Z{*juF
+ZQ-=F:\Z{*juF
+ߣZ0SaT5¨VkT pOQ
+ߣZ0SaT5¨VkT pOQՊLQר
+ZQ-=F:\Z{*jUHQר
+ZQ-=F:\Z{*jUHQר
+ZQ-=F0T;hj]ۣZU=^W[ZuU>}??~Y?}/_ws2Ml)>=!njƿ}zz
+^@[&V>@H*P;{mp:@S
+ejG
+P3>R
+j
+P3>R
+j
+P3>R
+j
+P3>R
+j
+P3>R
+j
+ ~u
+T=P;\{*vPT8@}
+T=P;\{*vPT8@}
+T=P;\{*vPT8@}
+T=P;\{j>@u
+ejElT8@p
+P3>RjgT-P;Z`{*vP|Z:@ʹgjG
+j
+p:@S
+j
+p:@S
+j<|PP':I^~S߇^O`~`ӷ]O}4endstream
+endobj
+1514 0 obj <<
+/Type /Page
+/Contents 1515 0 R
+/Resources 1513 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 1215 0 R
+/Annots [ 1517 0 R 1518 0 R 1519 0 R 1520 0 R 1521 0 R 1522 0 R 1523 0 R 1524 0 R 1525 0 R 1526 0 R 1527 0 R 1528 0 R 1529 0 R 1530 0 R 1531 0 R 1532 0 R 1533 0 R 1534 0 R 1535 0 R 1536 0 R 1537 0 R 1538 0 R 1539 0 R 1540 0 R 1541 0 R 1542 0 R 1543 0 R 1544 0 R 1545 0 R 1546 0 R 1547 0 R 1548 0 R 1549 0 R 1550 0 R 1551 0 R 1552 0 R 1553 0 R 1554 0 R 1555 0 R 1556 0 R 1557 0 R 1558 0 R 1559 0 R 1560 0 R 1561 0 R 1562 0 R 1563 0 R 1564 0 R 1565 0 R 1566 0 R 1567 0 R 1568 0 R 1569 0 R 1570 0 R 1571 0 R 1572 0 R 1573 0 R 1574 0 R 1575 0 R 1576 0 R 1577 0 R 1578 0 R 1579 0 R 1580 0 R 1581 0 R 1582 0 R 1583 0 R 1584 0 R 1585 0 R 1586 0 R 1587 0 R 1588 0 R 1589 0 R 1590 0 R 1591 0 R 1592 0 R 1593 0 R 1594 0 R 1595 0 R 1596 0 R 1597 0 R 1598 0 R 1599 0 R 1600 0 R 1601 0 R 1605 0 R 1606 0 R 1607 0 R 1608 0 R 1609 0 R 1610 0 R 1611 0 R 1612 0 R 1613 0 R ]
+>> endobj
+1517 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 707.2479 148.9411 716.0945]
+/Subtype /Link
+/A << /S /GoTo /D (products) >>
+>> endobj
+1518 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 707.2479 538.9788 716.0945]
+/Subtype /Link
+/A << /S /GoTo /D (products) >>
+>> endobj
+1519 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 692.2392 238.1759 703.1431]
+/Subtype /Link
+/A << /S /GoTo /D (create-product) >>
+>> endobj
+1520 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 692.2392 538.9788 703.1431]
+/Subtype /Link
+/A << /S /GoTo /D (create-product) >>
+>> endobj
+1521 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 679.2877 212.1537 690.1917]
+/Subtype /Link
+/A << /S /GoTo /D (edit-products) >>
+>> endobj
+1522 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 679.2877 538.9788 690.1917]
+/Subtype /Link
+/A << /S /GoTo /D (edit-products) >>
+>> endobj
+1523 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 666.3363 400.7249 677.2402]
+/Subtype /Link
+/A << /S /GoTo /D (comps-vers-miles-products) >>
+>> endobj
+1524 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 666.3363 538.9788 677.2402]
+/Subtype /Link
+/A << /S /GoTo /D (comps-vers-miles-products) >>
+>> endobj
+1525 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 653.3849 298.2202 664.2888]
+/Subtype /Link
+/A << /S /GoTo /D (product-group-controls) >>
+>> endobj
+1526 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 653.3849 538.9788 664.2888]
+/Subtype /Link
+/A << /S /GoTo /D (product-group-controls) >>
+>> endobj
+1527 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [142.4658 640.4334 342.8827 651.3374]
+/Subtype /Link
+/A << /S /GoTo /D (group-control-examples) >>
+>> endobj
+1528 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 640.4334 538.9788 651.3374]
+/Subtype /Link
+/A << /S /GoTo /D (group-control-examples) >>
+>> endobj
+1529 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 627.482 164.4429 638.3859]
+/Subtype /Link
+/A << /S /GoTo /D (components) >>
+>> endobj
+1530 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 627.482 538.9788 638.3859]
+/Subtype /Link
+/A << /S /GoTo /D (components) >>
+>> endobj
+1531 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 616.5879 148.3831 625.4345]
+/Subtype /Link
+/A << /S /GoTo /D (versions) >>
+>> endobj
+1532 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 616.5879 538.9788 625.4345]
+/Subtype /Link
+/A << /S /GoTo /D (versions) >>
+>> endobj
+1533 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 603.6364 157.7975 612.4831]
+/Subtype /Link
+/A << /S /GoTo /D (milestones) >>
+>> endobj
+1534 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 603.6364 538.9788 612.4831]
+/Subtype /Link
+/A << /S /GoTo /D (milestones) >>
+>> endobj
+1535 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 588.6277 135.6609 599.5316]
+/Subtype /Link
+/A << /S /GoTo /D (flags-overview) >>
+>> endobj
+1536 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 588.6277 538.9788 599.5316]
+/Subtype /Link
+/A << /S /GoTo /D (flags-overview) >>
+>> endobj
+1537 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 575.6763 221.2794 586.5802]
+/Subtype /Link
+/A << /S /GoTo /D (flags-simpleexample) >>
+>> endobj
+1538 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 575.6763 538.9788 586.5802]
+/Subtype /Link
+/A << /S /GoTo /D (flags-simpleexample) >>
+>> endobj
+1539 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 562.7248 194.4404 573.6288]
+/Subtype /Link
+/A << /S /GoTo /D (flags-about) >>
+>> endobj
+1540 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 562.7248 538.9788 573.6288]
+/Subtype /Link
+/A << /S /GoTo /D (flags-about) >>
+>> endobj
+1541 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [142.4658 551.7111 203.3969 560.6774]
+/Subtype /Link
+/A << /S /GoTo /D (flag-values) >>
+>> endobj
+1542 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 551.7111 538.9788 560.6774]
+/Subtype /Link
+/A << /S /GoTo /D (flag-values) >>
+>> endobj
+1543 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 536.822 221.8273 547.7259]
+/Subtype /Link
+/A << /S /GoTo /D (flag-askto) >>
+>> endobj
+1544 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 536.822 538.9788 547.7259]
+/Subtype /Link
+/A << /S /GoTo /D (flag-askto) >>
+>> endobj
+1545 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 523.8706 223.7305 534.7745]
+/Subtype /Link
+/A << /S /GoTo /D (flag-types) >>
+>> endobj
+1546 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 523.8706 538.9788 534.7745]
+/Subtype /Link
+/A << /S /GoTo /D (flag-types) >>
+>> endobj
+1547 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [142.4658 510.9191 247.4013 521.8231]
+/Subtype /Link
+/A << /S /GoTo /D (flag-type-attachment) >>
+>> endobj
+1548 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 510.9191 538.9788 521.8231]
+/Subtype /Link
+/A << /S /GoTo /D (flag-type-attachment) >>
+>> endobj
+1549 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [142.4658 497.9677 217.5239 508.8716]
+/Subtype /Link
+/A << /S /GoTo /D (flag-type-bug) >>
+>> endobj
+1550 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 497.9677 538.9788 508.8716]
+/Subtype /Link
+/A << /S /GoTo /D (flag-type-bug) >>
+>> endobj
+1551 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 485.0163 227.0973 495.9202]
+/Subtype /Link
+/A << /S /GoTo /D (flags-admin) >>
+>> endobj
+1552 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 485.0163 538.9788 495.9202]
+/Subtype /Link
+/A << /S /GoTo /D (flags-admin) >>
+>> endobj
+1553 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [142.4658 472.0648 233.2945 482.9688]
+/Subtype /Link
+/A << /S /GoTo /D (flags-edit) >>
+>> endobj
+1554 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 472.0648 538.9788 482.9688]
+/Subtype /Link
+/A << /S /GoTo /D (flags-edit) >>
+>> endobj
+1555 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [142.4658 459.1134 238.2658 470.0173]
+/Subtype /Link
+/A << /S /GoTo /D (flags-create) >>
+>> endobj
+1556 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 459.1134 538.9788 470.0173]
+/Subtype /Link
+/A << /S /GoTo /D (flags-create) >>
+>> endobj
+1557 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [142.4658 446.162 238.2658 457.0659]
+/Subtype /Link
+/A << /S /GoTo /D (flags-delete) >>
+>> endobj
+1558 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 446.162 538.9788 457.0659]
+/Subtype /Link
+/A << /S /GoTo /D (flags-delete) >>
+>> endobj
+1559 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 433.2105 154.5204 444.1145]
+/Subtype /Link
+/A << /S /GoTo /D (keywords) >>
+>> endobj
+1560 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 433.2105 538.9788 444.1145]
+/Subtype /Link
+/A << /S /GoTo /D (keywords) >>
+>> endobj
+1561 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 422.3164 176.9058 431.163]
+/Subtype /Link
+/A << /S /GoTo /D (custom-fields) >>
+>> endobj
+1562 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 422.3164 538.9788 431.163]
+/Subtype /Link
+/A << /S /GoTo /D (custom-fields) >>
+>> endobj
+1563 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 407.3077 240.6665 418.2116]
+/Subtype /Link
+/A << /S /GoTo /D (add-custom-fields) >>
+>> endobj
+1564 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 407.3077 538.9788 418.2116]
+/Subtype /Link
+/A << /S /GoTo /D (add-custom-fields) >>
+>> endobj
+1565 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 394.3562 240.1184 405.2602]
+/Subtype /Link
+/A << /S /GoTo /D (edit-custom-fields) >>
+>> endobj
+1566 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 394.3562 538.9788 405.2602]
+/Subtype /Link
+/A << /S /GoTo /D (edit-custom-fields) >>
+>> endobj
+1567 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 381.4048 245.0896 392.3087]
+/Subtype /Link
+/A << /S /GoTo /D (delete-custom-fields) >>
+>> endobj
+1568 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 381.4048 538.9788 392.3087]
+/Subtype /Link
+/A << /S /GoTo /D (delete-custom-fields) >>
+>> endobj
+1569 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 368.4534 170.5899 379.3573]
+/Subtype /Link
+/A << /S /GoTo /D (edit-values) >>
+>> endobj
+1570 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 368.4534 538.9788 379.3573]
+/Subtype /Link
+/A << /S /GoTo /D (edit-values) >>
+>> endobj
+1571 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 355.5019 265.3636 366.4059]
+/Subtype /Link
+/A << /S /GoTo /D (edit-values-list) >>
+>> endobj
+1572 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 355.5019 538.9788 366.4059]
+/Subtype /Link
+/A << /S /GoTo /D (edit-values-list) >>
+>> endobj
+1573 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 342.5505 234.1012 353.4544]
+/Subtype /Link
+/A << /S /GoTo /D (edit-values-delete) >>
+>> endobj
+1574 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 342.5505 538.9788 353.4544]
+/Subtype /Link
+/A << /S /GoTo /D (edit-values-delete) >>
+>> endobj
+1575 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 329.5991 204.353 340.503]
+/Subtype /Link
+/A << /S /GoTo /D (bug_status_workflow) >>
+>> endobj
+1576 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 329.5991 538.9788 340.503]
+/Subtype /Link
+/A << /S /GoTo /D (bug_status_workflow) >>
+>> endobj
+1577 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 316.6476 145.4444 327.5516]
+/Subtype /Link
+/A << /S /GoTo /D (voting) >>
+>> endobj
+1578 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 316.6476 538.9788 327.5516]
+/Subtype /Link
+/A << /S /GoTo /D (voting) >>
+>> endobj
+1579 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 303.6962 142.8541 314.6001]
+/Subtype /Link
+/A << /S /GoTo /D (quips) >>
+>> endobj
+1580 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 303.6962 538.9788 314.6001]
+/Subtype /Link
+/A << /S /GoTo /D (quips) >>
+>> endobj
+1581 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 290.7448 228.9009 301.6487]
+/Subtype /Link
+/A << /S /GoTo /D (groups) >>
+>> endobj
+1582 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 290.7448 538.9788 301.6487]
+/Subtype /Link
+/A << /S /GoTo /D (groups) >>
+>> endobj
+1583 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 277.7933 216.5673 288.6973]
+/Subtype /Link
+/A << /S /GoTo /D (create-groups) >>
+>> endobj
+1584 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 277.7933 538.9788 288.6973]
+/Subtype /Link
+/A << /S /GoTo /D (create-groups) >>
+>> endobj
+1585 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 264.8419 349.9657 275.7458]
+/Subtype /Link
+/A << /S /GoTo /D (edit-groups) >>
+>> endobj
+1586 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 264.8419 538.9788 275.7458]
+/Subtype /Link
+/A << /S /GoTo /D (edit-groups) >>
+>> endobj
+1587 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 251.8905 258.0811 262.7944]
+/Subtype /Link
+/A << /S /GoTo /D (users-and-groups) >>
+>> endobj
+1588 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 251.8905 538.9788 262.7944]
+/Subtype /Link
+/A << /S /GoTo /D (users-and-groups) >>
+>> endobj
+1589 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 238.939 303.2015 249.843]
+/Subtype /Link
+/A << /S /GoTo /D (2109) >>
+>> endobj
+1590 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 238.939 538.9788 249.843]
+/Subtype /Link
+/A << /S /GoTo /D (2109) >>
+>> endobj
+1591 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 225.9876 300.9695 236.8915]
+/Subtype /Link
+/A << /S /GoTo /D (sanitycheck) >>
+>> endobj
+1592 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 225.9876 538.9788 236.8915]
+/Subtype /Link
+/A << /S /GoTo /D (sanitycheck) >>
+>> endobj
+1593 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [70.7348 210.7796 155.476 221.6587]
+/Subtype /Link
+/A << /S /GoTo /D (security) >>
+>> endobj
+1594 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 210.7796 538.9788 221.6587]
+/Subtype /Link
+/A << /S /GoTo /D (security) >>
+>> endobj
+1595 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 195.3027 185.7426 206.2066]
+/Subtype /Link
+/A << /S /GoTo /D (security-os) >>
+>> endobj
+1596 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 195.3027 538.9788 206.2066]
+/Subtype /Link
+/A << /S /GoTo /D (security-os) >>
+>> endobj
+1597 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 184.4086 198.3256 193.2552]
+/Subtype /Link
+/A << /S /GoTo /D (security-os-ports) >>
+>> endobj
+1598 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 184.4086 538.9788 193.2552]
+/Subtype /Link
+/A << /S /GoTo /D (security-os-ports) >>
+>> endobj
+1599 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 169.3998 236.2132 180.3038]
+/Subtype /Link
+/A << /S /GoTo /D (security-os-accounts) >>
+>> endobj
+1600 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 169.3998 538.9788 180.3038]
+/Subtype /Link
+/A << /S /GoTo /D (security-os-accounts) >>
+>> endobj
+1601 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 158.4663 212.0444 167.3523]
+/Subtype /Link
+/A << /S /GoTo /D (security-os-chroot) >>
+>> endobj
+1605 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 158.4663 538.9788 167.3523]
+/Subtype /Link
+/A << /S /GoTo /D (security-os-chroot) >>
+>> endobj
+1606 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 145.5543 158.7644 154.4009]
+/Subtype /Link
+/A << /S /GoTo /D (security-webserver) >>
+>> endobj
+1607 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 145.5543 538.9788 154.4009]
+/Subtype /Link
+/A << /S /GoTo /D (security-webserver) >>
+>> endobj
+1608 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 130.5455 374.5924 141.4495]
+/Subtype /Link
+/A << /S /GoTo /D (security-webserver-access) >>
+>> endobj
+1609 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 130.5455 538.9788 141.4495]
+/Subtype /Link
+/A << /S /GoTo /D (security-webserver-access) >>
+>> endobj
+1610 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 117.5941 147.8351 128.498]
+/Subtype /Link
+/A << /S /GoTo /D (security-bugzilla) >>
+>> endobj
+1611 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 117.5941 538.9788 128.498]
+/Subtype /Link
+/A << /S /GoTo /D (security-bugzilla) >>
+>> endobj
+1612 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 104.6427 318.9317 115.5466]
+/Subtype /Link
+/A << /S /GoTo /D (security-bugzilla-charset) >>
+>> endobj
+1613 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 104.6427 538.9788 115.5466]
+/Subtype /Link
+/A << /S /GoTo /D (security-bugzilla-charset) >>
+>> endobj
+1516 0 obj <<
+/D [1514 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+1513 0 obj <<
+/Font << /F27 1222 0 R /F32 1230 0 R /F35 1604 0 R /F33 1322 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+1663 0 obj <<
+/Length 60780
+/Filter /FlateDecode
+>>
+stream
+xڜMdu>
+鷩!'SC!Kj?5cjpOB|L P!=pM ^RSC{j?5cjpOB|L PkjO "SSC{j?5cjpO2\SC~j(O "SSC{j?5SC!>~j(O "S %
+15DSC!>~j(O e
+15DSC!>~j(55xIB|L P!=
+15DSC>zSC!>~j(O "S %
+ujh짆BzL P!=15gSC!=~j(N :5DeSC!Kj?5cjpOmB{"2SCd{j?5SCN mPH!=
+15DSC!Kj?5cjpOB|L P!=pM ^RSC{j?5cjpOB|L PkjO "SSC{j?5cjpO2\SC~j(O "SSC{j?5SC!>~j(O "SSC=^SSC{j?5cjpOB|L PkjO "SSC{j?5cjpO2\SC~j(O "SSC{j?5SC!>~j(N :5DeSC!Kj?5cjpOBmjP!=pM ^R۩^~j(ٞO "S۩SC@{f?5cjlOB|L PhSCD[f?5њSC!>~j(O "S %
+15DSC!>~j(O e
+15DSC!>~j(P~j(O "SSC{j?5SC!>~j(O "S %
+15DSC!>~j Ͻ@P?q/~?O?~o<+p/O jOԺ|%jZ7
+ WpOZw B;gK*Ժ3\n=jZ7
+ WpOZwZwTugj݀{*Ժ3\n=jZ7
+/PpպTugj݀{*Ժ3\n=j>k=^R֝uPpպTugj݀{*Ժ#|ֺ{B;US֝uPpպTuGuxIZw B;US֝uPY
+ WpOZw B;US֝PpպTugj݀{*Ժ3\n=j>k=^RZwfZ7Ж VlOZw V=jZ7؞
+ WpOkݙ=j@[&Ժ#zֺ{B;USZwfZ7Ж VlOZwZwԾ֝٣ eB;US֝uPY
+ WpOZw B;US%jZ7
+ WpOZw B;gK*Ժ3\n=jZ7
+ WpOZwZwTugj݀{*Ժ3\n=jZ7
+/PpպTugj݀{*Ժ3\n=j Pn
+ WpOZw B;US%jZ7
+ WpOZw B;gK*Ժ3\n=jZ7
+ WpOZwZwTugj݀{*Ժ3\n=ugumPY
+ WpOmkֺ~ VhOZwZwԾ֝٣ eB;US֝u};UiτZwF B;USZwfZ7Ж .PpպTugj݀{*Ժ3\n=j>k=^R֝uPpպTugj݀{*Ժ#|ֺ{B;US֝uPpպTu'Ck*Ժ3\n=jZ7
+ WpOZwZwTugj݀{*Ժ3\n=jZ7
+/PpպTugj݀{*ԺqV4jݏu7w׺ؿPOպOk>|p_Ot{/b܇}wY>v_?8O]R7v;S|}qO5F27囹4ڞ__'nWzo#m^FS794?,_Ma=sC>/Ou+֗g>FS7)^9
+q=9"|sxIq 8
+q=92\{*sD
+q=92\{*sd9
+ 0592\{*sd9
+q=lsg8GFklOq 8ڏsd
+>9z8GkpOq 8
+q=9"|sxIq 8
+q=92\{*sD
+q=92\{*sd9
+>9z8GkpOq 8
+q=9"|sxIq 8
+q=92\{*sD
+q=92\{j?Αch˄q]Ra#5s
+q=92\{*sD
+q=92\{*sd9
+[ U5ߪUeVEFklO VoUD*z3a"UVEkpOm*2zݪ
+[
+=*2\[{*lUd*
+[>*zVEkpO V
+[
+=*"|nUxI V
+[
+=*2\[{*lU$VETتpmU
+-*2Z[`{*lUd*
+=*2\[{jUch˄[]Ra"õU[=*LتhmUUsKjUch˄V؞
+[
+=*"|nUxI V
+[
+=*2\[{*lUDܪ
+[
+=*2\[{*lUd*
+[
+=*2\[{*lUDܪ
+[
+=*2\[{*lUd*
+-*"znUvI VnUdUDت`mU
+[
+=ߪlmUgVEFklO VoUdت
+[ U5*2\[{*lUd*
+>1z<FkpOy <
+y =1"|cxIy <
+y =12\{*cD
+y =12\{*cd1
+y =1"|cxI12{c
+y =1e<FFklOy=^Ry@[&cd1Tpc
+>1z<FkpOy <
+y =1ak*cd1
+y =1"|cxIy <
+y =12\{*cD
+y =12\{*cd1
+>1z<FkpOy <
+͜C:hc<~u{DߟwyeC1{0cc Nd?o?g
+>zb@k1
+
+
+
+
+>zb@k1
+
+
+
+>zb@k1
+
+
+
+
+
+
+ W pOVB+S᳁%X
+ W pOVB+X-^Sj`p5
+ W pOXVO{&42Z ,=X72{4Lh`El`vIV  ,-X؞
+  /}+G h˄VFB+S᳁%X
+ W pOVB+gK*42\ ,=X
+ W pOVVTh`eX{*42\ ,=X
+  /p5
+ W pOVB+gK*42\ ,=X
+ W pOVVTh`eX{*42\ ,=X
+  /p5
+  /}+G h˄VFB+SVdӞ V lOV  ,-X=X]Rj`p5
+  /p5
+p;pOwf
+»C#|;K*;4P=zw(
+p;pOwF|whTxwhݡ{*;4P=zw(
+/ ׻CTxwhݡ{*;4P=᳹%:
+ WspOzBs=gsK*43\u=:
+ WspOzzThg{*43\u=:
+/\p5Thg{*43\u= \o
+ WspOzBs=\S᳹%oghm\h5Thg{j\l5{3j\p5Ծ٣eBs=gsK*43\u=oghm\h5ThGlxI=@[&43Zu=:
+/\p5Thg{*43\u=>=^Rj\p5Thg{*4#|6{Bs=\Sj\p5ThGlxIzBs=\Sj\\
+ WspOzBs=\S\p5Thg{*43\u=>=^Rj\p5Thg{*4#|6{Bs=\Sj\p5ThGlxIzBs=\Szf:Ж .\p5Զks\`5ׁThGlxI=@[&43Zu=:7#[Lhg`{*43\u=oghm\\
+ WspOzBs=\S᳹%:
+ WspOzBs=gsK*43\u=:
+ WspOz?4[Bs=\Sj\p5ThGlxIzBs=\Sj\\
+ WspOzBs\u@z~osrٝ˿溞\/o?קuG?qw>-hd^=o,}dI%%K=/Y(Y}2U%%K=/Y(Y}2GɒpOK%%/Y(Y}2GɒpOK!>J{j_p,/}2GɒpOK!>J{j_ Q$Sed xIK!>J{j_ Q$Se%ڗ,3\%KKj_ Q$Se%ڗ,C|, Ծdd5/Y(Y}2GɒpOK!>J{j_p,/m2ג%іٗ,Cz,ԾddI%%K=/Y(Y}2GɒpOmK,̾d*Y]Re%ږ,C{-Ym}2GɒlOK%%-YZ$2eH%ٞڗ,C|, Ծd*Y^Re%ڗ,C|, ԾddI% Wڗ,C|, ԾddI%%K=/YfJԾddI%%K=/Y(Y}2U%%K=/Y(Y}2GɒpOK%%/Y(Y}2GɒpOK!>J{j_Yڗ,C|, ԾddI%%K=/YfJԾddI%%K=/Y(Y}2U%%K=/Y(Y}2GɒpOK%%/Y(Y}2GɒpOmK,̾d*Y]Re%ڕ,CdIľd£dI% Wږ,C{-Ym}2GɒlOK!>J{j[Q3eH%ٞڗ,C|, ԶdkɒhK%%/Y(Y}2GɒpOK!>J{j_p,/}2GɒpOK!>J{j_ Q$Sed xIK!>J{j_ Q$Se%ڗ,#|,{%%K=/Y(Y}2GɒpOK%%/Y(Y}2GɒpOK!>J{j_p,/}2GɒpOK!>J{j_욁aɒ>ܻto,OpߝO-Y^}~]獖xn}줚x=ǯ~ǿ><zIO˕q\yx_oG|wrW?}woO?z}mO}~?Z=en,F279^o)>nWs?i=uMWw"{}hܐd4ڞڿ};۷ !m'o`}ڿ};۷ !>޾Mom=vۀ!>޾Mom=vo۷3|CxIa 0
+a=!2\{* CD
+a=!2\{* Cd!
+a=!2\{* Cd!
+>!z0DkpOa 0
+a=!"|CxIa 0
+a=!2\{* CD
+a=!2\{* Cd!
+>!z0DkpOa 0
+a=!"|CxIa 0
+a=1 e0DDa.0 
+a=!2\{* Cd!
+a=!"|CxIa 0
+a=!2\{* CD
+a=!2\{* C40|1 aaiw__hb<6!۷ï뛷ӯ|x??u:ïg_B=|eo7sVXK_J
+{VXTheZa{*2\0=ZaV
+/
+p
+p
+Sj
+p
+ W+ pOVXVXԾ٣eB+,
+Sj}+,
+iτVXFB+,
+SVXfVЖ .
+p
+S%oehm
+hTheZa{*"|zB+,
+Sj
+p
+Sj
+
+
+ W+ pOVXB+,
+S%ZaV
+ W+ pOVXB+,g+K*2\0=ZaV
+ W+ pOVX?ZB+,
+Sj
+p
+Sj
+
+
+ W+ pOVXB+,
+S%ZaV
+ W+ pO[a=Za@[&"zzB+,
+SVXF0'B+,
+S%oehm
+hTheZa{j
+lz3j
+p
+ W+ pOVXVXTheZa{*2\0=ZaV
+/
+p
+k
+ W+ pOVXB+,
+S%ZaV
+ W+ pOVXB+,g+K*2\0=ZaV
+VZa'֠6
+o?R:,s{9/xcIs>i}g;_zO#}w{?~{ixUawo!=&nvJi=u__%@[&:zj=^Onڿ:멁Lx=uF`{*:zj==ud=zTx=uԀ{jz^_O Dx=uStISg^O 3\SSz
+ppOSg^O 3\S>_O%^Oz=5
+ppOSg^O #|K*:zj=^Oz=5
+p
+C$ 0D5H2\C${* dH
+C$!="l gIFklO!  d"2a$IoT"p
+C$>HzIkpO! 
+C$!=H"|xI! 
+C$!=H2\C${* D"
+C$!=H2\C${* dH
+C$>HzIkpO! 
+C$!=H"|xI! 
+C$!=H2\C${* D"
+C$!=H2\C${j?Dch˄!C$]Ra$5DC$
+C$!=H2\C${* D"
+C$!=H2\C${* dH
+
+ W
+AB
+AD
+AoTd*{j_!Q!
+AB
+ W
+AB
+
+ W
+AB
+AB
+ W
+AB
+B
+
+ W
+AB
+
+ W
+A
+ATd*{*T2\=*
+
+xM
+AB
+ W
+AB
+
+ W
+AsҞV
+lWOENYqo??YB~z+\xR 8\^v_}0j)r:߷cz=N/_d1ܻY_.y=zO#uwgO#}"_FS۹x𒺁/ӈ{|;?i=u_Kc_FS7ڻ|;}cO^R7Z ˒TxYreɀ{j/K2e=_%^zY2ڿ,9˒LxYrFe`{*,9^5Yrf%m˒TxYreɀ{j˒{3e% ˒e,/K
+/Kp,pO%g^ ˒3\/KSe>_%^zY2
+/Kp,pO%g^ ˒#|,K*,9d=^zY2
+/Kp,pO%G|YrTxYreɀ{*,9d=^zY2
+/K/ ˒TxYreɀ{*,9d=^e=^Re% ˒TxYreɀ{*,9˒{˒3\/KSe% ˒TX:t%2\KG{*,e
+KG>zKG=LX:h-tZ:SQO{&,eTX:p-Qf#-"z.vI /eX:2a(tQϥ/Qf#-2ZKG`{*,e
+KG t52\KG{*,e
+KG>zQkpO 
+KG#="|.xI 
+KG#=2\KG{*,%QTX:p-tZ:Sa(õtQϥ/tZ:Sa(õtQkpOKG=^Ra(õtQkpOf']:4?Pt|a; xYxllֵÿ~~xw˷g1ɧ<.qu<ny]_>
+]Tfꮀ{*]#|]{B5UwS᪻Pwp]TFxIk
+B5UwS᪻PwYw
+u WpOk
+B5UwS%+
+u WpOk
+B5gݵK*]3\uW=+
+u WpOkϺkTfꮀ{*]3\uW=+
+uxMk
+B5UwS᪻PwYw]3{]Lf`{*]3\uW=F=Pwh]Tfꮀ{j_wQw2ѳ%+]3{]Lf`{*]#|]{uuW-+؞
+u WpOkϺkTfꮀ{*]3\uW=+
+uu/Pwp]Tfꮀ{*]3\uW=>=^R᪻Pwp]Tfꮀ{*]#|]{B5UwS᪻Pwp]TFxIk
+B5UwS᪻PwMkTfꮀ{*]3\uW=+
+uu/Pwp]Tfꮀ{*]3\uW=>=^R᪻Pwp]Tfꮀ{*]#|]{B5UwS᪻}5Gh˄kDϺkoTfꮀ{j[w
+Df@{*]#|]{uuW-+؞
+u WpO뮑kO{&]3ZuW=+]3{]LFvIk
+B5UwS᪻PwYw
+u WpOk
+B5UwS%+
+u WpOk
+B5-^S᪻Pwp]Tfꮀ{*]#|]{B5UwS᪻Pwp]TFxIk
+B5UwS2Ӻ+|Qw}@]ݵDz~}q7B_wcz\k,>-v?|g<\6ˮo~=oM}oJ} WMC|M ԾooJ}}S=fԾooJ}}S=}47}}S=}4GߔpO)%}4GߔpO!>{j7pM/}4GߔpO!>{j7 7%Sio
+xI!>{j7 7%Si)M#|M{}}S=}4GߔpO)%훆7%2iH)ٞMC|M Զo٣o
+g}Ӑ}S=m4׾)іM3Z}SKj7 7%S۾ih}S-蛒}47}^D[f7 7%Si)M3\}SKj7 7%Si)MC|M Ծo^Ri)MC|M ԾooJ} WMC|M ԾooJ}}S=fԾooJ}}S=}47}}S=}4GߔpO>=^Si)MC|M ԾooJ} WMC|M ԾooJ}}S=fԾooJ}}S=}47}}S=m4׾)іM3Z}SKj7 7%SiHM ~MCxMԾo^R۾ih}S-蛒}4GߔpOm=@{f7 7%Si)MC{m}47}}S=}4GߔpO)%}4GߔpO!>{j7pM/}4GߔpO!>{j7 7%SiϾiԾooJ}}S=}47}}S=}4GߔpO)%}4GߔpO]'2Z{n;i;?ξ<}jm<ڦkO_ӗ_ ?O5~Gs>~t~'4➺wi=9~;N9ӄ |v>FS79cO#_4/qӄ |v>FS7xqO`~/~{i|.v1.iWO|<no#m\|}mO}޴/_\O^S7y+췇 yf[JOn|}qO}fb<Lgnȧ~i=ug>FS7ȿ>;|<MtIzHi=u_8f4➺/קԍś9x𒺁/ӈ{>&2\CC{* e
+CC>zPkhpO 
+CC!=ahk* e
+CC!=14ePDϡ.04SCC@[& eT94%14ePFkhlO 
+CC>zPkhpO 
+CC!="| xI 
+CC!=2\CC{* E
+CC!=2\CC{* e
+CC!="| xI 
+CC!=2\CC{* E
+CC!=2\CC{* e
+CC!=2\CC{* e
+{ ="|5xI ^
+{ =2\{ {*5Dk
+{ =2\{ {*5d
+{ 52\{ {*5d
+{ =kl5g^CFklO ^5dk
+{ >z^CkpO ^
+{ ="|5xI ^
+{ =2\{ {*5Dk
+{ =2\{ {*5d
+{ >z^CkpO ^
+{ ="|5xI ^
+{ =2\{ {*5Dk
+{ =2\{ {jאch˄{ ]Ra!õ
+{ =2\{ {*5Dk
+{ =2\{ {*5d
+>{ƙ=L6h]pm6S8qT6p]pm6S8um µqk/pm6S8um µqpOk=^R8um µqpOk ׵1
+>{µqpOk ׵1
+kc=#|^xIk ׵1
+kc=3\ƀ{*\'õqT6p]pm6S8um µqk/pm6S8um µqpOk=^R8um µqpOk ׵1
+>{µqpOk ׵1_g628qoT6p]qF
+>{ƙ=L6h]pm6SkֵqO{&\gT6p]qfkc-#z^vIk ׵1
+kc=3\ƀ{*\G6
+kc=3\ƀ{*\gT6ym%3\ƀ{*\gT6p]pm-^S8um µqpOk ׵1
+>{µqpOk ׵1
+kc=#|^xIk ׵1
+kc=S>и6~@ݵqʵ Cۇ|_?ӏۇ񟿻o?rrܝOZ7?in<~jOeN$`-uy^봲g­N$T-u"Yw:lp:e˄D:p:e˄HmN/[&\Dsz2,'UN+{&DNrz2 'u˖ 8c^L8Iyʞ w83^L8‰deN$
+ ~>z ~=LXh-ZSւ_O{&,eTXp-_f?-"z.vI ׂ/eX2a/‚_/_f?-2Z ~`{*,e
+' pC53\gĀ{*g.T%ppNK*\gNT8*p]pW:,S8mqT.pp`0S8ud ™q;/pi:5S8um ƙ=L89ys%3\gǀ{j{x18u| q/rfd-3ZW`{*!g9uӞ sd=3\ɀ{j(h˄䈞wɽ]R29u qr:pO ׁ2
+'>o{•rLpOC ץ2
+ce=Ε#|+xI 2
+Ge=3\ˀ{*.'rT^p/p`S9u s;/pɜ:eS9u =spO7=^R9u aspO6;n4?}|}|<7}xj7w_uç]㗏?a.<~-\/ۯ #/u<Z';S|$u>H|pO#=^R|$u>H|pO 
+#>GzH|pO 
+#=G"|xI 
+#=G2\#{*$HT8p
+#>GzH|pO ڟd82|$HoT8p
+#>Gz#=GL8hp>:SHO{&dGT8p
+#=G2\#{*D<
+#=G2\#{*dG
+#>GzH|pO 
+#=G"|xI 
+#=GÅ|>8y@H~ ~}xj\w@~~wïh 8Eesoww'xz<?@Oi=u~e@=3\ˀ{*,F\/fX2a0 2`kpO#[ˀ=홰 ZSa0õ ˀ=LX %3\ˀ{j ch˄e2 ؞
+ˀ>{ˀ=LXh- ZSa02`TXp- ZSa0õ 2`/pڝ:S;u iwpO=^R;u iwpO i7
+>O{iwpO i7
+n=N#|vxI i7
+n=N3\݀{*v'iwT8pvpڝ:S;u iw/pڝ:S;u iwpO=^R;u iwpO i7
+>O{iwpO i7ڟvg82;iwoT8pviwF
+>O{ݙ=NL8hvpڝ:SiwO{&vgNT8pviwfn-N#zvvI i7
+n=N3\݀{*vG<
+n=N3\݀{*vgNT8y%N3\݀{*vgNT8pvpڝ-^S;u iwpO i7
+>O{iwpO i7
+n=N#|vxI ({I̲#<<>3Rw
+uP%uP*A\uȝ
+uP'WrBUܩP5:('N:N:S䪃@T*|PsԹWjcBԩU۩PurA wj*꠬v&AZu
+uP'Wr:s:Ƅ:R:(N:NuP^u uPVnBTw僚ν PN:N:SA:S䪃@T: ;J,T: ;N:N:SA:S䪃@T: ;J,T: ;N:N:SA:S䪃@T: ;F~UEBUܩPurA w*A\uȝ
+uP%uP*A\uȝ
+uP'WrBUܩPU]eBUܩPurA w*A\uȝ
+uP%uP*A\uȝ
+uP'Wr:s:Ƅ:R:(N:NuPA L:SAuP^u uPVnBUܩܪڙPujAv*A\uȝν PJT: ;N:N:SA:S䪃@T: ;J,T: ;N:N:SjWuPO*A\uȝ
+uP'WrBUܩPU]eBUܩPurA w*A\uȝ
+uP%uP*A\uȝ
+uP'WrBӕ :B
+7MW߿|o/O߾%~1^]ʠǟ~^8=_o~Ǎo?uӭB/[R7.tC~9rޑߟZ4ʝz[v(|&ޑ?Ci;<ǾNܩwo׏wQm}4tx}=|FS_/aT<?rN<?oL5rޑ?K~Ƽc>2/rN#?4_NܩGrμc>^ڏ{FS_Ƿ(w~΢3x8>|&ޑ?ci;<|?_Nܩw~(wmNc_I>wO>uN#?:ǧ(wyx~l(wm>uzG~:<}FS_c_Q;k/QJN|PϟSFSϟ}4ʝzG~tNcFSo_Ο}4W14ʝzG~tǾNܩwk0kNJgh|PakNN;fh:fh@T{&fh:fh@T䚡SakNJgh|PakNN;fh:fh@TiW34Q>0C5Cr M' ȝ
+34\34 w*T=C僚gh:1aSkNN;5Tnd3aSkNN;5t5Cjc M34=0C5Crνfh@mLԚSa,<Cӹ 34Z34v*trЀܩ0CS M*trЀܩ0C5Cr M' ȝ
+34|d M' ȝ
+34\34 w*trЀܩ0CS M*trЀܩ0C5Cr M' ȝ
+34|d M' ȝ
+34\34 w*trЀܩ0CS M*trЀܩ0C5Cr M' ȝ
+34j&'fh:fh@T䚡SakNJgh|PakNN;fh:fh@T{&fh:fh@T䚡SakNJgh|PakNN;5t5Cjc M34=0C5CrN}g"tbЀک0CS MjkƄNp;fh:fh@<CS5C΄Np;fh:fh@<Cӹ 34zd M' ȝ
+34\34 w*trЀܩ0CS M*trЀܩ0C5Cr M' ȝ
+34|d M' ȝ
+34\34 w*trЀܩ0Cȯfh|RakNN;fh:fh@T{&fh:fh@T䚡SakNJgh|PakNN;fhlJ;CZ34J34F|:Nz_R}|?*^=X֩5Cs ǟǷwRo?Ӭ?>loc#gt]_56[fcj^lădb=z zeVƄX2UfcB^g1!So̻RfgB^g1!Pӳ٘PWflLH.ӣٙХWfelL+l6&YAz6rƼkhv&Y)z6Bʬ= zeVƄ1= yeV~Ƅ2=fcBy^g1!;o̻:fgBs^g1!8ͳ٘PWflLH.ͣٙЙWfelL+l6&Yy6Ƽhv&Yiy6ʬ< UyeVTƄ0_<ГWflL+Zl6&Y!y62Ƽ+hvfn+Jȳ3ʫ~< xeV<x#V9Fn*bcB4^Ռg1s1^W07]G3Jų٘9īD+",6&$y܇W╇gg"Wmxʬ0< YxcUx4;ʬ$< AxeVƄ2+fcB
+ޘw ΄2+fcB^Հg1
+٘7]G3J٘~WfulL+l6&$ߍyL+rl6&ޕYw6Jʬ; wcޕw4;ʬ; weVߝƄ2+fcB]dL+l6&DݕYMw6ʬ; 9wc5w4;Zʬ; !weVǝƄ2+fcBݘw΄~2+fcB]ng1ܮ
+٘m7]mG3ٮJ٘lWfl\kWkgg"ڍwQL+2l6f+O<(+5&ٍyfWfgg"ٕW]vʬ(;F";z=vUƄ2fc;{?!nŽbgB]`g1!꯳٘P_WflLH.ٙ]WfelL+l6&וYu6rƼkhv&֕Yu6Bʬ: ueVdƄĺ0_<WWflL+l6&ՕYau6Ƽhv&4ՕYIu6ʬ: 5ueVLƄ1: ueVFƄ2fcBA}?m|O_&7g(/?}_K?9}|Jp~ooo+^}(L:MA#?^_?/Q;Qm;'T?it;??N7rޑ?MژwOU_N۩wZ?i;:yx+ݗävm
+\ w* PT@僚7(:
+;5QTnQd3aSkNINM
+;5Rt5Kjc0E=M5Nryν)@mLXԚ
+|Wd~E'׀ȝ
+\ w*XtrXܩ0dQE*lYtrYܩ0gɵgr¢E'פȝ
+|Zd®E'װȝ
+\ w*[tr[ܩ0pQE*l\tr\ܩ0sɵsrE'ȝ
+cj"'.:/@Tڼ
+3\; w*,itrMiܩ0QF*itr jܩ0ɵrªF'׬ȝ
+|/kd¶F'׸ȝ
+\ w*,ltrMlܩ0ȯV6|RagkhNN ;6:6@Tܨ{q#67:F7@T
+J%ߥJ**\
+J'WrBUܩP4R%'JNRNRTST*U@T(U*.U|PsҹWjcBҩU۩Ptr* wj.U*Jv&*Z
+
+J'WrRsRƄRRR%JNRNͥJ^
+ JVnBRw僚KνJPJNRNRTSTTARTST*U@T(U:J;JJK,T(U:J;JNRNRTSTTARTST*U@T(U:J;JJK,T(U:J;JNRNRTSTTARTST*U@T(U:J;JF~UDBUܩPtr* w**\
+J%ߥJ**\
+J'WrBUܩPT]dBUܩPtr* w**\
+J%ߥJ**\
+J'WrRsRƄRRR%JNRNJ* LRTSTTAͥJ^
+ JVnBUܩT*UڙPtj*v**\
+ȝKνJPJJKT(U:J;JNRNRTSTTARTST*U@T(U:J;JJK,T(U:J;JNRNRTSTiWJO**\
+J'WrBUܩPT]dBUܩPtr* w**\
+J%ߥJ**\
+J'WrBT RBT 7=UJ:JK_|?ҕtG^_y_yx}<q<g"Wf+;^yr+;^yr+;^yr++~a*땇 w*땇 w*땇 w*Wf+;^yr+;^yr+;^yr++~a*땇 w*땇 w*땇 w*Wf+;^yr+;^yr+;^yr++~a*땇 w*+$S!$
+AT+ɳ|P!$
+AT;Br;BNN<BNN+$S!$
+ATUH
+!y'WHrBHܩwr w*|Y>9$+$1!$
+T;Br;5[!yV;BNN+$SsH޹WHjcBH^wH݃
+!y'WHr搼sƄS+$S!$;$A!y^!9 !yVHnBHܩWgBHܩwr w*\!9ȝ
+!y%!y*\!9ȝ
+!y'WHrBHܩWgBHܩwr w*\!9ȝ
+!y%!y*\!9ȝ
+!y'WHrBHܩWgBHܩwr w*\!9ȝ
+!y#
+ɣ|R!$
+AT;Br;BNN<BNN+$S!$
+AT+ɳ|P!$
+AT;Br;BNN<BNN+$SsH޹WHjcBH^wH݃
+!y'WHrƐS_Br&BHމکWg搼sƄS+$S!$
+AWnYL;Brp;BNN!y^!9 !y!yv*\!9ȝ
+!y'WHrBHܩWgBHܩwr w*\!9ȝ
+!y%!y*\!9ȝ
+!y'WHrBHܩ7<'BNN+$S!$
+AT+ɳ|P!$
+AT;Br;BNN<BNN+$S!$sÅVH~}}+[Oc:=#?\?u>Ն}ǟS?})=>ދw qEƅR rB8ܩ8W8gB8ܩ8wr5 w*4Ν\3ȝ
+s%ߍs*4Ν\3ȝ
+s'W rB8ܩ8W8gB8ܩ8wr5 w*4Ν\3ȝ
+s%ߍs*4Ν\3ȝ
+s'W rB8ܩ8W8gB8ܩ8wr5 w*4Ν\3ȝ
+s%ߍs*4Ν\3ȝ
+s'W rB8ܩ8W8gB8ܩ8wr5 w*4Ν\3ȝ
+s#j|RqjATh;g;NNƹ957Ν{5Π6&4ΝZ3
+s'W rƹrqjgBܩ8۩8wr5 wjn;jAmLh+n{PqjA8w8ژ8wj5v*4Ε|7Y>qܫq1qjTh;g;J,Th;g;NNƹqSqqAƹqSqjATh;g;J,Th;g;NNƹqSqqAƹqSqjATh;g;J,Th;g;NNƹqSqnWsO*4Ν\3ȝ
+s'W rB8ܩ8W8gB8ܩ8wr5 w*4Ν\3ȝ
+s%ߍs*4Ν\3ȝ
+s'W rB8ܩ8W8gB8ܩ8wr5 wjn;jAmLh+n{PqjA8wK Dh;gP;J,8w8ژ8wj5v*4Ν\3ȝʭ9 sV nB8ܩqܫq1qԻqAƹqSqjATh;g;J,Th;g;NNƹqSqqAƹqSqjATh;g;F~8GB8ܩ8wr5 w*4Ν\3ȝ
+s%ߍs*4Ν\3ȝ
+s'W rB8ܩ8W8gB8ܩ8wr5 w*4!mgj/p8?/qz4^|_闯?~ |}$o/u{tüݭp?jc6oNN{POS+FS_/aT<?rND<?S'iO9Qm;'ڪ/;Gs|4ʝz[~<4orμc>^ڏ{FS_οI~o:rޑ^G,? SׇӤ;rG)_{ $wj)kN{ |큐ܩyk䃚@J!SH)_{ $wj)kN{ \{ ԼRɝ@J!SH)_{ $wj=R@HԼRɝ@J!SH'5Br=R@HԼRɝ@:@@>y=;5Br=R@HԼɵA{ |큐ܩy=;5Br=N=j)kN{ |큐ܩy=;5T哚@J!SH)_{ $wj)kN{ \{ ԸR/{ 6f)kN{ |큐ܩqs=P;3Bn=R@HԸR/{ 6f=R@HԸR/{ 6f)kN{ \{ ԸR/{ 6f)kN{ |큐ܩyk䃚@J!SH)_{ $wj)kN{ \{ ԼRɝ@J!SH)_{ $wj=R@HԼRɝ@J!SH'5Br=R@HԼRɝ@:@@>y=;5Br=R@HԼRHOj)kN{ |큐ܩy=;5tr큀|PH)_{ $wj)kN{ |큐ܩyk䃚@J!SH)_{ $wj)kN{ \{ ԼRɝ@J!SH쁐ژySk܃@J!SHBļR@:@@>qt_@Hm̼R@J!SH^{ vf)kN{ |큐ܩqt_@Hm̼ҩA{ |큐ܩy=;5Br=N=j)kN{ |큐ܩy=;5tr큀|PH)_{ $wj)kN{ |큐ܩy=,ԼRɝ@J!SH)_{ $wj=R@HԼRɝ@J!SH'5Br=R@HԼ+]rӅ^vx:}u9}׷z~3{AM|wy|<c?$@x^_:7.Z;NN+S!;A+S!kATk;Z;J,Tk;Z;NN+S!;A+S!kATk;Z;J,Tk;Z;NN+S!;A+S!kATk;Z;J,Tk;Z;NN+S!mWymO*䵝\y-ȝ
+ym'W^ rB^ɕׂܩVf漶sƄS+S!kAԜVnYLk;Zp;NNym^y- ymymv*䵝\y-ȝνZPNN65絝{嵠6&䵝Zy-
+ym'W^ rB^[w^
+ym'W^ rB^ɕׂܩvr w*䵕|Y>vr w*䵝\y-ȝ
+ym'W^ rB^[w^
+ym'W^ rB^ɕׂܩvr w*䵕|Y>vr w*䵝\y-ȝ
+ym'W^ rB^[w^
+ym'W^ rB^ɕׂܩvr w*䵍*I+S!kATk;Z;J,Tk;Z;NN+S!;A+S!kATk;Z;J,Tk;Z;NNym^y- ymymv*䵝\y-ȝN}kAym'V^ jB^[w^僚νZPNN+Ss^[f3!kTk;Z;5絝{嵠6&䵕z=vr w*䵝\y-ȝ
+ym'W^ rB^[w^
+ym'W^ rB^ɕׂܩvr w*䵕|Y>vr w*䵝\y-ȝ
+ym'W^ rB^ȯ(Tk;Z;NN+S!;A+S!kATk;Z;J,Tk;Z;NN6Ԡm^ ZyR^nڏxrJq%Oc>zӶͲ۩e~_p~]}ۂm˂٘ʬ7g1=ywL+"l6&$̕Ys6ʬ|9 rcr4;ʬp9 reVƄf2+YfcBܘw΄Z2+VfcB\U*g1Sʔ٘)7(G3P
+٘'WflLh+l6&ɍywL+l6&$ɕYEr6zʬ9 1rc-r4;Jʬ9 reVƄ2+AfcBܘw΄2+>fcBz\Ug1;ʎ٘8ʬ8 qeVmƄָ2+5fcBhܘwg̕q%^q~&Bb\yUg1/ʋ٘9.nj׈PW^YlLȊ+l6fn+J3ƻ{(v&ĕY1q63ĕxqUFƄ18 JL|bcB;\g1!n̻fgB5\ g1!*٘ WflLnٙP
+WflLȄ+*l6&4•Yp6Ƽhv&Yqp6ʬ28 ]peVƄ(1&8 EpeVƄ2fcB \g1!n̻fgB\g1!*٘VflL Ũ oeVƄ2fcB[f1!m̻fgB[f1!*}٘VfelL|n|ٙPVflL{+l6&Yio6Ƽhv&TYQo6ʬ7{JrL7 %oeVțƌoT oeU›Ƅ1~7JLtbcB[f1sۈFح
+vؘVfպlVfg"wwQLt+"l6&$Yn6ʬ<7 qncmn4;ʬ07 YneVƄ&2+fcBۘw΄2+fcB[Uf1íp٘7
+m٘VfUlLhlgmbo
+۫ۤu~_|w'ߏ=t)l??|?Ff{<N+؍=N/Ǘ{txyFs=,r*+!FSl֩Egx8>uԃzGNNSJAT+|P
+ATH;k;*NNк:ZNNܺSJAT+|P
+ATH;k;NN
+ATH;l;N,N02;N8N<SJAT+|P
+ATH;Zm;jN\N`_Q>lwrE w*d۝\6ȝ
+v'W rB]w僚νnPNN+Ss]Urg3ԊTȹ;zn;5ݝ{%ݠ6&DݕzW=uwr wjN;jAmL;np;J ,xwyژywjuv*ޝ\7ȝ
+w%ߵw*ޝ\7ȝ
+w'W rBɕ}ܩ~W]~gBܩwr w*\ 8ȝ
+x%x*t\!8ȝ
+)x'W rB ɕܩW]gBܩwru w*\i8ȝ
+qx%ux*\8ȝ
+x'W#rB%ɕܩ7R<'ZNXN\SJAT+Ƴ|P
+ATH;q;N|N<NN#S$JAT+ɳ|P'
+ATH;r;5W{e6&z=Жwr wj;/g"X9
+y%ߕyj;
+AmLH;Zsp;jNNyVq΄S+:S!;A\wژW]gBܩwr5 w*T\:ȝ
+!z%%z*\1:ȝ
+9z'WrBɕܩW]gBܩwr w*\y:ȝ
+z#*ԣ|RQAT;:u;JNTNXZ=zN`NdYSZAT+.׳|P]AT;u;
+\ ;\h5z}~8b_T?]*o_/?]*Hڿᇟ;?8s>=wiKƅRi rBiUڂܩPvr w*|Y>Pvr w*\-ȝ
+m'Wi rBi[wi
+m'Wi rBiUڂܩPvr w*|Y>Pvr w*\-ȝ
+m'Wi rBi[wi
+m'Wi rBiUڂܩPvr w*|Y>Pvr w*\-ȝ
+m'Wi rBi[wi
+m'Wi rBiUڂܩPvr w*IҶS*mAT(m;J[;JJK,\vUڂژPvjv*\-ȝKʭ6 mVi nBiUڂܩܫ1ԻAҶSsi۹Wi jcBi۩Uڂ۩PV]fҶsƄҶSS*mAT(m+.m|P*mAT(m;J[;JNNҶ6JNNҶS*mAT(m+.m|P*mAT(m;J[;JNNҶ6JNNҶS*mAT(m+.m|P*mAT(m;J[;JNNҶ_Q>Pvr w*\-ȝ
+m'Wi rBi[wi
+m'Wi rBiUڂܩPvr w*|Y>Pvr w*\-ȝ
+m'Wi rBi[wi
+m'Wi rBiUڂܩܫ1ԻAҶSci۩/-?*mAT(m+.m|Psi۹Wi jcBi۩Uڂ۩Pvr wj.m+J۬v&Z-
+m'Wi rҶsƄҶR6JNNҶS*mAT(m+.m|P*mAT(m;J[;JNNҶ6JNNҶS*mAT(mUi
+m'Wi rBiUڂܩPvr w*|Y>Pvr w*\-ȝ
+m'Wi rBi[wi
+m'Wi rBiUڂܩP+mBP*mÍ~ir+JKiO_ۏ?C?SWGF<}/ ͟O_A>wOcuQm;kFS~(wꝗr??Oܗävzey֋Txz'׋A"N}y:?Ex=^"t;^"t;^"t;^^<C*3tr3ܩ05
+ |3d<C'<ȝ
+ \ w*3tr3ܩ0P<C*3tr3ܩ05
+ |3d<C'<ȝ
+ \ w*3tr3ܩ0P<C*3tr3ܩ05
+ |3d<C'<ȝ
+ \ w*3tr3ܩ0P<C*3tr3ܩ05
+ j!':@Tgg
+ \ w*3tr3ܩ0P<C*3tr3ܩ05
+ |3d<C'<ȝ
+ \ w*3tr3ܩ0P<C*3tr3ܩ05
+ |3d<C'<ȝ
+ \ w*3tr3ܩ0P<C*3tr3ܩ05
+3|DdLD'Lȝ
+3\3 w*DtrDܩ0QLD*DtrDܩ05rLD'Lȝ
+3|DdLD'Lȝ
+3\3 w*DtrDܩ0QLD*DtrDܩ05rLD'Lȝ
+3|DdLD'Lȝ
+3\3 w*DtrDܩ0QLD*DtrDܩ05rLD'Lȝ
+3|DdLD'Lȝ
+3\3 w*DtrDܩ0ȯf"|Ra&k&NN;f":f"@T{&"5Dt5jcLDL
+3\3 wjܚjgLDL
+3\3 wjk&ƄJg"{Pa&k&N3{Dژ0ѩ5nLD%3Y>y&sPf":f"T䚉
+3|DdLD'Lȝ
+3\3 w*DtrDܩ0QLD*DtrDܩ05rLD'Lȝ
+3|DdLD'Lȝ
+3\3 wjk&ƄJg"{Pa&k&N32DĚ
+3\3 w*DtrDܩ0QLD*DtrDܩ05rLD'Lȝ
+3|DdLD'Lȝ
+3\3 w*DUv&.f"/z&"OǷ5<q<?vg`1 ET0`|>{v<E:stx;>޼ 4ʝzGr8=|B'FS<Qm;ScirSi;2Uҹ 'Z'v*̟4(<ҹ 'Z'v*̟tr͟ܩyrk$ 'Z'v*̟tr͟ܩqS_O@'xϟdI' ȝ
+'\' w*̟tr͟ܩ0RI*̟tr͟ܩ05rI' ȝ
+'|ϟdI' ȝ
+'\' w*̟tr͟ܩ0RI*̟tr͟ܩ05rI' ȝ
+'|ϟdI' ȝ
+'\' w*̟tr͟ܩ0RI*̟tr͟ܩ05rI' ȝ
+'|ϟdI' ȝ
+'\' w*̟tr͟ܩ0RI*̟tr͟ܩ05rI' ȝ
+'j$'O:O@T??SakNJO|PI^'6&̟tj͟۩05rʭv&̟tj͟۩05rνO@mL?{$O:O@<ҹ 'Z'v*̟T=僚O:?1aSkNN;O*?AN;O:O@T??Sa,T??SakNN;O*?AN;O:O@T??Sa,T??SakNN;O*?AN;O:O@T??Sa_͟DI' ȝ
+'\' w*̟tr͟ܩ0RI*̟tr͟ܩ05rI' ȝ
+'|ϟdI' ȝ
+'\' w*̟tr͟ܩ0RI*̟tr͟ܩ05rνO@mL?{$O:O@8ҩ/' LNP;O*?A'{͟ژ0ҩ5nI' ȝO*Oڙ0ҩ5nI' ȝO:?1aRT??SakNN;O*?AN;O:O@T??Sa,T??SakNN;OIO*̟tr͟ܩ05rI' ȝ
+'|ϟdI' ȝ
+'\' w*̟tr͟ܩ0RI*̟tr͟ܩ05rIOBkBi$wϟ.':?˭WOt:stB7(wDK' 55BrR_H8R//6f~~R_H8R//6f~)kN/|d}~!1K^/vj~)kN/{ ڙyTr;55BrR}ğykԃ_J~!SK)_/$wj~)kN/\/ <R ɝ_J~!SK)_/$wj~~R_H<R ɝ_J~!SK' 55BrR_H<R ɝ_:_@>y;55BrR_H<5A/| ܩy;55BrNj~)kN/| ܩy;5tr |PK)_/$wj~)kN/| ܩy,<R ɝ_J~!SK)_/$wj~~}~!1K^/vj~)kN/{ ڙyTr;55Br}~!1K 55Br}~!1K^/vj~~}~!1K^/vj~)kN/\/ <R ɝ_J~!SK)_/$wj~~R_H<R ɝ_J~!SK' 55BrR_H<R ɝ_:_@>y;55BrR_H<5A/| ܩy;55BrJ_|RK)_/$wj~)kN/| ܩyk䃚_J~!SK)_/$wj~)kN/\/ <R ɝ_J~!SK)_/$wj~~R_H<R ɝ_Je/Z/<R ɝ_J&R_H<5A/2BjcR_<R ɝ_:~3K^/vj~)kN/2BjcNpj~)kN/| ܩy;5tr |PK)_/$wj~)kN/| ܩyk䃚_J~!SK)_/$wj~)kN/|dR_H<R ɝ_J~!SK' 55BrR_H<R ɝ_:_@>y;55Brᗼ\R Ѕ.>]KOO{& <Otorj}9ٗ_~ǟ-|ԇ/nF'yU꾡3Ia6"Ƽhv&Ya6ʬ0 maeVZƄ10 UaeVTƄ2(fcBOXf1!&l̻%fgBIXf1!#̪٘Vf%lLٙPVfŃlLH+l6&tY`6Ƽhv&Y`6rʬZ0 `eV*ƄP0_u<P VfElLH+
+l6&Yy`6Ƽhvf.+
+3ʫ*0 M`eVA`#VF*bcB
+XUf1sXW6
+
+٘Uf|lLh*Rl6&|yw|L*"l6&$|Y_6ʬ|/ ^c^4;ʽʬp/ ^eVƄf2+fcBWzdL*bl6&zY^6:ʬL/ ^cލ^4;
+ʬ@/ y^eVƄ62+fcBטw΄*2+fcBWUe1ǫ٘5E3ī
+٘UfUxlUeg"xwwQL*l6fL*良O<*5&Dwy7w\Ueg"vW]Zʬ.CF.z]UdƄĮ2fc澮.{?!kbgBYWe1!̪٘Uf%ulLٙPUftlLH*Jl6&ttY]6"Ƽhv&tY]6ʬz. \eV:Ƅp0_us<PUfEslLH*l6&rY\6bƼ[hv&rY\62ʬJ. \eV"Ƅ@1>. u\eVƄ42fcBw?j|U]&Eq3gh^g֩*f_߾|Oz#{}9<}Oriv;}uƅR
+C'W
+AD'WrBȯ(T":;ʈN4N8
+ E%Ej(:(@mL():R
+p;bNN"NN+
++
+9F'WrBɕdܩetrU w*t|Y>ftr w*\yȝ
+F'WrBQw
+F'WrBɕjܩktr w*|Y>ltr5 w*T\ȝÍν PڍJT7: ;5p3"NN#5{6&Z9
+AG'Wr榣r+jgBѩu۩Pvtr wj;:;@mL;*<{P!j<@T<:2;BNN֣#rNN⣓+
+1H'W rBRw
+IH'WrBɕܩtr! w*!|!Y>tr! w*"\ȝ
+HJ.2 N$7ENO߾/O:۷ğͫc:u EV$˿
+Cf\Cf w* U=d
+Cf\Cf w* ur ܩ0d5drY#2I!N!3;:@T22SaȬ!,<dֹא CfZCfv* ur ܩyȬrk, CfZCfv* ur ܩyȬs!3P*2A!N!3;5u5djcY֐
+Cf|e!ν@mL22SaȬk N!J̲|PaȬk N!N!3;:@T2{,:@T22SaȬk N!J̲|PaȬk N!N!3;:@T2{,:@T22SaȬk N!J̲|PaȬk N!N!3;:@T2kWCfQ>0d5drY'אȝ
+Cf\Cf w* U=d
+Cf\Cf w* ur ܩ0d5drY%CfY>0d5drY'אȝ
+Cf\Cf w* U=d
+Cf\Cf w* ur ܩyȬs!3P*2A!N!3;5uː?aȬk N!J̲|PY^Cf6& uj ۩0d5dr!ʭ!v& uj ۩0d5dr!ν@mL2{,:@T22SaȬk N!J̲|PaȬk N!N!3;:@T2{,:@T22SaȬk N!F~5d
+Cf\Cf w* ur ܩ0d5drY%CfY>0d5drY'אȝ
+Cf\Cf w* U=d
+Cf\Cf w* ur ܩ0d!32!p=dvz9
+Cfet2ǘYC(W?wglOK iiJR w*LKurMKܩ0-UT*LKurMKܩ0-5-r´T'״ȝ
+R|OKe´T'״ȝ
+R\R w*LKurMKܩ0-UT*LKurMKܩ0-5-riν@mL{Z*:@<-չ״ RZRv*LK5i(<-չ״ RZRv*LKurMKܩyZrkZ* RZRv*LKurMKܩqZS_@RxOKe´T'״ȝ
+R\R w*LKurMKܩ0-UT*LKurMKܩ0-5-r´T'״ȝ
+R|OKe´T'״ȝ
+R\R w*LKurMKܩ0-UT*LKurMKܩ0-5-r´T'״ȝ
+R|OKe´T'״ȝ
+R\R w*LKurMKܩ0-UT*LKurMKܩ0-5-r´T'״ȝ
+R|OKe´T'״ȝ
+R\R w*LKurMKܩ0-UT*LKurMKܩ0-5-r´T'״ȝ
+RjZ*':@T䚖SaZkZ
+NiJ|PT^R6&LKujMK۩0-5-riʭiv&LKujMK۩0-5-riν@mL{Z*:@<-չ״ RZRv*LKU=-僚:1aZSkZ
+NiNi);*AiNi);:@T䚖SaZi,T䚖SaZkZ
+NiNi);*AiNi);:@T䚖SaZi,T䚖SaZkZ
+NiNi);*AiNi);:@T䚖SaZ_MKE´T'״ȝ
+R\R w*LKurMKܩ0-UT*LKurMKܩ0-5-r´T'״ȝ
+R|OKe´T'״ȝ
+R\R w*LKurMKܩ0-UT*LKurMKܩ0-5-riν@mL{Z*:@8-թ/R LiNi)P;*AR{MKژ0-թ5-n´T'״ȝ*ڙ0-թ5-n´T'״ȝ:1aZRiT䚖SaZkZ
+NiNi);*AiNi);:@T䚖SaZi,T䚖SaZkZ
+NiNi);մTO*LKurMKܩ0-5-r´T'״ȝ
+R|OKe´T'״ȝ
+R\R w*LKurMKܩ0-UT*LKurMKܩ0-5-r´TGꦥBkZBiZ*OǷ۟^vo?xɅ۳뜎i\x~ן~ Ek|/,Me|]-c\_7,cܸPZ
+\ w*,ctr-cܩɵr2F%Y>ɵr2F'2ȝ
+\ w*,cT򽌑
+\ w*,ctr-cܩɵr2F%Y>ɵr2F'2ȝ
+\ w*,cT򽌑
+\ w*,ctr-cܩɵr2F%Y>ɵr2F'2ȝ
+\ w*,cT򽌑
+\ w*,ctr-cܩɵr2F%Y>ɵr2F'2ȝ
+\ w*,c4e(TXZ
+\ w*,ctr-cܩɵr2F%Y>ɵr2F'2ȝ
+\ w*,cT򽌑
+\ w*,ctr-cܩɵr2F%Y>ɵr2F'2ȝ1:Z
+\ wj^ƨZjg2F2
+\ wj^kƄeJ1{PakNeNe ;1:1@TXƨ{#1:1@TXZ
+\ w*,cT򽌑
+\ w*,ctr-cܩɵr2F%Y>ɵr2F'2ȝ
+qΡ[ƀ e =_7OqF똖1/_~ǗW4Oߥ4x4_8$7.f;NN9NN+iS!iJATH+N|P!iJATH;f;NN9NN+iS!iJATH+N|P!iJATH;f;NN9NN+iS!iJATH+N|P!iJATH;f;NN9NN+iS!iJATHU
+Is'W rBɕ4ܩ4wr% w*$͕|'Y>9i+i1!iJTH;f;5'͕[IsV;NN+iSsܹW jcB\wҜ݃
+Is'W r椹sƄS+iS!i;iAIs^I3 IsV nBɕ4ܩ4W4gBɕ4ܩ4wr% w*$͝\I3ȝ
+Is%Is*$͝\I3ȝ
+Is'W rBɕ4ܩ4W4gBɕ4ܩ4wr% w*$͝\I3ȝ
+Is%Is*$͝\I3ȝ
+Is'W rBɕ4ܩ4W4gBɕ4ܩ4wr% w*$͝\I3ȝ
+Is#J|R!iJATH;f;NN9NN+iS!iJATH+N|P!iJATH;f;NN9NN+iSsܹW jcB\wҜ݃
+Is'W rƤS_f&B܉4ک4W4g椹sƄS+iS!iJAԜ4Wn%YLH;fp;NNIs^I3 IsIsv*$͝\I3ȝ
+Is'W rBɕ4ܩ4W4gBɕ4ܩ4wr% w*$͝\I3ȝ
+Is%Is*$͝\I3ȝ
+Is'W rBɕ4ܩ479'NN+iS!iJATH+N|P!iJATH;f;NN9NN+iS!in4ÅV|}4/딂͠_ׯ΄x
+7:~'̙muc -3KμBf03g̕Ys6;37̝y%`6f;l\/w/٘9]*ٙ[+[1sܙW fcb3`̹reV̭rg^2Cμ:e03WʝyE`6fN+
+lvf;ʓl'w&٘L+L1s\U%g3sܙW fc 35rg^12Sʬ9;μ2d03Gȝy5`6f.;
+l̜Wfw٘9<̫;1suܙWt fc183Ɲy`6f;jl\w٘93̪ٙ1ė90/1s]ܙW\ fcƴ8{n0 p+
+s7 r(&#aWd!£\̃U`.&`W dq\]9(xkI*d\]9xk%+rm2V壀ܛl]9xk+rmuQM.iݯ+rm2uQM;ʵWrouPQM;ʵו|G6\M;ʵɸוc{G6Yr`(&^U>˽ɦוCzG6r<(&j^cѽ.2U#y9ܛx]9wk3zc[wU].&]ѽ.2uݸMV;ʵdW#v[u]7\wk+rmu;"3]=JýFוCtG6r|(&\WdGr7rX(&\Wd\qUh\.&[\Wdq\p]9wk ) \,dᷣ\o]9vk+ގrm2Unܛln]9vk+ێrmu`QM|-{+َrm2uQMB%x㗼y}qS?_/ۗYǞ_Y~yևrendstream
+endobj
+1662 0 obj <<
+/Type /Page
+/Contents 1663 0 R
+/Resources 1661 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 1215 0 R
+/Annots [ 1665 0 R 1666 0 R 1667 0 R 1668 0 R 1669 0 R 1670 0 R 1671 0 R 1672 0 R 1673 0 R 1674 0 R 1675 0 R 1676 0 R 1677 0 R 1678 0 R 1679 0 R 1680 0 R 1681 0 R 1682 0 R 1683 0 R 1684 0 R 1685 0 R 1686 0 R 1687 0 R 1688 0 R 1689 0 R 1690 0 R 1691 0 R 1692 0 R 1693 0 R 1694 0 R 1695 0 R 1696 0 R 1697 0 R 1698 0 R 1699 0 R 1700 0 R 1701 0 R 1702 0 R 1703 0 R 1704 0 R 1705 0 R 1706 0 R 1707 0 R 1708 0 R 1709 0 R 1710 0 R 1711 0 R 1712 0 R 1713 0 R 1714 0 R 1715 0 R 1716 0 R 1717 0 R 1718 0 R 1719 0 R 1720 0 R 1721 0 R 1722 0 R 1723 0 R 1724 0 R 1725 0 R 1726 0 R 1727 0 R 1728 0 R 1729 0 R 1730 0 R 1731 0 R 1732 0 R 1733 0 R 1734 0 R 1735 0 R 1736 0 R 1737 0 R 1738 0 R 1739 0 R 1740 0 R 1741 0 R 1742 0 R 1743 0 R 1744 0 R 1745 0 R 1746 0 R 1747 0 R 1748 0 R 1749 0 R 1750 0 R 1751 0 R 1752 0 R 1753 0 R 1754 0 R 1755 0 R 1756 0 R 1757 0 R 1758 0 R 1759 0 R 1760 0 R ]
+>> endobj
+1665 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [70.7348 705.325 144.4176 716.2042]
+/Subtype /Link
+/A << /S /GoTo /D (using) >>
+>> endobj
+1666 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 705.325 538.9788 716.2042]
+/Subtype /Link
+/A << /S /GoTo /D (using) >>
+>> endobj
+1667 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 691.9054 163.3271 700.7521]
+/Subtype /Link
+/A << /S /GoTo /D (using-intro) >>
+>> endobj
+1668 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 691.9054 538.9788 700.7521]
+/Subtype /Link
+/A << /S /GoTo /D (using-intro) >>
+>> endobj
+1669 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 676.8967 219.4857 687.8006]
+/Subtype /Link
+/A << /S /GoTo /D (myaccount) >>
+>> endobj
+1670 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 676.8967 538.9788 687.8006]
+/Subtype /Link
+/A << /S /GoTo /D (myaccount) >>
+>> endobj
+1671 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 663.9453 187.9547 674.8492]
+/Subtype /Link
+/A << /S /GoTo /D (bug_page) >>
+>> endobj
+1672 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 663.9453 538.9788 674.8492]
+/Subtype /Link
+/A << /S /GoTo /D (bug_page) >>
+>> endobj
+1673 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 650.9938 193.2048 661.8978]
+/Subtype /Link
+/A << /S /GoTo /D (lifecycle) >>
+>> endobj
+1674 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 650.9938 538.9788 661.8978]
+/Subtype /Link
+/A << /S /GoTo /D (lifecycle) >>
+>> endobj
+1675 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 638.0424 190.9931 648.9463]
+/Subtype /Link
+/A << /S /GoTo /D (query) >>
+>> endobj
+1676 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 638.0424 538.9788 648.9463]
+/Subtype /Link
+/A << /S /GoTo /D (query) >>
+>> endobj
+1677 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 627.1483 207.1625 635.9949]
+/Subtype /Link
+/A << /S /GoTo /D (boolean) >>
+>> endobj
+1678 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 627.1483 538.9788 635.9949]
+/Subtype /Link
+/A << /S /GoTo /D (boolean) >>
+>> endobj
+1679 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [142.4658 614.1968 261.2595 623.0435]
+/Subtype /Link
+/A << /S /GoTo /D (pronouns) >>
+>> endobj
+1680 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 614.1968 538.9788 623.0435]
+/Subtype /Link
+/A << /S /GoTo /D (pronouns) >>
+>> endobj
+1681 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [142.4658 599.1881 213.1605 610.0921]
+/Subtype /Link
+/A << /S /GoTo /D (negation) >>
+>> endobj
+1682 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 599.1881 538.9788 610.0921]
+/Subtype /Link
+/A << /S /GoTo /D (negation) >>
+>> endobj
+1683 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [142.4658 586.2367 239.6603 597.1406]
+/Subtype /Link
+/A << /S /GoTo /D (multiplecharts) >>
+>> endobj
+1684 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 586.2367 538.9788 597.1406]
+/Subtype /Link
+/A << /S /GoTo /D (multiplecharts) >>
+>> endobj
+1685 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 573.6589 195.2473 584.1892]
+/Subtype /Link
+/A << /S /GoTo /D (quicksearch) >>
+>> endobj
+1686 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 573.6589 538.9788 584.1892]
+/Subtype /Link
+/A << /S /GoTo /D (quicksearch) >>
+>> endobj
+1687 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 560.3338 257.8315 571.2378]
+/Subtype /Link
+/A << /S /GoTo /D (casesensitivity) >>
+>> endobj
+1688 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 560.3338 538.9788 571.2378]
+/Subtype /Link
+/A << /S /GoTo /D (casesensitivity) >>
+>> endobj
+1689 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 547.3824 183.9298 558.2863]
+/Subtype /Link
+/A << /S /GoTo /D (list) >>
+>> endobj
+1690 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 547.3824 538.9788 558.2863]
+/Subtype /Link
+/A << /S /GoTo /D (list) >>
+>> endobj
+1691 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 534.431 288.1781 545.3349]
+/Subtype /Link
+/A << /S /GoTo /D (individual-buglists) >>
+>> endobj
+1692 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 534.431 538.9788 545.3349]
+/Subtype /Link
+/A << /S /GoTo /D (individual-buglists) >>
+>> endobj
+1693 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 521.4795 160.8562 532.3835]
+/Subtype /Link
+/A << /S /GoTo /D (bugreports) >>
+>> endobj
+1694 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 521.4795 538.9788 532.3835]
+/Subtype /Link
+/A << /S /GoTo /D (bugreports) >>
+>> endobj
+1695 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 508.5281 232.368 519.432]
+/Subtype /Link
+/A << /S /GoTo /D (fillingbugs) >>
+>> endobj
+1696 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 508.5281 538.9788 519.432]
+/Subtype /Link
+/A << /S /GoTo /D (fillingbugs) >>
+>> endobj
+1697 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 495.5767 235.9544 506.4806]
+/Subtype /Link
+/A << /S /GoTo /D (cloningbugs) >>
+>> endobj
+1698 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 495.5767 538.9788 506.4806]
+/Subtype /Link
+/A << /S /GoTo /D (cloningbugs) >>
+>> endobj
+1699 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 484.6825 164.4326 493.5292]
+/Subtype /Link
+/A << /S /GoTo /D (attachments) >>
+>> endobj
+1700 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 484.6825 538.9788 493.5292]
+/Subtype /Link
+/A << /S /GoTo /D (attachments) >>
+>> endobj
+1701 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 471.7311 198.4055 480.5777]
+/Subtype /Link
+/A << /S /GoTo /D (patchviewer) >>
+>> endobj
+1702 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 471.7311 538.9788 480.5777]
+/Subtype /Link
+/A << /S /GoTo /D (patchviewer) >>
+>> endobj
+1703 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [142.4658 456.7224 308.7609 467.6263]
+/Subtype /Link
+/A << /S /GoTo /D (patchviewer_view) >>
+>> endobj
+1704 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 456.7224 538.9788 467.6263]
+/Subtype /Link
+/A << /S /GoTo /D (patchviewer_view) >>
+>> endobj
+1705 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [142.4658 443.7709 353.433 454.6749]
+/Subtype /Link
+/A << /S /GoTo /D (patchviewer_diff) >>
+>> endobj
+1706 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 443.7709 538.9788 454.6749]
+/Subtype /Link
+/A << /S /GoTo /D (patchviewer_diff) >>
+>> endobj
+1707 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [142.4658 430.8195 306.3201 441.7234]
+/Subtype /Link
+/A << /S /GoTo /D (patchviewer_context) >>
+>> endobj
+1708 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 430.8195 538.9788 441.7234]
+/Subtype /Link
+/A << /S /GoTo /D (patchviewer_context) >>
+>> endobj
+1709 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [142.4658 417.8681 360.9847 428.772]
+/Subtype /Link
+/A << /S /GoTo /D (patchviewer_collapse) >>
+>> endobj
+1710 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 417.8681 538.9788 428.772]
+/Subtype /Link
+/A << /S /GoTo /D (patchviewer_collapse) >>
+>> endobj
+1711 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [142.4658 404.9166 300.1035 415.8206]
+/Subtype /Link
+/A << /S /GoTo /D (patchviewer_link) >>
+>> endobj
+1712 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 404.9166 538.9788 415.8206]
+/Subtype /Link
+/A << /S /GoTo /D (patchviewer_link) >>
+>> endobj
+1713 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [142.4658 391.9652 281.4438 402.8691]
+/Subtype /Link
+/A << /S /GoTo /D (patchviewer_bonsai_lxr) >>
+>> endobj
+1714 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 391.9652 538.9788 402.8691]
+/Subtype /Link
+/A << /S /GoTo /D (patchviewer_bonsai_lxr) >>
+>> endobj
+1715 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [142.4658 379.0138 269.2792 389.9177]
+/Subtype /Link
+/A << /S /GoTo /D (patchviewer_unified_diff) >>
+>> endobj
+1716 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 379.0138 538.9788 389.9177]
+/Subtype /Link
+/A << /S /GoTo /D (patchviewer_unified_diff) >>
+>> endobj
+1717 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 366.0623 172.3929 376.9663]
+/Subtype /Link
+/A << /S /GoTo /D (hintsandtips) >>
+>> endobj
+1718 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 366.0623 538.9788 376.9663]
+/Subtype /Link
+/A << /S /GoTo /D (hintsandtips) >>
+>> endobj
+1719 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 355.1682 213.5384 364.0148]
+/Subtype /Link
+/A << /S /GoTo /D (2590) >>
+>> endobj
+1720 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 355.1682 538.9788 364.0148]
+/Subtype /Link
+/A << /S /GoTo /D (2590) >>
+>> endobj
+1721 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 342.2168 188.6321 351.0634]
+/Subtype /Link
+/A << /S /GoTo /D (commenting) >>
+>> endobj
+1722 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 342.2168 538.9788 351.0634]
+/Subtype /Link
+/A << /S /GoTo /D (commenting) >>
+>> endobj
+1723 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 327.208 276.2629 338.112]
+/Subtype /Link
+/A << /S /GoTo /D (comment-wrapping) >>
+>> endobj
+1724 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 327.208 538.9788 338.112]
+/Subtype /Link
+/A << /S /GoTo /D (comment-wrapping) >>
+>> endobj
+1725 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 314.2566 215.4916 325.1605]
+/Subtype /Link
+/A << /S /GoTo /D (dependencytree) >>
+>> endobj
+1726 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 314.2566 538.9788 325.1605]
+/Subtype /Link
+/A << /S /GoTo /D (dependencytree) >>
+>> endobj
+1727 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 301.3052 222.9429 312.2091]
+/Subtype /Link
+/A << /S /GoTo /D (timetracking) >>
+>> endobj
+1728 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 301.3052 538.9788 312.2091]
+/Subtype /Link
+/A << /S /GoTo /D (timetracking) >>
+>> endobj
+1729 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 290.411 186.8186 299.2577]
+/Subtype /Link
+/A << /S /GoTo /D (userpreferences) >>
+>> endobj
+1730 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 290.411 538.9788 299.2577]
+/Subtype /Link
+/A << /S /GoTo /D (userpreferences) >>
+>> endobj
+1731 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 277.4596 230.9231 286.3062]
+/Subtype /Link
+/A << /S /GoTo /D (generalpreferences) >>
+>> endobj
+1732 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 277.4596 538.9788 286.3062]
+/Subtype /Link
+/A << /S /GoTo /D (generalpreferences) >>
+>> endobj
+1733 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 264.5082 223.1921 273.3548]
+/Subtype /Link
+/A << /S /GoTo /D (emailpreferences) >>
+>> endobj
+1734 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 264.5082 538.9788 273.3548]
+/Subtype /Link
+/A << /S /GoTo /D (emailpreferences) >>
+>> endobj
+1735 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 251.5567 212.3333 260.4034]
+/Subtype /Link
+/A << /S /GoTo /D (savedsearches) >>
+>> endobj
+1736 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 251.5567 538.9788 260.4034]
+/Subtype /Link
+/A << /S /GoTo /D (savedsearches) >>
+>> endobj
+1737 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 238.6053 231.5312 247.452]
+/Subtype /Link
+/A << /S /GoTo /D (accountpreferences) >>
+>> endobj
+1738 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 238.6053 538.9788 247.452]
+/Subtype /Link
+/A << /S /GoTo /D (accountpreferences) >>
+>> endobj
+1739 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 225.6539 198.5945 234.5005]
+/Subtype /Link
+/A << /S /GoTo /D (permissionsettings) >>
+>> endobj
+1740 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 225.6539 538.9788 234.5005]
+/Subtype /Link
+/A << /S /GoTo /D (permissionsettings) >>
+>> endobj
+1741 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 210.6452 195.4263 221.5491]
+/Subtype /Link
+/A << /S /GoTo /D (reporting) >>
+>> endobj
+1742 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 210.6452 538.9788 221.5491]
+/Subtype /Link
+/A << /S /GoTo /D (reporting) >>
+>> endobj
+1743 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 197.6937 181.4293 208.5977]
+/Subtype /Link
+/A << /S /GoTo /D (reports) >>
+>> endobj
+1744 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 197.6937 538.9788 208.5977]
+/Subtype /Link
+/A << /S /GoTo /D (reports) >>
+>> endobj
+1745 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 186.7996 176.4479 195.6462]
+/Subtype /Link
+/A << /S /GoTo /D (charts) >>
+>> endobj
+1746 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 186.7996 538.9788 195.6462]
+/Subtype /Link
+/A << /S /GoTo /D (charts) >>
+>> endobj
+1747 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [142.4658 171.7909 244.6318 182.6948]
+/Subtype /Link
+/A << /S /GoTo /D (2787) >>
+>> endobj
+1748 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 171.7909 538.9788 182.6948]
+/Subtype /Link
+/A << /S /GoTo /D (2787) >>
+>> endobj
+1749 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [142.4658 158.8394 277.5779 169.7434]
+/Subtype /Link
+/A << /S /GoTo /D (charts-new-series) >>
+>> endobj
+1750 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 158.8394 538.9788 169.7434]
+/Subtype /Link
+/A << /S /GoTo /D (charts-new-series) >>
+>> endobj
+1751 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 145.888 140.6423 156.7919]
+/Subtype /Link
+/A << /S /GoTo /D (flags) >>
+>> endobj
+1752 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 145.888 538.9788 156.7919]
+/Subtype /Link
+/A << /S /GoTo /D (flags) >>
+>> endobj
+1753 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 132.9366 153.9225 143.8405]
+/Subtype /Link
+/A << /S /GoTo /D (whining) >>
+>> endobj
+1754 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 132.9366 538.9788 143.8405]
+/Subtype /Link
+/A << /S /GoTo /D (whining) >>
+>> endobj
+1755 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 122.0424 191.5117 130.8891]
+/Subtype /Link
+/A << /S /GoTo /D (whining-overview) >>
+>> endobj
+1756 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 122.0424 538.9788 130.8891]
+/Subtype /Link
+/A << /S /GoTo /D (whining-overview) >>
+>> endobj
+1757 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 107.0337 224.3182 117.9376]
+/Subtype /Link
+/A << /S /GoTo /D (whining-schedule) >>
+>> endobj
+1758 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 107.0337 538.9788 117.9376]
+/Subtype /Link
+/A << /S /GoTo /D (whining-schedule) >>
+>> endobj
+1759 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 94.0823 223.2023 104.9862]
+/Subtype /Link
+/A << /S /GoTo /D (whining-query) >>
+>> endobj
+1760 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 94.0823 538.9788 104.9862]
+/Subtype /Link
+/A << /S /GoTo /D (whining-query) >>
+>> endobj
+1664 0 obj <<
+/D [1662 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+1661 0 obj <<
+/Font << /F32 1230 0 R /F27 1222 0 R /F33 1322 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+1811 0 obj <<
+/Length 54175
+/Filter /FlateDecode
+>>
+stream
+xڔ]\y{
+N{%=hG8Frtt >BvybE%2%d=kvisgwNմ<r86talwu7>5M8l~;A?9ny}wow7?oiۗ?>xW'9^N?*Oi:W^
+6lNӈ{}0m5ӄ+84➺69}ӈ{
+~Om_FS9^!ޞ&|H]OpM4➺ϟi=u?6~ۗitvS RWәK/O#+lwޞFSW/}/O#>}ӄ+!}ӈ{
+~_FSW9}yqO_OpJ4C
+>:s|tw)ŗ|t!ŗutmoO>׾moO#+qsnӷ=z}yqO|wGSW9)<sO)<C{}|?!}ӄ+餿j#m+L=oO+nO{<w—y]1<|٧4➺_65_F29w{!u?6}ӈ{
+>4H[<2h{}04] RWba-sE?>}h{
+~LC^FSt9riO>t>o{{qO]锾i=u?c4z?͟᜾i‡|t]/O#+.ޞFSWә۾<3mӷ=Mϟvi=u?n)ۄ4➺_6.~ۗe9wޞ&|H]ii=u?!}ӈ{
+>:%ŗutx{!u?)}ӈ{
+>:)}ӈ{
+>:s/O#wv9\/߬i|^._FSWpS|yqO]ϛyӈ{}|?6}ӄ+C/O#+vޞFSW}yqOOgO4C
+>:SOD4➺ϟpNi=u?o.c/O#vҷ=M6)}ӈ{
+~OmoO#+?/o#m.{ޞ&{H]fM4➺>?vXv _FSs橧 RWsI@{i\ui=u?oΗii=>~nvԖy9^FSW9=G_F2٧z!u?9}ӈ{
+>4SzM|pm_FSf^ӷ=M6KzM.moO#+ys:zqO_O4o{{!u?.}ӈ{
+>:XO#+۾<ŏ٥z1u?)-i=u?mZӈ{
+~m_FSv=M6szMٞ҂FSW9^}yqOO^i‡|tiAGO#+kb=[)NPDwvW/2 h\/
+?vK϶Nlk^eSzu}}z_4;o؏7)w^s5>Nrs^f|=~9}Awi=ul.!ŗew)ӄ+LmoO#+lmoO#+es4/#m]v{ޞ&{H]yi=u,x헇yd=_FSoGs83\O>G?H[<29|h4zf{ a=sEoNKq.=oO#+es":>6yi|\NS|\5ŗ|lm_FSWvPOgO4C
+>:}| 6?C3!gz~&5?Zτx!So3{j=?m~pOg2\3L= 6?C3!gz~&5?Zτx!So3{j=?m~pOg2\3L= 6?C3!gz~&5?Zτx!So3{j=?m~pOg2\3L= 6?C3!gz~&LL= 6?C3!gz~&5?Zτ2?Ce3!gz~& Zdv3n3d{j=?m~pO-gB{!2 Cj=?m~pO-gB{!2n3d{j=?᚟|H-gB{!2n3d{j=?m~pOg2\3L= 6?C3!gz~&5?Zτx!So3{j=?m~pOg2\3L= 6?C3!gz~&5?Zτx!So3{j=?m~pOg2\3L= 6?C3!gz~&LL= 6?C3!gz~&5?Zτx!So3{j=?m~pOg2\3L= 6?C3!gz~&5?Zτx!So3{j9? іYdgRo3{j5?/3NgBLk~! e~hˬgBϐL=6?g3!gz~& Zτ2?Ce3z~& Zτx!So3{j=?᚟|HgBL= 6?C3z~& Zτx!So3{j=?}~z~& Zτx!So3{j=?᚟|HgBL= 6?C3z~& Zτx!Sj6%<]3+EY>5tTez68?=|{K`[y~>_Y/x}))LhS0N7%>)LhS0N
+q
+=)"S
+q
+=)2\{*Sd)
+q
+=)"S
+q
+=)2\{*Sd)
+=)2\{j=Niτq8؞
+q
+=6Ne8EDqRa"5N)LhS0N}Ƈz"8Ж q
+=)2\{*SDx!)2\{*Sd)
+)j|Hq 8
+q
+=)2\{*SDx!)2\{*Sd)
+q
+=)"S
+q
+=)2\{*Sd)
+_'8EkhOq5>)LhS0N
+-)"S
+q
+=)2\{*Sd)
+;=Vw2\;{*dVw
+;=Vw"
+;=Vw2\;{*dVw
+;=VwfuTXp
+;Wwj|H՝ 
+;=Vw2\;{*Dx_ݩ!Vw2\;{*dVw
+;=Vw2\;{*dVw
+;=VwfuTXp
+;Wwj|H՝ 
+;=Vwlu^P[ݹ}AN~qڜN{eugy_?{zwO??)ܠ[z\^/_
+/(y9UlO}@^
+
+{*3\z=
+ 5>B>US@*PpT(Gx/
+ WpO}@B>US@@_C*3\z=
+=
+ WpO}}PpT(g
+{*3\z=
+ 5>B>US@*PpT(Gx/
+ WpO}@B>US@@_C*3\z=
+=
+ WpO}K|L}@B>US@*P^!.gv+mPhT(g
+{j]lk3@*ЃPpԺ@٭@eB>{T(g
+{j]V2@*ЃP^!.gv+mPhT(g
+{*#k|H}@B>US@*P^!
+=
+ WpO}@B>{ƇT(g
+{*3\z=
+=
+R@*PpT(g
+{*#k|H}@B>US@*PO}PpT(g
+{*3\z=
+ 5>B>US@*PpT(Gx/
+ WpO}@B>US@@_C*3\z=
+=Z3聶L(Gt/
+ WpO- ~ VhO}}u>[h˄}F@B>US}d@_Ӟ VlO}@nz-
+ =B>US@*PpT(Gx/
+ WpO}@B>US@@_C*3\z=
+=
+ WpO}K|L}@B>US@*P^!
+=
+ WpO}@B>{ƇT(g
+{*3\z=
+e;+ jTWal^Wi~< kszU~|{r%+=SZע_EE*ZԝTբ
+ W-
+pOZTB-*{-ƇTEejQ{*Ԣ2\(=jQZ
+E
+ W-
+pOZTB-*USUCj]V2ѪEPpբ
+h˄ZTDZTmPpբ
+ W-
+pOZTB-*USUC*Ԣ2\(=jQZ
+ W-
+pOZTZTPpբ
+ W-
+pOZTB-*US7SEPpբ
+ W-
+pOZTB-*{-ƇTEejQ{*Ԣ2\(=EevEmP^!jQZZ֢2zE:jQZО
+ﵨRZTfZЖ V-
+lOZTֵV-=jQZ؞
+ W-
+pOkQjQ@[&Ԣ"עj{HZTB-*USEP^!jQZ
+ W-
+pOZTB-*{-ƇTEejQ{*Ԣ2\(=jQZ
+E
+ W-
+pOZTB-*USUC*Ԣ2\(=jQZ
+ W-
+pOZTZTPpբ
+ ㋩
+lVH^V(eτB$P˖ =HVM-J
+B-[&4PC&"YZL&D lPLdj2ȽPʞYW"5ju""WuL(#Dlu!UD(]KB䪅PÖ HV-. DpDh$n/gB <eˬ{j:JA [&4J3AANAjPÖ EHVϠ-ZKP1d5 j2_ɪԲeB -e˄fA"bA){&
+"YZLD*lP(d j2MȽLPʞ UHV-zA-[&"YZLh$r/gB} e˄@$:P˖ ŁHVo-ZKPd5j2/ɪ ԲeBY e˄@ *yȄ@$%P˖ HVE-
+~@-[&J3jԲeB/ U e˄R@$P˖ DRLD
+~Ok/O#i‡|tmoO#+ޞFSWo}yqO]ZMޞ&|H]e pOoCp}
+߆6$= )!
+߆6$= )mH{*|Rې
+])
+u WpOarB0:SwxXCj]==2}ѪP@p5Ժ*!ִgB 1CS"u1[h˄>bDBbmPIpuԺ٭eB11LS὜XCj]OO2Ѫ(PRpT)Fx/*
+U WWpObBa1XSὴXC*3\E="
+ W{pObcPapuTh1fj{*3\MF=ˌ5>B1gSј4PjpT5Fx/6
+ WpOvcB1pS7%S9tpUT(;fڎ{*#k|HcB1U{Sj>}^~!#
+ WpOd B2{ƇTBf{*!3\uH=.DfvkDmЉ^!j^$Z6#3zF:ʑv$О
+Rdf$Ж -ɌVMlOd) ]VY=$؞
+ WepOKZ@[&&#'k{Hd; B{2US@jPС^!j%
+M WpO2eM B2{ƇTTf:{**3\J=f%
+)W
+ WpOeb B2ղSghYC*T-3\]K=ږ%
+ WpOeePp.Th^f{*/b`ھꗷ/r\_70kflW;v:y˻w$}馟iaǟn_isYaUܫ_z= *Y{j]
+V"SzVzZ׳2\,Ժ⭞Eo,=gxgu=+U|HY!Y{j]
+V"SzVzZ׳2\,Ժ⭞Eo,=gxgu=+{=Ժ⭞Eo,=gxgu=+U|H-YԳ̺ҭEo,=gevgu=+[=lOY!Y{jY
+EeV= !gxge=+zіY׳BճԺg>^YD[f]
+V"SzVzZ׳2\,Ժ⭞Eo,=gxgu=+U|HY!Y{j]
+V"SzVzZ׳2\,Ժ⭞Eo,=gxgu=+U|HY!Y{j]
+V"SzVzZ׳2\,Ժ⭞Eo,=gxgu=+{=Ժ⭞Eo,=gxgu=+U|HY!Y{j]
+V"SzVzZ׳2\,Ժ⭞Eo,=gxgu=+U|HY!Y{j]
+V"SzVh/,-geY`u=+[=pOY!R"ub]
+V"SzVZֳB{gmu=+[=lOY!Y{jYV3zVHzٞZ׳BճԲK=hˬYzCj]
+V"SzVzZ׳BճԺg>o,=gxgu=+[=pOYzCj]
+V"SzVzZ׳BճԺ὞Ucj]
+V"SzVzZ׳BճԺg>o,=gxgu=+[=pOYzCj]
+V"SzVzZ׳PXϢ<z崹^/gZ=Գ鹌>|!n~xÍ| D+U ;|zՋf?4i‡|9bӈ{
+~Lϕ_FSW>>×ԕ\,1W0x
+F=
+o_H_+ +3\_Zc`$S` {j!޾pO1{ǷƇTf:{*t|3\_=:/
+Rpu|Tf:{*t|#w|k|Ho B7S!:/
+ WpOo B7o:%>B7Spu|TFxZw|3u|Lf:`{*t|3\_=F:5hu|Tf:{j2ѽ[C*t|3\_=fvmhu|TFxZw|3u|Lf:`{*t|3\_=:;5>B7Spu|TFx
+ WpOo B7S*t|3\_=:/
+ WpOoopu|Tf:{*t|3\_=:;5>B7Spu|T&MǷTf:{*t|3\_=:/
+Rpu|Tf:{*t|#w|k|Ho B7S!:/
+ WpO;:@[&t|#w|k{Ho ߌ^:
+ WpOo B7o:%>B7Spu|TFx
+ WpOo B7S*t|3\_=:/
+ߢv| ;+:GOCǷ=:>|>O^yook>x2=<W;~Et9}EN ;x w
+;ޏwj|H 
+;=w2\;{*Dx?ީ!w2\;{*dw
+;=w"
+;=w2\;{*dw
+;=w2\;{*dw
+ac=#
+ac=3\ƀ{* gT6>l\C* gT6p 0l6Sa8oK|La װ1
+ac=3\ƀ{* Gx6!66l e°qFklOa װ1ZGk3a85l °qkpO3 m0l}ظT6p qfac-3Z`{* Gx6!66l e°qFklOa װ1
+އk|Ha W7
+pOnTB7*ՍSUC*t2\(=Qn
+ݨ W7
+pOnTnTЍpu
+ݨ W7
+pOnTB7*ՍS7ݨSFЍpu
+ݨ W7
+pOnTB7*{7ƇTFeQ{*t2\(=FevFmЍލ!QnZv2zF:QnО
+ݨݨRnTfnЖ ݨV7
+lOnTݨV7=Qn؞
+ݨ W7
+pOQݺQ@[&t"wj{HnTB7*ՍSFЍލ!Qn
+ݨ W7
+pOnTB7*{7ƇTFeQ{*t2\(=Qn
+ݨF
+ݨ W7
+pOnTB7*ՍSUC*t2\(=Qn
+ݨ W7
+pOnTnTЍpu
+ȢQpURQj
+
+QPc*4
+2\=F
+ W
+2\=F
+ W
+
+2\=5>B (
+
+ W
+2\=F
+ W
+J|LFAQ
+ W
+2\=F
+RQj
+
+"7
+j|H@[&4
+2Z=FZ7
+"[Lhd`{*4
+2\=ndvk
+ W
+2\=F
+RQj
+
+QPc*4
+2\=F
+ W
+
+2\=5>B (
+k=eGFklOُ(1^6eGFklO
+!7Cj|HՐ l
+!=C2\!{*̇Dx!D2\"{*dVD
+#7Fj|H 
+C#=F2\c#{*̍$H8Sat$õ:HkxpO#5>#GL h-A!SIM{&,dHT#p
+$Y=&Le6IFklOy$5> %&JL)hS*Sa$VIV+Sa$õXfIkpOْ%5>rIkpO z
+%=&L"o
++&=L2\K&{*ldL
+
+S(޷Pj|H5 
+
+(E=D6e,JD]Ra%5(
+ZϥDRj3a1%5hJk5pOwS2
+
+*7Uj|HU ׬
+
+*e=U2\*{*̫$;JXSad%õJkhpO[+5>JknpO
+
++=fW"
++=W2\+{*쯔!
+ljޏk|H q"
+ljD=3\lj{*'Fx?N!3\lj{*'fT8Np'p8ƇT8Np'p:NS81uqbR81uqb8pO q"
+ljޏk|H q"
+ljD=3\lj{*'&qbp:NS81uqb8pOlj5>qb8pO q"
+ljD=#'
+ljD=3\lj{*'fT8N~XC*'fT8Np'qbfD-#'
+ljD=<N8׉p:NS81qbqbfD-3Zlj`{*'f81uXӞ ljD=3\lj{j}8h˄Ĉlj=qb8pO q"
+ljD=#'
+ljD=3\lj{*'fT8N~XC*'fT8Np'p:NS81oK|L q"
+ljD=3\lj{*'Fx?N!3\lj{*'fT8Np'p8ƇT8Np'p:NS8KT''_ӹ>NS/lj8_}.e_|?m|{z.3v:|׷=Oμ}}xO/zmv}{ӻ,׏o!2{?:mgl~[/gTz;s{/Z\)ϕJ+>?W
+s%=R+ϕB\pOCjso?W"S+xZ\)ϕJ+>?W
+s%=R+ϕB{іY\)s%J!~DV?W
+闟+:R+ϕ2\?W|H-ϕJ!~D?W
+s%=Rf+ϕB\lOJ{js^~De?Wh\ !R+ϕB\pOJ{js ϕ
+ WpOaB0?SXC*3\C=!Z3LFt
+ WpO-~ VhOaau0[h˄aFB0?SadXӞ ÌVlOanC-=B0?S?pTFx
+ WpOaB0?SXC*3\C=!
+ WpOaK|LaB0?S??!!
+ WpOaB0{ƇTf{*3\C=E?. jt+ہK߿?˿lͻ/=|{i,70rb _G@"
+  Rj @p5Th f{*4#7k|HbB1@Sj @@!"
+ WpObB1{ƇTh f{*43\ D="
+  Rj @p5Th f{*4Xc*43\ D="
+ WpObbu1[h˄bFB1@SbdXӞ ČVlOb n D-=B1@Sbf"Ж ČVlObbu1[h˄bFB1@SὁXC*43\ D="
+ WpObb@p5Th f{*43\ D=5>B1@Sj @p5Th Fxo
+ WpObB1@SὁXC*43\ D="
+ WpOb4K|LbB1@Sj @@!"
+ WpObB1{ƇTh f{*43\ D="
+  Rj @p5Ժ٭eB1{Th f{j@襁Dh f@{*4#7k|H@[&43Z D="Z7#[ ĚLh f`{*43\ D=n fvk m@@!"
+ WpObB1{ƇTh f{*43\ D="
+  Rj @p5Th f{*4Xc*43\ D="
+ WpObb@p5Th f{*43\ D=5>B1@Sj @,qY^Pk ޾X@~؞zi |>~]
+>i[_?=/?|<_o^!\ʇps\J/8vcg=#;Z;gv;v29u ±spO#[5p:vS9u ΙݎL8v~\C*;g9۱3Ж cg=#;Z;gv;v29u ±spOc5>±spOc ױ3
+cg=#;
+cg=3\΀{*;gT8v~\C*;gT8vp;p:vS9sp:vS9u ±spOc5>±spOc ױ3
+cg=عT8vp;p:vS9u ±scR9u ±spOc ױ3
+ޏk|Hc ױ3
+cg=3\΀{*;Gx?v!3\΀{*;g9۱3Ж ݏk{Hc ױ3Z;gr D8v`;pعƇ9۱3Ж cg=3\΀{j}:viτcֱ3؞
+cg=>vv e±sDcR9u ±spOc ױ3
+ޏk|Hc ױ3
+cg=3\΀{*;Gx?v!3\΀{*;gT8vp;p7%>±spOc ױ3
+cg=#;
+cg=3\΀{*;gT8v~\C*;gT8vp;p\fڱ W8?\gOgzߛ><i{^ _~;],_k??zQnwbr燗wu^o~-wޖf=ΦMDuuuWWw^PuuUR*uuU
+pO 
+WWޯj|H 
+WW+=2\WW{*\]Ex!2\WW{*\]e
+pO 
+WW ~suUc*\]e
+pOWW5>U
+pO 
+WW+="_]
+WW+=2\WW{j}u
+h˄WW=U
+pO-2zu"\]eT~uUCj}u
+h˄؞
+WW+=l]]մgUF
+lO Z_]ev2*UmpuẺS*uuU
+pOWW5>U
+pO 
+WW+="_]
+WW+=2\WW{*\]e
+pO 
+WWޯj|H 
+WW+=2\WW{*\]Ex!2\WW{*\]e
+YpfpO,f~o`dY~x,7SnOH4mdz^–3ϯo?KJOᇛ\u_f{~_2=vچpM͞W3a=sE>{>Va-sE>}>a-sE?锾$[}y?&SnAy^&2Wpv{d\/.IaOn+&2W39nw=Legrw{yd˼/p2= ggۅv{d\dڇv{d\d{Ha-|6!| {押\.C(/l+i?w=LeȗtJa-<Fr<v{d\p 0ɖ"ϟ:.IyLv^&2˗3mw= gg2mw=Legw=Leȗߒ,ϒ:>{6.|
+0Ȟ"ϟ4il+lw&2Wpݧ0ɖy_>o7>| {押·nl+qqByyd\xLa-|?)| {<&snIyLvnl+LC(/ln+~3-|$[|\nl+eL黽<Le|vs9O?õ<dgrByyd\丟Byyd\dN(/l+8?&CnAyL g}t 0"$[ʷo7kK^Wfw90"7e$[|c,WY~<G3W9n÷=Le^ng}Ο~:0k< ]VAy@~y׉+l{\Hװe„t$kA-Ggxt$k;-#Yѵl0Ze˄Dѥ0ڋe˄HXt-[&LEGk2a':Lt){&DG6k2a!:5]˖ БuZL؆N> ]ʞ Б]ZLXdBײe$t$k-Agt$k -#YCеl0Ze˄ @!#Yϵle˄Hs-[&>'r}.eτHs-[&,>Gk2a9\˖ [ωܧK3a9\˖ +ϑZLxd-<ײe¾s"yRLwdm;ײe²s$kع-u\a9qs {& :Gk25]ƜkUL9Gk2a9s){f=mùv_'‚sj-#Y͵lvskt-#W5lmeˬ'#-6DkN>\ž c͑ZLXjd 5ײeLs$k-6O4g@s$k-֙#Y{{l۫e˄DK{왰ɚ٫e˄H^-[&,Ej2a^/o*yȄmHִ^-[& Evj2aU/5W˖ zJ3aO/5W˖ cz-ZLXҋd ղeŒ^"RLЋdMղe€^$k?-ևմWs=O筿ۙy0.}rfYOMwݗ?_N7?^wG)^ks~%{/huO/[QpOzCj V#S^ZwBԺ>ͽo==.xku/[pO+|Cj V#S"_&ZwBԺΗ>֍o>=.xku/[pO}nCj V#S_ZwBԺWcj V#S_ZBԺ>^jD[f] #S_Z2̺ҭH%o-@=R$2*`F ZC ԲK#hˬ;!Jd{j] pRf`h/@-.tku?0[ApO+ Cj V$S` ZwC Ժ. >֍oA=. xk uo0[qpO Cj V$Sa!ZwC ԺF>MoUB=.xku0[pO+N!Cj* V+$Sbaf!Zw C Ժ^_Xcj0 V1$Sa!Z C Ժj>mouC=.xku0[pOk!Cj< V=$Sa!ZC Ժ >-o5D=."xk"e12"іY3Z}DԺ⭒HVĐ~i%:%p+&u51M|H-ۉ̺ҭHo%E=)fv)uS1[UlOˊ!ڊ{jW He֕ŌVg!n-x-uq1[spO!ʋ{j]_pRc
+#ZC ԺHU W!n3x3u1[pO;!J{j]kk1n6x6u1[pO!
+{j]qpuRc#ZC5 ԺHu W!n<x<u1[pO{U9/,> Z{cn?ήtu=}ܭvz秗o~OO}+_+CCoU!TpH:S>oK|LC !=
+Cz=3\{*Gx?!>vHe!}FlOC !=ZGk3>uH!}pO3mpHT8p!}fCz-3Z`{*Gx?!>vHe!}FlOC !=
+k|HC !=
+Cz=3\{*Gx?!3\{*gT8ppHƇT8ppH:S>uH!}CR>uH!}pOC !=
+k|HC !=
+Cz=3\{*'!}pH:S>uH!}pOC5>!}pOC !=
+Cz=#
+Cz=3\{*gT8~H_C*gT8p!}fCz-#
+Cz=<׉pH:S>!}!}fCz-3Z`{*g>uH_Ӟ Cz=3\{j}Hh˄C=!}pOC !=
+Cz=#
+Cz=3\{*gT8~H_C*gT8ppH:S>oK|LC !=
+Cz=3\{*Gx?!3\{*gT8ppHƇT8ppH:Sᐾ<CTh$Y/_"tyڜ_
+'zx\mJ$u=@'AJ w*|R')ܩAJ\r)UAJYPე:>H NR %;>H냔@T J>?H)*|R')ܩAJ\r)ur}ȝ
+T)eBR %;>H냔@T NRSე* ,_AJ\r)ur}ȝ
+AJ w*|R% >Hk rk rk r?|k rk rk rg/Լc jcSk nk rg3 N'
+N'ȝ{?AmLV
+N'ȝ{?AmLvj?TV{?AmLvj?Tvr?ATV
+N'ȝ
+N'ȝ
+N'ȝ
+J>YPaɵSaɵSaɵSaY3*?; w*?; w*?; w*?+fBg'Ng'Ng'Ng%,_ܩܩܩlw(_ܩܩܩs \O;\O;\O;|?|k rk rk rg/Tvr?ATvr?AԼc jcRgv/Tvr?AԸğکs 5?;ژ۩ܩyYjgSk nk rgOPz?{k rk rk rg/Tvr?ATvr?ATvr?ATV
+N'ȝ
+N'ȝ
+N'ȝ
+F~
+N'ȝ
+N'ȝ
+N'ȝ
+J>YPaɵSaɵSaɵSaY3*?; w*?; w*?(…Bo/pR
+AJ_AJx_~}:o?q}=~;t7z}Kܧ[ ]_s|p4ʝ#i;x8>q uG~{;(wvɷ(wvNocNܩ9=}&Bݑ}4ʝ#ci;uG~=޹?ژozj8M#?^_3Qpè6f^gFSonN|o|iQm͜.z;noZ?4ʝX~0c>.ouvr8^g>Nܩ;R??΢3t<_>Nzorl8ro;vNݑ~i;|{;si/cQ|;rG+5rBjP/TH ur@TH ur@TH ur@TH U
+Nȝ
+Nȝ
+Nȝ
+J>SCYP!5ɕS!5ɕS!5ɕS!5Tgj(*:RC w*:RC w*:RC w**L eBP'WjNP'WjNP'WjNP%,_J ܩJ ܩJ ܩjw(_J ܩJ ܩJ ܩ35 5:H ژJ ۩J ܩ95TjgBjS+5nBj+5rP!PRCz{Bj+5rP!PRCZ!p;RC||P!PRCZ!p;RC\!;RC||Bj+5rBj+5rBj+5rBjP/TH ur@TH ur@TH ur@TH U
+Nȝ
+Nȝ
+Nȝ
+J>SCYP!5ɕS!5ɕS!5ɕS!5Tgj(*:RC w*:RC w*:RC w*]j(W*:RC w*:RC w*:RC w**L eBP'WjNP'WjNP'WjNP%,_J ܩJ ܩJ ܩ35 RC\!;RC\!;5:H ژ35 RC\!;5:g":RCv**L eBͩ=RC6&:RCv*:RC wjN UnڙJ ۩J ܩ95ԹGjƄP^J ܩJ ܩJ ܩ35 RC\!;RC\!;RC\!;RC||Bj+5rBj+5rBj+5rBjߥ|Bj+5rBj+5rBj+5rBjP/TH ur@TH ur@TH ur@TH U
+Nȝ
+Nȝ
+iSCpPJ ݧ_ΩyBjhH ПÿӏC^_tƝp}o{<K J(_йGƄRCVNRC'WNͥʭRCV;J Zp;J \;5:Vj
+NRȝ
+NRȝ
+NRȝ
+J>K YPUj
+NRȝ
+NRȝ
+NRȝ
+F~Wj
+NRȝ
+NRȝ
+NRȝ
+J>K YPUj
+J>K YPssR NR
+NRȝK [v&:J v*:J wj.5tQj
+AN ȝ[Av&9:v*9: wj rt-DrT
+AN ȝ
+AN ȝ
+AN ȝ
+AJ>YP!
+rܩ
+rܩ
+rܩ3ȑ \A;\A;\A;|9|B+rB+rB+rB G/Trtr9@Trtr9@Trtr9@Tr4 GTrtr9@Trtr9@Trtr9@TrT򅚃{9@mLrtj9Trtr9@
+rd3!ѩ
+AJ>YPss  AN 
+AN ȝ
+AJ>YP!
+rܩ
+rܩ
+rܩ3ȑ \A;\A;\A;.+\A;\A;\A;|9|B+rB+rB+rB G/Trtr9@Trtr9@Trtr9@TrT
+AN ȝ
+AN ȝ{9@mLrT
+AN ȝz r3XAP;|9| GAPZAp;\A;59*YLrtj9Trtr9@#jcBR Gv/Trtr9@Trtr9@Trtr9@TrT
+AN ȝ
+AN ȝ
+AN ȝ
+AJ>YP!
+sN-ȝ
+sN-ȝ
+sJ>YPan5San5San5San[6*m; w*m; w*m; w*m+fBm'Nm'Nm'Nm%s,_0ۂܩ0ۂܩ0ۂܩ0sn 涝\s[;涝\s[;涝\s[;涕|m|ܶkn rܶkn rܶkn rܶm|ܶkn rܶkn rܶkn rܶϹm/<cn jcܶSkn nܶkn rm6 sN-
+sN-ȝ綝{mAmLV9
+sN-ȝ綝{mAmLvjmTV9綝{mAmLvjmTvrmATV9
+sN-ȝ
+sN-ȝ
+sN-ȝ
+sJ>YPan5San5San5San[6*m; w*m; w*m; w*m+fBm'Nm'Nm'Nm%s,_0ۂܩ0ۂܩ0ۂܩ0mws(_0ۂܩ0ۂܩ0ۂܩ0sn 涝\s[;涝\s[;涝\s[;涕|m|ܶkn rܶkn rܶkn rܶϹm/TvrmATvrmA<cn jcܶRϹmv/TvrmA8ğ0Ěۂک0sn 5m;ۂژ0Ԛۂ۩0ۂܩyn[5jgܶSkn nܶkn rms[P涕zm{ܶkn rܶkn rܶkn rܶϹm/TvrmATvrmATvrmATV9
+sN-ȝ
+sN-ȝ
+sN-ȝ
+sF~7
+sN-ȝ
+sN-ȝ
+sN-ȝ
+sJ>YPan5San5San5San[6*m; w*m; w*m…Boosp?~Q3Q`_/1z6?~eG{|#O{}|vyb?_O>o
+Nȝ
+Nȝ
+Nȝ
+)J>[YPɕ
+ܩhw(_Pܩ
+ܩ9 RjgBS+ nB r6HqP zA{B!+rHHP:!Zp;R!|B|ZHP!Zp;!\;!|vC|B9+rB<rB?+ rBBφH/TtreD@TtrD@ThtrD@TȉT
+ENȝ
+QNȝ
+]Nȝ
+iJ>"YP.ɕS!0US1S!3Rgg$*F:R# w*F:j# w*F:# w*$G]s$W*TG:# w*G:# w*G:# w*G*dBI'WNI'WNI'WNI%-,_P#ʑܩ$*ܩ$䊒ܩ%K $\i;$\u;5I:ژ(ԳQ *%\;5J:V*g"J:b%v*J*dBŒ=%6&DK:%v*tK:% wjNTnKڙP/ʗ۩0*ܩaҹGƄI^P2Jܩ3䪙ܩ3
+ܩ4i &\Y;&\e;&\q;&|M|Bᤓ+qrB䤓rrB礓+trBꤑߵN|B+wrB𤓫xrB+zrBI/T(trO@TtrO@TtrP@THT@
+P$tܧ  Jtr,8~am _}~?ԯv<///eѨVڡQ:Q \
+;57*:hTژШjT۩Шhw(_QѹGƄFEVNFE'WN͍ʭFEV;Z
+p;\
+;56*:֨
+;\
+;\
+;|6*|BQrBQrBQrBFE/ThTtr5*@ThTtr5*@ThTtr5*@ThTT٨
+NFȝ
+NFȝ
+NFȝ
+J>YPQը
+;\
+;\
+;|6*|BQrBQrBQrBFE/ThTtr5*@ThTtr5*@ThTtr5*@ThTT٨
+NFȝ
+NFȝ
+NFȝ
+F~ר
+NFȝ
+NFȝ
+NFȝ
+J>YPQը
+J>YPssF NF
+NFȝ[v&4*:v*4*: wjnTtѨ
+;\
+;\
+;|6*|B"F\h4*/JKϷ_Q1N_ykTߟN?~]r/^P<=Cos.Bi;uG~9_ᯓoQp\|;r>ki/<_>Nܩ;9__[vNݑ~i;z{;$_;9>}FSw䷷tl8rȷ6OPz?z~{B陸rOPz?Zp;z?+5~:ژ۩ܩSjgBSnB陸rOz? LO%^ܩܩܩ z?\;z?\;z?\;z?|~|B陸rB陸rB陸rB戮O/Ttr~@Ttr~@Ttr~@TT
+Nȝ
+Nȝ
+Nȝ
+J>{?YPSSSSg'*~:z? w*~:z? w*~:z? w*~*dBO'WNO'WNO'WNO#DJO'WNO'WNO'WNO%,_ӹGƄOVNO'WNͽʭOV;z?Zp;z?\;5~:ژԳ z?\;5~:ژ۩ 5~:ژ۩ܩ z?\;z?\;z?\;z?|~|B陸rB陸rB陸rB戮O/Ttr~@Ttr~@Ttr~@TT
+Nȝ
+Nȝ
+Nȝ
+J>{?YPSSSz?QRSSSSg'*~:z? w*~:z? w*~:z? w*~*dBO'WNO'WNO'WNO%,_ܩܩӹGƄO^ܩө?ӉSSg'jt1өSSsr΄OVNO'WNͽ=z?6&~*dBO'WNO'WNO'WNO%,_ܩܩܩ z?\;z?\;z?\;z?+z?\;z?\;z?\;z?|~|B陸rB陸rB陸rB戮O/TZM OOyXd8~_~?/~Q||=;i/3[Up*xp*[{
+@mLhtj
+Thtr
+@*jd3UЩ*
+@Thtr
+@Thtr
+@ThT*
+NVȝ
+NVȝ
+NVȝ
+J>[YPU*
+:Z w*
+:Z w*
+:Z w*
+*ldBVA'W
+|BU
+|BU
+NVȝ[{
+@mLhT*
+NVȝ[{
+@mLhtj
+ThT*[{
+@mLhtj
+Thtr
+@ThT*
+NVȝ
+NVȝ
+NVȝ
+J>[YPU*
+:Z w*
+:Z w*
+:Z w*
+*ldBVA'W
+|BU
+@Thtr
+@*ܣU
+@*[
+:hژ*j۩*jܩUP*jgBSU
+{BU
+@Thtr
+@Thtr
+@ThT*
+NVȝ
+NVȝ
+NVȝ
+F~*
+NVȝ
+NVȝ
+NVȝ
+J>[YPU*
+jFBǧ~ \no?V8~Z?߾~7|7޶~/;u5$w?N۩0$ܩqHީ!9?aH^<* ; w* ; w* ; w* +gB!y'אN!y'אN!y'אN!y%C,_0$ܩ0$ܩ0$ܩ0$sH \Cr;\Cr;\Cr;|ɳ|kHrkHrkHr!y/Twr ATwr ATwr ATW9$
+CN!9ȝ
+CN!9ȝ
+CN!9ȝ
+CJ>YPaH5$SaH5$SaH5$SaHQRaH5$SaH5$SaH5$SaH^<jw1$1aHީ5$SaH5$SrkH΄!y֐N!y'אNC=6& +gB!y'אNC=6& ;v* +gBC=6& ;v* ; w* +gB!y'אN!y'אN!y'אN!y%C,_0$ܩ0$ܩ0$ܩ0$sH \Cr;\Cr;\Cr;|ɳ|kHrkHrkHr!y/Twr ATwr ATwr AT7!yTwr ATwr ATwr ATW9$
+CN!9ȝ
+CN!9ȝ
+CN!9ȝ
+CJ>YPaH5$SaH5$SaH5$SaH^<* ; w* ; wjw1$1aH^<* ; wjwmHDwb ATW9$򅚇{ AmLwj Twr A<$g3aHީ5$SaH5$Ss!9 CJ=ٽPaH5$SaH5$SaH5$SaH^<* ; w* ; w* ; w* +gB!y'אN!y'אN!y'אN!y#GJ!y'אN!y'אN!y'אN!y%C,_0$ܩ0$ܩ0$ܩ0$sH aBcH~8$WZ/0$:?=Ð|z6$/_t|zAǷ__not}/:1y/>sڙρV.́) \( @ThU9
+Nȝ
+Nȝ
+Nȝ
+J>ZYPa5Sa5Sa5SaU@+* :Z w* :Z w* :Z w* *heBV'@ NV'@ NV'@ NV#hEJV'@ NV'@ NV'@ NV%,_yչ@ ƄV@ NV'@ NʭVV;ZZ-p;Z\-;5:hژ0Ъs Z\-;5:hژ0h۩0Ъs 5:hژ0h۩0hܩ0Ъs Z\-;Z\-;Z\-;Z||@kr@kr@kr@ρV/Thur @Thur @Thur @ThU9
+Nȝ
+Nȝ
+Nȝ
+J>ZYPa5Sa5Sa5SaZQRa5Sa5Sa5SaU@+* :Z w* :Z w* :Z w* *heBV'@ NV'@ NV'@ NV%,_0hܩ0hܩyչ@ ƄV^0hܩqթ?aՉ5SaU@+jhu11aթ5Sa5S@rk΄V@ NV'@ N=Z6& *heBV'@ NV'@ NV'@ NV%,_0hܩ0hܩ0hܩ0Ъs Z\-;Z\-;Z\-;Zn+Z\-;Z\-;Z\-;Z||@kr@kr@kr@ρV/ThP7Ђ @+\G+|8~6t~6.{|(ۿN{}{\_DG|x;n&1w̗Rd6zx{z>0|9op<0cίid6z^.٘ͯwZ>q1|0c4ol흜ڧ&1wrz*8 fgoT>q1ӱ|0c~\<⏳L|};NSa;s|>G=٘;tigQoIl3 y~9Wf慹c%g>Nһ&1w̷r14ol̝qJw'w0c>޾3oloy8JOm>ؾuyTμ&1wGw0c>Ͼ3ol̝j>̾2`vy$μ&1ww0c>þ3ol̝g>2`vyμ&1wGw0c>3ol̝y3w'w0cjHy$lX̣fcReV\)JylU#fcRgM%03*J̜ṢfcRg)%03:(٘TQfgRg%03:'٘9ԙG; ʬpR6;3g:&٘ԙG2 <zI`6f%5K慙SIylI#fcHRg$03*I̘G[ yG a<H`6f"UE^#$RE$3:!٘1ԉx?s *<*H`6fl u-@꼣bcQeV(Gx+31w:ؘ9zԙGţʬQ6;3:٘uԙG̡<:G`6fUfEٙ9qԙG}<F`6fu61s٨2+lY<F`6fnu41sШ3kFY1lvfNuQ21sǨ3#Fy4l\0
+e3s3zEyl.̣[fcjQcѢh^9YԙG̽<rE`6fu*1s2+T̙<*E`6fnu(1s3>DYqlvfNuQ&1s3,Dy4l\$
+e3s3[Dyl"[BTy!bgQg"03:G~x9>YG{ʬP6;3f:Vgbnuޑ1sp3kCxĆ׈95yGi̝<2C`6f u1\
+ e3s^3Byḷ+fcPeVT(Byl#'fcPg-!03*BB̜̣"fcPg !03:٘Ԙg<(fNuQ1s73lAy4l\
+e3s.3Z[Ayl
+̣fcJPeV$(A1qS6۱λ}p:v8^/o@_~o;.r~ r-g.B)rB2fN/Ttres@Ttrs@Thtrs@TT
+Nȝ
+Nȝ
+Nȝ
+)J>[:YPɕS!USsSs YJ=:ٽPɕSs\s }N
+F~؉+;{dv@mLtjvThtrv@Ԝ۩d3өS!UScwSo&BzNV/Ttrw@Ttrx@ThtrEx@TT
+%Nȝ
+1Nȝ
+=N ȝ
+IJ><YPɕS!USS!Sg'*z:= w*Dz:*= w*tz:B= w*z*ldBZO'WN`O'WNfO'WNlO%ݞ,_PJܩܩ
+ܩ *>\;B>\%;Z>\1;r>|||Bѧ+rBԧrBק+rBڧߵ}|Bݧ+rB৓rB㧓+rB槒O/\#jcBSnB陸+rOV' ՟N
+Nȝ?{@mLT
+Nȝ#@{T@mL
+eN4ȝ
+qN:ȝ
+}N@ȝ
+J>AYPɕ S!U
+S S!Tg/(*:A w*D:A w*t:A w**leBzP'W>NP'WANP'WDNP%,_PJ ܩ ܩ
+
+ܩjwM(_P
+ܩ* ܩ ܩ/ 
+C\!;"C\!;:C\!;RC||Bm+7rBp8rBs+:rBvP/T(ur@TurՇ@#@jcBRQv/Ture@"[ğ"Ċک#G 5:Hژ%Ԫ۩%
+ܩ9MT&jgBS+OnBPrFQ"P2Ezv{B+UrBVrB+XrBfQ/Ture@Tur@Thurŋ@TU/
+Nȝ
+Nȝ
+Nȝ
+)F~2
+5Nȝ
+ANȝ
+MNȝ
+YJ>FYPlɕ6S!nU7So8S!qTg(*TBFB1t^t41t4N}\mK/}ׯ߿M?_o?}uvxypγ""N-b'N-b'N-b%[,_E"ܩE"ܩE"ܩEs \[D;\[D;\[D;|n|krkrkr-b/T"vrmAT"vrmAT"vrmAT"VE
+[N-"ȝ
+[N-"ȝ
+[N-"ȝ
+[F~E
+[N-"ȝ
+[N-"ȝ
+[N-"ȝ
+[J>YPs-" [N-"
+[N-"ȝ[[Ĭv&l;v*l; wj"vE1aX1*l; wj"vE1aةESaX1j"vE1aةESaɵESaX1*l; w*l; w*l; w*l+"fB-b'N-b'N-b'N-b%[,_E"ܩE"ܩE"ܩEs \[D;\[D;\[D;|n|krkrkrm|krkrkr-b/T"vrmAT"vrmAT"vrmAT"VE
+[N-"ȝ
+[N-"ȝ
+[N-"ȝ
+[J>YPaɵESaɵESs-" [J=ٽPaɵESSo[D&kj-b/ԼEcjcSknkr-b1 [N-"
+[N-"ȝ{lAmL"VE
+[N-"ȝ
+[N-"ȝ
+[N-"ȝ
+[J>YPaɵESaɵESaɵESaX1*l; w*l; w*l; w*l1W*l; w*l; w*l; w*l+"fB-b'N-b'N-b'N-b%[,_E v[σE WZϰE|9OϯEޯ|64eoy_>/o_:8޴Lrm✐ߜÄ҄N I'ׄN I%,_0!䚐ܩ0!䚐ܩ0!䚐ܩ0!sB &$\;&$\;&$\;&$nB+&$\;&$\;&$\;&$|NH| IP&$Zp;&$\;5OH*&$YLtjMHTtrMH@<!cBjc„R Iv/TtrMH@<!cBjc„SkBn„ I/<!cBjc„SkBn„kBr„ I/TtrMH@TtrMH@TtrMH@TT9!
+N ȝ
+N ȝ
+N ȝ
+J>'$YPaB5!SaB5!SaB5!SaBR$*LH:&$ w*LH:&$ w*LH:&$ w*LH*dB I'ׄN I'ׄN I'ׄN I#DJ I'ׄN I'ׄN I'ׄN I%,_0!䚐ܩ0!䚐ܩ0!䚐ܩ0!sB &$\;&$\;&$\;&$|NH|„kBr„kBr IP&$zNH{„kBr I&$ L I'քN I%,_yBҹDŽƄ IքN I'ׄNʭ IV;&$Zp;&$\;5OH:ژ0!sB &$\;&$\;&$\;&$|NH|„kBr„kBr„kBr„ I/TtrMH@TtrMH@TtrMH@T4 ITtrMH@TtrMH@TtrMH@TT9!
+N ȝ
+N ȝ
+N ȝ
+J>'$YPaBf .4&$pO/^? y qۿնS:~_Oo=~y<~`rV:^<Oȷ(wrx^m'Nܩ;ʷ(wcv$_;9?}FSw䷷szNFSw䷷sԏ};r>oo>q uG~>}FSw>qNݑ_oWɷ(wc|yk8M#Si;uG~{;/OcQ<ǾFS_no|:q uG~{;Si;uG~{;si;uG~{;OcNܩ>q uG~>\/ϭ|;rȷv)Nܩ;zԏ};r>~~ym8M#ki;uG~{;߮i;uG~{;oQ휞>N|oo>qNݑӱ}4ʝ#^ڟlQmt=|&Bݑ(wr8]jaTs|{3/3N۩7OWRN|o%q1ڪvj3Nܩ۫iNIpy{FSw>qNݑ_qş>q uG~{/ci;uG~{//ǷVFSww|vN},?>q uG~{;si;uG~{;(wv^N|;r>?Oo/cIPw>qNݑ_OcQryvN},kkO:M# FSw䷷s~k8rooT?4ʝX~ӱ}4vg14ʝ#Si;uG~=<ǾFSwZO}4|; w*$; w*$; w*$+L4fBDc'WNDc'WNDc'WNDc%,_hJ4ܩhJ4ܩhJ4ܩhlw(_hJ4ܩhJ4ܩhJ4ܩh3ј 5';H4ژhJ4۩hJ4ܩ9XhjgBS+nB+rDcFPz&{B+rDcFPZFp;|&|DcFPZFp;\F;|&|B+rB+rB+rBDc/TH4vr%ATH4vr%ATH4vr%ATH4Vh
+ND#ȝ
+ND#ȝ
+ND#ȝ
+J>YP!ɕhS!ɕhS!ɕhS!Xg1*$; w*$; w*$; w*$]1W*$; w*$; w*$; w*$+L4fBDc'WNDc'WNDc'WNDc%,_hJ4ܩhJ4ܩhJ4ܩh3ј \F;\F;5';H4ژh3ј \F;5&;hg"$;v*$+L4fB͉=6&$;v*$; wjN4Vn%ڙhJ4۩hJ4ܩ9عGƄDc^hJ4ܩhJ4ܩhJ4ܩh3ј \F;\F;\F;|&|B+rB+rB+rB%|B+rB+rB+rBDc/TH4vr%ATH4vr%ATH4vr%ATH4Vh
+PlphDc]v8]n?GOz͛?9ݾ/?~ݽm.GIendstream
+endobj
+1810 0 obj <<
+/Type /Page
+/Contents 1811 0 R
+/Resources 1809 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 1899 0 R
+/Annots [ 1813 0 R 1814 0 R 1815 0 R 1816 0 R 1817 0 R 1818 0 R 1819 0 R 1820 0 R 1821 0 R 1822 0 R 1823 0 R 1824 0 R 1825 0 R 1826 0 R 1827 0 R 1828 0 R 1829 0 R 1830 0 R 1831 0 R 1832 0 R 1833 0 R 1834 0 R 1835 0 R 1836 0 R 1837 0 R 1838 0 R 1839 0 R 1840 0 R 1841 0 R 1842 0 R 1843 0 R 1844 0 R 1845 0 R 1846 0 R 1847 0 R 1848 0 R 1849 0 R 1850 0 R 1851 0 R 1852 0 R 1853 0 R 1854 0 R 1855 0 R 1856 0 R 1857 0 R 1858 0 R 1859 0 R 1860 0 R 1861 0 R 1862 0 R 1863 0 R 1864 0 R 1865 0 R 1866 0 R 1867 0 R 1868 0 R 1869 0 R 1870 0 R 1871 0 R 1872 0 R 1873 0 R 1874 0 R 1875 0 R 1876 0 R 1877 0 R 1878 0 R 1879 0 R 1880 0 R 1881 0 R 1882 0 R 1883 0 R 1884 0 R 1885 0 R 1886 0 R 1887 0 R 1888 0 R 1889 0 R 1890 0 R 1891 0 R 1892 0 R 1893 0 R 1894 0 R 1895 0 R 1896 0 R 1897 0 R 1898 0 R ]
+>> endobj
+1813 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 705.1906 236.5823 716.0945]
+/Subtype /Link
+/A << /S /GoTo /D (2847) >>
+>> endobj
+1814 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 705.1906 538.9788 716.0945]
+/Subtype /Link
+/A << /S /GoTo /D (2847) >>
+>> endobj
+1815 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [70.7348 689.9826 173.7473 700.8617]
+/Subtype /Link
+/A << /S /GoTo /D (customization) >>
+>> endobj
+1816 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 689.9826 538.9788 700.8617]
+/Subtype /Link
+/A << /S /GoTo /D (customization) >>
+>> endobj
+1817 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 674.5057 194.0513 685.4096]
+/Subtype /Link
+/A << /S /GoTo /D (extensions) >>
+>> endobj
+1818 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 674.5057 538.9788 685.4096]
+/Subtype /Link
+/A << /S /GoTo /D (extensions) >>
+>> endobj
+1819 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 663.6115 169.7129 672.4582]
+/Subtype /Link
+/A << /S /GoTo /D (cust-skins) >>
+>> endobj
+1820 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 663.6115 538.9788 672.4582]
+/Subtype /Link
+/A << /S /GoTo /D (cust-skins) >>
+>> endobj
+1821 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 648.6028 211.6151 659.5068]
+/Subtype /Link
+/A << /S /GoTo /D (cust-templates) >>
+>> endobj
+1822 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 648.6028 538.9788 659.5068]
+/Subtype /Link
+/A << /S /GoTo /D (cust-templates) >>
+>> endobj
+1823 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 635.6514 262.0659 646.5553]
+/Subtype /Link
+/A << /S /GoTo /D (template-directory) >>
+>> endobj
+1824 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 635.6514 538.9788 646.5553]
+/Subtype /Link
+/A << /S /GoTo /D (template-directory) >>
+>> endobj
+1825 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 622.7 284.6611 633.6039]
+/Subtype /Link
+/A << /S /GoTo /D (template-method) >>
+>> endobj
+1826 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 622.7 538.9788 633.6039]
+/Subtype /Link
+/A << /S /GoTo /D (template-method) >>
+>> endobj
+1827 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 609.7485 239.7302 620.6525]
+/Subtype /Link
+/A << /S /GoTo /D (template-edit) >>
+>> endobj
+1828 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 609.7485 538.9788 620.6525]
+/Subtype /Link
+/A << /S /GoTo /D (template-edit) >>
+>> endobj
+1829 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 596.7971 260.3028 607.701]
+/Subtype /Link
+/A << /S /GoTo /D (template-formats) >>
+>> endobj
+1830 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 596.7971 538.9788 607.701]
+/Subtype /Link
+/A << /S /GoTo /D (template-formats) >>
+>> endobj
+1831 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 583.8457 227.3364 594.7496]
+/Subtype /Link
+/A << /S /GoTo /D (template-specific) >>
+>> endobj
+1832 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 583.8457 538.9788 594.7496]
+/Subtype /Link
+/A << /S /GoTo /D (template-specific) >>
+>> endobj
+1833 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [118.5554 570.8942 352.9843 581.7982]
+/Subtype /Link
+/A << /S /GoTo /D (template-http-accept) >>
+>> endobj
+1834 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 570.8942 538.9788 581.7982]
+/Subtype /Link
+/A << /S /GoTo /D (template-http-accept) >>
+>> endobj
+1835 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 557.9428 262.3947 568.8467]
+/Subtype /Link
+/A << /S /GoTo /D (cust-change-permissions) >>
+>> endobj
+1836 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 557.9428 538.9788 568.8467]
+/Subtype /Link
+/A << /S /GoTo /D (cust-change-permissions) >>
+>> endobj
+1837 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 544.9914 287.3108 555.8953]
+/Subtype /Link
+/A << /S /GoTo /D (integration) >>
+>> endobj
+1838 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 544.9914 538.9788 555.8953]
+/Subtype /Link
+/A << /S /GoTo /D (integration) >>
+>> endobj
+1839 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [70.7348 529.7833 154.2906 540.6625]
+/Subtype /Link
+/A << /S /GoTo /D (troubleshooting) >>
+>> endobj
+1840 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 529.7833 538.9788 540.6625]
+/Subtype /Link
+/A << /S /GoTo /D (troubleshooting) >>
+>> endobj
+1841 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 516.2441 179.0778 525.2104]
+/Subtype /Link
+/A << /S /GoTo /D (general-advice) >>
+>> endobj
+1842 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 516.2441 538.9788 525.2104]
+/Subtype /Link
+/A << /S /GoTo /D (general-advice) >>
+>> endobj
+1843 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 501.355 328.0678 512.2589]
+/Subtype /Link
+/A << /S /GoTo /D (trbl-testserver) >>
+>> endobj
+1844 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 501.355 538.9788 512.2589]
+/Subtype /Link
+/A << /S /GoTo /D (trbl-testserver) >>
+>> endobj
+1845 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 489.1209 401.6009 499.3075]
+/Subtype /Link
+/A << /S /GoTo /D (trbl-perlmodule) >>
+>> endobj
+1846 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 489.1209 538.9788 499.3075]
+/Subtype /Link
+/A << /S /GoTo /D (trbl-perlmodule) >>
+>> endobj
+1847 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 475.4521 245.6774 486.3561]
+/Subtype /Link
+/A << /S /GoTo /D (trbl-dbdSponge) >>
+>> endobj
+1848 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 475.4521 538.9788 486.3561]
+/Subtype /Link
+/A << /S /GoTo /D (trbl-dbdSponge) >>
+>> endobj
+1849 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 462.5007 246.3548 473.4046]
+/Subtype /Link
+/A << /S /GoTo /D (paranoid-security) >>
+>> endobj
+1850 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 462.5007 538.9788 473.4046]
+/Subtype /Link
+/A << /S /GoTo /D (paranoid-security) >>
+>> endobj
+1851 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 449.5493 305.9511 460.4532]
+/Subtype /Link
+/A << /S /GoTo /D (trbl-relogin-everyone) >>
+>> endobj
+1852 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 449.5493 538.9788 460.4532]
+/Subtype /Link
+/A << /S /GoTo /D (trbl-relogin-everyone) >>
+>> endobj
+1853 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 436.5979 344.6957 447.5018]
+/Subtype /Link
+/A << /S /GoTo /D (trbl-index) >>
+>> endobj
+1854 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 436.5979 538.9788 447.5018]
+/Subtype /Link
+/A << /S /GoTo /D (trbl-index) >>
+>> endobj
+1855 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 423.6464 485.6352 434.5504]
+/Subtype /Link
+/A << /S /GoTo /D (trbl-passwd-encryption) >>
+>> endobj
+1856 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 423.6464 538.9788 434.5504]
+/Subtype /Link
+/A << /S /GoTo /D (trbl-passwd-encryption) >>
+>> endobj
+1857 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [70.7348 410.3413 118.1166 419.3175]
+/Subtype /Link
+/A << /S /GoTo /D (patches) >>
+>> endobj
+1858 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 410.3413 538.9788 419.3175]
+/Subtype /Link
+/A << /S /GoTo /D (patches) >>
+>> endobj
+1859 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 394.8992 242.8977 403.8654]
+/Subtype /Link
+/A << /S /GoTo /D (cmdline) >>
+>> endobj
+1860 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 394.8992 538.9788 403.8654]
+/Subtype /Link
+/A << /S /GoTo /D (cmdline) >>
+>> endobj
+1861 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 380.0101 293.3978 390.914]
+/Subtype /Link
+/A << /S /GoTo /D (cmdline-bugmail) >>
+>> endobj
+1862 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 380.0101 538.9788 390.914]
+/Subtype /Link
+/A << /S /GoTo /D (cmdline-bugmail) >>
+>> endobj
+1863 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [70.7348 366.7049 239.1315 375.6812]
+/Subtype /Link
+/A << /S /GoTo /D (install-perlmodules-manual) >>
+>> endobj
+1864 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 366.7049 538.9788 375.6812]
+/Subtype /Link
+/A << /S /GoTo /D (install-perlmodules-manual) >>
+>> endobj
+1865 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 351.2628 162.7788 360.2291]
+/Subtype /Link
+/A << /S /GoTo /D (modules-manual-instructions) >>
+>> endobj
+1866 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 351.2628 538.9788 360.2291]
+/Subtype /Link
+/A << /S /GoTo /D (modules-manual-instructions) >>
+>> endobj
+1867 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 338.3114 198.7739 347.2776]
+/Subtype /Link
+/A << /S /GoTo /D (modules-manual-download) >>
+>> endobj
+1868 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 338.3114 538.9788 347.2776]
+/Subtype /Link
+/A << /S /GoTo /D (modules-manual-download) >>
+>> endobj
+1869 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 323.4223 187.9643 334.3262]
+/Subtype /Link
+/A << /S /GoTo /D (modules-manual-optional) >>
+>> endobj
+1870 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 323.4223 538.9788 334.3262]
+/Subtype /Link
+/A << /S /GoTo /D (modules-manual-optional) >>
+>> endobj
+1871 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [70.7348 310.1172 231.092 319.0934]
+/Subtype /Link
+/A << /S /GoTo /D (gfdl) >>
+>> endobj
+1872 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 310.1172 538.9788 319.0934]
+/Subtype /Link
+/A << /S /GoTo /D (gfdl) >>
+>> endobj
+1873 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 294.675 144.2287 303.6413]
+/Subtype /Link
+/A << /S /GoTo /D (gfdl-0) >>
+>> endobj
+1874 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 294.675 538.9788 303.6413]
+/Subtype /Link
+/A << /S /GoTo /D (gfdl-0) >>
+>> endobj
+1875 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 279.7859 218.9576 290.6899]
+/Subtype /Link
+/A << /S /GoTo /D (gfdl-1) >>
+>> endobj
+1876 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 279.7859 538.9788 290.6899]
+/Subtype /Link
+/A << /S /GoTo /D (gfdl-1) >>
+>> endobj
+1877 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 266.8345 179.8349 277.7384]
+/Subtype /Link
+/A << /S /GoTo /D (gfdl-2) >>
+>> endobj
+1878 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [522.0424 266.8345 538.9788 277.7384]
+/Subtype /Link
+/A << /S /GoTo /D (gfdl-2) >>
+>> endobj
+1879 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 253.8831 188.4227 264.787]
+/Subtype /Link
+/A << /S /GoTo /D (gfdl-3) >>
+>> endobj
+1880 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [522.0424 253.8831 538.9788 264.787]
+/Subtype /Link
+/A << /S /GoTo /D (gfdl-3) >>
+>> endobj
+1881 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 242.9889 161.952 251.8356]
+/Subtype /Link
+/A << /S /GoTo /D (gfdl-4) >>
+>> endobj
+1882 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [522.0424 242.9889 538.9788 251.8356]
+/Subtype /Link
+/A << /S /GoTo /D (gfdl-4) >>
+>> endobj
+1883 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 227.9802 199.3117 238.8841]
+/Subtype /Link
+/A << /S /GoTo /D (gfdl-5) >>
+>> endobj
+1884 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [522.0424 227.9802 538.9788 238.8841]
+/Subtype /Link
+/A << /S /GoTo /D (gfdl-5) >>
+>> endobj
+1885 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 217.0861 210.6488 225.9327]
+/Subtype /Link
+/A << /S /GoTo /D (gfdl-6) >>
+>> endobj
+1886 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [522.0424 217.0861 538.9788 225.9327]
+/Subtype /Link
+/A << /S /GoTo /D (gfdl-6) >>
+>> endobj
+1887 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 202.0773 256.3975 212.9813]
+/Subtype /Link
+/A << /S /GoTo /D (gfdl-7) >>
+>> endobj
+1888 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [522.0424 202.0773 538.9788 212.9813]
+/Subtype /Link
+/A << /S /GoTo /D (gfdl-7) >>
+>> endobj
+1889 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 191.1832 151.6308 200.0298]
+/Subtype /Link
+/A << /S /GoTo /D (gfdl-8) >>
+>> endobj
+1890 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [522.0424 191.1832 538.9788 200.0298]
+/Subtype /Link
+/A << /S /GoTo /D (gfdl-8) >>
+>> endobj
+1891 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 178.1122 155.1576 187.0784]
+/Subtype /Link
+/A << /S /GoTo /D (gfdl-9) >>
+>> endobj
+1892 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [522.0424 178.1122 538.9788 187.0784]
+/Subtype /Link
+/A << /S /GoTo /D (gfdl-9) >>
+>> endobj
+1893 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 165.2803 240.2873 174.127]
+/Subtype /Link
+/A << /S /GoTo /D (gfdl-10) >>
+>> endobj
+1894 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [522.0424 165.2803 538.9788 174.127]
+/Subtype /Link
+/A << /S /GoTo /D (gfdl-10) >>
+>> endobj
+1895 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [94.6451 150.2716 272.646 161.1755]
+/Subtype /Link
+/A << /S /GoTo /D (gfdl-howto) >>
+>> endobj
+1896 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [522.0424 150.2716 538.9788 161.1755]
+/Subtype /Link
+/A << /S /GoTo /D (gfdl-howto) >>
+>> endobj
+1897 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [70.7348 135.0636 110.3657 145.9427]
+/Subtype /Link
+/A << /S /GoTo /D (glossary) >>
+>> endobj
+1898 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [522.0424 135.0636 538.9788 145.9427]
+/Subtype /Link
+/A << /S /GoTo /D (glossary) >>
+>> endobj
+1812 0 obj <<
+/D [1810 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+1809 0 obj <<
+/Font << /F27 1222 0 R /F32 1230 0 R /F35 1604 0 R /F33 1322 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+1945 0 obj <<
+/Length 1860
+/Filter /FlateDecode
+>>
+stream
+xڍKk\G,:~l I d]"88ק$u]>/KGS%F#ͽn^[ %Zo|WaFr.P?ޕ,y{bEjvaS>ן^_ob|ǿx盟xV6(%df =FGء(w _Їބw?盷ӧӇw2ՃŞF\܋;.0U!wNgںk%i3d5 #Zwrrutv+ mjՅ>ؙ4uwW?e!BWїV|![ \R`񙆸n5pMg'NJWVĎ}![ \2;n5pَ'$>׭g<GaǾxrWv+ qjر4uwW$3 qzƋl+4/.ɃJC\l' v+ qjಝ4uر4/. }׭.JC\xwy$z3 qzƛa;F-c_iV/%%LC\lz3 qzƻl'Wv+KvRcǾ׭.ۑ)LC\l'3 qzƇlv+K''?ر4uׇgǾ׭]LC\-i_[ \Scf񙆸n5pNIge;`3 qzƃl'ev+KvgQ+ iiȲPؙ4uj|g>׭g<z{gѺӐLC[xq3 qj:X9 iyl^id_Z \VS<;󕆸n5pYMfgҪӐe3)Tih3_0WV~CZulƳ/4uwFLC\]}pYi_Z <
+;n5jWe;~L_i3^e;c_i_Z \zd񙆸n5pNWe;aci3d;c_i_Z \=;n5 + qj4uޕٱ4/JC\ldv+ qjಝ;4u!D+KvbeǾ׭^OcǾ׭.LC\ˣ4轳LC\xq3 qj͍4uʞl3 qjಝٱ4uvR>׭g<vbbǾl'iVX|![ >
+=n=ɻ+;F=x![ T;n5pz
+CZu,ɝJ#jಚ4ؙ4ujK?e!`e-iH3^d/N#jO+ iiٙ4u7:׭gzF4ZeO(ihVՔ|![ \V0UYn,^id_Z \Vrve5=x![ \;=n=ݻ0KsvVvl❆n5Bg/iVo6l❆n=CS tvVn5pNf&iVD}![xȞM
+endobj
+1944 0 obj <<
+/Type /Page
+/Contents 1945 0 R
+/Resources 1943 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 1899 0 R
+/Annots [ 1947 0 R 1948 0 R ]
+>> endobj
+1947 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [70.7348 676.8022 201.5132 687.7061]
+/Subtype /Link
+/A << /S /GoTo /D (lifecycle-image) >>
+>> endobj
+1948 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [527.0237 676.8022 538.9788 687.7061]
+/Subtype /Link
+/A << /S /GoTo /D (lifecycle-image) >>
+>> endobj
+1946 0 obj <<
+/D [1944 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+10 0 obj <<
+/D [1944 0 R /XYZ 214.0665 703.236 null]
+>> endobj
+1943 0 obj <<
+/Font << /F23 1214 0 R /F27 1222 0 R /F33 1322 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+1952 0 obj <<
+/Length 2298
+/Filter /FlateDecode
+>>
+stream
+xڕَ}yZuyid" H@KliITDi<_hnȪbX7Do8,6IagMվnz"4$s,`٦e߬L^߿{ln(̳28mmd T?aN2Ci8LWEY\# "/cz6oli0=(CFc;aWlm`nIf6!nVw#<=
+>{oLnz+ ODp,9?JwNv8;٢ŌB:r6|^ iw@]!)lT>GT<Y8?3f<2CJޡ
+e>gjɧZ ѕ /֒߳5@uQ5G`KI`ԋhiGMs1PyYgU!:^j ( /8juM Y87;SHz SX*dnCp5
+NCp} /"m~X6dKϔv<JṙgIn;9Wu}-)jɨJ(=MTcGGSwC\X3d~d{7Vw) îw\8N !#tbYx*!` +(Yڕ_..<ðgzdk&`#>G$AGCybo_5U}s=yly}N|ZeMK3y/~,Ŧmr5ަ j^kS(
+к#"|ϗ?@]/ hU-#eQZB
+endobj
+1951 0 obj <<
+/Type /Page
+/Contents 1952 0 R
+/Resources 1950 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 1899 0 R
+/Annots [ 1956 0 R ]
+>> endobj
+1956 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [370.6589 581.7312 416.7299 591.744]
+/Subtype /Link
+/A << /S /GoTo /D (gfdl) >>
+>> endobj
+1323 0 obj <<
+/D [1951 0 R /XYZ 71.731 718.3063 null]
+>> endobj
+14 0 obj <<
+/D [1951 0 R /XYZ 350.6594 703.236 null]
+>> endobj
+1324 0 obj <<
+/D [1951 0 R /XYZ 71.731 692.504 null]
+>> endobj
+18 0 obj <<
+/D [1951 0 R /XYZ 285.3895 651.1593 null]
+>> endobj
+1953 0 obj <<
+/D [1951 0 R /XYZ 71.731 638.7213 null]
+>> endobj
+1954 0 obj <<
+/D [1951 0 R /XYZ 71.731 627.4433 null]
+>> endobj
+1955 0 obj <<
+/D [1951 0 R /XYZ 71.731 617.4806 null]
+>> endobj
+1957 0 obj <<
+/D [1951 0 R /XYZ 71.731 577.7461 null]
+>> endobj
+1325 0 obj <<
+/D [1951 0 R /XYZ 71.731 546.6463 null]
+>> endobj
+22 0 obj <<
+/D [1951 0 R /XYZ 191.9617 503.5488 null]
+>> endobj
+1958 0 obj <<
+/D [1951 0 R /XYZ 71.731 494.726 null]
+>> endobj
+1959 0 obj <<
+/D [1951 0 R /XYZ 71.731 448.9486 null]
+>> endobj
+1960 0 obj <<
+/D [1951 0 R /XYZ 71.731 405.113 null]
+>> endobj
+1326 0 obj <<
+/D [1951 0 R /XYZ 71.731 348.326 null]
+>> endobj
+26 0 obj <<
+/D [1951 0 R /XYZ 216.7521 305.2285 null]
+>> endobj
+1961 0 obj <<
+/D [1951 0 R /XYZ 71.731 296.4057 null]
+>> endobj
+1962 0 obj <<
+/D [1951 0 R /XYZ 71.731 276.5312 null]
+>> endobj
+1963 0 obj <<
+/D [1951 0 R /XYZ 294.1837 265.7366 null]
+>> endobj
+1964 0 obj <<
+/D [1951 0 R /XYZ 71.731 245.647 null]
+>> endobj
+1965 0 obj <<
+/D [1951 0 R /XYZ 453.4051 234.8524 null]
+>> endobj
+1966 0 obj <<
+/D [1951 0 R /XYZ 454.1572 208.9495 null]
+>> endobj
+1327 0 obj <<
+/D [1951 0 R /XYZ 71.731 188.86 null]
+>> endobj
+30 0 obj <<
+/D [1951 0 R /XYZ 164.538 145.7625 null]
+>> endobj
+1967 0 obj <<
+/D [1951 0 R /XYZ 71.731 136.9397 null]
+>> endobj
+1968 0 obj <<
+/D [1951 0 R /XYZ 71.731 96.1436 null]
+>> endobj
+1969 0 obj <<
+/D [1951 0 R /XYZ 71.731 48.8169 null]
+>> endobj
+1950 0 obj <<
+/Font << /F23 1214 0 R /F27 1222 0 R /F33 1322 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+1972 0 obj <<
+/Length 2059
+/Filter /FlateDecode
+>>
+stream
+xڭXoF~(Dkp;I}MKtõpIR,KaA8H0J( ߜEޝaO24)-A.` 4H(A)iX)Dv6'<
+1r˺pQ(Fzg7lNpQG*dp(E*At]!gNgkz%JוhA
+"cfD *Mb,V(7eG,F)S+{%,B G١ IJ(dowC$O%>MQ(wW{䌉l›ލ;e߮X\o,x; t!7Hta9KN98`U|1KPWG,z#`|=0aVSTCPbxe GF()ǡTDY~uf,fRl,fÍ+PyGϟ~pp7ЯȪ,AJ0F-@=hg8 ײ.N
+2nx,{]g|=j=.)۵y N(%
+ONj}26ip8O""[W{w+7s}\ yib8e2Xܓ #d'n0R{5JNjFߚ ePg$ŷ3E|a)u_B"D7g[ScLCuH3!B2uQy-5)Jq>yݝS
+tdN'~H?pK~~endstream
+endobj
+1971 0 obj <<
+/Type /Page
+/Contents 1972 0 R
+/Resources 1970 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 1899 0 R
+>> endobj
+1973 0 obj <<
+/D [1971 0 R /XYZ 71.731 708.2048 null]
+>> endobj
+1974 0 obj <<
+/D [1971 0 R /XYZ 91.6563 690.4109 null]
+>> endobj
+1975 0 obj <<
+/D [1971 0 R /XYZ 71.731 678.2915 null]
+>> endobj
+1976 0 obj <<
+/D [1971 0 R /XYZ 138.8487 667.4969 null]
+>> endobj
+1977 0 obj <<
+/D [1971 0 R /XYZ 71.731 665.34 null]
+>> endobj
+1978 0 obj <<
+/D [1971 0 R /XYZ 91.6563 649.5641 null]
+>> endobj
+1979 0 obj <<
+/D [1971 0 R /XYZ 71.731 624.4932 null]
+>> endobj
+1980 0 obj <<
+/D [1971 0 R /XYZ 137.3147 613.6986 null]
+>> endobj
+1981 0 obj <<
+/D [1971 0 R /XYZ 71.731 612.3179 null]
+>> endobj
+1982 0 obj <<
+/D [1971 0 R /XYZ 91.6563 595.7659 null]
+>> endobj
+1983 0 obj <<
+/D [1971 0 R /XYZ 71.731 583.6464 null]
+>> endobj
+1984 0 obj <<
+/D [1971 0 R /XYZ 136.5079 572.8518 null]
+>> endobj
+1985 0 obj <<
+/D [1971 0 R /XYZ 71.731 572.7128 null]
+>> endobj
+1986 0 obj <<
+/D [1971 0 R /XYZ 91.6563 554.919 null]
+>> endobj
+1987 0 obj <<
+/D [1971 0 R /XYZ 71.731 542.7996 null]
+>> endobj
+1988 0 obj <<
+/D [1971 0 R /XYZ 128.5776 532.005 null]
+>> endobj
+1989 0 obj <<
+/D [1971 0 R /XYZ 71.731 530.6242 null]
+>> endobj
+1990 0 obj <<
+/D [1971 0 R /XYZ 91.6563 514.0722 null]
+>> endobj
+1991 0 obj <<
+/D [1971 0 R /XYZ 71.731 489.0013 null]
+>> endobj
+1992 0 obj <<
+/D [1971 0 R /XYZ 145.3244 478.2067 null]
+>> endobj
+1993 0 obj <<
+/D [1971 0 R /XYZ 71.731 476.0499 null]
+>> endobj
+1994 0 obj <<
+/D [1971 0 R /XYZ 91.6563 460.274 null]
+>> endobj
+1995 0 obj <<
+/D [1971 0 R /XYZ 71.731 448.1545 null]
+>> endobj
+1996 0 obj <<
+/D [1971 0 R /XYZ 122.2909 437.3599 null]
+>> endobj
+1997 0 obj <<
+/D [1971 0 R /XYZ 71.731 435.9791 null]
+>> endobj
+1998 0 obj <<
+/D [1971 0 R /XYZ 91.6563 419.4271 null]
+>> endobj
+1999 0 obj <<
+/D [1971 0 R /XYZ 71.731 401.3948 null]
+>> endobj
+2000 0 obj <<
+/D [1971 0 R /XYZ 450.945 388.5429 null]
+>> endobj
+2001 0 obj <<
+/D [1971 0 R /XYZ 518.6154 388.5429 null]
+>> endobj
+2002 0 obj <<
+/D [1971 0 R /XYZ 108.3457 375.5915 null]
+>> endobj
+2003 0 obj <<
+/D [1971 0 R /XYZ 175.2191 375.5915 null]
+>> endobj
+2004 0 obj <<
+/D [1971 0 R /XYZ 228.8127 375.5915 null]
+>> endobj
+2005 0 obj <<
+/D [1971 0 R /XYZ 281.8583 375.5915 null]
+>> endobj
+2006 0 obj <<
+/D [1971 0 R /XYZ 359.5411 375.5915 null]
+>> endobj
+2007 0 obj <<
+/D [1971 0 R /XYZ 429.4832 375.5915 null]
+>> endobj
+2008 0 obj <<
+/D [1971 0 R /XYZ 477.5574 375.5915 null]
+>> endobj
+2009 0 obj <<
+/D [1971 0 R /XYZ 71.731 362.6401 null]
+>> endobj
+2010 0 obj <<
+/D [1971 0 R /XYZ 140.4925 362.6401 null]
+>> endobj
+2011 0 obj <<
+/D [1971 0 R /XYZ 197.2193 362.6401 null]
+>> endobj
+2012 0 obj <<
+/D [1971 0 R /XYZ 71.731 356.2192 null]
+>> endobj
+2013 0 obj <<
+/D [1971 0 R /XYZ 419.446 344.7073 null]
+>> endobj
+1328 0 obj <<
+/D [1971 0 R /XYZ 71.731 298.7149 null]
+>> endobj
+34 0 obj <<
+/D [1971 0 R /XYZ 297.7505 255.6174 null]
+>> endobj
+2014 0 obj <<
+/D [1971 0 R /XYZ 71.731 255.4023 null]
+>> endobj
+2015 0 obj <<
+/D [1971 0 R /XYZ 71.731 246.7946 null]
+>> endobj
+2016 0 obj <<
+/D [1971 0 R /XYZ 71.731 231.9014 null]
+>> endobj
+2017 0 obj <<
+/D [1971 0 R /XYZ 71.731 216.9575 null]
+>> endobj
+2018 0 obj <<
+/D [1971 0 R /XYZ 71.731 216.9575 null]
+>> endobj
+1970 0 obj <<
+/Font << /F33 1322 0 R /F27 1222 0 R /F35 1604 0 R /F23 1214 0 R /F44 2021 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+2024 0 obj <<
+/Length 1029
+/Filter /FlateDecode
+>>
+stream
+xڥVm4~E$qm{'TC(M6"ĝ6~=DZӥ/l؏syK ?(jDId[/HdAD#-LֻS,Q\<YoO_Ι~QSxGC5c5??=O܂)s 'ԵtH'
+Kf(smg}י/rtd#͙qZm/g 2'Nd#Nrǐ+8& AZS&ٝy15#jZ=Cg'LIIy(zH%AKN|$mHF9H3BpkSlTp}0b¤,\썋ʡ)!@:As!,>#gH*>dJݨIeSUP6Ml>|EY)H)off> h^ئ)m q6{ߙmd3[e٤PuEдmń":7훢g^Fwm(cæa6L g|\rdC Z>~qf
+eUlj{,Wo^( 6v8UM/1H&C{ٯFш\'T6]?ּ类+n|{澩a9Ƿnĉ]t\XjuC=:x
+mM<Vf_c$'f]}_tHSH$̅KB R2\[[}eE1 bl
+vHi΃-W8"Fo?Ws(1􀁗݇rI1q}cw*YPqTyjTZ a;<%\}׏#fd7|sEyV9 fAAcZ`6'
+wx>8 .e<Fp+#3BPF=4ζ
+endobj
+2023 0 obj <<
+/Type /Page
+/Contents 2024 0 R
+/Resources 2022 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 1899 0 R
+/Annots [ 2025 0 R ]
+>> endobj
+2025 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [309.34 515.1637 344.5475 532.1001]
+/Subtype /Link
+/A << /S /GoTo /D (gloss-bugzilla) >>
+>> endobj
+2026 0 obj <<
+/D [2023 0 R /XYZ 71.731 478.3019 null]
+>> endobj
+2027 0 obj <<
+/D [2023 0 R /XYZ 433.4537 455.3878 null]
+>> endobj
+2022 0 obj <<
+/Font << /F33 1322 0 R /F23 1214 0 R /F27 1222 0 R /F44 2021 0 R /F35 1604 0 R /F32 1230 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+2031 0 obj <<
+/Length 2170
+/Filter /FlateDecode
+>>
+stream
+xڭْ}o+W9<u[f^d+̓ڦmeѽޯ@PS)?A
+U1SF? Pos)YHZcNVcr1s&,v
+Ǻmo(0d*ǯо'Shomk뾣ɵ#!]"=}EE_J60|Y_ų i ŁB 1Yz|}^ll9) $ vB(f e̤IBZI4^A.[4v B^&=A)*0Yinb\I%F)!S[.5@m \J]A9Uo<.xlE N8Ls5笁]Pup-1{AzYb!ŕ88obqSC"pl[Z̃G7%  `R5RI7_
+1|&51Z\hC4Fw /q`9Lf6\
+f>
++GfqȧBz x*۹w.oxy
+p$>~ Zrb7@˲F^ϗ.͡bÌ
+s 7
+gYt,<߾_Xsh}[Om^")>J)K#Jn{D%wz0'4`Hendstream
+endobj
+2030 0 obj <<
+/Type /Page
+/Contents 2031 0 R
+/Resources 2029 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 1899 0 R
+/Annots [ 2039 0 R 2040 0 R 2048 0 R 2050 0 R 2052 0 R 2054 0 R 2056 0 R 2058 0 R ]
+>> endobj
+2039 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [507.0988 565.2764 538.9788 576.1803]
+/Subtype /Link
+/A << /S /GoTo /D (os-specific) >>
+>> endobj
+2040 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [70.7348 552.325 85.1806 563.2289]
+/Subtype /Link
+/A << /S /GoTo /D (os-specific) >>
+>> endobj
+2048 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [88.6675 362.7319 134.1063 373.2623]
+/Subtype /Link
+/A << /S /GoTo /D (install-perl) >>
+>> endobj
+2050 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [88.6675 344.4256 192.198 355.3295]
+/Subtype /Link
+/A << /S /GoTo /D (install-database) >>
+>> endobj
+2052 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [88.6675 328.5501 167.1723 337.3968]
+/Subtype /Link
+/A << /S /GoTo /D (install-webserver) >>
+>> endobj
+2054 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [88.6675 308.5601 151.8196 319.464]
+/Subtype /Link
+/A << /S /GoTo /D (install-bzfiles) >>
+>> endobj
+2056 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [88.6675 292.6846 170.36 301.5313]
+/Subtype /Link
+/A << /S /GoTo /D (install-perlmodules) >>
+>> endobj
+2058 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [91.375 272.6946 210.0889 283.5985]
+/Subtype /Link
+/A << /S /GoTo /D (install-MTA) >>
+>> endobj
+1329 0 obj <<
+/D [2030 0 R /XYZ 71.731 718.3063 null]
+>> endobj
+38 0 obj <<
+/D [2030 0 R /XYZ 354.1294 703.236 null]
+>> endobj
+1330 0 obj <<
+/D [2030 0 R /XYZ 71.731 692.1839 null]
+>> endobj
+42 0 obj <<
+/D [2030 0 R /XYZ 196.1106 651.1593 null]
+>> endobj
+2032 0 obj <<
+/D [2030 0 R /XYZ 71.731 650.9442 null]
+>> endobj
+2033 0 obj <<
+/D [2030 0 R /XYZ 71.731 632.3738 null]
+>> endobj
+2034 0 obj <<
+/D [2030 0 R /XYZ 187.6288 620.9326 null]
+>> endobj
+2038 0 obj <<
+/D [2030 0 R /XYZ 71.731 581.3809 null]
+>> endobj
+2041 0 obj <<
+/D [2030 0 R /XYZ 71.731 548.3399 null]
+>> endobj
+2042 0 obj <<
+/D [2030 0 R /XYZ 71.731 511.5429 null]
+>> endobj
+2043 0 obj <<
+/D [2030 0 R /XYZ 118.5554 472.9789 null]
+>> endobj
+2044 0 obj <<
+/D [2030 0 R /XYZ 71.731 431.0456 null]
+>> endobj
+2045 0 obj <<
+/D [2030 0 R /XYZ 71.731 404.575 null]
+>> endobj
+2046 0 obj <<
+/D [2030 0 R /XYZ 71.731 391.25 null]
+>> endobj
+2047 0 obj <<
+/D [2030 0 R /XYZ 71.731 381.2874 null]
+>> endobj
+2049 0 obj <<
+/D [2030 0 R /XYZ 71.731 363.7282 null]
+>> endobj
+2051 0 obj <<
+/D [2030 0 R /XYZ 71.731 345.4218 null]
+>> endobj
+2053 0 obj <<
+/D [2030 0 R /XYZ 71.731 329.5464 null]
+>> endobj
+2055 0 obj <<
+/D [2030 0 R /XYZ 71.731 309.5563 null]
+>> endobj
+2057 0 obj <<
+/D [2030 0 R /XYZ 71.731 293.6809 null]
+>> endobj
+2059 0 obj <<
+/D [2030 0 R /XYZ 71.731 261.113 null]
+>> endobj
+1331 0 obj <<
+/D [2030 0 R /XYZ 71.731 242.8067 null]
+>> endobj
+46 0 obj <<
+/D [2030 0 R /XYZ 138.2961 205.5911 null]
+>> endobj
+2060 0 obj <<
+/D [2030 0 R /XYZ 71.731 198.2388 null]
+>> endobj
+2061 0 obj <<
+/D [2030 0 R /XYZ 71.731 175.4044 null]
+>> endobj
+2062 0 obj <<
+/D [2030 0 R /XYZ 71.731 135.9523 null]
+>> endobj
+2063 0 obj <<
+/D [2030 0 R /XYZ 165.1421 110.0494 null]
+>> endobj
+2029 0 obj <<
+/Font << /F23 1214 0 R /F44 2021 0 R /F48 2037 0 R /F27 1222 0 R /F35 1604 0 R /F33 1322 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+2066 0 obj <<
+/Length 1519
+/Filter /FlateDecode
+>>
+stream
+xXs6 _M Ǯne֬ݺfl^eɕd_?d)vrK_v
+c=IU@Y۸ڇsۖk SZ䱲!mq8:}A(ǚsd$ˊr>XC::\$nE۽fS^)@9IlFJ$*#[ۇS9_$RFw"1_CH0zCVGbeDGBT޼#Uڑ-@fu}R/}QPX]{Iwv/IX[A
+r
+koz9S?&]ABul T`s|B֎u, 0
+]E* F(W"n}U`6e/qDT dՍ,HYDaKUL4hZp:@&I b'-[?=~ÀZȀΡvpْ81aO (Kv!뎨 _cQ궘s^zup%jB[kZ0L!+0u3W(NrSTkNGJL-?ww}=tfk\1(wM,7K>֕!K<"t4L.4Y՝_w¬$* GF4{  Ӏ8Ȑpj0c`nj
+k}
+(@<PBHfL
+endobj
+2065 0 obj <<
+/Type /Page
+/Contents 2066 0 R
+/Resources 2064 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 2086 0 R
+>> endobj
+1332 0 obj <<
+/D [2065 0 R /XYZ 71.731 718.3063 null]
+>> endobj
+50 0 obj <<
+/D [2065 0 R /XYZ 227.213 707.8408 null]
+>> endobj
+2067 0 obj <<
+/D [2065 0 R /XYZ 71.731 697.4758 null]
+>> endobj
+1333 0 obj <<
+/D [2065 0 R /XYZ 71.731 672.608 null]
+>> endobj
+54 0 obj <<
+/D [2065 0 R /XYZ 156.1213 640.2941 null]
+>> endobj
+2068 0 obj <<
+/D [2065 0 R /XYZ 71.731 631.8419 null]
+>> endobj
+2069 0 obj <<
+/D [2065 0 R /XYZ 71.731 611.3029 null]
+>> endobj
+2070 0 obj <<
+/D [2065 0 R /XYZ 71.731 571.8508 null]
+>> endobj
+2071 0 obj <<
+/D [2065 0 R /XYZ 367.4271 558.8993 null]
+>> endobj
+2072 0 obj <<
+/D [2065 0 R /XYZ 71.731 543.7911 null]
+>> endobj
+2073 0 obj <<
+/D [2065 0 R /XYZ 71.731 528.8471 null]
+>> endobj
+2074 0 obj <<
+/D [2065 0 R /XYZ 363.9817 519.3477 null]
+>> endobj
+2075 0 obj <<
+/D [2065 0 R /XYZ 331.2343 496.0351 null]
+>> endobj
+2076 0 obj <<
+/D [2065 0 R /XYZ 71.731 468.1397 null]
+>> endobj
+1334 0 obj <<
+/D [2065 0 R /XYZ 71.731 424.2045 null]
+>> endobj
+58 0 obj <<
+/D [2065 0 R /XYZ 183.5462 388.8371 null]
+>> endobj
+2077 0 obj <<
+/D [2065 0 R /XYZ 71.731 380.1996 null]
+>> endobj
+2078 0 obj <<
+/D [2065 0 R /XYZ 71.731 359.8459 null]
+>> endobj
+2079 0 obj <<
+/D [2065 0 R /XYZ 71.731 320.3937 null]
+>> endobj
+2080 0 obj <<
+/D [2065 0 R /XYZ 364.8776 307.4423 null]
+>> endobj
+2081 0 obj <<
+/D [2065 0 R /XYZ 71.731 287.3527 null]
+>> endobj
+1335 0 obj <<
+/D [2065 0 R /XYZ 71.731 245.5744 null]
+>> endobj
+62 0 obj <<
+/D [2065 0 R /XYZ 151.9129 210.2069 null]
+>> endobj
+2082 0 obj <<
+/D [2065 0 R /XYZ 71.731 204.08 null]
+>> endobj
+2083 0 obj <<
+/D [2065 0 R /XYZ 71.731 181.2157 null]
+>> endobj
+2084 0 obj <<
+/D [2065 0 R /XYZ 71.731 139.6068 null]
+>> endobj
+2085 0 obj <<
+/D [2065 0 R /XYZ 370.1552 128.8122 null]
+>> endobj
+2064 0 obj <<
+/Font << /F33 1322 0 R /F23 1214 0 R /F27 1222 0 R /F35 1604 0 R /F44 2021 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+2089 0 obj <<
+/Length 2593
+/Filter /FlateDecode
+>>
+stream
+xڕY{۸?@ Ĵ(R"ܥ)pM,.(ezM,z)K(Xp83P
+JXaWhynyg*.ߖcud[{-|zwvfX.J~o\l˝wM^~4:@rәS]dL !yN4uۺZl(j!5EbF2V*R( $]~\)Pb˃ Z37BVp(wv/xh GleDŽ) NN/6?B70X  G|]ZK W<Aʾ1f[,- 󜻧t؃q|FKbg^w("?awM_Ux)pCH8^DY 8Ǎk=filoU(vIDR"&-ۢ%Ddz+<F˿}򖆋1j'6hO-P4"%5; 2d<ަ<U;R(>;d@j[u
+b`˩VWfbgR(߾Dž؂"Nܧ0T`iA`4;ʺƍSգ)ẖB*Hء`rV s Gz05nVj*Z@CMfsnf3a%PceBS鸭K[@fP #I'{x0pmis7eb:#@
+!/AJ%DC)ծdP*Iة1
+SxH(R zdA&Av9&F*B!U{k |QeЋE^pGI>_$\@-k(!OSXawWX>BڭیY'&EiCJT>J!鬷vt 
+4A-LӝGx=R}ayM{LyYfMn 92H'B$c*@eXgNΖWU.78DY5]9OBA-͏W .^\Y\f+5>
+!v\!4%~C!r)P`in#Ϛs~_ n*
+C
+endobj
+2088 0 obj <<
+/Type /Page
+/Contents 2089 0 R
+/Resources 2087 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 2086 0 R
+/Annots [ 2093 0 R 2110 0 R ]
+>> endobj
+2093 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [486.8903 586.9141 506.5463 597.8181]
+/Subtype /Link
+/A << /S /GoTo /D (gloss-cgi) >>
+>> endobj
+2110 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [145.4339 220.9943 192.2579 231.8982]
+/Subtype /Link
+/A << /S /GoTo /D (configuration) >>
+>> endobj
+2090 0 obj <<
+/D [2088 0 R /XYZ 71.731 718.3063 null]
+>> endobj
+1336 0 obj <<
+/D [2088 0 R /XYZ 71.731 667.3973 null]
+>> endobj
+66 0 obj <<
+/D [2088 0 R /XYZ 190.1856 628.1245 null]
+>> endobj
+2091 0 obj <<
+/D [2088 0 R /XYZ 71.731 620.7722 null]
+>> endobj
+2092 0 obj <<
+/D [2088 0 R /XYZ 71.731 600.8618 null]
+>> endobj
+2094 0 obj <<
+/D [2088 0 R /XYZ 435.9402 551.2129 null]
+>> endobj
+2095 0 obj <<
+/D [2088 0 R /XYZ 71.731 531.1234 null]
+>> endobj
+2096 0 obj <<
+/D [2088 0 R /XYZ 384.3864 520.3288 null]
+>> endobj
+1337 0 obj <<
+/D [2088 0 R /XYZ 71.731 513.1906 null]
+>> endobj
+70 0 obj <<
+/D [2088 0 R /XYZ 166.6153 475.9751 null]
+>> endobj
+2097 0 obj <<
+/D [2088 0 R /XYZ 71.731 465.6101 null]
+>> endobj
+2098 0 obj <<
+/D [2088 0 R /XYZ 71.731 465.6101 null]
+>> endobj
+2099 0 obj <<
+/D [2088 0 R /XYZ 317.0127 442.8991 null]
+>> endobj
+2100 0 obj <<
+/D [2088 0 R /XYZ 366.5397 442.8991 null]
+>> endobj
+2101 0 obj <<
+/D [2088 0 R /XYZ 267.9558 429.9477 null]
+>> endobj
+2102 0 obj <<
+/D [2088 0 R /XYZ 71.731 414.8394 null]
+>> endobj
+2103 0 obj <<
+/D [2088 0 R /XYZ 118.5554 379.2881 null]
+>> endobj
+2104 0 obj <<
+/D [2088 0 R /XYZ 393.1692 367.811 null]
+>> endobj
+2105 0 obj <<
+/D [2088 0 R /XYZ 273.304 356.1547 null]
+>> endobj
+2106 0 obj <<
+/D [2088 0 R /XYZ 71.731 334.2344 null]
+>> endobj
+2107 0 obj <<
+/D [2088 0 R /XYZ 202.34 314.5284 null]
+>> endobj
+1338 0 obj <<
+/D [2088 0 R /XYZ 71.731 307.3903 null]
+>> endobj
+74 0 obj <<
+/D [2088 0 R /XYZ 200.4719 270.1748 null]
+>> endobj
+2108 0 obj <<
+/D [2088 0 R /XYZ 71.731 262.8224 null]
+>> endobj
+2109 0 obj <<
+/D [2088 0 R /XYZ 303.3712 250.0502 null]
+>> endobj
+2111 0 obj <<
+/D [2088 0 R /XYZ 71.731 217.0092 null]
+>> endobj
+2112 0 obj <<
+/D [2088 0 R /XYZ 179.1881 206.2146 null]
+>> endobj
+2113 0 obj <<
+/D [2088 0 R /XYZ 71.731 181.1437 null]
+>> endobj
+2114 0 obj <<
+/D [2088 0 R /XYZ 71.731 181.1437 null]
+>> endobj
+2115 0 obj <<
+/D [2088 0 R /XYZ 71.731 160.3009 null]
+>> endobj
+2116 0 obj <<
+/D [2088 0 R /XYZ 71.731 160.3009 null]
+>> endobj
+2087 0 obj <<
+/Font << /F33 1322 0 R /F27 1222 0 R /F23 1214 0 R /F35 1604 0 R /F44 2021 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+2120 0 obj <<
+/Length 2518
+/Filter /FlateDecode
+>>
+stream
+xڕZr}PmBUYX/pRzk=xT2y%b %rIӍ(JH[~
+ ʜ)$ UV64sx Q02A2?~xΚ Fdݬ=<Oح1ϵP J3
+&UG8`JΉӚ[yn=3;-
++!7Z,LCo"g.ZAB(IaOYQ+bNtڃB
+vRTY1EmGh-fXwQ[p5nfaD& DĺEPl>PIE J68>v-+pfoi j܈;Myf<Kψk='daTr
+&).ċt^l&ߺ^ζ 4MkO0a҆<S8VnZ+iKwsT^:7#ئsJ6o
+2s$.+ﻁa:b9=wP ѻm:R q/OGX@$xVtkC[&d~q{z;!(bJLаqե^fxf-->kckB[wD"`10blqH%>Tv;`/&YL<")ȈɟaDG(<SzA7,P*1Ye̷-UQ̨铽OܙgbȄTdIΗ+iͅϛlBpDFq8by 7>$^;v׽.>pV,Q-hE gC&6iRHLwB϶9?ܐx X8*D¡b` гAlɋ*IU؁gT&Tq(20LRvI3ɤ"gRE: E
+qE@I
+=ۧ\_ҿ)j=|GѹAMPH.g` гmy (I!D"䙀E ѢE4*А煘гMB޾Կ=$}}Y}F"eocjE |~&tQj9R&)lj=N8)]Q7 Ӭ(9 
+&z y^z[~eq50W3gR5C!Y55Igu^;`eA.j@S[&EQ&9l#m4ѻU7ݱ1jDniEqEZQd<"z;ܾn+IIEZjB2'Cc^ $m|M%7() HAMHPFxh $г>v3DWΣ%g=9@GtLcr{ yz~:1Ɍ^Ş^MZUZO#xLXRmerџu (endstream
+endobj
+2119 0 obj <<
+/Type /Page
+/Contents 2120 0 R
+/Resources 2118 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 2086 0 R
+/Annots [ 2125 0 R 2127 0 R 2128 0 R ]
+>> endobj
+2125 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [418.7694 692.2392 480.8988 703.1431]
+/Subtype /Link
+/A << /S /GoTo /D (win32-perl-modules) >>
+>> endobj
+2127 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [497.6939 666.3363 538.9788 677.2402]
+/Subtype /Link
+/A << /S /GoTo /D (install-perlmodules-manual) >>
+>> endobj
+2128 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [70.7348 653.3849 79.3723 664.2888]
+/Subtype /Link
+/A << /S /GoTo /D (install-perlmodules-manual) >>
+>> endobj
+2121 0 obj <<
+/D [2119 0 R /XYZ 71.731 718.3063 null]
+>> endobj
+2122 0 obj <<
+/D [2119 0 R /XYZ 89.796 695.3923 null]
+>> endobj
+2123 0 obj <<
+/D [2119 0 R /XYZ 128.6955 695.3923 null]
+>> endobj
+2124 0 obj <<
+/D [2119 0 R /XYZ 240.639 695.3923 null]
+>> endobj
+2126 0 obj <<
+/D [2119 0 R /XYZ 362.1826 682.4408 null]
+>> endobj
+2129 0 obj <<
+/D [2119 0 R /XYZ 220.871 656.538 null]
+>> endobj
+2130 0 obj <<
+/D [2119 0 R /XYZ 71.731 644.4185 null]
+>> endobj
+2131 0 obj <<
+/D [2119 0 R /XYZ 71.731 644.4185 null]
+>> endobj
+2132 0 obj <<
+/D [2119 0 R /XYZ 71.731 633.5383 null]
+>> endobj
+2133 0 obj <<
+/D [2119 0 R /XYZ 71.731 613.613 null]
+>> endobj
+2134 0 obj <<
+/D [2119 0 R /XYZ 204.3747 591.6812 null]
+>> endobj
+2135 0 obj <<
+/D [2119 0 R /XYZ 465.9761 568.3686 null]
+>> endobj
+2136 0 obj <<
+/D [2119 0 R /XYZ 76.7123 540.0747 null]
+>> endobj
+2137 0 obj <<
+/D [2119 0 R /XYZ 71.731 520.1494 null]
+>> endobj
+2138 0 obj <<
+/D [2119 0 R /XYZ 140.0022 473.5243 null]
+>> endobj
+2139 0 obj <<
+/D [2119 0 R /XYZ 71.731 445.6289 null]
+>> endobj
+2140 0 obj <<
+/D [2119 0 R /XYZ 71.731 414.6452 null]
+>> endobj
+2141 0 obj <<
+/D [2119 0 R /XYZ 170.7984 401.7933 null]
+>> endobj
+2142 0 obj <<
+/D [2119 0 R /XYZ 71.731 394.6551 null]
+>> endobj
+2143 0 obj <<
+/D [2119 0 R /XYZ 89.6638 373.8979 null]
+>> endobj
+2144 0 obj <<
+/D [2119 0 R /XYZ 71.731 372.1147 null]
+>> endobj
+2145 0 obj <<
+/D [2119 0 R /XYZ 89.6638 355.9651 null]
+>> endobj
+2146 0 obj <<
+/D [2119 0 R /XYZ 71.731 354.1819 null]
+>> endobj
+2147 0 obj <<
+/D [2119 0 R /XYZ 89.6638 338.0324 null]
+>> endobj
+2148 0 obj <<
+/D [2119 0 R /XYZ 71.731 336.2492 null]
+>> endobj
+2149 0 obj <<
+/D [2119 0 R /XYZ 89.6638 320.0996 null]
+>> endobj
+2150 0 obj <<
+/D [2119 0 R /XYZ 71.731 318.3164 null]
+>> endobj
+2151 0 obj <<
+/D [2119 0 R /XYZ 89.6638 302.1669 null]
+>> endobj
+2152 0 obj <<
+/D [2119 0 R /XYZ 71.731 300.3837 null]
+>> endobj
+2153 0 obj <<
+/D [2119 0 R /XYZ 89.6638 284.2341 null]
+>> endobj
+2154 0 obj <<
+/D [2119 0 R /XYZ 71.731 282.0773 null]
+>> endobj
+2155 0 obj <<
+/D [2119 0 R /XYZ 89.6638 266.3014 null]
+>> endobj
+2156 0 obj <<
+/D [2119 0 R /XYZ 71.731 264.1446 null]
+>> endobj
+2157 0 obj <<
+/D [2119 0 R /XYZ 89.6638 248.3686 null]
+>> endobj
+2158 0 obj <<
+/D [2119 0 R /XYZ 71.731 246.2118 null]
+>> endobj
+2159 0 obj <<
+/D [2119 0 R /XYZ 89.6638 230.4359 null]
+>> endobj
+2160 0 obj <<
+/D [2119 0 R /XYZ 71.731 228.2791 null]
+>> endobj
+2161 0 obj <<
+/D [2119 0 R /XYZ 89.6638 212.5031 null]
+>> endobj
+2162 0 obj <<
+/D [2119 0 R /XYZ 71.731 210.7199 null]
+>> endobj
+2163 0 obj <<
+/D [2119 0 R /XYZ 89.6638 194.5704 null]
+>> endobj
+2164 0 obj <<
+/D [2119 0 R /XYZ 71.731 192.7871 null]
+>> endobj
+2165 0 obj <<
+/D [2119 0 R /XYZ 89.6638 176.6376 null]
+>> endobj
+2166 0 obj <<
+/D [2119 0 R /XYZ 71.731 174.4808 null]
+>> endobj
+2167 0 obj <<
+/D [2119 0 R /XYZ 89.6638 158.7049 null]
+>> endobj
+2168 0 obj <<
+/D [2119 0 R /XYZ 169.1445 140.7721 null]
+>> endobj
+2169 0 obj <<
+/D [2119 0 R /XYZ 71.731 133.634 null]
+>> endobj
+2170 0 obj <<
+/D [2119 0 R /XYZ 89.6638 112.8767 null]
+>> endobj
+2171 0 obj <<
+/D [2119 0 R /XYZ 71.731 110.7199 null]
+>> endobj
+2118 0 obj <<
+/Font << /F33 1322 0 R /F27 1222 0 R /F35 1604 0 R /F23 1214 0 R /F44 2021 0 R /F48 2037 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+2174 0 obj <<
+/Length 2323
+/Filter /FlateDecode
+>>
+stream
+xڵ[o8)(1+-3t2HlbYZWO?bI
+d_D^gaßrqcdn}z(JiV]/"fqD,m"߉[c-me{Wur84!YwZ>Yycx17rʵYBa1 m vĸöni*l}Y ѢkL<mCMг!<,Cb=44_?}Xd'㟶o'(:tP?qnUı(e@s ٪%|izU,:Eu"2gҵFu >v Z2/ hVa83C.45c{:*5qj.gP3гET?귇8n82*<7r<c=¿n^d[ɥ&a
+x*uTZ\@h gC2HJOHgCfNeP83Cgv
+
+^ #
+BA齨OW\6)بv`7N.*W2n;rmJG2>R-UtSƨ{dKC-/ɚϊ@#㨱_n QcODۈ)[ptQՃcB!iQH\PEh[z$RvnoUE%yO%r,-G}lTKy>'|D 5xS
+=  .LdL2~7ha 4o_ݪPdۊM/2Q-<RNi1BHu;tgV*Yl#9q9aN9Vdg ` Źӓ\ 3ZiWG=4/ H .I%'COfi1!эy7*ڥ
+綨jO֑"4SSgMp+, C/+/ ʪMjz:&Z(]'<hկ hRRIJb,tx96~הBFR< ]vzgEq/%l.tt!G*"gFEXswP-JnOp;X]ХΊܩ* 27ijߣ8HފPے.lhsl%Yr P?mendstream
+endobj
+2173 0 obj <<
+/Type /Page
+/Contents 2174 0 R
+/Resources 2172 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 2086 0 R
+/Annots [ 2221 0 R ]
+>> endobj
+2221 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [358.8765 186.1874 405.7271 196.662]
+/Subtype /Link
+/A << /S /GoTo /D (parameters) >>
+>> endobj
+2175 0 obj <<
+/D [2173 0 R /XYZ 89.6638 708.3437 null]
+>> endobj
+2176 0 obj <<
+/D [2173 0 R /XYZ 71.731 706.1869 null]
+>> endobj
+2177 0 obj <<
+/D [2173 0 R /XYZ 89.6638 690.4109 null]
+>> endobj
+2178 0 obj <<
+/D [2173 0 R /XYZ 71.731 688.2541 null]
+>> endobj
+2179 0 obj <<
+/D [2173 0 R /XYZ 89.6638 672.4782 null]
+>> endobj
+2180 0 obj <<
+/D [2173 0 R /XYZ 71.731 670.3214 null]
+>> endobj
+2181 0 obj <<
+/D [2173 0 R /XYZ 89.6638 654.5454 null]
+>> endobj
+2182 0 obj <<
+/D [2173 0 R /XYZ 71.731 652.3886 null]
+>> endobj
+2183 0 obj <<
+/D [2173 0 R /XYZ 89.6638 636.6127 null]
+>> endobj
+2184 0 obj <<
+/D [2173 0 R /XYZ 71.731 634.4559 null]
+>> endobj
+2185 0 obj <<
+/D [2173 0 R /XYZ 89.6638 618.6799 null]
+>> endobj
+2186 0 obj <<
+/D [2173 0 R /XYZ 71.731 616.5231 null]
+>> endobj
+2187 0 obj <<
+/D [2173 0 R /XYZ 89.6638 600.7472 null]
+>> endobj
+2188 0 obj <<
+/D [2173 0 R /XYZ 71.731 598.5904 null]
+>> endobj
+2189 0 obj <<
+/D [2173 0 R /XYZ 89.6638 582.8144 null]
+>> endobj
+2190 0 obj <<
+/D [2173 0 R /XYZ 71.731 580.6576 null]
+>> endobj
+2191 0 obj <<
+/D [2173 0 R /XYZ 89.6638 564.8817 null]
+>> endobj
+2192 0 obj <<
+/D [2173 0 R /XYZ 71.731 562.7248 null]
+>> endobj
+2193 0 obj <<
+/D [2173 0 R /XYZ 89.6638 546.9489 null]
+>> endobj
+2194 0 obj <<
+/D [2173 0 R /XYZ 71.731 544.7921 null]
+>> endobj
+2195 0 obj <<
+/D [2173 0 R /XYZ 89.6638 529.0162 null]
+>> endobj
+2196 0 obj <<
+/D [2173 0 R /XYZ 71.731 526.8593 null]
+>> endobj
+2197 0 obj <<
+/D [2173 0 R /XYZ 89.6638 511.0834 null]
+>> endobj
+2198 0 obj <<
+/D [2173 0 R /XYZ 71.731 509.3002 null]
+>> endobj
+2199 0 obj <<
+/D [2173 0 R /XYZ 89.6638 493.1507 null]
+>> endobj
+2200 0 obj <<
+/D [2173 0 R /XYZ 71.731 490.9938 null]
+>> endobj
+2201 0 obj <<
+/D [2173 0 R /XYZ 89.6638 475.2179 null]
+>> endobj
+2202 0 obj <<
+/D [2173 0 R /XYZ 71.731 473.0611 null]
+>> endobj
+2203 0 obj <<
+/D [2173 0 R /XYZ 89.6638 457.2852 null]
+>> endobj
+2204 0 obj <<
+/D [2173 0 R /XYZ 71.731 455.1283 null]
+>> endobj
+2205 0 obj <<
+/D [2173 0 R /XYZ 89.6638 439.3524 null]
+>> endobj
+2206 0 obj <<
+/D [2173 0 R /XYZ 71.731 437.1956 null]
+>> endobj
+2207 0 obj <<
+/D [2173 0 R /XYZ 89.6638 421.4197 null]
+>> endobj
+2208 0 obj <<
+/D [2173 0 R /XYZ 71.731 419.2628 null]
+>> endobj
+2209 0 obj <<
+/D [2173 0 R /XYZ 89.6638 403.4869 null]
+>> endobj
+2210 0 obj <<
+/D [2173 0 R /XYZ 71.731 401.3301 null]
+>> endobj
+2211 0 obj <<
+/D [2173 0 R /XYZ 89.6638 385.5542 null]
+>> endobj
+2212 0 obj <<
+/D [2173 0 R /XYZ 71.731 383.3973 null]
+>> endobj
+2213 0 obj <<
+/D [2173 0 R /XYZ 89.6638 367.6214 null]
+>> endobj
+2214 0 obj <<
+/D [2173 0 R /XYZ 71.731 365.4646 null]
+>> endobj
+2215 0 obj <<
+/D [2173 0 R /XYZ 89.6638 349.6887 null]
+>> endobj
+1339 0 obj <<
+/D [2173 0 R /XYZ 71.731 326.7746 null]
+>> endobj
+78 0 obj <<
+/D [2173 0 R /XYZ 287.7103 287.4022 null]
+>> endobj
+2216 0 obj <<
+/D [2173 0 R /XYZ 71.731 277.0372 null]
+>> endobj
+2217 0 obj <<
+/D [2173 0 R /XYZ 71.731 265.1209 null]
+>> endobj
+2218 0 obj <<
+/D [2173 0 R /XYZ 71.731 250.1769 null]
+>> endobj
+2219 0 obj <<
+/D [2173 0 R /XYZ 71.731 198.8399 null]
+>> endobj
+2220 0 obj <<
+/D [2173 0 R /XYZ 211.4364 189.071 null]
+>> endobj
+2222 0 obj <<
+/D [2173 0 R /XYZ 71.731 161.1756 null]
+>> endobj
+2172 0 obj <<
+/Font << /F33 1322 0 R /F27 1222 0 R /F23 1214 0 R /F44 2021 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+2225 0 obj <<
+/Length 2590
+/Filter /FlateDecode
+>>
+stream
+xڍk۸{~XX1"[Kmzi@YBd\w3%/,yq^*?JH5|*ժ8
+W;X+(swI&QiL4ԫT+2[=^v@Z >4uhn|ǿ8qu*L*ބ\@БNxO(\;K3c'۔iFkUОLvWe _ƪ+3VsPc>Tۚeqc=m==C[+R \ͮm3Oհ'(=W^p> '&VpZM?HKfk͔G[T*hJҀ@o.huiĖ4`Nx3XD/C^ r
+kw]~ite}QR@ ITFSC<*;JKƙqa"NU<hԏ9j!ؘ gM$#T͇5hI5ҤG ;4"6@p m[U6!#¾k@ \f+fY`o!w3*Ne3yjSKHA{g MfoDB Q#EamLI$Jhޠ(Wݷm:+Sd2Q;AF1M% ⩧2xF.{h__</m
+"$!oTY7B4yl_B(3̄}NOؔ,:^AJeQpYL(WbJ![pA8ʄ#]8Z^fAa K|0бΖ׳gC|<n{֒Ib*v-|#1/aNNӔy睲)w[vd
+V񪳫irK X3FK`bĈ(KKHu:2'2Nn"L.BQ4s@f7B@&"i6}`|aՀS~@ >
+F1÷?=1/K":0:RC%aY-F2C5i ڕM
+*ndb:u9녋>3 2- P x$ t)ҩʥil\WN=Z<:Fj5DC3TѾ%-RK
+Fvj.ah$)Mk#^nykwi(~?(8QoU@(3c xwLf囋`/l;F
+endobj
+2224 0 obj <<
+/Type /Page
+/Contents 2225 0 R
+/Resources 2223 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 2086 0 R
+/Annots [ 2239 0 R ]
+>> endobj
+2239 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [243.499 371.4791 284.8624 381.9537]
+/Subtype /Link
+/A << /S /GoTo /D (security) >>
+>> endobj
+2226 0 obj <<
+/D [2224 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+2227 0 obj <<
+/D [2224 0 R /XYZ 71.731 718.3063 null]
+>> endobj
+2228 0 obj <<
+/D [2224 0 R /XYZ 71.731 688.2541 null]
+>> endobj
+2229 0 obj <<
+/D [2224 0 R /XYZ 71.731 633.5244 null]
+>> endobj
+1340 0 obj <<
+/D [2224 0 R /XYZ 71.731 613.5343 null]
+>> endobj
+82 0 obj <<
+/D [2224 0 R /XYZ 323.661 576.3188 null]
+>> endobj
+2230 0 obj <<
+/D [2224 0 R /XYZ 71.731 565.9538 null]
+>> endobj
+2231 0 obj <<
+/D [2224 0 R /XYZ 284.618 556.1943 null]
+>> endobj
+2232 0 obj <<
+/D [2224 0 R /XYZ 378.5568 556.1943 null]
+>> endobj
+2233 0 obj <<
+/D [2224 0 R /XYZ 231.4007 543.2428 null]
+>> endobj
+2234 0 obj <<
+/D [2224 0 R /XYZ 71.731 536.1047 null]
+>> endobj
+2235 0 obj <<
+/D [2224 0 R /XYZ 144.5093 525.3101 null]
+>> endobj
+2236 0 obj <<
+/D [2224 0 R /XYZ 373.3836 525.3101 null]
+>> endobj
+1341 0 obj <<
+/D [2224 0 R /XYZ 71.731 495.6314 null]
+>> endobj
+86 0 obj <<
+/D [2224 0 R /XYZ 218.078 452.1604 null]
+>> endobj
+2237 0 obj <<
+/D [2224 0 R /XYZ 71.731 448.3301 null]
+>> endobj
+2238 0 obj <<
+/D [2224 0 R /XYZ 118.5554 406.1397 null]
+>> endobj
+1342 0 obj <<
+/D [2224 0 R /XYZ 71.731 362.5127 null]
+>> endobj
+90 0 obj <<
+/D [2224 0 R /XYZ 187.3454 330.009 null]
+>> endobj
+2240 0 obj <<
+/D [2224 0 R /XYZ 71.731 319.644 null]
+>> endobj
+2241 0 obj <<
+/D [2224 0 R /XYZ 154.5103 309.8845 null]
+>> endobj
+2242 0 obj <<
+/D [2224 0 R /XYZ 338.1402 309.8845 null]
+>> endobj
+2243 0 obj <<
+/D [2224 0 R /XYZ 71.731 297.765 null]
+>> endobj
+2244 0 obj <<
+/D [2224 0 R /XYZ 71.731 297.765 null]
+>> endobj
+2245 0 obj <<
+/D [2224 0 R /XYZ 71.731 276.9222 null]
+>> endobj
+2246 0 obj <<
+/D [2224 0 R /XYZ 113.8984 265.3515 null]
+>> endobj
+2247 0 obj <<
+/D [2224 0 R /XYZ 177.7024 252.4001 null]
+>> endobj
+2248 0 obj <<
+/D [2224 0 R /XYZ 71.731 245.2619 null]
+>> endobj
+2249 0 obj <<
+/D [2224 0 R /XYZ 290.4879 234.4673 null]
+>> endobj
+2250 0 obj <<
+/D [2224 0 R /XYZ 71.731 195.5135 null]
+>> endobj
+2251 0 obj <<
+/D [2224 0 R /XYZ 71.731 180.5695 null]
+>> endobj
+2252 0 obj <<
+/D [2224 0 R /XYZ 159.3536 169.0128 null]
+>> endobj
+2253 0 obj <<
+/D [2224 0 R /XYZ 71.731 129.4611 null]
+>> endobj
+2254 0 obj <<
+/D [2224 0 R /XYZ 229.3243 116.5097 null]
+>> endobj
+2223 0 obj <<
+/Font << /F33 1322 0 R /F27 1222 0 R /F23 1214 0 R /F35 1604 0 R /F44 2021 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+2257 0 obj <<
+/Length 2752
+/Filter /FlateDecode
+>>
+stream
+xڥَ}kvv`;1 bض21קE: A-X,u㫘XGLDXe7jK{lH<UUHD K@b)Xd~i6[kj;]E~,q891K *{֒AW &-{/u,xMU`7"Xpmh2Ӷ45ܜ^7&'<#ɺ: o`q" =%A,@Rx]85xػk6*\MdEL*uؽlp}) 炥!WUu3i748mxM+_ҝ5\D3yE;4ۛskȎcH =T]BhvW@]qp:Nbs\0V|%*eR2Ngi*UcVî
+G6?YID" EG:`mhV&ZRû~tG[>b|·t1`cI Xl$l ݑ)ݴ@%Amо'v(Z,s!tDF|]H
+fK>?2G>#MN&]:.P6h܎ (Pg+X #&C.}4m`?|O)3@8U(e^ k;F[גe)'o1`cI <ʤ@͕35dƐ@. H࿼1:`mhKFԦ;I  onFLPx[KjWģ7 -{ s9`r2]4#O!+ '
+<H1qa
+JCljT)eHa*gmfi1z3.n}5
+$g ڰۇ5 Ta'|8+.]Ȏ׭5`j
+FԻ`* ;C˞1FLW4ؙ43495E6kS6
+f^8&'P؞0ݺ|_2^xW,EPѕJ2=dٌmqNޔG6౰$Lݎ#|~0MH}@xuP)o8dRo
+idp+OzkǸcCESuۺxRQ[2$\Gĵ#rt츁rtѩ[՝*v*aJO FCڎіUؒ/&!LMxF ur3ں 5PRԁ xYTm g5CA߱)0!+bX䲮I‰:3xg}A=^5CݷYMxv_GEۍ 6L{7yQ}}b[4B{<H^ ~Üot܀id4k zwњZzBt}pEeC1ch5$a|]ʸL|![9pל~Z'hVoG$B%^%N)'LgMEچ̖ ued1GAg:S".RuU I bPlmvt_139%vۨ! [hxH.2xj %P*'6'^
+O4 [Y/(YrG髿͏p?c1Py,Wnp
+endobj
+2256 0 obj <<
+/Type /Page
+/Contents 2257 0 R
+/Resources 2255 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 2086 0 R
+/Annots [ 2264 0 R 2268 0 R 2269 0 R 2270 0 R ]
+>> endobj
+2264 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [362.8219 631.6671 424.6281 642.1417]
+/Subtype /Link
+/A << /S /GoTo /D (suexec) >>
+>> endobj
+2268 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [471.1281 525.4008 533.1707 536.3047]
+/Subtype /Link
+/A << /S /GoTo /D (mysql) >>
+>> endobj
+2269 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [125.2498 512.4494 187.0178 523.3533]
+/Subtype /Link
+/A << /S /GoTo /D (postgresql) >>
+>> endobj
+2270 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [240.0683 512.4494 301.8363 523.3533]
+/Subtype /Link
+/A << /S /GoTo /D (oracle) >>
+>> endobj
+2258 0 obj <<
+/D [2256 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+2259 0 obj <<
+/D [2256 0 R /XYZ 444.9382 708.3437 null]
+>> endobj
+2260 0 obj <<
+/D [2256 0 R /XYZ 178.9977 695.3923 null]
+>> endobj
+2261 0 obj <<
+/D [2256 0 R /XYZ 71.731 693.2354 null]
+>> endobj
+2262 0 obj <<
+/D [2256 0 R /XYZ 118.5554 657.6841 null]
+>> endobj
+2263 0 obj <<
+/D [2256 0 R /XYZ 391.646 646.207 null]
+>> endobj
+2265 0 obj <<
+/D [2256 0 R /XYZ 71.731 612.7381 null]
+>> endobj
+2266 0 obj <<
+/D [2256 0 R /XYZ 169.9804 605.9835 null]
+>> endobj
+1343 0 obj <<
+/D [2256 0 R /XYZ 71.731 585.8939 null]
+>> endobj
+94 0 obj <<
+/D [2256 0 R /XYZ 224.1857 548.6784 null]
+>> endobj
+2267 0 obj <<
+/D [2256 0 R /XYZ 71.731 541.3261 null]
+>> endobj
+1344 0 obj <<
+/D [2256 0 R /XYZ 71.731 513.4456 null]
+>> endobj
+98 0 obj <<
+/D [2256 0 R /XYZ 266.2987 481.1317 null]
+>> endobj
+2271 0 obj <<
+/D [2256 0 R /XYZ 71.731 472.4942 null]
+>> endobj
+2272 0 obj <<
+/D [2256 0 R /XYZ 247.7687 462.2027 null]
+>> endobj
+1345 0 obj <<
+/D [2256 0 R /XYZ 71.731 429.1617 null]
+>> endobj
+102 0 obj <<
+/D [2256 0 R /XYZ 156.1213 395.8515 null]
+>> endobj
+2273 0 obj <<
+/D [2256 0 R /XYZ 71.731 393.3769 null]
+>> endobj
+2274 0 obj <<
+/D [2256 0 R /XYZ 118.5554 356.8292 null]
+>> endobj
+2275 0 obj <<
+/D [2256 0 R /XYZ 407.0632 345.3522 null]
+>> endobj
+2276 0 obj <<
+/D [2256 0 R /XYZ 512.3437 333.6959 null]
+>> endobj
+2277 0 obj <<
+/D [2256 0 R /XYZ 118.5554 326.8272 null]
+>> endobj
+2278 0 obj <<
+/D [2256 0 R /XYZ 158.406 307.0956 null]
+>> endobj
+2279 0 obj <<
+/D [2256 0 R /XYZ 118.5554 305.2083 null]
+>> endobj
+2280 0 obj <<
+/D [2256 0 R /XYZ 158.406 290.458 null]
+>> endobj
+2281 0 obj <<
+/D [2256 0 R /XYZ 118.5554 288.5707 null]
+>> endobj
+2282 0 obj <<
+/D [2256 0 R /XYZ 158.406 273.8204 null]
+>> endobj
+2283 0 obj <<
+/D [2256 0 R /XYZ 71.731 247.1171 null]
+>> endobj
+106 0 obj <<
+/D [2256 0 R /XYZ 316.729 219.2218 null]
+>> endobj
+2284 0 obj <<
+/D [2256 0 R /XYZ 71.731 212.0238 null]
+>> endobj
+2285 0 obj <<
+/D [2256 0 R /XYZ 71.731 168.248 null]
+>> endobj
+2286 0 obj <<
+/D [2256 0 R /XYZ 465.4239 157.4534 null]
+>> endobj
+2287 0 obj <<
+/D [2256 0 R /XYZ 71.731 119.4311 null]
+>> endobj
+2255 0 obj <<
+/Font << /F33 1322 0 R /F27 1222 0 R /F35 1604 0 R /F23 1214 0 R /F44 2021 0 R /F48 2037 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+2290 0 obj <<
+/Length 2278
+/Filter /FlateDecode
+>>
+stream
+xڝX{o6?+!f$Jԣ=\q\:}[DBѬoʒ.
+ݼj4~&#i]R&kpl<XDҰĹq 6~dB
+7^B>};bJ A$5Cjjhp
+5TMV6H_ӽz+e}AW Z'her6(ѺEBJXus
+ajA( s[) *RI{ {S4=Z6VIx<ih<!t N4okIleb#7r:mJp57RDi?RR&zם|
+)=Nd; lyOQ:@ͣ|$dE@c\8,c^|W/K~߄o= IJ.|Ak*Rj)T ;d=69y:|sb}vm=r)fܚiql|E&5گNRsZ6% %eh&5,NIܥMl=9yyyybʴ Y\'0D'` L/Bǜi 4y>T@$HD@Ҏ-__fVpZHL!p%Ĉ1PP$)
+o\W^I
+Hc4lnp.䃌19!W*~Y#*ΥT@,9Y` \H t9 ĴKIT
+bx`@
+s) B׾>8^=sM.f@0t樟ȉH"zHatbȡ213'- e`jH @(Ap[2@ud#gjE9S6KaU-PMܥL= @x4xixGN׍y 
+Vpv,߳v{oE·ڀ@E 6 |yz&t}l1Ivj¬S4~}qB>jaƴjKj"O+٧?^>.~z^^{o[
+{Rֆ{23I)f۟}fYL1)endstream
+endobj
+2289 0 obj <<
+/Type /Page
+/Contents 2290 0 R
+/Resources 2288 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 2330 0 R
+/Annots [ 2307 0 R ]
+>> endobj
+2307 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [441.1732 426.4359 495.4692 437.3399]
+/Subtype /Link
+/A << /S /GoTo /D (localconfig) >>
+>> endobj
+2291 0 obj <<
+/D [2289 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+2292 0 obj <<
+/D [2289 0 R /XYZ 71.731 686.7248 null]
+>> endobj
+110 0 obj <<
+/D [2289 0 R /XYZ 276.5502 653.8481 null]
+>> endobj
+2293 0 obj <<
+/D [2289 0 R /XYZ 71.731 648.7523 null]
+>> endobj
+2294 0 obj <<
+/D [2289 0 R /XYZ 71.731 615.8257 null]
+>> endobj
+2295 0 obj <<
+/D [2289 0 R /XYZ 277.0796 592.0797 null]
+>> endobj
+2296 0 obj <<
+/D [2289 0 R /XYZ 71.731 579.9602 null]
+>> endobj
+2297 0 obj <<
+/D [2289 0 R /XYZ 71.731 536.0065 null]
+>> endobj
+2298 0 obj <<
+/D [2289 0 R /XYZ 357.7805 524.2341 null]
+>> endobj
+2299 0 obj <<
+/D [2289 0 R /XYZ 71.731 504.1445 null]
+>> endobj
+114 0 obj <<
+/D [2289 0 R /XYZ 211.2854 473.4247 null]
+>> endobj
+2300 0 obj <<
+/D [2289 0 R /XYZ 71.731 466.3463 null]
+>> endobj
+2301 0 obj <<
+/D [2289 0 R /XYZ 271.0674 442.5405 null]
+>> endobj
+2302 0 obj <<
+/D [2289 0 R /XYZ 243.4755 429.589 null]
+>> endobj
+2306 0 obj <<
+/D [2289 0 R /XYZ 370.2593 429.589 null]
+>> endobj
+2308 0 obj <<
+/D [2289 0 R /XYZ 71.731 422.4509 null]
+>> endobj
+2309 0 obj <<
+/D [2289 0 R /XYZ 137.5929 411.6563 null]
+>> endobj
+2310 0 obj <<
+/D [2289 0 R /XYZ 262.9731 411.6563 null]
+>> endobj
+2311 0 obj <<
+/D [2289 0 R /XYZ 403.4489 411.6563 null]
+>> endobj
+2312 0 obj <<
+/D [2289 0 R /XYZ 134.3878 398.7049 null]
+>> endobj
+2313 0 obj <<
+/D [2289 0 R /XYZ 344.0117 398.7049 null]
+>> endobj
+2314 0 obj <<
+/D [2289 0 R /XYZ 71.731 378.6153 null]
+>> endobj
+2315 0 obj <<
+/D [2289 0 R /XYZ 105.4942 367.8207 null]
+>> endobj
+2316 0 obj <<
+/D [2289 0 R /XYZ 71.731 356.4773 null]
+>> endobj
+2317 0 obj <<
+/D [2289 0 R /XYZ 93.2503 346.2017 null]
+>> endobj
+2318 0 obj <<
+/D [2289 0 R /XYZ 308.4431 311.2329 null]
+>> endobj
+2319 0 obj <<
+/D [2289 0 R /XYZ 93.2503 299.5766 null]
+>> endobj
+2320 0 obj <<
+/D [2289 0 R /XYZ 71.731 277.9577 null]
+>> endobj
+118 0 obj <<
+/D [2289 0 R /XYZ 318.7211 245.081 null]
+>> endobj
+2321 0 obj <<
+/D [2289 0 R /XYZ 71.731 237.883 null]
+>> endobj
+2322 0 obj <<
+/D [2289 0 R /XYZ 71.731 207.0586 null]
+>> endobj
+2323 0 obj <<
+/D [2289 0 R /XYZ 511.0835 196.264 null]
+>> endobj
+2324 0 obj <<
+/D [2289 0 R /XYZ 298.7034 183.3126 null]
+>> endobj
+2325 0 obj <<
+/D [2289 0 R /XYZ 490.1775 183.3126 null]
+>> endobj
+2326 0 obj <<
+/D [2289 0 R /XYZ 71.731 158.6153 null]
+>> endobj
+2327 0 obj <<
+/D [2289 0 R /XYZ 93.2503 148.7422 null]
+>> endobj
+2328 0 obj <<
+/D [2289 0 R /XYZ 149.2005 148.7422 null]
+>> endobj
+2329 0 obj <<
+/D [2289 0 R /XYZ 93.2503 137.0859 null]
+>> endobj
+2288 0 obj <<
+/Font << /F33 1322 0 R /F35 1604 0 R /F48 2037 0 R /F27 1222 0 R /F54 2305 0 R /F32 1230 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+2333 0 obj <<
+/Length 2079
+/Filter /FlateDecode
+>>
+stream
+xڭk۸{~")~J5EWl'K>=vpd+(] _\XXCBEF`W?eë^"TzحB4*J$F&_{vU&X+AϏu۪*=Ue7~ӫ?<E
+≀^ :ԱP:^f# Vi !Uy,{k詂 r~tnT0a'׍ S;!=T} jb J%R#Cg!W^ pytfr' z>x]of#H^"
+LIPrB`A e|^w 2t'i):m dHeݵ#%=F cS#q4~{đ<ӕ5h7=M*(=(>= @sdCm
+ z>فA\!J(0N\w)d*oo$|*[rJ{L<oq*À<N}k2BS3zs`'/5PL phЄSK%P2&~DN4Yz<UٟyM`iYIa8d}IӔ>`a uMl!HhCǓq[*1mJo$l4b)
+ |BU<~hꟃ@Ʒ ۏ=Zs ji_5<. D"QPvrxky* ru@ dnb,u$ |=*N{a-HLGky3$PU1t-
+IV.ܥŔ0]B?"4[S#[<>nB$gVNWEfY3Խ-0/~I~H7
+f* XT8EFendstream
+endobj
+2332 0 obj <<
+/Type /Page
+/Contents 2333 0 R
+/Resources 2331 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 2330 0 R
+/Annots [ 2343 0 R ]
+>> endobj
+2343 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [399.3902 518.0922 453.6862 528.9962]
+/Subtype /Link
+/A << /S /GoTo /D (localconfig) >>
+>> endobj
+2334 0 obj <<
+/D [2332 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+2335 0 obj <<
+/D [2332 0 R /XYZ 71.731 718.3063 null]
+>> endobj
+2336 0 obj <<
+/D [2332 0 R /XYZ 71.731 693.2354 null]
+>> endobj
+2337 0 obj <<
+/D [2332 0 R /XYZ 71.731 678.2915 null]
+>> endobj
+1346 0 obj <<
+/D [2332 0 R /XYZ 71.731 630.934 null]
+>> endobj
+122 0 obj <<
+/D [2332 0 R /XYZ 183.5462 595.467 null]
+>> endobj
+2338 0 obj <<
+/D [2332 0 R /XYZ 71.731 592.8071 null]
+>> endobj
+126 0 obj <<
+/D [2332 0 R /XYZ 233.3921 565.0809 null]
+>> endobj
+2339 0 obj <<
+/D [2332 0 R /XYZ 71.731 557.8829 null]
+>> endobj
+2340 0 obj <<
+/D [2332 0 R /XYZ 250.6331 534.1967 null]
+>> endobj
+2341 0 obj <<
+/D [2332 0 R /XYZ 201.6925 521.2453 null]
+>> endobj
+2342 0 obj <<
+/D [2332 0 R /XYZ 328.4763 521.2453 null]
+>> endobj
+2344 0 obj <<
+/D [2332 0 R /XYZ 71.731 514.1072 null]
+>> endobj
+2345 0 obj <<
+/D [2332 0 R /XYZ 71.731 491.1931 null]
+>> endobj
+2346 0 obj <<
+/D [2332 0 R /XYZ 77.1108 481.6936 null]
+>> endobj
+2347 0 obj <<
+/D [2332 0 R /XYZ 71.731 470.3502 null]
+>> endobj
+2348 0 obj <<
+/D [2332 0 R /XYZ 71.731 446.6601 null]
+>> endobj
+2349 0 obj <<
+/D [2332 0 R /XYZ 77.1108 437.1606 null]
+>> endobj
+2350 0 obj <<
+/D [2332 0 R /XYZ 71.731 425.8172 null]
+>> endobj
+2351 0 obj <<
+/D [2332 0 R /XYZ 367.5077 414.2466 null]
+>> endobj
+2352 0 obj <<
+/D [2332 0 R /XYZ 425.1787 414.2466 null]
+>> endobj
+2353 0 obj <<
+/D [2332 0 R /XYZ 71.731 386.5605 null]
+>> endobj
+2354 0 obj <<
+/D [2332 0 R /XYZ 71.731 371.6165 null]
+>> endobj
+2355 0 obj <<
+/D [2332 0 R /XYZ 71.731 333.8481 null]
+>> endobj
+130 0 obj <<
+/D [2332 0 R /XYZ 215.6691 300.9713 null]
+>> endobj
+2356 0 obj <<
+/D [2332 0 R /XYZ 71.731 293.7734 null]
+>> endobj
+2357 0 obj <<
+/D [2332 0 R /XYZ 178.5531 283.0386 null]
+>> endobj
+2358 0 obj <<
+/D [2332 0 R /XYZ 347.9401 283.0386 null]
+>> endobj
+2359 0 obj <<
+/D [2332 0 R /XYZ 71.731 265.0063 null]
+>> endobj
+2360 0 obj <<
+/D [2332 0 R /XYZ 71.731 265.0063 null]
+>> endobj
+2361 0 obj <<
+/D [2332 0 R /XYZ 71.731 245.7923 null]
+>> endobj
+2362 0 obj <<
+/D [2332 0 R /XYZ 71.731 214.1321 null]
+>> endobj
+2363 0 obj <<
+/D [2332 0 R /XYZ 240.4397 190.386 null]
+>> endobj
+2364 0 obj <<
+/D [2332 0 R /XYZ 71.731 177.4346 null]
+>> endobj
+2365 0 obj <<
+/D [2332 0 R /XYZ 181.2557 177.4346 null]
+>> endobj
+2366 0 obj <<
+/D [2332 0 R /XYZ 336.0356 177.4346 null]
+>> endobj
+2367 0 obj <<
+/D [2332 0 R /XYZ 470.0538 177.4346 null]
+>> endobj
+1347 0 obj <<
+/D [2332 0 R /XYZ 71.731 147.3824 null]
+>> endobj
+2331 0 obj <<
+/Font << /F33 1322 0 R /F27 1222 0 R /F23 1214 0 R /F44 2021 0 R /F48 2037 0 R /F35 1604 0 R /F54 2305 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+2370 0 obj <<
+/Length 1546
+/Filter /FlateDecode
+>>
+stream
+xڕn:_F#5Hq֙'ڦSPAi@Z"0>66]Fyr5Ec-b\5_\};bIG>:Bz׊[ HSMxu*)ۥMpJdY?t+27(w]0
+Q 2ZLJEMKWcи;TI%AӸhKyTŠ$VkEH^B#p5;A1U~
+}**
+\V~Q_|r! NQbhE(Q3}hU$U10텓jXɁ jw]2
+ݦ:S ŗk2y1u?['fVaR5=@'уNu-WY: _kCfa$U`?endstream
+endobj
+2369 0 obj <<
+/Type /Page
+/Contents 2370 0 R
+/Resources 2368 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 2330 0 R
+>> endobj
+2371 0 obj <<
+/D [2369 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+134 0 obj <<
+/D [2369 0 R /XYZ 151.9129 708.3437 null]
+>> endobj
+2372 0 obj <<
+/D [2369 0 R /XYZ 71.731 708.1943 null]
+>> endobj
+138 0 obj <<
+/D [2369 0 R /XYZ 229.6168 677.9576 null]
+>> endobj
+2373 0 obj <<
+/D [2369 0 R /XYZ 71.731 670.8793 null]
+>> endobj
+2374 0 obj <<
+/D [2369 0 R /XYZ 71.731 637.0113 null]
+>> endobj
+2375 0 obj <<
+/D [2369 0 R /XYZ 147.0485 613.7982 null]
+>> endobj
+2376 0 obj <<
+/D [2369 0 R /XYZ 71.731 580.523 null]
+>> endobj
+2377 0 obj <<
+/D [2369 0 R /XYZ 379.0423 567.5716 null]
+>> endobj
+2378 0 obj <<
+/D [2369 0 R /XYZ 240.1215 554.6202 null]
+>> endobj
+2379 0 obj <<
+/D [2369 0 R /XYZ 71.731 534.5306 null]
+>> endobj
+142 0 obj <<
+/D [2369 0 R /XYZ 208.9641 503.8107 null]
+>> endobj
+2380 0 obj <<
+/D [2369 0 R /XYZ 71.731 498.6252 null]
+>> endobj
+2381 0 obj <<
+/D [2369 0 R /XYZ 307.0341 485.878 null]
+>> endobj
+2382 0 obj <<
+/D [2369 0 R /XYZ 372.5173 485.878 null]
+>> endobj
+2383 0 obj <<
+/D [2369 0 R /XYZ 435.9172 485.878 null]
+>> endobj
+2384 0 obj <<
+/D [2369 0 R /XYZ 71.731 460.8071 null]
+>> endobj
+2385 0 obj <<
+/D [2369 0 R /XYZ 173.9476 439.6513 null]
+>> endobj
+2386 0 obj <<
+/D [2369 0 R /XYZ 71.731 313.1258 null]
+>> endobj
+146 0 obj <<
+/D [2369 0 R /XYZ 230.9615 280.2491 null]
+>> endobj
+2387 0 obj <<
+/D [2369 0 R /XYZ 71.731 273.0511 null]
+>> endobj
+2388 0 obj <<
+/D [2369 0 R /XYZ 246.9819 262.3163 null]
+>> endobj
+2389 0 obj <<
+/D [2369 0 R /XYZ 71.731 239.3027 null]
+>> endobj
+2390 0 obj <<
+/D [2369 0 R /XYZ 71.731 194.4708 null]
+>> endobj
+1348 0 obj <<
+/D [2369 0 R /XYZ 71.731 154.4559 null]
+>> endobj
+2368 0 obj <<
+/Font << /F33 1322 0 R /F23 1214 0 R /F48 2037 0 R /F27 1222 0 R /F35 1604 0 R /F54 2305 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+2393 0 obj <<
+/Length 2945
+/Filter /FlateDecode
+>>
+stream
+xڍk۸{~q_bkH;ȥ^ۢ(zEh, g;ʒ D| gy{ʇb /V'P+_^ 2v ۏJR/d߭xiU"Y^?dMVZz>+KSii?S?p?PU AV"TIO<'=)d+NF7_tjD
+LBݝՕ3R_o+|[D[<+gE#k;pF qcHA^zʎu֔PRo,ٲIX'
+D-<\mX U4Īc?7
+&֩5pEX޳>+_+UZ!.B2#@k p~ doG[inZF4F?пٞ֎(3RGWYPCTe,h|VvQWh@<`S8VMhx>bⲕ, &1}#7ٷ_"<zk1~:
+a|o5ڏRvm Bx+
+.6E]|C~i`0DGVB9[nLt'{EpP-nC[|6MHj괭cL吆/Nd
+Ugzs9BxtW8{UBF>ֽFeo?{\X~x$`40z4MHr>ۺ䙩ٸL6
+H;PT63 03 JOȏ 5 .FV]qkB`c+ ,
+0 ˔sr@$
+q-ʺ3 TK"u/ $qV2(}@^8RS].8́ &6SbTÙZrz.wf51εhQϪ]]9ěsk6PccBQ oN/^Dpp^ OuV,%gࡣ!jX1w FLj%TvQuͪ]WW|2ۍ{5Ӎ0{ʓCcB!Tyu^%>6VC
+;h1zZ9Yʱ@;Y@9* FR!ZCH/XD?`PozrRj‚rώ٤C5ܣ*ݷPApwee
+sپh]`TUil3
+2E,˨lӡ-&KCb底 JΩCnC7v`z1T& .EIQ.e%ʢLg[D5UjUHY/eW]sl$bٱ)͒>A w8n)؂f|+>nLhl36;M/AG3l#q)dF Z B vykНGߴ}-'):ΟhM ÏJ bO&)]xڡyKRP !3 ;lcҥrf!G`lP%"w@i%~$kfcfy*R_֖
+ $Cab |8H
+fD8My&R?$bq fG{~C$$ȃ+h̒K 2ǒCPל՚{0l,'mk"зcQ< Y?f6 F}6m[a1[uLJ ?غ!S"gP3C8}|h>47cV&G=h걅ʙ0 }b:##cE(}6\B(8Dl< endstream
+endobj
+2392 0 obj <<
+/Type /Page
+/Contents 2393 0 R
+/Resources 2391 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 2330 0 R
+/Annots [ 2403 0 R 2406 0 R 2407 0 R ]
+>> endobj
+2403 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [202.966 480.9165 258.7502 491.8204]
+/Subtype /Link
+/A << /S /GoTo /D (security-webserver-access) >>
+>> endobj
+2406 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [303.9053 414.5653 340.7668 425.4692]
+/Subtype /Link
+/A << /S /GoTo /D (http-apache-mod_cgi) >>
+>> endobj
+2407 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [408.2018 414.5653 448.3808 425.4692]
+/Subtype /Link
+/A << /S /GoTo /D (http-apache-mod_perl) >>
+>> endobj
+2394 0 obj <<
+/D [2392 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+150 0 obj <<
+/D [2392 0 R /XYZ 206.856 708.1493 null]
+>> endobj
+2395 0 obj <<
+/D [2392 0 R /XYZ 71.731 698.0066 null]
+>> endobj
+2396 0 obj <<
+/D [2392 0 R /XYZ 120.3029 688.0248 null]
+>> endobj
+2397 0 obj <<
+/D [2392 0 R /XYZ 71.731 654.9838 null]
+>> endobj
+2398 0 obj <<
+/D [2392 0 R /XYZ 71.731 611.1481 null]
+>> endobj
+2399 0 obj <<
+/D [2392 0 R /XYZ 71.731 611.1481 null]
+>> endobj
+2400 0 obj <<
+/D [2392 0 R /XYZ 270.6339 600.3535 null]
+>> endobj
+1349 0 obj <<
+/D [2392 0 R /XYZ 71.731 593.2154 null]
+>> endobj
+154 0 obj <<
+/D [2392 0 R /XYZ 188.5932 555.9999 null]
+>> endobj
+2401 0 obj <<
+/D [2392 0 R /XYZ 71.731 548.6475 null]
+>> endobj
+2402 0 obj <<
+/D [2392 0 R /XYZ 99.3014 509.9725 null]
+>> endobj
+2404 0 obj <<
+/D [2392 0 R /XYZ 319.3277 484.0696 null]
+>> endobj
+1350 0 obj <<
+/D [2392 0 R /XYZ 71.731 468.9613 null]
+>> endobj
+158 0 obj <<
+/D [2392 0 R /XYZ 242.3649 436.6474 null]
+>> endobj
+2405 0 obj <<
+/D [2392 0 R /XYZ 71.731 428.0099 null]
+>> endobj
+2408 0 obj <<
+/D [2392 0 R /XYZ 71.731 415.5616 null]
+>> endobj
+162 0 obj <<
+/D [2392 0 R /XYZ 236.6151 387.3324 null]
+>> endobj
+2409 0 obj <<
+/D [2392 0 R /XYZ 71.731 380.1344 null]
+>> endobj
+2410 0 obj <<
+/D [2392 0 R /XYZ 71.731 367.2428 null]
+>> endobj
+2411 0 obj <<
+/D [2392 0 R /XYZ 71.731 357.2801 null]
+>> endobj
+2412 0 obj <<
+/D [2392 0 R /XYZ 115.1182 341.5042 null]
+>> endobj
+2413 0 obj <<
+/D [2392 0 R /XYZ 429.3178 341.5042 null]
+>> endobj
+2414 0 obj <<
+/D [2392 0 R /XYZ 71.731 339.3474 null]
+>> endobj
+2415 0 obj <<
+/D [2392 0 R /XYZ 147.1884 323.5715 null]
+>> endobj
+2416 0 obj <<
+/D [2392 0 R /XYZ 314.7473 297.6686 null]
+>> endobj
+2417 0 obj <<
+/D [2392 0 R /XYZ 71.731 290.5304 null]
+>> endobj
+2418 0 obj <<
+/D [2392 0 R /XYZ 71.731 206.112 null]
+>> endobj
+2419 0 obj <<
+/D [2392 0 R /XYZ 155.0564 180.2091 null]
+>> endobj
+2420 0 obj <<
+/D [2392 0 R /XYZ 89.6638 167.2577 null]
+>> endobj
+2421 0 obj <<
+/D [2392 0 R /XYZ 71.731 165.1008 null]
+>> endobj
+2422 0 obj <<
+/D [2392 0 R /XYZ 71.731 150.1569 null]
+>> endobj
+2423 0 obj <<
+/D [2392 0 R /XYZ 130.9027 129.0011 null]
+>> endobj
+2391 0 obj <<
+/Font << /F33 1322 0 R /F23 1214 0 R /F27 1222 0 R /F35 1604 0 R /F48 2037 0 R /F44 2021 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+2427 0 obj <<
+/Length 2329
+/Filter /FlateDecode
+>>
+stream
+xYm۸_j1W|)Eܵ=E+
+] +K^pHYeo+j8 pWHx &Z=ogz7w\,BW',5\)Xdus>kzn"ւcYY7<Ze_~Ku+PJk
+(eq6-SouoߡJMĚD Of+^zoѺmd$x1}S_6"^[kzfeFy1(8a2Q-cFmZ ?Cu?٧ۗ-v¶xʙ: N6Y{~'(N`N_ZL&YwD{ԝk&"#ff\-qz3\&<f-g,y!,J?G ΩnSN; kJr"Be"3=/@ml{(fkb8^9<vĐU9Q󢵻n0S<DsUc`*}'zhh1EX0,rl՗^ꍣc4Pb21gqzrm>EpiE5Ɵ[f$g}]]=.Kf zַhSf;3\2fXړ6WNV<8b dG/7`[4mlz+aqIJK`
+J؈ wYF(̐2npQDE.IHi0ka(RDDL n)$128u5K, |=M
+}⎚.lɛHٮ][AwCE+УHe]B,Crm><~P.(`07<ZۜLhC]HDG%Ժ6'u|bn+0x˒,0_I$)#?TNfg*:fRpc0ۘL
+eNKGֻ`ɶ槬5ʇ;.
+4zxHHv#8y
+k((w_?‰/r;~'<!PQPdihtD.,Ip'OV:HRMOM)^p='-WM0vkvXX0[[[N,СvQf25(H.
+2%I`L15
+h&$qB
+wK8
+,WDgǺ=L5&39<E?G/=c&1id9nN}kr
+V2ՀV ZzTJv3(i"3TZM3'|%҄)O0RVuXtʷˌ9{b@U7A܅YO}6sZ3KT2pb<M>
+7nh 2P'Av|ҧ%p
+-MNR6lUC @-T~ fQ-
+endobj
+2426 0 obj <<
+/Type /Page
+/Contents 2427 0 R
+/Resources 2425 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 2330 0 R
+/Annots [ 2432 0 R ]
+>> endobj
+2432 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [108.5928 662.2222 203.6286 672.6969]
+/Subtype /Link
+/A << /S /GoTo /D (win32-http) >>
+>> endobj
+2428 0 obj <<
+/D [2426 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+2429 0 obj <<
+/D [2426 0 R /XYZ 76.7123 708.3437 null]
+>> endobj
+2430 0 obj <<
+/D [2426 0 R /XYZ 71.731 688.4184 null]
+>> endobj
+2431 0 obj <<
+/D [2426 0 R /XYZ 312.7176 676.7621 null]
+>> endobj
+2433 0 obj <<
+/D [2426 0 R /XYZ 74.2217 647.1731 null]
+>> endobj
+2434 0 obj <<
+/D [2426 0 R /XYZ 92.4688 624.259 null]
+>> endobj
+2435 0 obj <<
+/D [2426 0 R /XYZ 191.4782 611.3076 null]
+>> endobj
+2436 0 obj <<
+/D [2426 0 R /XYZ 252.6128 611.3076 null]
+>> endobj
+2437 0 obj <<
+/D [2426 0 R /XYZ 457.2853 611.3076 null]
+>> endobj
+2438 0 obj <<
+/D [2426 0 R /XYZ 134.2361 598.3562 null]
+>> endobj
+2439 0 obj <<
+/D [2426 0 R /XYZ 241.5533 598.3562 null]
+>> endobj
+2440 0 obj <<
+/D [2426 0 R /XYZ 71.731 596.9166 null]
+>> endobj
+2441 0 obj <<
+/D [2426 0 R /XYZ 280.4366 567.472 null]
+>> endobj
+2442 0 obj <<
+/D [2426 0 R /XYZ 400.4654 567.472 null]
+>> endobj
+2443 0 obj <<
+/D [2426 0 R /XYZ 71.731 547.3824 null]
+>> endobj
+2444 0 obj <<
+/D [2426 0 R /XYZ 71.731 521.2453 null]
+>> endobj
+2424 0 obj <<
+/D [2426 0 R /XYZ 71.731 483.223 null]
+>> endobj
+166 0 obj <<
+/D [2426 0 R /XYZ 240.6398 452.5031 null]
+>> endobj
+2445 0 obj <<
+/D [2426 0 R /XYZ 71.731 445.4247 null]
+>> endobj
+2446 0 obj <<
+/D [2426 0 R /XYZ 71.731 432.4135 null]
+>> endobj
+2447 0 obj <<
+/D [2426 0 R /XYZ 71.731 422.4509 null]
+>> endobj
+2448 0 obj <<
+/D [2426 0 R /XYZ 115.1182 406.675 null]
+>> endobj
+2449 0 obj <<
+/D [2426 0 R /XYZ 429.3178 406.675 null]
+>> endobj
+2450 0 obj <<
+/D [2426 0 R /XYZ 71.731 404.5181 null]
+>> endobj
+2451 0 obj <<
+/D [2426 0 R /XYZ 71.731 386.5854 null]
+>> endobj
+2452 0 obj <<
+/D [2426 0 R /XYZ 71.731 371.6414 null]
+>> endobj
+2453 0 obj <<
+/D [2426 0 R /XYZ 132.5157 350.4857 null]
+>> endobj
+2454 0 obj <<
+/D [2426 0 R /XYZ 254.2419 350.4857 null]
+>> endobj
+2455 0 obj <<
+/D [2426 0 R /XYZ 76.7123 333.8481 null]
+>> endobj
+2456 0 obj <<
+/D [2426 0 R /XYZ 136.4882 290.3027 null]
+>> endobj
+2457 0 obj <<
+/D [2426 0 R /XYZ 329.9487 281.8383 null]
+>> endobj
+2458 0 obj <<
+/D [2426 0 R /XYZ 71.731 253.2431 null]
+>> endobj
+2459 0 obj <<
+/D [2426 0 R /XYZ 71.731 224.1966 null]
+>> endobj
+2460 0 obj <<
+/D [2426 0 R /XYZ 92.4688 206.2638 null]
+>> endobj
+2461 0 obj <<
+/D [2426 0 R /XYZ 191.4782 193.3124 null]
+>> endobj
+2462 0 obj <<
+/D [2426 0 R /XYZ 252.6128 193.3124 null]
+>> endobj
+2463 0 obj <<
+/D [2426 0 R /XYZ 457.2853 193.3124 null]
+>> endobj
+2464 0 obj <<
+/D [2426 0 R /XYZ 134.2361 180.361 null]
+>> endobj
+2465 0 obj <<
+/D [2426 0 R /XYZ 241.5533 180.361 null]
+>> endobj
+2466 0 obj <<
+/D [2426 0 R /XYZ 71.731 168.9588 null]
+>> endobj
+2467 0 obj <<
+/D [2426 0 R /XYZ 71.731 142.3386 null]
+>> endobj
+2468 0 obj <<
+/D [2426 0 R /XYZ 71.731 127.3947 null]
+>> endobj
+2469 0 obj <<
+/D [2426 0 R /XYZ 470.1216 117.8952 null]
+>> endobj
+2470 0 obj <<
+/D [2426 0 R /XYZ 71.731 110.9189 null]
+>> endobj
+2425 0 obj <<
+/Font << /F33 1322 0 R /F23 1214 0 R /F44 2021 0 R /F35 1604 0 R /F27 1222 0 R /F54 2305 0 R /F48 2037 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+2473 0 obj <<
+/Length 3118
+/Filter /FlateDecode
+>>
+stream
+xڥZmo_!8X4kzsucESIKP_y[r)vB;;ܝyfv)gaY*xH޿re-<k;n[xNdšrwm]rfv}{Z.)ˢr(dۿVUhLxX bV:,WXLYS᠅-BA-4J|q]Xس<Ϗtz(4+/<±A
+'R?4/Z(쐢E~,ŪDH钯+^<C`j_a_Ui]YLʁ5^2MΤ"1}uI;dh[0㣶(p'mQ>^EsBL eBcr&^Hgr˙I0$c͉mN?H{sS<A׀z$@ #w+4i%Lsnt5DvhR M}^edX
+0-Uឨn=
+"Q6cB\OGR-r 1)>=TNq`t,d%c@j~69@9;$N;~Nۏ=ZXc^8j{qqme P)B~Z"3@Nz?2
+Epb,~%3jEOAxAE<\@zfbL)ؤ B#:Bvr$D1_7 L1RN"9R/'}iu…ϓ1o+np2$E,@C>[qht3U>ܘ:'5Qv赭"GoUȨg'lk
+y5INK,J%NR3Vlz8þj.ފ0^#}uky+)HRD
+NU>h !Lր8keиB ܘ7ą$`,F3k8GK
+oQe5[+P
+tWs= mx^ljhG;C3 f :oں5A >ۄrH $
+>^xg12*EёđQAa4L١L4P,ٕB}SU+ayΓVZIioz>PқS.:kg
+g+>ⷿq&m T1wy ϲOK+S#<lpGO"z9&gUb؀ciG썶E&Um]۶A:b3<XZ1k鸦$}񈘎
+
+f@w܌]OIZHʍYN%U3"(2T@ji?(n|p<Z2Fj)q)bbo8ה2uj]A_5x0gw*焿J)ʶp3ȍf(,?lo(*wPJ#;7"7L6I
+R%[ i}ƴJgh~4Nhy6] ̇;`IfZS=;:~7YznXn++V7m؞ЃW %.K3\ì(R4jkjE{FD[0n3?2Ksq9} ޥeFHCޥDH0:6β}Qm6qu[ԡlU
+<r6p{!~ uݾ
+ƳK-xfc>6V:v^^boWPt8o}ȅ?; gv
+Mv>/_MIg^pቓ |}OP/+"sSTs0f/IڅU!N62-
+[]"]7
+endobj
+2472 0 obj <<
+/Type /Page
+/Contents 2473 0 R
+/Resources 2471 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 2330 0 R
+>> endobj
+2474 0 obj <<
+/D [2472 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+2475 0 obj <<
+/D [2472 0 R /XYZ 101.6189 708.3437 null]
+>> endobj
+2476 0 obj <<
+/D [2472 0 R /XYZ 71.731 683.0361 null]
+>> endobj
+2477 0 obj <<
+/D [2472 0 R /XYZ 101.6189 668.3935 null]
+>> endobj
+2478 0 obj <<
+/D [2472 0 R /XYZ 230.3977 656.7372 null]
+>> endobj
+2479 0 obj <<
+/D [2472 0 R /XYZ 491.4206 656.7372 null]
+>> endobj
+2480 0 obj <<
+/D [2472 0 R /XYZ 71.731 643.086 null]
+>> endobj
+2481 0 obj <<
+/D [2472 0 R /XYZ 101.6189 628.4433 null]
+>> endobj
+2482 0 obj <<
+/D [2472 0 R /XYZ 398.5807 616.7871 null]
+>> endobj
+2483 0 obj <<
+/D [2472 0 R /XYZ 71.731 614.7921 null]
+>> endobj
+2484 0 obj <<
+/D [2472 0 R /XYZ 101.6189 600.1494 null]
+>> endobj
+2485 0 obj <<
+/D [2472 0 R /XYZ 71.731 574.9495 null]
+>> endobj
+2486 0 obj <<
+/D [2472 0 R /XYZ 101.6189 560.1993 null]
+>> endobj
+1351 0 obj <<
+/D [2472 0 R /XYZ 71.731 494.0473 null]
+>> endobj
+170 0 obj <<
+/D [2472 0 R /XYZ 337.1201 458.5803 null]
+>> endobj
+2487 0 obj <<
+/D [2472 0 R /XYZ 71.731 452.4534 null]
+>> endobj
+2488 0 obj <<
+/D [2472 0 R /XYZ 353.7741 439.6513 null]
+>> endobj
+2489 0 obj <<
+/D [2472 0 R /XYZ 483.4075 439.6513 null]
+>> endobj
+2490 0 obj <<
+/D [2472 0 R /XYZ 285.3609 413.7485 null]
+>> endobj
+2491 0 obj <<
+/D [2472 0 R /XYZ 119.5329 400.797 null]
+>> endobj
+2492 0 obj <<
+/D [2472 0 R /XYZ 437.0693 400.797 null]
+>> endobj
+2493 0 obj <<
+/D [2472 0 R /XYZ 117.1591 387.8456 null]
+>> endobj
+2494 0 obj <<
+/D [2472 0 R /XYZ 419.1025 387.8456 null]
+>> endobj
+2495 0 obj <<
+/D [2472 0 R /XYZ 355.4048 374.8942 null]
+>> endobj
+2496 0 obj <<
+/D [2472 0 R /XYZ 71.731 368.1296 null]
+>> endobj
+2497 0 obj <<
+/D [2472 0 R /XYZ 115.5601 344.01 null]
+>> endobj
+2498 0 obj <<
+/D [2472 0 R /XYZ 153.5057 331.0585 null]
+>> endobj
+2499 0 obj <<
+/D [2472 0 R /XYZ 343.0163 331.0585 null]
+>> endobj
+2500 0 obj <<
+/D [2472 0 R /XYZ 71.731 318.1071 null]
+>> endobj
+2501 0 obj <<
+/D [2472 0 R /XYZ 163.7652 292.2043 null]
+>> endobj
+2502 0 obj <<
+/D [2472 0 R /XYZ 71.731 285.0661 null]
+>> endobj
+2503 0 obj <<
+/D [2472 0 R /XYZ 71.731 236.2492 null]
+>> endobj
+2504 0 obj <<
+/D [2472 0 R /XYZ 71.731 205.1308 null]
+>> endobj
+2505 0 obj <<
+/D [2472 0 R /XYZ 71.731 180.0599 null]
+>> endobj
+2506 0 obj <<
+/D [2472 0 R /XYZ 71.731 158.9041 null]
+>> endobj
+2507 0 obj <<
+/D [2472 0 R /XYZ 71.731 138.9789 null]
+>> endobj
+2508 0 obj <<
+/D [2472 0 R /XYZ 458.4786 127.3226 null]
+>> endobj
+2509 0 obj <<
+/D [2472 0 R /XYZ 207.9215 115.6663 null]
+>> endobj
+2471 0 obj <<
+/Font << /F33 1322 0 R /F44 2021 0 R /F48 2037 0 R /F23 1214 0 R /F27 1222 0 R /F35 1604 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+2512 0 obj <<
+/Length 2362
+/Filter /FlateDecode
+>>
+stream
+xڅYm "@?lR5‹k{áw(~Pl%1ƱSlnK$ 0ddCEBTTCB%Zwj~|'Y$``.ӻjEd[E2yUb8`Mp=?7`j~kO4TռI@ [ 3#:>&$ c+_oiһݨx}у|˒@Քv#W?cz,6t ^IJpmƚm1m3ҰZ3[<kYR<>k :[ mBN]{P #5`; 1eIۃw=^xdY\$6 c+zlw-55CkzjZ2*:8b &*N3h>jFKسip?/lΛ`b"V׏İFjJj*̴fgnmӎ+9g</8v4aj{h@ExH#Qe8غ-L]ͮړU&& ̲nw 5LˆK3*2S/tqwgqGND2`?Jァ/j뇫d
+
+F~ MT;~(
+96;g 
+'$r L(IঞnCן!> "3uMT|3d[w2tűh[!v_lQ鸟
+-=Kp+ToU
+*JvTM*[#u|ʰJ!
+O!]G~h;,[ jgR«guBTHvXh$PGƍ ^˕"t'pU2m<ks^Օ N#h`[^pHh=:<GYXO{[=UNҭ͊#E C9ϛ<^ 7>S
+endobj
+2511 0 obj <<
+/Type /Page
+/Contents 2512 0 R
+/Resources 2510 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 2548 0 R
+/Annots [ 2518 0 R 2521 0 R 2526 0 R 2532 0 R ]
+>> endobj
+2518 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [165.3489 650.4608 219.6449 659.3075]
+/Subtype /Link
+/A << /S /GoTo /D (security-webserver-access) >>
+>> endobj
+2521 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [290.3691 561.0113 341.3377 571.9152]
+/Subtype /Link
+/A << /S /GoTo /D (troubleshooting) >>
+>> endobj
+2526 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [471.4741 468.9565 518.5364 479.8604]
+/Subtype /Link
+/A << /S /GoTo /D (parameters) >>
+>> endobj
+2532 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [473.0423 438.0723 519.8663 448.9762]
+/Subtype /Link
+/A << /S /GoTo /D (extraconfig) >>
+>> endobj
+2513 0 obj <<
+/D [2511 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+2514 0 obj <<
+/D [2511 0 R /XYZ 71.731 718.3063 null]
+>> endobj
+2515 0 obj <<
+/D [2511 0 R /XYZ 71.731 675.3027 null]
+>> endobj
+2516 0 obj <<
+/D [2511 0 R /XYZ 358.1766 664.5081 null]
+>> endobj
+2517 0 obj <<
+/D [2511 0 R /XYZ 461.0015 664.5081 null]
+>> endobj
+1352 0 obj <<
+/D [2511 0 R /XYZ 71.731 636.5131 null]
+>> endobj
+174 0 obj <<
+/D [2511 0 R /XYZ 166.6153 597.2403 null]
+>> endobj
+2519 0 obj <<
+/D [2511 0 R /XYZ 71.731 586.8753 null]
+>> endobj
+2520 0 obj <<
+/D [2511 0 R /XYZ 258.5428 577.1158 null]
+>> endobj
+2522 0 obj <<
+/D [2511 0 R /XYZ 71.731 562.0075 null]
+>> endobj
+2523 0 obj <<
+/D [2511 0 R /XYZ 71.731 547.0636 null]
+>> endobj
+2524 0 obj <<
+/D [2511 0 R /XYZ 71.731 498.0124 null]
+>> endobj
+2525 0 obj <<
+/D [2511 0 R /XYZ 321.6777 485.061 null]
+>> endobj
+2527 0 obj <<
+/D [2511 0 R /XYZ 158.3351 459.1581 null]
+>> endobj
+2528 0 obj <<
+/D [2511 0 R /XYZ 224.7455 459.1581 null]
+>> endobj
+2529 0 obj <<
+/D [2511 0 R /XYZ 369.3714 459.1581 null]
+>> endobj
+2530 0 obj <<
+/D [2511 0 R /XYZ 429.1468 459.1581 null]
+>> endobj
+2531 0 obj <<
+/D [2511 0 R /XYZ 71.731 452.02 null]
+>> endobj
+1353 0 obj <<
+/D [2511 0 R /XYZ 71.731 424.1246 null]
+>> endobj
+178 0 obj <<
+/D [2511 0 R /XYZ 381.4679 381.0271 null]
+>> endobj
+2533 0 obj <<
+/D [2511 0 R /XYZ 71.731 368.5891 null]
+>> endobj
+1354 0 obj <<
+/D [2511 0 R /XYZ 71.731 357.3111 null]
+>> endobj
+182 0 obj <<
+/D [2511 0 R /XYZ 193.7151 320.0956 null]
+>> endobj
+2534 0 obj <<
+/D [2511 0 R /XYZ 71.731 309.7306 null]
+>> endobj
+2535 0 obj <<
+/D [2511 0 R /XYZ 71.731 287.8516 null]
+>> endobj
+2536 0 obj <<
+/D [2511 0 R /XYZ 71.731 287.8516 null]
+>> endobj
+2537 0 obj <<
+/D [2511 0 R /XYZ 101.3201 278.3522 null]
+>> endobj
+2541 0 obj <<
+/D [2511 0 R /XYZ 71.731 268.1313 null]
+>> endobj
+2542 0 obj <<
+/D [2511 0 R /XYZ 416.3046 255.4381 null]
+>> endobj
+2543 0 obj <<
+/D [2511 0 R /XYZ 71.731 230.3672 null]
+>> endobj
+2544 0 obj <<
+/D [2511 0 R /XYZ 71.731 209.5243 null]
+>> endobj
+2545 0 obj <<
+/D [2511 0 R /XYZ 71.731 195.7968 null]
+>> endobj
+2546 0 obj <<
+/D [2511 0 R /XYZ 71.731 180.8529 null]
+>> endobj
+2547 0 obj <<
+/D [2511 0 R /XYZ 369.0986 159.6971 null]
+>> endobj
+1355 0 obj <<
+/D [2511 0 R /XYZ 71.731 131.8017 null]
+>> endobj
+2510 0 obj <<
+/Font << /F33 1322 0 R /F27 1222 0 R /F35 1604 0 R /F23 1214 0 R /F44 2021 0 R /F32 1230 0 R /F60 2540 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+2551 0 obj <<
+/Length 2157
+/Filter /FlateDecode
+>>
+stream
+xYݏ6߿":Dko~wM{PlM
+l__Rdv=`DQEQ$,| i 0a}/0I"kYυ^n\E.$L&D"BAؖ?{*y0_BAe74uq[4r7Ol(y?k$un`-6I6>HXV#_;44-u V׏40'%QWDP˸W_cx bt npf52#bmCtQA9J[ksдQ(d#WY; )^9JSԽeK A(8X' aCFS[ȺuםR@]YTD6J~E(VUhI9bW}7 fx6yur?E LLہxBmUBIb-uH,K4ĵn cXލ?Zٕ<K*QGu?.[Q3Ȗ)ٔiaўr 42嵾q{ydv]@AsGd<ǒ E$^@H˨@$An :}Ϸօ>q-J@拙XQs~
+W4wjƷS84u&b`,H2jf3c@T&ꮴI!2|`U
+eZy
+;\45X
+:Ӎ#6B-O)jr}\ (J]>Ўl+OmB a[L9A:F:~c2v@Hа
+Jf)Cԣz 1gvxjrŏ\FDal@Jƚou.\{Qq% iC 5 w]@gn"t|9(q\R E_3(Sw!TX$i"('K.T͉EYߤ#&8l8ӆFh,/e}e1ݺ!$?Uz)86,2ȹ%+ayjP bIz7$_ ;z돌@bjۺC>D"d 42ͳ9
+;LBo/;z}Iw] şYQkPjzgh[E3<~qEų"ISK 7 8opxUz@!uaE,
+}(`z*}#&zZd=6J!Q xe_Y |4ݷ pl]C;U
+endobj
+2550 0 obj <<
+/Type /Page
+/Contents 2551 0 R
+/Resources 2549 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 2548 0 R
+/Annots [ 2560 0 R ]
+>> endobj
+2560 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [234.9697 462.5756 288.044 473.4796]
+/Subtype /Link
+/A << /S /GoTo /D (whining) >>
+>> endobj
+2552 0 obj <<
+/D [2550 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+186 0 obj <<
+/D [2550 0 R /XYZ 234.8596 707.8408 null]
+>> endobj
+2553 0 obj <<
+/D [2550 0 R /XYZ 71.731 697.4758 null]
+>> endobj
+2554 0 obj <<
+/D [2550 0 R /XYZ 71.731 667.6267 null]
+>> endobj
+2555 0 obj <<
+/D [2550 0 R /XYZ 71.731 631.7612 null]
+>> endobj
+2556 0 obj <<
+/D [2550 0 R /XYZ 71.731 620.881 null]
+>> endobj
+2557 0 obj <<
+/D [2550 0 R /XYZ 71.731 600.9557 null]
+>> endobj
+2558 0 obj <<
+/D [2550 0 R /XYZ 369.0986 579.0239 null]
+>> endobj
+1356 0 obj <<
+/D [2550 0 R /XYZ 71.731 551.1285 null]
+>> endobj
+190 0 obj <<
+/D [2550 0 R /XYZ 168.1935 511.7561 null]
+>> endobj
+2559 0 obj <<
+/D [2550 0 R /XYZ 71.731 501.3911 null]
+>> endobj
+2561 0 obj <<
+/D [2550 0 R /XYZ 71.731 447.6964 null]
+>> endobj
+2562 0 obj <<
+/D [2550 0 R /XYZ 71.731 409.7737 null]
+>> endobj
+2563 0 obj <<
+/D [2550 0 R /XYZ 71.731 398.8934 null]
+>> endobj
+2564 0 obj <<
+/D [2550 0 R /XYZ 71.731 378.9682 null]
+>> endobj
+2565 0 obj <<
+/D [2550 0 R /XYZ 76.7123 328.7425 null]
+>> endobj
+2566 0 obj <<
+/D [2550 0 R /XYZ 71.731 308.8172 null]
+>> endobj
+2567 0 obj <<
+/D [2550 0 R /XYZ 369.0986 285.5046 null]
+>> endobj
+1357 0 obj <<
+/D [2550 0 R /XYZ 71.731 257.6092 null]
+>> endobj
+194 0 obj <<
+/D [2550 0 R /XYZ 460.1057 218.2368 null]
+>> endobj
+2568 0 obj <<
+/D [2550 0 R /XYZ 71.731 207.8718 null]
+>> endobj
+2569 0 obj <<
+/D [2550 0 R /XYZ 344.2788 198.1123 null]
+>> endobj
+2570 0 obj <<
+/D [2550 0 R /XYZ 197.3878 185.1609 null]
+>> endobj
+2571 0 obj <<
+/D [2550 0 R /XYZ 438.3495 185.1609 null]
+>> endobj
+2572 0 obj <<
+/D [2550 0 R /XYZ 474.7655 185.1609 null]
+>> endobj
+2573 0 obj <<
+/D [2550 0 R /XYZ 114.0618 172.2095 null]
+>> endobj
+2574 0 obj <<
+/D [2550 0 R /XYZ 71.731 165.0713 null]
+>> endobj
+2575 0 obj <<
+/D [2550 0 R /XYZ 428.1816 154.2767 null]
+>> endobj
+2576 0 obj <<
+/D [2550 0 R /XYZ 325.0515 141.3253 null]
+>> endobj
+2577 0 obj <<
+/D [2550 0 R /XYZ 71.731 128.3738 null]
+>> endobj
+2578 0 obj <<
+/D [2550 0 R /XYZ 71.731 121.2357 null]
+>> endobj
+2579 0 obj <<
+/D [2550 0 R /XYZ 71.731 111.273 null]
+>> endobj
+2549 0 obj <<
+/Font << /F33 1322 0 R /F23 1214 0 R /F27 1222 0 R /F35 1604 0 R /F44 2021 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+2582 0 obj <<
+/Length 2487
+/Filter /FlateDecode
+>>
+stream
+xڕ]6=OWwIMdsyDy+K:Q7ٲWX,D!-E*EGB%Z'~z"e8)˛'ׯp<Qfd&4iDlqSkNVkK%v*[pV^'n'a* # JE 2Fa{Q7Y X궭l{]}E fk.d*=`B!%@
+f߆mehvJv`x݁ZƝYb҉|%Eʟma/άTd uCt<$[vfkΔ7SRwi&qСx|gĂ?7܁X\x K| (2
+)cy!+ {@"1Mꊀu j+?Y4E#2;~fgmQ7<$3#POSd
+O V>75Anc#bZ!0|e"XFN[w0τ35n|,8!ő%y4ƹ7vC
+BJۙo{>j(2sk,j^潎#HIs=\k a楌D@?6UŜ1>$FS^?G#aV S_%OeX$*>mr џVqd;ͳОZʾOP4z~N*FA*PsQTIWpdP z%/bhJ"`6'j{m17j"G(dG藺3ЊRV*1)0>
+ޚ=mM7/пA쀞@,5N0xSN
+DtugQ|gAT{lz{T-4%3rӌo'<K咻,rcM7u'◽UE%`0\.j1[blн&XQ9ZIcvO>ؑ!=!$;
+endobj
+2581 0 obj <<
+/Type /Page
+/Contents 2582 0 R
+/Resources 2580 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 2548 0 R
+>> endobj
+2583 0 obj <<
+/D [2581 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+1358 0 obj <<
+/D [2581 0 R /XYZ 71.731 670.4857 null]
+>> endobj
+198 0 obj <<
+/D [2581 0 R /XYZ 533.8215 625.2314 null]
+>> endobj
+2584 0 obj <<
+/D [2581 0 R /XYZ 71.731 612.7934 null]
+>> endobj
+2585 0 obj <<
+/D [2581 0 R /XYZ 332.1794 564.8179 null]
+>> endobj
+2586 0 obj <<
+/D [2581 0 R /XYZ 135.5067 551.8665 null]
+>> endobj
+2587 0 obj <<
+/D [2581 0 R /XYZ 442.8346 551.8665 null]
+>> endobj
+2588 0 obj <<
+/D [2581 0 R /XYZ 186.5563 538.915 null]
+>> endobj
+2589 0 obj <<
+/D [2581 0 R /XYZ 371.7975 538.915 null]
+>> endobj
+2590 0 obj <<
+/D [2581 0 R /XYZ 192.5465 525.9636 null]
+>> endobj
+2591 0 obj <<
+/D [2581 0 R /XYZ 71.731 518.8255 null]
+>> endobj
+2592 0 obj <<
+/D [2581 0 R /XYZ 381.8209 508.0309 null]
+>> endobj
+2593 0 obj <<
+/D [2581 0 R /XYZ 156.8057 495.0794 null]
+>> endobj
+2594 0 obj <<
+/D [2581 0 R /XYZ 282.5705 495.0794 null]
+>> endobj
+2595 0 obj <<
+/D [2581 0 R /XYZ 190.7139 482.128 null]
+>> endobj
+2596 0 obj <<
+/D [2581 0 R /XYZ 71.731 474.9898 null]
+>> endobj
+2597 0 obj <<
+/D [2581 0 R /XYZ 71.731 426.1729 null]
+>> endobj
+2598 0 obj <<
+/D [2581 0 R /XYZ 71.731 342.1529 null]
+>> endobj
+1359 0 obj <<
+/D [2581 0 R /XYZ 71.731 322.0633 null]
+>> endobj
+202 0 obj <<
+/D [2581 0 R /XYZ 350.1354 278.9659 null]
+>> endobj
+2599 0 obj <<
+/D [2581 0 R /XYZ 71.731 266.7946 null]
+>> endobj
+2600 0 obj <<
+/D [2581 0 R /XYZ 71.731 224.3657 null]
+>> endobj
+2601 0 obj <<
+/D [2581 0 R /XYZ 440.4154 213.5711 null]
+>> endobj
+1360 0 obj <<
+/D [2581 0 R /XYZ 71.731 198.4628 null]
+>> endobj
+206 0 obj <<
+/D [2581 0 R /XYZ 242.6208 161.2473 null]
+>> endobj
+2602 0 obj <<
+/D [2581 0 R /XYZ 71.731 153.895 null]
+>> endobj
+2603 0 obj <<
+/D [2581 0 R /XYZ 411.4148 115.2199 null]
+>> endobj
+1361 0 obj <<
+/D [2581 0 R /XYZ 71.731 100.1116 null]
+>> endobj
+2580 0 obj <<
+/Font << /F33 1322 0 R /F35 1604 0 R /F23 1214 0 R /F27 1222 0 R /F32 1230 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+2606 0 obj <<
+/Length 2012
+/Filter /FlateDecode
+>>
+stream
+xڭk8|ڊ,9 &p(1Rp, TKfYLGB~{1J8ůZ
+Q*-8EI=˴y_OL7U"%{_CYU~ڪ*^9љ(r x#ֵJϢH l^W ɧD""A<bk|kBP6TBU"im:czU<벦ӕoJ[ _=A׃ŋQC9U&r7 i&(p{f
+
+V.#:(cy˾_OXuՔ~qn4BmSUt,UwMRa:5TI*eg($@IHD."-}o8LpJ6DJ,THf09'*:@eKsqMEŸuZ+;E$M|qPN=ϔ7'=GwK\ށ !qY6. I]fd((vY`;{UMqu%2 zfsLOӯW^Hy*S@L
+R8DNU<)IJ\!cʿU:5PUJv'grPB mX5+_sP:׳$KuqXTkjgrDP`J\ RxRvۗ&km5|jG3cyBib&z_W@/ S
+A@#S~Eu?.yF Le:I(T
+
+pN;
+֏PԡE
+7- ^ N8I]aMSd6i3}[½<*o 5XOw%z|G;c3xpV#`
+"/ݥ鱆%{vzpHPQQV";'t?]qYI5G Ȩ;\*Oe6QyS([7vLT?< ple#-tt?wqGYqvzmz~rjG|`xQ_.(3>{#L{!@خaH?gwwW/ 'M=6܉|ľ{c.Ƶf}pJ}?;BQZ =) ?`?$endstream
+endobj
+2605 0 obj <<
+/Type /Page
+/Contents 2606 0 R
+/Resources 2604 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 2548 0 R
+/Annots [ 2614 0 R 2615 0 R ]
+>> endobj
+2614 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [329.242 539.8108 384.2731 550.7147]
+/Subtype /Link
+/A << /S /GoTo /D (install-perlmodules) >>
+>> endobj
+2615 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [90.1929 526.8593 113.057 537.7633]
+/Subtype /Link
+/A << /S /GoTo /D (gloss-ppm) >>
+>> endobj
+2607 0 obj <<
+/D [2605 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+210 0 obj <<
+/D [2605 0 R /XYZ 175.7034 708.3437 null]
+>> endobj
+2608 0 obj <<
+/D [2605 0 R /XYZ 71.731 702.2168 null]
+>> endobj
+2609 0 obj <<
+/D [2605 0 R /XYZ 231.7149 689.4147 null]
+>> endobj
+2610 0 obj <<
+/D [2605 0 R /XYZ 131.5513 676.4632 null]
+>> endobj
+2611 0 obj <<
+/D [2605 0 R /XYZ 71.731 661.355 null]
+>> endobj
+2612 0 obj <<
+/D [2605 0 R /XYZ 71.731 646.411 null]
+>> endobj
+1362 0 obj <<
+/D [2605 0 R /XYZ 71.731 597.3599 null]
+>> endobj
+214 0 obj <<
+/D [2605 0 R /XYZ 245.4492 561.8929 null]
+>> endobj
+2613 0 obj <<
+/D [2605 0 R /XYZ 71.731 555.7659 null]
+>> endobj
+2616 0 obj <<
+/D [2605 0 R /XYZ 71.731 504.9415 null]
+>> endobj
+2617 0 obj <<
+/D [2605 0 R /XYZ 120.1494 495.4421 null]
+>> endobj
+2618 0 obj <<
+/D [2605 0 R /XYZ 71.731 473.8232 null]
+>> endobj
+2619 0 obj <<
+/D [2605 0 R /XYZ 71.731 435.8008 null]
+>> endobj
+2620 0 obj <<
+/D [2605 0 R /XYZ 71.731 435.8008 null]
+>> endobj
+2621 0 obj <<
+/D [2605 0 R /XYZ 71.731 404.6824 null]
+>> endobj
+2622 0 obj <<
+/D [2605 0 R /XYZ 71.731 366.6601 null]
+>> endobj
+2623 0 obj <<
+/D [2605 0 R /XYZ 71.731 366.6601 null]
+>> endobj
+2624 0 obj <<
+/D [2605 0 R /XYZ 71.731 345.5044 null]
+>> endobj
+2625 0 obj <<
+/D [2605 0 R /XYZ 71.731 325.5791 null]
+>> endobj
+2626 0 obj <<
+/D [2605 0 R /XYZ 71.731 302.0728 null]
+>> endobj
+2627 0 obj <<
+/D [2605 0 R /XYZ 71.731 302.0728 null]
+>> endobj
+2628 0 obj <<
+/D [2605 0 R /XYZ 76.7123 244.7223 null]
+>> endobj
+2629 0 obj <<
+/D [2605 0 R /XYZ 71.731 224.797 null]
+>> endobj
+2630 0 obj <<
+/D [2605 0 R /XYZ 91.6563 189.8282 null]
+>> endobj
+2631 0 obj <<
+/D [2605 0 R /XYZ 76.7123 173.1906 null]
+>> endobj
+2632 0 obj <<
+/D [2605 0 R /XYZ 71.731 153.2653 null]
+>> endobj
+2604 0 obj <<
+/Font << /F33 1322 0 R /F23 1214 0 R /F27 1222 0 R /F44 2021 0 R /F35 1604 0 R /F60 2540 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+2636 0 obj <<
+/Length 2316
+/Filter /FlateDecode
+>>
+stream
+xڕk۸{~XӒg)I$ˡ)Dۺ%UupHl+Lj8 f.Y챘̏B^,Y~\Z|4z; g1Yzl˹݋FvCUDYՎw-R91K ]k}><
+ SY}8*;q6"CvꌩM.sFkyJY^(3CdlD?=-d }l o%ᩚ 8Rif8S5$Ueַ:QhWM2=Yqz2S=`-h㸻dFw%EOX/IqE 0n5ƶnhv;-]#}wG6&΄1lu_2' y}ve~\90ߗbzAkjd~*S|)\<a E`] aYy
+ Q#4a~妨l 1"#J,d)R,.(̬x蔌V4>YO5Lm%u2Tc)nNb+) c(
+#N1E+eִo3!J/nj,8L|nY<2•:9:h{xN^3b\Tdl*7a6e YR?"--45f)EqI+hp]BN,淖?7:$jOjLM RCk55Qrl 8dX$&lɛ`eLcKxh(]J^o뮅X$1y[o۟߮Vwiv,K==NoF1^̱P 17ӓ o v!qgɸdya2KLSe#9)$r@iU{
+S [+)sJܯ D J8[hLWj8j3'4Z8 `G~0jk򡣽;|m]0bGVy Aa줎 q$P{NI_=JmnfkN}ԿwW;:gչN<kdY6%e*vU>.;yx
+)uDF($y>=g@@PXx;>GxX 88^6C4b=t6X/TH+nz2DjZC|-rmxQMt?ijYL'P2 Cq:쬙5~7[jֵYQsCZ4p@Qx% ]
+^Cqo͞*?f)znH
+227RF!@- DҕN4>(JumK*3uPM*ʐjEEnA_4AXȀ]L_S7HVw(x[kg89mU?
+endobj
+2635 0 obj <<
+/Type /Page
+/Contents 2636 0 R
+/Resources 2634 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 2548 0 R
+/Annots [ 2639 0 R 2640 0 R 2660 0 R ]
+>> endobj
+2639 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [138.5297 660.3587 192.8257 671.2627]
+/Subtype /Link
+/A << /S /GoTo /D (security-webserver-access) >>
+>> endobj
+2640 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [477.0575 660.3587 531.3535 671.2627]
+/Subtype /Link
+/A << /S /GoTo /D (http) >>
+>> endobj
+2660 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [243.4853 115.6663 270.6434 124.5329]
+/Subtype /Link
+/A << /S /GoTo /D (gloss-cpan) >>
+>> endobj
+2637 0 obj <<
+/D [2635 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+1363 0 obj <<
+/D [2635 0 R /XYZ 71.731 718.3063 null]
+>> endobj
+218 0 obj <<
+/D [2635 0 R /XYZ 244.6122 708.3437 null]
+>> endobj
+2638 0 obj <<
+/D [2635 0 R /XYZ 71.731 699.7062 null]
+>> endobj
+2641 0 obj <<
+/D [2635 0 R /XYZ 71.731 661.355 null]
+>> endobj
+2642 0 obj <<
+/D [2635 0 R /XYZ 71.731 646.411 null]
+>> endobj
+2643 0 obj <<
+/D [2635 0 R /XYZ 215.4742 636.9116 null]
+>> endobj
+2644 0 obj <<
+/D [2635 0 R /XYZ 91.6563 625.2553 null]
+>> endobj
+2645 0 obj <<
+/D [2635 0 R /XYZ 71.731 601.6414 null]
+>> endobj
+2646 0 obj <<
+/D [2635 0 R /XYZ 128.3551 571.5268 null]
+>> endobj
+2647 0 obj <<
+/D [2635 0 R /XYZ 71.731 559.6768 null]
+>> endobj
+2648 0 obj <<
+/D [2635 0 R /XYZ 109.9956 529.4545 null]
+>> endobj
+1364 0 obj <<
+/D [2635 0 R /XYZ 71.731 489.9029 null]
+>> endobj
+222 0 obj <<
+/D [2635 0 R /XYZ 197.3181 454.4359 null]
+>> endobj
+2649 0 obj <<
+/D [2635 0 R /XYZ 71.731 445.7984 null]
+>> endobj
+1365 0 obj <<
+/D [2635 0 R /XYZ 71.731 406.1719 null]
+>> endobj
+226 0 obj <<
+/D [2635 0 R /XYZ 177.7907 368.2391 null]
+>> endobj
+2650 0 obj <<
+/D [2635 0 R /XYZ 71.731 360.8868 null]
+>> endobj
+1366 0 obj <<
+/D [2635 0 R /XYZ 71.731 345.9577 null]
+>> endobj
+230 0 obj <<
+/D [2635 0 R /XYZ 168.0881 313.6438 null]
+>> endobj
+2651 0 obj <<
+/D [2635 0 R /XYZ 71.731 307.5169 null]
+>> endobj
+2652 0 obj <<
+/D [2635 0 R /XYZ 187.7954 294.7148 null]
+>> endobj
+1367 0 obj <<
+/D [2635 0 R /XYZ 71.731 261.6738 null]
+>> endobj
+234 0 obj <<
+/D [2635 0 R /XYZ 331.1663 228.3636 null]
+>> endobj
+2653 0 obj <<
+/D [2635 0 R /XYZ 71.731 222.2367 null]
+>> endobj
+2654 0 obj <<
+/D [2635 0 R /XYZ 71.731 202.2965 null]
+>> endobj
+2655 0 obj <<
+/D [2635 0 R /XYZ 180.715 191.5019 null]
+>> endobj
+2656 0 obj <<
+/D [2635 0 R /XYZ 316.8407 191.5019 null]
+>> endobj
+2657 0 obj <<
+/D [2635 0 R /XYZ 71.731 171.4123 null]
+>> endobj
+2658 0 obj <<
+/D [2635 0 R /XYZ 86.3959 147.6662 null]
+>> endobj
+2659 0 obj <<
+/D [2635 0 R /XYZ 71.731 140.5281 null]
+>> endobj
+2661 0 obj <<
+/D [2635 0 R /XYZ 71.731 116.6626 null]
+>> endobj
+2634 0 obj <<
+/Font << /F33 1322 0 R /F23 1214 0 R /F27 1222 0 R /F44 2021 0 R /F35 1604 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+2665 0 obj <<
+/Length 2471
+/Filter /FlateDecode
+>>
+stream
+xڕY_۸ϧ0ЇŚ+RSa\[${ ćhVmaesf8,brf8Y
+<#R\0m)j4m[uo~Z;a Կh^/uig`TDh`Y'`_0
+Skߝ2{)"fi(Zpbd~h" /D<Ab:/[Y{ )U=ª4hdvVh1E+=049j'V|^ך 0r]mjI Q!u(TfK%wV9i> $yjT9Z'D9NNce/^iGW%qU VEOLյ4v0tRENk:BF|,|I <v {Ox-L@H1+eiuUMYwk9/NX}#)zRsYENϘ I2`gx lNb~!j"պ'PlKwCB?j)pp1: /%?8h|o#t?=tw3J 1USQF,Iz,E{J@T{)
+)B` V+=-' kFF[$%qR<摵ۗ6i`iJt1nl QIƅ!&jt4{צȜMT#VWeH#%9#Ώ^c{y 3۷ $
+翽~ѥ#Hr[a66dE 0@GG^}Ml ƯS0J1ׇ=N?cʼn
+f9!iHg dm4zˮm&Q} KEb+FY_vgOQ+M64&@MDp=jmn=1{.$^慆gc'[>g~ѥ q64 7
+EM0:x,dT7~<c+mʶ%e*`\Dt/E3Q
+ ̹.+ᓁ;{",3׎e-YC^ Y qbH 'q$
+/=5@.FC˰"+<o9q3 ^
+^{ufqUv;ث(ȹ<(FO`H
+WSeGdO)=}IOOPFAa=0"m % 3fܵ~9E ZMIfתnh ZhLe'\:,}<g)j{w`QƇlqu〥u~+]WP48qiCpо}
+endobj
+2664 0 obj <<
+/Type /Page
+/Contents 2665 0 R
+/Resources 2663 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 2548 0 R
+/Annots [ 2689 0 R ]
+>> endobj
+2689 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [473.6147 155.7869 520.7127 166.6908]
+/Subtype /Link
+/A << /S /GoTo /D (installation) >>
+>> endobj
+2666 0 obj <<
+/D [2664 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+2667 0 obj <<
+/D [2664 0 R /XYZ 71.731 718.3063 null]
+>> endobj
+2668 0 obj <<
+/D [2664 0 R /XYZ 121.3788 696.6874 null]
+>> endobj
+2669 0 obj <<
+/D [2664 0 R /XYZ 101.8839 685.0311 null]
+>> endobj
+2670 0 obj <<
+/D [2664 0 R /XYZ 156.232 685.0311 null]
+>> endobj
+2671 0 obj <<
+/D [2664 0 R /XYZ 254.1265 685.0311 null]
+>> endobj
+2672 0 obj <<
+/D [2664 0 R /XYZ 313.3165 685.0311 null]
+>> endobj
+2673 0 obj <<
+/D [2664 0 R /XYZ 138.3168 673.3748 null]
+>> endobj
+2674 0 obj <<
+/D [2664 0 R /XYZ 239.6353 673.3748 null]
+>> endobj
+2675 0 obj <<
+/D [2664 0 R /XYZ 71.731 645.4794 null]
+>> endobj
+2676 0 obj <<
+/D [2664 0 R /XYZ 253.294 632.528 null]
+>> endobj
+2677 0 obj <<
+/D [2664 0 R /XYZ 71.731 581.5542 null]
+>> endobj
+2681 0 obj <<
+/D [2664 0 R /XYZ 71.731 525.4296 null]
+>> endobj
+2682 0 obj <<
+/D [2664 0 R /XYZ 71.731 515.467 null]
+>> endobj
+2683 0 obj <<
+/D [2664 0 R /XYZ 71.731 477.4447 null]
+>> endobj
+2684 0 obj <<
+/D [2664 0 R /XYZ 390.5821 461.6688 null]
+>> endobj
+1466 0 obj <<
+/D [2664 0 R /XYZ 71.731 431.6165 null]
+>> endobj
+238 0 obj <<
+/D [2664 0 R /XYZ 241.9033 394.401 null]
+>> endobj
+2685 0 obj <<
+/D [2664 0 R /XYZ 71.731 387.0487 null]
+>> endobj
+2686 0 obj <<
+/D [2664 0 R /XYZ 71.731 328.284 null]
+>> endobj
+2687 0 obj <<
+/D [2664 0 R /XYZ 456.3251 304.538 null]
+>> endobj
+1467 0 obj <<
+/D [2664 0 R /XYZ 71.731 274.4858 null]
+>> endobj
+242 0 obj <<
+/D [2664 0 R /XYZ 381.2953 231.3883 null]
+>> endobj
+1468 0 obj <<
+/D [2664 0 R /XYZ 71.731 227.8248 null]
+>> endobj
+246 0 obj <<
+/D [2664 0 R /XYZ 195.0063 192.0159 null]
+>> endobj
+2688 0 obj <<
+/D [2664 0 R /XYZ 71.731 184.6636 null]
+>> endobj
+1469 0 obj <<
+/D [2664 0 R /XYZ 71.731 138.8504 null]
+>> endobj
+2663 0 obj <<
+/Font << /F33 1322 0 R /F23 1214 0 R /F44 2021 0 R /F35 1604 0 R /F27 1222 0 R /F64 2680 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+2692 0 obj <<
+/Length 1692
+/Filter /FlateDecode
+>>
+stream
+xڽ]6콿oKbImq6t
+Sנ]J5QP<TvXE+h<)OiLGL“]P1
+B2E2AvV%gy>oUy{6^pnUeb@=V]J? )G,,K)/{F]>/C2
+}O7?\A0F
+endobj
+2691 0 obj <<
+/Type /Page
+/Contents 2692 0 R
+/Resources 2690 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 2708 0 R
+>> endobj
+2693 0 obj <<
+/D [2691 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+250 0 obj <<
+/D [2691 0 R /XYZ 161.0348 707.8408 null]
+>> endobj
+2694 0 obj <<
+/D [2691 0 R /XYZ 71.731 697.6981 null]
+>> endobj
+2695 0 obj <<
+/D [2691 0 R /XYZ 71.731 672.608 null]
+>> endobj
+2696 0 obj <<
+/D [2691 0 R /XYZ 118.5554 634.044 null]
+>> endobj
+2697 0 obj <<
+/D [2691 0 R /XYZ 281.083 625.5796 null]
+>> endobj
+2698 0 obj <<
+/D [2691 0 R /XYZ 252.4031 590.6107 null]
+>> endobj
+2699 0 obj <<
+/D [2691 0 R /XYZ 118.5554 583.6344 null]
+>> endobj
+1470 0 obj <<
+/D [2691 0 R /XYZ 71.731 550.4669 null]
+>> endobj
+254 0 obj <<
+/D [2691 0 R /XYZ 282.3071 521.8203 null]
+>> endobj
+2700 0 obj <<
+/D [2691 0 R /XYZ 71.731 519.1604 null]
+>> endobj
+258 0 obj <<
+/D [2691 0 R /XYZ 268.2114 491.4343 null]
+>> endobj
+2701 0 obj <<
+/D [2691 0 R /XYZ 71.731 484.2363 null]
+>> endobj
+2702 0 obj <<
+/D [2691 0 R /XYZ 71.731 461.382 null]
+>> endobj
+2703 0 obj <<
+/D [2691 0 R /XYZ 71.731 255.4194 null]
+>> endobj
+262 0 obj <<
+/D [2691 0 R /XYZ 228.4409 222.5426 null]
+>> endobj
+2704 0 obj <<
+/D [2691 0 R /XYZ 71.731 217.3572 null]
+>> endobj
+2705 0 obj <<
+/D [2691 0 R /XYZ 427.6193 204.6099 null]
+>> endobj
+2706 0 obj <<
+/D [2691 0 R /XYZ 387.2947 191.6585 null]
+>> endobj
+2707 0 obj <<
+/D [2691 0 R /XYZ 71.731 160.6747 null]
+>> endobj
+2690 0 obj <<
+/Font << /F33 1322 0 R /F23 1214 0 R /F27 1222 0 R /F44 2021 0 R /F48 2037 0 R /F35 1604 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+2711 0 obj <<
+/Length 1694
+/Filter /FlateDecode
+>>
+stream
+xڵXK6P"5-,aDf7m E[D[B$q+ɻ;!e<Cr8/~{>l!a Q(> l<efyfc'˳ )% x̒/,y-'OWt&B"Ol64ܕUN\p|9h%1y}xdsՏ ˜'XLexOd,{'7SNc/bfl|XRʉnMk'۲i6#<h4
+',kִJJ*J3u>bvc+2;{"Otvs*Bs9#ȅo$_Lp8117DQBla$ ʻp._l/"PK8<Pe_|et;ct[ #Ηn5w&gmnt,P
+4<ͺl[k,wijV ]ZksYX^u82xreH"a:
+<Rg*!/[(펦x܉2bM(+{F(!{(#)!E]6RLZ,Z[JW
+~%>-2Ni\R6gIG'z%1Vdd
+SSN(Δendstream
+endobj
+2710 0 obj <<
+/Type /Page
+/Contents 2711 0 R
+/Resources 2709 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 2708 0 R
+>> endobj
+2712 0 obj <<
+/D [2710 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+266 0 obj <<
+/D [2710 0 R /XYZ 199.5493 708.3437 null]
+>> endobj
+2713 0 obj <<
+/D [2710 0 R /XYZ 71.731 701.1457 null]
+>> endobj
+2714 0 obj <<
+/D [2710 0 R /XYZ 71.731 678.2915 null]
+>> endobj
+2715 0 obj <<
+/D [2710 0 R /XYZ 147.0485 668.792 null]
+>> endobj
+2716 0 obj <<
+/D [2710 0 R /XYZ 147.0485 657.1357 null]
+>> endobj
+2717 0 obj <<
+/D [2710 0 R /XYZ 71.731 635.5168 null]
+>> endobj
+2718 0 obj <<
+/D [2710 0 R /XYZ 71.731 612.5032 null]
+>> endobj
+2719 0 obj <<
+/D [2710 0 R /XYZ 147.0485 600.9464 null]
+>> endobj
+2720 0 obj <<
+/D [2710 0 R /XYZ 147.0485 589.2902 null]
+>> endobj
+2721 0 obj <<
+/D [2710 0 R /XYZ 71.731 567.6712 null]
+>> endobj
+2722 0 obj <<
+/D [2710 0 R /XYZ 361.1613 554.7198 null]
+>> endobj
+2723 0 obj <<
+/D [2710 0 R /XYZ 71.731 539.6115 null]
+>> endobj
+2724 0 obj <<
+/D [2710 0 R /XYZ 71.731 524.6676 null]
+>> endobj
+2725 0 obj <<
+/D [2710 0 R /XYZ 76.7123 475.2179 null]
+>> endobj
+2726 0 obj <<
+/D [2710 0 R /XYZ 118.5554 431.6726 null]
+>> endobj
+1471 0 obj <<
+/D [2710 0 R /XYZ 71.731 358.1577 null]
+>> endobj
+270 0 obj <<
+/D [2710 0 R /XYZ 138.2961 325.654 null]
+>> endobj
+2727 0 obj <<
+/D [2710 0 R /XYZ 71.731 318.3017 null]
+>> endobj
+2728 0 obj <<
+/D [2710 0 R /XYZ 71.731 280.4586 null]
+>> endobj
+2729 0 obj <<
+/D [2710 0 R /XYZ 114.7696 270.9592 null]
+>> endobj
+2730 0 obj <<
+/D [2710 0 R /XYZ 114.7696 259.3029 null]
+>> endobj
+2731 0 obj <<
+/D [2710 0 R /XYZ 114.7696 247.6466 null]
+>> endobj
+2732 0 obj <<
+/D [2710 0 R /XYZ 114.7696 235.9903 null]
+>> endobj
+2733 0 obj <<
+/D [2710 0 R /XYZ 114.7696 224.334 null]
+>> endobj
+2734 0 obj <<
+/D [2710 0 R /XYZ 114.7696 212.6777 null]
+>> endobj
+2735 0 obj <<
+/D [2710 0 R /XYZ 114.7696 201.0214 null]
+>> endobj
+2736 0 obj <<
+/D [2710 0 R /XYZ 114.7696 189.3652 null]
+>> endobj
+2737 0 obj <<
+/D [2710 0 R /XYZ 114.7696 177.7089 null]
+>> endobj
+2738 0 obj <<
+/D [2710 0 R /XYZ 114.7696 166.0526 null]
+>> endobj
+2739 0 obj <<
+/D [2710 0 R /XYZ 71.731 144.4337 null]
+>> endobj
+2740 0 obj <<
+/D [2710 0 R /XYZ 307.8359 131.4822 null]
+>> endobj
+1472 0 obj <<
+/D [2710 0 R /XYZ 71.731 113.4499 null]
+>> endobj
+2709 0 obj <<
+/Font << /F33 1322 0 R /F48 2037 0 R /F27 1222 0 R /F35 1604 0 R /F60 2540 0 R /F32 1230 0 R /F23 1214 0 R /F44 2021 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+2743 0 obj <<
+/Length 2523
+/Filter /FlateDecode
+>>
+stream
+xڝk۸{~Sb"귻]fܢ-
+٢mueѐ΋&ij8 "
+"QfQ&b{|{-$kYO~xx1"*8]<VQ)L?<uueʦ=8\/o0JNL9z#Kc626QðWG4V/?Z_}un\:lSX*ұMl7V-ˏ PGEgdַ瞌Pȷ[<5uply;L*jDZT^sS5kfuSscs&OC"ޯt ?.V<_urCY7=o;j0;[kFSY*+*=];CJTy^1; yZ[Y)`FIq~|xxzZ-?S9y&K4D}
+|\?,*E{^
+[!E 㜅 IIcB騜kXVըqd6&)[hd]'Ƅp@!"^n;8r8(HƩ!-TXW%<4-(a` x1<(ADbxލ6 &J2&7 ZX,OP[U;ͣhP1̩ףLP^_?AR# ƲᥫC_
+&_@m؏/ `z)J|K/ڇh*,TGT̴<C˲[{>BgWR_TCM1$/ ?}/h_i8%hRn$K)nf
+G+զ]qQY'IbǧJ/T/Mg(ltD5#.JuSW)d;Pqpexj3!S1عuAOcƄjR" t+GS4
+P(+cGiN( ݮ we%t*G-808 J.YZ)d?dH :,ODJャl
+endobj
+2742 0 obj <<
+/Type /Page
+/Contents 2743 0 R
+/Resources 2741 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 2708 0 R
+/Annots [ 2761 0 R ]
+>> endobj
+2761 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [290.6404 219.1733 344.6167 230.0772]
+/Subtype /Link
+/A << /S /GoTo /D (install-perlmodules-nonroot) >>
+>> endobj
+2744 0 obj <<
+/D [2742 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+2745 0 obj <<
+/D [2742 0 R /XYZ 71.731 741.2204 null]
+>> endobj
+274 0 obj <<
+/D [2742 0 R /XYZ 200.4719 708.1493 null]
+>> endobj
+2746 0 obj <<
+/D [2742 0 R /XYZ 71.731 700.797 null]
+>> endobj
+2747 0 obj <<
+/D [2742 0 R /XYZ 380.576 688.0248 null]
+>> endobj
+2748 0 obj <<
+/D [2742 0 R /XYZ 171.9039 675.0733 null]
+>> endobj
+2749 0 obj <<
+/D [2742 0 R /XYZ 171.9039 675.0733 null]
+>> endobj
+1473 0 obj <<
+/D [2742 0 R /XYZ 71.731 667.9352 null]
+>> endobj
+278 0 obj <<
+/D [2742 0 R /XYZ 197.8608 630.7197 null]
+>> endobj
+2750 0 obj <<
+/D [2742 0 R /XYZ 71.731 623.3673 null]
+>> endobj
+1474 0 obj <<
+/D [2742 0 R /XYZ 71.731 582.5354 null]
+>> endobj
+282 0 obj <<
+/D [2742 0 R /XYZ 284.1841 550.2215 null]
+>> endobj
+2751 0 obj <<
+/D [2742 0 R /XYZ 71.731 541.584 null]
+>> endobj
+2752 0 obj <<
+/D [2742 0 R /XYZ 481.5316 531.2925 null]
+>> endobj
+2753 0 obj <<
+/D [2742 0 R /XYZ 71.731 498.2515 null]
+>> endobj
+2754 0 obj <<
+/D [2742 0 R /XYZ 71.731 461.4545 null]
+>> endobj
+2755 0 obj <<
+/D [2742 0 R /XYZ 71.731 446.5105 null]
+>> endobj
+2756 0 obj <<
+/D [2742 0 R /XYZ 76.7123 395.0036 null]
+>> endobj
+2757 0 obj <<
+/D [2742 0 R /XYZ 118.5554 351.4582 null]
+>> endobj
+1475 0 obj <<
+/D [2742 0 R /XYZ 71.731 287.906 null]
+>> endobj
+286 0 obj <<
+/D [2742 0 R /XYZ 166.6153 255.4023 null]
+>> endobj
+2758 0 obj <<
+/D [2742 0 R /XYZ 71.731 245.0373 null]
+>> endobj
+2759 0 obj <<
+/D [2742 0 R /XYZ 131.1334 235.2778 null]
+>> endobj
+2760 0 obj <<
+/D [2742 0 R /XYZ 247.7914 235.2778 null]
+>> endobj
+2762 0 obj <<
+/D [2742 0 R /XYZ 407.9148 222.3264 null]
+>> endobj
+2763 0 obj <<
+/D [2742 0 R /XYZ 71.731 220.1695 null]
+>> endobj
+2764 0 obj <<
+/D [2742 0 R /XYZ 118.5554 181.6055 null]
+>> endobj
+2765 0 obj <<
+/D [2742 0 R /XYZ 174.165 173.1411 null]
+>> endobj
+2766 0 obj <<
+/D [2742 0 R /XYZ 173.7108 161.4848 null]
+>> endobj
+1476 0 obj <<
+/D [2742 0 R /XYZ 71.731 126.3223 null]
+>> endobj
+2741 0 obj <<
+/Font << /F33 1322 0 R /F23 1214 0 R /F27 1222 0 R /F35 1604 0 R /F32 1230 0 R /F44 2021 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+2769 0 obj <<
+/Length 2645
+/Filter /FlateDecode
+>>
+stream
+xڕ]۸=8ݮ">&E,ۺȒ QE{g8CZeo ,p8ߤ̃?9}T*TYͶWIL}Ͼ?KEh2iW" e2{Zk.ku-*JuVUe?eUe?ՏOnЏE
+ԇ (P
+my3z"qg_*gՀʋyYN tE~—Q_TMUySo)+E i$3&
+i8'R#"
+,nLc]M,"]JdyPuv3 Eq!N6^<fŴ/q
+MhnuBL!\4\P@X1
+!ܙڮɋ5NTfj|խ#d (VaS^&0T>Nr0@W?r@'gY)V^fzԼz4r
+ fpxnq5*'=+*%hsN]
+GXETLab^=Ҟ&##L*qfzm^WpK׃.Ni@pw>HAiG%nAd?.CB
+q>|_l,k2zte<uō"Tt6=%w8r925l3f9mou8'Lpn/L߻h9%/<;[&Lny9:G/pEXd*bAIbxOx,E;׏i<qhM&m"⧦q8QӇ<|W߱g]{e@UCܴ<DedGTw,o(nZ~bo]x?/"*
+PIQ 57;uz2q袦feة1tFkhBg('Y
+endobj
+2768 0 obj <<
+/Type /Page
+/Contents 2769 0 R
+/Resources 2767 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 2708 0 R
+/Annots [ 2790 0 R 2793 0 R 2794 0 R ]
+>> endobj
+2790 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [184.8991 208.6686 236.4861 219.5725]
+/Subtype /Link
+/A << /S /GoTo /D (sanitycheck) >>
+>> endobj
+2793 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [507.0988 177.7844 538.9788 188.6883]
+/Subtype /Link
+/A << /S /GoTo /D (parameters) >>
+>> endobj
+2794 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [70.7348 165.2066 103.1133 175.7369]
+/Subtype /Link
+/A << /S /GoTo /D (parameters) >>
+>> endobj
+2770 0 obj <<
+/D [2768 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+2771 0 obj <<
+/D [2768 0 R /XYZ 71.731 741.2204 null]
+>> endobj
+290 0 obj <<
+/D [2768 0 R /XYZ 259.4726 708.3437 null]
+>> endobj
+2772 0 obj <<
+/D [2768 0 R /XYZ 71.731 699.7062 null]
+>> endobj
+2773 0 obj <<
+/D [2768 0 R /XYZ 71.731 676.4632 null]
+>> endobj
+2774 0 obj <<
+/D [2768 0 R /XYZ 172.5953 676.4632 null]
+>> endobj
+2775 0 obj <<
+/D [2768 0 R /XYZ 271.7266 676.4632 null]
+>> endobj
+2776 0 obj <<
+/D [2768 0 R /XYZ 337.8826 663.5118 null]
+>> endobj
+2777 0 obj <<
+/D [2768 0 R /XYZ 71.731 650.5604 null]
+>> endobj
+1477 0 obj <<
+/D [2768 0 R /XYZ 71.731 531.0437 null]
+>> endobj
+294 0 obj <<
+/D [2768 0 R /XYZ 331.6984 487.9462 null]
+>> endobj
+2778 0 obj <<
+/D [2768 0 R /XYZ 71.731 475.5082 null]
+>> endobj
+2779 0 obj <<
+/D [2768 0 R /XYZ 71.731 446.2975 null]
+>> endobj
+2780 0 obj <<
+/D [2768 0 R /XYZ 71.731 407.4432 null]
+>> endobj
+2781 0 obj <<
+/D [2768 0 R /XYZ 71.731 392.4992 null]
+>> endobj
+2782 0 obj <<
+/D [2768 0 R /XYZ 214.8223 359.6872 null]
+>> endobj
+1478 0 obj <<
+/D [2768 0 R /XYZ 76.7123 330.0981 null]
+>> endobj
+298 0 obj <<
+/D [2768 0 R /XYZ 248.5887 290.7258 null]
+>> endobj
+2783 0 obj <<
+/D [2768 0 R /XYZ 71.731 280.3608 null]
+>> endobj
+2784 0 obj <<
+/D [2768 0 R /XYZ 71.731 268.4444 null]
+>> endobj
+2785 0 obj <<
+/D [2768 0 R /XYZ 71.731 263.4631 null]
+>> endobj
+2786 0 obj <<
+/D [2768 0 R /XYZ 89.6638 242.7059 null]
+>> endobj
+2787 0 obj <<
+/D [2768 0 R /XYZ 128.8307 242.7059 null]
+>> endobj
+2788 0 obj <<
+/D [2768 0 R /XYZ 71.731 227.5976 null]
+>> endobj
+2789 0 obj <<
+/D [2768 0 R /XYZ 89.6638 211.8217 null]
+>> endobj
+2791 0 obj <<
+/D [2768 0 R /XYZ 71.731 196.7134 null]
+>> endobj
+2792 0 obj <<
+/D [2768 0 R /XYZ 89.6638 180.9375 null]
+>> endobj
+2795 0 obj <<
+/D [2768 0 R /XYZ 71.731 166.2028 null]
+>> endobj
+2796 0 obj <<
+/D [2768 0 R /XYZ 89.6638 150.0533 null]
+>> endobj
+2797 0 obj <<
+/D [2768 0 R /XYZ 250.1301 150.0533 null]
+>> endobj
+2798 0 obj <<
+/D [2768 0 R /XYZ 71.731 134.945 null]
+>> endobj
+2767 0 obj <<
+/Font << /F33 1322 0 R /F23 1214 0 R /F27 1222 0 R /F35 1604 0 R /F32 1230 0 R /F44 2021 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+2801 0 obj <<
+/Length 2456
+/Filter /FlateDecode
+>>
+stream
+xڭYm~ȗ~(ФMzARM
+Lɢ#ɷq~}g8C8,"|捖*"
+DpoHf '2}拯hU"Uqe.,VYD|>* JmfO/֛߫_}a$DՋTYDȘUT"EUkVaZ
+*LL D$aZ$!nΟV2Q sP(*-2gr&׺mpð/V8'i,(%(ܤO -[wVQ?6&xQة:$ {#A{^lk﹙f>#!L%6^펾Kܪ;w<w)Et4A?lTF*=`?"zK-S6Dء@/l
+X1is:鶯sߙ|^]HH/ݟa(,9K%"L9( `*`CHB Kk=O |Ox=HpZyN < J"8%T<]o͋Gp$OwT/gCEON~j7d"58з_&r9 UB)ANLoLS#glø8BiD,r( '0Y]ȋXմi.\:9p`Ơ~aJ 8jwD,ik9.9Ema]Xf;6Sf.F_5D k_~ VEE#3S[~# Wgʞ%lxZ @R@\zQ5nyy.']>jX(%3Bąs=p~(C!qN*?t !GpSQrIwW!3'rj @uQ)W 6
++i_M ue12 N0zkZn9@:_%NJ׮\
+pt#hBM
+S+wEFgCQ83L0͢j-Q[빂^;"hb{zTk {LQf/搥R/lAUW;~g?ýN鶲Gm96R#$h[􄂇wb^)$FPw:ǛEs9G q#r[9Qt7l S0Z3Y7A#BU" <kJHC-8 "WIGԠE73⋇Wl1zyJ^q@Rߣ?s/fҭ*?=k3  =i_yv3=hq:T-):tCyQ
+endobj
+2800 0 obj <<
+/Type /Page
+/Contents 2801 0 R
+/Resources 2799 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 2708 0 R
+/Annots [ 2816 0 R 2821 0 R 2825 0 R 2830 0 R ]
+>> endobj
+2816 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [95.9201 421.6299 157.6881 432.1602]
+/Subtype /Link
+/A << /S /GoTo /D (upgrade-cvs) >>
+>> endobj
+2821 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [160.4076 367.8316 222.1755 378.3619]
+/Subtype /Link
+/A << /S /GoTo /D (upgrade-tarball) >>
+>> endobj
+2825 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [106.829 314.0333 168.597 324.5637]
+/Subtype /Link
+/A << /S /GoTo /D (upgrade-patches) >>
+>> endobj
+2830 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [106.7097 187.5975 161.0057 196.4441]
+/Subtype /Link
+/A << /S /GoTo /D (template-method) >>
+>> endobj
+2802 0 obj <<
+/D [2800 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+2803 0 obj <<
+/D [2800 0 R /XYZ 136.4882 689.7049 null]
+>> endobj
+2804 0 obj <<
+/D [2800 0 R /XYZ 71.731 619.2338 null]
+>> endobj
+2805 0 obj <<
+/D [2800 0 R /XYZ 71.731 604.2899 null]
+>> endobj
+2806 0 obj <<
+/D [2800 0 R /XYZ 71.731 591.3385 null]
+>> endobj
+2807 0 obj <<
+/D [2800 0 R /XYZ 109.5891 575.5625 null]
+>> endobj
+2808 0 obj <<
+/D [2800 0 R /XYZ 109.5891 575.5625 null]
+>> endobj
+2809 0 obj <<
+/D [2800 0 R /XYZ 71.731 563.5775 null]
+>> endobj
+2810 0 obj <<
+/D [2800 0 R /XYZ 71.731 550.4916 null]
+>> endobj
+2811 0 obj <<
+/D [2800 0 R /XYZ 109.5891 534.7157 null]
+>> endobj
+2812 0 obj <<
+/D [2800 0 R /XYZ 109.5891 534.7157 null]
+>> endobj
+1479 0 obj <<
+/D [2800 0 R /XYZ 71.731 511.8016 null]
+>> endobj
+302 0 obj <<
+/D [2800 0 R /XYZ 283.5785 472.4293 null]
+>> endobj
+2813 0 obj <<
+/D [2800 0 R /XYZ 71.731 462.0643 null]
+>> endobj
+2814 0 obj <<
+/D [2800 0 R /XYZ 71.731 450.1479 null]
+>> endobj
+2815 0 obj <<
+/D [2800 0 R /XYZ 71.731 435.204 null]
+>> endobj
+2817 0 obj <<
+/D [2800 0 R /XYZ 71.731 422.6261 null]
+>> endobj
+2818 0 obj <<
+/D [2800 0 R /XYZ 91.6563 406.4766 null]
+>> endobj
+2819 0 obj <<
+/D [2800 0 R /XYZ 121.0649 406.4766 null]
+>> endobj
+2820 0 obj <<
+/D [2800 0 R /XYZ 71.731 381.4057 null]
+>> endobj
+2822 0 obj <<
+/D [2800 0 R /XYZ 71.731 368.8279 null]
+>> endobj
+2823 0 obj <<
+/D [2800 0 R /XYZ 91.6563 352.6783 null]
+>> endobj
+2824 0 obj <<
+/D [2800 0 R /XYZ 71.731 327.6074 null]
+>> endobj
+2826 0 obj <<
+/D [2800 0 R /XYZ 71.731 315.0296 null]
+>> endobj
+2827 0 obj <<
+/D [2800 0 R /XYZ 91.6563 298.8801 null]
+>> endobj
+2828 0 obj <<
+/D [2800 0 R /XYZ 71.731 278.7905 null]
+>> endobj
+1480 0 obj <<
+/D [2800 0 R /XYZ 71.731 265.8391 null]
+>> endobj
+306 0 obj <<
+/D [2800 0 R /XYZ 308.1414 233.5252 null]
+>> endobj
+2829 0 obj <<
+/D [2800 0 R /XYZ 71.731 224.8877 null]
+>> endobj
+2831 0 obj <<
+/D [2800 0 R /XYZ 71.731 183.6124 null]
+>> endobj
+1481 0 obj <<
+/D [2800 0 R /XYZ 71.731 111.8166 null]
+>> endobj
+2799 0 obj <<
+/Font << /F33 1322 0 R /F23 1214 0 R /F44 2021 0 R /F27 1222 0 R /F32 1230 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+2834 0 obj <<
+/Length 1976
+/Filter /FlateDecode
+>>
+stream
+xڵXݏB.֔Hsܵ 8t/@Ym5H}{}g8V:{d?Pp ǽD 2&HxUaWܱlf7)eeI D
+F<֨nQwMo򺮚= /U]_&͑LXO7q=6PHsE@ dz)L}%L0kX>./.ue2;sm"{~I/C);+s @CJRqr9GD)3P5),5ZʛYfSNd[;;vP}?ٶ
+'큏UnE+vLy \VwDX+|}{hۢ5AGq;wv4)Na%#p0]Uj-OA `RNkGcx,tBdJ
+oF#$
+*E%Xt]kH=MÝp3#\cn*1~qh8)nWuNFr7N)*@YUoWd-0&6 ǡ&YSJmDlq0c;r`M+w|XYEв7P(2LRV}<{z gGo[pIuyo1(fya!gjF-ۼϺ+o(YDL$rzR1quǞW'wm7?cչQȵ4gٛm'!>qX~x| ⠊{e5|0!OrN՟ f-UjCfuv3 ef/6ċT`(EIȽ0,̂h=2)o7Ied# ͤdq.BˍmdAI/2$[uAAÆ ,Ilay
+l^9L.WW]+3I 'jw]B9' d6sP;~;lBh]jA`pl
+Bn?"o _yHl`i+O.khᒇ gSL74EnЗ~")R| ?@&
+XV-\+ P$`c
+x?wԗvL9j{ፏ.gLp 0e)'g<9,M+
+endstream
+endobj
+2833 0 obj <<
+/Type /Page
+/Contents 2834 0 R
+/Resources 2832 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 2708 0 R
+>> endobj
+2835 0 obj <<
+/D [2833 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+2836 0 obj <<
+/D [2833 0 R /XYZ 71.731 741.2204 null]
+>> endobj
+310 0 obj <<
+/D [2833 0 R /XYZ 237.8221 708.3437 null]
+>> endobj
+2837 0 obj <<
+/D [2833 0 R /XYZ 71.731 699.7062 null]
+>> endobj
+2838 0 obj <<
+/D [2833 0 R /XYZ 71.731 658.431 null]
+>> endobj
+2839 0 obj <<
+/D [2833 0 R /XYZ 71.731 622.5654 null]
+>> endobj
+2840 0 obj <<
+/D [2833 0 R /XYZ 104.0099 611.0087 null]
+>> endobj
+2841 0 obj <<
+/D [2833 0 R /XYZ 104.0099 599.3524 null]
+>> endobj
+2842 0 obj <<
+/D [2833 0 R /XYZ 147.0485 576.0398 null]
+>> endobj
+2843 0 obj <<
+/D [2833 0 R /XYZ 104.0099 564.3836 null]
+>> endobj
+2844 0 obj <<
+/D [2833 0 R /XYZ 71.731 516.3777 null]
+>> endobj
+2845 0 obj <<
+/D [2833 0 R /XYZ 71.731 494.4458 null]
+>> endobj
+2846 0 obj <<
+/D [2833 0 R /XYZ 118.5554 453.9132 null]
+>> endobj
+2847 0 obj <<
+/D [2833 0 R /XYZ 225.6892 442.4361 null]
+>> endobj
+2848 0 obj <<
+/D [2833 0 R /XYZ 332.3173 442.4361 null]
+>> endobj
+1482 0 obj <<
+/D [2833 0 R /XYZ 71.731 397.2033 null]
+>> endobj
+314 0 obj <<
+/D [2833 0 R /XYZ 270.3754 368.5567 null]
+>> endobj
+2849 0 obj <<
+/D [2833 0 R /XYZ 71.731 359.9192 null]
+>> endobj
+2850 0 obj <<
+/D [2833 0 R /XYZ 86.3959 336.6763 null]
+>> endobj
+2851 0 obj <<
+/D [2833 0 R /XYZ 71.731 329.5381 null]
+>> endobj
+2852 0 obj <<
+/D [2833 0 R /XYZ 401.1475 305.7921 null]
+>> endobj
+2853 0 obj <<
+/D [2833 0 R /XYZ 71.731 280.7212 null]
+>> endobj
+2854 0 obj <<
+/D [2833 0 R /XYZ 104.0099 271.2217 null]
+>> endobj
+2855 0 obj <<
+/D [2833 0 R /XYZ 104.0099 259.5654 null]
+>> endobj
+2856 0 obj <<
+/D [2833 0 R /XYZ 71.731 258.3505 null]
+>> endobj
+2857 0 obj <<
+/D [2833 0 R /XYZ 104.0099 236.2529 null]
+>> endobj
+2858 0 obj <<
+/D [2833 0 R /XYZ 71.731 211.5595 null]
+>> endobj
+2859 0 obj <<
+/D [2833 0 R /XYZ 104.0099 189.6277 null]
+>> endobj
+2860 0 obj <<
+/D [2833 0 R /XYZ 104.0099 177.9714 null]
+>> endobj
+2861 0 obj <<
+/D [2833 0 R /XYZ 104.0099 166.3151 null]
+>> endobj
+2862 0 obj <<
+/D [2833 0 R /XYZ 104.0099 154.6589 null]
+>> endobj
+2863 0 obj <<
+/D [2833 0 R /XYZ 104.0099 143.0026 null]
+>> endobj
+2864 0 obj <<
+/D [2833 0 R /XYZ 104.0099 131.3463 null]
+>> endobj
+2865 0 obj <<
+/D [2833 0 R /XYZ 71.731 119.69 null]
+>> endobj
+2832 0 obj <<
+/Font << /F33 1322 0 R /F23 1214 0 R /F27 1222 0 R /F35 1604 0 R /F60 2540 0 R /F54 2305 0 R /F44 2021 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+2868 0 obj <<
+/Length 2440
+/Filter /FlateDecode
+>>
+stream
+xڵˎ>_C-z.rٙda`: lEXHx{>U,R-jdbX'W!*,9I,V]:q8`G)W9g,OCJ`Y̳SǓj6õ`TFe}<O|s,Sg@xXf*1+\E LdYxa8aI%ayvdq(sE BxdT 9qWIjR6QV]GCn+I" pC4N 29N$8vȚc4U2]cN4uAKI nx.b=/rﶔ׍׸EgQ
+sJPEiF"ͬtpN8lxn|_
+y4}Ņ1O` EO!$A*$N o/v{2y3.Uw st0-o@m;ELӜQ
+endobj
+2867 0 obj <<
+/Type /Page
+/Contents 2868 0 R
+/Resources 2866 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 2899 0 R
+/Annots [ 2886 0 R 2895 0 R ]
+>> endobj
+2886 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [270.8639 294.4016 332.6702 304.9839]
+/Subtype /Link
+/A << /S /GoTo /D (upgrade-cvs) >>
+>> endobj
+2895 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [300.478 138.1053 346.8734 149.0092]
+/Subtype /Link
+/A << /S /GoTo /D (configuration) >>
+>> endobj
+2869 0 obj <<
+/D [2867 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+2870 0 obj <<
+/D [2867 0 R /XYZ 118.5554 684.7236 null]
+>> endobj
+2871 0 obj <<
+/D [2867 0 R /XYZ 137.5117 676.2592 null]
+>> endobj
+1483 0 obj <<
+/D [2867 0 R /XYZ 71.731 604.9444 null]
+>> endobj
+318 0 obj <<
+/D [2867 0 R /XYZ 258.3011 569.5769 null]
+>> endobj
+2872 0 obj <<
+/D [2867 0 R /XYZ 71.731 560.9394 null]
+>> endobj
+2873 0 obj <<
+/D [2867 0 R /XYZ 71.731 543.5098 null]
+>> endobj
+2874 0 obj <<
+/D [2867 0 R /XYZ 473.4713 519.7637 null]
+>> endobj
+2875 0 obj <<
+/D [2867 0 R /XYZ 71.731 499.6741 null]
+>> endobj
+2876 0 obj <<
+/D [2867 0 R /XYZ 71.731 465.8659 null]
+>> endobj
+2877 0 obj <<
+/D [2867 0 R /XYZ 104.0099 454.3092 null]
+>> endobj
+2878 0 obj <<
+/D [2867 0 R /XYZ 104.0099 442.6529 null]
+>> endobj
+2879 0 obj <<
+/D [2867 0 R /XYZ 71.731 441.438 null]
+>> endobj
+2880 0 obj <<
+/D [2867 0 R /XYZ 104.0099 419.3403 null]
+>> endobj
+2881 0 obj <<
+/D [2867 0 R /XYZ 104.0099 407.684 null]
+>> endobj
+2882 0 obj <<
+/D [2867 0 R /XYZ 71.731 382.9907 null]
+>> endobj
+2883 0 obj <<
+/D [2867 0 R /XYZ 71.731 361.0589 null]
+>> endobj
+2884 0 obj <<
+/D [2867 0 R /XYZ 118.5554 317.5135 null]
+>> endobj
+2885 0 obj <<
+/D [2867 0 R /XYZ 421.5762 309.0491 null]
+>> endobj
+1484 0 obj <<
+/D [2867 0 R /XYZ 71.731 265.51 null]
+>> endobj
+322 0 obj <<
+/D [2867 0 R /XYZ 287.9255 233.1139 null]
+>> endobj
+2887 0 obj <<
+/D [2867 0 R /XYZ 71.731 222.7489 null]
+>> endobj
+2888 0 obj <<
+/D [2867 0 R /XYZ 71.731 210.8325 null]
+>> endobj
+2889 0 obj <<
+/D [2867 0 R /XYZ 71.731 205.8512 null]
+>> endobj
+2890 0 obj <<
+/D [2867 0 R /XYZ 89.6638 185.094 null]
+>> endobj
+2891 0 obj <<
+/D [2867 0 R /XYZ 278.683 172.1426 null]
+>> endobj
+2892 0 obj <<
+/D [2867 0 R /XYZ 371.2702 172.1426 null]
+>> endobj
+2893 0 obj <<
+/D [2867 0 R /XYZ 71.731 157.0343 null]
+>> endobj
+2894 0 obj <<
+/D [2867 0 R /XYZ 89.6638 141.2584 null]
+>> endobj
+2896 0 obj <<
+/D [2867 0 R /XYZ 71.731 126.1501 null]
+>> endobj
+2897 0 obj <<
+/D [2867 0 R /XYZ 89.6638 110.3742 null]
+>> endobj
+2898 0 obj <<
+/D [2867 0 R /XYZ 417.8838 110.3742 null]
+>> endobj
+2866 0 obj <<
+/Font << /F33 1322 0 R /F23 1214 0 R /F44 2021 0 R /F27 1222 0 R /F35 1604 0 R /F60 2540 0 R /F54 2305 0 R /F32 1230 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+2902 0 obj <<
+/Length 1989
+/Filter /FlateDecode
+>>
+stream
+xڝXKϯ!@dMCҞ3$lr6C6X-VFIίOG'AY,~7-"% Y2d-,,3}|,eRd&y$J̹LrB"y~2<_aMS[xn>q:Y˜uN
+%3FI &=';~>>c;dyQ ȵKyfj7 Y˸)30ZK)LL. '4Lb"XYf:]6d8:=p9r:bi$!
+^d<=SfcXq_=9Vh1m
+ӸwKkĎUݤbs͸1WB]u0׻d#- yE^gc'ly( MkigǃLK7,~uĵDt9ml=ԁa%t @A)3Ż
+EFb Ex<׸0FH9 A;# j ȧs_"l}G(W]V{Sxql2hY $Rz?׺A% >:bg%&Hh&pTrk$ɞtT;Z(wNu ]w@?`[ѺB@)֭0ؾ
+T1]˯v`Îx- \t%5QOnbȪ1ܨN78DsvG;W%]΁tt9Pm/<?4Fg1R&'StO',8 @PgWeTŦl֍NAT01c9SZe9Z5Mk ﶆHm¢#wuPn7 fLx˸
+ 'PZ:}EOuC韇F %zh[ގ0sm iNywD!Q87G |azڞEGp!wIH!1Q^%4H褕
+Tm5 ܤ bk׎8U Sd _B:; A^Y>,%C蔝o-e
+hA\6436t?}K&;
+awr\⪳qQ$<S7Eqnendstream
+endobj
+2901 0 obj <<
+/Type /Page
+/Contents 2902 0 R
+/Resources 2900 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 2899 0 R
+/Annots [ 2917 0 R 2921 0 R ]
+>> endobj
+2917 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [185.7724 505.4277 237.5777 516.3316]
+/Subtype /Link
+/A << /S /GoTo /D (sanitycheck) >>
+>> endobj
+2921 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [242.3291 358.2596 289.7803 369.1635]
+/Subtype /Link
+/A << /S /GoTo /D (parameters) >>
+>> endobj
+2903 0 obj <<
+/D [2901 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+2904 0 obj <<
+/D [2901 0 R /XYZ 71.731 718.3063 null]
+>> endobj
+2905 0 obj <<
+/D [2901 0 R /XYZ 121.9427 708.3437 null]
+>> endobj
+2906 0 obj <<
+/D [2901 0 R /XYZ 121.9427 696.6874 null]
+>> endobj
+2907 0 obj <<
+/D [2901 0 R /XYZ 71.731 685.0311 null]
+>> endobj
+2908 0 obj <<
+/D [2901 0 R /XYZ 136.4882 646.4671 null]
+>> endobj
+2909 0 obj <<
+/D [2901 0 R /XYZ 314.0312 638.0027 null]
+>> endobj
+2910 0 obj <<
+/D [2901 0 R /XYZ 71.731 631.0264 null]
+>> endobj
+2911 0 obj <<
+/D [2901 0 R /XYZ 136.4882 590.4937 null]
+>> endobj
+2912 0 obj <<
+/D [2901 0 R /XYZ 377.04 579.0167 null]
+>> endobj
+2913 0 obj <<
+/D [2901 0 R /XYZ 76.7123 544.4463 null]
+>> endobj
+2914 0 obj <<
+/D [2901 0 R /XYZ 89.6638 526.5136 null]
+>> endobj
+2915 0 obj <<
+/D [2901 0 R /XYZ 71.731 524.3567 null]
+>> endobj
+2916 0 obj <<
+/D [2901 0 R /XYZ 89.6638 508.5808 null]
+>> endobj
+2918 0 obj <<
+/D [2901 0 R /XYZ 71.731 501.4426 null]
+>> endobj
+1485 0 obj <<
+/D [2901 0 R /XYZ 71.731 433.7615 null]
+>> endobj
+326 0 obj <<
+/D [2901 0 R /XYZ 389.1797 394.4886 null]
+>> endobj
+2919 0 obj <<
+/D [2901 0 R /XYZ 71.731 387.1363 null]
+>> endobj
+2920 0 obj <<
+/D [2901 0 R /XYZ 71.731 361.4127 null]
+>> endobj
+2922 0 obj <<
+/D [2901 0 R /XYZ 86.9811 348.4612 null]
+>> endobj
+2923 0 obj <<
+/D [2901 0 R /XYZ 146.9968 335.5098 null]
+>> endobj
+2924 0 obj <<
+/D [2901 0 R /XYZ 395.8718 335.5098 null]
+>> endobj
+2925 0 obj <<
+/D [2901 0 R /XYZ 247.6993 322.5584 null]
+>> endobj
+2900 0 obj <<
+/Font << /F33 1322 0 R /F35 1604 0 R /F60 2540 0 R /F23 1214 0 R /F44 2021 0 R /F27 1222 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+2928 0 obj <<
+/Length 1853
+/Filter /FlateDecode
+>>
+stream
+xڭ]o6} O(dv^{
+GcMmyחz-P$EQ.?8$ ]uXH1+rvs!?<(NLy۟ƣbɄTt{(< =^Lc:Q.O3&xx1 X,զ6X<mmHR+X@y$8KBAv)C%#b7s<=˽H5|-^rUdѩĻF/ˠmwHMYml7]_ V< ׺[>i6jcMUI!Noz%TcE4
+[JCo<4 'kᮠeVohῘ7JzHE$oTݖ5iy59bJD
+Z@:' ذ2`3&%0yKS]yzzyyarYe/OdlTk^,N$1+> !)T
+
+JYniNդ+e*
+QR,IʘlXp(!˛y3ry=C)覄k^i):=b("|
+endobj
+2927 0 obj <<
+/Type /Page
+/Contents 2928 0 R
+/Resources 2926 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 2899 0 R
+>> endobj
+2929 0 obj <<
+/D [2927 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+1486 0 obj <<
+/D [2927 0 R /XYZ 71.731 718.3063 null]
+>> endobj
+330 0 obj <<
+/D [2927 0 R /XYZ 402.3254 703.236 null]
+>> endobj
+1487 0 obj <<
+/D [2927 0 R /XYZ 71.731 692.1839 null]
+>> endobj
+334 0 obj <<
+/D [2927 0 R /XYZ 288.867 651.1593 null]
+>> endobj
+2930 0 obj <<
+/D [2927 0 R /XYZ 71.731 638.7213 null]
+>> endobj
+1488 0 obj <<
+/D [2927 0 R /XYZ 71.731 588.589 null]
+>> endobj
+338 0 obj <<
+/D [2927 0 R /XYZ 234.3716 551.3735 null]
+>> endobj
+2931 0 obj <<
+/D [2927 0 R /XYZ 71.731 541.0084 null]
+>> endobj
+2932 0 obj <<
+/D [2927 0 R /XYZ 71.731 516.1407 null]
+>> endobj
+2933 0 obj <<
+/D [2927 0 R /XYZ 71.731 516.1407 null]
+>> endobj
+2934 0 obj <<
+/D [2927 0 R /XYZ 71.731 501.1967 null]
+>> endobj
+2935 0 obj <<
+/D [2927 0 R /XYZ 71.731 490.3026 null]
+>> endobj
+2936 0 obj <<
+/D [2927 0 R /XYZ 91.6563 472.4693 null]
+>> endobj
+2937 0 obj <<
+/D [2927 0 R /XYZ 71.731 447.3984 null]
+>> endobj
+2938 0 obj <<
+/D [2927 0 R /XYZ 71.731 436.5043 null]
+>> endobj
+2939 0 obj <<
+/D [2927 0 R /XYZ 91.6563 418.6711 null]
+>> endobj
+2940 0 obj <<
+/D [2927 0 R /XYZ 71.731 411.5329 null]
+>> endobj
+2941 0 obj <<
+/D [2927 0 R /XYZ 277.6373 400.7383 null]
+>> endobj
+2942 0 obj <<
+/D [2927 0 R /XYZ 500.3641 400.7383 null]
+>> endobj
+2943 0 obj <<
+/D [2927 0 R /XYZ 156.4128 387.7869 null]
+>> endobj
+2944 0 obj <<
+/D [2927 0 R /XYZ 71.731 376.4435 null]
+>> endobj
+2945 0 obj <<
+/D [2927 0 R /XYZ 71.731 363.6923 null]
+>> endobj
+2946 0 obj <<
+/D [2927 0 R /XYZ 91.6563 346.9401 null]
+>> endobj
+2947 0 obj <<
+/D [2927 0 R /XYZ 71.731 328.9078 null]
+>> endobj
+2948 0 obj <<
+/D [2927 0 R /XYZ 91.6563 303.1045 null]
+>> endobj
+2949 0 obj <<
+/D [2927 0 R /XYZ 452.6579 303.1045 null]
+>> endobj
+2950 0 obj <<
+/D [2927 0 R /XYZ 91.6563 290.153 null]
+>> endobj
+2951 0 obj <<
+/D [2927 0 R /XYZ 71.731 278.8096 null]
+>> endobj
+2952 0 obj <<
+/D [2927 0 R /XYZ 71.731 267.1394 null]
+>> endobj
+2953 0 obj <<
+/D [2927 0 R /XYZ 91.6563 249.3062 null]
+>> endobj
+2954 0 obj <<
+/D [2927 0 R /XYZ 71.731 231.2739 null]
+>> endobj
+2955 0 obj <<
+/D [2927 0 R /XYZ 273.2199 218.422 null]
+>> endobj
+2956 0 obj <<
+/D [2927 0 R /XYZ 500.9123 218.422 null]
+>> endobj
+2957 0 obj <<
+/D [2927 0 R /XYZ 156.4128 205.4706 null]
+>> endobj
+2958 0 obj <<
+/D [2927 0 R /XYZ 71.731 194.1272 null]
+>> endobj
+2959 0 obj <<
+/D [2927 0 R /XYZ 71.731 181.3759 null]
+>> endobj
+2960 0 obj <<
+/D [2927 0 R /XYZ 91.6563 164.6238 null]
+>> endobj
+2961 0 obj <<
+/D [2927 0 R /XYZ 71.731 141.6102 null]
+>> endobj
+2962 0 obj <<
+/D [2927 0 R /XYZ 71.731 128.6587 null]
+>> endobj
+2963 0 obj <<
+/D [2927 0 R /XYZ 91.6563 110.8255 null]
+>> endobj
+2926 0 obj <<
+/Font << /F23 1214 0 R /F27 1222 0 R /F35 1604 0 R /F33 1322 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+2966 0 obj <<
+/Length 2690
+/Filter /FlateDecode
+>>
+stream
+xڭ]۸='XsE}ڢwz
+4hȒ+Qq~}g8CIʛ> pf8O_y_$!|L V:/|F2v㫇waDq<O$^0cR6@zPX5U9GUjǿz82a"8(\ ɘ"q9Ew}efɺ JGnʀ'+9طm{]DĔiƑ(a/pS 
+}Fu '˜;ս*PƑsF^Dj"~d1NjמJv@>
+Bx|ZqUOfv~&iûޡw F‹Ø  > -? 3r龜nM~g, /N3'ۥ]2)q}uAQ>8sE_< EQ=}N*\M
+/ԋ@B'{04@`A})XYrh[~:3(&]a
+,WUqߠ0}z#49`Ģp
+Tn N٩kw>oBi0.((=Mv"ZƔ?g"LBMbGn:JuuJ֍fy;bߵG 8R[a#!/=h oڙsXOc15wF#z!dȢ)]u2mdBƃXĞ F&tX)",GJh M`nxJF'm|FGMkFw~;Zj` :bi΋ c.?] QkNjxaWhb˚ZuX6SA)c/42rSc}#Ed}6#x;9$wa`4kհ1Y"hŒ{:g7(pU#[^(T+@xQ
+IA3׬碅3t` 1
+6HP%FOӪ LmWd L>pX "1Bq|MNv0 R@e80aoEkkQP aUG=lmqNjWՕy 7mo!粪d&z?kl^ƘlG~B> ،|Sr6HSd_LwF2.:4^d34B s4fDBouKQ]nL+\74=0lbl\j-h~‹B/⥁8 jFODq2:B*,lmN
+qL HMcS
+;y(%|i
+zT3Ue|%#*sif)QCaf+]tԱfw>t`={
+FB]Z+sQPGU#ƣf%/)QdX_> MavFuXc@
+32#"@ןx!ZGzpmܞ D*[.X:K(s8>,IeEs(^?_9nQ GKzdJEًp,)/ Pendstream
+endobj
+2965 0 obj <<
+/Type /Page
+/Contents 2966 0 R
+/Resources 2964 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 2899 0 R
+>> endobj
+2967 0 obj <<
+/D [2965 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+2968 0 obj <<
+/D [2965 0 R /XYZ 146.0002 708.3437 null]
+>> endobj
+2969 0 obj <<
+/D [2965 0 R /XYZ 318.2313 708.3437 null]
+>> endobj
+2970 0 obj <<
+/D [2965 0 R /XYZ 433.516 708.3437 null]
+>> endobj
+2971 0 obj <<
+/D [2965 0 R /XYZ 71.731 683.2728 null]
+>> endobj
+2972 0 obj <<
+/D [2965 0 R /XYZ 71.731 670.3214 null]
+>> endobj
+2973 0 obj <<
+/D [2965 0 R /XYZ 91.6563 654.5454 null]
+>> endobj
+2974 0 obj <<
+/D [2965 0 R /XYZ 91.6563 641.594 null]
+>> endobj
+2975 0 obj <<
+/D [2965 0 R /XYZ 158.7209 641.594 null]
+>> endobj
+2976 0 obj <<
+/D [2965 0 R /XYZ 329.0454 641.594 null]
+>> endobj
+2977 0 obj <<
+/D [2965 0 R /XYZ 442.8963 641.594 null]
+>> endobj
+2978 0 obj <<
+/D [2965 0 R /XYZ 71.731 616.5231 null]
+>> endobj
+2979 0 obj <<
+/D [2965 0 R /XYZ 71.731 605.629 null]
+>> endobj
+2980 0 obj <<
+/D [2965 0 R /XYZ 91.6563 587.7958 null]
+>> endobj
+2981 0 obj <<
+/D [2965 0 R /XYZ 397.1684 561.8929 null]
+>> endobj
+2982 0 obj <<
+/D [2965 0 R /XYZ 71.731 559.7361 null]
+>> endobj
+2983 0 obj <<
+/D [2965 0 R /XYZ 71.731 544.7921 null]
+>> endobj
+2984 0 obj <<
+/D [2965 0 R /XYZ 374.7853 535.2926 null]
+>> endobj
+2985 0 obj <<
+/D [2965 0 R /XYZ 71.731 484.4832 null]
+>> endobj
+2986 0 obj <<
+/D [2965 0 R /XYZ 71.731 471.4322 null]
+>> endobj
+2987 0 obj <<
+/D [2965 0 R /XYZ 91.6563 453.599 null]
+>> endobj
+2988 0 obj <<
+/D [2965 0 R /XYZ 71.731 427.5966 null]
+>> endobj
+2989 0 obj <<
+/D [2965 0 R /XYZ 71.731 412.6526 null]
+>> endobj
+2990 0 obj <<
+/D [2965 0 R /XYZ 336.3446 401.0959 null]
+>> endobj
+2991 0 obj <<
+/D [2965 0 R /XYZ 126.7256 377.7833 null]
+>> endobj
+2992 0 obj <<
+/D [2965 0 R /XYZ 71.731 315.3176 null]
+>> endobj
+2993 0 obj <<
+/D [2965 0 R /XYZ 71.731 302.2666 null]
+>> endobj
+2994 0 obj <<
+/D [2965 0 R /XYZ 91.6563 284.4334 null]
+>> endobj
+2995 0 obj <<
+/D [2965 0 R /XYZ 332.6904 271.4819 null]
+>> endobj
+2996 0 obj <<
+/D [2965 0 R /XYZ 379.415 258.5305 null]
+>> endobj
+2997 0 obj <<
+/D [2965 0 R /XYZ 462.7813 258.5305 null]
+>> endobj
+2998 0 obj <<
+/D [2965 0 R /XYZ 71.731 246.411 null]
+>> endobj
+2999 0 obj <<
+/D [2965 0 R /XYZ 71.731 233.4596 null]
+>> endobj
+3000 0 obj <<
+/D [2965 0 R /XYZ 91.6563 217.6837 null]
+>> endobj
+3001 0 obj <<
+/D [2965 0 R /XYZ 318.1286 204.7323 null]
+>> endobj
+3002 0 obj <<
+/D [2965 0 R /XYZ 249.373 191.7808 null]
+>> endobj
+3003 0 obj <<
+/D [2965 0 R /XYZ 71.731 179.6614 null]
+>> endobj
+3004 0 obj <<
+/D [2965 0 R /XYZ 71.731 166.7099 null]
+>> endobj
+3005 0 obj <<
+/D [2965 0 R /XYZ 91.6563 150.934 null]
+>> endobj
+2964 0 obj <<
+/Font << /F33 1322 0 R /F27 1222 0 R /F35 1604 0 R /F32 1230 0 R /F23 1214 0 R /F44 2021 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+3008 0 obj <<
+/Length 2106
+/Filter /FlateDecode
+>>
+stream
+xڭ]o6}Š/9$:[ۦ-nSCVBmkꏤ?Rd3)E1(?d/(RRC.wU.ޝ`w 2w&Mw(eq,XTcw]Γ2TCݙތ7P_ΧM۪~}<O Qa?0PQ$iv(W7.1IT$Bd1H6<i}ȶ2zDg;X$2ƌ4:iL4(G;كME8c/if&cQвnDڹfFOfemk3 kjdDhredN4T_06nfKQ=50)2ړ-. f0Mٴ?N4y?J9?׬7+@+h'Q3&pm?ӨiY1ؖM0! '84Ŗ3LT[x#u ~w#\ؕ\Ȩh
+3v4tIR4!~9qTI3WB3ЫfF;a
+#crC,ːZOp8P7
+vKGߠHN<gTwn ^u^Gu6S|kOD= doIG [t<4or60s BU>Ȝg.r
+2BPl~+8ȩh95-'уIWx$2'܆,> -sC]-Rde\
+)B&r<TC;O
+Zq"ir#7Y3ԁBYdGR|}"nd
+endobj
+3007 0 obj <<
+/Type /Page
+/Contents 3008 0 R
+/Resources 3006 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 2899 0 R
+>> endobj
+3009 0 obj <<
+/D [3007 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+1489 0 obj <<
+/D [3007 0 R /XYZ 71.731 718.3063 null]
+>> endobj
+342 0 obj <<
+/D [3007 0 R /XYZ 268.9021 707.8408 null]
+>> endobj
+3010 0 obj <<
+/D [3007 0 R /XYZ 71.731 700.4885 null]
+>> endobj
+1490 0 obj <<
+/D [3007 0 R /XYZ 71.731 667.6267 null]
+>> endobj
+346 0 obj <<
+/D [3007 0 R /XYZ 247.484 630.4112 null]
+>> endobj
+3011 0 obj <<
+/D [3007 0 R /XYZ 71.731 623.0588 null]
+>> endobj
+3012 0 obj <<
+/D [3007 0 R /XYZ 71.731 569.2755 null]
+>> endobj
+3013 0 obj <<
+/D [3007 0 R /XYZ 71.731 554.3316 null]
+>> endobj
+3014 0 obj <<
+/D [3007 0 R /XYZ 71.731 541.3801 null]
+>> endobj
+3015 0 obj <<
+/D [3007 0 R /XYZ 91.6563 525.6042 null]
+>> endobj
+3016 0 obj <<
+/D [3007 0 R /XYZ 385.5711 499.7013 null]
+>> endobj
+3017 0 obj <<
+/D [3007 0 R /XYZ 71.731 476.6877 null]
+>> endobj
+3018 0 obj <<
+/D [3007 0 R /XYZ 71.731 463.7363 null]
+>> endobj
+3019 0 obj <<
+/D [3007 0 R /XYZ 91.6563 445.9031 null]
+>> endobj
+3020 0 obj <<
+/D [3007 0 R /XYZ 486.1475 445.9031 null]
+>> endobj
+1491 0 obj <<
+/D [3007 0 R /XYZ 71.731 412.8621 null]
+>> endobj
+350 0 obj <<
+/D [3007 0 R /XYZ 198.3489 375.6465 null]
+>> endobj
+3021 0 obj <<
+/D [3007 0 R /XYZ 71.731 368.2942 null]
+>> endobj
+1492 0 obj <<
+/D [3007 0 R /XYZ 71.731 335.4324 null]
+>> endobj
+354 0 obj <<
+/D [3007 0 R /XYZ 253.8823 298.2169 null]
+>> endobj
+3022 0 obj <<
+/D [3007 0 R /XYZ 71.731 287.8519 null]
+>> endobj
+3023 0 obj <<
+/D [3007 0 R /XYZ 71.731 250.0327 null]
+>> endobj
+3024 0 obj <<
+/D [3007 0 R /XYZ 71.731 235.0887 null]
+>> endobj
+3025 0 obj <<
+/D [3007 0 R /XYZ 71.731 224.1946 null]
+>> endobj
+3026 0 obj <<
+/D [3007 0 R /XYZ 91.6563 206.3614 null]
+>> endobj
+3027 0 obj <<
+/D [3007 0 R /XYZ 71.731 160.3689 null]
+>> endobj
+3028 0 obj <<
+/D [3007 0 R /XYZ 71.731 134.466 null]
+>> endobj
+3006 0 obj <<
+/Font << /F33 1322 0 R /F23 1214 0 R /F27 1222 0 R /F32 1230 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+3031 0 obj <<
+/Length 2040
+/Filter /FlateDecode
+>>
+stream
+xڭXK6ϯڊNY`wEB$ԋzx܃|PX,V"b%2 >i"]wS%"\olUG.ݭ<2UQU$x=lioִ91[ujyܼʨ`Z8J}ic*qIDE<+<L:-lJ=y׭o>i~%Zu+O7 gA[ k;h(4W}ڤZwlk#{ >q@jIxҟ#xD^
+lW+vy[45K05YcQ+Lt
+lHQ^핣0dYO-=-xM4@b_fq
+¯<Sx*Elg<(jŞ=2&@qMYm-ﲨ5n,kk f/W<$Yeeq/(w*yI 64r6m%ԣ||m/ He `"ʮt
+tҩt9'>1΋ƭ ˫X 1LN ŗ.MK>q( w⋼i-M@^ni57 q)g";^/##]j}P|3@Kh']hOŸ ? P6YIE΢2p Hm͜Ytgrt^uO{9NQçBӽsm噗hAA42<ZoɄ21~/I)zjp2 AµuxE?Yh"4~ ~6(nϮ$tVj0CNC
+endobj
+3030 0 obj <<
+/Type /Page
+/Contents 3031 0 R
+/Resources 3029 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 2899 0 R
+>> endobj
+3032 0 obj <<
+/D [3030 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+3033 0 obj <<
+/D [3030 0 R /XYZ 71.731 718.3063 null]
+>> endobj
+3034 0 obj <<
+/D [3030 0 R /XYZ 71.731 634.2217 null]
+>> endobj
+3035 0 obj <<
+/D [3030 0 R /XYZ 71.731 619.1134 null]
+>> endobj
+3036 0 obj <<
+/D [3030 0 R /XYZ 91.6563 603.3375 null]
+>> endobj
+1493 0 obj <<
+/D [3030 0 R /XYZ 71.731 570.2965 null]
+>> endobj
+358 0 obj <<
+/D [3030 0 R /XYZ 184.9496 533.0809 null]
+>> endobj
+3037 0 obj <<
+/D [3030 0 R /XYZ 71.731 522.7159 null]
+>> endobj
+3038 0 obj <<
+/D [3030 0 R /XYZ 71.731 486.954 null]
+>> endobj
+3039 0 obj <<
+/D [3030 0 R /XYZ 71.731 472.01 null]
+>> endobj
+3040 0 obj <<
+/D [3030 0 R /XYZ 71.731 457.0013 null]
+>> endobj
+3041 0 obj <<
+/D [3030 0 R /XYZ 91.6563 441.2254 null]
+>> endobj
+3042 0 obj <<
+/D [3030 0 R /XYZ 71.731 416.1545 null]
+>> endobj
+3043 0 obj <<
+/D [3030 0 R /XYZ 71.731 405.2603 null]
+>> endobj
+3044 0 obj <<
+/D [3030 0 R /XYZ 91.6563 387.4271 null]
+>> endobj
+1494 0 obj <<
+/D [3030 0 R /XYZ 71.731 354.3861 null]
+>> endobj
+362 0 obj <<
+/D [3030 0 R /XYZ 193.414 317.1706 null]
+>> endobj
+3045 0 obj <<
+/D [3030 0 R /XYZ 71.731 306.8056 null]
+>> endobj
+3046 0 obj <<
+/D [3030 0 R /XYZ 101.0411 258.1918 null]
+>> endobj
+1495 0 obj <<
+/D [3030 0 R /XYZ 71.731 251.0536 null]
+>> endobj
+366 0 obj <<
+/D [3030 0 R /XYZ 250.9846 213.8381 null]
+>> endobj
+3047 0 obj <<
+/D [3030 0 R /XYZ 71.731 203.6954 null]
+>> endobj
+3048 0 obj <<
+/D [3030 0 R /XYZ 484.3889 180.7621 null]
+>> endobj
+1496 0 obj <<
+/D [3030 0 R /XYZ 71.731 160.6725 null]
+>> endobj
+3029 0 obj <<
+/Font << /F33 1322 0 R /F23 1214 0 R /F44 2021 0 R /F27 1222 0 R /F35 1604 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+3051 0 obj <<
+/Length 3020
+/Filter /FlateDecode
+>>
+stream
+xڭˎ806`3
+鏫E*PJˏe{>+]5<T@*W/6F ZuݮL|q/wm 1\>Tmvвᶝ\3Ҿi5OBXm%dhCW .[<y3jO&Oʢ $ 鄩·?EkS<]{׾\%SזbyV oe7<
+"~\A4nƙlqn=[X(.& q<"[M֤ӡ`ZxUe88O
+`x ;#1p"L-܏+8r]bt DXE 6E~/[YHeHߑhm'dDA
+ypZ<{x+
+qt7"N1v9B}P;^, @ GOy_ {@t] uΝS zz6H] 0BPދ?eL6saٷNp+\^%"K
+h7wIʬI/
+\V? 7nJ *J4蘅REhI/e&Ztnw4VqjG,ތL6_J ;`Acj8! U3hlorENZ UnB3BIm /'1?LZ1f@Z,88=5^g/wҀ9$L{ Sh簆Х돯[\8! =3J7,EOTK\eO.| _v7XXܑvO<
+DCJ<ϫ?*=2X)kNćpqBqj{Sq:
+,;/1킞Fk-^nCtg
+Ѓ:pxVv[m`oU0TR@.fGEh$~7CPQ613觡ۀ,~@'ȯ`v)Z}b|w~mxwU9h ^ ?gD@$)z-f oendstream
+endobj
+3050 0 obj <<
+/Type /Page
+/Contents 3051 0 R
+/Resources 3049 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 3076 0 R
+/Annots [ 3054 0 R 3062 0 R 3066 0 R 3072 0 R ]
+>> endobj
+3054 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [466.7066 632.7575 518.512 643.6614]
+/Subtype /Link
+/A << /S /GoTo /D (groups) >>
+>> endobj
+3062 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [281.6014 507.2282 340.8787 518.1321]
+/Subtype /Link
+/A << /S /GoTo /D (edit-groups) >>
+>> endobj
+3066 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [205.9567 453.4299 269.8466 464.3339]
+/Subtype /Link
+/A << /S /GoTo /D (savedsearches) >>
+>> endobj
+3072 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [269.8985 144.561 309.6129 155.1432]
+/Subtype /Link
+/A << /S /GoTo /D (gloss-contrib) >>
+>> endobj
+3052 0 obj <<
+/D [3050 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+370 0 obj <<
+/D [3050 0 R /XYZ 214.9614 707.8408 null]
+>> endobj
+3053 0 obj <<
+/D [3050 0 R /XYZ 71.731 697.6981 null]
+>> endobj
+3055 0 obj <<
+/D [3050 0 R /XYZ 71.731 633.7537 null]
+>> endobj
+3056 0 obj <<
+/D [3050 0 R /XYZ 71.731 618.8098 null]
+>> endobj
+3057 0 obj <<
+/D [3050 0 R /XYZ 71.731 605.8583 null]
+>> endobj
+3058 0 obj <<
+/D [3050 0 R /XYZ 91.6563 590.0824 null]
+>> endobj
+3059 0 obj <<
+/D [3050 0 R /XYZ 71.731 565.0115 null]
+>> endobj
+3060 0 obj <<
+/D [3050 0 R /XYZ 71.731 552.0601 null]
+>> endobj
+3061 0 obj <<
+/D [3050 0 R /XYZ 91.6563 536.2841 null]
+>> endobj
+3063 0 obj <<
+/D [3050 0 R /XYZ 71.731 498.2618 null]
+>> endobj
+3064 0 obj <<
+/D [3050 0 R /XYZ 71.731 485.3104 null]
+>> endobj
+3065 0 obj <<
+/D [3050 0 R /XYZ 91.6563 469.5345 null]
+>> endobj
+1497 0 obj <<
+/D [3050 0 R /XYZ 71.731 449.4449 null]
+>> endobj
+374 0 obj <<
+/D [3050 0 R /XYZ 262.0456 412.2293 null]
+>> endobj
+3067 0 obj <<
+/D [3050 0 R /XYZ 71.731 404.877 null]
+>> endobj
+3068 0 obj <<
+/D [3050 0 R /XYZ 71.731 372.0152 null]
+>> endobj
+3069 0 obj <<
+/D [3050 0 R /XYZ 71.731 229.5495 null]
+>> endobj
+3070 0 obj <<
+/D [3050 0 R /XYZ 118.5554 193.9981 null]
+>> endobj
+3071 0 obj <<
+/D [3050 0 R /XYZ 118.5554 147.5522 null]
+>> endobj
+3073 0 obj <<
+/D [3050 0 R /XYZ 492.6563 147.5522 null]
+>> endobj
+3074 0 obj <<
+/D [3050 0 R /XYZ 71.731 113.9757 null]
+>> endobj
+3075 0 obj <<
+/D [3050 0 R /XYZ 71.731 105.0642 null]
+>> endobj
+3049 0 obj <<
+/Font << /F33 1322 0 R /F23 1214 0 R /F27 1222 0 R /F44 2021 0 R /F35 1604 0 R /F54 2305 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+3080 0 obj <<
+/Length 1883
+/Filter /FlateDecode
+>>
+stream
+xڭXo8 ޿"0׶,u;0kaVcc]?RCQ(?ϟ$Q0)g~?Yd2;!&A<]NBs/&4my7l r]5U#p_UgooH,J$#iHC'o?~9ˇoEwݾ0<7f^hnuiLcfG_/s{΃¿aeN[4:$JrFKwT!iZU> <|D+:ɺ x%Mk m-% YiqԲh=p+࿹Y_g//TC&*R *;˴0e]ψEz4lTOĂf
+}Pw,߉]q )PH H#j1D~UuDAmd)jh맾\͐ȲH
+endobj
+3079 0 obj <<
+/Type /Page
+/Contents 3080 0 R
+/Resources 3078 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 3076 0 R
+>> endobj
+3081 0 obj <<
+/D [3079 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+3082 0 obj <<
+/D [3079 0 R /XYZ 71.731 718.3063 null]
+>> endobj
+3083 0 obj <<
+/D [3079 0 R /XYZ 71.731 706.1869 null]
+>> endobj
+3084 0 obj <<
+/D [3079 0 R /XYZ 91.6563 690.4109 null]
+>> endobj
+3085 0 obj <<
+/D [3079 0 R /XYZ 167.8682 690.4109 null]
+>> endobj
+3086 0 obj <<
+/D [3079 0 R /XYZ 376.6947 664.5081 null]
+>> endobj
+3087 0 obj <<
+/D [3079 0 R /XYZ 101.8978 651.5566 null]
+>> endobj
+3088 0 obj <<
+/D [3079 0 R /XYZ 71.731 641.4945 null]
+>> endobj
+3089 0 obj <<
+/D [3079 0 R /XYZ 71.731 628.543 null]
+>> endobj
+3090 0 obj <<
+/D [3079 0 R /XYZ 91.6563 610.7098 null]
+>> endobj
+3091 0 obj <<
+/D [3079 0 R /XYZ 71.731 590.6202 null]
+>> endobj
+3092 0 obj <<
+/D [3079 0 R /XYZ 146.6995 579.8256 null]
+>> endobj
+3093 0 obj <<
+/D [3079 0 R /XYZ 243.8447 579.8256 null]
+>> endobj
+3094 0 obj <<
+/D [3079 0 R /XYZ 71.731 572.6875 null]
+>> endobj
+3095 0 obj <<
+/D [3079 0 R /XYZ 71.731 546.7846 null]
+>> endobj
+3096 0 obj <<
+/D [3079 0 R /XYZ 71.731 531.8407 null]
+>> endobj
+3097 0 obj <<
+/D [3079 0 R /XYZ 71.731 494.4458 null]
+>> endobj
+3098 0 obj <<
+/D [3079 0 R /XYZ 217.4518 481.4944 null]
+>> endobj
+3099 0 obj <<
+/D [3079 0 R /XYZ 411.628 481.4944 null]
+>> endobj
+3100 0 obj <<
+/D [3079 0 R /XYZ 234.1811 468.543 null]
+>> endobj
+3101 0 obj <<
+/D [3079 0 R /XYZ 71.731 456.4235 null]
+>> endobj
+3102 0 obj <<
+/D [3079 0 R /XYZ 71.731 443.4721 null]
+>> endobj
+3103 0 obj <<
+/D [3079 0 R /XYZ 91.6563 427.6961 null]
+>> endobj
+3104 0 obj <<
+/D [3079 0 R /XYZ 71.731 394.6551 null]
+>> endobj
+3105 0 obj <<
+/D [3079 0 R /XYZ 107.7061 383.8605 null]
+>> endobj
+3106 0 obj <<
+/D [3079 0 R /XYZ 71.731 371.741 null]
+>> endobj
+3107 0 obj <<
+/D [3079 0 R /XYZ 71.731 360.8469 null]
+>> endobj
+3108 0 obj <<
+/D [3079 0 R /XYZ 91.6563 343.0137 null]
+>> endobj
+3109 0 obj <<
+/D [3079 0 R /XYZ 71.731 322.9241 null]
+>> endobj
+3110 0 obj <<
+/D [3079 0 R /XYZ 107.7061 312.1295 null]
+>> endobj
+3111 0 obj <<
+/D [3079 0 R /XYZ 71.731 300.01 null]
+>> endobj
+3112 0 obj <<
+/D [3079 0 R /XYZ 71.731 289.1159 null]
+>> endobj
+3113 0 obj <<
+/D [3079 0 R /XYZ 91.6563 271.2827 null]
+>> endobj
+3114 0 obj <<
+/D [3079 0 R /XYZ 71.731 251.1931 null]
+>> endobj
+3115 0 obj <<
+/D [3079 0 R /XYZ 107.7061 240.3985 null]
+>> endobj
+3116 0 obj <<
+/D [3079 0 R /XYZ 71.731 230.3363 null]
+>> endobj
+3117 0 obj <<
+/D [3079 0 R /XYZ 71.731 217.3849 null]
+>> endobj
+3118 0 obj <<
+/D [3079 0 R /XYZ 91.6563 199.5517 null]
+>> endobj
+3119 0 obj <<
+/D [3079 0 R /XYZ 71.731 179.4621 null]
+>> endobj
+3120 0 obj <<
+/D [3079 0 R /XYZ 107.7061 168.6675 null]
+>> endobj
+1498 0 obj <<
+/D [3079 0 R /XYZ 71.731 163.5866 null]
+>> endobj
+3078 0 obj <<
+/Font << /F33 1322 0 R /F27 1222 0 R /F35 1604 0 R /F23 1214 0 R /F44 2021 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+3123 0 obj <<
+/Length 2107
+/Filter /FlateDecode
+>>
+stream
+xڭ]oF}@<F_}޶Eu C0ƱGܯ?rȑ%[CX?`ÿ`" #3!X;[|0Ɇi6Sww?*Y"v|_Vi(zcoFƾ
+.;> O+J׻﷣8LE@«T:pD"JPO*"A㏿}"~Mݗ˦Fe:H_2-|zz~p+;ޣ)j״8[|c#c5<5k}A;'CuY;=
+y[ VYu^ xkGX:=SUП9H—qJ"%$Jc`Ll ̬r fTF5_U"p}E/ÈWӪ['o3BC0'̿ ;kk;)gtp} @ڽ<s[qL䂥2sw=HH` GoS8T;^
+8Pl-F͐q2xH`>`Y7=ЏCֆ(yb!$ӊyq:7ʤ
+r
+/[C2,qlGh-^7[I?H%A;28
+vB)Y=t^??@՝Ծ p o"ˆq%[|y:ID d"NmkX±O'&j™O~.&`jsx.kڟ1
+endobj
+3122 0 obj <<
+/Type /Page
+/Contents 3123 0 R
+/Resources 3121 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 3076 0 R
+/Annots [ 3128 0 R ]
+>> endobj
+3128 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [480.0297 645.281 538.9788 655.7556]
+/Subtype /Link
+/A << /S /GoTo /D (bzldap) >>
+>> endobj
+3124 0 obj <<
+/D [3122 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+378 0 obj <<
+/D [3122 0 R /XYZ 278.5577 707.8408 null]
+>> endobj
+3125 0 obj <<
+/D [3122 0 R /XYZ 71.731 700.4885 null]
+>> endobj
+3126 0 obj <<
+/D [3122 0 R /XYZ 71.731 672.608 null]
+>> endobj
+3127 0 obj <<
+/D [3122 0 R /XYZ 71.731 657.6641 null]
+>> endobj
+3129 0 obj <<
+/D [3122 0 R /XYZ 71.731 608.6129 null]
+>> endobj
+3130 0 obj <<
+/D [3122 0 R /XYZ 71.731 593.5047 null]
+>> endobj
+3131 0 obj <<
+/D [3122 0 R /XYZ 71.731 578.5607 null]
+>> endobj
+3132 0 obj <<
+/D [3122 0 R /XYZ 71.731 565.6093 null]
+>> endobj
+3133 0 obj <<
+/D [3122 0 R /XYZ 91.6563 549.8333 null]
+>> endobj
+3134 0 obj <<
+/D [3122 0 R /XYZ 165.0015 549.8333 null]
+>> endobj
+3135 0 obj <<
+/D [3122 0 R /XYZ 376.6947 523.9305 null]
+>> endobj
+3136 0 obj <<
+/D [3122 0 R /XYZ 101.8978 510.979 null]
+>> endobj
+3137 0 obj <<
+/D [3122 0 R /XYZ 71.731 500.9169 null]
+>> endobj
+3138 0 obj <<
+/D [3122 0 R /XYZ 71.731 486.8844 null]
+>> endobj
+3139 0 obj <<
+/D [3122 0 R /XYZ 91.6563 470.1322 null]
+>> endobj
+3140 0 obj <<
+/D [3122 0 R /XYZ 71.731 458.0128 null]
+>> endobj
+3141 0 obj <<
+/D [3122 0 R /XYZ 71.731 446.0376 null]
+>> endobj
+3142 0 obj <<
+/D [3122 0 R /XYZ 91.6563 429.2854 null]
+>> endobj
+3143 0 obj <<
+/D [3122 0 R /XYZ 71.731 417.1659 null]
+>> endobj
+3144 0 obj <<
+/D [3122 0 R /XYZ 71.731 405.1908 null]
+>> endobj
+3145 0 obj <<
+/D [3122 0 R /XYZ 91.6563 388.4386 null]
+>> endobj
+3146 0 obj <<
+/D [3122 0 R /XYZ 71.731 342.4461 null]
+>> endobj
+3147 0 obj <<
+/D [3122 0 R /XYZ 91.6563 318.7001 null]
+>> endobj
+1499 0 obj <<
+/D [3122 0 R /XYZ 71.731 311.5619 null]
+>> endobj
+382 0 obj <<
+/D [3122 0 R /XYZ 157.864 274.3464 null]
+>> endobj
+3148 0 obj <<
+/D [3122 0 R /XYZ 71.731 266.9941 null]
+>> endobj
+3149 0 obj <<
+/D [3122 0 R /XYZ 71.731 239.1136 null]
+>> endobj
+3150 0 obj <<
+/D [3122 0 R /XYZ 71.731 224.1697 null]
+>> endobj
+3151 0 obj <<
+/D [3122 0 R /XYZ 71.731 211.2182 null]
+>> endobj
+3152 0 obj <<
+/D [3122 0 R /XYZ 91.6563 195.4423 null]
+>> endobj
+3153 0 obj <<
+/D [3122 0 R /XYZ 109.9275 169.5394 null]
+>> endobj
+3154 0 obj <<
+/D [3122 0 R /XYZ 71.731 157.42 null]
+>> endobj
+3155 0 obj <<
+/D [3122 0 R /XYZ 71.731 146.5258 null]
+>> endobj
+3156 0 obj <<
+/D [3122 0 R /XYZ 91.6563 128.6926 null]
+>> endobj
+3121 0 obj <<
+/Font << /F33 1322 0 R /F23 1214 0 R /F27 1222 0 R /F44 2021 0 R /F35 1604 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+3159 0 obj <<
+/Length 2301
+/Filter /FlateDecode
+>>
+stream
+xڭr`VofR}Y*A 2_~
+xd
+O%t;+E*]CXmf"3Fb#}A8;vf USrʃرs<-oo77yķ0Xk>.|6
+F!lgks4{ vF*{,1Lkѝkxt-eă4&硿/=
+]:!7Ɣq+k ;^:- v) H`|:j_Cz9}K2Fd3azAHg#aHqrűV|\ӘnQGϥX<LwrA4<[}^+uMH e8v:$E~
+Dxy6KKM8P- uV>/hPDlEY๫<-@byWhʡyM~wkdlCHoē©*mKs}9M
+]<@S5|&zjQl1?3DMƵ
+K
+뭬={ʙ/lu
+o760yz޿a ,]KߐpzҫA7Ƙ01 9;2s/ K [tuſkFYALr" )qj|) kY3#Xa}᪟ĻehJ~፻FxJqǿ ZQB\EA,wJ)ZUk+2T戍dNhg{f#'`"yqM|U6x_0f>ҋҝJGD}t#o()d0O&aNdӐNuES4we@ЕreJe긭i7C2V0*g>L{Y24)dzpy:a8Z۳#O-P|5"8Je]ײBc
+]qnک[ ] >/V(8d
+ I.j Z WГܻ!yج҉_E?2|P]Lf?Cᮁ)a1΃U}
+endobj
+3158 0 obj <<
+/Type /Page
+/Contents 3159 0 R
+/Resources 3157 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 3076 0 R
+>> endobj
+3160 0 obj <<
+/D [3158 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+3161 0 obj <<
+/D [3158 0 R /XYZ 71.731 718.3063 null]
+>> endobj
+3162 0 obj <<
+/D [3158 0 R /XYZ 71.731 706.1869 null]
+>> endobj
+3163 0 obj <<
+/D [3158 0 R /XYZ 91.6563 690.4109 null]
+>> endobj
+3164 0 obj <<
+/D [3158 0 R /XYZ 71.731 652.7622 null]
+>> endobj
+3165 0 obj <<
+/D [3158 0 R /XYZ 71.731 639.4372 null]
+>> endobj
+3166 0 obj <<
+/D [3158 0 R /XYZ 91.6563 623.6613 null]
+>> endobj
+1500 0 obj <<
+/D [3158 0 R /XYZ 71.731 590.6202 null]
+>> endobj
+386 0 obj <<
+/D [3158 0 R /XYZ 208.104 553.4047 null]
+>> endobj
+3167 0 obj <<
+/D [3158 0 R /XYZ 71.731 546.0524 null]
+>> endobj
+1501 0 obj <<
+/D [3158 0 R /XYZ 71.731 502.2965 null]
+>> endobj
+390 0 obj <<
+/D [3158 0 R /XYZ 221.7756 463.0236 null]
+>> endobj
+3168 0 obj <<
+/D [3158 0 R /XYZ 71.731 452.881 null]
+>> endobj
+1502 0 obj <<
+/D [3158 0 R /XYZ 71.731 409.8581 null]
+>> endobj
+394 0 obj <<
+/D [3158 0 R /XYZ 242.1475 372.6426 null]
+>> endobj
+3169 0 obj <<
+/D [3158 0 R /XYZ 71.731 365.2902 null]
+>> endobj
+3170 0 obj <<
+/D [3158 0 R /XYZ 71.731 306.5256 null]
+>> endobj
+3171 0 obj <<
+/D [3158 0 R /XYZ 89.7017 295.731 null]
+>> endobj
+3172 0 obj <<
+/D [3158 0 R /XYZ 71.731 280.6227 null]
+>> endobj
+3173 0 obj <<
+/D [3158 0 R /XYZ 71.731 265.6788 null]
+>> endobj
+1503 0 obj <<
+/D [3158 0 R /XYZ 71.731 204.9713 null]
+>> endobj
+398 0 obj <<
+/D [3158 0 R /XYZ 218.2898 165.599 null]
+>> endobj
+3174 0 obj <<
+/D [3158 0 R /XYZ 71.731 155.234 null]
+>> endobj
+3157 0 obj <<
+/Font << /F33 1322 0 R /F27 1222 0 R /F23 1214 0 R /F44 2021 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+3177 0 obj <<
+/Length 2254
+/Filter /FlateDecode
+>>
+stream
+x}˒_K*! >ܼv8UJ9@"$"C
+;; $ĥjh4~7M&\O\qƛC_>DBrO+)"I0cU9~[`!Ní
+n67F}ouw>ßOʃ7|U‰Kܓѽ6<*Q9IhE06uTٞjǺdmj9{6;6F+`F١bHh;P0،3.NWt&қ\t
+z:DqPQBd5+^~sy/xyv}蛑#u2tiAf "Hb()C P E7r5 6p2r4pKCjA ޝAT[1j.?nf9
+SʌQdiR=M@.$ռ 63d@Ul߻eϻSZn/$ ńTC qVKK/rEg\w
+,d,?NeF8)v^^}3b/rv$y{L]âِw5񾒟 wo#EJm˙[ɓ`0qv5
+ P*ܷKT,Hܡ [ܯ=M>V'Cye*ok9@}Sr9kNF0qN':^eEG`Nղ 3Ye`awH˂\P#+߈Hgͽ!aŋnxpo>!`QB!)bF*~ /i ?#̊W DOxc
+`L` !^ SNZaE3+=dZ{0 TfAY6k-Gjiie$ZI
+@Q&rؓG(jH 3O[l U
+7wܸ+wf 9{غYs1lh(!4MoPM&|C1I{$' v!@B4G\I?dB2("rg~9)V)'+=QΓͬN4膔Q
+y15<d%µ̀[-ğqGR_^F8Rׯⵙg,d-y,+qU4cJ32\ƞ99(ϷsڻMp2EAa. w5";z_T'TN\<]j9U&L1T!W<.YLK.?瞩dXR%^:_>t2bZ?kԓuO
+Rp#Qm/-|Q.|J
+F
+oj)a7-;w8pv5sY8=u/20 ^0MӒ~
+T?jIVHa{`n tץ8]/
+g#vR.kP6 e X֙ gqݲ$?I[*dWL=^pAo8W?4 9>O
+_d<9 a@K0W52سT]d"A+(v!(\Q_k`
+endobj
+3176 0 obj <<
+/Type /Page
+/Contents 3177 0 R
+/Resources 3175 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 3076 0 R
+>> endobj
+3178 0 obj <<
+/D [3176 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+3179 0 obj <<
+/D [3176 0 R /XYZ 71.731 741.2204 null]
+>> endobj
+1504 0 obj <<
+/D [3176 0 R /XYZ 71.731 678.2915 null]
+>> endobj
+402 0 obj <<
+/D [3176 0 R /XYZ 269.7575 635.194 null]
+>> endobj
+1505 0 obj <<
+/D [3176 0 R /XYZ 71.731 634.9789 null]
+>> endobj
+406 0 obj <<
+/D [3176 0 R /XYZ 283.7934 595.8216 null]
+>> endobj
+3180 0 obj <<
+/D [3176 0 R /XYZ 71.731 585.4566 null]
+>> endobj
+3181 0 obj <<
+/D [3176 0 R /XYZ 71.731 547.6374 null]
+>> endobj
+3182 0 obj <<
+/D [3176 0 R /XYZ 71.731 532.6935 null]
+>> endobj
+1506 0 obj <<
+/D [3176 0 R /XYZ 71.731 471.986 null]
+>> endobj
+410 0 obj <<
+/D [3176 0 R /XYZ 264.3119 432.6137 null]
+>> endobj
+1507 0 obj <<
+/D [3176 0 R /XYZ 71.731 429.4218 null]
+>> endobj
+414 0 obj <<
+/D [3176 0 R /XYZ 274.763 398.1429 null]
+>> endobj
+3183 0 obj <<
+/D [3176 0 R /XYZ 71.731 389.5054 null]
+>> endobj
+3184 0 obj <<
+/D [3176 0 R /XYZ 122.2213 379.2139 null]
+>> endobj
+3185 0 obj <<
+/D [3176 0 R /XYZ 468.4811 379.2139 null]
+>> endobj
+3186 0 obj <<
+/D [3176 0 R /XYZ 71.731 359.1243 null]
+>> endobj
+3187 0 obj <<
+/D [3176 0 R /XYZ 354.5783 309.4754 null]
+>> endobj
+3188 0 obj <<
+/D [3176 0 R /XYZ 71.731 276.4344 null]
+>> endobj
+1508 0 obj <<
+/D [3176 0 R /XYZ 71.731 206.6959 null]
+>> endobj
+418 0 obj <<
+/D [3176 0 R /XYZ 224.8627 173.3858 null]
+>> endobj
+3189 0 obj <<
+/D [3176 0 R /XYZ 71.731 170.7259 null]
+>> endobj
+422 0 obj <<
+/D [3176 0 R /XYZ 185.7017 142.9997 null]
+>> endobj
+3190 0 obj <<
+/D [3176 0 R /XYZ 71.731 135.8017 null]
+>> endobj
+3191 0 obj <<
+/D [3176 0 R /XYZ 359.6067 125.067 null]
+>> endobj
+3175 0 obj <<
+/Font << /F33 1322 0 R /F27 1222 0 R /F23 1214 0 R /F44 2021 0 R /F48 2037 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+3194 0 obj <<
+/Length 2680
+/Filter /FlateDecode
+>>
+stream
+xڍۮ}8/c$}ڤI -A탎%ʒzݯHQ89r[-bω|_lom7o"uȏ"p]'vt+IB/Yl/8dhWk?tXe3a6qc*v(<`n@D-+]fu`7 mo˭@o!۶AY0lmf!|Ve1%^f 4>b1+m{6;򰽬 ë=IC/7ChO6KQ S/yŵ8v_%( jV@km
+4db,i%y}CC +<ܞ<8HOAbauB?#A0U9q|bxy򂒿4
+b-cߴLy#T^ye
+ٖ_JT3 'ȅFcǁ|05n@{w-8yu#o ƚ1<d:Q6CkXЛ^aldθª+kUC6x<pd[Yz_ƜrcY,g rk{R SıRhcݗ'JqR|O䙇[!GmfEW`6D
+5IC(73G@OAV.}8G^7 ^^˅|ʹ(1wH(F(#cZy54NYቔ4- ?ĘI> d0bu3w2KÒ<,/u&@q·=2U
+*
+ 6(=ڬJGT[3(_q9)8:Ń_ꚰ~
+Qh|h+P ?( aMв&;"`2t\k_tIwn%9}I4VYsE8 ;t=^
+$|JXBCu[oAr@ Ugzg(tiQrֽ24/H3J1F$>=;R){lG};H okfE&qDŽi&ĉUSՀykӚ'7f$0 9
+&~>4(AG_N`_Nrd!!RV4F gܠrNF;j<BB*.BE75wms1Q9F'
+z>KZUIxP۔@Jŋ J`ݑ_R
+endobj
+3193 0 obj <<
+/Type /Page
+/Contents 3194 0 R
+/Resources 3192 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 3076 0 R
+/Annots [ 3198 0 R ]
+>> endobj
+3198 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [176.4275 692.2392 223.2515 703.1431]
+/Subtype /Link
+/A << /S /GoTo /D (parameters) >>
+>> endobj
+3195 0 obj <<
+/D [3193 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+3196 0 obj <<
+/D [3193 0 R /XYZ 388.1827 708.3437 null]
+>> endobj
+3197 0 obj <<
+/D [3193 0 R /XYZ 71.731 695.3923 null]
+>> endobj
+426 0 obj <<
+/D [3193 0 R /XYZ 280.1962 657.5342 null]
+>> endobj
+3199 0 obj <<
+/D [3193 0 R /XYZ 71.731 650.4559 null]
+>> endobj
+3200 0 obj <<
+/D [3193 0 R /XYZ 117.1103 639.6015 null]
+>> endobj
+3201 0 obj <<
+/D [3193 0 R /XYZ 71.731 637.4447 null]
+>> endobj
+3202 0 obj <<
+/D [3193 0 R /XYZ 71.731 632.4633 null]
+>> endobj
+3203 0 obj <<
+/D [3193 0 R /XYZ 89.6638 611.7061 null]
+>> endobj
+3204 0 obj <<
+/D [3193 0 R /XYZ 71.731 609.5493 null]
+>> endobj
+3205 0 obj <<
+/D [3193 0 R /XYZ 89.6638 593.7733 null]
+>> endobj
+3206 0 obj <<
+/D [3193 0 R /XYZ 71.731 591.6165 null]
+>> endobj
+3207 0 obj <<
+/D [3193 0 R /XYZ 71.731 576.6725 null]
+>> endobj
+3208 0 obj <<
+/D [3193 0 R /XYZ 244.0118 567.1731 null]
+>> endobj
+3209 0 obj <<
+/D [3193 0 R /XYZ 441.8906 543.8605 null]
+>> endobj
+1509 0 obj <<
+/D [3193 0 R /XYZ 71.731 464.7572 null]
+>> endobj
+430 0 obj <<
+/D [3193 0 R /XYZ 207.7551 429.2902 null]
+>> endobj
+3210 0 obj <<
+/D [3193 0 R /XYZ 71.731 420.6527 null]
+>> endobj
+3211 0 obj <<
+/D [3193 0 R /XYZ 71.731 408.2043 null]
+>> endobj
+3212 0 obj <<
+/D [3193 0 R /XYZ 71.731 403.223 null]
+>> endobj
+3213 0 obj <<
+/D [3193 0 R /XYZ 81.6937 382.4657 null]
+>> endobj
+3214 0 obj <<
+/D [3193 0 R /XYZ 81.6937 382.4657 null]
+>> endobj
+3215 0 obj <<
+/D [3193 0 R /XYZ 484.5537 382.4657 null]
+>> endobj
+3216 0 obj <<
+/D [3193 0 R /XYZ 71.731 354.4061 null]
+>> endobj
+3217 0 obj <<
+/D [3193 0 R /XYZ 81.6937 338.6301 null]
+>> endobj
+3218 0 obj <<
+/D [3193 0 R /XYZ 81.6937 338.6301 null]
+>> endobj
+3219 0 obj <<
+/D [3193 0 R /XYZ 71.731 336.4733 null]
+>> endobj
+3220 0 obj <<
+/D [3193 0 R /XYZ 81.6937 320.6974 null]
+>> endobj
+3221 0 obj <<
+/D [3193 0 R /XYZ 81.6937 320.6974 null]
+>> endobj
+3222 0 obj <<
+/D [3193 0 R /XYZ 71.731 305.5891 null]
+>> endobj
+3223 0 obj <<
+/D [3193 0 R /XYZ 81.6937 289.8132 null]
+>> endobj
+3224 0 obj <<
+/D [3193 0 R /XYZ 81.6937 289.8132 null]
+>> endobj
+3225 0 obj <<
+/D [3193 0 R /XYZ 71.731 274.7049 null]
+>> endobj
+3226 0 obj <<
+/D [3193 0 R /XYZ 81.6937 258.929 null]
+>> endobj
+3227 0 obj <<
+/D [3193 0 R /XYZ 81.6937 258.929 null]
+>> endobj
+3228 0 obj <<
+/D [3193 0 R /XYZ 71.731 225.888 null]
+>> endobj
+3229 0 obj <<
+/D [3193 0 R /XYZ 213.707 189.1905 null]
+>> endobj
+3230 0 obj <<
+/D [3193 0 R /XYZ 71.731 187.0337 null]
+>> endobj
+3231 0 obj <<
+/D [3193 0 R /XYZ 71.731 172.0897 null]
+>> endobj
+3232 0 obj <<
+/D [3193 0 R /XYZ 210.6668 150.934 null]
+>> endobj
+3233 0 obj <<
+/D [3193 0 R /XYZ 76.7123 134.2964 null]
+>> endobj
+3192 0 obj <<
+/Font << /F33 1322 0 R /F27 1222 0 R /F48 2037 0 R /F23 1214 0 R /F44 2021 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+3236 0 obj <<
+/Length 3217
+/Filter /FlateDecode
+>>
+stream
+xڝێ|s^$R (;NN thBkѶ[2D;J"ᐜGWUT#Ej{0QGW?IEDeRA WDCqtZ
+z~[2
+URqz#ǡ"`]@g(<W^e,"TIX:†e$) $/"*&d
+g*&nO*Xm,_*ۃs`s
+D$2Mn(eeק(X5A^ۧꚖms;r%0+@$HRB{]?ǡEU74-$λtz]1VkSR%(HVns ii{$阈o퓊\'3>,3! kO?𙬯ͅ
+nd J yL̕
+a/
+,= 
+5wEUlrwF|gn7~4߀E'c)0w{(3?aU76Ʋ7M/R"
+ozmsHMowޔe@ӳ_bj.?8
+HPo/cF $@]*j4V)Fئ%wD =rwHp;>aY>ߜ1ޭz8^-n5?'Vp9M-kW-B$Y_&TTÃF[wѼPWǹPI`2D!SAV L(BZ3\ݓhgħ?v/ 0EW m|.zӣ"?_z}PY2g*+M#_9~' MQfq*z<.8*:9'޼.nܥdύ?kCQ&=E2 jy%p%kS
+4r > ?D^y;irP}W.$}4'eOhjvUѹܗAp0ZA ` ǰ-x-澵UBGu)v@ ;ުo
+&cDiW?Uh-Y1xրwT6N'UBbr+jq.X1O3.[t}* ui
+5=[ TD? yN3+ۆvݜ>S՟%"ImZ룃bPp|K" .F
+endobj
+3235 0 obj <<
+/Type /Page
+/Contents 3236 0 R
+/Resources 3234 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 3278 0 R
+/Annots [ 3272 0 R ]
+>> endobj
+3272 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [289.2129 261.0513 336.4379 271.9553]
+/Subtype /Link
+/A << /S /GoTo /D (parameters) >>
+>> endobj
+3237 0 obj <<
+/D [3235 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+3238 0 obj <<
+/D [3235 0 R /XYZ 71.731 741.2204 null]
+>> endobj
+3239 0 obj <<
+/D [3235 0 R /XYZ 128.5181 684.7236 null]
+>> endobj
+3240 0 obj <<
+/D [3235 0 R /XYZ 76.7123 653.3452 null]
+>> endobj
+3241 0 obj <<
+/D [3235 0 R /XYZ 81.6937 635.4124 null]
+>> endobj
+3242 0 obj <<
+/D [3235 0 R /XYZ 81.6937 635.4124 null]
+>> endobj
+3243 0 obj <<
+/D [3235 0 R /XYZ 71.731 607.3527 null]
+>> endobj
+3244 0 obj <<
+/D [3235 0 R /XYZ 81.6937 591.5768 null]
+>> endobj
+3245 0 obj <<
+/D [3235 0 R /XYZ 81.6937 591.5768 null]
+>> endobj
+3246 0 obj <<
+/D [3235 0 R /XYZ 71.731 576.4685 null]
+>> endobj
+3247 0 obj <<
+/D [3235 0 R /XYZ 81.6937 560.6926 null]
+>> endobj
+3248 0 obj <<
+/D [3235 0 R /XYZ 81.6937 560.6926 null]
+>> endobj
+3249 0 obj <<
+/D [3235 0 R /XYZ 71.731 558.5358 null]
+>> endobj
+3250 0 obj <<
+/D [3235 0 R /XYZ 81.6937 542.7599 null]
+>> endobj
+3251 0 obj <<
+/D [3235 0 R /XYZ 81.6937 542.7599 null]
+>> endobj
+3252 0 obj <<
+/D [3235 0 R /XYZ 71.731 527.6516 null]
+>> endobj
+3253 0 obj <<
+/D [3235 0 R /XYZ 81.6937 511.8757 null]
+>> endobj
+3254 0 obj <<
+/D [3235 0 R /XYZ 81.6937 511.8757 null]
+>> endobj
+3255 0 obj <<
+/D [3235 0 R /XYZ 71.731 483.816 null]
+>> endobj
+3256 0 obj <<
+/D [3235 0 R /XYZ 81.6937 468.0401 null]
+>> endobj
+3257 0 obj <<
+/D [3235 0 R /XYZ 81.6937 468.0401 null]
+>> endobj
+3258 0 obj <<
+/D [3235 0 R /XYZ 71.731 439.9804 null]
+>> endobj
+3259 0 obj <<
+/D [3235 0 R /XYZ 81.6937 424.2044 null]
+>> endobj
+3260 0 obj <<
+/D [3235 0 R /XYZ 81.6937 424.2044 null]
+>> endobj
+3261 0 obj <<
+/D [3235 0 R /XYZ 71.731 409.0962 null]
+>> endobj
+3262 0 obj <<
+/D [3235 0 R /XYZ 81.6937 393.3203 null]
+>> endobj
+3263 0 obj <<
+/D [3235 0 R /XYZ 81.6937 393.3203 null]
+>> endobj
+3264 0 obj <<
+/D [3235 0 R /XYZ 374.7417 393.3203 null]
+>> endobj
+3265 0 obj <<
+/D [3235 0 R /XYZ 71.731 391.1634 null]
+>> endobj
+3266 0 obj <<
+/D [3235 0 R /XYZ 81.6937 375.3875 null]
+>> endobj
+3267 0 obj <<
+/D [3235 0 R /XYZ 81.6937 375.3875 null]
+>> endobj
+3268 0 obj <<
+/D [3235 0 R /XYZ 96.3701 362.4361 null]
+>> endobj
+3269 0 obj <<
+/D [3235 0 R /XYZ 239.3308 323.5818 null]
+>> endobj
+1510 0 obj <<
+/D [3235 0 R /XYZ 71.731 316.4436 null]
+>> endobj
+434 0 obj <<
+/D [3235 0 R /XYZ 198.4659 283.1335 null]
+>> endobj
+3270 0 obj <<
+/D [3235 0 R /XYZ 71.731 274.496 null]
+>> endobj
+3271 0 obj <<
+/D [3235 0 R /XYZ 96.3235 264.2044 null]
+>> endobj
+1511 0 obj <<
+/D [3235 0 R /XYZ 71.731 205.2606 null]
+>> endobj
+438 0 obj <<
+/D [3235 0 R /XYZ 233.4943 171.9504 null]
+>> endobj
+3273 0 obj <<
+/D [3235 0 R /XYZ 71.731 163.3129 null]
+>> endobj
+3274 0 obj <<
+/D [3235 0 R /XYZ 436.1187 153.0214 null]
+>> endobj
+3275 0 obj <<
+/D [3235 0 R /XYZ 71.731 139.9704 null]
+>> endobj
+3276 0 obj <<
+/D [3235 0 R /XYZ 71.731 125.0264 null]
+>> endobj
+3277 0 obj <<
+/D [3235 0 R /XYZ 300.5965 113.4697 null]
+>> endobj
+3234 0 obj <<
+/Font << /F33 1322 0 R /F23 1214 0 R /F44 2021 0 R /F27 1222 0 R /F35 1604 0 R /F32 1230 0 R /F48 2037 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+3281 0 obj <<
+/Length 2373
+/Filter /FlateDecode
+>>
+stream
+xڍk
+c֌D~lo+4h68~eNJm_P/+{!977/b?22
+&+y3g=H?YM*HF&<{II'㿷Oem2L[?\ˋBw^桊E
+"!Rھ/;PЊk7/]"&㽣6ozrبOLˆR|:W]^3+p?Q}DUT>Xʠ?Ҳ˦l*Kbr{(L!d: .^fn|پ#m6m}͏YKa` d
+kh
+ )mb_v\57
+h45p XDJ93۝a&PB?V
+HbP/'TVO
+Y: KF俴nR^\&_H_P(#)XCXg CHm,|X.N$P`{)-uɥAcG4A_ڂ+Us>jl
+>~GvaؙK1xHoO&
+&Q ʨnkyIv髭"ȟ=,Vy1zJ%Y|T<c*w7C;h5뢭DT&ypM=ak_fX;vxMgr?~~ϟ>pfns譗1%\un&mV
+endobj
+3280 0 obj <<
+/Type /Page
+/Contents 3281 0 R
+/Resources 3279 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 3278 0 R
+/Annots [ 3294 0 R 3295 0 R ]
+>> endobj
+3294 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [70.7348 260.9588 111.8612 271.8627]
+/Subtype /Link
+/A << /S /GoTo /D (gloss-product) >>
+>> endobj
+3295 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [409.6821 260.9588 469.7956 271.8627]
+/Subtype /Link
+/A << /S /GoTo /D (classifications) >>
+>> endobj
+3282 0 obj <<
+/D [3280 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+3283 0 obj <<
+/D [3280 0 R /XYZ 71.731 741.2204 null]
+>> endobj
+3284 0 obj <<
+/D [3280 0 R /XYZ 71.731 626.4857 null]
+>> endobj
+3285 0 obj <<
+/D [3280 0 R /XYZ 71.731 600.5829 null]
+>> endobj
+3286 0 obj <<
+/D [3280 0 R /XYZ 118.5554 562.0188 null]
+>> endobj
+1512 0 obj <<
+/D [3280 0 R /XYZ 71.731 488.3963 null]
+>> endobj
+442 0 obj <<
+/D [3280 0 R /XYZ 226.7368 450.1183 null]
+>> endobj
+3287 0 obj <<
+/D [3280 0 R /XYZ 71.731 441.2955 null]
+>> endobj
+3288 0 obj <<
+/D [3280 0 R /XYZ 71.731 421.421 null]
+>> endobj
+3289 0 obj <<
+/D [3280 0 R /XYZ 71.731 397.675 null]
+>> endobj
+3290 0 obj <<
+/D [3280 0 R /XYZ 71.731 390.5369 null]
+>> endobj
+3291 0 obj <<
+/D [3280 0 R /XYZ 349.6963 379.7423 null]
+>> endobj
+3292 0 obj <<
+/D [3280 0 R /XYZ 71.731 359.6527 null]
+>> endobj
+1614 0 obj <<
+/D [3280 0 R /XYZ 71.731 328.7685 null]
+>> endobj
+446 0 obj <<
+/D [3280 0 R /XYZ 179.4984 285.671 null]
+>> endobj
+3293 0 obj <<
+/D [3280 0 R /XYZ 71.731 276.8482 null]
+>> endobj
+3296 0 obj <<
+/D [3280 0 R /XYZ 238.5875 238.209 null]
+>> endobj
+3297 0 obj <<
+/D [3280 0 R /XYZ 71.731 205.5416 null]
+>> endobj
+3298 0 obj <<
+/D [3280 0 R /XYZ 411.9612 194.3734 null]
+>> endobj
+3299 0 obj <<
+/D [3280 0 R /XYZ 71.731 163.3897 null]
+>> endobj
+3300 0 obj <<
+/D [3280 0 R /XYZ 71.731 148.3809 null]
+>> endobj
+3301 0 obj <<
+/D [3280 0 R /XYZ 71.731 133.437 null]
+>> endobj
+3302 0 obj <<
+/D [3280 0 R /XYZ 71.731 122.5428 null]
+>> endobj
+3303 0 obj <<
+/D [3280 0 R /XYZ 91.6563 104.7096 null]
+>> endobj
+3279 0 obj <<
+/Font << /F33 1322 0 R /F27 1222 0 R /F23 1214 0 R /F44 2021 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+3307 0 obj <<
+/Length 1880
+/Filter /FlateDecode
+>>
+stream
+xYK6WbV$-$}Imm\^ EC9L 3o8rʃ?(80`[ϨeZmٷo9_%$ YWȋUvٟ뛃85lY9ߗ1/yyW绯yQ߻ٵ$ \cYԱ֋ yd,|-OMʡr&LfOH0Fն+qleBIWqra1hJeZm)(`_m# Źhp1/dݨRA'$}Pt$.@Ḵe!fl~+s*"{I|PMj $n>ۃ+˦zC YD#hu$.hߪ/r X}oVRGKmBY9&X
+;8'݌eƌOXD<'IGO(*<V%7JUd3qHhOzW<-6x0uuj})e6DWHQi |5 Y[P~{?kF4r!~PJ8?a9[pRpi0D×;Bpm+U`VR *)eS?&Mď9{B; w\kQ|ʵ g_D^_2b08H]M[ńZy)-GW|~ʀT_x.ǜ~` nvKC xjh!qp8hX10N>EBY<she&G VI)1Py^ȽǏɧڍ޵Ãe>V&f{
+$ŘN-Ӷ5q5FR2T'$ y<r6|ݢ+&d/ƸVş {ګ(vUr((gudK1,WZy/M|Mɞ [4*;40W`p-0gaPM&)"S|LWОp1z>;BJ
+=?<Ir'8. ? ɢ #ٝ0; .Ç 7I|C׍^ }R3lMrJ ?tuhMINWk;˱d-e[uݨ@Irs+9k'UJMz`'¥m[dj]07تh!#<`@p㸟p\1ӝv&:4Z3)f_r]v݋S9|R>,ӽUR>ևM۶f'5;%M(Fs"O?<.@{sFX.bm?+92-D]:5f7nv*x*H&ys/19A64cUP^(@xz},Ox0)G˴tP
+nѡyzׇ6{ S֕5 }5Y+V 2XkK0.A6,Q!EtPkS"`o%Ov40Ng<b
+V iܮL.p4Xf9Ar;>ʿf tNmKW[eLWLjHJ4|}X,a(_њKeJy+ޓ\}wa4L-`1( ˴,|h
+*Ӗendstream
+endobj
+3306 0 obj <<
+/Type /Page
+/Contents 3307 0 R
+/Resources 3305 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 3278 0 R
+/Annots [ 3333 0 R 3347 0 R ]
+>> endobj
+3333 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [382.851 383.3973 437.147 394.3013]
+/Subtype /Link
+/A << /S /GoTo /D (product-group-controls) >>
+>> endobj
+3347 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [471.8565 229.2554 524.2196 240.1593]
+/Subtype /Link
+/A << /S /GoTo /D (components) >>
+>> endobj
+3308 0 obj <<
+/D [3306 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+3309 0 obj <<
+/D [3306 0 R /XYZ 71.731 718.3063 null]
+>> endobj
+3310 0 obj <<
+/D [3306 0 R /XYZ 71.731 706.1869 null]
+>> endobj
+3311 0 obj <<
+/D [3306 0 R /XYZ 91.6563 690.4109 null]
+>> endobj
+3312 0 obj <<
+/D [3306 0 R /XYZ 71.731 667.3973 null]
+>> endobj
+3313 0 obj <<
+/D [3306 0 R /XYZ 91.6563 649.5641 null]
+>> endobj
+3314 0 obj <<
+/D [3306 0 R /XYZ 71.731 637.4447 null]
+>> endobj
+3315 0 obj <<
+/D [3306 0 R /XYZ 71.731 624.4932 null]
+>> endobj
+3316 0 obj <<
+/D [3306 0 R /XYZ 91.6563 608.7173 null]
+>> endobj
+3317 0 obj <<
+/D [3306 0 R /XYZ 71.731 596.5978 null]
+>> endobj
+3318 0 obj <<
+/D [3306 0 R /XYZ 71.731 583.6464 null]
+>> endobj
+3319 0 obj <<
+/D [3306 0 R /XYZ 91.6563 567.8705 null]
+>> endobj
+3320 0 obj <<
+/D [3306 0 R /XYZ 71.731 555.751 null]
+>> endobj
+3321 0 obj <<
+/D [3306 0 R /XYZ 71.731 542.7996 null]
+>> endobj
+3322 0 obj <<
+/D [3306 0 R /XYZ 91.6563 527.0236 null]
+>> endobj
+3323 0 obj <<
+/D [3306 0 R /XYZ 71.731 514.9042 null]
+>> endobj
+3324 0 obj <<
+/D [3306 0 R /XYZ 71.731 504.01 null]
+>> endobj
+3325 0 obj <<
+/D [3306 0 R /XYZ 91.6563 486.1768 null]
+>> endobj
+3326 0 obj <<
+/D [3306 0 R /XYZ 71.731 474.0574 null]
+>> endobj
+3327 0 obj <<
+/D [3306 0 R /XYZ 71.731 463.1632 null]
+>> endobj
+3328 0 obj <<
+/D [3306 0 R /XYZ 91.6563 445.33 null]
+>> endobj
+3329 0 obj <<
+/D [3306 0 R /XYZ 71.731 433.2105 null]
+>> endobj
+3330 0 obj <<
+/D [3306 0 R /XYZ 71.731 420.2591 null]
+>> endobj
+3331 0 obj <<
+/D [3306 0 R /XYZ 91.6563 404.4832 null]
+>> endobj
+3332 0 obj <<
+/D [3306 0 R /XYZ 71.731 397.345 null]
+>> endobj
+1615 0 obj <<
+/D [3306 0 R /XYZ 71.731 384.3936 null]
+>> endobj
+450 0 obj <<
+/D [3306 0 R /XYZ 268.9457 347.1781 null]
+>> endobj
+3334 0 obj <<
+/D [3306 0 R /XYZ 71.731 336.8131 null]
+>> endobj
+3335 0 obj <<
+/D [3306 0 R /XYZ 71.731 324.8967 null]
+>> endobj
+3336 0 obj <<
+/D [3306 0 R /XYZ 71.731 319.9154 null]
+>> endobj
+3337 0 obj <<
+/D [3306 0 R /XYZ 89.6638 299.1581 null]
+>> endobj
+3338 0 obj <<
+/D [3306 0 R /XYZ 116.5027 299.1581 null]
+>> endobj
+3339 0 obj <<
+/D [3306 0 R /XYZ 317.6563 299.1581 null]
+>> endobj
+3340 0 obj <<
+/D [3306 0 R /XYZ 71.731 297.0013 null]
+>> endobj
+3341 0 obj <<
+/D [3306 0 R /XYZ 89.6638 281.2254 null]
+>> endobj
+3342 0 obj <<
+/D [3306 0 R /XYZ 131.1675 281.2254 null]
+>> endobj
+3343 0 obj <<
+/D [3306 0 R /XYZ 71.731 279.0686 null]
+>> endobj
+3344 0 obj <<
+/D [3306 0 R /XYZ 89.6638 263.2926 null]
+>> endobj
+3345 0 obj <<
+/D [3306 0 R /XYZ 71.731 261.1358 null]
+>> endobj
+3346 0 obj <<
+/D [3306 0 R /XYZ 89.6638 245.3599 null]
+>> endobj
+1616 0 obj <<
+/D [3306 0 R /XYZ 71.731 214.3762 null]
+>> endobj
+454 0 obj <<
+/D [3306 0 R /XYZ 226.1083 175.1033 null]
+>> endobj
+3348 0 obj <<
+/D [3306 0 R /XYZ 71.731 164.7383 null]
+>> endobj
+3305 0 obj <<
+/Font << /F33 1322 0 R /F27 1222 0 R /F23 1214 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+3351 0 obj <<
+/Length 2765
+/Filter /FlateDecode
+>>
+stream
+xڥˎ>_a%6fD# 6`{䠶h[Y2$y'_*VQ,{"E֓["T4ʅJZN|emßU.D%JGH,_XmDXcyxrb}<7q* (5Q^(SaєmF롥oپDZ\U.knmU//ewUNic̚v6GFmOUm<`dе3TJFjA5fںʉWR*ځ}f ,Ѻ$s_7@R88&2
+Ƥ朦{}V\ !h6p}:$x3h[|M?}>T NP2LdŁ疪da\(
+B{ C>5 L|}(:!S;2ޛ^:kQnۆz}ij D{ t.
+uD;`[omS4[bYAkI vXF¼EGi< ~eZֿ]mKt.|OmDcߓ{Z[
+
+endobj
+3350 0 obj <<
+/Type /Page
+/Contents 3351 0 R
+/Resources 3349 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 3278 0 R
+/Annots [ 3355 0 R 3357 0 R 3359 0 R 3364 0 R 3367 0 R ]
+>> endobj
+3355 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [240.2078 552.3239 292.5709 563.2278]
+/Subtype /Link
+/A << /S /GoTo /D (components) >>
+>> endobj
+3357 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [225.1145 535.1084 271.9385 545.295]
+/Subtype /Link
+/A << /S /GoTo /D (versions) >>
+>> endobj
+3359 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [234.6782 517.1756 281.5022 527.3623]
+/Subtype /Link
+/A << /S /GoTo /D (milestones) >>
+>> endobj
+3364 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [145.4437 395.1931 197.249 406.097]
+/Subtype /Link
+/A << /S /GoTo /D (groups) >>
+>> endobj
+3367 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [87.6113 281.619 149.3793 292.5229]
+/Subtype /Link
+/A << /S /GoTo /D (group-control-examples) >>
+>> endobj
+3352 0 obj <<
+/D [3350 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+1617 0 obj <<
+/D [3350 0 R /XYZ 71.731 690.3114 null]
+>> endobj
+458 0 obj <<
+/D [3350 0 R /XYZ 145.8713 632.3885 null]
+>> endobj
+3353 0 obj <<
+/D [3350 0 R /XYZ 71.731 625.0362 null]
+>> endobj
+3354 0 obj <<
+/D [3350 0 R /XYZ 71.731 566.2715 null]
+>> endobj
+3356 0 obj <<
+/D [3350 0 R /XYZ 71.731 548.3388 null]
+>> endobj
+3358 0 obj <<
+/D [3350 0 R /XYZ 71.731 531.1233 null]
+>> endobj
+1618 0 obj <<
+/D [3350 0 R /XYZ 71.731 513.1906 null]
+>> endobj
+462 0 obj <<
+/D [3350 0 R /XYZ 373.7867 475.2578 null]
+>> endobj
+3360 0 obj <<
+/D [3350 0 R /XYZ 71.731 464.8928 null]
+>> endobj
+3361 0 obj <<
+/D [3350 0 R /XYZ 100.9239 455.1332 null]
+>> endobj
+3362 0 obj <<
+/D [3350 0 R /XYZ 268.3242 455.1332 null]
+>> endobj
+3363 0 obj <<
+/D [3350 0 R /XYZ 71.731 435.0437 null]
+>> endobj
+3365 0 obj <<
+/D [3350 0 R /XYZ 71.731 391.208 null]
+>> endobj
+3366 0 obj <<
+/D [3350 0 R /XYZ 71.731 347.3724 null]
+>> endobj
+3368 0 obj <<
+/D [3350 0 R /XYZ 71.731 282.6153 null]
+>> endobj
+3369 0 obj <<
+/D [3350 0 R /XYZ 71.731 267.6713 null]
+>> endobj
+3370 0 obj <<
+/D [3350 0 R /XYZ 71.731 218.6202 null]
+>> endobj
+3371 0 obj <<
+/D [3350 0 R /XYZ 141.0038 205.6687 null]
+>> endobj
+3372 0 obj <<
+/D [3350 0 R /XYZ 527.4622 205.6687 null]
+>> endobj
+3373 0 obj <<
+/D [3350 0 R /XYZ 136.2087 192.7173 null]
+>> endobj
+3374 0 obj <<
+/D [3350 0 R /XYZ 71.731 185.5791 null]
+>> endobj
+3375 0 obj <<
+/D [3350 0 R /XYZ 139.2157 174.7845 null]
+>> endobj
+3376 0 obj <<
+/D [3350 0 R /XYZ 501.9343 174.7845 null]
+>> endobj
+3377 0 obj <<
+/D [3350 0 R /XYZ 121.4494 161.8331 null]
+>> endobj
+3378 0 obj <<
+/D [3350 0 R /XYZ 192.6467 161.8331 null]
+>> endobj
+3379 0 obj <<
+/D [3350 0 R /XYZ 348.3123 161.8331 null]
+>> endobj
+3380 0 obj <<
+/D [3350 0 R /XYZ 71.731 130.8494 null]
+>> endobj
+3381 0 obj <<
+/D [3350 0 R /XYZ 284.0181 117.9975 null]
+>> endobj
+3349 0 obj <<
+/Font << /F33 1322 0 R /F27 1222 0 R /F23 1214 0 R /F44 2021 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+3384 0 obj <<
+/Length 2152
+/Filter /FlateDecode
+>>
+stream
+xڽYYoH~ϯ$2qY vh v"[1<I<~MqX]*R†?g9VM,7 EVGXz| ܘBw7oy" ·m+dyNZݟs/+Ϣ6_Gb ?L׿a7JbU ԵndبcHZNzYx:pF^ҵk`ӅQl~BE5չEw$>7 -׍<D)^eZ V:wDҷSC)jŵlseɛtgؒiS%64}qC r$p|iuQg넍nQz`ut9_1Sԝ%wA3$siPA Vs@Κ߶L:J#xGRF_`wamߨ(K}ݏ4p-@VP V6:@^hفMdD`D3XC
+bR[[>D}SVM9=q ,!yO?S8Va &(r9Yn OQ~۩NDa !I,z.jjB ADȊĚ ñHdu_
+bF:GN
+^>i<EN4Y׸aSkD,؀|J-Y&:A)2(F2n(TyhAjҢTe{
+6$w ^,~xX{s
+`5E)ouG`A%w] GX$ D.^ҍ,D 8BգzRCQ[5FGFw;Hid5z!5m+8&ʥI tAl a+-flK5mmm6I?D' `6{{4g މxU7=q7-YT=*|~6f73'ޥ]Gq ;|n[vL4/$ʒ
+Xcd#7fdPTGFjlX";UAMHzD3=:!6 C M루|@+TfE*jQ} S`=; :1* i{m СinIi Olwϟ~{)^>=~7??D6hOqlA0$n1 S0>q-]*E]PA) nj
+DZBHB|+\gy/ڜ}󗧷?s0Q>>?1O`H)% <xINa):!^կdTx<7l"oh
+ەa58z D}p
+# ad/Qm{کzڇq*`AdC[l$uguL
+endobj
+3383 0 obj <<
+/Type /Page
+/Contents 3384 0 R
+/Resources 3382 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 3278 0 R
+>> endobj
+3385 0 obj <<
+/D [3383 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+3386 0 obj <<
+/D [3383 0 R /XYZ 71.731 718.3063 null]
+>> endobj
+3387 0 obj <<
+/D [3383 0 R /XYZ 149.9766 708.3437 null]
+>> endobj
+3388 0 obj <<
+/D [3383 0 R /XYZ 71.731 688.2541 null]
+>> endobj
+3389 0 obj <<
+/D [3383 0 R /XYZ 146.371 677.4595 null]
+>> endobj
+3390 0 obj <<
+/D [3383 0 R /XYZ 71.731 670.3214 null]
+>> endobj
+3391 0 obj <<
+/D [3383 0 R /XYZ 146.371 659.5268 null]
+>> endobj
+3392 0 obj <<
+/D [3383 0 R /XYZ 71.731 652.3886 null]
+>> endobj
+3393 0 obj <<
+/D [3383 0 R /XYZ 89.804 641.594 null]
+>> endobj
+3394 0 obj <<
+/D [3383 0 R /XYZ 173.1124 641.594 null]
+>> endobj
+1619 0 obj <<
+/D [3383 0 R /XYZ 71.731 613.5343 null]
+>> endobj
+466 0 obj <<
+/D [3383 0 R /XYZ 347.5933 581.2204 null]
+>> endobj
+3395 0 obj <<
+/D [3383 0 R /XYZ 71.731 572.7682 null]
+>> endobj
+3396 0 obj <<
+/D [3383 0 R /XYZ 218.9123 549.34 null]
+>> endobj
+3397 0 obj <<
+/D [3383 0 R /XYZ 71.731 505.4048 null]
+>> endobj
+3398 0 obj <<
+/D [3383 0 R /XYZ 71.731 485.4148 null]
+>> endobj
+3399 0 obj <<
+/D [3383 0 R /XYZ 71.731 423.6464 null]
+>> endobj
+3400 0 obj <<
+/D [3383 0 R /XYZ 71.731 380.8717 null]
+>> endobj
+3401 0 obj <<
+/D [3383 0 R /XYZ 71.731 342.8494 null]
+>> endobj
+3402 0 obj <<
+/D [3383 0 R /XYZ 71.731 300.0747 null]
+>> endobj
+3403 0 obj <<
+/D [3383 0 R /XYZ 71.731 241.1308 null]
+>> endobj
+3404 0 obj <<
+/D [3383 0 R /XYZ 71.731 223.1981 null]
+>> endobj
+3405 0 obj <<
+/D [3383 0 R /XYZ 71.731 187.3326 null]
+>> endobj
+3406 0 obj <<
+/D [3383 0 R /XYZ 71.731 132.9016 null]
+>> endobj
+3382 0 obj <<
+/Font << /F33 1322 0 R /F27 1222 0 R /F23 1214 0 R /F35 1604 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+3409 0 obj <<
+/Length 1565
+/Filter /FlateDecode
+>>
+stream
+xko6{E/so]vî aN$,׏)GN  H/Q$%gdEyq w,P"D'ͯn>x( Gȷm+dyN<g6exYfe^yFl+/ty'<"+AY ;cHQYkǖ{p>m-ܲ[(N`"bǥ 0jmCصݚ#&]3+ӦvKLe0rM.9cm?Ԝp}i.I K\
+mTvL+ BORrBҠ-.yaًB!ҷ"9"yfnp]]tRN}Xۡd`
+y 9֡8ЋE~;Ԣ **VE}Պe?r0O?0=oYx 2ͭsuod0v3]7 n֨θYQ n7Uͳuy{ZgoK:=MŲ= UELk,S*I uYB9uN+.2Uڻkwն_wdȘ]ƅhC^C
+013-e$(>)(~"CKhYFLj)dCVc+,wwT2c(<0 ̰na50LZ5 + lo@ M˶E80(!T-&W#MdHԝ/MtA`% wO!:\;%TqAlRW끞'7f
+L?!XC,H;޿v>DՁr?x|Dz:>;ar-P9~
+ vqɷ'J_endstream
+endobj
+3408 0 obj <<
+/Type /Page
+/Contents 3409 0 R
+/Resources 3407 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 3278 0 R
+>> endobj
+3410 0 obj <<
+/D [3408 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+3411 0 obj <<
+/D [3408 0 R /XYZ 71.731 718.3063 null]
+>> endobj
+3412 0 obj <<
+/D [3408 0 R /XYZ 71.731 670.695 null]
+>> endobj
+3413 0 obj <<
+/D [3408 0 R /XYZ 71.731 615.8904 null]
+>> endobj
+3414 0 obj <<
+/D [3408 0 R /XYZ 71.731 595.8008 null]
+>> endobj
+3415 0 obj <<
+/D [3408 0 R /XYZ 71.731 569.898 null]
+>> endobj
+3416 0 obj <<
+/D [3408 0 R /XYZ 71.731 564.9166 null]
+>> endobj
+3417 0 obj <<
+/D [3408 0 R /XYZ 89.6638 544.1594 null]
+>> endobj
+3418 0 obj <<
+/D [3408 0 R /XYZ 71.731 542.0026 null]
+>> endobj
+3419 0 obj <<
+/D [3408 0 R /XYZ 89.6638 526.2266 null]
+>> endobj
+3420 0 obj <<
+/D [3408 0 R /XYZ 71.731 524.0698 null]
+>> endobj
+3421 0 obj <<
+/D [3408 0 R /XYZ 89.6638 508.2939 null]
+>> endobj
+3422 0 obj <<
+/D [3408 0 R /XYZ 71.731 501.1557 null]
+>> endobj
+3423 0 obj <<
+/D [3408 0 R /XYZ 71.731 478.2417 null]
+>> endobj
+3424 0 obj <<
+/D [3408 0 R /XYZ 71.731 412.1544 null]
+>> endobj
+3425 0 obj <<
+/D [3408 0 R /XYZ 71.731 335.2778 null]
+>> endobj
+3426 0 obj <<
+/D [3408 0 R /XYZ 71.731 222.5654 null]
+>> endobj
+3427 0 obj <<
+/D [3408 0 R /XYZ 71.731 202.4758 null]
+>> endobj
+3428 0 obj <<
+/D [3408 0 R /XYZ 71.731 153.6589 null]
+>> endobj
+3429 0 obj <<
+/D [3408 0 R /XYZ 71.731 120.8469 null]
+>> endobj
+3407 0 obj <<
+/Font << /F33 1322 0 R /F27 1222 0 R /F35 1604 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+3432 0 obj <<
+/Length 2132
+/Filter /FlateDecode
+>>
+stream
+xڍَ6}@DS3bv`E0t-dr"ŪbN[Vb?e~}]al g;D~gW)K#?ZuY&*>KB/Y9O'qnepǬPj
+XD!,=!&Դ / n؋uS0zOUqJYqw<aw={swA ЊKXwp#;25=P/
+}o(10(eC)sI8AP <W5A0:B(¨]^E%Mbfc8Qcg`t-ࢣp$ʣdõ=\b@wj
+me5;P!D}59bJ',!b^`xi%l_7b@aYra4:сE8x
+eYdDzϔkYA^bݎ=r52~l_aBi1TkZЦU;F]Ԩ24j9Ztpۊ&mߚg,w\b'L"q3G`AЮ\+k82F&4Q4-E^0Ͽ7ۗl+~x5eS-T3g2pih;٨9ʯ MO1iڧ'ݶFgHH{֑ ӵ|W*H1Tq$?5 u+ '5_/
+endobj
+3431 0 obj <<
+/Type /Page
+/Contents 3432 0 R
+/Resources 3430 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 3468 0 R
+/Annots [ 3435 0 R ]
+>> endobj
+3435 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [413.9471 705.4601 465.783 715.9347]
+/Subtype /Link
+/A << /S /GoTo /D (groups) >>
+>> endobj
+3433 0 obj <<
+/D [3431 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+3434 0 obj <<
+/D [3431 0 R /XYZ 71.731 718.3063 null]
+>> endobj
+1620 0 obj <<
+/D [3431 0 R /XYZ 71.731 660.523 null]
+>> endobj
+470 0 obj <<
+/D [3431 0 R /XYZ 210.4345 615.2687 null]
+>> endobj
+3436 0 obj <<
+/D [3431 0 R /XYZ 71.731 603.0975 null]
+>> endobj
+3437 0 obj <<
+/D [3431 0 R /XYZ 71.731 547.7171 null]
+>> endobj
+3438 0 obj <<
+/D [3431 0 R /XYZ 510.3067 498.0682 null]
+>> endobj
+3439 0 obj <<
+/D [3431 0 R /XYZ 71.731 477.9786 null]
+>> endobj
+3440 0 obj <<
+/D [3431 0 R /XYZ 71.731 465.0272 null]
+>> endobj
+3441 0 obj <<
+/D [3431 0 R /XYZ 71.731 460.0459 null]
+>> endobj
+3442 0 obj <<
+/D [3431 0 R /XYZ 89.6638 439.2886 null]
+>> endobj
+3443 0 obj <<
+/D [3431 0 R /XYZ 131.1675 439.2886 null]
+>> endobj
+3444 0 obj <<
+/D [3431 0 R /XYZ 264.267 439.2886 null]
+>> endobj
+3445 0 obj <<
+/D [3431 0 R /XYZ 71.731 437.1318 null]
+>> endobj
+3446 0 obj <<
+/D [3431 0 R /XYZ 89.6638 421.3559 null]
+>> endobj
+3447 0 obj <<
+/D [3431 0 R /XYZ 131.1675 421.3559 null]
+>> endobj
+3448 0 obj <<
+/D [3431 0 R /XYZ 71.731 419.1991 null]
+>> endobj
+3449 0 obj <<
+/D [3431 0 R /XYZ 89.6638 403.4231 null]
+>> endobj
+3450 0 obj <<
+/D [3431 0 R /XYZ 137.6244 403.4231 null]
+>> endobj
+3451 0 obj <<
+/D [3431 0 R /XYZ 249.7943 403.4231 null]
+>> endobj
+3452 0 obj <<
+/D [3431 0 R /XYZ 325.9286 403.4231 null]
+>> endobj
+3453 0 obj <<
+/D [3431 0 R /XYZ 409.7042 403.4231 null]
+>> endobj
+3454 0 obj <<
+/D [3431 0 R /XYZ 503.7813 403.4231 null]
+>> endobj
+3455 0 obj <<
+/D [3431 0 R /XYZ 214.4934 390.4717 null]
+>> endobj
+3456 0 obj <<
+/D [3431 0 R /XYZ 89.6638 377.5203 null]
+>> endobj
+1621 0 obj <<
+/D [3431 0 R /XYZ 71.731 370.3821 null]
+>> endobj
+474 0 obj <<
+/D [3431 0 R /XYZ 176.8299 327.2846 null]
+>> endobj
+3457 0 obj <<
+/D [3431 0 R /XYZ 71.731 318.4618 null]
+>> endobj
+3458 0 obj <<
+/D [3431 0 R /XYZ 71.731 285.6359 null]
+>> endobj
+3459 0 obj <<
+/D [3431 0 R /XYZ 71.731 274.7418 null]
+>> endobj
+3460 0 obj <<
+/D [3431 0 R /XYZ 71.731 269.7604 null]
+>> endobj
+3461 0 obj <<
+/D [3431 0 R /XYZ 89.6638 246.9459 null]
+>> endobj
+3462 0 obj <<
+/D [3431 0 R /XYZ 71.731 244.7891 null]
+>> endobj
+3463 0 obj <<
+/D [3431 0 R /XYZ 89.6638 229.0132 null]
+>> endobj
+3464 0 obj <<
+/D [3431 0 R /XYZ 71.731 213.9049 null]
+>> endobj
+3465 0 obj <<
+/D [3431 0 R /XYZ 89.6638 198.129 null]
+>> endobj
+1622 0 obj <<
+/D [3431 0 R /XYZ 71.731 190.9908 null]
+>> endobj
+478 0 obj <<
+/D [3431 0 R /XYZ 194.2 147.8934 null]
+>> endobj
+3466 0 obj <<
+/D [3431 0 R /XYZ 71.731 139.0705 null]
+>> endobj
+3467 0 obj <<
+/D [3431 0 R /XYZ 71.731 111.2259 null]
+>> endobj
+3430 0 obj <<
+/Font << /F33 1322 0 R /F23 1214 0 R /F44 2021 0 R /F27 1222 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+3471 0 obj <<
+/Length 2192
+/Filter /FlateDecode
+>>
+stream
+xڍYKϯ0|r#mޏ,f`fb'ZmeIt~}XEeu&YH"EB"H`Q>yLϐ5c.觗OOaEe<O^/0Yg_ӪX
+CTЫfGUu-O_^0y'r8ny E<&"p|0
+S⯭V?OQ䀃X^ ՠFYj;]
+YJo7—RtՀ .@ٝ_:> |
+ڸk Ჴ^1.mHӾ*@PIî*]W"}Ra?!^ɋ%0a͋uIFs%:R&J
+?ƘTTFpg]G!+P 
+C]5JcjHΡlFJ~n.+Y.hؿ^C)Gy>鈚|?1gfoS{Zy#HYkE/LLn
+ev'oFkmmeaX3NCYQxq YК/Qaq5rZbV
+endobj
+3470 0 obj <<
+/Type /Page
+/Contents 3471 0 R
+/Resources 3469 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 3468 0 R
+>> endobj
+3472 0 obj <<
+/D [3470 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+3473 0 obj <<
+/D [3470 0 R /XYZ 71.731 741.2204 null]
+>> endobj
+3474 0 obj <<
+/D [3470 0 R /XYZ 71.731 718.3063 null]
+>> endobj
+3475 0 obj <<
+/D [3470 0 R /XYZ 71.731 668.792 null]
+>> endobj
+3476 0 obj <<
+/D [3470 0 R /XYZ 71.731 654.401 null]
+>> endobj
+3477 0 obj <<
+/D [3470 0 R /XYZ 71.731 649.4197 null]
+>> endobj
+3478 0 obj <<
+/D [3470 0 R /XYZ 89.6638 627.9452 null]
+>> endobj
+3479 0 obj <<
+/D [3470 0 R /XYZ 71.731 625.7884 null]
+>> endobj
+3480 0 obj <<
+/D [3470 0 R /XYZ 89.6638 610.0124 null]
+>> endobj
+3481 0 obj <<
+/D [3470 0 R /XYZ 71.731 607.8556 null]
+>> endobj
+3482 0 obj <<
+/D [3470 0 R /XYZ 89.6638 592.0797 null]
+>> endobj
+1623 0 obj <<
+/D [3470 0 R /XYZ 71.731 548.1445 null]
+>> endobj
+482 0 obj <<
+/D [3470 0 R /XYZ 150.0257 502.9898 null]
+>> endobj
+3483 0 obj <<
+/D [3470 0 R /XYZ 71.731 490.5518 null]
+>> endobj
+3484 0 obj <<
+/D [3470 0 R /XYZ 366.7665 481.4306 null]
+>> endobj
+3485 0 obj <<
+/D [3470 0 R /XYZ 395.8187 481.4306 null]
+>> endobj
+3486 0 obj <<
+/D [3470 0 R /XYZ 396.1912 455.5277 null]
+>> endobj
+1624 0 obj <<
+/D [3470 0 R /XYZ 71.731 440.4195 null]
+>> endobj
+486 0 obj <<
+/D [3470 0 R /XYZ 235.9924 403.204 null]
+>> endobj
+3487 0 obj <<
+/D [3470 0 R /XYZ 71.731 393.0613 null]
+>> endobj
+3488 0 obj <<
+/D [3470 0 R /XYZ 260.9652 383.0794 null]
+>> endobj
+3489 0 obj <<
+/D [3470 0 R /XYZ 154.0914 370.128 null]
+>> endobj
+3490 0 obj <<
+/D [3470 0 R /XYZ 71.731 362.9898 null]
+>> endobj
+3491 0 obj <<
+/D [3470 0 R /XYZ 220.5913 352.1952 null]
+>> endobj
+3492 0 obj <<
+/D [3470 0 R /XYZ 71.731 345.0571 null]
+>> endobj
+3493 0 obj <<
+/D [3470 0 R /XYZ 89.6638 324.2999 null]
+>> endobj
+3494 0 obj <<
+/D [3470 0 R /XYZ 299.9426 324.2999 null]
+>> endobj
+3495 0 obj <<
+/D [3470 0 R /XYZ 71.731 317.1617 null]
+>> endobj
+3496 0 obj <<
+/D [3470 0 R /XYZ 164.6079 306.3671 null]
+>> endobj
+3497 0 obj <<
+/D [3470 0 R /XYZ 287.74 306.3671 null]
+>> endobj
+3498 0 obj <<
+/D [3470 0 R /XYZ 258.7481 293.4157 null]
+>> endobj
+3499 0 obj <<
+/D [3470 0 R /XYZ 276.9995 293.4157 null]
+>> endobj
+3500 0 obj <<
+/D [3470 0 R /XYZ 311.0217 293.4157 null]
+>> endobj
+3501 0 obj <<
+/D [3470 0 R /XYZ 71.731 291.2588 null]
+>> endobj
+3502 0 obj <<
+/D [3470 0 R /XYZ 89.6638 275.4829 null]
+>> endobj
+3503 0 obj <<
+/D [3470 0 R /XYZ 208.7959 275.4829 null]
+>> endobj
+3504 0 obj <<
+/D [3470 0 R /XYZ 71.731 273.3261 null]
+>> endobj
+3505 0 obj <<
+/D [3470 0 R /XYZ 89.6638 257.5502 null]
+>> endobj
+3506 0 obj <<
+/D [3470 0 R /XYZ 178.1909 257.5502 null]
+>> endobj
+3507 0 obj <<
+/D [3470 0 R /XYZ 284.4121 257.5502 null]
+>> endobj
+3508 0 obj <<
+/D [3470 0 R /XYZ 71.731 255.3933 null]
+>> endobj
+3509 0 obj <<
+/D [3470 0 R /XYZ 89.6638 239.6174 null]
+>> endobj
+3510 0 obj <<
+/D [3470 0 R /XYZ 89.6638 226.666 null]
+>> endobj
+3511 0 obj <<
+/D [3470 0 R /XYZ 202.6386 226.666 null]
+>> endobj
+3512 0 obj <<
+/D [3470 0 R /XYZ 71.731 225.2264 null]
+>> endobj
+3513 0 obj <<
+/D [3470 0 R /XYZ 89.6638 208.7332 null]
+>> endobj
+1625 0 obj <<
+/D [3470 0 R /XYZ 71.731 172.8677 null]
+>> endobj
+3469 0 obj <<
+/Font << /F33 1322 0 R /F23 1214 0 R /F44 2021 0 R /F27 1222 0 R /F35 1604 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+3516 0 obj <<
+/Length 1886
+/Filter /FlateDecode
+>>
+stream
+xڵXK4ϯmډ\,RP-pNI$_O%gR˭VwC
+Qa>, Y,
+E|~Y~s,AuuHsS^7"}OroNyeAë:6B" r!"2&F"!ZN_~cd<hɑH#'XXYy"$ ytQvi V\aaP`</U,?dTDUHx,@\,<E^H27yi
+9P*箟#2blK${.H4qХ78h rQI~( 3/\I=#ne7tfjN9e8PײmEP2o8?I3dyK|Džm/۽j[i(aj Jjί3˦RxmFy-/#|A/{_O^nu_o}VĿGDiBFoå<:
+
+@f$˞%>僾tÑH.1yD偸MC4!efv0Ҁ;P*_>==1UN۝JfA[1zV-UH6ӀD+&&l;6*=<9]>z}VCUf|Bì|s4$LNd΂a(dzj c6Hxo2]iSV9XS靓B1{Lil9@"IX 2T)`.\
+DMD7^@ĚxOLjO C}^$L60sq3 uSrP<u&FС,N)EɲP6Y6$NČ7Y @G3*֤֬9cdSJ\
+=F tjGĒsYhE g@ǙF́VKv˽͡-MDi~u9S'ʹF``x@BR3IBFY
+r 3IU&"ʠt$
+=PBx[L.1Yҽ,ۅ{-IlHwAjw oՙ)Mdcs݉ \
+endobj
+3515 0 obj <<
+/Type /Page
+/Contents 3516 0 R
+/Resources 3514 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 3468 0 R
+>> endobj
+3517 0 obj <<
+/D [3515 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+490 0 obj <<
+/D [3515 0 R /XYZ 194.3607 708.1493 null]
+>> endobj
+1626 0 obj <<
+/D [3515 0 R /XYZ 71.731 704.9574 null]
+>> endobj
+494 0 obj <<
+/D [3515 0 R /XYZ 152.7618 673.6786 null]
+>> endobj
+3518 0 obj <<
+/D [3515 0 R /XYZ 71.731 667.5516 null]
+>> endobj
+3519 0 obj <<
+/D [3515 0 R /XYZ 188.4422 654.7495 null]
+>> endobj
+3520 0 obj <<
+/D [3515 0 R /XYZ 71.731 636.6525 null]
+>> endobj
+3521 0 obj <<
+/D [3515 0 R /XYZ 71.731 636.6525 null]
+>> endobj
+3522 0 obj <<
+/D [3515 0 R /XYZ 71.731 625.7189 null]
+>> endobj
+3523 0 obj <<
+/D [3515 0 R /XYZ 91.6563 607.9251 null]
+>> endobj
+3524 0 obj <<
+/D [3515 0 R /XYZ 71.731 585.0111 null]
+>> endobj
+3525 0 obj <<
+/D [3515 0 R /XYZ 91.6563 567.0783 null]
+>> endobj
+3526 0 obj <<
+/D [3515 0 R /XYZ 365.4273 567.0783 null]
+>> endobj
+3527 0 obj <<
+/D [3515 0 R /XYZ 71.731 554.9588 null]
+>> endobj
+3528 0 obj <<
+/D [3515 0 R /XYZ 71.731 554.9588 null]
+>> endobj
+3529 0 obj <<
+/D [3515 0 R /XYZ 71.731 544.1642 null]
+>> endobj
+3530 0 obj <<
+/D [3515 0 R /XYZ 91.6563 526.2315 null]
+>> endobj
+3531 0 obj <<
+/D [3515 0 R /XYZ 363.4245 526.2315 null]
+>> endobj
+3532 0 obj <<
+/D [3515 0 R /XYZ 71.731 503.3174 null]
+>> endobj
+3533 0 obj <<
+/D [3515 0 R /XYZ 273.6017 490.366 null]
+>> endobj
+1627 0 obj <<
+/D [3515 0 R /XYZ 71.731 460.3138 null]
+>> endobj
+498 0 obj <<
+/D [3515 0 R /XYZ 244.6004 423.0982 null]
+>> endobj
+3534 0 obj <<
+/D [3515 0 R /XYZ 71.731 412.7332 null]
+>> endobj
+3535 0 obj <<
+/D [3515 0 R /XYZ 144.9646 390.0223 null]
+>> endobj
+3536 0 obj <<
+/D [3515 0 R /XYZ 327.322 390.0223 null]
+>> endobj
+3537 0 obj <<
+/D [3515 0 R /XYZ 107.1477 377.0708 null]
+>> endobj
+3538 0 obj <<
+/D [3515 0 R /XYZ 134.8934 377.0708 null]
+>> endobj
+3539 0 obj <<
+/D [3515 0 R /XYZ 71.731 371.99 null]
+>> endobj
+3540 0 obj <<
+/D [3515 0 R /XYZ 311.2942 346.1866 null]
+>> endobj
+3541 0 obj <<
+/D [3515 0 R /XYZ 71.731 326.0971 null]
+>> endobj
+3542 0 obj <<
+/D [3515 0 R /XYZ 120.8687 315.3025 null]
+>> endobj
+3543 0 obj <<
+/D [3515 0 R /XYZ 319.5499 302.351 null]
+>> endobj
+3544 0 obj <<
+/D [3515 0 R /XYZ 448.3736 302.351 null]
+>> endobj
+1628 0 obj <<
+/D [3515 0 R /XYZ 71.731 282.2615 null]
+>> endobj
+502 0 obj <<
+/D [3515 0 R /XYZ 242.592 245.0459 null]
+>> endobj
+3545 0 obj <<
+/D [3515 0 R /XYZ 71.731 234.6809 null]
+>> endobj
+1629 0 obj <<
+/D [3515 0 R /XYZ 71.731 222.7646 null]
+>> endobj
+506 0 obj <<
+/D [3515 0 R /XYZ 214.9999 190.4507 null]
+>> endobj
+3546 0 obj <<
+/D [3515 0 R /XYZ 71.731 181.8132 null]
+>> endobj
+3547 0 obj <<
+/D [3515 0 R /XYZ 71.731 164.3835 null]
+>> endobj
+3548 0 obj <<
+/D [3515 0 R /XYZ 361.8061 153.5889 null]
+>> endobj
+3549 0 obj <<
+/D [3515 0 R /XYZ 490.9416 140.6375 null]
+>> endobj
+3550 0 obj <<
+/D [3515 0 R /XYZ 71.731 127.686 null]
+>> endobj
+3551 0 obj <<
+/D [3515 0 R /XYZ 71.731 107.5964 null]
+>> endobj
+3552 0 obj <<
+/D [3515 0 R /XYZ 320.7944 96.8018 null]
+>> endobj
+3514 0 obj <<
+/Font << /F33 1322 0 R /F23 1214 0 R /F27 1222 0 R /F35 1604 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+3555 0 obj <<
+/Length 2056
+/Filter /FlateDecode
+>>
+stream
+xڝX[ ~?oGڊ$ڗNs4LiL-smH+gey!Å7M
+<K6u!ڜ`bf3~ӇbSU䛧MEaզIXfqy:=tQ}EVwzqݝh?iO?|o"JM Ӭ 26$
+U;*P0F"*C3CB;pYYy.\rUpQN6]%H\HCF8ܪnhBw4kDkfd Ma0vjRKzPj!=lG(GШYʛG!yYn(
+^x[OF4DJy7tvhqsV8do1rV=Ձ#ic\A@]; >dYw
+%i2 T0z@J;o(ׁh1x8zTǬU2+7z)N+}+>YVt^Q8-fحEr>9*҇Y,HpJu|24ʺtר Pyp4#?A5dëA A)_ӨW9br'aF m\SN{JÁħNUbzvt˾\qa)
+
+4 3P/o5gYn>Y w
+q.|d=68<NJCryEXPQ
+Ej^L-&;v $Ud}BQOX/%i(h a 3`Q+ 64]<.o䋶 H(&[2S; v7(]6)!:U4 =lJFx!1`b?Ʀ5ÔےE}j|̫ GhZh ]pv^1-`ѳ5VMd ًk\Uu`EO0KLWv4LE^̱=:y|03@\Oqe~ ٨հ[W#oܳ&o}<t \kzw-q?;
+L}ܭ#FQXF i?odPtݧ"E.:ٙ&Kժ
+d)6")2owi?WNGe4
+
+i9OicnuIQf wMu(i lG_
+ќI{3ۇVMۺ_ wG;TL]S@_\"y|Q$lxn%>=4OKy[{1&Gfc_m~yjGcQ_h셂BH e &)H"f!^9oo?K^<*{so{4*z OHטݿ}ZeWo>xj\\U^u ~K !endstream
+endobj
+3554 0 obj <<
+/Type /Page
+/Contents 3555 0 R
+/Resources 3553 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 3468 0 R
+/Annots [ 3580 0 R 3581 0 R ]
+>> endobj
+3580 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [507.0988 337.6489 538.9788 348.5528]
+/Subtype /Link
+/A << /S /GoTo /D (flags-create) >>
+>> endobj
+3581 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [70.7348 325.071 100.1245 335.6014]
+/Subtype /Link
+/A << /S /GoTo /D (flags-create) >>
+>> endobj
+3556 0 obj <<
+/D [3554 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+3557 0 obj <<
+/D [3554 0 R /XYZ 71.731 718.3063 null]
+>> endobj
+3558 0 obj <<
+/D [3554 0 R /XYZ 89.6638 708.3437 null]
+>> endobj
+3559 0 obj <<
+/D [3554 0 R /XYZ 219.6243 708.3437 null]
+>> endobj
+3560 0 obj <<
+/D [3554 0 R /XYZ 71.731 693.2354 null]
+>> endobj
+3561 0 obj <<
+/D [3554 0 R /XYZ 89.6638 677.4595 null]
+>> endobj
+3562 0 obj <<
+/D [3554 0 R /XYZ 134.39 677.4595 null]
+>> endobj
+3563 0 obj <<
+/D [3554 0 R /XYZ 109.8678 664.5081 null]
+>> endobj
+3564 0 obj <<
+/D [3554 0 R /XYZ 71.731 662.3513 null]
+>> endobj
+3565 0 obj <<
+/D [3554 0 R /XYZ 89.6638 646.5753 null]
+>> endobj
+3566 0 obj <<
+/D [3554 0 R /XYZ 192.792 646.5753 null]
+>> endobj
+3567 0 obj <<
+/D [3554 0 R /XYZ 384.0197 646.5753 null]
+>> endobj
+3568 0 obj <<
+/D [3554 0 R /XYZ 114.0123 633.6239 null]
+>> endobj
+1630 0 obj <<
+/D [3554 0 R /XYZ 71.731 610.7098 null]
+>> endobj
+510 0 obj <<
+/D [3554 0 R /XYZ 172.6073 575.2428 null]
+>> endobj
+3569 0 obj <<
+/D [3554 0 R /XYZ 71.731 566.6053 null]
+>> endobj
+3570 0 obj <<
+/D [3554 0 R /XYZ 389.4099 556.3138 null]
+>> endobj
+3571 0 obj <<
+/D [3554 0 R /XYZ 458.9373 556.3138 null]
+>> endobj
+3572 0 obj <<
+/D [3554 0 R /XYZ 71.731 538.2815 null]
+>> endobj
+3573 0 obj <<
+/D [3554 0 R /XYZ 176.4672 512.4782 null]
+>> endobj
+1631 0 obj <<
+/D [3554 0 R /XYZ 71.731 495.3774 null]
+>> endobj
+514 0 obj <<
+/D [3554 0 R /XYZ 249.3775 458.1619 null]
+>> endobj
+3574 0 obj <<
+/D [3554 0 R /XYZ 71.731 447.7969 null]
+>> endobj
+3575 0 obj <<
+/D [3554 0 R /XYZ 135.5078 438.0373 null]
+>> endobj
+3576 0 obj <<
+/D [3554 0 R /XYZ 86.3732 425.0859 null]
+>> endobj
+3577 0 obj <<
+/D [3554 0 R /XYZ 220.9876 425.0859 null]
+>> endobj
+3578 0 obj <<
+/D [3554 0 R /XYZ 71.731 404.9963 null]
+>> endobj
+1632 0 obj <<
+/D [3554 0 R /XYZ 71.731 392.0449 null]
+>> endobj
+518 0 obj <<
+/D [3554 0 R /XYZ 193.2056 359.731 null]
+>> endobj
+3579 0 obj <<
+/D [3554 0 R /XYZ 71.731 351.0935 null]
+>> endobj
+1633 0 obj <<
+/D [3554 0 R /XYZ 71.731 321.086 null]
+>> endobj
+522 0 obj <<
+/D [3554 0 R /XYZ 201.1796 287.4022 null]
+>> endobj
+3582 0 obj <<
+/D [3554 0 R /XYZ 71.731 278.7647 null]
+>> endobj
+3583 0 obj <<
+/D [3554 0 R /XYZ 165.864 268.4732 null]
+>> endobj
+3584 0 obj <<
+/D [3554 0 R /XYZ 71.731 255.4222 null]
+>> endobj
+526 0 obj <<
+/D [3554 0 R /XYZ 142.614 225.1357 null]
+>> endobj
+3585 0 obj <<
+/D [3554 0 R /XYZ 71.731 219.9502 null]
+>> endobj
+3586 0 obj <<
+/D [3554 0 R /XYZ 71.731 187.1134 null]
+>> endobj
+530 0 obj <<
+/D [3554 0 R /XYZ 166.0159 156.3935 null]
+>> endobj
+3587 0 obj <<
+/D [3554 0 R /XYZ 71.731 149.3151 null]
+>> endobj
+3588 0 obj <<
+/D [3554 0 R /XYZ 511.1135 138.4607 null]
+>> endobj
+3589 0 obj <<
+/D [3554 0 R /XYZ 106.0422 125.5093 null]
+>> endobj
+3590 0 obj <<
+/D [3554 0 R /XYZ 71.731 118.3712 null]
+>> endobj
+3553 0 obj <<
+/Font << /F33 1322 0 R /F27 1222 0 R /F23 1214 0 R /F35 1604 0 R /F48 2037 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+3593 0 obj <<
+/Length 2778
+/Filter /FlateDecode
+>>
+stream
+xڝَ8=_o1Z}r.zf7L`,٢m!t{~Drh,b],Rxߤ$qpڽn60,fa}xq1 or7Oa}yzY~~vPy-:A9rW5U 0qgU?33C {K̒0
+S0t37vK3]ѩC98ZûY- xz{Nq" g,>4jc}Z
+ؕ/&Zn;-3{U*vq eє5+F5];gjżMXƾAfׯ,"RZ&'l<18n(nbR\YǶM;,O.Cwk춼a:xDETڪg6}9s!-|G;ԆU2ª1Ub?ŋ=2.vsD4bEP+urgN'$@HQ8@7@`YtP;25q<cdzI`> DT }<ժB8IaW k@0@ Yp01\4ιߪZ:3K-B[RS{9'Q*a)\$`0 :J[}\o
+r<M[r[AȎDWc=utŇCOK-와xk'nU]N䰲 SN-ʖS%es.JXC meXOXl~C[ڻð*
+lbbX.:փ80 Z
+wSl脞`:_*0DoP*6̥6@H96%T"Ec0$GqE\zSbԻn!?b|D4kK-6U*F{
+.bŨá6NS$̓NJlD*-'(˜{&HލfxMFc~WCU*} ;ąa3
+<+
+LBτy2V;dQ݁>̹,q2LzE;OK2u>sC*vS370$.qT
+A^>m#HC5
+%pz#,̙]b;0.@VƗş)ihlк-StDt2Z0l߸fiD\֪bv=rޢT.b5X`88Tjԧ9g< zvR<@CE9jUe@S/qL^gٌ̍y$Ms ,.fpsM%TufdXO1$\՚ oyCn]mFWvnUӊd\@vY0N^eGVsǺ?Od7ZFnBצy%j}2&Pw}az`)2K;{r]Cї-Ƕ{M8Io6
+ujsa'/i4*C)C)!3mT]xwknF%=!;46!jeЭ!bx1Û_,!)dēp!SYBT㛑#jޥYc#SjL0g*@ô
+7|!)$,Q>=%S.S.&ɨ @5|
+endobj
+3592 0 obj <<
+/Type /Page
+/Contents 3593 0 R
+/Resources 3591 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 3468 0 R
+>> endobj
+3594 0 obj <<
+/D [3592 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+534 0 obj <<
+/D [3592 0 R /XYZ 156.7607 708.3437 null]
+>> endobj
+3595 0 obj <<
+/D [3592 0 R /XYZ 71.731 701.1457 null]
+>> endobj
+3596 0 obj <<
+/D [3592 0 R /XYZ 71.731 677.4595 null]
+>> endobj
+3597 0 obj <<
+/D [3592 0 R /XYZ 266.7311 677.4595 null]
+>> endobj
+3598 0 obj <<
+/D [3592 0 R /XYZ 71.731 651.5566 null]
+>> endobj
+3599 0 obj <<
+/D [3592 0 R /XYZ 71.731 644.4185 null]
+>> endobj
+3600 0 obj <<
+/D [3592 0 R /XYZ 244.2357 620.6725 null]
+>> endobj
+3601 0 obj <<
+/D [3592 0 R /XYZ 397.3909 620.6725 null]
+>> endobj
+3602 0 obj <<
+/D [3592 0 R /XYZ 111.0175 607.721 null]
+>> endobj
+3603 0 obj <<
+/D [3592 0 R /XYZ 279.6199 607.721 null]
+>> endobj
+3604 0 obj <<
+/D [3592 0 R /XYZ 71.731 594.7696 null]
+>> endobj
+3605 0 obj <<
+/D [3592 0 R /XYZ 345.1534 594.7696 null]
+>> endobj
+3606 0 obj <<
+/D [3592 0 R /XYZ 71.731 587.6314 null]
+>> endobj
+3607 0 obj <<
+/D [3592 0 R /XYZ 226.9571 563.8854 null]
+>> endobj
+3608 0 obj <<
+/D [3592 0 R /XYZ 485.4103 563.8854 null]
+>> endobj
+3609 0 obj <<
+/D [3592 0 R /XYZ 71.731 543.7958 null]
+>> endobj
+3610 0 obj <<
+/D [3592 0 R /XYZ 109.3962 533.0012 null]
+>> endobj
+3611 0 obj <<
+/D [3592 0 R /XYZ 143.7536 533.0012 null]
+>> endobj
+3612 0 obj <<
+/D [3592 0 R /XYZ 388.8861 533.0012 null]
+>> endobj
+3613 0 obj <<
+/D [3592 0 R /XYZ 134.6438 520.0498 null]
+>> endobj
+3614 0 obj <<
+/D [3592 0 R /XYZ 226.9412 520.0498 null]
+>> endobj
+3615 0 obj <<
+/D [3592 0 R /XYZ 71.731 507.0984 null]
+>> endobj
+3616 0 obj <<
+/D [3592 0 R /XYZ 146.7192 507.0984 null]
+>> endobj
+3617 0 obj <<
+/D [3592 0 R /XYZ 71.731 501.9976 null]
+>> endobj
+3618 0 obj <<
+/D [3592 0 R /XYZ 71.731 456.1246 null]
+>> endobj
+3619 0 obj <<
+/D [3592 0 R /XYZ 71.731 456.1246 null]
+>> endobj
+3620 0 obj <<
+/D [3592 0 R /XYZ 257.935 445.33 null]
+>> endobj
+3621 0 obj <<
+/D [3592 0 R /XYZ 439.3913 432.3786 null]
+>> endobj
+3622 0 obj <<
+/D [3592 0 R /XYZ 146.1379 419.4271 null]
+>> endobj
+3623 0 obj <<
+/D [3592 0 R /XYZ 222.4669 419.4271 null]
+>> endobj
+3624 0 obj <<
+/D [3592 0 R /XYZ 281.2438 406.4757 null]
+>> endobj
+3625 0 obj <<
+/D [3592 0 R /XYZ 435.614 406.4757 null]
+>> endobj
+3626 0 obj <<
+/D [3592 0 R /XYZ 71.731 399.3376 null]
+>> endobj
+538 0 obj <<
+/D [3592 0 R /XYZ 154.0508 368.6177 null]
+>> endobj
+3627 0 obj <<
+/D [3592 0 R /XYZ 71.731 361.5393 null]
+>> endobj
+3628 0 obj <<
+/D [3592 0 R /XYZ 71.731 304.6925 null]
+>> endobj
+3629 0 obj <<
+/D [3592 0 R /XYZ 71.731 304.6925 null]
+>> endobj
+3630 0 obj <<
+/D [3592 0 R /XYZ 71.731 273.8083 null]
+>> endobj
+542 0 obj <<
+/D [3592 0 R /XYZ 142.9228 243.0884 null]
+>> endobj
+3631 0 obj <<
+/D [3592 0 R /XYZ 71.731 237.9029 null]
+>> endobj
+3632 0 obj <<
+/D [3592 0 R /XYZ 224.1952 212.2042 null]
+>> endobj
+3633 0 obj <<
+/D [3592 0 R /XYZ 71.731 179.1632 null]
+>> endobj
+546 0 obj <<
+/D [3592 0 R /XYZ 171.7743 148.4433 null]
+>> endobj
+3634 0 obj <<
+/D [3592 0 R /XYZ 71.731 141.3649 null]
+>> endobj
+3635 0 obj <<
+/D [3592 0 R /XYZ 181.4647 130.5106 null]
+>> endobj
+3636 0 obj <<
+/D [3592 0 R /XYZ 380.9392 130.5106 null]
+>> endobj
+3637 0 obj <<
+/D [3592 0 R /XYZ 473.5968 130.5106 null]
+>> endobj
+3638 0 obj <<
+/D [3592 0 R /XYZ 509.5202 130.5106 null]
+>> endobj
+3639 0 obj <<
+/D [3592 0 R /XYZ 191.5112 117.5591 null]
+>> endobj
+3640 0 obj <<
+/D [3592 0 R /XYZ 71.731 110.421 null]
+>> endobj
+3591 0 obj <<
+/Font << /F33 1322 0 R /F48 2037 0 R /F27 1222 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+3643 0 obj <<
+/Length 2196
+/Filter /FlateDecode
+>>
+stream
+xڕ]۸=BoqkF${(6irhEh[]}ܽ g(ѻN.rC(r)r U
+*7qtOo$fxQ)Le!JXqQFVHe=|8lNF ]7H5>LJysQ.TeL@Fm\IE!RD旳/q*Ӷl}l[oZB`8IA+R=,ya+㍹3
+ëQ KO*]mc,(%)tAgaxWaW>쉝uXuI102-q%mfMl2@YńZb
+>G7[`ot
+}; āMpݞ`Kf=_i1/wfMul?3CU">Opz+;h+j<\p
+ɞhf84Uh4\hG
+UcR֯w= X0M04[KPx1OR7.1Koۚ/4xG5 z":"wv?"YMWe"I-`5^;
++NKt:B;
+n4Ѐ\6x%le[K(h -F҅'k_.U>LwYgږ*RCOĬ\/M%3طdyւf-qUֶLa ;CfRp;m}>-SS,z)du8sӵEJqu5+/~ ?˥Ou%A%K5N'V_X5_#wGv00[7H7d$wrc샹Lr=W17#=̀iRC
+}+tf43; vƨtX_qY CamF{%]0b?jqm-:{<Lv*ZĉtQpOcҜ6
+-nᄃ'8%ǯdP!JU ӔZBG!B-)61*!ӑF[uO|+Tp78gGQ\؉@x'e#`/
+p_nٮ7{V"bw$֞fmmiw08xCvQ [~q/? ,4W^5UGȧW@ Ŀ< УcKAJu.4X;^{U DCd*
+S73>ѱEOdSUvE2.|RBfT"ey*^l:/endstream
+endobj
+3642 0 obj <<
+/Type /Page
+/Contents 3643 0 R
+/Resources 3641 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 3468 0 R
+>> endobj
+3644 0 obj <<
+/D [3642 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+550 0 obj <<
+/D [3642 0 R /XYZ 224.3666 708.3437 null]
+>> endobj
+3645 0 obj <<
+/D [3642 0 R /XYZ 71.731 701.2653 null]
+>> endobj
+3646 0 obj <<
+/D [3642 0 R /XYZ 71.731 664.5081 null]
+>> endobj
+3647 0 obj <<
+/D [3642 0 R /XYZ 71.731 644.4185 null]
+>> endobj
+554 0 obj <<
+/D [3642 0 R /XYZ 170.6486 613.6986 null]
+>> endobj
+3648 0 obj <<
+/D [3642 0 R /XYZ 71.731 606.6202 null]
+>> endobj
+3649 0 obj <<
+/D [3642 0 R /XYZ 129.5759 595.7659 null]
+>> endobj
+3650 0 obj <<
+/D [3642 0 R /XYZ 279.8553 582.8144 null]
+>> endobj
+3651 0 obj <<
+/D [3642 0 R /XYZ 349.9283 582.8144 null]
+>> endobj
+3652 0 obj <<
+/D [3642 0 R /XYZ 71.731 562.7248 null]
+>> endobj
+558 0 obj <<
+/D [3642 0 R /XYZ 148.7011 532.005 null]
+>> endobj
+3653 0 obj <<
+/D [3642 0 R /XYZ 71.731 526.8195 null]
+>> endobj
+3654 0 obj <<
+/D [3642 0 R /XYZ 71.731 493.9826 null]
+>> endobj
+562 0 obj <<
+/D [3642 0 R /XYZ 176.855 463.2627 null]
+>> endobj
+3655 0 obj <<
+/D [3642 0 R /XYZ 71.731 456.1844 null]
+>> endobj
+3656 0 obj <<
+/D [3642 0 R /XYZ 412.3745 445.33 null]
+>> endobj
+3657 0 obj <<
+/D [3642 0 R /XYZ 446.4532 445.33 null]
+>> endobj
+3658 0 obj <<
+/D [3642 0 R /XYZ 306.7655 432.3786 null]
+>> endobj
+3659 0 obj <<
+/D [3642 0 R /XYZ 71.731 412.289 null]
+>> endobj
+566 0 obj <<
+/D [3642 0 R /XYZ 189.1389 381.5691 null]
+>> endobj
+3660 0 obj <<
+/D [3642 0 R /XYZ 71.731 374.4907 null]
+>> endobj
+3661 0 obj <<
+/D [3642 0 R /XYZ 148.1582 350.6849 null]
+>> endobj
+1634 0 obj <<
+/D [3642 0 R /XYZ 71.731 320.6327 null]
+>> endobj
+570 0 obj <<
+/D [3642 0 R /XYZ 199.8526 287.3225 null]
+>> endobj
+3662 0 obj <<
+/D [3642 0 R /XYZ 71.731 278.685 null]
+>> endobj
+3663 0 obj <<
+/D [3642 0 R /XYZ 159.6658 268.3935 null]
+>> endobj
+3664 0 obj <<
+/D [3642 0 R /XYZ 71.731 248.3039 null]
+>> endobj
+3665 0 obj <<
+/D [3642 0 R /XYZ 186.5893 237.5093 null]
+>> endobj
+3666 0 obj <<
+/D [3642 0 R /XYZ 71.731 235.3525 null]
+>> endobj
+3667 0 obj <<
+/D [3642 0 R /XYZ 118.5554 196.7885 null]
+>> endobj
+3668 0 obj <<
+/D [3642 0 R /XYZ 232.2278 188.3241 null]
+>> endobj
+3669 0 obj <<
+/D [3642 0 R /XYZ 378.4963 165.0115 null]
+>> endobj
+1635 0 obj <<
+/D [3642 0 R /XYZ 71.731 123.166 null]
+>> endobj
+3641 0 obj <<
+/Font << /F33 1322 0 R /F48 2037 0 R /F27 1222 0 R /F23 1214 0 R /F44 2021 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+3672 0 obj <<
+/Length 2815
+/Filter /FlateDecode
+>>
+stream
+xZK۸WMK|f㔳`)JA$4B"x@7@Pf^Rfw
+fD~)qՊʼnૌ,|ZyMȆ*1̊j@F!4BD%<J(xnPdC
+),>E$ y
+Pmn8t20sm(6 `ߞƽf| XSSH򨏊TTd>%ҁfF@%mD0[HMBKr&G1.%O V|«{cQgjDH2Du"u*5[!D0:R 3^{i`vN$CF2!&/KQS#/>XOj0pJ(xMM!,ŌOT8Tgf0d(YԮ&v,H<./)1r*60-*斏of#iy[< @[,> ɰ۱cd Nwƙ؜xZ#Ȳ`T0brjsgwlw~-|^≋x-q1XWyz.a)%}I?}se욯Y|fQ]n^̉B$HP;ؙ@B-GTtk`M+h
+64EL߾lk!ԑHsS[fb`*xɃpF(Z9pG7HP^}:Zw# ut*f ŹC5v*mVdV˚݊(gI&^ v4;WyAw N๥N<.e1 tkȚA(p". iXvFa`oZҔSfX2P͜ư*wheq}X,]?^tclH[AK21BA^ %ѕ A43b{'riL
+R B[&ַbo|}ڈ zO4A mUZ@ps]f`eJX拺
+ϊD0C.eڄl׷WE,e3<+f䉹.>t cp<N \lrfbB;*2Cߝi7qYĜHY&"(Â[n*ip-Dܥ 68Wl(SD
+ƅA
+qw< WtG@o)b;x)٤`=\ol e%J|Hendstream
+endobj
+3671 0 obj <<
+/Type /Page
+/Contents 3672 0 R
+/Resources 3670 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 3703 0 R
+/Annots [ 3676 0 R 3698 0 R 3699 0 R ]
+>> endobj
+3676 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [252.1469 624.6186 304.4235 635.5225]
+/Subtype /Link
+/A << /S /GoTo /D (sanitycheck) >>
+>> endobj
+3698 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [683.8881 140.057 743.1654 153.1727]
+/Subtype /Link
+/A << /S /GoTo /D (edit-values-list) >>
+>> endobj
+3699 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [634.6345 128.9339 693.9118 142.0496]
+/Subtype /Link
+/A << /S /GoTo /D (edit-values-list) >>
+>> endobj
+3673 0 obj <<
+/D [3671 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+574 0 obj <<
+/D [3671 0 R /XYZ 186.2988 706.1179 null]
+>> endobj
+3674 0 obj <<
+/D [3671 0 R /XYZ 71.731 693.9467 null]
+>> endobj
+3675 0 obj <<
+/D [3671 0 R /XYZ 71.731 651.5177 null]
+>> endobj
+3677 0 obj <<
+/D [3671 0 R /XYZ 71.731 607.6821 null]
+>> endobj
+1636 0 obj <<
+/D [3671 0 R /XYZ 71.731 563.8465 null]
+>> endobj
+578 0 obj <<
+/D [3671 0 R /XYZ 233.4164 520.749 null]
+>> endobj
+3678 0 obj <<
+/D [3671 0 R /XYZ 71.731 511.9262 null]
+>> endobj
+3679 0 obj <<
+/D [3671 0 R /XYZ 71.731 460.236 null]
+>> endobj
+3680 0 obj <<
+/D [3671 0 R /XYZ 71.731 445.2921 null]
+>> endobj
+3681 0 obj <<
+/D [3671 0 R /XYZ 71.731 382.5274 null]
+>> endobj
+3682 0 obj <<
+/D [3671 0 R /XYZ 285.3061 369.5759 null]
+>> endobj
+1637 0 obj <<
+/D [3671 0 R /XYZ 71.731 354.4677 null]
+>> endobj
+582 0 obj <<
+/D [3671 0 R /XYZ 271.6858 317.2521 null]
+>> endobj
+3683 0 obj <<
+/D [3671 0 R /XYZ 71.731 306.8871 null]
+>> endobj
+3684 0 obj <<
+/D [3671 0 R /XYZ 71.731 277.7553 null]
+>> endobj
+3685 0 obj <<
+/D [3671 0 R /XYZ 327.8178 266.2434 null]
+>> endobj
+3686 0 obj <<
+/D [3671 0 R /XYZ 71.731 259.1053 null]
+>> endobj
+3687 0 obj <<
+/D [3671 0 R /XYZ 81.6937 238.348 null]
+>> endobj
+3688 0 obj <<
+/D [3671 0 R /XYZ 81.6937 238.348 null]
+>> endobj
+3689 0 obj <<
+/D [3671 0 R /XYZ 445.6032 238.348 null]
+>> endobj
+3690 0 obj <<
+/D [3671 0 R /XYZ 71.731 223.2398 null]
+>> endobj
+3691 0 obj <<
+/D [3671 0 R /XYZ 81.6937 207.4638 null]
+>> endobj
+3692 0 obj <<
+/D [3671 0 R /XYZ 81.6937 207.4638 null]
+>> endobj
+3693 0 obj <<
+/D [3671 0 R /XYZ 71.731 192.3556 null]
+>> endobj
+3694 0 obj <<
+/D [3671 0 R /XYZ 81.6937 176.5797 null]
+>> endobj
+3695 0 obj <<
+/D [3671 0 R /XYZ 81.6937 176.5797 null]
+>> endobj
+3696 0 obj <<
+/D [3671 0 R /XYZ 348.7394 176.5797 null]
+>> endobj
+3697 0 obj <<
+/D [3671 0 R /XYZ 71.731 174.4228 null]
+>> endobj
+3700 0 obj <<
+/D [3671 0 R /XYZ 71.731 118.807 null]
+>> endobj
+3701 0 obj <<
+/D [3671 0 R /XYZ 81.6937 103.031 null]
+>> endobj
+3702 0 obj <<
+/D [3671 0 R /XYZ 81.6937 103.031 null]
+>> endobj
+3670 0 obj <<
+/Font << /F33 1322 0 R /F23 1214 0 R /F27 1222 0 R /F44 2021 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+3706 0 obj <<
+/Length 2261
+/Filter /FlateDecode
+>>
+stream
+xڥYݏ8_)%snrJ$:vv:)_YaDQEGR\'ʄ#؝# d lLIE&Xŋ" iHi$?ˇ^mT,9/yy$ϼ([<҉RXU ;*Jg:YB@vsv:mo'j7{Zf4>o.y{nZ1-[: {4L~`$ӱ$oJEC ]r: <[{ă)IZ6F
+ǺFs+)heمݵv},6D
+mumov,(qJu3 %:̃/p؛lYQ
++<Hw.@%\{ rVJ*=K`"Y::MmP&I|mF ' 5<[jLQpP{Ƶސw g#J 8MԈ: Ԇv rE\:eOm|wxyhgS_I-0}k&; eDidШD"uƮ 3ږhK'LdQ`!BT-"U27۪fg4]nY(jRW4% '\U7 B- ܬeo޳#B5\lՉR`JiN@$?8B+$ڮ*gw?gm]x; -<H̙҉]hΖ@ՈUJnECTl~ \د+p.%Ζ)}GGwW<]l}6P%1q*K{45C<el^'׃`K/ DGgS[
+H}|iZ{n$ʐI a*\^^hF8Μ9;l}!L5w?G\k@WPA+_bE5AM"jdy.sMyrz :@-R88 h& gJEcQLC~+03!$5uA fM ʿPWyzMC9*h.~\[@8'J͉2ؑiyGNoH#hy2`jNL~0TtiZeFnb=A =tiP7@^C{/w2Vc4g Ä`N
+ )Ԛ8Ю88\
+\Ա퍜g\0˖-kuwoD:1cS-^Bi`6r]o#>~ =$hP J J7#ƗWe\t(1\ѵ{F*^E.P[y9 D B
+?x@$@fY' MwY/endstream
+endobj
+3705 0 obj <<
+/Type /Page
+/Contents 3706 0 R
+/Resources 3704 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 3703 0 R
+/Annots [ 3711 0 R 3719 0 R ]
+>> endobj
+3711 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [315.6739 674.3064 362.4979 685.2104]
+/Subtype /Link
+/A << /S /GoTo /D (bugreports) >>
+>> endobj
+3719 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [201.9014 519.2329 261.1787 528.0796]
+/Subtype /Link
+/A << /S /GoTo /D (edit-values-list) >>
+>> endobj
+3707 0 obj <<
+/D [3705 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+3708 0 obj <<
+/D [3705 0 R /XYZ 71.731 706.1869 null]
+>> endobj
+3709 0 obj <<
+/D [3705 0 R /XYZ 81.6937 690.4109 null]
+>> endobj
+3710 0 obj <<
+/D [3705 0 R /XYZ 81.6937 690.4109 null]
+>> endobj
+3712 0 obj <<
+/D [3705 0 R /XYZ 71.731 675.3027 null]
+>> endobj
+3713 0 obj <<
+/D [3705 0 R /XYZ 81.6937 659.5268 null]
+>> endobj
+3714 0 obj <<
+/D [3705 0 R /XYZ 81.6937 659.5268 null]
+>> endobj
+3715 0 obj <<
+/D [3705 0 R /XYZ 71.731 644.4185 null]
+>> endobj
+3716 0 obj <<
+/D [3705 0 R /XYZ 81.6937 628.6426 null]
+>> endobj
+3717 0 obj <<
+/D [3705 0 R /XYZ 81.6937 628.6426 null]
+>> endobj
+1638 0 obj <<
+/D [3705 0 R /XYZ 71.731 592.7771 null]
+>> endobj
+586 0 obj <<
+/D [3705 0 R /XYZ 271.04 553.4047 null]
+>> endobj
+3718 0 obj <<
+/D [3705 0 R /XYZ 71.731 543.0397 null]
+>> endobj
+1639 0 obj <<
+/D [3705 0 R /XYZ 71.731 515.2479 null]
+>> endobj
+590 0 obj <<
+/D [3705 0 R /XYZ 279.0164 475.9751 null]
+>> endobj
+3720 0 obj <<
+/D [3705 0 R /XYZ 71.731 465.6101 null]
+>> endobj
+3721 0 obj <<
+/D [3705 0 R /XYZ 124.8506 429.9477 null]
+>> endobj
+1640 0 obj <<
+/D [3705 0 R /XYZ 71.731 399.8955 null]
+>> endobj
+594 0 obj <<
+/D [3705 0 R /XYZ 219.0243 356.798 null]
+>> endobj
+3722 0 obj <<
+/D [3705 0 R /XYZ 71.731 344.36 null]
+>> endobj
+3723 0 obj <<
+/D [3705 0 R /XYZ 441.4437 322.2874 null]
+>> endobj
+1641 0 obj <<
+/D [3705 0 R /XYZ 71.731 294.2277 null]
+>> endobj
+598 0 obj <<
+/D [3705 0 R /XYZ 311.2372 257.0122 null]
+>> endobj
+3724 0 obj <<
+/D [3705 0 R /XYZ 71.731 246.6472 null]
+>> endobj
+3725 0 obj <<
+/D [3705 0 R /XYZ 190.7774 236.8876 null]
+>> endobj
+3726 0 obj <<
+/D [3705 0 R /XYZ 71.731 203.8466 null]
+>> endobj
+1642 0 obj <<
+/D [3705 0 R /XYZ 71.731 172.9624 null]
+>> endobj
+602 0 obj <<
+/D [3705 0 R /XYZ 261.227 135.7469 null]
+>> endobj
+3727 0 obj <<
+/D [3705 0 R /XYZ 71.731 125.3819 null]
+>> endobj
+3728 0 obj <<
+/D [3705 0 R /XYZ 71.731 113.4656 null]
+>> endobj
+3729 0 obj <<
+/D [3705 0 R /XYZ 71.731 108.4842 null]
+>> endobj
+3704 0 obj <<
+/Font << /F33 1322 0 R /F27 1222 0 R /F23 1214 0 R /F35 1604 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+3732 0 obj <<
+/Length 2479
+/Filter /FlateDecode
+>>
+stream
+xڝ]۶ݿBH3w,>Ŏqgb7<DBjTqgwDḃ?1[ oG&B9KJ0#<^o^ %^hV8šg쟋7ujt|<MajbOsn:a8ܕpƞE8{HLꑀ~`)"EZzR8(gCbmzA+(VOK.T-ʆm3mn)jsFٕKgWj:kDפּܷe;,TK޴˗-{_+(͵`جi[Uh3ntm c7 -P.T(~5ҲLcʢ%SO:mt%A` <U-2klvE~&г )$= Iabek/Ff6qqȵnaW
+ 7UT-o^{kX;*K̞ @ʜw6qh<J}[j?D KSl"8:5eR$|!ъu@97ן;m% B'XljDi[+hHBbJaIO:9 4(38[H٤ȹX攦D$;^>kQRJ+[ZcX\m<? GKxp}m ER@ІbkS6#XY_cQ0Jrh-#lrx<Вpe.5Y\'>i%\AN}2Xp@>
+NSXx>M2flf0=,Y[+3%A XfIWLnʊٗ^waaU [L6C*հ5b#/o2C@@Pn4¾4X _75QamvG]?[S15YpnãkP" µ+h#Nvǥ #?H O%D\a9Wn8يs;:h=ȳ&]c2YDG3h57tv܏uU%B}c@# <ӽdnOvif}jGƬFL{YȤm*RM>BJYBC68{6a4U1_ǭZG9(U-ap*LbH8ڠ9;J~ DRۄ8p]?&FԄJ(fxUV9OUZv4x•}rcwK/8ks+w~f\US.NRz,cv,3c!ƹMwߺ7`(I!{suEb"\ѶfhƺYwfWڴҊZ?&Lԇ1v d季(o!mwX-Xl+FkWQsl]d
+%&U1Q{JD RFA}>{n5X.7_:
+5.Bh<]K*1P9w[pٙػ.\1W>7P\UaGq#>8#KR0ha;/歼ZW]u IwT]yoC&|!Xwa!|` p)t> ExBiP ' u=`/Fj
+*
+endobj
+3731 0 obj <<
+/Type /Page
+/Contents 3732 0 R
+/Resources 3730 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 3703 0 R
+>> endobj
+3733 0 obj <<
+/D [3731 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+3734 0 obj <<
+/D [3731 0 R /XYZ 89.6638 708.3437 null]
+>> endobj
+3735 0 obj <<
+/D [3731 0 R /XYZ 71.731 706.1869 null]
+>> endobj
+3736 0 obj <<
+/D [3731 0 R /XYZ 89.6638 690.4109 null]
+>> endobj
+3737 0 obj <<
+/D [3731 0 R /XYZ 71.731 683.2728 null]
+>> endobj
+1643 0 obj <<
+/D [3731 0 R /XYZ 71.731 642.426 null]
+>> endobj
+606 0 obj <<
+/D [3731 0 R /XYZ 286.6291 599.3285 null]
+>> endobj
+3738 0 obj <<
+/D [3731 0 R /XYZ 71.731 586.8905 null]
+>> endobj
+3739 0 obj <<
+/D [3731 0 R /XYZ 71.731 492.9226 null]
+>> endobj
+1644 0 obj <<
+/D [3731 0 R /XYZ 71.731 464.0957 null]
+>> endobj
+610 0 obj <<
+/D [3731 0 R /XYZ 166.8108 418.9409 null]
+>> endobj
+3740 0 obj <<
+/D [3731 0 R /XYZ 71.731 406.5029 null]
+>> endobj
+3741 0 obj <<
+/D [3731 0 R /XYZ 71.731 351.3893 null]
+>> endobj
+3742 0 obj <<
+/D [3731 0 R /XYZ 71.731 338.4379 null]
+>> endobj
+3743 0 obj <<
+/D [3731 0 R /XYZ 71.731 333.4566 null]
+>> endobj
+3744 0 obj <<
+/D [3731 0 R /XYZ 89.6638 312.6993 null]
+>> endobj
+3745 0 obj <<
+/D [3731 0 R /XYZ 71.731 310.5425 null]
+>> endobj
+3746 0 obj <<
+/D [3731 0 R /XYZ 89.6638 294.7666 null]
+>> endobj
+3747 0 obj <<
+/D [3731 0 R /XYZ 89.6638 294.7666 null]
+>> endobj
+3748 0 obj <<
+/D [3731 0 R /XYZ 71.731 292.6098 null]
+>> endobj
+3749 0 obj <<
+/D [3731 0 R /XYZ 89.6638 276.8338 null]
+>> endobj
+3750 0 obj <<
+/D [3731 0 R /XYZ 89.6638 276.8338 null]
+>> endobj
+3751 0 obj <<
+/D [3731 0 R /XYZ 71.731 250.8314 null]
+>> endobj
+3752 0 obj <<
+/D [3731 0 R /XYZ 89.6638 232.9982 null]
+>> endobj
+3753 0 obj <<
+/D [3731 0 R /XYZ 89.6638 232.9982 null]
+>> endobj
+3754 0 obj <<
+/D [3731 0 R /XYZ 71.731 217.89 null]
+>> endobj
+3755 0 obj <<
+/D [3731 0 R /XYZ 89.6638 202.114 null]
+>> endobj
+1645 0 obj <<
+/D [3731 0 R /XYZ 71.731 194.9759 null]
+>> endobj
+614 0 obj <<
+/D [3731 0 R /XYZ 163.5913 151.8784 null]
+>> endobj
+3756 0 obj <<
+/D [3731 0 R /XYZ 71.731 139.7072 null]
+>> endobj
+3730 0 obj <<
+/Font << /F33 1322 0 R /F27 1222 0 R /F23 1214 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+3759 0 obj <<
+/Length 3290
+/Filter /FlateDecode
+>>
+stream
+xڍ˒_5UBoƮx)N\(
+PK2I/%ƥFh_%|ttUq୎0/([َ{`,
+=O%^@4O3VG:P}ڟme[Ƞ?mY{LJ?GAVauˣNF<z
+ !}ulx뢮.K+Ag;=hg@#wSa{ɛlP@QVȏ3qʅ<hя֦K^궵 F^^M-Orij>4vȜR986=Ym#hiE~H{j@LB7"T
+@ lݘZv/ q=x) i@I4 ⅤH
+;S[E f-]۞[y/c03%=mr4z!
+ca=%hMj?
+1-C:(-ʏ({Jk}d9
+&z`( t*-(Ago);YW6,&( CKBֶ53Ӧka=R&U,(c vr` 3$a0aHXE}vU.4x<Lf-g•`:?lArq!۵BL)37K{*AE> lƤcⱂ%c
+ |EaږG~Uc}J8PKУ"1!;;[N+%`L"&קBsm
+:I?]aIيb'p'xРgjt{ |ޱmKf߼m²NPmעÂ$ D.]N9QCDD3XD–↼VY4ἵb*?V喞3)vߢ`nY6W9
+]{ )2$S N mX !sZA_͗L3A۳& mdHr$xm
+EtϏG.
+@7
+ƞP4Xxb^0/ᨺ&Z!Q8-wY\HTlOw{uGrV%be69ؒB'p!H4N0YL9y-aL0#%O.!)g,g*abR5VRɺ[
+ F(m :7i6A끶8 $g*/oͭ*MhPeIa)-Yʜ؊+T Q=f[j(/|P;v*H\ޮs}7\ZIt
+^tzZP?R U};<P8 cd)lh2% \ɣ`)^jkTc6 J8vA⏔1e]
+%":7&t%_T6~àly&-mF?$3GL1=:K7avak 
+Aۿ:#yAoH`V+yETU4Vd33{w߯(ޯ*Prp{W8 Ԑ.ik;F5[j-@Ї%a >q
+1o0tn)V11LTw[F:@iƢ)UW c q"YĞlWsAm =3A9֐E ]+Egk<B1ǼٗcN[(Su'y=Ɖ]'nC~#0݊xs_-|yVmrSg^80%q
+_cOfr0v k!4n
+'8"PfQ?? endstream
+endobj
+3758 0 obj <<
+/Type /Page
+/Contents 3759 0 R
+/Resources 3757 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 3703 0 R
+/Annots [ 3773 0 R 3774 0 R 3777 0 R 3780 0 R 3783 0 R ]
+>> endobj
+3773 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [507.0988 308.6138 538.9788 319.5177]
+/Subtype /Link
+/A << /S /GoTo /D (param-group-security) >>
+>> endobj
+3774 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [70.7348 297.6 110.5853 306.5663]
+/Subtype /Link
+/A << /S /GoTo /D (param-group-security) >>
+>> endobj
+3777 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [103.8802 253.884 158.1762 262.7306]
+/Subtype /Link
+/A << /S /GoTo /D (product-group-controls) >>
+>> endobj
+3780 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [200.9852 233.894 260.2625 244.7979]
+/Subtype /Link
+/A << /S /GoTo /D (users-and-groups) >>
+>> endobj
+3783 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [341.5858 190.0583 395.8818 200.9623]
+/Subtype /Link
+/A << /S /GoTo /D (product-group-controls) >>
+>> endobj
+3760 0 obj <<
+/D [3758 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+3761 0 obj <<
+/D [3758 0 R /XYZ 71.731 718.3063 null]
+>> endobj
+3762 0 obj <<
+/D [3758 0 R /XYZ 181.7247 708.3437 null]
+>> endobj
+3763 0 obj <<
+/D [3758 0 R /XYZ 71.731 675.3027 null]
+>> endobj
+3764 0 obj <<
+/D [3758 0 R /XYZ 71.731 605.5642 null]
+>> endobj
+3765 0 obj <<
+/D [3758 0 R /XYZ 71.731 561.7286 null]
+>> endobj
+1646 0 obj <<
+/D [3758 0 R /XYZ 71.731 543.7958 null]
+>> endobj
+618 0 obj <<
+/D [3758 0 R /XYZ 339.8756 500.6984 null]
+>> endobj
+3766 0 obj <<
+/D [3758 0 R /XYZ 71.731 488.5271 null]
+>> endobj
+3767 0 obj <<
+/D [3758 0 R /XYZ 71.731 409.3012 null]
+>> endobj
+3768 0 obj <<
+/D [3758 0 R /XYZ 71.731 394.2925 null]
+>> endobj
+3769 0 obj <<
+/D [3758 0 R /XYZ 71.731 389.3111 null]
+>> endobj
+3770 0 obj <<
+/D [3758 0 R /XYZ 89.6638 368.5539 null]
+>> endobj
+3771 0 obj <<
+/D [3758 0 R /XYZ 71.731 340.4942 null]
+>> endobj
+3772 0 obj <<
+/D [3758 0 R /XYZ 89.6638 324.7183 null]
+>> endobj
+3775 0 obj <<
+/D [3758 0 R /XYZ 71.731 298.5963 null]
+>> endobj
+3776 0 obj <<
+/D [3758 0 R /XYZ 89.6638 280.8827 null]
+>> endobj
+3778 0 obj <<
+/D [3758 0 R /XYZ 71.731 254.8803 null]
+>> endobj
+3779 0 obj <<
+/D [3758 0 R /XYZ 89.6638 237.047 null]
+>> endobj
+3781 0 obj <<
+/D [3758 0 R /XYZ 71.731 229.9089 null]
+>> endobj
+3782 0 obj <<
+/D [3758 0 R /XYZ 231.1139 206.1629 null]
+>> endobj
+3784 0 obj <<
+/D [3758 0 R /XYZ 71.731 191.0546 null]
+>> endobj
+3785 0 obj <<
+/D [3758 0 R /XYZ 71.731 176.1106 null]
+>> endobj
+3786 0 obj <<
+/D [3758 0 R /XYZ 462.4737 143.2986 null]
+>> endobj
+1647 0 obj <<
+/D [3758 0 R /XYZ 76.7123 113.7096 null]
+>> endobj
+3757 0 obj <<
+/Font << /F33 1322 0 R /F27 1222 0 R /F23 1214 0 R /F44 2021 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+3789 0 obj <<
+/Length 2876
+/Filter /FlateDecode
+>>
+stream
+xڍ]}E08``ʒ{vo=\ڇn<ةח)qkHJ◘VE4
+S2r==L}~1Ɇi6SR<wX0YJ 3eCqLH-}y| _U??ky^S](*C'tiłTTaUKcgn-EОO=Ɇj#EX[6 Hi7[b.Ә3 | xu;Z0ɏIs8(Z%:3) d͔Ng+^-8,De"_[
+WQlFE)_TΎ:LLsuNP̖28 3MMyYKg~IkjqD2uVͫY$}j'T\B.}U+ި{lx<hw*<c֑9{F=K
+
+>: "{U twhxFA 9{:=k
+8dhOVO`*/A
+4V"
+΢<Cp\w6QYΪ(B^lLO wk o82SDaJ?WBw!A_zz>(u+ Z4 ,pM95Rq㻇JH^XE;NovB>ZSQZ T$-/Xؘ+G
+j
+ƫi*C-QD=ε А
+4T!O'o4b<5N2w*\\v@`k q%r쁱jA+ee a(UTӸ}jѩpt|`juIY4[zdiH|] X*Af[Kت!,d(NJbRȞ pjB-Ce9Yh`(m-=^AFO-mM z߬0s˔1d%Ѣ38͔xQKpV#W:zK+nXOF4
+ZcYr-vlt
+Hv$QIQuX|X(@?X!gXin
+v삕XtaP欝v,M:hw"~`NYl߲O5[A ue
+ ]
+ȗL
+:;yoM
+Gs){B`V7 K2QAI
+Ir jHI]}9jtV#DS!=o箃eC+t?2$=dMN-F #¾翳_&YEIaLh CC3sͿXęendstream
+endobj
+3788 0 obj <<
+/Type /Page
+/Contents 3789 0 R
+/Resources 3787 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 3703 0 R
+/Annots [ 3809 0 R 3815 0 R ]
+>> endobj
+3809 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [240.9848 286.551 300.2621 297.4549]
+/Subtype /Link
+/A << /S /GoTo /D (edit-groups) >>
+>> endobj
+3815 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [429.0596 121.4501 475.9177 132.354]
+/Subtype /Link
+/A << /S /GoTo /D (parameters) >>
+>> endobj
+3790 0 obj <<
+/D [3788 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+622 0 obj <<
+/D [3788 0 R /XYZ 232.4924 707.8408 null]
+>> endobj
+3791 0 obj <<
+/D [3788 0 R /XYZ 71.731 697.4758 null]
+>> endobj
+3792 0 obj <<
+/D [3788 0 R /XYZ 71.731 685.5594 null]
+>> endobj
+3793 0 obj <<
+/D [3788 0 R /XYZ 71.731 680.5781 null]
+>> endobj
+3794 0 obj <<
+/D [3788 0 R /XYZ 89.6638 659.8209 null]
+>> endobj
+3795 0 obj <<
+/D [3788 0 R /XYZ 132.5044 659.8209 null]
+>> endobj
+3796 0 obj <<
+/D [3788 0 R /XYZ 379.7938 659.8209 null]
+>> endobj
+3797 0 obj <<
+/D [3788 0 R /XYZ 71.731 644.7126 null]
+>> endobj
+3798 0 obj <<
+/D [3788 0 R /XYZ 89.6638 628.9367 null]
+>> endobj
+3799 0 obj <<
+/D [3788 0 R /XYZ 157.7278 615.9853 null]
+>> endobj
+3800 0 obj <<
+/D [3788 0 R /XYZ 71.731 613.8284 null]
+>> endobj
+3801 0 obj <<
+/D [3788 0 R /XYZ 89.6638 598.0525 null]
+>> endobj
+3802 0 obj <<
+/D [3788 0 R /XYZ 71.731 531.1385 null]
+>> endobj
+3803 0 obj <<
+/D [3788 0 R /XYZ 71.731 516.1946 null]
+>> endobj
+3804 0 obj <<
+/D [3788 0 R /XYZ 142.175 506.6951 null]
+>> endobj
+3805 0 obj <<
+/D [3788 0 R /XYZ 76.7123 443.4324 null]
+>> endobj
+3806 0 obj <<
+/D [3788 0 R /XYZ 136.4882 399.887 null]
+>> endobj
+3807 0 obj <<
+/D [3788 0 R /XYZ 76.7123 333.5397 null]
+>> endobj
+3808 0 obj <<
+/D [3788 0 R /XYZ 89.6638 315.6069 null]
+>> endobj
+1648 0 obj <<
+/D [3788 0 R /XYZ 71.731 282.5659 null]
+>> endobj
+626 0 obj <<
+/D [3788 0 R /XYZ 461.369 245.3504 null]
+>> endobj
+3810 0 obj <<
+/D [3788 0 R /XYZ 71.731 234.9854 null]
+>> endobj
+3811 0 obj <<
+/D [3788 0 R /XYZ 253.9624 225.2259 null]
+>> endobj
+3812 0 obj <<
+/D [3788 0 R /XYZ 499.806 225.2259 null]
+>> endobj
+3813 0 obj <<
+/D [3788 0 R /XYZ 71.731 192.1849 null]
+>> endobj
+3814 0 obj <<
+/D [3788 0 R /XYZ 71.731 148.3492 null]
+>> endobj
+3787 0 obj <<
+/Font << /F33 1322 0 R /F23 1214 0 R /F27 1222 0 R /F44 2021 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+3818 0 obj <<
+/Length 2568
+/Filter /FlateDecode
+>>
+stream
+xڽZ_6Of϶]=:}ރh&F;g;}DJc'Ӣb"QEI"=l[,pLUuaF,L>~Bt3V2M<-*<)+V?` WZ$iwz<#á?>|W"Ot+0puymZ$Bi5x^m3_?s;]{>?%Hxg|E7=fCphќ;s(ulZM4fgO:D n`j=Meyr.R%"לxi{ߕ՞TxsV)$XmObұ7lLwn 5L_uvݭ')XutyRh+}Zӱ}9Eu"_%`[oyU .-l$:QdKp&87M͒Leb\n`5N!`L_zs0F _
+ là͕
+ $"J1
+W9܁61BR)K.,teX\Pa&;d(
+z3\ -3-5t޵5gZG]zL7q‡U,.,;xSS*!~SZ*SJ,s|>I2 .g{%miԟLU[CvCŇP;ܹG/9rMɄeD= g
+endobj
+3817 0 obj <<
+/Type /Page
+/Contents 3818 0 R
+/Resources 3816 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 3703 0 R
+/Annots [ 3842 0 R 3847 0 R 3852 0 R 3855 0 R 3858 0 R ]
+>> endobj
+3842 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [368.5546 392.3637 415.1942 403.2676]
+/Subtype /Link
+/A << /S /GoTo /D (parameters) >>
+>> endobj
+3847 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [414.1442 325.614 461.4474 336.5179]
+/Subtype /Link
+/A << /S /GoTo /D (parameters) >>
+>> endobj
+3852 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [415.7972 168.4833 462.6212 179.3872]
+/Subtype /Link
+/A << /S /GoTo /D (useradmin) >>
+>> endobj
+3855 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [336.7154 137.5991 395.9927 148.503]
+/Subtype /Link
+/A << /S /GoTo /D (edit-groups) >>
+>> endobj
+3858 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [88.6675 93.7635 147.9448 104.6674]
+/Subtype /Link
+/A << /S /GoTo /D (create-groups) >>
+>> endobj
+3819 0 obj <<
+/D [3817 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+3820 0 obj <<
+/D [3817 0 R /XYZ 456.9916 708.3437 null]
+>> endobj
+3821 0 obj <<
+/D [3817 0 R /XYZ 71.731 693.2354 null]
+>> endobj
+3822 0 obj <<
+/D [3817 0 R /XYZ 71.731 678.2915 null]
+>> endobj
+3823 0 obj <<
+/D [3817 0 R /XYZ 71.731 678.2915 null]
+>> endobj
+3824 0 obj <<
+/D [3817 0 R /XYZ 71.731 665.4595 null]
+>> endobj
+3825 0 obj <<
+/D [3817 0 R /XYZ 91.6563 649.5641 null]
+>> endobj
+3826 0 obj <<
+/D [3817 0 R /XYZ 71.731 624.4932 null]
+>> endobj
+3827 0 obj <<
+/D [3817 0 R /XYZ 71.731 624.4932 null]
+>> endobj
+3828 0 obj <<
+/D [3817 0 R /XYZ 71.731 611.6613 null]
+>> endobj
+3829 0 obj <<
+/D [3817 0 R /XYZ 91.6563 595.7659 null]
+>> endobj
+3830 0 obj <<
+/D [3817 0 R /XYZ 71.731 544.7921 null]
+>> endobj
+3831 0 obj <<
+/D [3817 0 R /XYZ 71.731 544.7921 null]
+>> endobj
+3832 0 obj <<
+/D [3817 0 R /XYZ 71.731 531.9602 null]
+>> endobj
+3833 0 obj <<
+/D [3817 0 R /XYZ 91.6563 516.0647 null]
+>> endobj
+3834 0 obj <<
+/D [3817 0 R /XYZ 71.731 490.9938 null]
+>> endobj
+3835 0 obj <<
+/D [3817 0 R /XYZ 71.731 490.9938 null]
+>> endobj
+3836 0 obj <<
+/D [3817 0 R /XYZ 71.731 478.1619 null]
+>> endobj
+3837 0 obj <<
+/D [3817 0 R /XYZ 91.6563 462.2665 null]
+>> endobj
+3838 0 obj <<
+/D [3817 0 R /XYZ 71.731 437.1956 null]
+>> endobj
+3839 0 obj <<
+/D [3817 0 R /XYZ 71.731 437.1956 null]
+>> endobj
+3840 0 obj <<
+/D [3817 0 R /XYZ 71.731 424.3636 null]
+>> endobj
+3841 0 obj <<
+/D [3817 0 R /XYZ 91.6563 408.4682 null]
+>> endobj
+3843 0 obj <<
+/D [3817 0 R /XYZ 71.731 370.4459 null]
+>> endobj
+3844 0 obj <<
+/D [3817 0 R /XYZ 71.731 370.4459 null]
+>> endobj
+3845 0 obj <<
+/D [3817 0 R /XYZ 71.731 357.6139 null]
+>> endobj
+3846 0 obj <<
+/D [3817 0 R /XYZ 91.6563 341.7185 null]
+>> endobj
+1649 0 obj <<
+/D [3817 0 R /XYZ 71.731 308.6775 null]
+>> endobj
+630 0 obj <<
+/D [3817 0 R /XYZ 304.8252 271.462 null]
+>> endobj
+3848 0 obj <<
+/D [3817 0 R /XYZ 71.731 261.097 null]
+>> endobj
+3849 0 obj <<
+/D [3817 0 R /XYZ 71.731 249.1806 null]
+>> endobj
+3850 0 obj <<
+/D [3817 0 R /XYZ 71.731 244.1993 null]
+>> endobj
+3851 0 obj <<
+/D [3817 0 R /XYZ 89.6638 223.4421 null]
+>> endobj
+3853 0 obj <<
+/D [3817 0 R /XYZ 71.731 169.4795 null]
+>> endobj
+3854 0 obj <<
+/D [3817 0 R /XYZ 89.6638 153.7036 null]
+>> endobj
+3856 0 obj <<
+/D [3817 0 R /XYZ 71.731 138.5953 null]
+>> endobj
+3857 0 obj <<
+/D [3817 0 R /XYZ 89.6638 122.8194 null]
+>> endobj
+3816 0 obj <<
+/Font << /F33 1322 0 R /F27 1222 0 R /F23 1214 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+3861 0 obj <<
+/Length 1640
+/Filter /FlateDecode
+>>
+stream
+xڍXK6r5Kz Rh+6YRE)w3^gpy*SQDT"ɳ$w2:;"[ٮ{uT*OhwR)E!**t"Ln{dmX ޟ]<]{$֛ksEy QE 6&:Rie*D-T&R4(޻cKfIdM= vHg S=zhH4 *w'jXlGajuiH݁<Mş7*&Cw&j$E</  AҺkkۏvk*Uhq&A7EȺMt 4Ǎ'4YpadZ0}<hft v4n^j@K5羱<ɱ]c׻k2\R,Z B@ CX9Z?
+\
+];
+ kfڇ 3_}4m8@(~zEdjD@Yv|Q&DD{fl`{[Yi Azm ]bM @^g0DЙq 8
+Dݸӷm\˲nkk&قxb
+['Pf R}
+vVx4 Qʀ1j;_Ǝ쐴0p 1ZA h&Ԧ7Tg@2d02]E~Ϻk#zK Uċ-sOL܁/֓@S
+U `Kz]V
+MRendstream
+endobj
+3860 0 obj <<
+/Type /Page
+/Contents 3861 0 R
+/Resources 3859 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 3873 0 R
+/Annots [ 3864 0 R ]
+>> endobj
+3864 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [70.7348 660.7176 125.0308 669.5643]
+/Subtype /Link
+/A << /S /GoTo /D (product-group-controls) >>
+>> endobj
+3862 0 obj <<
+/D [3860 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+1650 0 obj <<
+/D [3860 0 R /XYZ 71.731 718.3063 null]
+>> endobj
+634 0 obj <<
+/D [3860 0 R /XYZ 381.7631 707.8408 null]
+>> endobj
+3863 0 obj <<
+/D [3860 0 R /XYZ 71.731 697.4758 null]
+>> endobj
+1651 0 obj <<
+/D [3860 0 R /XYZ 71.731 646.7699 null]
+>> endobj
+638 0 obj <<
+/D [3860 0 R /XYZ 481.7981 601.6151 null]
+>> endobj
+3865 0 obj <<
+/D [3860 0 R /XYZ 71.731 589.1772 null]
+>> endobj
+3866 0 obj <<
+/D [3860 0 R /XYZ 71.731 534.0635 null]
+>> endobj
+3867 0 obj <<
+/D [3860 0 R /XYZ 71.731 490.2279 null]
+>> endobj
+3868 0 obj <<
+/D [3860 0 R /XYZ 416.5658 479.4333 null]
+>> endobj
+3869 0 obj <<
+/D [3860 0 R /XYZ 151.9594 466.4819 null]
+>> endobj
+3870 0 obj <<
+/D [3860 0 R /XYZ 71.731 459.3437 null]
+>> endobj
+3871 0 obj <<
+/D [3860 0 R /XYZ 71.731 446.3923 null]
+>> endobj
+3872 0 obj <<
+/D [3860 0 R /XYZ 118.5554 407.8283 null]
+>> endobj
+3859 0 obj <<
+/Font << /F33 1322 0 R /F23 1214 0 R /F27 1222 0 R /F35 1604 0 R /F32 1230 0 R /F44 2021 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+3876 0 obj <<
+/Length 2492
+/Filter /FlateDecode
+>>
+stream
+xڍM6_ˢ6HHI{kviEك,ѶYtE)/_3,
+x3|P/^EXd BH:V@U$BXfUI&\vWJFB\UQ"dW`Vf:xoӶ%ޛj1[p0V[1K~?6l"^;{hmjsrkk*gQ$ܗ=M[siϦ/;]\B2r4a{>j<m?-PXBũ#Yf(_cӣ2#=t${"x3#tٶ >r`*R;DQC)|$fW t{;$peOM皍TOXͭx4
+~/~l>/?폿e]p-؞T| :rktL7433g7vAqf'_R]֧k܀Q#S b5d}]3ؠda*A3GC4Oؒr} DiG p[C ԄfZ?)ߓd琢Rmgp7fz3,T (fZ'Dimc(eD*~
+tK3ngP> T@9y~:{Tps#*Ow,b[CC=GS41\}m(q섌`r*#-reOV\`CN#
+9[]k]pJW
+hJ"nwvGc␊$Ւ9$H2^yGw4Ru}kF")N(24 `ݲ SQ$;y=w)"bbR*i:IH
+n n
+nQ=O:$7|j{}C!;?x3|Qjq} L,_<Ir_lp1L
+M3ML R&Degl}A @sq@("ϥ/ĐIB8wf^h%;ȘH*!4QH@3}ES!"B8=\|,*o.DK6{~f{Iٱ [;Xy?)FƎlD4t{-0*t3Ӛ;>}6/pMoC`##z,[g`'h.]S׎V>$dpMH8j(E8 [F;`Kٞ ~^@\%b}֓pu
+WR4c pEOLG3k
+.2:A, M([4Ƞ{'DjBRxX+%T!c k;o/l
+endobj
+3875 0 obj <<
+/Type /Page
+/Contents 3876 0 R
+/Resources 3874 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 3873 0 R
+/Annots [ 3883 0 R 3888 0 R ]
+>> endobj
+3883 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [96.5016 366.6214 133.9111 377.5253]
+/Subtype /Link
+/A << /S /GoTo /D (gloss-daemon) >>
+>> endobj
+3888 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [257.7381 353.6699 291.8196 364.5739]
+/Subtype /Link
+/A << /S /GoTo /D (gloss-service) >>
+>> endobj
+3877 0 obj <<
+/D [3875 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+1652 0 obj <<
+/D [3875 0 R /XYZ 71.731 718.3063 null]
+>> endobj
+642 0 obj <<
+/D [3875 0 R /XYZ 344.9571 703.236 null]
+>> endobj
+3878 0 obj <<
+/D [3875 0 R /XYZ 71.731 681.8546 null]
+>> endobj
+3879 0 obj <<
+/D [3875 0 R /XYZ 522.288 634.6452 null]
+>> endobj
+3880 0 obj <<
+/D [3875 0 R /XYZ 71.731 616.593 null]
+>> endobj
+1653 0 obj <<
+/D [3875 0 R /XYZ 71.731 575.7013 null]
+>> endobj
+646 0 obj <<
+/D [3875 0 R /XYZ 252.5595 532.6038 null]
+>> endobj
+1654 0 obj <<
+/D [3875 0 R /XYZ 71.731 528.7736 null]
+>> endobj
+650 0 obj <<
+/D [3875 0 R /XYZ 198.2194 493.2315 null]
+>> endobj
+3881 0 obj <<
+/D [3875 0 R /XYZ 71.731 485.8792 null]
+>> endobj
+1655 0 obj <<
+/D [3875 0 R /XYZ 71.731 427.1145 null]
+>> endobj
+654 0 obj <<
+/D [3875 0 R /XYZ 267.8696 389.899 null]
+>> endobj
+3882 0 obj <<
+/D [3875 0 R /XYZ 71.731 379.7563 null]
+>> endobj
+3884 0 obj <<
+/D [3875 0 R /XYZ 209.7301 369.7744 null]
+>> endobj
+3885 0 obj <<
+/D [3875 0 R /XYZ 291.3343 369.7744 null]
+>> endobj
+3886 0 obj <<
+/D [3875 0 R /XYZ 381.0612 369.7744 null]
+>> endobj
+3887 0 obj <<
+/D [3875 0 R /XYZ 419.6033 369.7744 null]
+>> endobj
+3889 0 obj <<
+/D [3875 0 R /XYZ 322.3875 356.823 null]
+>> endobj
+3890 0 obj <<
+/D [3875 0 R /XYZ 449.9815 356.823 null]
+>> endobj
+3891 0 obj <<
+/D [3875 0 R /XYZ 489.8335 356.823 null]
+>> endobj
+3892 0 obj <<
+/D [3875 0 R /XYZ 436.781 343.8716 null]
+>> endobj
+3893 0 obj <<
+/D [3875 0 R /XYZ 258.7334 330.9202 null]
+>> endobj
+3894 0 obj <<
+/D [3875 0 R /XYZ 171.6422 317.9687 null]
+>> endobj
+3895 0 obj <<
+/D [3875 0 R /XYZ 71.731 304.9177 null]
+>> endobj
+3896 0 obj <<
+/D [3875 0 R /XYZ 71.731 289.9738 null]
+>> endobj
+3897 0 obj <<
+/D [3875 0 R /XYZ 209.8716 278.417 null]
+>> endobj
+3898 0 obj <<
+/D [3875 0 R /XYZ 316.0526 278.417 null]
+>> endobj
+3899 0 obj <<
+/D [3875 0 R /XYZ 129.3774 266.7608 null]
+>> endobj
+1656 0 obj <<
+/D [3875 0 R /XYZ 71.731 238.8654 null]
+>> endobj
+658 0 obj <<
+/D [3875 0 R /XYZ 215.5068 199.493 null]
+>> endobj
+3900 0 obj <<
+/D [3875 0 R /XYZ 71.731 192.0618 null]
+>> endobj
+3901 0 obj <<
+/D [3875 0 R /XYZ 401.9114 179.3685 null]
+>> endobj
+1657 0 obj <<
+/D [3875 0 R /XYZ 71.731 136.3648 null]
+>> endobj
+3874 0 obj <<
+/Font << /F23 1214 0 R /F27 1222 0 R /F33 1322 0 R /F35 1604 0 R /F44 2021 0 R /F60 2540 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+3906 0 obj <<
+/Length 2100
+/Filter /FlateDecode
+>>
+stream
+xڵZMϯWc>u'oRMdd1n
+#-^/ &@J}%Ş͎> );!o&o"/_tٹj3]~HӬitr}]}IWYcˁ |BPDOY q/
+7R/)iGKA󲃙F(ymU]$nSu-Loee*C|_c]G_|p8
+!<=c'ԏѓɵ^uO^qJ7r74Xge[P#To"oڡW5j,uΓ0X؛lW{G $p
+VtZ/E&mvP!E㚧t3`Tg}-D'Bp
+$%C+mִ/C~!9x6]ֆR(XS:|7DA-Kb^x:*o(DkH^g g8lJT@WĆ̪m>T=*0A/4d`k6mxy{ҕZPHy U`2<4J& ڈDJL=jgTH9q!uj3"055> 北=- Ny)b"8ꃧiMChz:pHC_%FK!mΤgNfdK .MLTh3\ AWaSN{Á
+҇}2gJ@![!`-?;aHfD"r[ €X}RJTgؽu&-=
+e!rJI^vER8ox4ECD]@;u AY{Ԝ #:κID̀cQ>8/2_vkq+!I68l6 L*"FOzԺT,F .0'">H1;EA h^ࢿ"hA*;?e@^ǿ~[{2kKH@hYHh^HhUHw!$ĝB
+rOdrMN !dաV0z$I<GK:DaCuw|]^ s!Ȥs;,os.^q1V
+&L8 endstream
+endobj
+3905 0 obj <<
+/Type /Page
+/Contents 3906 0 R
+/Resources 3904 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 3873 0 R
+/Annots [ 3912 0 R 3913 0 R ]
+>> endobj
+3912 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [271.1346 578.1752 316.7037 588.7575]
+/Subtype /Link
+/A << /S /GoTo /D (gloss-htaccess) >>
+>> endobj
+3913 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [261.1174 566.6266 322.9236 577.1012]
+/Subtype /Link
+/A << /S /GoTo /D (http-apache) >>
+>> endobj
+3907 0 obj <<
+/D [3905 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+662 0 obj <<
+/D [3905 0 R /XYZ 197.6084 706.1179 null]
+>> endobj
+1658 0 obj <<
+/D [3905 0 R /XYZ 71.731 705.9028 null]
+>> endobj
+666 0 obj <<
+/D [3905 0 R /XYZ 498.0946 666.7455 null]
+>> endobj
+3908 0 obj <<
+/D [3905 0 R /XYZ 71.731 656.3805 null]
+>> endobj
+3909 0 obj <<
+/D [3905 0 R /XYZ 213.9976 620.7181 null]
+>> endobj
+3910 0 obj <<
+/D [3905 0 R /XYZ 71.731 605.6099 null]
+>> endobj
+3911 0 obj <<
+/D [3905 0 R /XYZ 71.731 590.6659 null]
+>> endobj
+3914 0 obj <<
+/D [3905 0 R /XYZ 76.7123 551.5774 null]
+>> endobj
+3915 0 obj <<
+/D [3905 0 R /XYZ 71.731 541.6148 null]
+>> endobj
+3916 0 obj <<
+/D [3905 0 R /XYZ 81.6937 508.7381 null]
+>> endobj
+3917 0 obj <<
+/D [3905 0 R /XYZ 71.731 506.5812 null]
+>> endobj
+3918 0 obj <<
+/D [3905 0 R /XYZ 71.731 506.5812 null]
+>> endobj
+3919 0 obj <<
+/D [3905 0 R /XYZ 91.6563 495.7866 null]
+>> endobj
+3920 0 obj <<
+/D [3905 0 R /XYZ 120.717 495.7866 null]
+>> endobj
+3921 0 obj <<
+/D [3905 0 R /XYZ 120.717 495.7866 null]
+>> endobj
+3922 0 obj <<
+/D [3905 0 R /XYZ 147.2176 495.7866 null]
+>> endobj
+3923 0 obj <<
+/D [3905 0 R /XYZ 147.2176 495.7866 null]
+>> endobj
+3924 0 obj <<
+/D [3905 0 R /XYZ 76.7123 477.8539 null]
+>> endobj
+3925 0 obj <<
+/D [3905 0 R /XYZ 81.6937 464.9025 null]
+>> endobj
+3926 0 obj <<
+/D [3905 0 R /XYZ 92.4832 464.9025 null]
+>> endobj
+3927 0 obj <<
+/D [3905 0 R /XYZ 71.731 464.7635 null]
+>> endobj
+3928 0 obj <<
+/D [3905 0 R /XYZ 71.731 464.7635 null]
+>> endobj
+3929 0 obj <<
+/D [3905 0 R /XYZ 91.6563 451.951 null]
+>> endobj
+3930 0 obj <<
+/D [3905 0 R /XYZ 76.7123 434.0183 null]
+>> endobj
+3931 0 obj <<
+/D [3905 0 R /XYZ 81.6937 421.0668 null]
+>> endobj
+3932 0 obj <<
+/D [3905 0 R /XYZ 92.4832 421.0668 null]
+>> endobj
+3933 0 obj <<
+/D [3905 0 R /XYZ 71.731 420.3586 null]
+>> endobj
+3934 0 obj <<
+/D [3905 0 R /XYZ 71.731 420.3586 null]
+>> endobj
+3935 0 obj <<
+/D [3905 0 R /XYZ 91.6563 408.1154 null]
+>> endobj
+3936 0 obj <<
+/D [3905 0 R /XYZ 71.731 405.9586 null]
+>> endobj
+3937 0 obj <<
+/D [3905 0 R /XYZ 71.731 405.9586 null]
+>> endobj
+3938 0 obj <<
+/D [3905 0 R /XYZ 101.6189 395.164 null]
+>> endobj
+3939 0 obj <<
+/D [3905 0 R /XYZ 71.731 393.0071 null]
+>> endobj
+3940 0 obj <<
+/D [3905 0 R /XYZ 101.6189 382.2125 null]
+>> endobj
+3941 0 obj <<
+/D [3905 0 R /XYZ 142.8837 382.2125 null]
+>> endobj
+3942 0 obj <<
+/D [3905 0 R /XYZ 142.8837 382.2125 null]
+>> endobj
+3943 0 obj <<
+/D [3905 0 R /XYZ 76.7123 364.2798 null]
+>> endobj
+3944 0 obj <<
+/D [3905 0 R /XYZ 91.6563 351.3284 null]
+>> endobj
+3945 0 obj <<
+/D [3905 0 R /XYZ 71.731 349.1715 null]
+>> endobj
+3946 0 obj <<
+/D [3905 0 R /XYZ 71.731 349.1715 null]
+>> endobj
+3947 0 obj <<
+/D [3905 0 R /XYZ 101.6189 338.3769 null]
+>> endobj
+3948 0 obj <<
+/D [3905 0 R /XYZ 71.731 336.2201 null]
+>> endobj
+3949 0 obj <<
+/D [3905 0 R /XYZ 101.6189 325.4255 null]
+>> endobj
+3950 0 obj <<
+/D [3905 0 R /XYZ 145.6532 325.4255 null]
+>> endobj
+3951 0 obj <<
+/D [3905 0 R /XYZ 145.6532 325.4255 null]
+>> endobj
+3952 0 obj <<
+/D [3905 0 R /XYZ 177.5336 325.4255 null]
+>> endobj
+3953 0 obj <<
+/D [3905 0 R /XYZ 177.5336 325.4255 null]
+>> endobj
+3954 0 obj <<
+/D [3905 0 R /XYZ 209.4141 325.4255 null]
+>> endobj
+3955 0 obj <<
+/D [3905 0 R /XYZ 209.4141 325.4255 null]
+>> endobj
+3956 0 obj <<
+/D [3905 0 R /XYZ 241.2945 325.4255 null]
+>> endobj
+3957 0 obj <<
+/D [3905 0 R /XYZ 241.2945 325.4255 null]
+>> endobj
+3958 0 obj <<
+/D [3905 0 R /XYZ 76.7123 307.4927 null]
+>> endobj
+3959 0 obj <<
+/D [3905 0 R /XYZ 91.6563 294.5413 null]
+>> endobj
+3960 0 obj <<
+/D [3905 0 R /XYZ 71.731 292.3845 null]
+>> endobj
+3961 0 obj <<
+/D [3905 0 R /XYZ 71.731 292.3845 null]
+>> endobj
+3962 0 obj <<
+/D [3905 0 R /XYZ 101.6189 281.5899 null]
+>> endobj
+3963 0 obj <<
+/D [3905 0 R /XYZ 76.7123 245.7244 null]
+>> endobj
+3964 0 obj <<
+/D [3905 0 R /XYZ 81.6937 232.7729 null]
+>> endobj
+3965 0 obj <<
+/D [3905 0 R /XYZ 92.4832 232.7729 null]
+>> endobj
+3966 0 obj <<
+/D [3905 0 R /XYZ 71.731 231.3922 null]
+>> endobj
+3967 0 obj <<
+/D [3905 0 R /XYZ 71.731 231.3922 null]
+>> endobj
+3968 0 obj <<
+/D [3905 0 R /XYZ 91.6563 219.8215 null]
+>> endobj
+3969 0 obj <<
+/D [3905 0 R /XYZ 76.7123 201.8888 null]
+>> endobj
+3970 0 obj <<
+/D [3905 0 R /XYZ 81.6937 188.9373 null]
+>> endobj
+3971 0 obj <<
+/D [3905 0 R /XYZ 92.4832 188.9373 null]
+>> endobj
+3972 0 obj <<
+/D [3905 0 R /XYZ 71.731 187.5566 null]
+>> endobj
+3973 0 obj <<
+/D [3905 0 R /XYZ 71.731 187.5566 null]
+>> endobj
+3974 0 obj <<
+/D [3905 0 R /XYZ 91.6563 175.9859 null]
+>> endobj
+3975 0 obj <<
+/D [3905 0 R /XYZ 71.731 153.0718 null]
+>> endobj
+3904 0 obj <<
+/Font << /F33 1322 0 R /F23 1214 0 R /F27 1222 0 R /F35 1604 0 R /F44 2021 0 R /F54 2305 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+3979 0 obj <<
+/Length 1342
+/Filter /FlateDecode
+>>
+stream
+xڅVێ6}߯0ЇJIQEioM%V[%j;áy€9$>g~|rJXDDĪh~eyKۻORr'"YmXL<^RLl-
+uoz:|ZOSLCewo]%SgEft%(c2sjh[u}9Fc؅"
+mxL""_m6Džšny6
+NꇪtN߭;hG2cZ 5,W<v/ںj }]1ͮ*7(|޷a֌ȱjL0hjV$/1ypRdG f`}MWNi⹽=zk*ɮ
+0G.̮ev,Dt
+Ux́PH4xvGS`M>=Os$X߆ۅg`r`v&t`@#,~j_@D\/:03쁐ЍQ\؇2rCv@B'՛EΚ=4B
+
+endobj
+3978 0 obj <<
+/Type /Page
+/Contents 3979 0 R
+/Resources 3977 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 3873 0 R
+/Annots [ 3986 0 R ]
+>> endobj
+3986 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [178.6818 665.9084 233.0102 676.383]
+/Subtype /Link
+/A << /S /GoTo /D (http) >>
+>> endobj
+3980 0 obj <<
+/D [3978 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+3981 0 obj <<
+/D [3978 0 R /XYZ 152.1362 708.3437 null]
+>> endobj
+3982 0 obj <<
+/D [3978 0 R /XYZ 457.3046 708.3437 null]
+>> endobj
+3983 0 obj <<
+/D [3978 0 R /XYZ 322.4878 695.3923 null]
+>> endobj
+3984 0 obj <<
+/D [3978 0 R /XYZ 71.731 693.2354 null]
+>> endobj
+3985 0 obj <<
+/D [3978 0 R /XYZ 71.731 678.2915 null]
+>> endobj
+1659 0 obj <<
+/D [3978 0 R /XYZ 71.731 630.934 null]
+>> endobj
+670 0 obj <<
+/D [3978 0 R /XYZ 171.2348 585.6797 null]
+>> endobj
+1660 0 obj <<
+/D [3978 0 R /XYZ 71.731 581.8494 null]
+>> endobj
+674 0 obj <<
+/D [3978 0 R /XYZ 413.6679 546.3073 null]
+>> endobj
+3987 0 obj <<
+/D [3978 0 R /XYZ 71.731 535.9423 null]
+>> endobj
+3988 0 obj <<
+/D [3978 0 R /XYZ 401.1834 526.1828 null]
+>> endobj
+3989 0 obj <<
+/D [3978 0 R /XYZ 457.301 513.2314 null]
+>> endobj
+3990 0 obj <<
+/D [3978 0 R /XYZ 239.3111 487.3285 null]
+>> endobj
+3991 0 obj <<
+/D [3978 0 R /XYZ 71.731 480.1903 null]
+>> endobj
+3992 0 obj <<
+/D [3978 0 R /XYZ 319.2438 443.4929 null]
+>> endobj
+3977 0 obj <<
+/Font << /F33 1322 0 R /F27 1222 0 R /F35 1604 0 R /F23 1214 0 R /F44 2021 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+3995 0 obj <<
+/Length 2396
+/Filter /FlateDecode
+>>
+stream
+xڍ]۸=o'VԷMPMm+K$w/ʲ,X ÙpHE
+7RITL$B8` *v@{ Qs4Ud7=V,Ӷ(nyZ&|Y p]gOT2j"z/:\l#[ 6<#_,F6:ꌈk4$^hktj07~Zɒdn%vuspSS C>'H#BC,u  "d{!|Y*G T6tEkLj@BϭqZʒ=#{Zr=8!g̀ hD
+ ,kɾLQw/`-70%>[\9F2><1v x~\ C7&QI
+2[9S6K$$TJԂ$CQ4'87SJz8ذCg4
+Bdb<榶'<jÉcb8:<U(A` `k9DVYr*>F@/X(p!A!r|G))*hNYן0ݾ9U%#B
+%Y%"l[q i])ѱF+&~n|Oy㻿<B@AO*lGc*n(x V7"󍟦Q>+rTpP7 3x*xQ R,r`R|fmDz[ٙs?Vy,+ZTxfQ"3ahjFkFTg_7
+4vAbFT~^1%zՆ إ :x:4nWIә]Ķiz=1$~m20h9b y
+1*3&GU[(Rduu%7RݹŹ} cZs:<ՙj+6V
+n)[5HfHOf):ԥe\lNy.x>JѲ
+2
+<G`oTӃ%Eot0f[5"GGOfȢ-:swY>!bdA3 kpk Aei(4acA:Z=WVXύ+0E !]Jљn'&<h<ʖqE9H8-~t,1aǠ#5%ƒ1]Hk#ݩ=c!
+GyB<6s 3H׃+x^)xk A^}˜N> ̶dVH5WMzC0"8"4Fxv7)gO500ܫK-@¯+Z
+7"1'{]#
+c/j'H߽qn,1pl"cK;*JF'8MT5;2 vLmZp3X
+5;?p]ڢh^ʃk8ߍZ1(c4yyJW*8XJX_z_@7-_ ?ꮓrunHۿwM
+endobj
+3994 0 obj <<
+/Type /Page
+/Contents 3995 0 R
+/Resources 3993 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 3873 0 R
+>> endobj
+3996 0 obj <<
+/D [3994 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+1761 0 obj <<
+/D [3994 0 R /XYZ 71.731 718.3063 null]
+>> endobj
+678 0 obj <<
+/D [3994 0 R /XYZ 320.8286 703.236 null]
+>> endobj
+1762 0 obj <<
+/D [3994 0 R /XYZ 71.731 692.1839 null]
+>> endobj
+682 0 obj <<
+/D [3994 0 R /XYZ 205.3041 651.1593 null]
+>> endobj
+3997 0 obj <<
+/D [3994 0 R /XYZ 71.731 642.3364 null]
+>> endobj
+3998 0 obj <<
+/D [3994 0 R /XYZ 506.4313 629.6001 null]
+>> endobj
+3999 0 obj <<
+/D [3994 0 R /XYZ 71.731 583.6077 null]
+>> endobj
+4000 0 obj <<
+/D [3994 0 R /XYZ 472.2997 572.8131 null]
+>> endobj
+1763 0 obj <<
+/D [3994 0 R /XYZ 71.731 552.7235 null]
+>> endobj
+686 0 obj <<
+/D [3994 0 R /XYZ 317.5989 509.626 null]
+>> endobj
+4001 0 obj <<
+/D [3994 0 R /XYZ 71.731 497.188 null]
+>> endobj
+4002 0 obj <<
+/D [3994 0 R /XYZ 71.731 462.164 null]
+>> endobj
+4003 0 obj <<
+/D [3994 0 R /XYZ 71.731 460.0072 null]
+>> endobj
+4004 0 obj <<
+/D [3994 0 R /XYZ 71.731 455.0258 null]
+>> endobj
+4005 0 obj <<
+/D [3994 0 R /XYZ 89.6638 434.2686 null]
+>> endobj
+4006 0 obj <<
+/D [3994 0 R /XYZ 165.4621 434.2686 null]
+>> endobj
+4007 0 obj <<
+/D [3994 0 R /XYZ 255.7901 434.2686 null]
+>> endobj
+4008 0 obj <<
+/D [3994 0 R /XYZ 431.2068 434.2686 null]
+>> endobj
+4009 0 obj <<
+/D [3994 0 R /XYZ 378.8166 421.3172 null]
+>> endobj
+4010 0 obj <<
+/D [3994 0 R /XYZ 71.731 419.1603 null]
+>> endobj
+4011 0 obj <<
+/D [3994 0 R /XYZ 71.731 404.2164 null]
+>> endobj
+4012 0 obj <<
+/D [3994 0 R /XYZ 76.7123 354.7667 null]
+>> endobj
+4013 0 obj <<
+/D [3994 0 R /XYZ 71.731 334.8415 null]
+>> endobj
+4014 0 obj <<
+/D [3994 0 R /XYZ 76.7123 259.0258 null]
+>> endobj
+4015 0 obj <<
+/D [3994 0 R /XYZ 89.6638 241.093 null]
+>> endobj
+4016 0 obj <<
+/D [3994 0 R /XYZ 71.731 187.1305 null]
+>> endobj
+4017 0 obj <<
+/D [3994 0 R /XYZ 89.6638 171.3545 null]
+>> endobj
+4018 0 obj <<
+/D [3994 0 R /XYZ 71.731 143.2948 null]
+>> endobj
+4019 0 obj <<
+/D [3994 0 R /XYZ 89.6638 127.5189 null]
+>> endobj
+4020 0 obj <<
+/D [3994 0 R /XYZ 71.731 112.4107 null]
+>> endobj
+3993 0 obj <<
+/Font << /F23 1214 0 R /F27 1222 0 R /F35 1604 0 R /F44 2021 0 R /F33 1322 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+4023 0 obj <<
+/Length 3240
+/Filter /FlateDecode
+>>
+stream
+xڝk۶ōx|tquN|it:XSB\~}w
+Ȣ&c?fG~CyYZGi>}ui{C47~拜%iGyެ/"h8/Mb#87fڀ3P?<,/,&9+,F(Q^W6 6B42Vijǩ_xg5t`4sF߷Mgɚ{\4~6}Ϗ4frϦHaZ>NX@ uE&R*zacY7,w :RUxS#JxAԪ\0j-I,Ќ^q
+FKRK6(R+m Gk"ǒ+;5jOOOƣdoL@:2ؑD%r+=G"Z ~зwW'ޡ~Y)HlکEm4J~E?:T[vLFJsnOkhWw S#? OQ+Ձ74< /l" (N3;b&޴%:E h@z|K>;
+j/ȱpş wwF$E xكۮltWO!rNZ̍`v\O i{4 fJwmŠ-k!Pe5;@00z?И NdY4XKiA/?cxT8ȝp+Qu_dK!]֜%5bubN#Sok)c[ʣ_᧙o_ @yAvOEh19;unF)g]3l<@<Ezq$W;Í,|s7n@q񆍷x-<H9h/N<xM @B@ˌ"TDc
+p;C:qcf:Kc>T:i'gP*s c[hW6t@c&Ԇf>_ TykRD۩l
+̑3?/PX;D$=C,MN申t|ck~ő߱MqjZ|VOrDjA `*6yVgi@UWk^VGk
+~5˓V1 v3:v-k/4G]YUMby5awPcZSXnfV ~-IV쒑M) ϖMkNKQgVlgq|94'}oyJߙ<OS+;9?8%`]~T0
+6!9ʮ/i+fAm~d'i OHǔw N"Pl>tT^F7ʟ/ocО
+:[>:#j1f(W 7DHpQXe'ydRJMԦT$t^k,5 @C+&joqJ78܎gi-Ĥv|uOi"UDxGɰP,ah+粩'UcFU/}P0Ih 2"L (jѺH^hXpf>3`"d* 6o[$~\/pa @e7O4( ܫ5(BԊ#<.y&IねB( duGPM*UPmW&x{(ٜ0$1=A4x&.5]}`0鯨eC }:A
+8ggwc^F3INgt*rdݘK.+оENgF8/rXPԪP.Z$9 n,*L9Y-i49M 7wNvxUҜbCO=I`2u"-PW$*&H֣pԨH
+
+!vmg<:.4YPNCes|YpGHG/7'pqLk8wT<g?jGsx`s
+@z\Q &c?-6CQs\)XԔuMԄ QBg~:>+PlHR^^Ҙz,;(edjZylDVьߒ>'hN9
+mqBOVjV '4N8b+IM-4:9^7k- `ՎRP?&?̹,aNٴĦOf_!O^~뱠.Xֳ| O-Ⱥd w¹ɳ/#
+3 ay`C;ȿht)oa%鸶8n%nu (\ϯ EW6喎g"H{/ HMr?f-sI¢0da,A8endstream
+endobj
+4022 0 obj <<
+/Type /Page
+/Contents 4023 0 R
+/Resources 4021 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 3873 0 R
+>> endobj
+4024 0 obj <<
+/D [4022 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+4025 0 obj <<
+/D [4022 0 R /XYZ 89.6638 708.3437 null]
+>> endobj
+4026 0 obj <<
+/D [4022 0 R /XYZ 241.2196 708.3437 null]
+>> endobj
+4027 0 obj <<
+/D [4022 0 R /XYZ 417.1824 695.3923 null]
+>> endobj
+4028 0 obj <<
+/D [4022 0 R /XYZ 71.731 688.2541 null]
+>> endobj
+1764 0 obj <<
+/D [4022 0 R /XYZ 71.731 657.3699 null]
+>> endobj
+690 0 obj <<
+/D [4022 0 R /XYZ 252.0091 614.2725 null]
+>> endobj
+4029 0 obj <<
+/D [4022 0 R /XYZ 71.731 601.8345 null]
+>> endobj
+4030 0 obj <<
+/D [4022 0 R /XYZ 71.731 579.7619 null]
+>> endobj
+4031 0 obj <<
+/D [4022 0 R /XYZ 71.731 551.7022 null]
+>> endobj
+4032 0 obj <<
+/D [4022 0 R /XYZ 71.731 546.7209 null]
+>> endobj
+4033 0 obj <<
+/D [4022 0 R /XYZ 89.6638 525.9636 null]
+>> endobj
+4034 0 obj <<
+/D [4022 0 R /XYZ 89.6638 525.9636 null]
+>> endobj
+4035 0 obj <<
+/D [4022 0 R /XYZ 89.6638 495.0794 null]
+>> endobj
+4036 0 obj <<
+/D [4022 0 R /XYZ 71.731 495.0794 null]
+>> endobj
+4037 0 obj <<
+/D [4022 0 R /XYZ 71.731 383.8477 null]
+>> endobj
+4038 0 obj <<
+/D [4022 0 R /XYZ 89.6638 365.9149 null]
+>> endobj
+4039 0 obj <<
+/D [4022 0 R /XYZ 89.6638 365.9149 null]
+>> endobj
+4040 0 obj <<
+/D [4022 0 R /XYZ 71.731 337.8552 null]
+>> endobj
+4041 0 obj <<
+/D [4022 0 R /XYZ 89.6638 322.0793 null]
+>> endobj
+4042 0 obj <<
+/D [4022 0 R /XYZ 89.6638 322.0793 null]
+>> endobj
+4043 0 obj <<
+/D [4022 0 R /XYZ 71.731 319.9225 null]
+>> endobj
+4044 0 obj <<
+/D [4022 0 R /XYZ 89.6638 304.1466 null]
+>> endobj
+4045 0 obj <<
+/D [4022 0 R /XYZ 89.6638 304.1466 null]
+>> endobj
+4046 0 obj <<
+/D [4022 0 R /XYZ 71.731 301.9897 null]
+>> endobj
+4047 0 obj <<
+/D [4022 0 R /XYZ 89.6638 286.2138 null]
+>> endobj
+4048 0 obj <<
+/D [4022 0 R /XYZ 89.6638 286.2138 null]
+>> endobj
+4049 0 obj <<
+/D [4022 0 R /XYZ 71.731 284.057 null]
+>> endobj
+4050 0 obj <<
+/D [4022 0 R /XYZ 89.6638 268.2811 null]
+>> endobj
+4051 0 obj <<
+/D [4022 0 R /XYZ 89.6638 268.2811 null]
+>> endobj
+4052 0 obj <<
+/D [4022 0 R /XYZ 71.731 266.1242 null]
+>> endobj
+4053 0 obj <<
+/D [4022 0 R /XYZ 89.6638 250.3483 null]
+>> endobj
+4054 0 obj <<
+/D [4022 0 R /XYZ 89.6638 250.3483 null]
+>> endobj
+4055 0 obj <<
+/D [4022 0 R /XYZ 71.731 248.1915 null]
+>> endobj
+4056 0 obj <<
+/D [4022 0 R /XYZ 89.6638 232.4156 null]
+>> endobj
+4057 0 obj <<
+/D [4022 0 R /XYZ 89.6638 232.4156 null]
+>> endobj
+4058 0 obj <<
+/D [4022 0 R /XYZ 71.731 217.3073 null]
+>> endobj
+4059 0 obj <<
+/D [4022 0 R /XYZ 89.6638 201.5314 null]
+>> endobj
+4060 0 obj <<
+/D [4022 0 R /XYZ 89.6638 201.5314 null]
+>> endobj
+4061 0 obj <<
+/D [4022 0 R /XYZ 71.731 199.3745 null]
+>> endobj
+4062 0 obj <<
+/D [4022 0 R /XYZ 89.6638 183.5986 null]
+>> endobj
+4063 0 obj <<
+/D [4022 0 R /XYZ 89.6638 183.5986 null]
+>> endobj
+4064 0 obj <<
+/D [4022 0 R /XYZ 71.731 168.4904 null]
+>> endobj
+4065 0 obj <<
+/D [4022 0 R /XYZ 89.6638 152.7144 null]
+>> endobj
+4066 0 obj <<
+/D [4022 0 R /XYZ 89.6638 152.7144 null]
+>> endobj
+4067 0 obj <<
+/D [4022 0 R /XYZ 71.731 137.6062 null]
+>> endobj
+4068 0 obj <<
+/D [4022 0 R /XYZ 89.6638 121.8302 null]
+>> endobj
+4069 0 obj <<
+/D [4022 0 R /XYZ 89.6638 121.8302 null]
+>> endobj
+4070 0 obj <<
+/D [4022 0 R /XYZ 71.731 106.722 null]
+>> endobj
+4021 0 obj <<
+/Font << /F33 1322 0 R /F27 1222 0 R /F23 1214 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+4074 0 obj <<
+/Length 2395
+/Filter /FlateDecode
+>>
+stream
+xڵYݓ۸ _NtEQ\I7m̵ԕ%$g ,۴@~|r
+r%27l S?daiSwon?1YDq D"Y*"IVKt7_D2 $U[Ujad*E v,NLFl,Ys JÌX
+>U@̉fwî4eej\sY% %^Σ0حSv0jv]Ytab\ʠGg 'G,<6boH,#xl>J7'Z#Ƴ׌BZ`4鈯݂M;H]U zE}T!m=n]FЉ&h vGB50
+H=g:2`OԀF!w:j~zhZXNl rSβﲁgm,EF꜀:UzULnA{= R\ĿKϥƔ
+R yE_*Z
+-ckyn0̛R/+)d,&$,K2Zh
+Gu B2Ƈ
+ XNm!èvǪuL怔Vxl#6by8|1}Pî;C6d,)XC; 
+z 8vRq6Az(BJ0Wɡ3[ تR Rحٱ]~@@¾njpm`7 McQڍap{lB
+[7-}o03sDc8~8ֶnNk Λ$}zBszM(C?ǝPDa? Uendstream
+endobj
+4073 0 obj <<
+/Type /Page
+/Contents 4074 0 R
+/Resources 4072 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 4108 0 R
+/Annots [ 4103 0 R 4104 0 R 4105 0 R ]
+>> endobj
+4103 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [519.2829 343.5385 538.9788 354.4424]
+/Subtype /Link
+/A << /S /GoTo /D (bug_status_workflow) >>
+>> endobj
+4104 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [70.7348 330.5871 109.0945 341.491]
+/Subtype /Link
+/A << /S /GoTo /D (bug_status_workflow) >>
+>> endobj
+4105 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [113.0235 330.5871 157.7393 341.491]
+/Subtype /Link
+/A << /S /GoTo /D (lifecycle-image) >>
+>> endobj
+4075 0 obj <<
+/D [4073 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+1766 0 obj <<
+/D [4073 0 R /XYZ 71.731 741.2204 null]
+>> endobj
+4076 0 obj <<
+/D [4073 0 R /XYZ 89.6638 708.3437 null]
+>> endobj
+4077 0 obj <<
+/D [4073 0 R /XYZ 89.6638 708.3437 null]
+>> endobj
+4078 0 obj <<
+/D [4073 0 R /XYZ 71.731 680.284 null]
+>> endobj
+4079 0 obj <<
+/D [4073 0 R /XYZ 89.6638 664.5081 null]
+>> endobj
+4080 0 obj <<
+/D [4073 0 R /XYZ 89.6638 664.5081 null]
+>> endobj
+4081 0 obj <<
+/D [4073 0 R /XYZ 71.731 662.3513 null]
+>> endobj
+4082 0 obj <<
+/D [4073 0 R /XYZ 89.6638 646.5753 null]
+>> endobj
+4083 0 obj <<
+/D [4073 0 R /XYZ 89.6638 646.5753 null]
+>> endobj
+4084 0 obj <<
+/D [4073 0 R /XYZ 71.731 644.4185 null]
+>> endobj
+4085 0 obj <<
+/D [4073 0 R /XYZ 89.6638 628.6426 null]
+>> endobj
+4086 0 obj <<
+/D [4073 0 R /XYZ 89.6638 628.6426 null]
+>> endobj
+4087 0 obj <<
+/D [4073 0 R /XYZ 206.4347 615.6911 null]
+>> endobj
+4088 0 obj <<
+/D [4073 0 R /XYZ 335.6388 615.6911 null]
+>> endobj
+4089 0 obj <<
+/D [4073 0 R /XYZ 71.731 613.5343 null]
+>> endobj
+4090 0 obj <<
+/D [4073 0 R /XYZ 71.731 539.8663 null]
+>> endobj
+4091 0 obj <<
+/D [4073 0 R /XYZ 89.6638 524.0903 null]
+>> endobj
+4092 0 obj <<
+/D [4073 0 R /XYZ 89.6638 524.0903 null]
+>> endobj
+4093 0 obj <<
+/D [4073 0 R /XYZ 71.731 496.0306 null]
+>> endobj
+4094 0 obj <<
+/D [4073 0 R /XYZ 89.6638 480.2547 null]
+>> endobj
+4095 0 obj <<
+/D [4073 0 R /XYZ 89.6638 480.2547 null]
+>> endobj
+4096 0 obj <<
+/D [4073 0 R /XYZ 71.731 465.1465 null]
+>> endobj
+4097 0 obj <<
+/D [4073 0 R /XYZ 89.6638 449.3705 null]
+>> endobj
+4098 0 obj <<
+/D [4073 0 R /XYZ 89.6638 449.3705 null]
+>> endobj
+4099 0 obj <<
+/D [4073 0 R /XYZ 71.731 447.2137 null]
+>> endobj
+4100 0 obj <<
+/D [4073 0 R /XYZ 89.6638 431.4378 null]
+>> endobj
+4101 0 obj <<
+/D [4073 0 R /XYZ 89.6638 431.4378 null]
+>> endobj
+1765 0 obj <<
+/D [4073 0 R /XYZ 71.731 411.3482 null]
+>> endobj
+694 0 obj <<
+/D [4073 0 R /XYZ 259.6867 368.2507 null]
+>> endobj
+4102 0 obj <<
+/D [4073 0 R /XYZ 71.731 355.8127 null]
+>> endobj
+4106 0 obj <<
+/D [4073 0 R /XYZ 288.6723 320.7887 null]
+>> endobj
+4107 0 obj <<
+/D [4073 0 R /XYZ 516.9517 320.7887 null]
+>> endobj
+1949 0 obj <<
+/D [4073 0 R /XYZ 71.731 305.6805 null]
+>> endobj
+4072 0 obj <<
+/Font << /F33 1322 0 R /F27 1222 0 R /F23 1214 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+4111 0 obj <<
+/Length 885
+/Filter /FlateDecode
+>>
+stream
+x}UQ6 ~ϯ$6ng;rsC'0%s;_%X6{$IAxrn9Jnprٓ~9<q\ A,V2XL,8V`Bpkl ˷m?72NY?ExM,Izde2[9Ԝuy+[ϸwg:%2fI
+9)<m2ljJzR1"Oϛ~:2e*3&D|!$}I.VDaQXc ޠENW\jΤ̹y]bW 5Q8iF\|ȜDQF[?,:"U(;  wzcwt^u7k@֡9L8c|`z(h-wD
+GҨjܰvR]җ@Eۢ௛[ x붢 ߑ*=9̧+=_5JsשdwT. ':Cmݏz>2j #v(2$`3zh&.=+Lg
+endobj
+4110 0 obj <<
+/Type /Page
+/Contents 4111 0 R
+/Resources 4109 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 4108 0 R
+>> endobj
+4071 0 obj <<
+/Type /XObject
+/Subtype /Image
+/Width 593
+/Height 470
+/BitsPerComponent 8
+/ColorSpace /DeviceRGB
+/Length 24250
+/Filter /FlateDecode
+>>
+stream
+xwxǥ" D UXhJr^*M [
+@(JIo<93fgwg#|̙9r
+
+;ȺR2Qw)Ooɿt饡'
+
+ J7?/T_toWrOh<1|uJR
+xO7E"[^ V~1A1
+<~8
+xY(etv
+
+l"##;w\|,YHߦ`
+XbE?r%Dc?_;v`X|OSO]z)ʓS<$z
+[ґky"K:5%J?P:yKyL8Q,X=111Gf)<(ϗxG)ˎa>cE=;0+V_
+q/ڧ<v1,gR#G`U^DDw}ZsY-#RLSOy<Bg ZT=)K}ʯ;ERK/RpM6͟?ŋP2ëUh"(DՀFeΜ[nyWGTr<dRY^كo+[lZQQQC
++VH[zutPs:{7ontHg7^re4idM:dtg^Q8Wy3fh׮}n|\˃t"Ab(d*ϕe؉Wl*OƝ<ϔqƹs碖ɓ'ٽ{Q*ThӦM噱t<hڴiڴi3k׮<(ϗ߿5o)tɏ|̍7z}ĉL2)w%(<_*n3go޾`7]t1Oʃ|?/\
+)<3.\ػw={`PCyC ov)to>uԃBy
+Vٳg<dDDDԫW/**
+P'PY`A^PPFyC/,jy:9m۶ѿP<\͟?GwFPPP=e@1}t-[(ʓJ̛7Y
+)O!)sO#@<Ŭd-gi ufv4˫ĒRV@?+쩈U|&SyGn޼9jy:9N?AyPE'R0%b#itCq&(/lj(c(+#262f%~U<Yyjٍsdɒ&P~Te̪]IDi,<*%33e WU(Y3%9է ,oţFB-siѢjy3bS$bĦ<E-+Z$]ybb="VdXS)Ly.[y3gl߾PpGy>PgB ƌӲeK.G-׏9ҾgAByP$ә])Kٍst8p
+jyױ'^zKuΝӕwEҧOߥKU+oܹ?<d,Z~Xn]*O)7 ƍO?4+ř2e.ZŚ7o|ب@*Ox$M6D=qh2eJ
+ҤI#^DM<C頖tg<7ݢ˨l4622xKoگ_L#,gb,7 wcDgR<)nUV3ȑ#بJÉ>P%RoگV,mTjY<nE9S@ ͙3gѱcׇTgց"L#< @oگOc^@,C,塖gɓ6mi&lT 'jyf9cXdyJ~u:>,OY@N9KyN:V </V\ٿ4+6 ;TKƢgѴ_>2.Z!sƹ[z5}=ʡྼ+-t=!sfϞݯ_cǎAy Xx1(ʳ#CD塖gFxxxƍ7n܈
+Eωʣ)kZwO>>h !sƹ[f̈́ Ps.k }p*ĉSLx||A
+ƹ[~)SB LݻwSJv}l>}zʦTM>G
+eWLgn;oy<!;&[lzBRGеkW<Ix*zp+Oջ< )oP*-IWWk(pZ+,ܽ-ϔ'ˎq֍ y1cԭ[wݺupSy<a.3ʒRt&/KW;͔gDJt9.@yg۷}A-ŋw<(ONlR)/N-hx}Ή) <iӦӧ\WfEѢE=VxĦȡ +OL (љ-,JTyu1%K%K?/YG葎(\ha…s P͟?[R<o<mFܹs)޾};s̡HYLYfQk.W•޽ەA{RLKv%<%^GrL6PLM<JСCP2V\ٿ'$Wd}x剕E,0xByӧG9'Q<x`۵kGСCſcnݚ#Fnh6j(7oN1c(nڴm9oذ!ׯ_)S(ߦobu֥k׮M1I"EP\F IW^b.UVB-ϜѣGAyz+9SR<ڜg<]yI|OTy׿Ǝ3gNCeʔgϞZA|Wtt8z#IԖ/_N1gj˖-3(9S[t)ŜQH1gj9Rg)欍dD}86ŜQI1gmz5/S g:_I⣘PI1ߋ_%tPs:=ر @yJrjyJE+2{;Wl)Ou-O^l^QקOgmBy :ɓ/9`AyfʓOyEy173T<sO+ YCe͚͛P^*8@yU
+fsVy#G֬Ysڵ
+_Z<$1$(+W4h_A AyPY0bĈ5jY_
+jyϨQh4hf͚aP5k 2; Ay͛ED`+-*5P_Wzj|Y
+۷ofPرcϜ9CA-}tho>JRH Ay XYv-`I޼yܹ@4-Z?Æ ZUPݻ^{^dƍ私}Wo߾ Aytϟ߶m[\%#GRHѻw:U`r~L4O<dU(ʳ`Ν .D-Ϲ,<xгgϰ0n&M{oܦ}> aԩvě 
++WF<
+ ʃAy^f_塖'Bb⼏&+pbHS<QL`<y#bf'6  先 pm۶6@y/'6}_MEْ<9}FXJGheXS 兎GEEAyAy Yo(' 9wD&/#lRyTjyNW^DDDz,Z<cQԳ<;}, 򠼐Uޑ#G֭[w%(<($_yBI|PRS#&R3q'|ZK(/}S<x>S>}-[vҥPjyPVjyNW޴iY@y
+)ީG @.H^$)OAe~TΝ;g̘qa(@yPgM^hZY2%%̈,Ϻf1eE-݃&jy
+۷o߂
+ fʔ))OZl٫W~ z٪U+
+ܹsiI|a 1J</r޸qC-[|7-?@A֚fYށSii]\̔]V-M`!)o. rDD윘AyRC-Yɓ<!/ӛXŔ.+z-Ȓ<VIR^.]hի>5j 31={4FyN:CyyP#ApZD^T;1)<S^
+hz>~gW^ypPjyPuiw:o޼R8)Z1>LrGMCm0s\cʳky(vy oӦMATR)S/^gҦM(/\x{,'n3BJU&".))^B//ɗ/!&={6 /L~3j
+$jy(O37)+ы܆%{NQnr_*XN:AyPPc(tҝ;wdh*C%S6aoy mw(/@g&>$zy<9u&`O93By=iƎ駟Ν; z ڥG2|Sfo~?n2MŇD/d_=z֭[(oߍP8-ӞzdyqC-/ ߵ6+{JULna-Oi2WV">$zy<~\3'=PC`}#GXP^)O(Fч/÷2֐rȮtSyIyB|t')@y~ț7%ST=?)>؛JE-ʃgx"Q-fT.VDFy
+*1H:|s'6{,n'T%2>P^t鰽 K.?~ZZH^|:29KN'ӏY |GFGG9s-SxW,q-OXBZ^lٰ'8Ny۷W=Щ
+)2e
+|Bxxo1d'MD1.'L@x1c)RdԨQ3bzby7i K(9S<lذEN<bZ
+|4HK/V-Ń
+H;3fPLkN1_GD|_~%JG]tI:5?> [%C 4Yf#7{t$C[2$ ~_vںDɜU+/9<YyyEU\iY-==Y6SI}v։͓'O&ʎya~Wt@֧ON4%KmfR^R'G/E*7'zy .{fn*&*oQM(8RGiH(̱pJ9SfҦM۷oxF$>7Y"-"~Xl+uqbV3b]kK
+C-@yP](ZG4{c|޴lݔ/}bUZ@T PLPs Rxq~
+<
+
+tz#G &`(@rPBݯ]f7n
+`_/+O<
+,Xh 9rƍ<( NBn{m˝;w._|= سg%Pz֬Y
+JXΝ
+ubDȢa"1g1^ʘuߑk+& .eK%t9ңG)B0|4
+M*#_f͚eΜyƌpҵkק~zf 1d(MvLQl䆽dA]XTJaGdɒeٲeГ,_gA 8ė5;uu֟ *&r
+TBqtt4ş9ŴRJ]
+*P~z?3ޜO>M6QGQe?oN{GΝ;).[,ܜtݻbS~K*E).QŇXb=z7|uiH hE&Н
+)})Rϟ/(P
+ׯO17ŬBYgssrfQQ􍣘AY7SL\Y􍦘0
+Ѭ0
+
+l:q ^2 W|Pˑ#ǽ{hi_ |тYL8|CYLc,/cD,]g1
+1_M
+endobj
+4112 0 obj <<
+/D [4110 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+4113 0 obj <<
+/D [4110 0 R /XYZ 71.731 696.3587 null]
+>> endobj
+698 0 obj <<
+/D [4110 0 R /XYZ 263.1645 198.2296 null]
+>> endobj
+4114 0 obj <<
+/D [4110 0 R /XYZ 71.731 185.7916 null]
+>> endobj
+4115 0 obj <<
+/D [4110 0 R /XYZ 245.7962 163.719 null]
+>> endobj
+4116 0 obj <<
+/D [4110 0 R /XYZ 71.731 156.5809 null]
+>> endobj
+4117 0 obj <<
+/D [4110 0 R /XYZ 71.731 112.7453 null]
+>> endobj
+4109 0 obj <<
+/Font << /F33 1322 0 R /F32 1230 0 R /F23 1214 0 R /F27 1222 0 R >>
+/XObject << /Im1 4071 0 R >>
+/ProcSet [ /PDF /Text /ImageC ]
+>> endobj
+4120 0 obj <<
+/Length 2159
+/Filter /FlateDecode
+>>
+stream
+xڥYY~_ah@ڌ#O;f`I}-V=:biVb1Y,ůEu_E"Lp;}X맀Y̳3}|ӏQ(Db_Q$4)Y< Ŧt^u^"]he8kuO8I"oZm mK?QeƲRFuBQ ^l*HCxβ'nžHhp@[bK#Z&/U$^ jcT۱Nĩ>ՙ8>004rwtۆYvwo)8wI0SsEU4C^( 6'aF0U "A
+b"BڔD,h]+
+$)k(_l{x܊)\`F
+Z~̾_,H+7g6.9sB<6Llrhe5-fxt=8]P
+o, kLľ<!?P'V־5~?bfzД/ ~4c4Fӌ6H_Vn0p:]XْN?65˄(üy *d~`@CjK\Hgx!'GTh|\_︓yzpS0P_ue@|:ZM
+endobj
+4119 0 obj <<
+/Type /Page
+/Contents 4120 0 R
+/Resources 4118 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 4108 0 R
+/Annots [ 4122 0 R ]
+>> endobj
+4122 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [459.3526 705.1906 524.0572 716.0945]
+/Subtype /Link
+/A << /S /GoTo /D (savedsearches) >>
+>> endobj
+4121 0 obj <<
+/D [4119 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+1767 0 obj <<
+/D [4119 0 R /XYZ 71.731 695.2927 null]
+>> endobj
+702 0 obj <<
+/D [4119 0 R /XYZ 217.9167 656.0199 null]
+>> endobj
+4123 0 obj <<
+/D [4119 0 R /XYZ 71.731 648.6676 null]
+>> endobj
+4124 0 obj <<
+/D [4119 0 R /XYZ 71.731 628.7572 null]
+>> endobj
+4125 0 obj <<
+/D [4119 0 R /XYZ 71.731 599.9303 null]
+>> endobj
+4126 0 obj <<
+/D [4119 0 R /XYZ 427.5857 587.0784 null]
+>> endobj
+4127 0 obj <<
+/D [4119 0 R /XYZ 113.3184 574.127 null]
+>> endobj
+4128 0 obj <<
+/D [4119 0 R /XYZ 205.0794 574.127 null]
+>> endobj
+4129 0 obj <<
+/D [4119 0 R /XYZ 71.731 554.0374 null]
+>> endobj
+4130 0 obj <<
+/D [4119 0 R /XYZ 71.731 543.1433 null]
+>> endobj
+4131 0 obj <<
+/D [4119 0 R /XYZ 71.731 538.162 null]
+>> endobj
+4132 0 obj <<
+/D [4119 0 R /XYZ 81.6937 515.3474 null]
+>> endobj
+4133 0 obj <<
+/D [4119 0 R /XYZ 81.6937 515.3474 null]
+>> endobj
+4134 0 obj <<
+/D [4119 0 R /XYZ 71.731 513.1906 null]
+>> endobj
+4135 0 obj <<
+/D [4119 0 R /XYZ 81.6937 497.4147 null]
+>> endobj
+4136 0 obj <<
+/D [4119 0 R /XYZ 81.6937 497.4147 null]
+>> endobj
+4137 0 obj <<
+/D [4119 0 R /XYZ 71.731 495.2578 null]
+>> endobj
+4138 0 obj <<
+/D [4119 0 R /XYZ 81.6937 479.4819 null]
+>> endobj
+4139 0 obj <<
+/D [4119 0 R /XYZ 81.6937 479.4819 null]
+>> endobj
+1768 0 obj <<
+/D [4119 0 R /XYZ 71.731 477.3251 null]
+>> endobj
+706 0 obj <<
+/D [4119 0 R /XYZ 236.9017 445.0112 null]
+>> endobj
+4140 0 obj <<
+/D [4119 0 R /XYZ 71.731 438.8842 null]
+>> endobj
+4141 0 obj <<
+/D [4119 0 R /XYZ 71.731 367.1383 null]
+>> endobj
+1769 0 obj <<
+/D [4119 0 R /XYZ 71.731 310.3512 null]
+>> endobj
+710 0 obj <<
+/D [4119 0 R /XYZ 166.0799 277.0411 null]
+>> endobj
+4142 0 obj <<
+/D [4119 0 R /XYZ 71.731 268.4036 null]
+>> endobj
+4143 0 obj <<
+/D [4119 0 R /XYZ 344.894 258.1121 null]
+>> endobj
+4144 0 obj <<
+/D [4119 0 R /XYZ 71.731 244.0001 null]
+>> endobj
+4145 0 obj <<
+/D [4119 0 R /XYZ 155.2771 221.5492 null]
+>> endobj
+4146 0 obj <<
+/D [4119 0 R /XYZ 71.731 211.487 null]
+>> endobj
+4147 0 obj <<
+/D [4119 0 R /XYZ 154.7791 186.9788 null]
+>> endobj
+4148 0 obj <<
+/D [4119 0 R /XYZ 71.731 175.5766 null]
+>> endobj
+4149 0 obj <<
+/D [4119 0 R /XYZ 426.1592 152.4084 null]
+>> endobj
+4150 0 obj <<
+/D [4119 0 R /XYZ 71.731 140.289 null]
+>> endobj
+4118 0 obj <<
+/Font << /F33 1322 0 R /F27 1222 0 R /F23 1214 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+4153 0 obj <<
+/Length 2036
+/Filter /FlateDecode
+>>
+stream
+xX[~_p=6\
+6<fС,V6W gP/Fޛ^ZuGN]_9KS8YC!-¼%/I~
+Me].P.6B4;RYtԪR KwZH'_)yE &[
+yR0QV"͆+}$G]N _e5Q|h{;Db
+%. \+>ՠ{;ПOECJsvU.uW̭ mt|lO0';y-zhYSG) tq13&ls>(rw3A
+z`!k+j3m=w.h[$T@N 2
+YLTb.y<t wu;~ΕˌR
+U;k*(\PtW|#W¶uxQ
+0;wHzPk,D80ӝ(
+0JP„"2O<d`uǣ%TnQ[#=Nn-l)#Z`?G!h4ܻa( 51>PbuNxRY/E7~fŕG5TYsJ$.$ p(*T
+?zl6$v6"7|;]~GK $-I*I@d
+oOd#;(˵hEl'xr 8p2hMLgwudd[G|#&Ⰴ.gjE:} ܧӵ<߃U"mt|5M0λc"J ^
+r_rEρ?{ "pyW6<̊~<<FE1gG;O( ]endstream
+endobj
+4152 0 obj <<
+/Type /Page
+/Contents 4153 0 R
+/Resources 4151 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 4108 0 R
+>> endobj
+4154 0 obj <<
+/D [4152 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+4155 0 obj <<
+/D [4152 0 R /XYZ 103.2724 682.4408 null]
+>> endobj
+4156 0 obj <<
+/D [4152 0 R /XYZ 71.731 672.3787 null]
+>> endobj
+4157 0 obj <<
+/D [4152 0 R /XYZ 425.1626 647.8705 null]
+>> endobj
+4158 0 obj <<
+/D [4152 0 R /XYZ 71.731 635.751 null]
+>> endobj
+1770 0 obj <<
+/D [4152 0 R /XYZ 71.731 606.162 null]
+>> endobj
+714 0 obj <<
+/D [4152 0 R /XYZ 201.5262 572.8518 null]
+>> endobj
+4159 0 obj <<
+/D [4152 0 R /XYZ 71.731 564.3996 null]
+>> endobj
+4160 0 obj <<
+/D [4152 0 R /XYZ 463.4687 540.9713 null]
+>> endobj
+4161 0 obj <<
+/D [4152 0 R /XYZ 71.731 526.8593 null]
+>> endobj
+4162 0 obj <<
+/D [4152 0 R /XYZ 514.935 491.457 null]
+>> endobj
+4163 0 obj <<
+/D [4152 0 R /XYZ 71.731 479.3376 null]
+>> endobj
+4164 0 obj <<
+/D [4152 0 R /XYZ 71.731 462.9156 null]
+>> endobj
+1771 0 obj <<
+/D [4152 0 R /XYZ 71.731 423.1483 null]
+>> endobj
+718 0 obj <<
+/D [4152 0 R /XYZ 197.0146 385.9327 null]
+>> endobj
+4165 0 obj <<
+/D [4152 0 R /XYZ 71.731 378.0138 null]
+>> endobj
+4166 0 obj <<
+/D [4152 0 R /XYZ 103.9341 352.8568 null]
+>> endobj
+4167 0 obj <<
+/D [4152 0 R /XYZ 105.3508 339.9053 null]
+>> endobj
+4168 0 obj <<
+/D [4152 0 R /XYZ 71.731 332.7672 null]
+>> endobj
+4169 0 obj <<
+/D [4152 0 R /XYZ 518.6154 321.9726 null]
+>> endobj
+1772 0 obj <<
+/D [4152 0 R /XYZ 71.731 301.883 null]
+>> endobj
+722 0 obj <<
+/D [4152 0 R /XYZ 305.7426 264.6675 null]
+>> endobj
+4170 0 obj <<
+/D [4152 0 R /XYZ 71.731 254.5248 null]
+>> endobj
+1773 0 obj <<
+/D [4152 0 R /XYZ 71.731 211.5019 null]
+>> endobj
+726 0 obj <<
+/D [4152 0 R /XYZ 176.9732 174.2864 null]
+>> endobj
+4171 0 obj <<
+/D [4152 0 R /XYZ 71.731 163.9214 null]
+>> endobj
+4172 0 obj <<
+/D [4152 0 R /XYZ 71.731 147.0237 null]
+>> endobj
+4173 0 obj <<
+/D [4152 0 R /XYZ 71.731 105.3449 null]
+>> endobj
+4151 0 obj <<
+/Font << /F33 1322 0 R /F27 1222 0 R /F23 1214 0 R /F35 1604 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+4176 0 obj <<
+/Length 3350
+/Filter /FlateDecode
+>>
+stream
+xڵZs~Uh)srI.I`H .h_N3&b߂
+| "xIxU^WES<g;m]^*"/J*Bo8w]o_%=Ҫ>Pe*7/^9Iy(?+ ٲ ?QfDBܮapմ'}\߆huyiuGiAda$+]MO AJ`:Hz= zPq2n,U'+IyWeU_?x6vC 0
+;滥͇ >,d7TJg305FËY.nNբT`bYq+h@[-Ϣ,z7NRܴnc]ZG) Է G<K ]s
+;{clt.vhH$EYN);8Դ?SܱMʫҦwx&Q*;$/NY A6t`mp 顯.4NDL}$WOVv}[?տ"wǶǧC&aAiNVMI[o-6INoAD0O\[iM<L羗}g͵@ibVbhNSݟgɞȃ_"Cy4e!I9Mtzvmoѐ G5\˶6O
+zh
+<b'1"BjMqȠ {q
+|>/ !.BוotrtGuQxdF;/I"?̼<
+q 
+Y[z֝唟/ղ/@l4nnYtb6t FIgT
+PRǹZQM`ᒋi%I Ԧgܹro.°MKw2'1 Z|qrr*W
+y^[lN
+G *1L8
+ .s1[&za?`~d&lz1Ks!Xz(AR/VǮ;v}]A(2߸*wg~5q6}+x+/lY#ޱ;UTl #9
+쎯k#w j/7Vg+8uC˟m8u`0 %AV;FV9mSmq08Hfᄫvǀ4˽ӏ|gV<i;u G/h~2jr{iYΟN"\6߉+ˎ!d#{՞,qȑ["uPi'0BDۡ8eq ƻr|'GkcH÷ rz)8G]vSÿ͹ \<?sG EL'w߷Bendstream
+endobj
+4175 0 obj <<
+/Type /Page
+/Contents 4176 0 R
+/Resources 4174 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 4108 0 R
+/Annots [ 4183 0 R ]
+>> endobj
+4183 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [259.4752 403.9514 311.2805 414.8553]
+/Subtype /Link
+/A << /S /GoTo /D (userpreferences) >>
+>> endobj
+4177 0 obj <<
+/D [4175 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+4178 0 obj <<
+/D [4175 0 R /XYZ 71.731 741.2204 null]
+>> endobj
+4179 0 obj <<
+/D [4175 0 R /XYZ 71.731 718.3063 null]
+>> endobj
+4180 0 obj <<
+/D [4175 0 R /XYZ 71.731 601.097 null]
+>> endobj
+1774 0 obj <<
+/D [4175 0 R /XYZ 71.731 542.1531 null]
+>> endobj
+730 0 obj <<
+/D [4175 0 R /XYZ 353.5731 504.9376 null]
+>> endobj
+4181 0 obj <<
+/D [4175 0 R /XYZ 71.731 494.5726 null]
+>> endobj
+4182 0 obj <<
+/D [4175 0 R /XYZ 86.3959 407.1044 null]
+>> endobj
+4184 0 obj <<
+/D [4175 0 R /XYZ 71.731 399.9663 null]
+>> endobj
+4185 0 obj <<
+/D [4175 0 R /XYZ 512.7873 376.2203 null]
+>> endobj
+4186 0 obj <<
+/D [4175 0 R /XYZ 112.803 363.2688 null]
+>> endobj
+4187 0 obj <<
+/D [4175 0 R /XYZ 202.5552 363.2688 null]
+>> endobj
+1775 0 obj <<
+/D [4175 0 R /XYZ 71.731 320.2652 null]
+>> endobj
+734 0 obj <<
+/D [4175 0 R /XYZ 198.9687 277.1677 null]
+>> endobj
+1776 0 obj <<
+/D [4175 0 R /XYZ 71.731 273.3374 null]
+>> endobj
+738 0 obj <<
+/D [4175 0 R /XYZ 256.7516 237.7953 null]
+>> endobj
+4188 0 obj <<
+/D [4175 0 R /XYZ 71.731 227.4303 null]
+>> endobj
+4189 0 obj <<
+/D [4175 0 R /XYZ 434.2261 217.6708 null]
+>> endobj
+4190 0 obj <<
+/D [4175 0 R /XYZ 71.731 158.7269 null]
+>> endobj
+4191 0 obj <<
+/D [4175 0 R /XYZ 71.731 145.7755 null]
+>> endobj
+4192 0 obj <<
+/D [4175 0 R /XYZ 71.731 140.7942 null]
+>> endobj
+4193 0 obj <<
+/D [4175 0 R /XYZ 89.6638 120.0369 null]
+>> endobj
+4194 0 obj <<
+/D [4175 0 R /XYZ 128.2622 120.0369 null]
+>> endobj
+4195 0 obj <<
+/D [4175 0 R /XYZ 328.5279 120.0369 null]
+>> endobj
+4196 0 obj <<
+/D [4175 0 R /XYZ 71.731 104.9287 null]
+>> endobj
+4174 0 obj <<
+/Font << /F33 1322 0 R /F27 1222 0 R /F23 1214 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+4199 0 obj <<
+/Length 3131
+/Filter /FlateDecode
+>>
+stream
+xڍے|N^bju$OI&2f:\DWu
+0r(ו|,҄+ZeYy,/'G6mmU V`<KXQrX듃~khxvL47s"EE
+ blHv/ NAr ?*^3tw=
+@-XK95FӱD4x+?#ש^_G*H-])SUo Pݱ'ݻXp }~u7FfY]uEOXxPM.W.:+\tn qfA c"T#(\}7N gBlIz&!ٜ3>\@@kpVV!YhQd?S"4;jkyQ1ZKA j푗<jc^I!g jt:W@@?4'r-a42C=gSB"Wm,I}b5iF֋4Aź2x ϸT3w(4BNVf)2<HJ֕|fO(O].|(|K9.
+zZ4@Ȩ\1%gپXbU6 ܘ$P&P* R]6a?zpJk!ɭYőpߴ(.N]Rr8U=~0Fksr=\VV7so1ͽՔ6IN[TaJʅG'_S\Msu['p9笄ձU;e:w ]O>uw4KÀeFӮtЏh{I#~.~GH6$1_~/r7/}'oWpeQTI\AqՓek Hbxx/Xm"m u[]?a,ۚ>
+#k﹅GS\mP:%$+ :vԸd)Zѿ >Mr{) W*h4dT w2 po?hhhJR%0̬ ZbLufa;D}=h6-mޏ/ xC==+گus`+?DKaǵ4H<Naw@P7ᒺ _X([z+&bQň=Nמd i{8 S1^<I#Rbӫ Ov="[,@n$i|#Fi ]Df gfG2;n&R+][<N ɡ-r@[RlanȚ[ A#+*r&f"0{F.f#Y
+endobj
+4198 0 obj <<
+/Type /Page
+/Contents 4199 0 R
+/Resources 4197 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 4108 0 R
+>> endobj
+4200 0 obj <<
+/D [4198 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+4201 0 obj <<
+/D [4198 0 R /XYZ 76.7123 655.8406 null]
+>> endobj
+4202 0 obj <<
+/D [4198 0 R /XYZ 89.6638 637.9078 null]
+>> endobj
+4203 0 obj <<
+/D [4198 0 R /XYZ 71.731 635.751 null]
+>> endobj
+4204 0 obj <<
+/D [4198 0 R /XYZ 89.6638 619.9751 null]
+>> endobj
+4205 0 obj <<
+/D [4198 0 R /XYZ 259.764 607.0236 null]
+>> endobj
+4206 0 obj <<
+/D [4198 0 R /XYZ 71.731 566.0125 null]
+>> endobj
+4207 0 obj <<
+/D [4198 0 R /XYZ 89.6638 550.2366 null]
+>> endobj
+4208 0 obj <<
+/D [4198 0 R /XYZ 407.2684 550.2366 null]
+>> endobj
+4209 0 obj <<
+/D [4198 0 R /XYZ 71.731 483.3226 null]
+>> endobj
+4210 0 obj <<
+/D [4198 0 R /XYZ 71.731 468.3787 null]
+>> endobj
+4211 0 obj <<
+/D [4198 0 R /XYZ 76.7123 406.3761 null]
+>> endobj
+4212 0 obj <<
+/D [4198 0 R /XYZ 89.6638 388.4433 null]
+>> endobj
+4213 0 obj <<
+/D [4198 0 R /XYZ 71.731 386.2865 null]
+>> endobj
+4214 0 obj <<
+/D [4198 0 R /XYZ 89.6638 370.5106 null]
+>> endobj
+4215 0 obj <<
+/D [4198 0 R /XYZ 71.731 342.4509 null]
+>> endobj
+4216 0 obj <<
+/D [4198 0 R /XYZ 89.6638 326.675 null]
+>> endobj
+4217 0 obj <<
+/D [4198 0 R /XYZ 220.2822 274.8692 null]
+>> endobj
+4218 0 obj <<
+/D [4198 0 R /XYZ 71.731 267.7311 null]
+>> endobj
+4219 0 obj <<
+/D [4198 0 R /XYZ 71.731 238.9042 null]
+>> endobj
+1777 0 obj <<
+/D [4198 0 R /XYZ 71.731 205.9627 null]
+>> endobj
+742 0 obj <<
+/D [4198 0 R /XYZ 263.867 168.7472 null]
+>> endobj
+4220 0 obj <<
+/D [4198 0 R /XYZ 71.731 158.3822 null]
+>> endobj
+4221 0 obj <<
+/D [4198 0 R /XYZ 300.7046 122.7198 null]
+>> endobj
+4222 0 obj <<
+/D [4198 0 R /XYZ 96.7348 109.7684 null]
+>> endobj
+4197 0 obj <<
+/Font << /F33 1322 0 R /F23 1214 0 R /F44 2021 0 R /F27 1222 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+4225 0 obj <<
+/Length 2459
+/Filter /FlateDecode
+>>
+stream
+xڝ]6=Eي%Yu=lrM"(frhDCupUsa%9)G E<
+,ej8"FY3Gz$Ya&Idiȓ8ܥnX;xwg#:N7Ak#MU]|xhaߔlz)[,<t¹kn0Š9ʓcD6Q`{,qi[-#?顮pC/ ('[d5ss-1j-]pvڧƇO>|i%Xf?'M'Jɻ/M0n0Qiĵml>CZ=U)Qp]f`ET7+-5^ZaZ #iqpe-4U{9iX|?4:)*(7mE{Ys-}-sY"{p0dݍP5ǡ&ނd 7 K:YpT{e'e ƾ@e=Z&jn
+,&_%/t#cH+#Qgd"[Κ)[d<bVY<>8s<%8kU
+t3IjXZ`JJ9(Ȝ% Հ vh$wd1
+ՠ%Y9Z퟈C NRIT^9W~$?C}?fhz;-WTL[lא t΅
+F >CHJ%kQk3KwU8ŵ79djESw6
+ҵ#󞧾{1
+'rs!2fg{zN=^ Blz ;qq|@zim*TjA("; P3'>4
+0zByW u5>
+gR7Z;S!DeNU HbOW(+]^`Ofev`Y+s;G]FKz]ԦyrcJ/J̘Oo"Kd߶N I;\-
+N81j!̬ JR8ϱq֦Vasqj qÝSge5} 4˘%w;LI:x$v&,3/Ud23ɹޮPڣ]XԊ&QP@ m)t!T!QآB,!]CuD [Gw'6j뉸ӎ>-N ljK5,qCz3{e\dhViO
+endobj
+4224 0 obj <<
+/Type /Page
+/Contents 4225 0 R
+/Resources 4223 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 4242 0 R
+>> endobj
+4226 0 obj <<
+/D [4224 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+1778 0 obj <<
+/D [4224 0 R /XYZ 71.731 718.3063 null]
+>> endobj
+746 0 obj <<
+/D [4224 0 R /XYZ 209.3152 706.1179 null]
+>> endobj
+4227 0 obj <<
+/D [4224 0 R /XYZ 71.731 697.2951 null]
+>> endobj
+4228 0 obj <<
+/D [4224 0 R /XYZ 71.731 653.575 null]
+>> endobj
+4229 0 obj <<
+/D [4224 0 R /XYZ 71.731 620.6335 null]
+>> endobj
+4230 0 obj <<
+/D [4224 0 R /XYZ 71.731 583.9361 null]
+>> endobj
+4231 0 obj <<
+/D [4224 0 R /XYZ 71.731 577.574 null]
+>> endobj
+4232 0 obj <<
+/D [4224 0 R /XYZ 436.4724 553.0519 null]
+>> endobj
+4233 0 obj <<
+/D [4224 0 R /XYZ 169.3177 527.149 null]
+>> endobj
+4234 0 obj <<
+/D [4224 0 R /XYZ 176.5876 501.2462 null]
+>> endobj
+4235 0 obj <<
+/D [4224 0 R /XYZ 71.731 483.2139 null]
+>> endobj
+4236 0 obj <<
+/D [4224 0 R /XYZ 238.3949 470.362 null]
+>> endobj
+1779 0 obj <<
+/D [4224 0 R /XYZ 71.731 429.3509 null]
+>> endobj
+750 0 obj <<
+/D [4224 0 R /XYZ 200.1276 392.1353 null]
+>> endobj
+4237 0 obj <<
+/D [4224 0 R /XYZ 71.731 384.783 null]
+>> endobj
+4238 0 obj <<
+/D [4224 0 R /XYZ 71.731 338.9698 null]
+>> endobj
+4239 0 obj <<
+/D [4224 0 R /XYZ 71.731 310.2424 null]
+>> endobj
+4240 0 obj <<
+/D [4224 0 R /XYZ 71.731 310.2424 null]
+>> endobj
+1780 0 obj <<
+/D [4224 0 R /XYZ 71.731 232.3802 null]
+>> endobj
+754 0 obj <<
+/D [4224 0 R /XYZ 299.6652 197.9095 null]
+>> endobj
+4241 0 obj <<
+/D [4224 0 R /XYZ 71.731 189.272 null]
+>> endobj
+1781 0 obj <<
+/D [4224 0 R /XYZ 71.731 147.9967 null]
+>> endobj
+4223 0 obj <<
+/Font << /F33 1322 0 R /F23 1214 0 R /F27 1222 0 R /F35 1604 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+4245 0 obj <<
+/Length 1906
+/Filter /FlateDecode
+>>
+stream
+xڭ]o6=9hIM!Z}e"KD~#E)NCx
+g4(f*UH$DsݬYh5[fI4l8:b|\?teGSV_|]yjm8 nfnQ< C?g3̏8ժ1?C?R:^T
+`y,wExuA!BԸY/Vw^D "<. uz:v
+faQX/2M-"|m|EyV{' 2xtN"%lB}][}'%|7V&(-cD%
+9T[ hD0&1"۵QA P۶9m-F ٜhv D} X092o4}aYy%93s3UYjmǯgLMMɲF)
+
+BOrHV8( }ȅV#Б׊ t~0c/
+^N 9(;{ 8m?@Gq*s#;Xt@BLJ6*uo{M_{Yw)N- g|:sz~h[7&?R$) O-hj
+T.t̤&
+h,򆚪Q*9K>Ȇ7SE*MRsr휨Tv
+y,3@)0/^bQRIݨ,:\Ƞ}4!P><!rɬB.I~^Uc+Gpnl\Js&:\YÔ݌RL41<Iuv8j΀5!u?I]8֩N Ɍ썎QGILESBݎٜqDPu_n
+؄"(%M] ϾG ܛߢz)+ԦvO%8]Vt}%zhMm kќJQN?uMm}וj~j0̈<Oۏ$y{Z%RKʹy|Lc
+rrcF.M
+<=!/ц!&N3t60~Q(&>i>uqm#:zpaaGځEvxLuJ`՜֟~boIg*__e~湽IY#{A Oendstream
+endobj
+4244 0 obj <<
+/Type /Page
+/Contents 4245 0 R
+/Resources 4243 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 4242 0 R
+>> endobj
+4246 0 obj <<
+/D [4244 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+4247 0 obj <<
+/D [4244 0 R /XYZ 71.731 741.2204 null]
+>> endobj
+758 0 obj <<
+/D [4244 0 R /XYZ 364.5094 708.3437 null]
+>> endobj
+4248 0 obj <<
+/D [4244 0 R /XYZ 71.731 699.7062 null]
+>> endobj
+1782 0 obj <<
+/D [4244 0 R /XYZ 71.731 656.3737 null]
+>> endobj
+762 0 obj <<
+/D [4244 0 R /XYZ 295.6245 623.0635 null]
+>> endobj
+4249 0 obj <<
+/D [4244 0 R /XYZ 71.731 614.426 null]
+>> endobj
+1783 0 obj <<
+/D [4244 0 R /XYZ 71.731 558.142 null]
+>> endobj
+766 0 obj <<
+/D [4244 0 R /XYZ 378.198 524.8319 null]
+>> endobj
+4250 0 obj <<
+/D [4244 0 R /XYZ 71.731 516.1944 null]
+>> endobj
+1784 0 obj <<
+/D [4244 0 R /XYZ 71.731 472.8618 null]
+>> endobj
+770 0 obj <<
+/D [4244 0 R /XYZ 288.5111 439.5516 null]
+>> endobj
+4251 0 obj <<
+/D [4244 0 R /XYZ 71.731 430.9141 null]
+>> endobj
+1785 0 obj <<
+/D [4244 0 R /XYZ 71.731 389.6389 null]
+>> endobj
+774 0 obj <<
+/D [4244 0 R /XYZ 259.0781 354.2714 null]
+>> endobj
+4252 0 obj <<
+/D [4244 0 R /XYZ 71.731 345.6339 null]
+>> endobj
+4253 0 obj <<
+/D [4244 0 R /XYZ 71.731 304.3587 null]
+>> endobj
+1786 0 obj <<
+/D [4244 0 R /XYZ 71.731 271.4172 null]
+>> endobj
+778 0 obj <<
+/D [4244 0 R /XYZ 240.4757 238.1071 null]
+>> endobj
+4254 0 obj <<
+/D [4244 0 R /XYZ 71.731 229.4696 null]
+>> endobj
+1787 0 obj <<
+/D [4244 0 R /XYZ 71.731 179.1632 null]
+>> endobj
+782 0 obj <<
+/D [4244 0 R /XYZ 223.8447 136.0657 null]
+>> endobj
+4255 0 obj <<
+/D [4244 0 R /XYZ 71.731 123.8945 null]
+>> endobj
+1788 0 obj <<
+/D [4244 0 R /XYZ 71.731 112.3497 null]
+>> endobj
+4243 0 obj <<
+/Font << /F33 1322 0 R /F23 1214 0 R /F27 1222 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+4258 0 obj <<
+/Length 1932
+/Filter /FlateDecode
+>>
+stream
+xڕk6{E/s$X{ݣC+^m˵˲_?R'v8SER|+,p
+U hGYY0OB&Y2rJq<,fY~*ug{b}vGaOUbg?܏BxoTOuYqEfI@Q{o0ڏ՟ATEjr`?2_FUhZ^"y]-pF.jI:2 {='U1ziJ^wWRۄ)Pf+T97 #j^G~I4!M΀`l>aI"-w50%\,HzO'F#tGQ=t }Θ؀=/n"K=7wvsm[fҰ[FVCӡT8MYvY%w&d^RLmDۺDȾz{%0tdBđbQl
+ ;  9<X6CG:vF~TC] g{,9˚dU9cl%JgVRiAa*6,'<TjZ[6>7Xq6[ʭ_!t5ȉ mPw%YH#ާ.4@}:E6IJǼAt"eEv"ȂAiQ(cRV,'N;y姡%d um_i[I
+_QF.B6YS/ﵳ՞:ˬoTS_fzQL 4ZO}*
+(',N
+*s Cr<tӋ-y;3#_dl&4OK -?\=Ypendstream
+endobj
+4257 0 obj <<
+/Type /Page
+/Contents 4258 0 R
+/Resources 4256 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 4242 0 R
+>> endobj
+4259 0 obj <<
+/D [4257 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+4260 0 obj <<
+/D [4257 0 R /XYZ 71.731 741.2204 null]
+>> endobj
+786 0 obj <<
+/D [4257 0 R /XYZ 223.5692 707.8408 null]
+>> endobj
+4261 0 obj <<
+/D [4257 0 R /XYZ 71.731 700.4885 null]
+>> endobj
+4262 0 obj <<
+/D [4257 0 R /XYZ 282.4959 661.8134 null]
+>> endobj
+4263 0 obj <<
+/D [4257 0 R /XYZ 71.731 641.5893 null]
+>> endobj
+4264 0 obj <<
+/D [4257 0 R /XYZ 71.731 553.7945 null]
+>> endobj
+1789 0 obj <<
+/D [4257 0 R /XYZ 71.731 522.9103 null]
+>> endobj
+790 0 obj <<
+/D [4257 0 R /XYZ 185.7387 483.6375 null]
+>> endobj
+4265 0 obj <<
+/D [4257 0 R /XYZ 71.731 476.2852 null]
+>> endobj
+4266 0 obj <<
+/D [4257 0 R /XYZ 71.731 404.5691 null]
+>> endobj
+1790 0 obj <<
+/D [4257 0 R /XYZ 71.731 375.7422 null]
+>> endobj
+794 0 obj <<
+/D [4257 0 R /XYZ 331.4799 336.4694 null]
+>> endobj
+4267 0 obj <<
+/D [4257 0 R /XYZ 71.731 326.1044 null]
+>> endobj
+1791 0 obj <<
+/D [4257 0 R /XYZ 71.731 296.2553 null]
+>> endobj
+798 0 obj <<
+/D [4257 0 R /XYZ 229.9103 259.0397 null]
+>> endobj
+4268 0 obj <<
+/D [4257 0 R /XYZ 71.731 248.897 null]
+>> endobj
+4269 0 obj <<
+/D [4257 0 R /XYZ 101.1818 238.9152 null]
+>> endobj
+4270 0 obj <<
+/D [4257 0 R /XYZ 71.731 220.8829 null]
+>> endobj
+1792 0 obj <<
+/D [4257 0 R /XYZ 71.731 165.0274 null]
+>> endobj
+4256 0 obj <<
+/Font << /F33 1322 0 R /F23 1214 0 R /F27 1222 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+4273 0 obj <<
+/Length 2744
+/Filter /FlateDecode
+>>
+stream
+xڝks
+X>E|wNƙkN2ӡ$HB">.H$cX` 3~,$O*f;ov d _Yꦫ`5{Ϣ0tUΒ0pױ=|8fU+2='vK#5wo2ϳſ~|woEz״O#$NӋ$JEZHOvIpk:.LD}ǁ>e, 6H{eq
+/2}XRc#pڣơ.M% Ď7/? lE"bh &# cQdۭhžY"ZBB]~঱c~ fyY3#SYAFjDe! +v JaI`
+HWjnݡ 鷫i"wbǔThY24g#ƘS\Z vRiN~r`GAtd8C;=Ҵ'FeCCy !auy=h- iQ0m.,H(Pla5":G;t^;VQ /-Y:uYnRgMTE,@:hgj*@>&k۞v9D.AWʘk_!
+LOSn%gߖr!-i85KVlO#4TFp
+endobj
+4272 0 obj <<
+/Type /Page
+/Contents 4273 0 R
+/Resources 4271 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 4242 0 R
+>> endobj
+4274 0 obj <<
+/D [4272 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+802 0 obj <<
+/D [4272 0 R /XYZ 319.3549 705.7477 null]
+>> endobj
+4275 0 obj <<
+/D [4272 0 R /XYZ 71.731 693.3097 null]
+>> endobj
+4276 0 obj <<
+/D [4272 0 R /XYZ 270.8616 684.1886 null]
+>> endobj
+4277 0 obj <<
+/D [4272 0 R /XYZ 341.5855 640.3529 null]
+>> endobj
+4278 0 obj <<
+/D [4272 0 R /XYZ 89.9163 627.4015 null]
+>> endobj
+4279 0 obj <<
+/D [4272 0 R /XYZ 71.731 607.3119 null]
+>> endobj
+1793 0 obj <<
+/D [4272 0 R /XYZ 71.731 576.4277 null]
+>> endobj
+806 0 obj <<
+/D [4272 0 R /XYZ 256.2435 533.3303 null]
+>> endobj
+4280 0 obj <<
+/D [4272 0 R /XYZ 71.731 524.5074 null]
+>> endobj
+1794 0 obj <<
+/D [4272 0 R /XYZ 71.731 496.6628 null]
+>> endobj
+810 0 obj <<
+/D [4272 0 R /XYZ 258.989 459.4473 null]
+>> endobj
+4281 0 obj <<
+/D [4272 0 R /XYZ 71.731 452.095 null]
+>> endobj
+4282 0 obj <<
+/D [4272 0 R /XYZ 71.731 437.166 null]
+>> endobj
+4283 0 obj <<
+/D [4272 0 R /XYZ 71.731 432.1846 null]
+>> endobj
+4284 0 obj <<
+/D [4272 0 R /XYZ 81.6937 411.4274 null]
+>> endobj
+4285 0 obj <<
+/D [4272 0 R /XYZ 71.731 409.2706 null]
+>> endobj
+4286 0 obj <<
+/D [4272 0 R /XYZ 81.6937 398.476 null]
+>> endobj
+4287 0 obj <<
+/D [4272 0 R /XYZ 71.731 383.3677 null]
+>> endobj
+4288 0 obj <<
+/D [4272 0 R /XYZ 81.6937 372.5731 null]
+>> endobj
+4289 0 obj <<
+/D [4272 0 R /XYZ 71.731 370.4163 null]
+>> endobj
+4290 0 obj <<
+/D [4272 0 R /XYZ 81.6937 359.6217 null]
+>> endobj
+4291 0 obj <<
+/D [4272 0 R /XYZ 71.731 344.5134 null]
+>> endobj
+4292 0 obj <<
+/D [4272 0 R /XYZ 81.6937 333.7188 null]
+>> endobj
+4293 0 obj <<
+/D [4272 0 R /XYZ 71.731 331.562 null]
+>> endobj
+4294 0 obj <<
+/D [4272 0 R /XYZ 81.6937 320.7674 null]
+>> endobj
+4295 0 obj <<
+/D [4272 0 R /XYZ 71.731 305.6591 null]
+>> endobj
+4296 0 obj <<
+/D [4272 0 R /XYZ 81.6937 294.8645 null]
+>> endobj
+4297 0 obj <<
+/D [4272 0 R /XYZ 71.731 292.7077 null]
+>> endobj
+4298 0 obj <<
+/D [4272 0 R /XYZ 81.6937 281.9131 null]
+>> endobj
+4299 0 obj <<
+/D [4272 0 R /XYZ 71.731 266.8048 null]
+>> endobj
+4300 0 obj <<
+/D [4272 0 R /XYZ 81.6937 256.0102 null]
+>> endobj
+4301 0 obj <<
+/D [4272 0 R /XYZ 71.731 240.9019 null]
+>> endobj
+4302 0 obj <<
+/D [4272 0 R /XYZ 81.6937 230.1073 null]
+>> endobj
+1795 0 obj <<
+/D [4272 0 R /XYZ 71.731 222.9692 null]
+>> endobj
+814 0 obj <<
+/D [4272 0 R /XYZ 243.8395 185.7537 null]
+>> endobj
+4303 0 obj <<
+/D [4272 0 R /XYZ 71.731 178.4013 null]
+>> endobj
+4304 0 obj <<
+/D [4272 0 R /XYZ 71.731 158.491 null]
+>> endobj
+4305 0 obj <<
+/D [4272 0 R /XYZ 317.3926 134.745 null]
+>> endobj
+4306 0 obj <<
+/D [4272 0 R /XYZ 232.3467 121.7935 null]
+>> endobj
+4307 0 obj <<
+/D [4272 0 R /XYZ 71.731 119.6367 null]
+>> endobj
+4271 0 obj <<
+/Font << /F33 1322 0 R /F23 1214 0 R /F27 1222 0 R /F35 1604 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+4310 0 obj <<
+/Length 2969
+/Filter /FlateDecode
+>>
+stream
+xڝr>_*c4SJm*ڝ9bK5E*lW!Q僺h
+X C
+7<bmϺDCxsRe/*P]: a8ztz'ꕷ2̴Hʴ!D-*_xa"\7ìhy+E-k\{ZR5@&P#`7+JCqjU*K8N" U"1v,?ݐc?
+u09:H@h"Qa5Ce<q> / DAQb꣪+L[adT+r] =dh;Yt\`쳐dpa h^px2
+}:,j43O^qHy2
+x5xB{ %DnLlxUf*̔8 :#t=Kzi\-En8D*(Ua70LΎP˔j5
+:& *lV;wgrX$`]a
+vãi'=z큃)#&7wTVQ\~6nDt\{#>i*3 VC116U쾆 ~ h+"oW1!;V1Ctw.ў>$KH'UMG 021 0 #CG6;dBP-+%WqߑwB2xh!Z׍n_x=K[6[ݱit#z9+wʜ'X&HT++ʲWKWA.]z蠄Љ|7w;=s 0lA5'u%M,rNDD#.I5|uY6aVQó<LldYGQj
+ n7r8#_@/\3nbu뎐"i0 3 h<4!#,R-qLw8xPJ  +A  (;+'Ls9f<rW\etUDu7((W2m/ۼ`$wkn4FF=[n4m7ҍ&BbP\d \
+
+/B})bǷ4xD
+FjwFNY7<%?mǍD nya!xd ?FAoL{zHG⤱$cVLo, njK^pX:o1lq`= >77r0J27>!Z\=~}Lris-g6'>{grp}pqjm!X\@x>!(ԑcC($8(tQݬ<AUͿ&'D0Lm>p֣QmO.
+\
+qXy);FI Z?N C7~Je ۥ LX c7ZlE1aD1Z3(Uo0[եH~U$ә7gMj1c1ySQ׮z;$Cg*>J\S)x ihTb *mJ 4޳&i1~-9UAR(Krs3G9 6la>y7Pa ]DVTXE=FA,GR
+FG [ہm`Q3FVy8'!˦S3B:o.Q7kl5}9Bo*E Wpw w*o;e;4E(LvX;rP
+P :\ÄE`]) ,F8%m מⷍIԑoZRJ>Vgm$1]2}Ms [No'0q,)!0Dendstream
+endobj
+4309 0 obj <<
+/Type /Page
+/Contents 4310 0 R
+/Resources 4308 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 4242 0 R
+>> endobj
+4311 0 obj <<
+/D [4309 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+4312 0 obj <<
+/D [4309 0 R /XYZ 71.731 718.3063 null]
+>> endobj
+4313 0 obj <<
+/D [4309 0 R /XYZ 423.2461 708.3437 null]
+>> endobj
+4314 0 obj <<
+/D [4309 0 R /XYZ 71.731 657.1357 null]
+>> endobj
+4315 0 obj <<
+/D [4309 0 R /XYZ 205.0582 644.1843 null]
+>> endobj
+4316 0 obj <<
+/D [4309 0 R /XYZ 429.4863 644.1843 null]
+>> endobj
+4317 0 obj <<
+/D [4309 0 R /XYZ 71.731 611.1433 null]
+>> endobj
+4318 0 obj <<
+/D [4309 0 R /XYZ 475.4481 600.3487 null]
+>> endobj
+4319 0 obj <<
+/D [4309 0 R /XYZ 71.731 559.3376 null]
+>> endobj
+4320 0 obj <<
+/D [4309 0 R /XYZ 71.731 554.3562 null]
+>> endobj
+4321 0 obj <<
+/D [4309 0 R /XYZ 81.6937 533.599 null]
+>> endobj
+4322 0 obj <<
+/D [4309 0 R /XYZ 491.5075 533.599 null]
+>> endobj
+4323 0 obj <<
+/D [4309 0 R /XYZ 71.731 520.548 null]
+>> endobj
+4324 0 obj <<
+/D [4309 0 R /XYZ 81.6937 507.6961 null]
+>> endobj
+4325 0 obj <<
+/D [4309 0 R /XYZ 139.5162 494.7447 null]
+>> endobj
+4326 0 obj <<
+/D [4309 0 R /XYZ 71.731 492.5879 null]
+>> endobj
+4327 0 obj <<
+/D [4309 0 R /XYZ 81.6937 481.7933 null]
+>> endobj
+4328 0 obj <<
+/D [4309 0 R /XYZ 478.2916 481.7933 null]
+>> endobj
+4329 0 obj <<
+/D [4309 0 R /XYZ 71.731 466.685 null]
+>> endobj
+4330 0 obj <<
+/D [4309 0 R /XYZ 81.6937 455.8904 null]
+>> endobj
+4331 0 obj <<
+/D [4309 0 R /XYZ 373.716 455.8904 null]
+>> endobj
+4332 0 obj <<
+/D [4309 0 R /XYZ 71.731 453.7336 null]
+>> endobj
+4333 0 obj <<
+/D [4309 0 R /XYZ 81.6937 442.939 null]
+>> endobj
+4334 0 obj <<
+/D [4309 0 R /XYZ 511.1135 442.939 null]
+>> endobj
+4335 0 obj <<
+/D [4309 0 R /XYZ 71.731 427.8307 null]
+>> endobj
+4336 0 obj <<
+/D [4309 0 R /XYZ 71.731 412.8867 null]
+>> endobj
+4337 0 obj <<
+/D [4309 0 R /XYZ 71.731 375.4919 null]
+>> endobj
+4338 0 obj <<
+/D [4309 0 R /XYZ 339.0302 323.6862 null]
+>> endobj
+4339 0 obj <<
+/D [4309 0 R /XYZ 96.6374 297.7833 null]
+>> endobj
+4340 0 obj <<
+/D [4309 0 R /XYZ 276.3221 297.7833 null]
+>> endobj
+4341 0 obj <<
+/D [4309 0 R /XYZ 71.731 295.6265 null]
+>> endobj
+4342 0 obj <<
+/D [4309 0 R /XYZ 71.731 280.6825 null]
+>> endobj
+4343 0 obj <<
+/D [4309 0 R /XYZ 187.6784 271.1831 null]
+>> endobj
+4344 0 obj <<
+/D [4309 0 R /XYZ 71.731 219.9751 null]
+>> endobj
+4345 0 obj <<
+/D [4309 0 R /XYZ 184.7759 207.0237 null]
+>> endobj
+4346 0 obj <<
+/D [4309 0 R /XYZ 71.731 166.0125 null]
+>> endobj
+4347 0 obj <<
+/D [4309 0 R /XYZ 71.731 151.0686 null]
+>> endobj
+4308 0 obj <<
+/Font << /F33 1322 0 R /F23 1214 0 R /F44 2021 0 R /F27 1222 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+4350 0 obj <<
+/Length 1782
+/Filter /FlateDecode
+>>
+stream
+xڵXY6~ϯ0Re$QcR$E-ZldIՑ eWv-C9>E+~* D*as&q*>*`{}x\IWB&qJe(8V;[߇łƏ4};n1U>$43|S\03!#Zީb~=*.inj#oi# G iuw4S~+SDr*)bI1J2EYCs"(L̯Ppf޸+S!
+E=|W:6JI9 ]ֶԏ6<qfB`ZxhW4|jcδ*pw1 meSF#XF[)2ݹr-XPؘVG\˲=yhV"R{!u1vf8
+NP}frG0:@M=?qw#}Kvh;E[&e$4` 
+Rrc$܍?aU/9Չ؎A_,$jrԍ E;BzpQo&8B(wef
+Z-a%G`j,HDh77B? endstream
+endobj
+4349 0 obj <<
+/Type /Page
+/Contents 4350 0 R
+/Resources 4348 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 4242 0 R
+/Annots [ 4358 0 R ]
+>> endobj
+4358 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [461.7553 601.8581 521.8353 612.762]
+/Subtype /Link
+/A << /S /GoTo /D (groups) >>
+>> endobj
+4351 0 obj <<
+/D [4349 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+4352 0 obj <<
+/D [4349 0 R /XYZ 71.731 741.2204 null]
+>> endobj
+4353 0 obj <<
+/D [4349 0 R /XYZ 71.731 718.3063 null]
+>> endobj
+4354 0 obj <<
+/D [4349 0 R /XYZ 164.9444 708.3437 null]
+>> endobj
+4355 0 obj <<
+/D [4349 0 R /XYZ 368.7175 708.3437 null]
+>> endobj
+4356 0 obj <<
+/D [4349 0 R /XYZ 273.8015 695.3923 null]
+>> endobj
+1796 0 obj <<
+/D [4349 0 R /XYZ 71.731 688.2541 null]
+>> endobj
+818 0 obj <<
+/D [4349 0 R /XYZ 228.9919 651.0386 null]
+>> endobj
+4357 0 obj <<
+/D [4349 0 R /XYZ 71.731 643.6863 null]
+>> endobj
+1797 0 obj <<
+/D [4349 0 R /XYZ 71.731 584.9216 null]
+>> endobj
+822 0 obj <<
+/D [4349 0 R /XYZ 258.6885 547.7061 null]
+>> endobj
+4359 0 obj <<
+/D [4349 0 R /XYZ 71.731 540.3538 null]
+>> endobj
+4360 0 obj <<
+/D [4349 0 R /XYZ 406.4083 514.6301 null]
+>> endobj
+4361 0 obj <<
+/D [4349 0 R /XYZ 512.6778 514.6301 null]
+>> endobj
+1798 0 obj <<
+/D [4349 0 R /XYZ 71.731 481.5891 null]
+>> endobj
+826 0 obj <<
+/D [4349 0 R /XYZ 204.4744 444.3736 null]
+>> endobj
+4362 0 obj <<
+/D [4349 0 R /XYZ 71.731 437.0213 null]
+>> endobj
+4363 0 obj <<
+/D [4349 0 R /XYZ 71.731 417.1109 null]
+>> endobj
+4364 0 obj <<
+/D [4349 0 R /XYZ 308.5793 406.3163 null]
+>> endobj
+4365 0 obj <<
+/D [4349 0 R /XYZ 71.731 393.2653 null]
+>> endobj
+4366 0 obj <<
+/D [4349 0 R /XYZ 71.731 378.3214 null]
+>> endobj
+4367 0 obj <<
+/D [4349 0 R /XYZ 71.731 365.3699 null]
+>> endobj
+4368 0 obj <<
+/D [4349 0 R /XYZ 91.6563 347.5367 null]
+>> endobj
+4369 0 obj <<
+/D [4349 0 R /XYZ 71.731 337.4745 null]
+>> endobj
+4370 0 obj <<
+/D [4349 0 R /XYZ 71.731 323.4421 null]
+>> endobj
+4371 0 obj <<
+/D [4349 0 R /XYZ 91.6563 306.6899 null]
+>> endobj
+4372 0 obj <<
+/D [4349 0 R /XYZ 71.731 294.5704 null]
+>> endobj
+4373 0 obj <<
+/D [4349 0 R /XYZ 71.731 282.5953 null]
+>> endobj
+4374 0 obj <<
+/D [4349 0 R /XYZ 91.6563 265.8431 null]
+>> endobj
+4375 0 obj <<
+/D [4349 0 R /XYZ 71.731 253.7236 null]
+>> endobj
+4376 0 obj <<
+/D [4349 0 R /XYZ 71.731 241.7484 null]
+>> endobj
+4377 0 obj <<
+/D [4349 0 R /XYZ 91.6563 224.9962 null]
+>> endobj
+4378 0 obj <<
+/D [4349 0 R /XYZ 71.731 212.8768 null]
+>> endobj
+4379 0 obj <<
+/D [4349 0 R /XYZ 71.731 199.9253 null]
+>> endobj
+4380 0 obj <<
+/D [4349 0 R /XYZ 91.6563 184.1494 null]
+>> endobj
+4381 0 obj <<
+/D [4349 0 R /XYZ 71.731 172.03 null]
+>> endobj
+4382 0 obj <<
+/D [4349 0 R /XYZ 71.731 161.1358 null]
+>> endobj
+4383 0 obj <<
+/D [4349 0 R /XYZ 91.6563 143.3026 null]
+>> endobj
+4384 0 obj <<
+/D [4349 0 R /XYZ 71.731 131.1831 null]
+>> endobj
+4385 0 obj <<
+/D [4349 0 R /XYZ 71.731 118.2317 null]
+>> endobj
+4386 0 obj <<
+/D [4349 0 R /XYZ 91.6563 102.4558 null]
+>> endobj
+4348 0 obj <<
+/Font << /F33 1322 0 R /F27 1222 0 R /F23 1214 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+4389 0 obj <<
+/Length 1808
+/Filter /FlateDecode
+>>
+stream
+xڵXK60r zQ޲A#h-EDdɐ8_Pmy76Ap88/: fi >a.DʟaU" YnnD,y&,"%2Q(2dF:=)Gk5SjOW(y:-LGS(ttwOr_āO8S GsXacfHd ?VYNؾuh.UE(UDhC]clhCqƲTmkP3uu82gDeTסl4_Ӡm3A t] . i|.'MONQ{8 Y7%d,!6x^["6S
+SQ`4>9´ÊSwN5j{ 8KD&0x^+f5zIwI4`$M|
+!Eu}y{|؝RDsM>ݨ
+E.G.GL31 P^΃0X-`n`3fsaHdX5K TQhak ("Y: )xw#EԂM{$o3G塺HIgTS\G6@.)f.?E$dW"ׄo'6X7endstream
+endobj
+4388 0 obj <<
+/Type /Page
+/Contents 4389 0 R
+/Resources 4387 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 4417 0 R
+/Annots [ 4410 0 R ]
+>> endobj
+4410 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [477.0161 456.5853 523.8667 467.1676]
+/Subtype /Link
+/A << /S /GoTo /D (cust-change-permissions) >>
+>> endobj
+4390 0 obj <<
+/D [4388 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+1801 0 obj <<
+/D [4388 0 R /XYZ 71.731 741.2204 null]
+>> endobj
+4391 0 obj <<
+/D [4388 0 R /XYZ 71.731 718.3063 null]
+>> endobj
+4392 0 obj <<
+/D [4388 0 R /XYZ 71.731 706.1869 null]
+>> endobj
+4393 0 obj <<
+/D [4388 0 R /XYZ 91.6563 690.4109 null]
+>> endobj
+4394 0 obj <<
+/D [4388 0 R /XYZ 71.731 667.3973 null]
+>> endobj
+4395 0 obj <<
+/D [4388 0 R /XYZ 91.6563 649.5641 null]
+>> endobj
+4396 0 obj <<
+/D [4388 0 R /XYZ 71.731 637.4447 null]
+>> endobj
+4397 0 obj <<
+/D [4388 0 R /XYZ 71.731 624.4932 null]
+>> endobj
+4398 0 obj <<
+/D [4388 0 R /XYZ 91.6563 608.7173 null]
+>> endobj
+4399 0 obj <<
+/D [4388 0 R /XYZ 71.731 596.5978 null]
+>> endobj
+4400 0 obj <<
+/D [4388 0 R /XYZ 71.731 583.6464 null]
+>> endobj
+4401 0 obj <<
+/D [4388 0 R /XYZ 91.6563 567.8705 null]
+>> endobj
+4402 0 obj <<
+/D [4388 0 R /XYZ 71.731 555.751 null]
+>> endobj
+4403 0 obj <<
+/D [4388 0 R /XYZ 71.731 544.8569 null]
+>> endobj
+4404 0 obj <<
+/D [4388 0 R /XYZ 91.6563 527.0236 null]
+>> endobj
+4405 0 obj <<
+/D [4388 0 R /XYZ 71.731 516.9615 null]
+>> endobj
+4406 0 obj <<
+/D [4388 0 R /XYZ 71.731 501.9527 null]
+>> endobj
+4407 0 obj <<
+/D [4388 0 R /XYZ 91.6563 486.1768 null]
+>> endobj
+4408 0 obj <<
+/D [4388 0 R /XYZ 71.731 484.02 null]
+>> endobj
+4409 0 obj <<
+/D [4388 0 R /XYZ 71.731 469.076 null]
+>> endobj
+1799 0 obj <<
+/D [4388 0 R /XYZ 71.731 421.7185 null]
+>> endobj
+830 0 obj <<
+/D [4388 0 R /XYZ 275.2321 376.4642 null]
+>> endobj
+4411 0 obj <<
+/D [4388 0 R /XYZ 71.731 364.293 null]
+>> endobj
+1800 0 obj <<
+/D [4388 0 R /XYZ 71.731 327.219 null]
+>> endobj
+834 0 obj <<
+/D [4388 0 R /XYZ 174.0752 289.6299 null]
+>> endobj
+4412 0 obj <<
+/D [4388 0 R /XYZ 71.731 279.4872 null]
+>> endobj
+4413 0 obj <<
+/D [4388 0 R /XYZ 71.731 262.3672 null]
+>> endobj
+4414 0 obj <<
+/D [4388 0 R /XYZ 71.731 220.5889 null]
+>> endobj
+4415 0 obj <<
+/D [4388 0 R /XYZ 71.731 174.6959 null]
+>> endobj
+4416 0 obj <<
+/D [4388 0 R /XYZ 71.731 143.8118 null]
+>> endobj
+4387 0 obj <<
+/Font << /F33 1322 0 R /F27 1222 0 R /F23 1214 0 R /F44 2021 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+4420 0 obj <<
+/Length 2612
+/Filter /FlateDecode
+>>
+stream
+xڕn_!3}lP`X KY 桇!!'<C@]]]]]wx7.y7$|ȿoܛ#,H??˧ ɜ,㛧MNGMNyS͇Dޕ??˪R=ݓ=4
+'K9X/y onagBm[ݨ87;Jժo_ !IG$O5RyV<,~]0gzA?>eb8d筬 P ;YyJ 7^Œ}/
+ah#hdT3w#=U^myF%NW~em&Bj:;*|UXYA[AɇuOӓx?QԂoê,x;<TPX)^b8?wRxQ CZLr\5v۔ă>%$_8 VsF@1oVwM5eS f_M1d
+MI>i1LqQ
+Z3\,KBK߱VʲŮ[jYؓ]+hޞeeFp{\uc$WeL.
+9D v|1x :iG`rC,3"IZ$*Ifx$ {I|<N7)km66 x9Uɾ4ZUw=}Qi3l侞XtÚ%`AdfCo<V q# =q؂f|&Wj+zR +A[T(k$*x
+[ Ꮵ+{rBwU,xkZ-
+ɬ)1_n_PTR]qD gּRodJz~Y"3sfY (?ᅩzq)'8/q,P&I+ڦendstream
+endobj
+4419 0 obj <<
+/Type /Page
+/Contents 4420 0 R
+/Resources 4418 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 4417 0 R
+>> endobj
+4421 0 obj <<
+/D [4419 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+838 0 obj <<
+/D [4419 0 R /XYZ 165.3097 651.0386 null]
+>> endobj
+4422 0 obj <<
+/D [4419 0 R /XYZ 71.731 643.6863 null]
+>> endobj
+4423 0 obj <<
+/D [4419 0 R /XYZ 71.731 623.7759 null]
+>> endobj
+4424 0 obj <<
+/D [4419 0 R /XYZ 71.731 574.0275 null]
+>> endobj
+4425 0 obj <<
+/D [4419 0 R /XYZ 71.731 559.0835 null]
+>> endobj
+4426 0 obj <<
+/D [4419 0 R /XYZ 71.731 507.9751 null]
+>> endobj
+4427 0 obj <<
+/D [4419 0 R /XYZ 71.731 461.9826 null]
+>> endobj
+1802 0 obj <<
+/D [4419 0 R /XYZ 71.731 412.2342 null]
+>> endobj
+842 0 obj <<
+/D [4419 0 R /XYZ 211.4968 377.863 null]
+>> endobj
+4428 0 obj <<
+/D [4419 0 R /XYZ 71.731 369.2255 null]
+>> endobj
+4429 0 obj <<
+/D [4419 0 R /XYZ 71.731 312.9415 null]
+>> endobj
+4430 0 obj <<
+/D [4419 0 R /XYZ 71.731 269.1059 null]
+>> endobj
+4431 0 obj <<
+/D [4419 0 R /XYZ 71.731 238.2217 null]
+>> endobj
+4432 0 obj <<
+/D [4419 0 R /XYZ 71.731 207.3376 null]
+>> endobj
+1803 0 obj <<
+/D [4419 0 R /XYZ 71.731 189.4048 null]
+>> endobj
+846 0 obj <<
+/D [4419 0 R /XYZ 255.5989 156.0946 null]
+>> endobj
+4433 0 obj <<
+/D [4419 0 R /XYZ 71.731 147.4571 null]
+>> endobj
+4418 0 obj <<
+/Font << /F33 1322 0 R /F27 1222 0 R /F23 1214 0 R /F44 2021 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+4436 0 obj <<
+/Length 2072
+/Filter /FlateDecode
+>>
+stream
+xڝM~ڈX%[IW!٢(hDUq~}g8C)Pp87/vŋCR%EY(O)f-lC_?}9METIx=/iyV,ix~Y*AmVYD^~Z}WiT2O,[rdstd;.^@u[$YǫNտwTh}Q5 Ri:9VU`r T7jUn+ڊr-QqZHG3FƶqY篥1d2$ c=HT)+q3re%qGmF]nE-,9WbxYh#XQ]
+%9v~
+3jhV o:`f$~CE fac<ABlM'Keɿ,
+8]/Eo l6P1yҐmhXa oA!H9{w䳉k(>skfI
+S?Bg`
+8[[9oCq&cȈt<XTmJy9f96Md3a8{ʎp!Z×o4oBx*-8^ETMEgl:hj G"MT ރ93Hٛ<!$!2.]n:s3v^Ye!ZVfh9/R >FD/ۗIcşTL'o3qBȬ/>~$&wBWخqc_,0NRϳ#V6 zmda<,g[Q6"ĉ{Mt 2r}*iKJ
+endobj
+4435 0 obj <<
+/Type /Page
+/Contents 4436 0 R
+/Resources 4434 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 4417 0 R
+>> endobj
+4437 0 obj <<
+/D [4435 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+4438 0 obj <<
+/D [4435 0 R /XYZ 71.731 741.2204 null]
+>> endobj
+4439 0 obj <<
+/D [4435 0 R /XYZ 71.731 718.3063 null]
+>> endobj
+1804 0 obj <<
+/D [4435 0 R /XYZ 71.731 668.3288 null]
+>> endobj
+850 0 obj <<
+/D [4435 0 R /XYZ 159.5974 625.2314 null]
+>> endobj
+4440 0 obj <<
+/D [4435 0 R /XYZ 71.731 612.7934 null]
+>> endobj
+4441 0 obj <<
+/D [4435 0 R /XYZ 71.731 583.5826 null]
+>> endobj
+4442 0 obj <<
+/D [4435 0 R /XYZ 71.731 552.6984 null]
+>> endobj
+4443 0 obj <<
+/D [4435 0 R /XYZ 71.731 495.9114 null]
+>> endobj
+4444 0 obj <<
+/D [4435 0 R /XYZ 71.731 465.0272 null]
+>> endobj
+4445 0 obj <<
+/D [4435 0 R /XYZ 71.731 434.143 null]
+>> endobj
+4446 0 obj <<
+/D [4435 0 R /XYZ 71.731 403.2588 null]
+>> endobj
+4447 0 obj <<
+/D [4435 0 R /XYZ 71.731 359.4232 null]
+>> endobj
+1805 0 obj <<
+/D [4435 0 R /XYZ 71.731 315.5876 null]
+>> endobj
+854 0 obj <<
+/D [4435 0 R /XYZ 182.7004 272.4901 null]
+>> endobj
+4448 0 obj <<
+/D [4435 0 R /XYZ 71.731 260.0521 null]
+>> endobj
+4449 0 obj <<
+/D [4435 0 R /XYZ 71.731 209.9198 null]
+>> endobj
+4434 0 obj <<
+/Font << /F33 1322 0 R /F27 1222 0 R /F23 1214 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+4452 0 obj <<
+/Length 2522
+/Filter /FlateDecode
+>>
+stream
+xڥYY~I
+FW8 - QSn_ji4#HbǪjJ"YUj12IlfhSye)<)ӻ՛[;Ud̜&q>KQY٪}8],Mc/]U?rſV}s7m $S3grQϖڨ<֎"P(fL<7͸,ym$/*N'wiK FZ8ӳ$OUfLEp;7l5K"B/QMZڶm~t6۪^6< Ƚ3E {_0o|+B3mBM͆g>|:µ ͟Uaa-,U'%·oܼQz+26mIXRp)_A' co h^'!/jwd*٥͂eRv^Nż8~0KG?YGF{DqT tihzVнu j;B oV7Mؒd '-8Za>bڇ7Xjl eݺ^tb7PLzSbv 4,cD . @U
+[a=Ie9'=dM]!$K`@U}R<L"`3@g4͔1!,l~4S6rꙟKC)
+;>"Tk5-IC- <WQ.SF篠0QpE Ax!κAG.WcĆq؉S4M h\%ƥWM7r-lM\ڙQtLXZ\h l\z{7ػ~op&b!6T
+HOkcN+^
+{Nz&RڸXİ8Y/ȁ!X}F&72! MMc3a*zv$P`mK!ŎYc~U p"ȲîZmƼ)x M' O`L.:. g9E*״!m1G^Nv"8lHy8
+qW?Q0cym) &õf2BxӇY{&攰9 Ls<O9ZQG|
+{5c?]C]R470B6#r f!4 C"
+2Jܱ 8f+1 <xA 9e}1H\'h߻dV֔C+u
+"p((D⇦ y}[LCข˲"&uɕ Dg>x~IOri\K-cizH |wp@a #8rBmyD&%霿u)*N&'㜍$t'5$^Q*Hb֙WAX;* $e
++œ?m= W
+.%qpF >{pB!ZBh*Q
+gx|*߯^W^-Ƈ"|>e*I~d*y> \h,z/m)fendstream
+endobj
+4451 0 obj <<
+/Type /Page
+/Contents 4452 0 R
+/Resources 4450 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 4417 0 R
+/Annots [ 4457 0 R 4460 0 R ]
+>> endobj
+4457 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [137.4191 566.6638 191.7475 575.4349]
+/Subtype /Link
+/A << /S /GoTo /D (installation-whining) >>
+>> endobj
+4460 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [363.9329 516.7411 419.5802 527.2157]
+/Subtype /Link
+/A << /S /GoTo /D (installation-whining-cron) >>
+>> endobj
+4453 0 obj <<
+/D [4451 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+4454 0 obj <<
+/D [4451 0 R /XYZ 118.5554 689.7049 null]
+>> endobj
+4455 0 obj <<
+/D [4451 0 R /XYZ 118.5554 650.9517 null]
+>> endobj
+4456 0 obj <<
+/D [4451 0 R /XYZ 71.731 586.0949 null]
+>> endobj
+4458 0 obj <<
+/D [4451 0 R /XYZ 76.7123 551.2063 null]
+>> endobj
+4459 0 obj <<
+/D [4451 0 R /XYZ 71.731 531.281 null]
+>> endobj
+1806 0 obj <<
+/D [4451 0 R /XYZ 76.7123 490.0357 null]
+>> endobj
+858 0 obj <<
+/D [4451 0 R /XYZ 188.1488 450.6633 null]
+>> endobj
+4461 0 obj <<
+/D [4451 0 R /XYZ 71.731 443.311 null]
+>> endobj
+4462 0 obj <<
+/D [4451 0 R /XYZ 71.731 410.4492 null]
+>> endobj
+4463 0 obj <<
+/D [4451 0 R /XYZ 71.731 353.6621 null]
+>> endobj
+1807 0 obj <<
+/D [4451 0 R /XYZ 71.731 323.1516 null]
+>> endobj
+862 0 obj <<
+/D [4451 0 R /XYZ 243.7971 285.5624 null]
+>> endobj
+4464 0 obj <<
+/D [4451 0 R /XYZ 71.731 275.1974 null]
+>> endobj
+4465 0 obj <<
+/D [4451 0 R /XYZ 71.731 232.3969 null]
+>> endobj
+4466 0 obj <<
+/D [4451 0 R /XYZ 71.731 193.5426 null]
+>> endobj
+4467 0 obj <<
+/D [4451 0 R /XYZ 118.5554 154.9786 null]
+>> endobj
+4450 0 obj <<
+/Font << /F33 1322 0 R /F23 1214 0 R /F44 2021 0 R /F27 1222 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+4470 0 obj <<
+/Length 2750
+/Filter /FlateDecode
+>>
+stream
+xڍَ}'3o`'
+ #TN){ t }uʎ9^tgaBHݙ!t_naX8kg%),"DیB_.^LDzžI՝+8<
+rH @ES>IG&DT:JL}yJ̇L.%'C+5)"Rg~´D;M}ٳ.e1DsB'0|sm,Dn7X>;771oHqede(Z-?QBPFq'wE/p?ZZ5z)Fg'iы^h\rg`Gj! b i  bڗ-/"
+}ec7,c)4+O@mjy;0imsW31eLɲi|(_{JK)k|k>~uy8IDE
+d빏2pFqҵ8[gE?8~\ᷕ_w2
+2a/0e-BK$q@NN@Z&N`W7 [$9( !*Bx:I2UcVG
+XCH֖\,vBs@O!;M,WR+l8o1yC!tuv* ԇ)HR:PVP)y  uL7ddl
+T'VJQ΍p@nDčdTSjx_oi/?G[1`u{Ski;hYg?diRE?˿,~ 5\WMieuPA_̋?VQ4:
+RWRS#fv?C%endstream
+endobj
+4469 0 obj <<
+/Type /Page
+/Contents 4470 0 R
+/Resources 4468 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 4417 0 R
+/Annots [ 4479 0 R ]
+>> endobj
+4479 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [191.6402 347.0138 245.9362 357.9177]
+/Subtype /Link
+/A << /S /GoTo /D (list) >>
+>> endobj
+4471 0 obj <<
+/D [4469 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+4472 0 obj <<
+/D [4469 0 R /XYZ 71.731 718.3063 null]
+>> endobj
+4473 0 obj <<
+/D [4469 0 R /XYZ 71.731 675.3027 null]
+>> endobj
+4474 0 obj <<
+/D [4469 0 R /XYZ 71.731 623.497 null]
+>> endobj
+4475 0 obj <<
+/D [4469 0 R /XYZ 71.731 608.553 null]
+>> endobj
+1808 0 obj <<
+/D [4469 0 R /XYZ 71.731 536.1893 null]
+>> endobj
+866 0 obj <<
+/D [4469 0 R /XYZ 243.5245 496.8169 null]
+>> endobj
+4476 0 obj <<
+/D [4469 0 R /XYZ 71.731 486.4519 null]
+>> endobj
+4477 0 obj <<
+/D [4469 0 R /XYZ 71.731 443.6514 null]
+>> endobj
+4478 0 obj <<
+/D [4469 0 R /XYZ 71.731 412.7672 null]
+>> endobj
+4480 0 obj <<
+/D [4469 0 R /XYZ 71.731 348.01 null]
+>> endobj
+4481 0 obj <<
+/D [4469 0 R /XYZ 71.731 333.0661 null]
+>> endobj
+4482 0 obj <<
+/D [4469 0 R /XYZ 71.731 284.0149 null]
+>> endobj
+4483 0 obj <<
+/D [4469 0 R /XYZ 71.731 238.0225 null]
+>> endobj
+4484 0 obj <<
+/D [4469 0 R /XYZ 71.731 214.1769 null]
+>> endobj
+4485 0 obj <<
+/D [4469 0 R /XYZ 118.5554 175.6129 null]
+>> endobj
+1900 0 obj <<
+/D [4469 0 R /XYZ 71.731 133.572 null]
+>> endobj
+4468 0 obj <<
+/Font << /F33 1322 0 R /F27 1222 0 R /F23 1214 0 R /F44 2021 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+4488 0 obj <<
+/Length 676
+/Filter /FlateDecode
+>>
+stream
+x}TMo0@{2R7cjnUۃfA] f %83͌1hPp 3%Y\݆.`vk~s(Ùb*ڗs%( Ѿ+})1Iİuwx]7?lnKRKmGT`.2ISA@Ĕc%%)c*rטxӕ# {?YN#2!'O]n9َtzICNL
+n[
+endobj
+4487 0 obj <<
+/Type /Page
+/Contents 4488 0 R
+/Resources 4486 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 4417 0 R
+>> endobj
+4489 0 obj <<
+/D [4487 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+870 0 obj <<
+/D [4487 0 R /XYZ 266.3635 707.8408 null]
+>> endobj
+4490 0 obj <<
+/D [4487 0 R /XYZ 71.731 697.4758 null]
+>> endobj
+4491 0 obj <<
+/D [4487 0 R /XYZ 71.731 672.608 null]
+>> endobj
+4492 0 obj <<
+/D [4487 0 R /XYZ 71.731 657.6641 null]
+>> endobj
+4486 0 obj <<
+/Font << /F33 1322 0 R /F23 1214 0 R /F27 1222 0 R /F44 2021 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+4495 0 obj <<
+/Length 2064
+/Filter /FlateDecode
+>>
+stream
+xڍXͯ6_0ѳ$[֙ShɞvJ6׿H9Nf)!?EnBM"E2L6]9$DK]lvK!FiB-6}9Fow*I#qRU5'0:w*dP"LL!&7hj$Ld&J8Δ[c;A{︕vz0_i,
+VٯZb 1?|mǬڎN4si6R,Ўg`M0m*--MMIĿ?], l0;}#9󑦋,[hKo$P'i@z]۳
+;~i?n.Qe;Uw-'I(}fB*?XfU޴&RL'JCGv[Eaj˞4Qo>[:ޤq **]{*ƕ˟(T3wYek@,-`-^gnV?JJ\3z n+=/!@>)D7J4cHDsr@yZg
+`'K|<߳hKHmj\G-v:JTܖZS&IPڥ *kӱHML~0 HY ȟ@̞|"]Q[1loNt5s^^Tum/0Ck%ۋGk]xr
+
+B u\$;4@ wqA0h:v:0HM?yM{{4 |>,jS]5K܁C2(,rZ~{D98h5N8TM[CP9/?B8wrwOfOM~TN~UY00 [nt5 pw$?;3ahZTg ^+`m4.,!v@u-3 4oK,2u#0
+O^ +a0>ݶK9D5HD'*FFfnϰFSwCv hћ#\> #9GL#P4"X=H+0ԡ2OS%V+<ïVL.+Ѣg"<@4>>6Εzz˱uӃbTyUz4ejd(*L%#yE
+0DF%嫥'n ]:[n¿WJ*Qܻ֔c|%\HM0*tSizz; mGDmu&7b5@I@QʘhьH/r
+Vf>SǃU]V( v!wMjf92'`õc3Gx?ό) FE{92@}T5
+, ]h4:! KE5D.D0ۮ5Oȃ$ΒE w@$Y<.uiߩ1<jH2
+o[s`_U !r E <L=kUi%JB@RGYT}n̜8]o(wUqg+Tdo<j0V)di͒T7ݽrendstream
+endobj
+4494 0 obj <<
+/Type /Page
+/Contents 4495 0 R
+/Resources 4493 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 4521 0 R
+/Annots [ 4513 0 R ]
+>> endobj
+4513 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [389.6158 206.972 443.9118 217.876]
+/Subtype /Link
+/A << /S /GoTo /D (template-http-accept) >>
+>> endobj
+4496 0 obj <<
+/D [4494 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+1901 0 obj <<
+/D [4494 0 R /XYZ 71.731 718.3063 null]
+>> endobj
+874 0 obj <<
+/D [4494 0 R /XYZ 387.3898 703.236 null]
+>> endobj
+1902 0 obj <<
+/D [4494 0 R /XYZ 71.731 692.1839 null]
+>> endobj
+878 0 obj <<
+/D [4494 0 R /XYZ 267.8641 651.1593 null]
+>> endobj
+4497 0 obj <<
+/D [4494 0 R /XYZ 71.731 638.7213 null]
+>> endobj
+4498 0 obj <<
+/D [4494 0 R /XYZ 71.731 596.5591 null]
+>> endobj
+4499 0 obj <<
+/D [4494 0 R /XYZ 104.202 585.7645 null]
+>> endobj
+1903 0 obj <<
+/D [4494 0 R /XYZ 71.731 567.7322 null]
+>> endobj
+882 0 obj <<
+/D [4494 0 R /XYZ 220.0229 522.5774 null]
+>> endobj
+4500 0 obj <<
+/D [4494 0 R /XYZ 71.731 513.7546 null]
+>> endobj
+4501 0 obj <<
+/D [4494 0 R /XYZ 269.9659 488.0668 null]
+>> endobj
+4502 0 obj <<
+/D [4494 0 R /XYZ 71.731 477.3023 null]
+>> endobj
+4503 0 obj <<
+/D [4494 0 R /XYZ 81.6937 449.2923 null]
+>> endobj
+4504 0 obj <<
+/D [4494 0 R /XYZ 242.9373 449.2923 null]
+>> endobj
+4505 0 obj <<
+/D [4494 0 R /XYZ 71.731 447.1354 null]
+>> endobj
+4506 0 obj <<
+/D [4494 0 R /XYZ 81.6937 431.3595 null]
+>> endobj
+4507 0 obj <<
+/D [4494 0 R /XYZ 346.2678 431.3595 null]
+>> endobj
+4508 0 obj <<
+/D [4494 0 R /XYZ 81.6937 418.4081 null]
+>> endobj
+4509 0 obj <<
+/D [4494 0 R /XYZ 71.731 395.494 null]
+>> endobj
+4510 0 obj <<
+/D [4494 0 R /XYZ 71.731 362.453 null]
+>> endobj
+1904 0 obj <<
+/D [4494 0 R /XYZ 71.731 318.6174 null]
+>> endobj
+886 0 obj <<
+/D [4494 0 R /XYZ 303.1555 275.5199 null]
+>> endobj
+4511 0 obj <<
+/D [4494 0 R /XYZ 71.731 263.3487 null]
+>> endobj
+4512 0 obj <<
+/D [4494 0 R /XYZ 71.731 233.8712 null]
+>> endobj
+1905 0 obj <<
+/D [4494 0 R /XYZ 71.731 207.9683 null]
+>> endobj
+890 0 obj <<
+/D [4494 0 R /XYZ 308.5976 170.7528 null]
+>> endobj
+4514 0 obj <<
+/D [4494 0 R /XYZ 71.731 160.6101 null]
+>> endobj
+4515 0 obj <<
+/D [4494 0 R /XYZ 363.7058 150.6282 null]
+>> endobj
+4516 0 obj <<
+/D [4494 0 R /XYZ 219.3353 124.7254 null]
+>> endobj
+4517 0 obj <<
+/D [4494 0 R /XYZ 320.9613 124.7254 null]
+>> endobj
+4518 0 obj <<
+/D [4494 0 R /XYZ 71.731 111.7739 null]
+>> endobj
+4519 0 obj <<
+/D [4494 0 R /XYZ 157.2001 111.7739 null]
+>> endobj
+4520 0 obj <<
+/D [4494 0 R /XYZ 71.731 109.6171 null]
+>> endobj
+4493 0 obj <<
+/Font << /F23 1214 0 R /F27 1222 0 R /F35 1604 0 R /F33 1322 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+4524 0 obj <<
+/Length 2758
+/Filter /FlateDecode
+>>
+stream
+xڝY[:~_72Unmx,,u
+]Kӛvl#w8U=O)9nhߊLAI}\HuIm:;NSVЈfT4G ;/{=@ 62H/L) ;]~|Q(` S4o2'
++7G7Q;nAI#XhPFhAGxj۞
+&r+w"")'-6 knu}mGlm DZI!%_=$ɶgG 4癕/0^p)>j^Ԣ/̺/汥~oǸkKڂѾD{YN(`9v)Om]W45^Nuy&s)tAi4tۗ핛xgUJ,վq ]Rt w7kק$,ɠ>Ǯ:yi*K)>sRoL
+;o}<JDf\6N8lz"!I]Ȉ]r!%euV֜bDب O55k*2(
+,'rveIJǸVBA(݄lnWhJaOl
+([JW^k=LVEI.;
+fb'1MHz$u7!rɾ3J vEBfn RÝ[XM(9/<L݌
+0Lxl%haݱZ񱒼l _#|l𿲩5. 7l- 3Obg:Dn|
+mh&/6R 3p/Xth˹&szj4%,l
+J|WJ,~j'K
+o"clH#\N<'_ϽƫN3+Va3|
+endobj
+4523 0 obj <<
+/Type /Page
+/Contents 4524 0 R
+/Resources 4522 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 4521 0 R
+>> endobj
+4525 0 obj <<
+/D [4523 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+4526 0 obj <<
+/D [4523 0 R /XYZ 118.5554 689.7049 null]
+>> endobj
+4527 0 obj <<
+/D [4523 0 R /XYZ 165.5238 681.2406 null]
+>> endobj
+4528 0 obj <<
+/D [4523 0 R /XYZ 341.2842 669.5843 null]
+>> endobj
+1906 0 obj <<
+/D [4523 0 R /XYZ 71.731 636.0077 null]
+>> endobj
+894 0 obj <<
+/D [4523 0 R /XYZ 347.5336 603.6117 null]
+>> endobj
+4529 0 obj <<
+/D [4523 0 R /XYZ 71.731 593.2467 null]
+>> endobj
+4530 0 obj <<
+/D [4523 0 R /XYZ 71.731 550.4461 null]
+>> endobj
+4531 0 obj <<
+/D [4523 0 R /XYZ 412.638 539.6515 null]
+>> endobj
+4532 0 obj <<
+/D [4523 0 R /XYZ 111.2626 513.7487 null]
+>> endobj
+4533 0 obj <<
+/D [4523 0 R /XYZ 71.731 511.5918 null]
+>> endobj
+4534 0 obj <<
+/D [4523 0 R /XYZ 71.731 496.6479 null]
+>> endobj
+4535 0 obj <<
+/D [4523 0 R /XYZ 71.731 447.5967 null]
+>> endobj
+4536 0 obj <<
+/D [4523 0 R /XYZ 71.731 421.6939 null]
+>> endobj
+4537 0 obj <<
+/D [4523 0 R /XYZ 213.9555 408.7424 null]
+>> endobj
+4538 0 obj <<
+/D [4523 0 R /XYZ 71.731 406.5856 null]
+>> endobj
+4539 0 obj <<
+/D [4523 0 R /XYZ 71.731 391.6416 null]
+>> endobj
+4540 0 obj <<
+/D [4523 0 R /XYZ 134.9992 382.1422 null]
+>> endobj
+4541 0 obj <<
+/D [4523 0 R /XYZ 71.731 354.2468 null]
+>> endobj
+4542 0 obj <<
+/D [4523 0 R /XYZ 71.731 282.3515 null]
+>> endobj
+4543 0 obj <<
+/D [4523 0 R /XYZ 71.731 230.5458 null]
+>> endobj
+4544 0 obj <<
+/D [4523 0 R /XYZ 71.731 215.6018 null]
+>> endobj
+4545 0 obj <<
+/D [4523 0 R /XYZ 422.0761 206.1023 null]
+>> endobj
+4546 0 obj <<
+/D [4523 0 R /XYZ 176.1789 194.4461 null]
+>> endobj
+4547 0 obj <<
+/D [4523 0 R /XYZ 508.9315 194.4461 null]
+>> endobj
+4548 0 obj <<
+/D [4523 0 R /XYZ 76.7123 166.1522 null]
+>> endobj
+4549 0 obj <<
+/D [4523 0 R /XYZ 118.5554 122.6068 null]
+>> endobj
+4550 0 obj <<
+/D [4523 0 R /XYZ 135.3953 114.1424 null]
+>> endobj
+4551 0 obj <<
+/D [4523 0 R /XYZ 222.2315 114.1424 null]
+>> endobj
+4552 0 obj <<
+/D [4523 0 R /XYZ 433.1768 114.1424 null]
+>> endobj
+4522 0 obj <<
+/Font << /F33 1322 0 R /F23 1214 0 R /F44 2021 0 R /F35 1604 0 R /F48 2037 0 R /F27 1222 0 R /F32 1230 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+4555 0 obj <<
+/Length 3087
+/Filter /FlateDecode
+>>
+stream
+xڅk6{~~ Zm Mt^HP.h[T6_3$k9 Cr7
+/C?~Uu|\aËI6Lx}_~E* r?N*#H NFM^:65ݞ?xt~Yoza_ EȢ8-~V0hp*Onf'V]{F%xD&*)##$F) RV8*U4D " O =BSah6MgdW:a{l]so+*\rOU {jݸMG+]-zp,& ,b+^> `CROt|ZV4RŢ%U'k045|
+`黛ԧEZU^k.`3Z@ ddew`Ğ~pbG+U{$-({{n4} QʌU4Zx{zЪ(aȫaNMuOd_abZk"M^UCЬ3k=M%#Z RU# l D+ꔌiA>Rrjvl,=_{ԑ9IZfzhch+D
+ o?ڎx:uhd@+9_켸_3@7M5ي@bC jNJGG绿J
+Q0m%xݷRv
+3EK>)?)9DIao[I_hSrl !t@W ͒ \1DSqlzٚ}* 9FĠ)1]r jAzVɰ ·/*.0;z;%i֋x$
+^+-+Aw%0`B,\Ϗ5ず^5
+ Rǣj@mpVi0  /oa*[;*
+6v@7cq943:\r_y{Ji2!4ln
+핪 A[>z(|k5#)⓲4,ɇ턘maA=
+NKϦkfmGR`O!*mE:0fЗT"VYdddiʲ"ӥ]/u Qgz0`~
+O~p*S}k=Kh)>p@<=2
+
+32,;G
+R>3b˒OۼΊd{<~dHӗR
+q['f*Gyn?>E_D1! 0p~Z7n]M0 XM;[zxy°cgA vJz# 'gx
+VB\a͵=#l3r iwUۡOden? W}~üd
+8ed҅+֧sKp!գYEj~a[ˏQjr(ǟk"zG{y2x_P%tH x3x])cH᧸Ur)^wgi7zQ=)"o@XNxb>/uȽendstream
+endobj
+4554 0 obj <<
+/Type /Page
+/Contents 4555 0 R
+/Resources 4553 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 4521 0 R
+>> endobj
+4556 0 obj <<
+/D [4554 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+1907 0 obj <<
+/D [4554 0 R /XYZ 71.731 718.3063 null]
+>> endobj
+898 0 obj <<
+/D [4554 0 R /XYZ 267.2242 707.8408 null]
+>> endobj
+4557 0 obj <<
+/D [4554 0 R /XYZ 71.731 704.8712 null]
+>> endobj
+4558 0 obj <<
+/D [4554 0 R /XYZ 71.731 687.7355 null]
+>> endobj
+4559 0 obj <<
+/D [4554 0 R /XYZ 266.9195 667.3925 null]
+>> endobj
+4560 0 obj <<
+/D [4554 0 R /XYZ 71.731 639.4971 null]
+>> endobj
+4561 0 obj <<
+/D [4554 0 R /XYZ 419.4076 613.5942 null]
+>> endobj
+4562 0 obj <<
+/D [4554 0 R /XYZ 71.731 593.5047 null]
+>> endobj
+4563 0 obj <<
+/D [4554 0 R /XYZ 71.731 536.7176 null]
+>> endobj
+4564 0 obj <<
+/D [4554 0 R /XYZ 71.731 479.9306 null]
+>> endobj
+4565 0 obj <<
+/D [4554 0 R /XYZ 253.9215 469.136 null]
+>> endobj
+4566 0 obj <<
+/D [4554 0 R /XYZ 311.6869 456.1845 null]
+>> endobj
+1908 0 obj <<
+/D [4554 0 R /XYZ 71.731 436.0949 null]
+>> endobj
+902 0 obj <<
+/D [4554 0 R /XYZ 308.3972 398.8794 null]
+>> endobj
+4567 0 obj <<
+/D [4554 0 R /XYZ 71.731 388.7367 null]
+>> endobj
+4568 0 obj <<
+/D [4554 0 R /XYZ 366.7725 378.7549 null]
+>> endobj
+4569 0 obj <<
+/D [4554 0 R /XYZ 71.731 358.6653 null]
+>> endobj
+4570 0 obj <<
+/D [4554 0 R /XYZ 386.4974 334.9193 null]
+>> endobj
+4571 0 obj <<
+/D [4554 0 R /XYZ 71.731 314.8297 null]
+>> endobj
+4572 0 obj <<
+/D [4554 0 R /XYZ 380.2047 304.0351 null]
+>> endobj
+4573 0 obj <<
+/D [4554 0 R /XYZ 71.731 283.9455 null]
+>> endobj
+4574 0 obj <<
+/D [4554 0 R /XYZ 71.731 240.1099 null]
+>> endobj
+4575 0 obj <<
+/D [4554 0 R /XYZ 71.731 222.1771 null]
+>> endobj
+4576 0 obj <<
+/D [4554 0 R /XYZ 71.731 198.4311 null]
+>> endobj
+4577 0 obj <<
+/D [4554 0 R /XYZ 228.316 198.4311 null]
+>> endobj
+4578 0 obj <<
+/D [4554 0 R /XYZ 71.731 183.3228 null]
+>> endobj
+4579 0 obj <<
+/D [4554 0 R /XYZ 71.731 168.3789 null]
+>> endobj
+4580 0 obj <<
+/D [4554 0 R /XYZ 351.5704 158.8794 null]
+>> endobj
+4553 0 obj <<
+/Font << /F33 1322 0 R /F23 1214 0 R /F44 2021 0 R /F27 1222 0 R /F35 1604 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+4583 0 obj <<
+/Length 3194
+/Filter /FlateDecode
+>>
+stream
+xڕk
+w?$2JJ﮸(PE,6J9,y+Xᐜ'p_HC?QX-MOoB!Y jJÏQ<Qb[ԏQH#gq-6տqrK|n8mo37Sߛ3|#s U:5PД{TyƞY(oͱ.A=
+{pL1@)m?m;5@miAp>ؿ`f<̅ oQugɞR.CO3SE]#ÂUb? V8\-/._GqvY ?>MM}MqL-5Kf4_ր?c<4@^ԬLy 2 <pPc9NG^{0ٞh3[|,:~Еs[^Ƚs
+6M]v[
+"_ݾۢ~8waz&EH)3RW_@O4산{mcplѸ'0[Ӗzh[ Y=ڸ=Ly娡}"9*G]t,^;TVNuKz6cډ(8j6Y sgK
+Oj>
+B!2fvZ>nItiFSZ2(PW ~aʙ KqO"=5[R\Gpmd2r;tcˣՎa;hRed!*6Օ?{
+u(X3_LPXp8!DȢ1*GߤF}Ub,ȡ,jZO| [u޹K7Xg
+b3tFn33`p&m>1YEyt nj,C֘VwЈR=P-E'ӄˮC:~Dc;cbd%mdvKpxm&ާ['w/c k2$Y(^Debx+0I(;N,at]\qdpUE֝jX
+DyP!_YGI4QGNX;["r0::!H<HFC >$:;=ײUi*)*7Qp ǪÎnGL@rC++4i j=_G._ hmsj~ ?sWpE)(ɻX/2'vn (X`Idoȅ#GtV/D\{5[MS3C\!2<I}y?Tnva,J]Ga qE}/lc+ۭWb}I<`Ro:<}k% Kptjz G6c ycb|\p?s b|vgfw9.ܱ$5;L33O.?\
+7X.R4#]-PSk
++QɅA*3.Uq Bmm4`GEIxsjdQ<yr&GwS
+K(RLe:/@.GALX:% u
+넣{#ijIZ,Yvh:-xjAX#]W7b^jT:f֢W /AN+( Q/QI!rAh(``Gj'i_l*D )$ŋ GD
+^a9v]:z"r)[}&_fꞎ c?&$pӸcDܸg9 ѥ/8@PӴ)c6K8/"| ~(鏺4E9@{$l/N%~H1[EJfrɧRhw
+方wkzb&%LO\H\:C_UrXQakb~9H'XwЁ"sŒ]( ${eJFrp!!P,#
+^Q7e³?3"D[*EYv q+Ƚlswn?`^<a>Ȃ KʠDWY$rl#~Ngg;֨;FqcMgww(>\!lRIiJIyk ÝYi?I!6M2IZb@cRP3r28w~ľ7|v#a=<\2y,[\ދ!}N 3ʺKo? (j%S
+endobj
+4582 0 obj <<
+/Type /Page
+/Contents 4583 0 R
+/Resources 4581 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 4521 0 R
+>> endobj
+4584 0 obj <<
+/D [4582 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+4585 0 obj <<
+/D [4582 0 R /XYZ 71.731 718.3063 null]
+>> endobj
+4586 0 obj <<
+/D [4582 0 R /XYZ 154.7543 708.3437 null]
+>> endobj
+4587 0 obj <<
+/D [4582 0 R /XYZ 102.1666 695.3923 null]
+>> endobj
+1909 0 obj <<
+/D [4582 0 R /XYZ 71.731 689.0302 null]
+>> endobj
+906 0 obj <<
+/D [4582 0 R /XYZ 251.7299 651.0386 null]
+>> endobj
+4588 0 obj <<
+/D [4582 0 R /XYZ 71.731 640.8959 null]
+>> endobj
+4589 0 obj <<
+/D [4582 0 R /XYZ 71.731 623.7759 null]
+>> endobj
+4590 0 obj <<
+/D [4582 0 R /XYZ 71.731 623.7759 null]
+>> endobj
+4591 0 obj <<
+/D [4582 0 R /XYZ 71.731 605.8432 null]
+>> endobj
+4592 0 obj <<
+/D [4582 0 R /XYZ 71.731 605.8432 null]
+>> endobj
+4593 0 obj <<
+/D [4582 0 R /XYZ 71.731 562.0075 null]
+>> endobj
+4594 0 obj <<
+/D [4582 0 R /XYZ 71.731 562.0075 null]
+>> endobj
+4595 0 obj <<
+/D [4582 0 R /XYZ 253.5336 551.2129 null]
+>> endobj
+4596 0 obj <<
+/D [4582 0 R /XYZ 71.731 505.2205 null]
+>> endobj
+4597 0 obj <<
+/D [4582 0 R /XYZ 71.731 505.2205 null]
+>> endobj
+4598 0 obj <<
+/D [4582 0 R /XYZ 71.731 474.3363 null]
+>> endobj
+4599 0 obj <<
+/D [4582 0 R /XYZ 71.731 474.3363 null]
+>> endobj
+4600 0 obj <<
+/D [4582 0 R /XYZ 439.2249 463.5417 null]
+>> endobj
+4601 0 obj <<
+/D [4582 0 R /XYZ 191.1469 450.5903 null]
+>> endobj
+4602 0 obj <<
+/D [4582 0 R /XYZ 307.0556 450.5903 null]
+>> endobj
+4603 0 obj <<
+/D [4582 0 R /XYZ 71.731 437.6388 null]
+>> endobj
+4604 0 obj <<
+/D [4582 0 R /XYZ 71.731 430.5007 null]
+>> endobj
+4605 0 obj <<
+/D [4582 0 R /XYZ 71.731 430.5007 null]
+>> endobj
+4606 0 obj <<
+/D [4582 0 R /XYZ 71.731 373.7136 null]
+>> endobj
+4607 0 obj <<
+/D [4582 0 R /XYZ 71.731 373.7136 null]
+>> endobj
+4608 0 obj <<
+/D [4582 0 R /XYZ 71.731 342.8295 null]
+>> endobj
+4609 0 obj <<
+/D [4582 0 R /XYZ 71.731 342.8295 null]
+>> endobj
+4610 0 obj <<
+/D [4582 0 R /XYZ 71.731 273.091 null]
+>> endobj
+4611 0 obj <<
+/D [4582 0 R /XYZ 71.731 273.091 null]
+>> endobj
+4612 0 obj <<
+/D [4582 0 R /XYZ 210.674 262.2964 null]
+>> endobj
+4613 0 obj <<
+/D [4582 0 R /XYZ 137.0351 184.5878 null]
+>> endobj
+4614 0 obj <<
+/D [4582 0 R /XYZ 71.731 173.1856 null]
+>> endobj
+4615 0 obj <<
+/D [4582 0 R /XYZ 71.731 134.9739 null]
+>> endobj
+4616 0 obj <<
+/D [4582 0 R /XYZ 258.0065 122.122 null]
+>> endobj
+4581 0 obj <<
+/Font << /F33 1322 0 R /F27 1222 0 R /F35 1604 0 R /F23 1214 0 R /F32 1230 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+4619 0 obj <<
+/Length 2439
+/Filter /FlateDecode
+>>
+stream
+xڍk۶{~ \z+K 6[nE-6Y2$w^-q
+y^</jOR! AI̊+OydR,=z|˓ gza4 ,Vu{g2ywO0|U/#8L<wW]Kg
+|\kW6bG\w2,T<w0bݴ X4E=Fwi uɠOn+Ae::eFaxsP5(ZYnz[ۺ]97NW`ũl2%1XS^4fF6.Ab]*80`X-Lc^&3ڎ5׷EP762ɂNZ@%^Z~{C@ 6jC|^¢4k<nWn*Oѣpak-eHJNLaMh vAN{aFOIAG6 -*N"g]U[|$[0NZ̷YƉh`)
+c2Z4f!KW?:rc\9B) ͚G[zc~]ESUpF>.yޚN+u[A*|R ;RuFٷMɟc DUc$BIy
+ Bj]!M+q*GWOJB7
+#v=)ޅ`*,
+G[6 DIdgSBC#˅la ݾ78H)$lwx+{e^A_Z,ޮ-DfaWݲ ?0y`|ih<~4{9وY] RPw4{oqe#Eiv0chrl3Xb~N"$q3)HhNdPh=לQq$+$[[Q#<~wܛo^;ѽf7Wl)~\ qH`J˴0*EP߿a&>- Oua''
+mGmt͟bӷ$Ff0~oT-
+@d+}OQ,tmjp'Fe8ˇjcLM3eȚZgl9HL[iy`EJmLs`].->~jjk:o[lSiÀTʦ3X2"X<ŚjLW"c'Pˑ * pJv"*Y9mG2D;>ȭs7Á-@8lA<?US@y1~$-T | xPVC_uSe-VzU[D
+JiI(&s^x<:B h5)LVC.J4j$݃,k2Td<N5 J
+2IDpNj3vQSPwbe~K& [/B:6R.7}cg[~3CM)Ď qCуx,|n4wEEABNP $y1U0(\$'8 cQ<(n~2L8.@C8ꋓS3HPZ/2BkSq r1 qe)̴fy& Cd wUbK=ARUGFN^K<&~[)0M`vK_(0K0yIFYJCnnkKƊkmOvq |հN͆ 4jeRrtׁ~Ü{؇*܈ !
+JM-6; \_#w(Dib~aX؎W8S<åy^zd_@}aĀzD&lbtwd ?
+~5J\=͜ W8H&2/SI~ϒ5A
+V#%4m~ F7,endstream
+endobj
+4618 0 obj <<
+/Type /Page
+/Contents 4619 0 R
+/Resources 4617 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 4521 0 R
+>> endobj
+4620 0 obj <<
+/D [4618 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+4621 0 obj <<
+/D [4618 0 R /XYZ 394.4509 708.3437 null]
+>> endobj
+4622 0 obj <<
+/D [4618 0 R /XYZ 71.731 695.3923 null]
+>> endobj
+4623 0 obj <<
+/D [4618 0 R /XYZ 71.731 689.0302 null]
+>> endobj
+4624 0 obj <<
+/D [4618 0 R /XYZ 288.1288 677.4595 null]
+>> endobj
+4625 0 obj <<
+/D [4618 0 R /XYZ 111.0884 664.5081 null]
+>> endobj
+4626 0 obj <<
+/D [4618 0 R /XYZ 325.6187 664.5081 null]
+>> endobj
+4627 0 obj <<
+/D [4618 0 R /XYZ 71.731 644.4185 null]
+>> endobj
+4628 0 obj <<
+/D [4618 0 R /XYZ 263.4371 633.6239 null]
+>> endobj
+4629 0 obj <<
+/D [4618 0 R /XYZ 71.731 620.6725 null]
+>> endobj
+4630 0 obj <<
+/D [4618 0 R /XYZ 100.4128 607.721 null]
+>> endobj
+4631 0 obj <<
+/D [4618 0 R /XYZ 71.731 587.6314 null]
+>> endobj
+4632 0 obj <<
+/D [4618 0 R /XYZ 71.731 564.7174 null]
+>> endobj
+4633 0 obj <<
+/D [4618 0 R /XYZ 71.731 520.1844 null]
+>> endobj
+4634 0 obj <<
+/D [4618 0 R /XYZ 71.731 475.6514 null]
+>> endobj
+1910 0 obj <<
+/D [4618 0 R /XYZ 71.731 436.0997 null]
+>> endobj
+910 0 obj <<
+/D [4618 0 R /XYZ 461.4838 398.8842 null]
+>> endobj
+4635 0 obj <<
+/D [4618 0 R /XYZ 71.731 388.5192 null]
+>> endobj
+4636 0 obj <<
+/D [4618 0 R /XYZ 71.731 352.8568 null]
+>> endobj
+1911 0 obj <<
+/D [4618 0 R /XYZ 71.731 324.8618 null]
+>> endobj
+914 0 obj <<
+/D [4618 0 R /XYZ 402.8496 279.7071 null]
+>> endobj
+4637 0 obj <<
+/D [4618 0 R /XYZ 71.731 275.8768 null]
+>> endobj
+4638 0 obj <<
+/D [4618 0 R /XYZ 118.5554 233.6864 null]
+>> endobj
+4639 0 obj <<
+/D [4618 0 R /XYZ 71.731 179.9891 null]
+>> endobj
+4640 0 obj <<
+/D [4618 0 R /XYZ 71.731 129.2994 null]
+>> endobj
+4641 0 obj <<
+/D [4618 0 R /XYZ 271.0004 116.4475 null]
+>> endobj
+4617 0 obj <<
+/Font << /F33 1322 0 R /F27 1222 0 R /F35 1604 0 R /F23 1214 0 R /F44 2021 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+4644 0 obj <<
+/Length 2064
+/Filter /FlateDecode
+>>
+stream
+xڵk۸{~.bzEMC
+ ESEl$ѡ+P^"h83 EUĪa'8ծ}@` l.ܽxM|u_Q&iR$Lۣ< ʮ7qyH߷c?V!ތu?~,)ªoza\\hmJTpUMG(USv q +˪~z7u߯EVveAH?Gtn{e(1`?"(FTTz =qlKҊOҍB6 3N1<vI*"*;qgVu]jjW ygho~awLvWt;KHaՍym7=Pc@%^ 3|lMBu7:\
+䙯PDތtB>V 6WY!X6oxj( ]$>.EPkvp.Bby6`N#N8,6"Xz{%}'%#rTP{)s._dVC|\ȑ~VGIUQ;i~l\8Jc/JZ:O"`x>VG{
+PaV/Xb=.HY]Qd K*@Uv=m5{{{( S\QFvvJM ᠪLVG`B)à Qc\2K? #ldK9H_D-pc)<Vfo--?}O'5pj`M B"D6
+^ClB@>tWx%3G
+[:(e0{\d,=6
+;|hd]^IlhaJs%p=v|-;^^^rmӧOL_Vx@\0GPc YF,Uq<sHzz 58NJ%$ - >5r`ҡ
++"I#rPc,S-!Q<C
+ CU0(.Ƙ|odc*bί}KI)@-=>gp~>Ҋ
+k((,I:=3nLS/Q]y;|:o9oM3X4-Rg%y#qa$j哿._ehendstream
+endobj
+4643 0 obj <<
+/Type /Page
+/Contents 4644 0 R
+/Resources 4642 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 4521 0 R
+>> endobj
+4645 0 obj <<
+/D [4643 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+4646 0 obj <<
+/D [4643 0 R /XYZ 344.4787 708.3437 null]
+>> endobj
+4647 0 obj <<
+/D [4643 0 R /XYZ 71.731 690.3114 null]
+>> endobj
+4648 0 obj <<
+/D [4643 0 R /XYZ 389.061 664.5081 null]
+>> endobj
+4649 0 obj <<
+/D [4643 0 R /XYZ 118.6881 651.5566 null]
+>> endobj
+4650 0 obj <<
+/D [4643 0 R /XYZ 411.7689 651.5566 null]
+>> endobj
+4651 0 obj <<
+/D [4643 0 R /XYZ 71.731 631.4671 null]
+>> endobj
+4652 0 obj <<
+/D [4643 0 R /XYZ 403.6536 607.721 null]
+>> endobj
+4653 0 obj <<
+/D [4643 0 R /XYZ 71.731 582.6501 null]
+>> endobj
+4654 0 obj <<
+/D [4643 0 R /XYZ 71.731 508.1296 null]
+>> endobj
+4655 0 obj <<
+/D [4643 0 R /XYZ 477.6839 484.3836 null]
+>> endobj
+4656 0 obj <<
+/D [4643 0 R /XYZ 71.731 451.3425 null]
+>> endobj
+4657 0 obj <<
+/D [4643 0 R /XYZ 71.731 389.5742 null]
+>> endobj
+4658 0 obj <<
+/D [4643 0 R /XYZ 71.731 268.4285 null]
+>> endobj
+4659 0 obj <<
+/D [4643 0 R /XYZ 71.731 245.5144 null]
+>> endobj
+4642 0 obj <<
+/Font << /F33 1322 0 R /F27 1222 0 R /F35 1604 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+4662 0 obj <<
+/Length 1237
+/Filter /FlateDecode
+>>
+stream
+xڍVmo6 _aS,K?뀥p>lC᜕;!u_?R/iukP(>:d'RRGBF%"KBˆ}6^o/.kԢ.Tlo<+uZ*ٶoqjLo0}sÞ 7u/F ;{=P*\ΚUXh\tth&b$!D@WC3M'2evMZ3go l߸v!|'&Ίiv@v:JF'6K %2i͋#)/>|W~ZAv<m5)+T
+</b_Џq@@<
+@? U5QX>wYmXFo9`NUL@ԜMӶ&XA\ *&Slw$蚹]f8ws,<!!XXU_ [\nGT:X$|42Pā$&JbB*8LvtI֨ݹ@<|#v|ơnP‰WW w?pȭ0L8ۮa{P[y)>M(8(#`ٖLT&JKkTԺi%SX)IGzcwhC O7:xMp-WoVx GTEzP¡Ȇ @5< @5نY}7'&P<ҥXw 4؈(fi&8
+endobj
+4661 0 obj <<
+/Type /Page
+/Contents 4662 0 R
+/Resources 4660 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 4670 0 R
+>> endobj
+4663 0 obj <<
+/D [4661 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+4664 0 obj <<
+/D [4661 0 R /XYZ 71.731 693.2354 null]
+>> endobj
+4665 0 obj <<
+/D [4661 0 R /XYZ 118.5554 654.6714 null]
+>> endobj
+4666 0 obj <<
+/D [4661 0 R /XYZ 211.9919 646.207 null]
+>> endobj
+4667 0 obj <<
+/D [4661 0 R /XYZ 71.731 601.0818 null]
+>> endobj
+1912 0 obj <<
+/D [4661 0 R /XYZ 71.731 574.2377 null]
+>> endobj
+918 0 obj <<
+/D [4661 0 R /XYZ 449.6052 531.1402 null]
+>> endobj
+4668 0 obj <<
+/D [4661 0 R /XYZ 71.731 518.7022 null]
+>> endobj
+4669 0 obj <<
+/D [4661 0 R /XYZ 120.1486 483.6782 null]
+>> endobj
+4660 0 obj <<
+/Font << /F33 1322 0 R /F27 1222 0 R /F23 1214 0 R /F44 2021 0 R /F35 1604 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+4673 0 obj <<
+/Length 2440
+/Filter /FlateDecode
+>>
+stream
+xڝk8``XOvq7EQ±X2/)RN\Eŧ#WU"E2H`uOo$q,T$cX,n#,Uj{OJT (VIT]ۚm*I=7i6*Xq6ڡj &Ld1j rGꁍL׽)68V62Zoq50f,_|?SuNVmg9L֟nlcNfAK8פ2p"F^s>'<6T*E2t}c$QUU "
+ٔawj!F>M J"2mHEr,NQYyAkhbU
+LbGLe!dVTΒnȹ<. Z wD+y^S>Dگ; ) 8Yt[n+@(OR oG }UX;wDn;9xI_XMplеǦ*/lZ3k9"6EǶ >\<
+ *PHݽxHc<HFZiPؙU§9љi뜗ЪO ;&b'qGo>OYPHD!8af"ͲL9m(Q^3^x_}@H
+xWH5lFFWُ.Y `"`yIemZ0+Rԝ^V B!+zs|@+8v31yQw!9z\JZ m\/F@Kk)%O w\NN#,-mQ]% ==zQtvihj{ ),Sks`9iG.ba0 ddۄA<_*2awf(vC; C[fB:aE*\MBn,?b+
+`xߥ
+A| ̭dx/:^B@ {o 0#88v9ids8G'oK5.S6[Xaj8qEkkF)\UE" M+}XiKw<fW
+'C0ӢՌW>Gf౫eM~`S+tZP@12۪:WL$(Io3N8aGYuP~08y ͊J4)9B-s )4vxj kx; ӱ^$Q`tfSz*ռѽ>
+řC=,u+[坘1#4E?TЯM3*Դ#.Gq!YK1®jv7UΛ\zd e(ƿ殼ByW=2,|xHe`a"Ow> q"+UU0GmB{(GL~g/s@5ec8
+x
+HPg7Co'4ӗiĠ9s7iC:b0qW D[]  ]Xym7c 'ND]{GoZ"/[C"hq_hoh>|dj/Rn=aˉa@5a[c\a͇oZzIAࡳًmYBNM!viWW.Cxt ^󦿜u4p-s5&$鷚0>0^z$+ Z_9RPeMY07 Xendstream
+endobj
+4672 0 obj <<
+/Type /Page
+/Contents 4673 0 R
+/Resources 4671 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 4670 0 R
+/Annots [ 4680 0 R 4681 0 R ]
+>> endobj
+4680 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [234.5514 546.7459 281.8882 557.6498]
+/Subtype /Link
+/A << /S /GoTo /D (installation) >>
+>> endobj
+4681 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [354.758 546.7459 402.0948 557.6498]
+/Subtype /Link
+/A << /S /GoTo /D (configuration) >>
+>> endobj
+4674 0 obj <<
+/D [4672 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+1913 0 obj <<
+/D [4672 0 R /XYZ 71.731 718.3063 null]
+>> endobj
+922 0 obj <<
+/D [4672 0 R /XYZ 358.6963 703.236 null]
+>> endobj
+4675 0 obj <<
+/D [4672 0 R /XYZ 71.731 681.8546 null]
+>> endobj
+1914 0 obj <<
+/D [4672 0 R /XYZ 71.731 658.3912 null]
+>> endobj
+926 0 obj <<
+/D [4672 0 R /XYZ 233.1753 615.2938 null]
+>> endobj
+4676 0 obj <<
+/D [4672 0 R /XYZ 71.731 606.4709 null]
+>> endobj
+4677 0 obj <<
+/D [4672 0 R /XYZ 146.6603 593.7346 null]
+>> endobj
+4678 0 obj <<
+/D [4672 0 R /XYZ 441.3262 580.7832 null]
+>> endobj
+4679 0 obj <<
+/D [4672 0 R /XYZ 71.731 560.6936 null]
+>> endobj
+4682 0 obj <<
+/D [4672 0 R /XYZ 82.1385 523.9961 null]
+>> endobj
+4683 0 obj <<
+/D [4672 0 R /XYZ 71.731 490.9551 null]
+>> endobj
+4684 0 obj <<
+/D [4672 0 R /XYZ 430.9687 467.2091 null]
+>> endobj
+4685 0 obj <<
+/D [4672 0 R /XYZ 71.731 454.2576 null]
+>> endobj
+4686 0 obj <<
+/D [4672 0 R /XYZ 468.5487 428.3548 null]
+>> endobj
+1915 0 obj <<
+/D [4672 0 R /XYZ 71.731 421.2166 null]
+>> endobj
+930 0 obj <<
+/D [4672 0 R /XYZ 121.4833 355.7391 null]
+>> endobj
+4687 0 obj <<
+/D [4672 0 R /XYZ 71.731 343.3011 null]
+>> endobj
+4688 0 obj <<
+/D [4672 0 R /XYZ 149.514 334.1799 null]
+>> endobj
+4689 0 obj <<
+/D [4672 0 R /XYZ 252.2636 334.1799 null]
+>> endobj
+4690 0 obj <<
+/D [4672 0 R /XYZ 71.731 309.109 null]
+>> endobj
+4691 0 obj <<
+/D [4672 0 R /XYZ 71.731 309.109 null]
+>> endobj
+1916 0 obj <<
+/D [4672 0 R /XYZ 71.731 241.641 null]
+>> endobj
+934 0 obj <<
+/D [4672 0 R /XYZ 207.49 175.3874 null]
+>> endobj
+4692 0 obj <<
+/D [4672 0 R /XYZ 71.731 166.5646 null]
+>> endobj
+4693 0 obj <<
+/D [4672 0 R /XYZ 71.731 151.6714 null]
+>> endobj
+4694 0 obj <<
+/D [4672 0 R /XYZ 71.731 146.6901 null]
+>> endobj
+4695 0 obj <<
+/D [4672 0 R /XYZ 89.6638 125.9328 null]
+>> endobj
+4696 0 obj <<
+/D [4672 0 R /XYZ 89.6638 100.03 null]
+>> endobj
+4697 0 obj <<
+/D [4672 0 R /XYZ 71.731 97.8731 null]
+>> endobj
+4671 0 obj <<
+/Font << /F23 1214 0 R /F27 1222 0 R /F35 1604 0 R /F32 1230 0 R /F60 2540 0 R /F33 1322 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+4700 0 obj <<
+/Length 1744
+/Filter /FlateDecode
+>>
+stream
+xko6{~? D4V,YC;X$z$yJؾ#y;(y#q
+?m?'%) YG_ሶac7SM#UQ%^UW#y5nYj),>-F2S9O5G9k|UtM{>k{4-f.un1j 
+p>v?Mﴐz{rߣ``+6rNl(F:'&Nˋ˳i]*sjQ-9 v}
+&+ed+%29c'wPZ'O#jS.}C5H m'6+*wi7“YvF.dmhk_WAY⥒ CoDղ][zyz/dkenJAhZ
+cNkdZZj W`8i\~yGk<k0!d$)A
+;4
+:-Rd9޸?v<`S vyJ`JoǞ^r(Kg+^.ŶK[gFuӱiJmqeg|2&]a8&uת8L m4$[J<x/a[>8ME%9 E=VR=
+ؘendstream
+endobj
+4699 0 obj <<
+/Type /Page
+/Contents 4700 0 R
+/Resources 4698 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 4670 0 R
+>> endobj
+4701 0 obj <<
+/D [4699 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+4702 0 obj <<
+/D [4699 0 R /XYZ 89.6638 708.3437 null]
+>> endobj
+1917 0 obj <<
+/D [4699 0 R /XYZ 71.731 688.2541 null]
+>> endobj
+938 0 obj <<
+/D [4699 0 R /XYZ 370.3296 645.1566 null]
+>> endobj
+4703 0 obj <<
+/D [4699 0 R /XYZ 71.731 632.7186 null]
+>> endobj
+4704 0 obj <<
+/D [4699 0 R /XYZ 71.731 611.478 null]
+>> endobj
+4705 0 obj <<
+/D [4699 0 R /XYZ 71.731 556.0608 null]
+>> endobj
+4706 0 obj <<
+/D [4699 0 R /XYZ 139.5762 544.0956 null]
+>> endobj
+4707 0 obj <<
+/D [4699 0 R /XYZ 71.731 531.9762 null]
+>> endobj
+4708 0 obj <<
+/D [4699 0 R /XYZ 71.731 464.7099 null]
+>> endobj
+4709 0 obj <<
+/D [4699 0 R /XYZ 71.731 442.8753 null]
+>> endobj
+4710 0 obj <<
+/D [4699 0 R /XYZ 71.731 373.5518 null]
+>> endobj
+1918 0 obj <<
+/D [4699 0 R /XYZ 71.731 355.0148 null]
+>> endobj
+942 0 obj <<
+/D [4699 0 R /XYZ 374.4611 311.5437 null]
+>> endobj
+4711 0 obj <<
+/D [4699 0 R /XYZ 71.731 299.3725 null]
+>> endobj
+4712 0 obj <<
+/D [4699 0 R /XYZ 402.9907 289.9846 null]
+>> endobj
+4713 0 obj <<
+/D [4699 0 R /XYZ 71.731 264.9137 null]
+>> endobj
+4714 0 obj <<
+/D [4699 0 R /XYZ 71.731 227.5188 null]
+>> endobj
+4715 0 obj <<
+/D [4699 0 R /XYZ 175.6818 214.5674 null]
+>> endobj
+4716 0 obj <<
+/D [4699 0 R /XYZ 395.942 214.5674 null]
+>> endobj
+4717 0 obj <<
+/D [4699 0 R /XYZ 486.8069 214.5674 null]
+>> endobj
+4718 0 obj <<
+/D [4699 0 R /XYZ 71.731 201.6159 null]
+>> endobj
+4719 0 obj <<
+/D [4699 0 R /XYZ 71.731 188.6645 null]
+>> endobj
+4720 0 obj <<
+/D [4699 0 R /XYZ 107.0481 188.6645 null]
+>> endobj
+1919 0 obj <<
+/D [4699 0 R /XYZ 71.731 181.5264 null]
+>> endobj
+946 0 obj <<
+/D [4699 0 R /XYZ 496.414 138.4289 null]
+>> endobj
+4721 0 obj <<
+/D [4699 0 R /XYZ 71.731 125.9909 null]
+>> endobj
+4722 0 obj <<
+/D [4699 0 R /XYZ 206.804 116.8697 null]
+>> endobj
+4698 0 obj <<
+/Font << /F33 1322 0 R /F27 1222 0 R /F23 1214 0 R /F35 1604 0 R /F32 1230 0 R /F60 2540 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+4725 0 obj <<
+/Length 2096
+/Filter /FlateDecode
+>>
+stream
+xڭXݏ)Q} M@EQ@۴-, g(KwC9 3?)W!*"rVˇpu?̲a͔QEd|\J\f*ȴVχOMcCeQ: }Z:A=J۝/YG3y#׭*f"ԙ|M$ҁ)G\g75ۖ<P{gs;LIC}_^l՛µk=4p(KCzGtQ"t"',ӱYVȽڌ Dev3Su
+:ʑoPă-+Ad8\rV-tfFci۷]Hzzo<pT DhUS@Fx+cF&
+i<ْp%(G("~+",'CmҍhfBo19F/>J( Ͽt'*:'3϶Y>&XJ5o-L[w>BLo7s%(>XO<h: + *Ӆ,;POٟ#10]I*T)|}ï9AC==fB%Xot!4dFI_BxUh _c~DwtYy^2@^:-o&E¡1(G_ϴrݾBe%JB.iog#ndB#?O$m?}*Qj՛ow+Nakendstream
+endobj
+4724 0 obj <<
+/Type /Page
+/Contents 4725 0 R
+/Resources 4723 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 4670 0 R
+/Annots [ 4749 0 R ]
+>> endobj
+4749 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [80.9762 109.1627 142.7442 118.0094]
+/Subtype /Link
+/A << /S /GoTo /D (http-apache) >>
+>> endobj
+4726 0 obj <<
+/D [4724 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+4727 0 obj <<
+/D [4724 0 R /XYZ 71.731 718.3063 null]
+>> endobj
+4728 0 obj <<
+/D [4724 0 R /XYZ 508.2921 708.3437 null]
+>> endobj
+4729 0 obj <<
+/D [4724 0 R /XYZ 71.731 649.3998 null]
+>> endobj
+4730 0 obj <<
+/D [4724 0 R /XYZ 71.731 631.4671 null]
+>> endobj
+4731 0 obj <<
+/D [4724 0 R /XYZ 71.731 579.6613 null]
+>> endobj
+4732 0 obj <<
+/D [4724 0 R /XYZ 71.731 546.9191 null]
+>> endobj
+4733 0 obj <<
+/D [4724 0 R /XYZ 71.731 536.9564 null]
+>> endobj
+4734 0 obj <<
+/D [4724 0 R /XYZ 135.9845 527.3225 null]
+>> endobj
+4735 0 obj <<
+/D [4724 0 R /XYZ 135.9845 492.3537 null]
+>> endobj
+4736 0 obj <<
+/D [4724 0 R /XYZ 71.731 435.7659 null]
+>> endobj
+4737 0 obj <<
+/D [4724 0 R /XYZ 71.731 396.812 null]
+>> endobj
+4738 0 obj <<
+/D [4724 0 R /XYZ 71.731 362.0125 null]
+>> endobj
+4739 0 obj <<
+/D [4724 0 R /XYZ 71.731 352.0499 null]
+>> endobj
+4740 0 obj <<
+/D [4724 0 R /XYZ 135.9845 342.416 null]
+>> endobj
+4741 0 obj <<
+/D [4724 0 R /XYZ 135.9845 307.4471 null]
+>> endobj
+4742 0 obj <<
+/D [4724 0 R /XYZ 71.731 274.1719 null]
+>> endobj
+4743 0 obj <<
+/D [4724 0 R /XYZ 181.6909 261.2205 null]
+>> endobj
+4744 0 obj <<
+/D [4724 0 R /XYZ 485.8887 261.2205 null]
+>> endobj
+1920 0 obj <<
+/D [4724 0 R /XYZ 71.731 228.1794 null]
+>> endobj
+950 0 obj <<
+/D [4724 0 R /XYZ 107.1086 162.7019 null]
+>> endobj
+4745 0 obj <<
+/D [4724 0 R /XYZ 71.731 153.8791 null]
+>> endobj
+4746 0 obj <<
+/D [4724 0 R /XYZ 71.731 134.0046 null]
+>> endobj
+4747 0 obj <<
+/D [4724 0 R /XYZ 274.3729 123.21 null]
+>> endobj
+4748 0 obj <<
+/D [4724 0 R /XYZ 390.7657 123.21 null]
+>> endobj
+1921 0 obj <<
+/D [4724 0 R /XYZ 71.731 105.1777 null]
+>> endobj
+4723 0 obj <<
+/Font << /F33 1322 0 R /F27 1222 0 R /F32 1230 0 R /F23 1214 0 R /F60 2540 0 R /F35 1604 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+4752 0 obj <<
+/Length 885
+/Filter /FlateDecode
+>>
+stream
+xڕUKo6WD\-hѠ(@Ip%zWT(9i
+K*F^
++@&h4zIC6)8# ˤ9]!}5JwcႦLfHͼt4ݚYijCxշ
+p
+RUH1UufGfwrHUVIS7śW!D4
+n8'yMYH+gw7}FSgZ2ɩٽןc=%M !/|(ʈ f
+Ǧ|Wb5]F
+pAGJbt\
+~;/UjD.>x<w+)>*wҚYGW;hp55sXδ Gƛ +|)9݂6p[ ݧ6ѝ⏧I oׄY#yM3 -endstream
+endobj
+4751 0 obj <<
+/Type /Page
+/Contents 4752 0 R
+/Resources 4750 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 4670 0 R
+>> endobj
+4753 0 obj <<
+/D [4751 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+954 0 obj <<
+/D [4751 0 R /XYZ 452.3944 683.3676 null]
+>> endobj
+4754 0 obj <<
+/D [4751 0 R /XYZ 71.731 671.1964 null]
+>> endobj
+4755 0 obj <<
+/D [4751 0 R /XYZ 71.731 648.857 null]
+>> endobj
+4756 0 obj <<
+/D [4751 0 R /XYZ 437.9897 648.857 null]
+>> endobj
+4757 0 obj <<
+/D [4751 0 R /XYZ 71.731 628.7675 null]
+>> endobj
+4758 0 obj <<
+/D [4751 0 R /XYZ 130.4005 592.07 null]
+>> endobj
+4750 0 obj <<
+/Font << /F33 1322 0 R /F23 1214 0 R /F27 1222 0 R /F35 1604 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+4761 0 obj <<
+/Length 2959
+/Filter /FlateDecode
+>>
+stream
+xڕk۸{~q= ^֊z날Hn8(Ц8mWw3eap8Z\X8/u(VY]a'I(rPD0X݆~줉s&/wOn+u0 Wô;v?/Vչtdҡ﫦:GR'?pW[#,S&֒x-i\^u7kpuL˒f__uYJ^ۦiJ
+E*a
+w6C.BBva; MwµC;
+ݓ@=ɛl"='VxNT=E+7W"v< YګJԵ"[%]g7]d>ca]<'ZcwAzЪAx T]S2@F!DlJ>1Θ=6pDY9rZN+.l`41
+|҇-P@&g*NlC<3n
+&H ~jtRs ia+I\8qd;[mxU0B1N 76Ѽ3ԩB@0J
+%Faŕlp W|٘C^Tﳕ}ʇB01T|Cw"0<޺]!; 4`GXsY00pI8tJN+S=D#zC1lC\BIS\xV( ʱ 6Q0O_&'
+՛~뻷;
+ZNMe@w짾}&yo7Np_v9 p[ U|ڥ7an/o'O*/{c5{5Mq*ż
+g&^.%sɰC!JǼ )
+M5gD/"GPԤ.iPQ0L628rFI۱k؊"۹D9VSM]ar#L ?ry9/ES^R: (@v[ɫV +<7m80a]²~&mʌ6k6myt֣S֣
+R
+endobj
+4760 0 obj <<
+/Type /Page
+/Contents 4761 0 R
+/Resources 4759 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 4670 0 R
+>> endobj
+4762 0 obj <<
+/D [4760 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+1922 0 obj <<
+/D [4760 0 R /XYZ 71.731 718.3063 null]
+>> endobj
+958 0 obj <<
+/D [4760 0 R /XYZ 271.435 703.236 null]
+>> endobj
+4763 0 obj <<
+/D [4760 0 R /XYZ 71.731 682.1747 null]
+>> endobj
+4764 0 obj <<
+/D [4760 0 R /XYZ 297.9985 673.4995 null]
+>> endobj
+1923 0 obj <<
+/D [4760 0 R /XYZ 71.731 660.4485 null]
+>> endobj
+962 0 obj <<
+/D [4760 0 R /XYZ 365.8704 615.2938 null]
+>> endobj
+4765 0 obj <<
+/D [4760 0 R /XYZ 71.731 606.4709 null]
+>> endobj
+4766 0 obj <<
+/D [4760 0 R /XYZ 457.2853 593.7346 null]
+>> endobj
+4767 0 obj <<
+/D [4760 0 R /XYZ 199.7198 580.7832 null]
+>> endobj
+4768 0 obj <<
+/D [4760 0 R /XYZ 258.4993 580.7832 null]
+>> endobj
+4769 0 obj <<
+/D [4760 0 R /XYZ 315.5253 580.7832 null]
+>> endobj
+4770 0 obj <<
+/D [4760 0 R /XYZ 71.731 578.6263 null]
+>> endobj
+4771 0 obj <<
+/D [4760 0 R /XYZ 118.5554 540.0623 null]
+>> endobj
+4772 0 obj <<
+/D [4760 0 R /XYZ 71.731 509.7853 null]
+>> endobj
+4773 0 obj <<
+/D [4760 0 R /XYZ 71.731 509.7853 null]
+>> endobj
+4774 0 obj <<
+/D [4760 0 R /XYZ 71.731 490.0793 null]
+>> endobj
+4775 0 obj <<
+/D [4760 0 R /XYZ 165.1103 477.1279 null]
+>> endobj
+4776 0 obj <<
+/D [4760 0 R /XYZ 71.731 469.9897 null]
+>> endobj
+4777 0 obj <<
+/D [4760 0 R /XYZ 71.731 469.9897 null]
+>> endobj
+4778 0 obj <<
+/D [4760 0 R /XYZ 164.0649 446.2437 null]
+>> endobj
+4779 0 obj <<
+/D [4760 0 R /XYZ 210.3517 446.2437 null]
+>> endobj
+4780 0 obj <<
+/D [4760 0 R /XYZ 352.5688 446.2437 null]
+>> endobj
+4781 0 obj <<
+/D [4760 0 R /XYZ 442.6605 446.2437 null]
+>> endobj
+4782 0 obj <<
+/D [4760 0 R /XYZ 203.7146 433.2922 null]
+>> endobj
+4783 0 obj <<
+/D [4760 0 R /XYZ 372.0612 433.2922 null]
+>> endobj
+4784 0 obj <<
+/D [4760 0 R /XYZ 71.731 426.1541 null]
+>> endobj
+4785 0 obj <<
+/D [4760 0 R /XYZ 460.2171 415.3595 null]
+>> endobj
+4786 0 obj <<
+/D [4760 0 R /XYZ 71.731 382.3185 null]
+>> endobj
+4787 0 obj <<
+/D [4760 0 R /XYZ 71.731 382.3185 null]
+>> endobj
+4788 0 obj <<
+/D [4760 0 R /XYZ 237.4512 371.5239 null]
+>> endobj
+4789 0 obj <<
+/D [4760 0 R /XYZ 71.731 358.5724 null]
+>> endobj
+4790 0 obj <<
+/D [4760 0 R /XYZ 220.8703 345.621 null]
+>> endobj
+4791 0 obj <<
+/D [4760 0 R /XYZ 71.731 338.4829 null]
+>> endobj
+4792 0 obj <<
+/D [4760 0 R /XYZ 257.1241 327.6883 null]
+>> endobj
+4793 0 obj <<
+/D [4760 0 R /XYZ 358.7127 327.6883 null]
+>> endobj
+1924 0 obj <<
+/D [4760 0 R /XYZ 71.731 320.5501 null]
+>> endobj
+966 0 obj <<
+/D [4760 0 R /XYZ 462.0005 277.4526 null]
+>> endobj
+4794 0 obj <<
+/D [4760 0 R /XYZ 71.731 265.0146 null]
+>> endobj
+4795 0 obj <<
+/D [4760 0 R /XYZ 117.2903 255.8935 null]
+>> endobj
+4796 0 obj <<
+/D [4760 0 R /XYZ 427.8955 255.8935 null]
+>> endobj
+4797 0 obj <<
+/D [4760 0 R /XYZ 71.731 224.9097 null]
+>> endobj
+4798 0 obj <<
+/D [4760 0 R /XYZ 173.632 212.0579 null]
+>> endobj
+4799 0 obj <<
+/D [4760 0 R /XYZ 420.183 212.0579 null]
+>> endobj
+4800 0 obj <<
+/D [4760 0 R /XYZ 71.731 166.0654 null]
+>> endobj
+4801 0 obj <<
+/D [4760 0 R /XYZ 71.731 122.2298 null]
+>> endobj
+4802 0 obj <<
+/D [4760 0 R /XYZ 71.731 122.2298 null]
+>> endobj
+4759 0 obj <<
+/Font << /F23 1214 0 R /F27 1222 0 R /F35 1604 0 R /F44 2021 0 R /F32 1230 0 R /F33 1322 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+4805 0 obj <<
+/Length 1588
+/Filter /FlateDecode
+>>
+stream
+xڽ]o6=*-R"%À4ي M0쁑hK-yH(Yvnxǻs,?>9C %,ۜޝq)ń
+' c&Bwg_D8S2h*.ӻnM/Ļd՜^tN׺+00Lu+x Bid=1\F pJXŖqYRᑜLTXk"SP#$&LI=F]A맹91RzBzNg7%ueI]WuNĮ0SmY[gBJT03Ğ]4,<Pdu$5餫
+=qK2e0<2ȿ|M+4&ĺ˵Ǔl ]_,Ibs`@S(@?B|e7"MՕa`Jyn3H ܴSzoFZ@qs F,qԊ0rS6"\KmG(%~@R_c?^jC?Ƀ*n(jbǦn;:<+c5lΟlwmlu" ]dCSuqBw<*q#귻D}E*l!lc:Bw y̆q#[KLw!nƁЋGjš'zz4f[e7 iim7I'x aPj)$c26*6zE6sy_gzNҨ, J}},QrUe3#eW0p4I)'w Ɲl:$"KH<hCʸmH#a 1!x|x.܎MtT؎h9YI90 SA&
+/BJy^84<?
+dکw>䓌cEq8nu7Ť~w%rkV, xZHD/BQ:ժ
+fCቷZ`,n~#]Y]~ý=3Q*y8Mήt~r_~pşLJ?OQƑcsNTMCendstream
+endobj
+4804 0 obj <<
+/Type /Page
+/Contents 4805 0 R
+/Resources 4803 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 4833 0 R
+/Annots [ 4821 0 R ]
+>> endobj
+4821 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [157.2677 413.7501 206.2336 424.3324]
+/Subtype /Link
+/A << /S /GoTo /D (modules-manual-download) >>
+>> endobj
+4806 0 obj <<
+/D [4804 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+1925 0 obj <<
+/D [4804 0 R /XYZ 71.731 718.3063 null]
+>> endobj
+970 0 obj <<
+/D [4804 0 R /XYZ 155.5214 676.3797 null]
+>> endobj
+1926 0 obj <<
+/D [4804 0 R /XYZ 71.731 669.6658 null]
+>> endobj
+974 0 obj <<
+/D [4804 0 R /XYZ 206.6123 624.303 null]
+>> endobj
+4807 0 obj <<
+/D [4804 0 R /XYZ 71.731 615.4802 null]
+>> endobj
+4808 0 obj <<
+/D [4804 0 R /XYZ 71.731 582.6542 null]
+>> endobj
+4809 0 obj <<
+/D [4804 0 R /XYZ 71.731 572.6916 null]
+>> endobj
+4810 0 obj <<
+/D [4804 0 R /XYZ 71.731 572.6916 null]
+>> endobj
+4811 0 obj <<
+/D [4804 0 R /XYZ 71.731 561.8114 null]
+>> endobj
+4812 0 obj <<
+/D [4804 0 R /XYZ 71.731 551.2777 null]
+>> endobj
+4813 0 obj <<
+/D [4804 0 R /XYZ 71.731 538.4988 null]
+>> endobj
+4814 0 obj <<
+/D [4804 0 R /XYZ 71.731 527.9651 null]
+>> endobj
+4815 0 obj <<
+/D [4804 0 R /XYZ 71.731 516.3088 null]
+>> endobj
+4816 0 obj <<
+/D [4804 0 R /XYZ 76.7123 483.2918 null]
+>> endobj
+4817 0 obj <<
+/D [4804 0 R /XYZ 71.731 468.3478 null]
+>> endobj
+4818 0 obj <<
+/D [4804 0 R /XYZ 486.2278 456.6915 null]
+>> endobj
+4819 0 obj <<
+/D [4804 0 R /XYZ 451.4238 445.0352 null]
+>> endobj
+4820 0 obj <<
+/D [4804 0 R /XYZ 71.731 426.5103 null]
+>> endobj
+1927 0 obj <<
+/D [4804 0 R /XYZ 71.731 365.5334 null]
+>> endobj
+978 0 obj <<
+/D [4804 0 R /XYZ 276.1797 320.2791 null]
+>> endobj
+4822 0 obj <<
+/D [4804 0 R /XYZ 71.731 320.064 null]
+>> endobj
+4823 0 obj <<
+/D [4804 0 R /XYZ 71.731 301.4936 null]
+>> endobj
+4824 0 obj <<
+/D [4804 0 R /XYZ 91.6563 266.7399 null]
+>> endobj
+4825 0 obj <<
+/D [4804 0 R /XYZ 349.077 266.7399 null]
+>> endobj
+4826 0 obj <<
+/D [4804 0 R /XYZ 71.731 227.1882 null]
+>> endobj
+4827 0 obj <<
+/D [4804 0 R /XYZ 71.731 204.1746 null]
+>> endobj
+4828 0 obj <<
+/D [4804 0 R /XYZ 188.0244 191.3227 null]
+>> endobj
+4829 0 obj <<
+/D [4804 0 R /XYZ 158.3455 178.3713 null]
+>> endobj
+4830 0 obj <<
+/D [4804 0 R /XYZ 71.731 137.5244 null]
+>> endobj
+4831 0 obj <<
+/D [4804 0 R /XYZ 71.731 112.4535 null]
+>> endobj
+4832 0 obj <<
+/D [4804 0 R /XYZ 188.0244 101.6589 null]
+>> endobj
+4803 0 obj <<
+/Font << /F23 1214 0 R /F27 1222 0 R /F35 1604 0 R /F44 2021 0 R /F33 1322 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+4836 0 obj <<
+/Length 534
+/Filter /FlateDecode
+>>
+stream
+xڽUMs0W$!nqh<L:>pk{P`QW 8AVb
+endobj
+4835 0 obj <<
+/Type /Page
+/Contents 4836 0 R
+/Resources 4834 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 4833 0 R
+>> endobj
+4837 0 obj <<
+/D [4835 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+4838 0 obj <<
+/D [4835 0 R /XYZ 158.3455 708.3437 null]
+>> endobj
+4839 0 obj <<
+/D [4835 0 R /XYZ 71.731 667.4969 null]
+>> endobj
+4840 0 obj <<
+/D [4835 0 R /XYZ 71.731 642.426 null]
+>> endobj
+4841 0 obj <<
+/D [4835 0 R /XYZ 188.0244 631.6314 null]
+>> endobj
+4842 0 obj <<
+/D [4835 0 R /XYZ 158.3455 618.6799 null]
+>> endobj
+4843 0 obj <<
+/D [4835 0 R /XYZ 71.731 577.8331 null]
+>> endobj
+4844 0 obj <<
+/D [4835 0 R /XYZ 71.731 554.8195 null]
+>> endobj
+4845 0 obj <<
+/D [4835 0 R /XYZ 188.0244 541.9676 null]
+>> endobj
+4846 0 obj <<
+/D [4835 0 R /XYZ 158.3455 529.0162 null]
+>> endobj
+4847 0 obj <<
+/D [4835 0 R /XYZ 71.731 488.1693 null]
+>> endobj
+4848 0 obj <<
+/D [4835 0 R /XYZ 71.731 463.0984 null]
+>> endobj
+4849 0 obj <<
+/D [4835 0 R /XYZ 188.0244 452.3038 null]
+>> endobj
+4850 0 obj <<
+/D [4835 0 R /XYZ 158.3455 439.3524 null]
+>> endobj
+4851 0 obj <<
+/D [4835 0 R /XYZ 71.731 398.5056 null]
+>> endobj
+4852 0 obj <<
+/D [4835 0 R /XYZ 71.731 373.4347 null]
+>> endobj
+4853 0 obj <<
+/D [4835 0 R /XYZ 188.0244 362.6401 null]
+>> endobj
+4854 0 obj <<
+/D [4835 0 R /XYZ 158.3455 349.6887 null]
+>> endobj
+4855 0 obj <<
+/D [4835 0 R /XYZ 71.731 308.8418 null]
+>> endobj
+4856 0 obj <<
+/D [4835 0 R /XYZ 71.731 283.7709 null]
+>> endobj
+4857 0 obj <<
+/D [4835 0 R /XYZ 188.0244 272.9763 null]
+>> endobj
+4858 0 obj <<
+/D [4835 0 R /XYZ 158.3455 260.0249 null]
+>> endobj
+4859 0 obj <<
+/D [4835 0 R /XYZ 71.731 219.1781 null]
+>> endobj
+4860 0 obj <<
+/D [4835 0 R /XYZ 71.731 196.1645 null]
+>> endobj
+4861 0 obj <<
+/D [4835 0 R /XYZ 188.0244 183.3126 null]
+>> endobj
+4862 0 obj <<
+/D [4835 0 R /XYZ 158.3455 170.3611 null]
+>> endobj
+4863 0 obj <<
+/D [4835 0 R /XYZ 71.731 129.5143 null]
+>> endobj
+4834 0 obj <<
+/Font << /F33 1322 0 R /F27 1222 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+4866 0 obj <<
+/Length 699
+/Filter /FlateDecode
+>>
+stream
+xڽV]o0}ϯz,jlUMr 8J_?c M[Ecs\d8 
+}B$7dVG@6jG$ dN
+yc*,LM u"je*eApV)+VQ[(0W0 hT6 ՐS ^J B
+dD d? Lx[-Hek<%J0 vqEQǧnxsSZ;mn}H))1l |wUoRٕ^‘T]-r 1rQ%x
+endobj
+4865 0 obj <<
+/Type /Page
+/Contents 4866 0 R
+/Resources 4864 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 4833 0 R
+>> endobj
+4867 0 obj <<
+/D [4865 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+4868 0 obj <<
+/D [4865 0 R /XYZ 71.731 718.3063 null]
+>> endobj
+4869 0 obj <<
+/D [4865 0 R /XYZ 185.5337 708.3437 null]
+>> endobj
+4870 0 obj <<
+/D [4865 0 R /XYZ 155.8548 695.3923 null]
+>> endobj
+4871 0 obj <<
+/D [4865 0 R /XYZ 71.731 654.5454 null]
+>> endobj
+4872 0 obj <<
+/D [4865 0 R /XYZ 71.731 629.4745 null]
+>> endobj
+4873 0 obj <<
+/D [4865 0 R /XYZ 188.0244 618.6799 null]
+>> endobj
+4874 0 obj <<
+/D [4865 0 R /XYZ 158.3455 605.7285 null]
+>> endobj
+1928 0 obj <<
+/D [4865 0 R /XYZ 71.731 564.8817 null]
+>> endobj
+982 0 obj <<
+/D [4865 0 R /XYZ 252.5255 519.6274 null]
+>> endobj
+4875 0 obj <<
+/D [4865 0 R /XYZ 71.731 507.4562 null]
+>> endobj
+4876 0 obj <<
+/D [4865 0 R /XYZ 71.731 488.006 null]
+>> endobj
+4877 0 obj <<
+/D [4865 0 R /XYZ 188.0244 475.1541 null]
+>> endobj
+4878 0 obj <<
+/D [4865 0 R /XYZ 158.3455 462.2027 null]
+>> endobj
+4879 0 obj <<
+/D [4865 0 R /XYZ 71.731 421.3559 null]
+>> endobj
+4880 0 obj <<
+/D [4865 0 R /XYZ 71.731 396.285 null]
+>> endobj
+4881 0 obj <<
+/D [4865 0 R /XYZ 188.0244 385.4904 null]
+>> endobj
+4882 0 obj <<
+/D [4865 0 R /XYZ 158.3455 372.539 null]
+>> endobj
+4883 0 obj <<
+/D [4865 0 R /XYZ 71.731 331.6921 null]
+>> endobj
+4884 0 obj <<
+/D [4865 0 R /XYZ 71.731 306.6212 null]
+>> endobj
+4885 0 obj <<
+/D [4865 0 R /XYZ 188.0244 295.8266 null]
+>> endobj
+4886 0 obj <<
+/D [4865 0 R /XYZ 158.3455 282.8752 null]
+>> endobj
+4887 0 obj <<
+/D [4865 0 R /XYZ 71.731 242.0284 null]
+>> endobj
+4888 0 obj <<
+/D [4865 0 R /XYZ 71.731 216.9575 null]
+>> endobj
+4889 0 obj <<
+/D [4865 0 R /XYZ 188.0244 206.1629 null]
+>> endobj
+4890 0 obj <<
+/D [4865 0 R /XYZ 158.3455 193.2114 null]
+>> endobj
+4891 0 obj <<
+/D [4865 0 R /XYZ 71.731 152.3646 null]
+>> endobj
+4892 0 obj <<
+/D [4865 0 R /XYZ 71.731 129.351 null]
+>> endobj
+4893 0 obj <<
+/D [4865 0 R /XYZ 188.0244 116.4991 null]
+>> endobj
+4894 0 obj <<
+/D [4865 0 R /XYZ 158.3455 103.5477 null]
+>> endobj
+4864 0 obj <<
+/Font << /F33 1322 0 R /F27 1222 0 R /F23 1214 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+4897 0 obj <<
+/Length 201
+/Filter /FlateDecode
+>>
+stream
+x}; P xs_MFnPlVZA% Grj #L+ZP{a{,I{&F!LAC؁
+hdG Xө=IS;]-~^_yUCSwf*a[6ŭ*/&,Ixs&C{Ԏ<ZoR{PziU3S[endstream
+endobj
+4896 0 obj <<
+/Type /Page
+/Contents 4897 0 R
+/Resources 4895 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 4833 0 R
+>> endobj
+4898 0 obj <<
+/D [4896 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+4895 0 obj <<
+/Font << /F33 1322 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+4901 0 obj <<
+/Length 2587
+/Filter /FlateDecode
+>>
+stream
+xYK8W eϞ$bhD9=^zv
+&ڡ_Uij&4]oY緼(M4*[ wIƢ.P1</R:LYeSϿ胿T'i4MW!`NΓ<O"biYX\H1՝Mw/#7I<:NWq&qN׽s`;2jkvy~HhÅuz>C*G#f!ف,h#'TpjTӱȤ~h>xoӝ>ylcr{nk< ۟fICgtfv+Y95BV@ ']M$Y?h@ 
+%:@-t`p@NYL'RL'aqkS;oKv-jT606B''a侤u"~Df, .<`T&"ĺDt2e1kaEQKm.Ubd
+:ī:Ԟdiv9 klќUA֫fdҀea$b v4VK
+3Tb.pQdB$h&=ǖU@AW^r;Jy,qeK"<\ޅ5)n]b%࣐-4!RsX9Q'v%p&x_ 7g kH
+]2ӊCe=lJ~
+endobj
+4900 0 obj <<
+/Type /Page
+/Contents 4901 0 R
+/Resources 4899 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 4833 0 R
+>> endobj
+4902 0 obj <<
+/D [4900 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+1929 0 obj <<
+/D [4900 0 R /XYZ 71.731 718.3063 null]
+>> endobj
+986 0 obj <<
+/D [4900 0 R /XYZ 531.42 703.236 null]
+>> endobj
+4903 0 obj <<
+/D [4900 0 R /XYZ 71.731 682.1747 null]
+>> endobj
+4904 0 obj <<
+/D [4900 0 R /XYZ 71.731 672.0599 null]
+>> endobj
+4905 0 obj <<
+/D [4900 0 R /XYZ 71.731 662.0973 null]
+>> endobj
+1930 0 obj <<
+/D [4900 0 R /XYZ 71.731 638.2831 null]
+>> endobj
+990 0 obj <<
+/D [4900 0 R /XYZ 168.2049 594.97 null]
+>> endobj
+4906 0 obj <<
+/D [4900 0 R /XYZ 71.731 586.1472 null]
+>> endobj
+4907 0 obj <<
+/D [4900 0 R /XYZ 71.731 527.4184 null]
+>> endobj
+4908 0 obj <<
+/D [4900 0 R /XYZ 71.731 485.64 null]
+>> endobj
+1931 0 obj <<
+/D [4900 0 R /XYZ 71.731 415.9016 null]
+>> endobj
+994 0 obj <<
+/D [4900 0 R /XYZ 312.7959 370.7468 null]
+>> endobj
+4909 0 obj <<
+/D [4900 0 R /XYZ 71.731 358.5756 null]
+>> endobj
+4910 0 obj <<
+/D [4900 0 R /XYZ 71.731 316.1466 null]
+>> endobj
+4911 0 obj <<
+/D [4900 0 R /XYZ 71.731 285.2624 null]
+>> endobj
+4912 0 obj <<
+/D [4900 0 R /XYZ 71.731 202.5725 null]
+>> endobj
+4913 0 obj <<
+/D [4900 0 R /XYZ 71.731 171.6884 null]
+>> endobj
+4914 0 obj <<
+/D [4900 0 R /XYZ 71.731 140.8042 null]
+>> endobj
+4899 0 obj <<
+/Font << /F23 1214 0 R /F27 1222 0 R /F33 1322 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+4917 0 obj <<
+/Length 2983
+/Filter /FlateDecode
+>>
+stream
+xڍْ۸_1PUDz)o)ORN%yHhĘ$<<ߧ/D{\eF[>/|8!Ot}= ~z
+Npv.?㇣GC$84{V7Eu{6iui-(&k j(Màe^o?>N|?fpwُ!̏@̗pp{(m2@5pQʞXTia!k.F0Mz_iCAwBBp8(sݩk9dROuBt41.c&
+|r7lZ=C˻πWM ʳZ䯒.{.{Y~ĠpVu ODv}>-/Ik9}b)y<#ǪCqR^Q;ޏ^62w^Ds+9]ypq+
+J o
+mv%0Ӆn%>#h[&53,LS䂓_M?|̻ w!δ`Twek+[VNB-G8@O{{'6I }s@6k`9}o~,E\' 8[/ g0C,P'!1UK #Dȓntw]֝l9bAKN*7HxV 6D<&TP9T2M|K–Ǣ)~@ ] ''c> `i3ٖ0ʡYPj$KC,阪2,0 F2S5U䁞? qXQh1ʮ=lbCHK COqܐox6VÓI{D|t-4t˚ N )dU( E6Q"填.~UPD %Gw|'G &D
+_pz?a.q`Reߊ{:4 cod7Ȱ 50M07tOGE2ب8!o0O
+4kZI%-ϘdqW>]n/-ǽJC'jm[q8憲 ӓo_"uaBnS@)F
+|pyOՍ.<n9ܐG{T0l,N-u>D|US`Ti{M)tw{U!M[xo'N 3TFW?&b>(^
+U",!3_:l8ĜJMsKAf%.!NO5<
+`#ybS엑>eYLkE8!Mr [[@B;{W œ3zA \up^ABO05iݾoYa:%7dي;3H.wμ3֙\q^skǤX51j}l]6}2[6
+A,[a69)GɼbJX֢:zf8` % "BWeQQ m&Q0pzyd `*hpd5z{0gN}&0hC&YV ?N9q^c0N\ÄUF'f+]7 }gpQ/ _l \[SXKߧ-4Β[L5*h@HC=^ Vf#=fRfgj_iYޞmm}~ $[K)#ۡs߷/wH^\GETF`kpD
+A|U"'{Z@֛,X ]3
+Xgeb ; Ar_#&0Za؝,wN#X(#v<GiER%Hsf#`s^?C~g!r$endstream
+endobj
+4916 0 obj <<
+/Type /Page
+/Contents 4917 0 R
+/Resources 4915 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 4833 0 R
+>> endobj
+4918 0 obj <<
+/D [4916 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+4919 0 obj <<
+/D [4916 0 R /XYZ 71.731 662.3513 null]
+>> endobj
+4920 0 obj <<
+/D [4916 0 R /XYZ 71.731 592.6128 null]
+>> endobj
+1932 0 obj <<
+/D [4916 0 R /XYZ 71.731 535.8257 null]
+>> endobj
+998 0 obj <<
+/D [4916 0 R /XYZ 237.0663 492.7282 null]
+>> endobj
+4921 0 obj <<
+/D [4916 0 R /XYZ 71.731 480.2903 null]
+>> endobj
+4922 0 obj <<
+/D [4916 0 R /XYZ 71.731 401.3311 null]
+>> endobj
+1933 0 obj <<
+/D [4916 0 R /XYZ 71.731 381.341 null]
+>> endobj
+1002 0 obj <<
+/D [4916 0 R /XYZ 254.1783 338.2435 null]
+>> endobj
+4923 0 obj <<
+/D [4916 0 R /XYZ 71.731 325.8056 null]
+>> endobj
+4924 0 obj <<
+/D [4916 0 R /XYZ 71.731 231.8377 null]
+>> endobj
+4925 0 obj <<
+/D [4916 0 R /XYZ 71.731 200.9535 null]
+>> endobj
+4915 0 obj <<
+/Font << /F33 1322 0 R /F27 1222 0 R /F23 1214 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+4928 0 obj <<
+/Length 3095
+/Filter /FlateDecode
+>>
+stream
+xڝ]6}E/
+n顫O{i{ګkW,<u;!uqj.tZAx._( eFȈ6DSL6ߴ-^ E/k#}W6
+'Xi
+zV9>]+8!X]PDLR!FXB|ڤzylإbhVP/}Pt]S'ʃ9R( Zkn- ƜYQ19PN( 8ZT/%c<1>)µm$̋vԧ.z`WR-`Kpe'U:AWftmcb>/w[mGSRF`E:RkVg70ԻP߇cB@o!-`@-NO۵%7/ɮ*{ 93eYIXX7L`?,&qSm+MڥPƁjb'0[Iyqk4Oii`yn0r)SUS%Yz~ϕ-ͲC\- Ğڠ -:8GC|hj![Jѫt_~Km*38WgR/Mre@ʙ?zTlhcye`06XjzZ.CiOz`I.|ԅ7^X>PJ<l)[rv()m|
+JKSL#sg ǖS3!gSz16<b͇A_(3
+f֨2׵`F7zݜ;A _QXyh*rr#daFPPX `8qf8s
+q)Ll)30~h=? 󽜖9d oP9f#[0Ց;9vyiFľ'e튑v6îp?/ەvuSmRuCMao ,NՄApkIu?.T,Q" <<, ~Yޭ9;6)e+'*l<P}ٱo>nOe&;4]up#&w"2@Ø`'ֽ6,O\0KRcI-=}|86p26Jv@< $BqhyJ#M_0RQ2Z*͍K 9KbOn Ť;|x1gyD⹢
+endobj
+4927 0 obj <<
+/Type /Page
+/Contents 4928 0 R
+/Resources 4926 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 4963 0 R
+>> endobj
+4929 0 obj <<
+/D [4927 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+4930 0 obj <<
+/D [4927 0 R /XYZ 71.731 741.2204 null]
+>> endobj
+4931 0 obj <<
+/D [4927 0 R /XYZ 71.731 718.3063 null]
+>> endobj
+1934 0 obj <<
+/D [4927 0 R /XYZ 71.731 688.2541 null]
+>> endobj
+1006 0 obj <<
+/D [4927 0 R /XYZ 201.8268 645.1566 null]
+>> endobj
+4932 0 obj <<
+/D [4927 0 R /XYZ 71.731 636.3338 null]
+>> endobj
+4933 0 obj <<
+/D [4927 0 R /XYZ 71.731 582.5864 null]
+>> endobj
+4934 0 obj <<
+/D [4927 0 R /XYZ 71.731 577.605 null]
+>> endobj
+4935 0 obj <<
+/D [4927 0 R /XYZ 89.6638 556.8478 null]
+>> endobj
+4936 0 obj <<
+/D [4927 0 R /XYZ 71.731 528.7881 null]
+>> endobj
+4937 0 obj <<
+/D [4927 0 R /XYZ 89.6638 513.0122 null]
+>> endobj
+4938 0 obj <<
+/D [4927 0 R /XYZ 71.731 485.3261 null]
+>> endobj
+4939 0 obj <<
+/D [4927 0 R /XYZ 89.6638 469.1766 null]
+>> endobj
+4940 0 obj <<
+/D [4927 0 R /XYZ 71.731 467.0197 null]
+>> endobj
+4941 0 obj <<
+/D [4927 0 R /XYZ 89.6638 451.2438 null]
+>> endobj
+4942 0 obj <<
+/D [4927 0 R /XYZ 71.731 449.087 null]
+>> endobj
+4943 0 obj <<
+/D [4927 0 R /XYZ 89.6638 433.3111 null]
+>> endobj
+4944 0 obj <<
+/D [4927 0 R /XYZ 71.731 431.1542 null]
+>> endobj
+4945 0 obj <<
+/D [4927 0 R /XYZ 89.6638 415.3783 null]
+>> endobj
+4946 0 obj <<
+/D [4927 0 R /XYZ 71.731 400.9873 null]
+>> endobj
+4947 0 obj <<
+/D [4927 0 R /XYZ 89.6638 384.4941 null]
+>> endobj
+4948 0 obj <<
+/D [4927 0 R /XYZ 71.731 371.4431 null]
+>> endobj
+4949 0 obj <<
+/D [4927 0 R /XYZ 89.6638 353.6099 null]
+>> endobj
+4950 0 obj <<
+/D [4927 0 R /XYZ 71.731 351.4531 null]
+>> endobj
+4951 0 obj <<
+/D [4927 0 R /XYZ 89.6638 335.6772 null]
+>> endobj
+4952 0 obj <<
+/D [4927 0 R /XYZ 71.731 294.6661 null]
+>> endobj
+4953 0 obj <<
+/D [4927 0 R /XYZ 89.6638 278.8901 null]
+>> endobj
+4954 0 obj <<
+/D [4927 0 R /XYZ 71.731 237.879 null]
+>> endobj
+4955 0 obj <<
+/D [4927 0 R /XYZ 89.6638 222.1031 null]
+>> endobj
+4956 0 obj <<
+/D [4927 0 R /XYZ 71.731 206.9948 null]
+>> endobj
+4957 0 obj <<
+/D [4927 0 R /XYZ 89.6638 191.2189 null]
+>> endobj
+4958 0 obj <<
+/D [4927 0 R /XYZ 71.731 176.1106 null]
+>> endobj
+4959 0 obj <<
+/D [4927 0 R /XYZ 89.6638 160.3347 null]
+>> endobj
+4960 0 obj <<
+/D [4927 0 R /XYZ 71.731 158.1779 null]
+>> endobj
+4961 0 obj <<
+/D [4927 0 R /XYZ 89.6638 142.402 null]
+>> endobj
+4962 0 obj <<
+/D [4927 0 R /XYZ 71.731 135.2638 null]
+>> endobj
+4926 0 obj <<
+/Font << /F33 1322 0 R /F27 1222 0 R /F23 1214 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+4966 0 obj <<
+/Length 2570
+/Filter /FlateDecode
+>>
+stream
+xڝYKP%TT~8>xT*! ^TصOr7k@hv7?@f& ,&<a~so_,<.ӗ/&6弉̏<dQIo^{wSs콯}n߹f$^2aiN}ۦf_ԝW߼z%Qrʏ\fj}Gq{6LE݃lp6{v;&eɝjl+]W~3Nv=3o;3GsBPo.? 3mkuPj` I)Tm@uw> 676Hгn폻_p
+OKz.3RӎYmzAgSgH\m}6{CLRy=3fhz9(yCx
+A3Se.䔲{]q|1T!ux h5XIVv>Xh(tpkU
+CQ:\\rFdPNM fjϴ\3L^'Q&W,n;8лio
+REJkX㥐`.f7͒:+,3c
+4YrS8!ij̏Pokf
+}0djr E0s>vE..I.G\>MVkSBJ&'yu
+őɧD.xc2/<cM@pp '0. ¤b"сq2B
+,R`ˁhP,߇SιA;HEHI
+>9i7J(.+1.#JQB<Vo%GmO\π> SH3"Lg(zk"p(jh9,3xaȝ3K%N!F:&f"W:8vc юaʏ>zrI}Zȹ87KFDKKt[~
+-
+ҭ/ߢS R/- lPK? VͽnSBM!+rBw[\~"I@#-߀`bI?0Es(Zl#
+endobj
+4965 0 obj <<
+/Type /Page
+/Contents 4966 0 R
+/Resources 4964 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 4963 0 R
+>> endobj
+4967 0 obj <<
+/D [4965 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+4968 0 obj <<
+/D [4965 0 R /XYZ 71.731 646.4758 null]
+>> endobj
+4969 0 obj <<
+/D [4965 0 R /XYZ 71.731 561.7286 null]
+>> endobj
+1935 0 obj <<
+/D [4965 0 R /XYZ 71.731 530.8444 null]
+>> endobj
+1010 0 obj <<
+/D [4965 0 R /XYZ 279.2956 487.7469 null]
+>> endobj
+4970 0 obj <<
+/D [4965 0 R /XYZ 71.731 475.3089 null]
+>> endobj
+4971 0 obj <<
+/D [4965 0 R /XYZ 71.731 433.1468 null]
+>> endobj
+4972 0 obj <<
+/D [4965 0 R /XYZ 71.731 365.4656 null]
+>> endobj
+1936 0 obj <<
+/D [4965 0 R /XYZ 71.731 321.6299 null]
+>> endobj
+1014 0 obj <<
+/D [4965 0 R /XYZ 303.2245 276.4752 null]
+>> endobj
+4973 0 obj <<
+/D [4965 0 R /XYZ 71.731 267.6524 null]
+>> endobj
+4974 0 obj <<
+/D [4965 0 R /XYZ 71.731 221.875 null]
+>> endobj
+1937 0 obj <<
+/D [4965 0 R /XYZ 71.731 178.0394 null]
+>> endobj
+1018 0 obj <<
+/D [4965 0 R /XYZ 394.7926 134.9419 null]
+>> endobj
+4975 0 obj <<
+/D [4965 0 R /XYZ 71.731 122.5039 null]
+>> endobj
+4964 0 obj <<
+/Font << /F33 1322 0 R /F27 1222 0 R /F23 1214 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+4978 0 obj <<
+/Length 2426
+/Filter /FlateDecode
+>>
+stream
+xڍYݏ¸ʀW˒ӧk\&eS),98} g~3C
+M!ʩZν,GcԻ<Y8a#TLVy^UǝjLϘT'6&: 75e ݹK_IRGݘu <Ax08b+۽pC[JZa: <)7UP2ŵ,
+LabIh:N lS;7t/@WLTB >_ i 5 |@9ka^iu]uۺ7d!ٗV}YZ+'Ζ>j4>F: @ 1< .̞ c!k:74m+"l/<LYLDX,Ma&SQ6PLQsg&,f`/ƒ#eI!0(afu<Qq(.a Cd2VDN@G n>C =MM0e
+a+`Yua?8jU icjal=/'z X&@%v-%x/CAEqUu頔-E=[غ
+wZXcہ |qRoAt"ODx#"ok.P7UګiL'2ņ~z5⥻S "(
+Ŗ5NF
+Ng oPB!xɁxb-d=X%NjT
+Yn^,V8|^j{ *Sr S"\uYr<cK!#bb'4}I9:JFe5| j[0è Z@s6qlrV"ˁRITI0><5!5V-X N)L`MR} HmRiS 7{
+Dz$?m3? wW,sx^VDnX?q-Aendstream
+endobj
+4977 0 obj <<
+/Type /Page
+/Contents 4978 0 R
+/Resources 4976 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 4963 0 R
+>> endobj
+4979 0 obj <<
+/D [4977 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+4980 0 obj <<
+/D [4977 0 R /XYZ 71.731 675.3027 null]
+>> endobj
+1938 0 obj <<
+/D [4977 0 R /XYZ 71.731 631.4671 null]
+>> endobj
+1022 0 obj <<
+/D [4977 0 R /XYZ 182.2872 588.3696 null]
+>> endobj
+4981 0 obj <<
+/D [4977 0 R /XYZ 71.731 579.5468 null]
+>> endobj
+1939 0 obj <<
+/D [4977 0 R /XYZ 71.731 494.9151 null]
+>> endobj
+1026 0 obj <<
+/D [4977 0 R /XYZ 188.3641 451.8176 null]
+>> endobj
+4982 0 obj <<
+/D [4977 0 R /XYZ 71.731 442.9948 null]
+>> endobj
+1940 0 obj <<
+/D [4977 0 R /XYZ 71.731 384.266 null]
+>> endobj
+1030 0 obj <<
+/D [4977 0 R /XYZ 365.182 341.1686 null]
+>> endobj
+4983 0 obj <<
+/D [4977 0 R /XYZ 71.731 332.3458 null]
+>> endobj
+4984 0 obj <<
+/D [4977 0 R /XYZ 179.3565 293.7066 null]
+>> endobj
+4985 0 obj <<
+/D [4977 0 R /XYZ 71.731 286.5684 null]
+>> endobj
+1941 0 obj <<
+/D [4977 0 R /XYZ 71.731 216.8299 null]
+>> endobj
+1034 0 obj <<
+/D [4977 0 R /XYZ 433.2515 173.7324 null]
+>> endobj
+4986 0 obj <<
+/D [4977 0 R /XYZ 71.731 161.5612 null]
+>> endobj
+4987 0 obj <<
+/D [4977 0 R /XYZ 71.731 137.065 null]
+>> endobj
+4988 0 obj <<
+/D [4977 0 R /XYZ 71.731 127.1024 null]
+>> endobj
+4976 0 obj <<
+/Font << /F33 1322 0 R /F27 1222 0 R /F23 1214 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+4991 0 obj <<
+/Length 764
+/Filter /FlateDecode
+>>
+stream
+xڕUKo0 W9@ْة.C1 wvPmH4%*n!@H~Hd%A $(R0"4hЬ'qpIC>f4贞|:g,H<K Ҳ
+FI%eP“FVGsWoTΣ, MĊPZR+.e#T/Y=T%1eJh?OmeBAqIX
+~XٮJ.T]D`4mǠk! :.W5/E}yv5C|'cs0$R'_q(,r?bXHsվZ79 L$˙ľ= _ Fo$KX3djm+ڽuC>ȼO)[rH׹S &Qdf4 hg(0{F9S< >cT x+ʻQF!r<hJ,I)vn۵i
+g,!`%"6W6R .Ϩw2Vd\jsl,G/0Y92<͙ADV
+(+'|
+,#mKwwaztG1ڍ7lt+fJ#Rq*6pûNtdžU+c;VTapudl.EzvvHO/ɀ9J;^Ct\>ϠØL>otl.L 4%)z|(#1+1oޑ
+endobj
+4990 0 obj <<
+/Type /Page
+/Contents 4991 0 R
+/Resources 4989 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 4963 0 R
+>> endobj
+4992 0 obj <<
+/D [4990 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+4993 0 obj <<
+/D [4990 0 R /XYZ 71.731 689.7649 null]
+>> endobj
+4994 0 obj <<
+/D [4990 0 R /XYZ 71.731 647.7709 null]
+>> endobj
+4989 0 obj <<
+/Font << /F33 1322 0 R /F27 1222 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+4997 0 obj <<
+/Length 1841
+/Filter /FlateDecode
+>>
+stream
+xڭX_4OQ $Zi㍝ p &nkQ^Y>=3;MwCNxƞx\Ꮿ
+ΊQ1gbU_ū=|z{4ϙxQ*EJy|uHV"fyVfd*NHcmk&i㏠2*Ɠ,qqTm"QU9T2&0ʺVzb&ʁ`"U<n^6|}V[">lxV&wĔ]ClVnW-֫0ۉG1M[7jҊ'=-E,k ZQxNY˶U $V%$O'"gy32DUрݲZʎGŎvU0JM`3=x7ߺm_ N7<z9(bjfasRU+;|g\aNs`@\̝E-XMR ̱q?ӀT#UR[Db{Fdqʲ(+[с~`(\ag:^LmZ:Oz9ʭD^0^B
+_c4hslJYVGS."(ng1n.8 @8DB3%1$#vcx<}P;3\RSO
+pR.lx eWkE˙FZ:,{}T;V]o7"^: %6ՕxGKD'dQH #]ql94:K7!Id/G(
+:Wx#AJ}3+&cHrdrCRHQÁ\ ƕ'fx/e߷aD|&IHZH7VQöSX5T!DESƩa_L8E\v0fjK4t9GD3c1Oo?F? @@Sd4j $aE3|ᇈZ7:x",W%xKAÎFp6(| iDud^/ -a㭶ޒ(nsG~kC<5U$Ϯ%B׿p ޾tad,򴚿m@/!E.iCM\%i2!pSf
+oIRLzu0ux3D.K kh= *Zyvc Y hl6J7:}أP۽5j_j:.ILw-BhyrG9bG0:C_m.\!R18rH<MпR
+endobj
+4996 0 obj <<
+/Type /Page
+/Contents 4997 0 R
+/Resources 4995 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 4963 0 R
+/Annots [ 5043 0 R ]
+>> endobj
+5043 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [374.7025 133.0074 436.4705 143.9114]
+/Subtype /Link
+/A << /S /GoTo /D (http-apache) >>
+>> endobj
+4998 0 obj <<
+/D [4996 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+1942 0 obj <<
+/D [4996 0 R /XYZ 71.731 718.3063 null]
+>> endobj
+1038 0 obj <<
+/D [4996 0 R /XYZ 160.3549 703.236 null]
+>> endobj
+4999 0 obj <<
+/D [4996 0 R /XYZ 71.731 692.504 null]
+>> endobj
+1042 0 obj <<
+/D [4996 0 R /XYZ 208.3645 644.1007 null]
+>> endobj
+3976 0 obj <<
+/D [4996 0 R /XYZ 71.731 629.1751 null]
+>> endobj
+1046 0 obj <<
+/D [4996 0 R /XYZ 117.1402 620.82 null]
+>> endobj
+5000 0 obj <<
+/D [4996 0 R /XYZ 71.731 615.7142 null]
+>> endobj
+5001 0 obj <<
+/D [4996 0 R /XYZ 71.731 610.7329 null]
+>> endobj
+5002 0 obj <<
+/D [4996 0 R /XYZ 118.3278 584.9545 null]
+>> endobj
+5003 0 obj <<
+/D [4996 0 R /XYZ 296.214 572.0031 null]
+>> endobj
+5004 0 obj <<
+/D [4996 0 R /XYZ 71.731 536.1376 null]
+>> endobj
+1050 0 obj <<
+/D [4996 0 R /XYZ 86.6464 483.8248 null]
+>> endobj
+5005 0 obj <<
+/D [4996 0 R /XYZ 71.731 473.4955 null]
+>> endobj
+1054 0 obj <<
+/D [4996 0 R /XYZ 107.6162 460.5441 null]
+>> endobj
+5006 0 obj <<
+/D [4996 0 R /XYZ 71.731 453.5005 null]
+>> endobj
+5007 0 obj <<
+/D [4996 0 R /XYZ 71.731 448.5192 null]
+>> endobj
+5008 0 obj <<
+/D [4996 0 R /XYZ 256.795 411.7271 null]
+>> endobj
+5009 0 obj <<
+/D [4996 0 R /XYZ 392.1662 411.7271 null]
+>> endobj
+5010 0 obj <<
+/D [4996 0 R /XYZ 71.731 409.5703 null]
+>> endobj
+5011 0 obj <<
+/D [4996 0 R /XYZ 71.731 395.6226 null]
+>> endobj
+1058 0 obj <<
+/D [4996 0 R /XYZ 320.8499 382.2377 null]
+>> endobj
+5012 0 obj <<
+/D [4996 0 R /XYZ 71.731 369.6152 null]
+>> endobj
+5013 0 obj <<
+/D [4996 0 R /XYZ 71.731 369.6152 null]
+>> endobj
+5014 0 obj <<
+/D [4996 0 R /XYZ 71.731 369.6152 null]
+>> endobj
+5015 0 obj <<
+/D [4996 0 R /XYZ 71.731 357.9429 null]
+>> endobj
+5016 0 obj <<
+/D [4996 0 R /XYZ 111.5816 341.3909 null]
+>> endobj
+5017 0 obj <<
+/D [4996 0 R /XYZ 71.731 329.2714 null]
+>> endobj
+5018 0 obj <<
+/D [4996 0 R /XYZ 71.731 329.2714 null]
+>> endobj
+5019 0 obj <<
+/D [4996 0 R /XYZ 71.731 329.2714 null]
+>> endobj
+5020 0 obj <<
+/D [4996 0 R /XYZ 71.731 317.0961 null]
+>> endobj
+5021 0 obj <<
+/D [4996 0 R /XYZ 71.731 317.0961 null]
+>> endobj
+5022 0 obj <<
+/D [4996 0 R /XYZ 71.731 317.0961 null]
+>> endobj
+5023 0 obj <<
+/D [4996 0 R /XYZ 71.731 304.1446 null]
+>> endobj
+5024 0 obj <<
+/D [4996 0 R /XYZ 111.5816 287.5926 null]
+>> endobj
+5025 0 obj <<
+/D [4996 0 R /XYZ 326.8524 274.6412 null]
+>> endobj
+5026 0 obj <<
+/D [4996 0 R /XYZ 71.731 262.5217 null]
+>> endobj
+5027 0 obj <<
+/D [4996 0 R /XYZ 71.731 262.5217 null]
+>> endobj
+5028 0 obj <<
+/D [4996 0 R /XYZ 71.731 262.5217 null]
+>> endobj
+5029 0 obj <<
+/D [4996 0 R /XYZ 71.731 250.3464 null]
+>> endobj
+5030 0 obj <<
+/D [4996 0 R /XYZ 111.5816 233.7944 null]
+>> endobj
+5031 0 obj <<
+/D [4996 0 R /XYZ 352.0179 233.7944 null]
+>> endobj
+5032 0 obj <<
+/D [4996 0 R /XYZ 135.3745 220.843 null]
+>> endobj
+5033 0 obj <<
+/D [4996 0 R /XYZ 224.9831 220.843 null]
+>> endobj
+5034 0 obj <<
+/D [4996 0 R /XYZ 297.9916 220.843 null]
+>> endobj
+5035 0 obj <<
+/D [4996 0 R /XYZ 419.7283 220.843 null]
+>> endobj
+5036 0 obj <<
+/D [4996 0 R /XYZ 111.5816 207.8915 null]
+>> endobj
+5037 0 obj <<
+/D [4996 0 R /XYZ 71.731 196.5481 null]
+>> endobj
+5038 0 obj <<
+/D [4996 0 R /XYZ 71.731 196.5481 null]
+>> endobj
+5039 0 obj <<
+/D [4996 0 R /XYZ 71.731 196.5481 null]
+>> endobj
+5040 0 obj <<
+/D [4996 0 R /XYZ 71.731 183.5967 null]
+>> endobj
+5041 0 obj <<
+/D [4996 0 R /XYZ 111.5816 167.0447 null]
+>> endobj
+5042 0 obj <<
+/D [4996 0 R /XYZ 71.731 146.9551 null]
+>> endobj
+5044 0 obj <<
+/D [4996 0 R /XYZ 71.731 113.2464 null]
+>> endobj
+4995 0 obj <<
+/Font << /F23 1214 0 R /F27 1222 0 R /F35 1604 0 R /F32 1230 0 R /F33 1322 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+5047 0 obj <<
+/Length 1454
+/Filter /FlateDecode
+>>
+stream
+xڕWM8 ϯzז?`OӢ-bmQbal++M_H;NN$ꉢ'RN1|bDDE>ċL}|HE$eu)r"]8*2_lVqe,_(\X4e"p`jJ+"Z'YO26q0PIP]7U{e{CHHܠ^Y5V<JǣSdR霩IYtCU6wwNvtxntGg̟̓2-O.E"iZE=8C=<,Ԡz] p'0sռUe3az^vtelܙOfp2,@e慘82L{%?K pyBSQK8!9\^ײ՜ w%rH)цxc"8>&RBnL-{84 )Gjjy 8=E<^AIޚ_1rṫ<0d˓_ްhBQ)?d1wflMg[5{=(zU{9,Q^#!S; Q<ILOR1>~IF
+'fݰu"MF5#~b6C>قNZk(^\1fJ.n Ռ/Hfl 28H;=OMs/|l(FHvp
+˔y>C xϐl&~H(z%}s*ؓ)?Ұ
+,ĸ$IXhЙ+ܽC{\}Qx&|$4j9UQxSܠѓR YA
+'_^cõ>Cd z{ " Jf!~'[?L{|GMD[ rPDEV4+wduj%p'nS!s"*5=Ò||{eT#5sϒcUf|;@t{qy2;~dTX-<<U0^@E("* Y_$QT1DgH5fq7;m!RДiT 8`{Ff"pǞ z#PUuva'Ϫ."dYUg+dZFȩeYk./R`@ GrpEf{9) jkIі`zRqUV2r5khdw F[
+endobj
+5046 0 obj <<
+/Type /Page
+/Contents 5047 0 R
+/Resources 5045 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 4963 0 R
+>> endobj
+5048 0 obj <<
+/D [5046 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+1062 0 obj <<
+/D [5046 0 R /XYZ 86.6464 703.6802 null]
+>> endobj
+5049 0 obj <<
+/D [5046 0 R /XYZ 71.731 693.3509 null]
+>> endobj
+1066 0 obj <<
+/D [5046 0 R /XYZ 91.0983 680.3995 null]
+>> endobj
+5050 0 obj <<
+/D [5046 0 R /XYZ 71.731 673.2016 null]
+>> endobj
+5051 0 obj <<
+/D [5046 0 R /XYZ 71.731 668.2203 null]
+>> endobj
+5052 0 obj <<
+/D [5046 0 R /XYZ 101.8648 657.4854 null]
+>> endobj
+5053 0 obj <<
+/D [5046 0 R /XYZ 236.362 644.534 null]
+>> endobj
+5054 0 obj <<
+/D [5046 0 R /XYZ 284.4011 644.534 null]
+>> endobj
+5055 0 obj <<
+/D [5046 0 R /XYZ 71.731 619.1293 null]
+>> endobj
+1070 0 obj <<
+/D [5046 0 R /XYZ 131.5064 606.1778 null]
+>> endobj
+5056 0 obj <<
+/D [5046 0 R /XYZ 71.731 598.9799 null]
+>> endobj
+5057 0 obj <<
+/D [5046 0 R /XYZ 71.731 593.9986 null]
+>> endobj
+2028 0 obj <<
+/D [5046 0 R /XYZ 71.731 544.9076 null]
+>> endobj
+1074 0 obj <<
+/D [5046 0 R /XYZ 109.9273 531.9562 null]
+>> endobj
+5058 0 obj <<
+/D [5046 0 R /XYZ 71.731 524.7583 null]
+>> endobj
+5059 0 obj <<
+/D [5046 0 R /XYZ 71.731 519.7769 null]
+>> endobj
+5060 0 obj <<
+/D [5046 0 R /XYZ 71.731 486.128 null]
+>> endobj
+1078 0 obj <<
+/D [5046 0 R /XYZ 86.6464 433.8152 null]
+>> endobj
+2117 0 obj <<
+/D [5046 0 R /XYZ 71.731 423.2278 null]
+>> endobj
+1082 0 obj <<
+/D [5046 0 R /XYZ 202.5889 410.5345 null]
+>> endobj
+5061 0 obj <<
+/D [5046 0 R /XYZ 71.731 403.491 null]
+>> endobj
+5062 0 obj <<
+/D [5046 0 R /XYZ 71.731 398.5096 null]
+>> endobj
+5063 0 obj <<
+/D [5046 0 R /XYZ 71.731 398.5096 null]
+>> endobj
+5064 0 obj <<
+/D [5046 0 R /XYZ 257.3634 374.669 null]
+>> endobj
+5065 0 obj <<
+/D [5046 0 R /XYZ 71.731 349.2643 null]
+>> endobj
+1086 0 obj <<
+/D [5046 0 R /XYZ 127.0732 336.3128 null]
+>> endobj
+5066 0 obj <<
+/D [5046 0 R /XYZ 71.731 329.2693 null]
+>> endobj
+5067 0 obj <<
+/D [5046 0 R /XYZ 71.731 324.288 null]
+>> endobj
+2662 0 obj <<
+/D [5046 0 R /XYZ 71.731 262.0912 null]
+>> endobj
+1090 0 obj <<
+/D [5046 0 R /XYZ 248.6554 249.1397 null]
+>> endobj
+5068 0 obj <<
+/D [5046 0 R /XYZ 71.731 242.0962 null]
+>> endobj
+5069 0 obj <<
+/D [5046 0 R /XYZ 71.731 237.1149 null]
+>> endobj
+5070 0 obj <<
+/D [5046 0 R /XYZ 71.731 237.1149 null]
+>> endobj
+5071 0 obj <<
+/D [5046 0 R /XYZ 180.012 226.2257 null]
+>> endobj
+5072 0 obj <<
+/D [5046 0 R /XYZ 118.4953 213.2742 null]
+>> endobj
+3077 0 obj <<
+/D [5046 0 R /XYZ 71.731 187.8695 null]
+>> endobj
+5073 0 obj <<
+/D [5046 0 R /XYZ 71.731 187.8695 null]
+>> endobj
+1094 0 obj <<
+/D [5046 0 R /XYZ 109.3898 174.9181 null]
+>> endobj
+5074 0 obj <<
+/D [5046 0 R /XYZ 71.731 169.7575 null]
+>> endobj
+5075 0 obj <<
+/D [5046 0 R /XYZ 71.731 164.7761 null]
+>> endobj
+5076 0 obj <<
+/D [5046 0 R /XYZ 109.5683 153.2991 null]
+>> endobj
+5045 0 obj <<
+/Font << /F23 1214 0 R /F27 1222 0 R /F33 1322 0 R /F60 2540 0 R /F35 1604 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+5079 0 obj <<
+/Length 1389
+/Filter /FlateDecode
+>>
+stream
+xڕWKs6 WVy&V%Jv6ӝ큖h[IԒT2/@P~d5@@NW UEb&8[]:] 1㩀gE\L6J?}=+VU\ {گ4\d"),ϊSWҭ7'4vqT'ռ ud
+컲chx_p݇wi~@9
+,>
+8RP17)F$Sڱ VOVƝUU`qIc
+endobj
+5078 0 obj <<
+/Type /Page
+/Contents 5079 0 R
+/Resources 5077 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 5104 0 R
+>> endobj
+5080 0 obj <<
+/D [5078 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+5081 0 obj <<
+/D [5078 0 R /XYZ 71.731 741.2204 null]
+>> endobj
+5082 0 obj <<
+/D [5078 0 R /XYZ 527.5668 708.3437 null]
+>> endobj
+5083 0 obj <<
+/D [5078 0 R /XYZ 71.731 691.2429 null]
+>> endobj
+5084 0 obj <<
+/D [5078 0 R /XYZ 194.7222 681.7434 null]
+>> endobj
+5085 0 obj <<
+/D [5078 0 R /XYZ 71.731 606.3263 null]
+>> endobj
+1098 0 obj <<
+/D [5078 0 R /XYZ 86.6464 554.0134 null]
+>> endobj
+3902 0 obj <<
+/D [5078 0 R /XYZ 71.731 543.6842 null]
+>> endobj
+1102 0 obj <<
+/D [5078 0 R /XYZ 109.9275 530.7327 null]
+>> endobj
+5086 0 obj <<
+/D [5078 0 R /XYZ 71.731 525.627 null]
+>> endobj
+5087 0 obj <<
+/D [5078 0 R /XYZ 71.731 520.6456 null]
+>> endobj
+5088 0 obj <<
+/D [5078 0 R /XYZ 408.8762 494.8672 null]
+>> endobj
+5089 0 obj <<
+/D [5078 0 R /XYZ 91.6563 481.9158 null]
+>> endobj
+5090 0 obj <<
+/D [5078 0 R /XYZ 71.731 456.5111 null]
+>> endobj
+1106 0 obj <<
+/D [5078 0 R /XYZ 126.3357 443.5596 null]
+>> endobj
+5091 0 obj <<
+/D [5078 0 R /XYZ 71.731 438.4539 null]
+>> endobj
+5092 0 obj <<
+/D [5078 0 R /XYZ 71.731 433.4725 null]
+>> endobj
+5093 0 obj <<
+/D [5078 0 R /XYZ 71.731 358.8772 null]
+>> endobj
+1110 0 obj <<
+/D [5078 0 R /XYZ 87.8032 306.5644 null]
+>> endobj
+5094 0 obj <<
+/D [5078 0 R /XYZ 71.731 295.977 null]
+>> endobj
+1114 0 obj <<
+/D [5078 0 R /XYZ 106.9586 283.2837 null]
+>> endobj
+5095 0 obj <<
+/D [5078 0 R /XYZ 71.731 276.2401 null]
+>> endobj
+5096 0 obj <<
+/D [5078 0 R /XYZ 71.731 271.2588 null]
+>> endobj
+5097 0 obj <<
+/D [5078 0 R /XYZ 135.3051 260.3696 null]
+>> endobj
+5098 0 obj <<
+/D [5078 0 R /XYZ 477.1051 247.4182 null]
+>> endobj
+5099 0 obj <<
+/D [5078 0 R /XYZ 91.6563 234.4667 null]
+>> endobj
+5100 0 obj <<
+/D [5078 0 R /XYZ 71.731 211.5527 null]
+>> endobj
+1118 0 obj <<
+/D [5078 0 R /XYZ 83.217 159.2398 null]
+>> endobj
+5101 0 obj <<
+/D [5078 0 R /XYZ 71.731 148.6525 null]
+>> endobj
+1122 0 obj <<
+/D [5078 0 R /XYZ 121.7728 135.9591 null]
+>> endobj
+5102 0 obj <<
+/D [5078 0 R /XYZ 71.731 128.9156 null]
+>> endobj
+5103 0 obj <<
+/D [5078 0 R /XYZ 71.731 123.9343 null]
+>> endobj
+5077 0 obj <<
+/Font << /F27 1222 0 R /F23 1214 0 R /F44 2021 0 R /F35 1604 0 R /F33 1322 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+5107 0 obj <<
+/Length 1383
+/Filter /FlateDecode
+>>
+stream
+xڭ]o6=2Q"%#o-[aZm*%7px#F x<O|/R3H(p/c @2JYdυ<\D!Kd&Dr9ʿ?XƜ pወ,LEZ,}K_BϨv;~INg9BF _ʿ^I5M2Hiju7蒠t#nOauK!'s[]7r]Ken{$=_]DP<|
+e*LeEZᙷ j+G A] e}ҏ#iSt֗DӶ*DmUuC Ă@ʀoUBQ*A"
+ { hֵ67-^5}Vcյ3bܩvч{t%:^qk+ +]'K]W_?6zv幨! 
+O(f)#8EE2'?~}k/xlY+v͈@FU}E\,dCbgkl:DsN3|ws{rXS\<q^|d!Sf2=Fa
+tFƘ͆/V%x[w
+9"Lag
+
+-iro[=v"5.Jb~\B⹉97ݨe7hH?Y&C7%" c`0Eb Vsyk_uVC2.ȵV-x7kCp}ĊWpwwmӵEf⿌ wNYG\-+CHٙ=mbuz8&1pIhABCNպ7&f0q!Ws1d۝v (k"x 6&R}z
+endobj
+5106 0 obj <<
+/Type /Page
+/Contents 5107 0 R
+/Resources 5105 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 5104 0 R
+/Annots [ 5117 0 R ]
+>> endobj
+5117 0 obj <<
+/Type /Annot
+/Border[0 0 0]/H/I/C[1 0 0]
+/Rect [221.9349 501.5772 256.6546 512.4811]
+/Subtype /Link
+/A << /S /GoTo /D (gloss-rdbms) >>
+>> endobj
+5108 0 obj <<
+/D [5106 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+1126 0 obj <<
+/D [5106 0 R /XYZ 88.9395 651.0495 null]
+>> endobj
+5109 0 obj <<
+/D [5106 0 R /XYZ 71.731 640.7203 null]
+>> endobj
+1130 0 obj <<
+/D [5106 0 R /XYZ 193.5729 627.7689 null]
+>> endobj
+5110 0 obj <<
+/D [5106 0 R /XYZ 71.731 620.5709 null]
+>> endobj
+5111 0 obj <<
+/D [5106 0 R /XYZ 71.731 615.5896 null]
+>> endobj
+5112 0 obj <<
+/D [5106 0 R /XYZ 488.7181 604.8548 null]
+>> endobj
+5113 0 obj <<
+/D [5106 0 R /XYZ 91.6563 566.0005 null]
+>> endobj
+5114 0 obj <<
+/D [5106 0 R /XYZ 71.731 540.5958 null]
+>> endobj
+1134 0 obj <<
+/D [5106 0 R /XYZ 106.052 527.6443 null]
+>> endobj
+5115 0 obj <<
+/D [5106 0 R /XYZ 71.731 520.6008 null]
+>> endobj
+5116 0 obj <<
+/D [5106 0 R /XYZ 71.731 515.6195 null]
+>> endobj
+5118 0 obj <<
+/D [5106 0 R /XYZ 444.2551 504.7302 null]
+>> endobj
+5119 0 obj <<
+/D [5106 0 R /XYZ 71.731 489.622 null]
+>> endobj
+5120 0 obj <<
+/D [5106 0 R /XYZ 71.731 474.678 null]
+>> endobj
+5121 0 obj <<
+/D [5106 0 R /XYZ 71.731 474.678 null]
+>> endobj
+5122 0 obj <<
+/D [5106 0 R /XYZ 71.731 461.7266 null]
+>> endobj
+5123 0 obj <<
+/D [5106 0 R /XYZ 111.5816 445.9507 null]
+>> endobj
+5124 0 obj <<
+/D [5106 0 R /XYZ 71.731 433.8312 null]
+>> endobj
+5125 0 obj <<
+/D [5106 0 R /XYZ 71.731 433.8312 null]
+>> endobj
+5126 0 obj <<
+/D [5106 0 R /XYZ 71.731 420.8798 null]
+>> endobj
+5127 0 obj <<
+/D [5106 0 R /XYZ 111.5816 405.1038 null]
+>> endobj
+5128 0 obj <<
+/D [5106 0 R /XYZ 315.2763 405.1038 null]
+>> endobj
+5129 0 obj <<
+/D [5106 0 R /XYZ 71.731 392.9844 null]
+>> endobj
+5130 0 obj <<
+/D [5106 0 R /XYZ 71.731 392.9844 null]
+>> endobj
+5131 0 obj <<
+/D [5106 0 R /XYZ 71.731 380.0329 null]
+>> endobj
+5132 0 obj <<
+/D [5106 0 R /XYZ 111.5816 364.257 null]
+>> endobj
+5133 0 obj <<
+/D [5106 0 R /XYZ 71.731 341.3429 null]
+>> endobj
+1138 0 obj <<
+/D [5106 0 R /XYZ 85.5101 289.0301 null]
+>> endobj
+2633 0 obj <<
+/D [5106 0 R /XYZ 71.731 278.7009 null]
+>> endobj
+1142 0 obj <<
+/D [5106 0 R /XYZ 176.6962 265.7494 null]
+>> endobj
+5134 0 obj <<
+/D [5106 0 R /XYZ 71.731 258.5515 null]
+>> endobj
+5135 0 obj <<
+/D [5106 0 R /XYZ 71.731 253.5702 null]
+>> endobj
+5136 0 obj <<
+/D [5106 0 R /XYZ 71.731 253.5702 null]
+>> endobj
+3304 0 obj <<
+/D [5106 0 R /XYZ 71.731 217.4306 null]
+>> endobj
+1146 0 obj <<
+/D [5106 0 R /XYZ 109.1703 204.4792 null]
+>> endobj
+5137 0 obj <<
+/D [5106 0 R /XYZ 71.731 199.3734 null]
+>> endobj
+5138 0 obj <<
+/D [5106 0 R /XYZ 71.731 194.3921 null]
+>> endobj
+5105 0 obj <<
+/Font << /F23 1214 0 R /F27 1222 0 R /F35 1604 0 R /F33 1322 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+5142 0 obj <<
+/Length 1305
+/Filter /FlateDecode
+>>
+stream
+xڍWo6_a`/23h}-i:=,{eZ"*I5M(Ʉ5yd_*VpXҊy"^5 " *<gi@,H7/XUjsfwquUŪd)R\2jw'q$Mw;g lӌ6iʪdx[e[o8FF9'") ۿe$#F;ei E;IhtcDOD'fdD[|6X 8uA/@:ɢoj:K
+LŪS\mU5 $&"@ ;L}.iFC.0dw EK7N]8?ϲxEN+ 5p GdKoY,kO!V3(@J.{"=|@M,Z:߻VuḞ#8$tÃ̢`}Zu>=\Rj~ṚWc[,b WtY,OM4 _wF/5N2
+aeĺE9`y)?z %q 1;uG=0! (k
+endobj
+5141 0 obj <<
+/Type /Page
+/Contents 5142 0 R
+/Resources 5140 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 5104 0 R
+>> endobj
+5143 0 obj <<
+/D [5141 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+5144 0 obj <<
+/D [5141 0 R /XYZ 71.731 718.3063 null]
+>> endobj
+1150 0 obj <<
+/D [5141 0 R /XYZ 90.2612 708.3437 null]
+>> endobj
+5145 0 obj <<
+/D [5141 0 R /XYZ 71.731 703.2379 null]
+>> endobj
+5146 0 obj <<
+/D [5141 0 R /XYZ 71.731 698.2566 null]
+>> endobj
+5147 0 obj <<
+/D [5141 0 R /XYZ 134.824 659.5268 null]
+>> endobj
+5148 0 obj <<
+/D [5141 0 R /XYZ 71.731 636.6127 null]
+>> endobj
+1154 0 obj <<
+/D [5141 0 R /XYZ 87.8032 584.2999 null]
+>> endobj
+5149 0 obj <<
+/D [5141 0 R /XYZ 71.731 572.8966 null]
+>> endobj
+1158 0 obj <<
+/D [5141 0 R /XYZ 86.6748 561.0192 null]
+>> endobj
+5150 0 obj <<
+/D [5141 0 R /XYZ 71.731 555.5199 null]
+>> endobj
+5151 0 obj <<
+/D [5141 0 R /XYZ 71.731 550.5386 null]
+>> endobj
+5152 0 obj <<
+/D [5141 0 R /XYZ 71.731 550.5386 null]
+>> endobj
+5153 0 obj <<
+/D [5141 0 R /XYZ 119.8414 538.1051 null]
+>> endobj
+5154 0 obj <<
+/D [5141 0 R /XYZ 167.6439 538.1051 null]
+>> endobj
+5155 0 obj <<
+/D [5141 0 R /XYZ 249.4106 538.1051 null]
+>> endobj
+5156 0 obj <<
+/D [5141 0 R /XYZ 442.1221 512.2022 null]
+>> endobj
+5157 0 obj <<
+/D [5141 0 R /XYZ 71.731 476.3367 null]
+>> endobj
+1162 0 obj <<
+/D [5141 0 R /XYZ 86.6464 424.0239 null]
+>> endobj
+5139 0 obj <<
+/D [5141 0 R /XYZ 71.731 413.6946 null]
+>> endobj
+1166 0 obj <<
+/D [5141 0 R /XYZ 269.3776 400.7432 null]
+>> endobj
+5158 0 obj <<
+/D [5141 0 R /XYZ 71.731 393.5453 null]
+>> endobj
+5159 0 obj <<
+/D [5141 0 R /XYZ 71.731 388.564 null]
+>> endobj
+5160 0 obj <<
+/D [5141 0 R /XYZ 71.731 339.473 null]
+>> endobj
+1170 0 obj <<
+/D [5141 0 R /XYZ 165.299 326.5215 null]
+>> endobj
+5161 0 obj <<
+/D [5141 0 R /XYZ 71.731 319.3236 null]
+>> endobj
+5162 0 obj <<
+/D [5141 0 R /XYZ 71.731 314.3423 null]
+>> endobj
+5163 0 obj <<
+/D [5141 0 R /XYZ 476.5536 303.6075 null]
+>> endobj
+5164 0 obj <<
+/D [5141 0 R /XYZ 71.731 267.742 null]
+>> endobj
+1174 0 obj <<
+/D [5141 0 R /XYZ 85.5101 215.4291 null]
+>> endobj
+3903 0 obj <<
+/D [5141 0 R /XYZ 71.731 204.8418 null]
+>> endobj
+1178 0 obj <<
+/D [5141 0 R /XYZ 107.2772 192.1484 null]
+>> endobj
+5165 0 obj <<
+/D [5141 0 R /XYZ 71.731 187.0426 null]
+>> endobj
+5166 0 obj <<
+/D [5141 0 R /XYZ 71.731 182.0613 null]
+>> endobj
+5167 0 obj <<
+/D [5141 0 R /XYZ 382.967 156.2829 null]
+>> endobj
+5140 0 obj <<
+/Font << /F23 1214 0 R /F27 1222 0 R /F33 1322 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+5170 0 obj <<
+/Length 1975
+/Filter /FlateDecode
+>>
+stream
+xڕM4>*1 'fvZng+9גy~=
+d5"BC1?ލ8XTL- "݁?Ɠ|:i4p2 dh/ =:/B5DgN
+|?t~oD6ڞss/AAX#r3bkV ףؗ]p! :>9&.P[>Bv;нzP
+84X^avsQ#7"L[Ͻ/̦W
+endobj
+5169 0 obj <<
+/Type /Page
+/Contents 5170 0 R
+/Resources 5168 0 R
+/MediaBox [0 0 609.7136 789.0411]
+/Parent 5104 0 R
+>> endobj
+5171 0 obj <<
+/D [5169 0 R /XYZ 71.731 729.2652 null]
+>> endobj
+5172 0 obj <<
+/D [5169 0 R /XYZ 71.731 718.3063 null]
+>> endobj
+5173 0 obj <<
+/D [5169 0 R /XYZ 71.731 718.3063 null]
+>> endobj
+1182 0 obj <<
+/D [5169 0 R /XYZ 103.2824 708.3437 null]
+>> endobj
+5174 0 obj <<
+/D [5169 0 R /XYZ 71.731 703.2379 null]
+>> endobj
+5175 0 obj <<
+/D [5169 0 R /XYZ 71.731 698.2566 null]
+>> endobj
+5176 0 obj <<
+/D [5169 0 R /XYZ 71.731 698.2566 null]
+>> endobj
+5177 0 obj <<
+/D [5169 0 R /XYZ 166.8364 685.4296 null]
+>> endobj
+5178 0 obj <<
+/D [5169 0 R /XYZ 408.4751 672.4782 null]
+>> endobj
+5179 0 obj <<
+/D [5169 0 R /XYZ 243.4665 659.5268 null]
+>> endobj
+5180 0 obj <<
+/D [5169 0 R /XYZ 246.8008 659.5268 null]
+>> endobj
+5181 0 obj <<
+/D [5169 0 R /XYZ 298.9104 659.5268 null]
+>> endobj
+5182 0 obj <<
+/D [5169 0 R /XYZ 448.559 659.5268 null]
+>> endobj
+5183 0 obj <<
+/D [5169 0 R /XYZ 164.884 646.5753 null]
+>> endobj
+5184 0 obj <<
+/D [5169 0 R /XYZ 481.1574 646.5753 null]
+>> endobj
+5185 0 obj <<
+/D [5169 0 R /XYZ 132.3631 633.6239 null]
+>> endobj
+5186 0 obj <<
+/D [5169 0 R /XYZ 71.731 610.7098 null]
+>> endobj
+1186 0 obj <<
+/D [5169 0 R /XYZ 84.3534 558.397 null]
+>> endobj
+5187 0 obj <<
+/D [5169 0 R /XYZ 71.731 548.0677 null]
+>> endobj
+1190 0 obj <<
+/D [5169 0 R /XYZ 150.0465 535.1163 null]
+>> endobj
+5188 0 obj <<
+/D [5169 0 R /XYZ 71.731 527.9184 null]
+>> endobj
+5189 0 obj <<
+/D [5169 0 R /XYZ 71.731 522.9371 null]
+>> endobj
+5190 0 obj <<
+/D [5169 0 R /XYZ 192.9628 499.2508 null]
+>> endobj
+5191 0 obj <<
+/D [5169 0 R /XYZ 71.731 447.9432 null]
+>> endobj
+1194 0 obj <<
+/D [5169 0 R /XYZ 193.2643 434.9918 null]
+>> endobj
+5192 0 obj <<
+/D [5169 0 R /XYZ 71.731 427.7939 null]
+>> endobj
+5193 0 obj <<
+/D [5169 0 R /XYZ 71.731 422.8125 null]
+>> endobj
+5194 0 obj <<
+/D [5169 0 R /XYZ 71.731 363.2608 null]
+>> endobj
+1198 0 obj <<
+/D [5169 0 R /XYZ 84.3534 310.9479 null]
+>> endobj
+5195 0 obj <<
+/D [5169 0 R /XYZ 71.731 300.6187 null]
+>> endobj
+1202 0 obj <<
+/D [5169 0 R /XYZ 163.9645 287.6672 null]
+>> endobj
+5196 0 obj <<
+/D [5169 0 R /XYZ 71.731 280.4693 null]
+>> endobj
+5197 0 obj <<
+/D [5169 0 R /XYZ 71.731 275.488 null]
+>> endobj
+5198 0 obj <<
+/D [5169 0 R /XYZ 71.731 249.6449 null]
+>> endobj
+5199 0 obj <<
+/D [5169 0 R /XYZ 71.731 239.6823 null]
+>> endobj
+5200 0 obj <<
+/D [5169 0 R /XYZ 71.731 176.6352 null]
+>> endobj
+5201 0 obj <<
+/D [5169 0 R /XYZ 469.8557 143.6075 null]
+>> endobj
+5168 0 obj <<
+/Font << /F23 1214 0 R /F27 1222 0 R /F33 1322 0 R >>
+/ProcSet [ /PDF /Text ]
+>> endobj
+2679 0 obj <<
+/Length1 1605
+/Length2 1380
+/Length3 532
+/Length 2205
+/Filter /FlateDecode
+>>
+stream
+xT{<TiKiVK䖹5\6K9fq2Ι9je#RzMU,X*3S[cyg3{y<Ֆ<,^%t
+2ET-y*Ea@F g
+;0uwqH
+XBP
+!HBĄ8(CDPV|`a>0AP2|:u b7z_B Nc0Ai + oqH 2}w ̨: 
+$Gӿ*ә ,$~; 0zfY.8P4eo¿@ш@1ZV|
+XڲQ~7
+OFA(o|E38k\|-7}(?I?*] +1| &,`ZlXe(TX+!G R
+'xr6
+@+q)>
+ X+ a..Iؐu<=;J[5,+ۅ%~\i!V5_ ƊK^4?0ﮯTΉov)wz
+[xFh}I:Tlݝ BM4hIoʕZTq&ErZҌy>{jRwՃj-L6^IuҍGGTGs,Sv]Qph/mlX;-?ד73ڶנcg 5V1gLbWU5g6R55t0sst-'#Njg%tU[#ݲvۣcDN0uWieϔp0^vi5bskfv
+RK} ~plcu^2|Fܯs^VWX\t7Ϭ\bB֘x ޝu6[Y\ U`KY\%Ƭ9D&şVjRqiʮYN1I'GY:Iʉk9ۇIWg5-ӳ^TJ d?K̭?ʴ)p%ڞ&%ݥ-{Y40xtqOflT=9Dyj,<c`ls-DC]"ٞ7&?55M}ej%^\w2rO CfڅZoe(ݽyNʙV{Sx7wSQYmx *ўAĆђuGJ39Gs5g?,]?vꇴuC{/tiVwϮ$z5uf/fz)UD4<
+endobj
+2680 0 obj <<
+/Type /Font
+/Subtype /Type1
+/Encoding 5202 0 R
+/FirstChar 202
+/LastChar 204
+/Widths 5203 0 R
+/BaseFont /NKXBQA+Dingbats
+/FontDescriptor 2678 0 R
+>> endobj
+2678 0 obj <<
+/Ascent 708
+/CapHeight 708
+/Descent 0
+/FontName /NKXBQA+Dingbats
+/ItalicAngle 0
+/StemV 0
+/XHeight 400
+/FontBBox [-1 -143 981 819]
+/Flags 4
+/CharSet (/a150/a151/a152)
+/FontFile 2679 0 R
+>> endobj
+5203 0 obj
+[788 788 788 ]
+endobj
+5202 0 obj <<
+/Type /Encoding
+/Differences [ 0 /.notdef 202/a150/a151/a152 205/.notdef]
+>> endobj
+5204 0 obj <<
+/Type /Encoding
+/Differences [ 0 /.notdef 1/dotaccent/fi/fl/fraction/hungarumlaut/Lslash/lslash/ogonek/ring 10/.notdef 11/breve/minus 13/.notdef 14/Zcaron/zcaron/caron/dotlessi/dotlessj/ff/ffi/ffl/notequal/infinity/lessequal/greaterequal/partialdiff/summation/product/pi/grave/quotesingle/space/exclam/quotedbl/numbersign/dollar/percent/ampersand/quoteright/parenleft/parenright/asterisk/plus/comma/hyphen/period/slash/zero/one/two/three/four/five/six/seven/eight/nine/colon/semicolon/less/equal/greater/question/at/A/B/C/D/E/F/G/H/I/J/K/L/M/N/O/P/Q/R/S/T/U/V/W/X/Y/Z/bracketleft/backslash/bracketright/asciicircum/underscore/quoteleft/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/braceleft/bar/braceright/asciitilde 127/.notdef 128/Euro/integral/quotesinglbase/florin/quotedblbase/ellipsis/dagger/daggerdbl/circumflex/perthousand/Scaron/guilsinglleft/OE/Omega/radical/approxequal 144/.notdef 147/quotedblleft/quotedblright/bullet/endash/emdash/tilde/trademark/scaron/guilsinglright/oe/Delta/lozenge/Ydieresis 160/.notdef 161/exclamdown/cent/sterling/currency/yen/brokenbar/section/dieresis/copyright/ordfeminine/guillemotleft/logicalnot/hyphen/registered/macron/degree/plusminus/twosuperior/threesuperior/acute/mu/paragraph/periodcentered/cedilla/onesuperior/ordmasculine/guillemotright/onequarter/onehalf/threequarters/questiondown/Agrave/Aacute/Acircumflex/Atilde/Adieresis/Aring/AE/Ccedilla/Egrave/Eacute/Ecircumflex/Edieresis/Igrave/Iacute/Icircumflex/Idieresis/Eth/Ntilde/Ograve/Oacute/Ocircumflex/Otilde/Odieresis/multiply/Oslash/Ugrave/Uacute/Ucircumflex/Udieresis/Yacute/Thorn/germandbls/agrave/aacute/acircumflex/atilde/adieresis/aring/ae/ccedilla/egrave/eacute/ecircumflex/edieresis/igrave/iacute/icircumflex/idieresis/eth/ntilde/ograve/oacute/ocircumflex/otilde/odieresis/divide/oslash/ugrave/uacute/ucircumflex/udieresis/yacute/thorn/ydieresis]
+>> endobj
+2539 0 obj <<
+/Length1 1606
+/Length2 14569
+/Length3 532
+/Length 15415
+/Filter /FlateDecode
+>>
+stream
+xwUp]˒e13333X333Z`133bb ~o&D]Y+Wʪ؛XI^(aoBP5vu11Ñ:\,Č\<
+_@; @ gisR
+4cTw@SOW_&j<+s
+KJ_II{Ĩ_q}SnBIpwRYE ` :ԭ
+_l捕ո!{gpW2gc,dl ֠[`(>,u AsȫjslsTdw^sMMSx9I1T
+0-HC [QܓDc Zaη~Gf7c]ǨuG# pKC=ћ
+t]vܻi:@ tɺňni;szD팆s!!abA 
+C7}s0_4T.RN38G6>|[Q(j{p5G<W>FE'{er= l}iٽ.YK~#BN ۨ]8/a2qkr Wd9aRBH
+Pp4~`q%sZ1sԶu~'.p{<YF-_ Q WH>S$a w#KySLﲄ#\5rTV3O%U#Y4?8C&.4
+&`&7onx\6Ρ5"Wr'`^ӋQU _CvIhrPOF#HE{5V Q䩮L
+dBvU)8(1 juƒOA-&DiPW2Wq\f!dV2K:DZ}S~0)6r| 74B% qzRa}rYS rhQT 8.޼TcSmwހ()6CSѳېʇ`3WH(8J T_G=Ě5Ԣ>"~7䟔HU!c@- #>ZhHyk`3x#v)1}}]>obVԉTpBMv5."Ab1,[wPȖxAlBwe^I3ownS[y@(%{64ZBſ(4 QMyRuF5嘼\7~D[)Gç_Usn8E&RC^FѢ2/g/&7Zx#QZo U0Ɏb@Ky"y4YPk s*tt@M]LGx З8f:&4*RhYy|֫4 sf4Pg o$NYgΙ=e@K"|mH@KfUTijZwCi&zܐ/{>
+=XeE 7 9ٰ+NjErs 1w<@2'
+o?lv^Aqq;n4+ ۴*d0(%N7+@sc_X.<# mrϵ}@% Yr+J3q}el+\,| 'L`:g
+cRpȃ|>y=, ROG͌evv 0JqBfi` O"1\Jz:B4Sv-uQLhbe-2,{JN)$2SV?
+GU<y3V>\+VA^i@Rt#Hzņ 9IXg
+1b-
+~Tc&j'K~4:&[w"AhSwlgvoteuӗ7s Ljў%b]f GV3i tEPļؽesC0*[Y9Qa$h.Tzm{a zdr''?&9&380lu[GCX<.cod{4"TO0!U{ swe:DvUV4
+XțoQJXc'pDT2⵳5~!qߵ}GRHMM))cMʏƇa \j:Qtiy:p@=[شCJL󄫇&OsR&eIVKk(n1zn+hSM6<yMހdE3V,݌ Wƫ&m
+3l9U,}&DyȔ"iN0aRJyGYK}mfq##NFrF+i(}!x)+cX[kX~=RݮT2Z{sʈң=(;h g@8ɟHքÓ4qw0S
+<ortڮ֤#\e_MVam.Qع+
+cNZ[:*0̞b.^7JrLO.e/QI|K:ezX#GƘtM AFL18|^$ T]mRG`Vi/>
+h)oNYkBN҆XqةxZezOKiv]pMβvYd>AҎ7tDNSv52L=Sy2d1<1 ֥ Y\v]FZkR
+xQe~[}ml$K.INgDBML"IYܲaOuWN*9AU_mg:6GnQGURԼyw\Kփ"Pog}"\'DWND<gz‚;o>pJ[(3P"- zY3lrebG1(XXr;֖CJJ`~d]e}4^!2p{c:%5Lv!k1V_=6dqic^<ŵ]d4PJ\Sn& E68=VX[
+!2 bߛ;#ĮP*W9˗[X
+kҁ_sIFR+7yqϪ{;Q)7i3Mu8&Ƈ nY}p!پ#Qfgnڄ+rZTYޥ3#
+Rɐ4C@YMR-EIlocήMڔg/L|vyE5 w͍Hbf(1or׍(Ԩ)4؈6y9\8q\wvnPתZ km^^ W؃//߿DF'6~+ph / /PN&a[Yd C(KAU=?0;L5 OLp%t_[Ǜ
+WtOOSt-KhO0"X?:!$agm][wZ n<v}+ŀF tb ;կ."n!WdXȬ<eeȮq6pda
+#2߭LZ&tޢMNl"v?oa݁)F!˾ ̒
+v[w
+-RsUKY?ys$2c@ר:ͪG^D#9FcjF,_%iS1l:J[e+f@*V.*jLC~K!Fl/?3ֶ94 .IuICM5tjTޏʽW$!Q)n'4džhJ9.V}'*e ދWmT d`;
+%5j[n/hw)әv]`M=>)]ɷtIjP 706FU>!Z9
+i Lw`;ҕҠ'߀+Be<I& ~nSq.Jj SԐ>׾̪Bk&OB
+_hA2AiC#\J"LUXY^gso%FR "3[0IjL">]_^I\$}>Z8>GG
+3cm)k-* IMnJ7䞳"+b1|~=fG]zԒI A-DXRIzdApbט^"%I,g pwHM5뉏@Mm8mFtƲ_=e4`W2" K^[c?,,8\
+(lUsc;PME !U`%$oz&
+Yɳ r3t+V,W QG׵LV~\">\Ujoݝ5Z>:i<! ̬Icwg‡y77fu4| }lB&g1? D
+jd<CV*/qAr&L11d+[C=<߯g&i4HW.\BRf t-º yFhcl>
+6GMvTXKṵѦkԒCp+|6OK≪O=o
+F9-eIZۃ&xt&/荺)N9ԈSܫn[4J:`Fy4&cü|Ic]!0EB'Y$:1QF/oTgcAV)3 I%͙?xEX
+%]0kb$1xG4Ո-TS~{`F[ gxN8e1QDtف" o/;4eO1ƾ89nNo ۝^p!Kгh^%qJ9) tkUoڤĘg^ׯHs¨$~6VSk˗Ś>lY^H{)vL*+mK8O?8 -VQU)
+IƠF)O7̆I"S6
+ݍrZTEnh,ܪK
+^9̧UV@in~D5/6{
+}&݃b\)ceGhw_m~R/>?0KVtaP$MLa H܁kFQ6k=Yl7Vd} c+,F%TH9"g;Wna M'؀nǑʹLMɺ]"`-"C+|X4y;?)=krEu;F @ܽ& A#cb]~af1+(#n
+T$}rsLmyOY;o-HaiLS8_Gm0ɛX,'j {ƨhNPťϹfeR'0vi~Wkb
+[r! ȅq&1F FSYs;KN Fh2VYr( #<1,3 7} h,kc6cRtI{^H
+DY*rL8?aR.:|&ØV%tȄ
+(^94 &,T$[n~%oDr}GH& wpyÜ01҆&z]Ql] L</8:QiSkȳY>n E08(W@P)t׬2;upܔ nׅ7SVR3o=5w3Re!/ӾJҒ ;iЫPÆpY-MP9}<hƭLB +6wj8r~\#!<lX? Q}YLH_ k&}Y?/=^LSd]dNPluQ)tcjB#l[LD@7/p]FMnp#[/G0"yk͏OR)Ngw8`e/w+lFJS|K4h*N rMȸ
+8ݎ'羚s.!xXy٧'eB$M+vm\]JR%G6! 4jEF4/>J$
+cT1ŝxtrqćTW@zr_T!ɪ2^-F{O/EOOmՒ&˴)&֍5(`D5> ̮鎤1޾ORrfRFz"8h~;dXPHf/Iۖ]Ucxv4' !ݼz _'iS 50nEg㺕Sݍ-{X[՟3d0l?Pl`Ce鲊0n"s&j m9-XV1pu YzI% )pWbP5aT'ϯW
+QԶ]xl Cm}YA7GK}!I- )&Wլ}_CՇi`#[)mF .s3+I<Ь;%Z g"/L)뗜TT]w
+endobj
+2540 0 obj <<
+/Type /Font
+/Subtype /Type1
+/Encoding 5204 0 R
+/FirstChar 38
+/LastChar 122
+/Widths 5205 0 R
+/BaseFont /JVMXTG+NimbusMonL-Bold
+/FontDescriptor 2538 0 R
+>> endobj
+2538 0 obj <<
+/Ascent 623
+/CapHeight 552
+/Descent -126
+/FontName /JVMXTG+NimbusMonL-Bold
+/ItalicAngle 0
+/StemV 101
+/XHeight 439
+/FontBBox [-43 -278 681 871]
+/Flags 4
+/CharSet (/ampersand/asterisk/hyphen/period/slash/zero/one/two/five/eight/colon/less/equal/greater/A/B/C/D/G/I/L/M/N/P/S/U/Z/underscore/a/b/c/d/e/f/g/h/i/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z)
+/FontFile 2539 0 R
+>> endobj
+5205 0 obj
+[600 0 0 0 600 0 0 600 600 600 600 600 600 0 0 600 0 0 600 0 600 0 600 600 600 0 0 600 600 600 600 0 0 600 0 600 0 0 600 600 600 0 600 0 0 600 0 600 0 0 0 0 600 0 0 0 0 600 0 600 600 600 600 600 600 600 600 600 0 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 ]
+endobj
+2304 0 obj <<
+/Length1 1630
+/Length2 8814
+/Length3 532
+/Length 9681
+/Filter /FlateDecode
+>>
+stream
+xweTݒ.!K. 4ҍ,Hpwww $8C\}s:w~͜_w^{S{Ȕ՘D!@)ؙ3uqRT.J +DE%4qA&@~ 4
+
+wzؽbd'g'3G35u:[8zWOs˟^i^Qg tw09ۚx~%wUl
+@KGs[++gҽ_ѐւ5knKϰȀ-
+|o3I
+4W9Y,Ll_/h _k[_XYSـ7krU<&w: @R`bpx|Ѱs`r2^_h$f?l64\_56_c͐ fSSk𲿍Jvl_\^_WnT01{h-K3ԍkK5 xKCAדBðbXzuC~ FUsgcTEհ xΟ5=Yr(#[o_/`{  R  RG;d
+j{3lAFes`YJ=$~ HQb~ۑ!(Su&=_sg&U&[}Rga>ɿ6n2oӟ-˜ڷ" b$JT\y[9y .6fiu}Z='؅jy"d!bM|겏kCxO~*J /JL4_USD \ƩZ^HzJPf3˛g xa8nT'fo/9MQ~⥻/E6 Ѡw=W l_ " !LG#REvKmvd7YFn I]jqQ/M<anWCcP5MHlmR?2U^4"U" * {}8 wcLh0au.ݭ'):BeEzN?:5oׇMf{|-0ds~}3<=2QBTk}Dmj I;:V4MگQ:))p*R ahoҟnP{Ɇ0vqZĪKȳel.ɒr aşyIH*"DQ|Lt|އp%~nTD:&[{0.9 1]@T"<pz}A/
+V_4CY';VAG(r@6Am:tmѲؗcF6.ղoWt#*WI
+v uo ˌR
+E5W<hг]aCƞT]EhOCane<{{TvÄО7`*nn<|q"V~?j&w+/zT9/|}#$gvQP̺qә#ojxw^R;!{ F?L3ij3~쾷*/2ʿ3d x?K;)$x`&lX_BBf2t*GyR2Oo}II]1*)%/} uGὤa3BsaE/3 %QM`ҋS6aB1d `L6[K!Y1=a~t^Y.Z.YA<638Z:uni$uvYg|x<[]יƃi\4J˩u#rcDUHNh8 vQK)vtڵYd"|`FEC&%(ڞ[᭭F5(:8XqE={mIvPTޞXⱈ*-NB <VًB8E ذgZb!j̪ycwF\y)\xDh>X,[/=D}
+v'G+jMՂ} #ٱ20
+0VPy0t9J%{]S{ygb>FvѫTY=Н1xM~]''9"zCT8'c퍋G-h$Yώ]S-È%54XF;Oj>>}y={zN/
+Nrٚ FQД=uiMZi_n$5WpV^w޲Z\X!7yMف2ctK^Hқ5wD28%AVyLCSXAQ-'WΆ#{ZChj՘wJ!bHԓ1$CuI{+,~U"DdW(OrV
+P
+rr-%lݹ|>MQ -i̐^3zfLzVMɗ-f댵]"[*S tf]#qO% nBA73׫
+:f?
+.Cb?wh&\Ɖ"w׺ J@7kRVyԔcJ PCMҔRlwj%L2Qɽ%hXB m~g~{%e-3`41N[2tyT4>06[u𶧪f~vef y-Nз*QЋVҢRU)
+p_lrT3=q(|c:>_*L,Bsgh`h v=a!g\Iv
+zWo]8قQ4^I߷b.łh! o f+߷HMYP:̤ݹV{ΨjHVf)˪0i3 Dr˛_BBݼ~"iv ϛX IZTX?|pf85
+w5ˬF%w!SbTƩǨ
+kCTfF?0Eephi }`@*PI `ES[' .yݾqIa?NWBn?|2QZ» ''
+N
+&g/c\xQ۰ʊ=:5.EXs<QlHoG@g*=@gdouIh]V{N(UNmiwOT,(O\gw C B^xU֥rH#OHE˷Cv-*}
+mNzXAg.ɕ4_1řP3En,0y`F*@"AV#^w# ]ѧ-@rs_hI#&R$s[2\ i $`)4942y$]y)k` q=M]]=}/*E!~, Lc-hx5/FTn8oYx#ɦía6'LG(єV64 ,kzͯ[;KP,L$|UZMO2?buK8Ufp;Φ׌k"PE
+RJ|GXJ5$h޹]^CX8/=ȇ'bkI4@i7}XUh%a-3oЊDfW;Mu C囮0dqþrE^Gj^ʵϛoK}S552HW7ꌘnS1 ?KQShb2c+43uBdֆZZ :}Iʜ]pSORoq+Tw՜θ򫨮J< ;!ΐ c#@ gn?<C u_i${K6Wacb[yVգ٫(u"똙3AV
+'-˧ŤC%ZG8W꧹E~"ә<Doc1ۑZHοfdo1TXuWĒak"Ble%|oۯulhߕ6>5*#ILŠ{f'F,VKC \|h]k!BϢʥr83.Ǎ~x!,͹Q>䱣 Cl5g"?;<9=}&77/QV rLB9OTݔcPHo8Ӷ\s`^r/kdR`rNH|Y;=٩ͳvq>ϣa L/.?I B5wSKHsmdr,xHU8Fj_8o(0%~#,oבjBxe^y"A僕J5,dz#ļ1;bfFgh[s/kp1*wQ7Dj)\MQ,o(}tԎ t@^J_tj'ӛC']HpeN'C0$^j"î5Tyy,sTQq@d:O_I鳠yb/!qVPrYM{D6gY|@>Q4pv8&afp.\ބA wr&83xnD*!{!#?۲җ!E4{̡گnB_q4"8y:~/|b<m͏: ƵM}[HahWG;XB}MOa`=@febEI
+9%'ާw)Mt ;զi?~#zBχOUx;,8|;< ~t=*w\Xua\|9
+TxLB7?MGgIԑonbQ >)&h
+7ae51?x n%2;L LR,8Kc'XWZ|UHo!kmJQ! aTƘg٘r5 l[REt"JSOZp+OT0^GUۓrk~c
+|߭AЦbaaq'i:jgǔۑɍzb,.PFv,މ( @5W|[QpyaA8d#
+ug¿ngF9{+DW$<J_PL`K~+Cyu;Ds;S|KO Tf6)8蔯} MƕAE:P'#Y@3,
+,q%!u2=k0me!3F^0a`97e N \HNq E7L<awc0)Y3[H&Jj$8komE9k^0g9w.}^s`y!Zyl֑븭CկR;T\٦빟(.Cз{Te S>{Y"
+*o*&AOM!v&6H+endstream
+endobj
+2305 0 obj <<
+/Type /Font
+/Subtype /Type1
+/Encoding 5204 0 R
+/FirstChar 36
+/LastChar 121
+/Widths 5206 0 R
+/BaseFont /JRNLXV+NimbusMonL-ReguObli
+/FontDescriptor 2303 0 R
+>> endobj
+2303 0 obj <<
+/Ascent 625
+/CapHeight 557
+/Descent -147
+/FontName /JRNLXV+NimbusMonL-ReguObli
+/ItalicAngle -12
+/StemV 43
+/XHeight 426
+/FontBBox [-61 -237 774 811]
+/Flags 4
+/CharSet (/dollar/quoteright/parenleft/parenright/comma/period/O/underscore/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/r/s/t/u/v/w/y)
+/FontFile 2304 0 R
+>> endobj
+5206 0 obj
+[600 0 0 600 600 600 0 0 600 0 600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 600 0 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 0 600 600 600 600 600 600 0 600 ]
+endobj
+2036 0 obj <<
+/Length1 1199
+/Length2 8891
+/Length3 544
+/Length 9720
+/Filter /FlateDecode
+>>
+stream
+xzU\]-R@qB+ZXHC)PH"ݭ{)+w޻sw~'x5ffMBO.i
+
+>3@ v0cfeeKPP`/ vn`{lO`(T_ ˟L..Bsp2?+ 9!p  zj̓4ܡ Algj
+Vg ?6+  
+
+D/
+`k_ b3F@>==vC9\R
+vv~: 0BA0K@iD6ApNS`0cqiU)Iξ9գ+5/F=:R]o@gakr#dUdİX
+h~w;A64|P|$5'ϙ&N=qQəҞ_iJ ŤG`D/n8RP䔡1 +i
+
+h
+iҲu6 ǮiT.NLPJD"@v?pT㮪ʫSxuWab¹M-޾<?5z u`|| (#
+0qp&z~cqxZD#TڍIW4
+2G#r Ay\]϶:3/<il e0RDwn?v+ytgJ0JZTn&ozipji:PQ2ȃ+@ǎ_ $*DԒ
+󣽤l`ėjP)s[4&CJ<
+,xⓇ:Gigb?&_*r |X^ y(-h5B2r&STEbT_*`Qu\hZ :Fc 4 ̼`6B-"JTMI©ri *PరkQzM `Z^yn
+b@)bfos{GXJJ*^=̃!2B H;H!IĆͷ-sSl:^p.<[xT0R$;mӅ
+u`Lz~hJÿ7镬0c=f+lh܁T۴UfrO&$`g%[t-Gc9oU|zߡ/M(? o"^Ql|(գx1ϛGbcd|}Au욌q%f{p,$\EJ]+oUW&FiZII=K|E",#WzU청"Dg4 qbX_Lc]#S+ V+^oNpZRprc+Vα(vdkEOjem WȒڝPxEe.0uɺBT6Jƴ ^SCƔoUI]#ݟ1d쁳A-MvUqDyH\e]
+3l_!Es
+Dױj)(V7'o>q ak*< zivIˢ h׫ WW<Hp7#(ΣR_%kN&c{I
+#?d8@RDtO{/{%26V3y#;/: &D*9#~WLX8%7WMհ4+zP£\a6J?8ja2\ˆ4: T@Iq?CVB$E4}=4qU"E*HGuk
+f8NT)C<Qpi",m'&5]uqlGyG.Mx6"ڛr$+s1*`3
+6kBӰ9oAbѮccyC;ZIșvIAQa{Kԅ9㴃
+Yѕ8F走
+1{ %h[ioeKm$H}wA_m8rʱF4}ri'2텴 du6(< 7"`Vs7up9\RK좗L˄MTpS]2WCli7='lÕ0FdubY6܏P0}fVB[9I}]Cܼ=}5idst0:&ro%XJ!y&v
+w_,vl^sTeE.w=A'*쮥ۺZT8]MҲLrM?.AR9xygnֹS :OQ[Ob*v1nAif)ڹj"3#bxJ7xv8m=٭@zת@跱c.~; <w^jGeX,jt; WuaE|:ڡWÝ,NNbGi'±WUL +vUvkm-^~>~a5<1M+z ~ }scG2AEjK&-Vm\ RZ'osS`fވ@:\/_7ҟPш?jPvMԻ
+4•(y2e_}6â)F*K3'Py1| & 6n~3T>%ʛUT74"Fc|el݈B[b㜰ʯ^&fyfY=r>3t՗efxRDeqWu~6٩3^IsknpT뫸gߕu'*֭rY]@ (dZ/. :}"j
+#bV:r`]RzrO*-=q]BHJrb+[;w^o'لHڌ(PE:E*C&/taps\ i
+iSWEi_)嶒=t%愑(W[jQq5)xlc[kshE6Q{
+0-!b3ƊSZ=S: BK8!\|ƶ{"ȻD
+1QQў8M~uWGk_ dZ4>:?B%D^KD;T֝+p sZqY_a{53hyhcA4<玐j9 ?E-b e<nvk(h_APоa[WKC J;/?u]O>#qy^on~j%V#sa 4{/&ⳛde@"Yh⩸ \\z[gF.s>7ϟE?v
+Bc.8 gu4Hx?Lz[ԙlgʋ8QE`$dJ~$̙U S'BCpw ,D4F(D~[u@Dn/fVYJ:igscҧ I1sYZwNC85*\)4ʍtbޱ n,'xVɤ6n*{sѦث8twس{wf<K
+r.!<ԸkeUݢzAlL<l#
+1tg5$I))=¢pמ.2/:F'hĸ7b䠔CWs9sn##Wclev?yd;nnLxRܮJ}đPW[ }uÉ9`'@`s n ;y
+endobj
+2037 0 obj <<
+/Type /Font
+/Subtype /Type1
+/Encoding 5204 0 R
+/FirstChar 2
+/LastChar 122
+/Widths 5207 0 R
+/BaseFont /NCDUWX+NimbusSanL-ReguItal
+/FontDescriptor 2035 0 R
+>> endobj
+2035 0 obj <<
+/Ascent 712
+/CapHeight 712
+/Descent -213
+/FontName /NCDUWX+NimbusSanL-ReguItal
+/ItalicAngle -12
+/StemV 88
+/XHeight 523
+/FontBBox [-178 -284 1108 953]
+/Flags 4
+/CharSet (/fi/quoteright/hyphen/period/zero/one/two/three/four/five/six/seven/eight/nine/A/B/C/D/G/K/L/M/N/O/P/Q/R/S/T/U/W/underscore/a/b/c/d/e/f/g/h/i/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z)
+/FontFile 2036 0 R
+>> endobj
+5207 0 obj
+[500 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 222 0 0 0 0 0 333 278 0 556 556 556 556 556 556 556 556 556 556 0 0 0 0 0 0 0 667 667 722 722 0 0 778 0 0 0 667 556 833 722 778 667 778 722 667 611 722 0 944 0 0 0 0 0 0 0 556 0 556 556 500 556 556 278 556 556 222 0 0 222 833 556 556 556 556 333 500 278 556 500 722 500 500 500 ]
+endobj
+2020 0 obj <<
+/Length1 1166
+/Length2 11097
+/Length3 544
+/Length 11916
+/Filter /FlateDecode
+>>
+stream
+x{U\_%Npwww ݡB
++<xNpww 2w}i7U9ko٧ފBUE p
+YJ9:%g@ş7;?`+ o-Y9in
+! y99
+&ppi\](
+]wr긍h24dhL4d+<v|)k %
+}F&#i$[:,TD%`.ecEA|Ў%\a mWPRLZ+ 1J,h,C E!nˉ {5S1Y&4t -"Wo~+avgOfGnnф?銪dڠjsIݖ |7+,)*"26{SwW3[zطԃ͕gڈ?zDkg&c.xR<Vz܏=̭#>zƧcPϾ?BzP~ ,cc(ۛABYkOYe !Ńkqjc>:"?{g*NwP0FoYG%(c;~"ôgjYGj,ʤ@k _OJTXUh[%Uł#w߄m& dVr.R>;\ d1$o
+>Z$t]b ~}Ȣ,91]iu ;:KSV눲CSpp7bY.J]b@p?oGד:W`*nF BcyUyhL?rҠ}H,J,ʽ/إ> īғ<sYw.sO2Qfؓe)=n.UdANVSXjS~p_% ml-!ѩrl:r
+S,fT(\60
++f7%u#-m
+.fÎ|/nH?X)cOɸ,F 0D1*/(^nlɨq`wXQtu!M&l\'Mʯgc\K@{ߐųGμhf`PӋ"Fz1&`׹ 9M%:Vڝw1sRӣm`'(yVM h6YWZyWpD㗭a(=}_[prm9ԮbЊ._kyI3Bт_wPn_kԧкGddx{9L$W۰zLX*#h`= Ol:o84۔HGȦzNdJIFreL۷
+5 Kg!
+0&җVr=LN9FԞ^"_
+" Di5"zhۖ;p񳿆Cn]~H[U F_!dKIh\Ȉ1
+!I¡
+@عڒX'C =dTJ"N\$
+;dnV@%-@̉o&'ᶓ?'bW0eEMּTbhkx~gXUJ )2$m%OޤL#*9d7s&"TG'T=GM4NWw% !Ln?tS_湁K߳*)qb&d\#E`rߕ^kgd]9q$`{`S=j`R@ypTGah{)قs'ֵQu<bv
+@W@ꮚEBaecP8ԱGlA؃Rͥ{]PMǐ7~} `hr֋& [_jq .K7Sd:gE>)@x?%K+ڋ_ j]&^lV4 Y'R2ˉ\gev.ҸIB ~Lq|J
+awvǘ_N&x$-nNԷO@Ɇk7 03Cd1 ;ba-d-3]q4V na7 ~|3ɱmk VfF7| 4!ML/גqNBU@
+0X):t۸/fa{q.>(\2Qޫrͅj܇,Ugo
+T?q=8Gri ~V+T# y38@uUC$r1D{x^lx7C&xbx\³xD^MzEx!&>y_OZS4㧳IML\o,o"8XըI2hs0I:̫!}d?ta_
+UK:M>qL]~&~|?W!CҚp
+7
+| 2$)MӁЂo='&
+&giY+}7ٶFւƧ~H`hE5'-g h1qvJ *Ʒ*ʑo#yB1t YA?Lg_u}1V(YvKc ^:MkU ,>wGҳa-OWJ|BU3O y:9fQz2)=^胰#/JI5
+0H
+0K9*MkIm_
+zS}Ʒ쀸ߕ 'k!)6a `o5(s>s JJ#^g]gӣ|n:HG-Aay
+B犯<ibYDʊAFNݗa@{M7Đ@CBd=9 c"V6WR/FfWeިZI OUCm5 ~}yov-}>7ʭw.?/B>x .kqEqg@?(Gf~k[ f x9d%,x 7xB9]
+impz 1(ߠsIS֛IТ|*5śo͛3=liϴNVEhȷ\
+j
+,
+ʠ'rXoŮ9}֦'ѯsԻh<dTF%*t;
+x\ j@1wJFͥyun:CCvIrQ̫??р7b 5]U#m:ʖFQG fZI_cT/TD탿/Jc/sDryR[<\GΆ&fHޗY
+N@Cc?!XK;Jh%jk3q58}q rQ]н^U-V{6'MÚʌi=7cK4TiǮ]5z;,:V4H=X/RmҗD>5^㊣n*`bnĈ Bg`qʉ5u=:oDXN{3Oc S?;xs_mnXUz2نi`%0B)EVC-<DR@qGBąf7yJ%$/_UXѓC=8!LV V}=g;^n1JI3 = ;QY,@ Xc5dKuhFBa3D=?hy ĵN r p&̠|nm&pqU@.Ԕ:[, *1'R,w8iWaN"=
+wN=~0]@..M)P)q e"0ENA`
+kDsJBde<$t={V%WZ]
+,P6MQYƪޑ2Ǣ
+I[>VQ6-vNSHр5w/v{Ex8od+<NSʆ bmNj#oEs?qaWZ zQYe Y ypȲ{<-J+P96TSL(g] * k5b_}_ lcU](<>*xyKru6"cT>/ U@ϛ|Yj],ebmԩYV*Ͳ*nyVEhbN$4xߤÂX>: =A)5o [zj xRr,wwە͉ t{$rۂ^EDsFX09)d|͠8cWc4'~eXJ'-ѣ]3K^lkᕿ'6$hhNNWEuE*SdC*/>[[t03DzZb ҫ[r G?9uT~P═v6iDSxk7,3:tNJ( 08<sğޣzN sKs]/e!)1)h), cv4-;k~#Iں4wz'j+|&+FӎMt9Zcptd6xl|T
+F,JɎ),6}nd[zKiI䭭,,<bP/Mφ/*[w.Sn+2}un񶻦7|GQ{So\Xbު(?ּ҃Tq/
+wP$i'ͷot򫿨PtϮ/S,97[BAboyZ6<|JD ? ӄU{4Vژg%}m
+^2>1Fgwǖ"G͊ox+0./kLKw3P5Sq(X7WLߚ5P)Ug$mP\գ4}A_*|-^<]tLvkS`yS}.&.7Hf癁Kk{S}wv.8(mPdlax7NH@o5rqE2=fgxNkCM'Og3#SEgZGe#IgZK
+41rpUY"~ѿ ]{4Q+E
+1
+L* *%wf&ɘb8) c6l" lW;tmDO+UJxCn ~l YIQ-k3:CoRղE%0-W˧`*jf?zuR*B1ŖFǢh_(Ӻڃ.@H.ESā!gIuORppW#-k1-Ԕt8ΐx'AOX\f.vH>.
+endobj
+2021 0 obj <<
+/Type /Font
+/Subtype /Type1
+/Encoding 5204 0 R
+/FirstChar 2
+/LastChar 149
+/Widths 5208 0 R
+/BaseFont /FQIYZG+NimbusSanL-Regu
+/FontDescriptor 2019 0 R
+>> endobj
+2019 0 obj <<
+/Ascent 712
+/CapHeight 712
+/Descent -213
+/FontName /FQIYZG+NimbusSanL-Regu
+/ItalicAngle 0
+/StemV 85
+/XHeight 523
+/FontBBox [-174 -285 1001 953]
+/Flags 4
+/CharSet (/fi/fl/exclam/quotedbl/numbersign/dollar/quoteright/parenleft/parenright/asterisk/plus/comma/hyphen/period/slash/zero/one/two/three/four/five/six/seven/eight/nine/colon/semicolon/less/equal/greater/question/at/A/B/C/D/E/F/G/H/I/J/L/M/N/O/P/Q/R/S/T/U/V/W/X/Y/backslash/underscore/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/quotedblleft/quotedblright/bullet)
+/FontFile 2020 0 R
+>> endobj
+5208 0 obj
+[500 500 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 278 355 556 556 0 0 222 333 333 389 584 278 333 278 278 556 556 556 556 556 556 556 556 556 556 278 278 584 584 584 556 1015 667 667 722 722 667 611 778 722 278 500 0 556 833 722 778 667 778 722 667 611 722 667 944 667 667 0 0 278 0 0 556 0 556 556 500 556 556 278 556 556 222 222 500 222 833 556 556 556 556 333 500 278 556 500 722 500 500 500 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 333 333 350 ]
+endobj
+1603 0 obj <<
+/Length1 1612
+/Length2 18969
+/Length3 532
+/Length 19889
+/Filter /FlateDecode
+>>
+stream
+xڬSem%m۶mvmgVҶm۶>}{V&Ęܱ#9*HE GbnFWCN.lnj
+`j3+õ3odJLwt$̱I0W=*0?^dh0(R"oStq3g\j.p0iO)~At:CݾR8"7&w#5]\R$P ?¥M&u tQ򉢆hýCcONCm/dYJKwj?¸[Nt#wkx% ͂l$ypG[U.kh8"grFmwt![2*ω$,iw]ʙ;ga)1\Fp7ߋ Nˍ(LJ\*{uĸ%VxB0h|bl.Sn[T{M-@]'N$Aa=}1x@Yu}6A3XdxHYY@f'lU<EQZSFWW9:0ğk`?[$ pԝ}A
+ AYe9q9 m,z8%HzSJOM[X Ik#} wBၩѿ ފ Ҵ'BAs.$ݟ7yc#[K`]7 x:lx¹w5FX=RS}}##T2=OջC2]z"BGWP1z PCaF3Jz}_}II,FI6h10q1vB0ohUl/pjgi`pP t7*N8h3.YAxFAMK&7##O
+^Mr:,^["wJb]*m@ՄQn|gbxKmU{-YlƖ, d㫃ˉ [$1\8z#$JKT-rYg2[wXᙔ֩e].E"7 US!9 T4}~O@}$F6^I=$=^Z(ą")dp'MȾ!oD+c
+M aNղ{EbY>C5#"Pjh)O g<_Kn<N^t8w`ẄԚ6s`ralI3RJ랻w_k3ZwVx >s=Rfѱ53%I%ug] pcOXP6v2.vu]A-{6~.,qm[Rpf=)@s'6@E;c/x)9\ y_?GQ飂\t0$b"gwklfґ ePz^V
+iS#EcRfX:|<Z[-nygO}|)l[e) /#OYTzןQkҜ?T+
+N5=+s@l?G%*G|k*bͥVɕ*+luDgH{:QJZz4_9;.Y~= orKq_X+G ȬJ,ތIk"D侵0ȈMjyԡcM ^:)h&WK&YU)+Qvf3eź/'i)#[ؠBۻemd[ct52A+C)^L'6[&XH ȘJaA e.g'b(݈j>Ҽ|gpb {6>*pS~7WCJx4Bm(8%D!bO`Xl
+-vstme?|F{qٯZz0Zg%a
+{R
+
+j>!W&;>6qmhV71ZW}(g<H%pZ+#x1 .,RNjvfwrN7SI<dg|G;U(>/
+T=kTS XaX诅ي՞pϜ|'E8ͫ@S;2YU'R޴T47"^,JRR2f[*WnӚZkX6O<%~ *"!֊|)#j4&Oȼ S/^ց"<7;ZRw CLlrG×|="~{[S11FG1 .1&A%YNzgf Kʮ^&6e~?"&(_#@|H 썔~[O6Dbw2V]ܢPmwwALy$hOpa7fx՛?\bt%.K{2 MW`ivbTJq[|寵Cksߔ_M.&,GM e:?ژl}^WO[{s08 rɟAw%i)wÍ(m*Ib%OcaIu1*0^ڧjvcק#zU,=rvx~Z]"f= ɏ,He䴑Ԏ&3}l%^=Ї;ߤIm _!k{b8u'B|Ma bd"> 4ʇz
+GDE}qEY VB* [ZX\d _ވ^ebrB̲ :I7SAͿyJG8S.wvHM.(}R*%`mdΗ8kn>/3XH&4@![EЦn͌ }.LP1Y[peF{xk0װ_25PIE6mBƓזfKl,
+ZcN-!rSGLk_e l57-$RQ6i;-K[fp R8lUW8Kr9J)C,qv$g6JO/AO<ђ$Al?2VG0qQ@-9KfWa_eLS^@[{dWgjl
+3pny]zG&  pCg^ᣟ\\ӟ(LnP %k;uրN:}E+R#YׯZF0`U@r-hNK bdo?Oq.̙QH&DK|J v@d6w"~=:@:Iï:ɗv;Yu2Y5pZmLYo%DX46렐jᨠ-L  m.<AЕf-1z7ƓQTƘʢ^AWU|Gn j
+'LJ=C.m:z9`ݶB
+ޡ*^\~8 s%-N J N@6{!nփŕqshף\#[4K <l=J3! =i R _>$?OޣvZأ]JM!xI4:ېb͵L-]|j(JG{cʦhLt b~^#FGkX&*9nNy nfáKO/qefx1`73̛Om# dZ 01CFD`c,] VMҲx:0JVO7FӒ}he6 ۲
+4Ԋ!c#0
+Ǧ+D^ٸ 5x>2'ׁZiaُCs^~t9v?vՆla=Ե<*A-v
+ Pr삝Wg|e,wԇ'96iZ% ӧTt0bm5q/ia= W0u
+ݥ pC*@;V2G#u+P0 b:]QȊZI_P RݟPM=a@,-)+95T]Z'hbeF~ՔްeF6՝@Q9(hDPf3jpT{OR_^lBZm?H4|YZwJi)Q̸;LEg\P*+ڤ2ˉTod2vԄN+m;wnxBĭth쇓-5匡<7{p:TYqn֛ $nϹXZqݾ//'imV:R!la=p^Z)[^n[?dPd
+'
+ySɗ>ߙLDqP#/n^+ >
+A:%לɅHĆD%g'wzf i7:K֖sismnqy=PYPi=!FUOAts|?bxI{%;Ş^K00 MsHS>M/~zu#M|F "Д&m|hr6ʘDb>)s[AQaY3,V۹^|}N^jo[J^u!I\Y!ǵNh:Ϩ>=~ \mhi+ l:y琽
+Q62ﺡSmH %HvyBA1)#8r^c5?-}[71|q1vf|nϾ!e|'#}8 ՃónnQ&2RBm3AalE(3Y;Kp
+R9Pf&bl+b쑛^ϸ:Tܪ:o,qڋ[--֞<܈:>
+?&dmP#uՖl$HiJo82* xH=ȉ1Z$tλ *F
+.XP`\;/
+הu?ϠOkiz0`{03PKnf,3
+@n2*OӺ #hIACY@hQ4y9^ b_C +d6}KI2|k[8L>
+Xtj M(tH $_YG!>UaGSv-|1pfy#@/H>
+={*EԳw$.o
+ fYE= ٙW5,Eل)@q!ae [ojC-M\Hbs?% xf{ڨ¥CQ9Ľ%@nH[
+NʎUJ/Sً|Kϐ"x
+@^mr.7m|y$w $\9Mg۞G‰.s`!Mt>T=%muC{sXe=Q5ln-:f5.bX,()uY<VTs@cKtͪJ`([a&@BKgv5ċnaP֯gL$<z]Hвr z RP<jpg`| 5Ӑ-
+M![B ##
+T\=}Yj)Q˘w cW|aq$&
+tff,O86T!Bߊb#Sϳ8QvЎ3DM~T7;m؊GOe&"vk8e) σx_S~k
+pwVZ(R~|]'|KnCT`چۨ\ Κ=ɹ%NK='^@.s^kBLzz4o8"+7w0\M=oxdz酅@l"W)h,R5"S8`.O!b?ӂ6 ՚Ky(uM&Σ
+D_CM ĂPşcSj~Cmwƙ' ̴ȟܷ{إG[3-hۢf[R<#$杝w҉|Fۂ@,+5AK,9jmCb!MTzSqK J\=t6u/p~u?*}H.&XFI18YӢ%P5l:Uj-aM;K;YN{)IÑ:~/$⋇.]vYͦ^K/<.ϯ k+9a:1Wc}7I U,-sӅlUؼZdzzyxl(,7ot~4=m7v^]&1piga$+G5ߔ3>KְW tI=tiV|'
+2oc
+vgVFhĜ:ox}Z3f(!Z0Eh|U(H1.eA]XpݮKA}y6)tm(n* _+>;eUTq+Dì/%k 8И\9^kTa(tzIp\$$WG!G&
+Rja?\g9 F ɓqOMYiw8)ҁOI`r\Lk?_᚟Γa+:3|ta/w:  \K._|.,mޯ68lv-VF(;b\rx3:IOd͞00k%RͬHb`cʜY8Q~~x`p*n/<qBէ0mb5Ȧg3H~<Tu-$Q.\ 鉅VZcv`m4Uhut*`泵xJySNzhwb*w
+PyH͢~ELmԾ6~Co.009"
+
+p݊ : l O5,QF,sb46|
+f*caf_[M;Ѐ|<-+@O9 @Fl>l6&ĭ.PY<u<֪X8jgը{~`tc!
+'"<<Қ˖ބs
+I147 ieÚmb=iJ݄t; d
+PjŰCh,/-Aů*Ի}h]FCkP0Mm+bȏeM>]rOn27'(z|H;zOQq@~¯GW ZWoLb%Fo=we|ixSc|Xv
+qฑq,+#3E3gs(rޫ){dN:2^o>yϟ_?"  zu]ΚK)+GGc *7))OW 1/X~㹊Io FSR+xy~˚J@-̓:3͋?~ۅ}S"ˉTSڷ|q`׉Xz|AXzUMŎKӻb7\,\7E0"%
+-kUЃ(
+g2[X_ɔGJ)$8Tu!Ӛj<\#g/*Kڟf=׵)UX( zU^D"U7\$/
+ep~5Ϙ~@bL]=Q;ai^>?3F*L:w]-HE(*
+AÜ? k|j;*-e&k*.WtF7[[6yMJkd{;S{؍s=ǻ|5*PSt-~ O55ۃ])z8ꬲkzo' ](>/p<vS'Ē)_0?ԕ,H6".aڍSB>~D20Jm7LO:>Y6NE$kX1IңsXwP&AS4-[NcDdsJ3l]lf/<щ\FZ)G>x<7$/~ٔd*,\i 1k#x(>RKJ6 MeٔҺ9]kS]<I`Tp$ojO<eq[Qio+ڪ^M*ʴ8-i[5|Ҥ*~/5[VYHtq\G{#s!+YKu8z-Q݅3T8cwiftqݴXbOf֥{̅BmE2/LOrqjLc lUCy<4^o}2Adf >5d*ΔoduЦ('Zy'{wew82K$6:o,OѯͿ\Y')6mX|A!nIѧ_(|摸n![DgҸrF)s?r>w>Tl)|W뿚R]8%>
+ȟxF+щ{l~VgP#i-?祔ҧ҄D-jF_exP8%z{ڇ"+ Xƅ#Yg<]G=I Ou*zWҿUI; hM8qR1TROA|ϾZɝ]XyOh|=hIP?pZl}DZ .ƨ_{tWo&B^5[S{MBR$q`{[8oKa`Yh>a
+K4O̓@
+2OA-H [A^'(+頹W_9?
+endobj
+1604 0 obj <<
+/Type /Font
+/Subtype /Type1
+/Encoding 5204 0 R
+/FirstChar 34
+/LastChar 126
+/Widths 5209 0 R
+/BaseFont /PJJTLG+NimbusMonL-Regu
+/FontDescriptor 1602 0 R
+>> endobj
+1602 0 obj <<
+/Ascent 625
+/CapHeight 557
+/Descent -147
+/FontName /PJJTLG+NimbusMonL-Regu
+/ItalicAngle 0
+/StemV 41
+/XHeight 426
+/FontBBox [-12 -237 650 811]
+/Flags 4
+/CharSet (/quotedbl/numbersign/dollar/percent/ampersand/quoteright/parenleft/parenright/asterisk/plus/comma/hyphen/period/slash/zero/one/two/three/four/five/seven/eight/colon/semicolon/less/equal/greater/question/at/A/B/C/D/E/F/G/H/I/J/K/L/M/N/O/P/Q/R/S/T/U/V/W/X/Y/Z/bracketleft/backslash/bracketright/underscore/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/braceleft/bar/braceright/asciitilde)
+/FontFile 1603 0 R
+>> endobj
+5209 0 obj
+[600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 0 600 600 0 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 0 600 0 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 ]
+endobj
+1321 0 obj <<
+/Length1 1647
+/Length2 16978
+/Length3 532
+/Length 17921
+/Filter /FlateDecode
+>>
+stream
+xڬUTͲ%KpwwwwwENPKݧOsotƗ13fD䌌1XI^(`JP3qsQqSpWZIbp@cW+{1cW @hXX
+wR^
+W5/G FҔw0_DD<>
+FtG+Jkz2#wׅ14|vx-9~}M^RnStq3T fk\/`8ܝRV1("bu~$u/ oј"p-dm~,9;"ELS>wxty3# Yl/ T;ĿJxe)>ذ W5>Ώ:RW|D5<Ghvש~$ݓ\1ubYy;Ec +2y(o>{>iz?]_ڤ H(xўDeZAĤv1Ya"׷dV~8D8T)ѱi^# kE4
+\sKaGo&yѰxDL`duA-W rЦ$yʑ/IQ.N7^Z\N νГ<Wʰ)NGKpY> H*vNF8q=0ܷgoq/hӸDl^rI&(r2yxo.?WzS.jR%zH,Q.|Pco2Y"I4HoxdshvF]۟iumQ""F1fi l{ %ngswWS%7$?-YM!$ěz{ݍ |v.5& b9gӣ.82Rm\#WY }R"T
+*U=Ө,U`JtzȈU*ԓ\o-D#mULq(bg
+v z̯F{%d+hF"i[*GU5K6cm*kJSy7~͔lB?e2VwJXF<@L25G4b̋z ڢm>w)vsNUk,{ge_kԮKJS&%h8齰@Dv7x#/|g~FR[}vНLXA d6 6#Sk`Ovct~$^NNZxa H,=c-*Ru_aT4כ:_hvG4uiVZP;^=5SC.>z0t Zy)φvX@x)!QbSLZG\3}yŘ~2Uj&69PhJdNuS~|#Ua+p(ҦPK-O26,Z@'t'^ Z}@9zY
+cHNN<=2%5dԅvTN a+fQ$mD?L
+sC-D3e>,{Hfa]{}MIj±XHI
+1?k@d  Ż jl*}w N4lqQ<YQUB`x6XJ)e(fgYѻgca]Mj:{[?
+GRoGJep0Wq3^Ad4og5]%vÑco%QYߦ6>pkX!&L)F7"qǞa 8\.$:E!KEw0ݬa]<3[m/-t0مcٞa ()GâY~Rݲ=\W(5=\==}uߓA#4LWJ/G%1mO:~tOwHz6ZY"ʻ@2|qq
+?jX@]H{,Z,X+LpF ?c[ \. ߭AA)p
+Qf9o)egK+Ƈ/&Gͬ&_!iy5f{wX?m~]Fai  pl,)߼ftNQ._p\Ȉ!˄+֤X' VaVҜY]Fz[fge<T1!6xg *xxs&Ʈ;eӛN&/ꅉ~d1uT5c8]}9Dqۨ^cCRk}k,)}!Yʙ ecuFƋ|LWJ;{hF==˻P-Jă9Ir]2QjJKSވc͢T2%6D)%н~5i>*7@ìV5z
+,+^XJ~^:M)jP/^yHXi[ɣ;>_,Qlv)۞<ܒu#X7QUo7ܲ)}dU=ޘO)˜:v4O6j(~Ѓ;Bʚ*dwS {H|],c[h:R$a\E
+iZCdM
+0{D 9H/f$WT!|RLVшy̦ AR,V>zURi1
+̘IEzI}!Vúrgg<Z|A,}~22xHiQ~Ga}l.}%6R٤iaz2۬ɻ2Uܨ|> %fW!"l,;9`HA-,
+4n<^v1]Q!KTI+4'eI'4&F7c
+) ;+|y/yFN"v?ѳ6N櫡!39O1/w0
+b?څ=dJeUtla
+|25ɶq=+7};ũPn9u<-geV
+ C~0)󟢩ܳ(noV5(H3Xo'sp:ZMBǙ'\/_Juɓ;cP>%pZ|;ÎHG)1:"]@}liMD˼(9~i f`7O/rw}tӒѺIG{$Hl9r#>1^qABFgz7Cϗ 2S'ԗu<cZT. V6 ̩;}MR!jl,*0[@%Zj1sѩ&}Qv?"0u z{@֓@q|~8IԱ^-ڍrF{nؕJ SUՒl8uHS4%a U )C{[=#/ $;Uj.. Wm$tQĐXeٚcߩu~b~ܸA|sɢz!3##[h;!V+xzG
+.8"E+!x%.B5{L7 ,ũ0vM$Bq(YmmyTVD{Mjd˟̞gg5Kw0n]0Ҧ`+"\C8qJjֳ "EQ`uع5 e0&[HL*5ty/ɗ&h'-9|bN`^ ubܒf!_MY6PH}z/'`G*C!@0e`^?
+:='6bB2\9"ՄKH25(sB6D
+N8dVPYɴj\8Akeϒ/FZʫODEhmU5{&BwU xh`ݐD|
+4C0Gă /o'Z#Gk8VF~j,Zψ%5V!Q$jSL~{ZiT<_A]U6=lg@]f,5!B
+yCW:rLyCk0MFR:
+N*@YfHOgj/Uvwv6dvrGt6De‡UwITP뚦J4]}1ܳPᒊ}#tlיG:~\E1LFMjh$8#jXݔ3$91? fB&Ɗs 8E{C$1uKl>"^ !{uf8{O藶SԢ7 {݈kޙa\ًA#bWkFk0=wr'"M;OuEsW7\Ks Sesfp-f%}F6];R4 .q)OƎӄCtsOP5W w73ՊkWq,ɲO׊+b5f[@Ɲ5 w[5 m'K
+R\З Y7"'h똶5qDO>!vl\qxVMAYǮ u% 5rY$Da?*k]WB螥οx24OՔJaE/FO8r2@HkLnFx
+σSe,J[sHnGD ]v7q,>QP,<%MT6I0* = {FzV`Yq9m7Q0^Y]"yz <WY#g= $rKhU<4IۅY2nDQ/.%2 "B<5!0ӏ߷6S% ês&*
+j uEڏl4XƉU{Wna؀ʩ'pgƹ$VxH3ߕn|)께PR.U!ܩi?>7fJ=<-a z
+~Ø"ZH Oz&#
+̃O=~pǶ)PNbMidVV`mẋ=;Pm
+A/no%O\w8_ HE2K85I]
+kc'^o$D.r{=I܃quk5Ǥ_|iX<2\n9ckv<dѕ(6fnOP<;Iu0mZfQ ORr DǼ{/e 9Wz4.!H6!;bBkS:`mpR֡S#b\' rRӏhy6*PXaf/ĪInhr.\$L|+#z/ r7kzĀ=B_wX浪UasQPCO`Dn7qîSR^b 3r(l)'Jt36%Zt1^r`*s4vVvFZG]ޘ^|PDk{h1Kd?A"FYAڦ:<M,tp-M5^6Qɜ jh,a0˫>U51/OrQd?6k&eJ/IG>gk^{Ie\6!r:휗:g! Z^}ÛnЏ^99y7`]0H\if+n9/
+k:֔^|x0Ge;kDr<TBe7kuQToS~ }0
+qă쪣
+1RWA!̽Nx i~? ~1!"l>DrcD@4z@D%Gazm/jMJtS Q`vg IF+ro==[,`ĻH\fCu/Dbԥ= #4pˆm}E1)9 jIލ/ j߮H6➏g
+Hp4y;ugʗO**ccI2T
+I5nQCljгSԂx%:fb7:YSc7tXUw%%MfOrZ -O:YٟK}k [݄O橧veV&| )f% mZTcCSL=H 1 ț/vr5P/catAf@)Io4!2Zi "@=+o#fԍ\ !>y~'zӬsPM:,}ܑ$#mN=C^{JR]aP0~>થʄdƅ͉F)vmd`Z.DV#?ٷ|a޲\gJCTWf/?V7n`L[^94 NMN^P}z@Ʃ.)tdi9 OY<OMkyLkq =X=_
+=MD>  9hTH=I3=ɠvz,i.n[ʧ?9A ߒ yW-leafk -F%6aYZ(TÀpy,ULYu 7 &̛y&xx6;AF FӦA#ױ-'
+n~,(DT?\>rzw]jL#ۀ;6sCh:3* g5Vi_ Kv!ҕ?V?r6 Aq)sqg>Ք\4+FBxyjֻboMroW[<.zy
+_M (zfJ`W·Ǻ :o}\;b25kvڴFoMkl='N_ !m ~:C;`Ur]72SWH0{QL! T>b1yօP<R9ńyhL'ױҐUiYz ?F/)W!G~%F#:?2ŲY lIBW_ϻe@ f91W QKE@ZN#;5.‰r4 l
+NZ'l~j# ep.a</Mi;{"zЌDC#Оvc|So|^騶#cQy-פ ulXOGfZ0hax7;?L/ .2ݶ0Z{5gyHx FE kk,q. vjn-֠䣧T@[{3aћlMn
+LR4ي5nàR=Um@!7{N u-Bc-/ix7sj;so*V'F&Y#'_J&N$2/GvUW8p<7T{>W`'Nk=0߼)k|I&-G¯ilyKL\4ʔgZ[Q(*h sF'E1+>!)%~S?ۣq D;o=&ޤgORfݯddP H=h
+SNdnG@nXL 8:G<iEx!Z>ј-OoI$OmQ̉])֋; "߹Oy TEWVW)wFbm!p%Y#Mjb*n%l*r%mjGL V c}^~MU>&@)=:PnX*T|ˢ h]dB'0ZQBc"eY>zчb[kvQ1$]ioT!mC`cK$+E`< "w)Y',i\ov؁ŀAxs콑2"a[LΌ4i){Ib(,Z{;9[NVjWp9mgE%7=u-rZx&]ro*x Yc
+5T$d{-FlBJI8)$=b/`,2ŖZl-皥F԰?[Yr'5e:4 `Rۑt7"vfVB={T(k0ّE/&$\
+ 5;z{s"Ñp̛㛚K0&
+r1OvtVŧO߯8H৐oF^b1F߲m *FQ
+ 6K©0q:h(eεl(⒫!J(F?za n :q׽L!Vև`CSnMip-
+P
+;69jJI ƚ#Ca>&ϣhct#ۚ`әTN<ʄ&K[q#: @X#|^Iәiݷ:u1`,Px!#ܬT͐qA&j>b`Qǟ*C.-U?
++,ʷm98iFo5֭[ll
+Κ$JxFtVuAg_ 繋Gϙyw{n6xrӸa۞O;DT;zp*n$7;X 5
+wYa E9*W &WTcc@XD), g:c=_@/1h&_d޵ijA 8ľWfC
+c,ܣ-spFeC*<S*O\g/0%JwK١
+k%w=>Ic&Mu{ͯ#4口*"pЇn9y
+h֥n9x+ بqMӮC,AVZ%]V4_"Lz^1DH(rJ(ۦD9Oe;mIֹ"n^T.tR;9nx$Sˋz7(S,ҭ) BK'X" +}3DJ;Az-lEVn%'"(ZtUzCjH;&UbJ{mjr
+endobj
+1322 0 obj <<
+/Type /Font
+/Subtype /Type1
+/Encoding 5204 0 R
+/FirstChar 2
+/LastChar 122
+/Widths 5210 0 R
+/BaseFont /MFESGT+NimbusRomNo9L-ReguItal
+/FontDescriptor 1320 0 R
+>> endobj
+1320 0 obj <<
+/Ascent 669
+/CapHeight 669
+/Descent -193
+/FontName /MFESGT+NimbusRomNo9L-ReguItal
+/ItalicAngle -15.5
+/StemV 78
+/XHeight 441
+/FontBBox [-169 -270 1010 924]
+/Flags 4
+/CharSet (/fi/percent/quoteright/asterisk/comma/hyphen/period/slash/zero/one/two/three/four/five/six/seven/eight/nine/colon/less/greater/A/B/C/D/E/F/G/H/I/K/L/M/N/O/P/Q/R/S/T/U/V/W/X/Y/a/b/c/d/e/f/g/h/i/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z)
+/FontFile 1321 0 R
+>> endobj
+5210 0 obj
+[500 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 833 0 333 0 0 500 0 250 333 250 278 500 500 500 500 500 500 500 500 500 500 333 0 675 0 675 0 0 611 611 667 722 611 611 722 722 333 0 667 556 833 667 722 611 722 611 500 556 722 611 833 611 556 0 0 0 0 0 0 0 500 500 444 500 444 278 500 500 278 0 444 278 722 500 500 500 500 389 389 278 500 444 667 444 444 389 ]
+endobj
+1229 0 obj <<
+/Length1 1626
+/Length2 15139
+/Length3 532
+/Length 16006
+/Filter /FlateDecode
+>>
+stream
+xvcefڶm۶m۪mTfUڶ3+m۶QiO}qtq"g]{) :;ػ13r\dL
+8p*Tb33wytݎ4S0 x鰰arñF >
+$iC{W6HlJpT&0\vOj]rkcWfrr>% Tq3ws%ZWɥ٢>y!MI23qU3lҁ~#*<ФXf"~iYעJ
+(} %N7͞Qt]n擇_8o+Ԣ*(ؓO_$ a^-( IoԉEHf69`r *Y}Q'>T"}Ҥ}^i"Eem 8kqF;>:EDl@+M> ;1Y#%H@>*pdE y򔀢U=pq}c!:2w{H:-i} #Z}NRU0$&їd~` U%^O,ȑTb]uuÝ=$KV}T13=@/3T*Q r.ْRg+F!}~L۫+'qJPH&6)dOXvUaO*OJQE:F݌;Sfᔠ-ꆒC|YĢ(=g-[TɮMEa
+=VJ;M1!+AmH€~ͅ:<zdqC-TdL̜p!VcpjF%E{o)G})oBy?ry|(}WZN_Y+w=NsYAo`C) cTvqe݄~r&QmjDV(!&PXP._>+#{YÖG=m.
+ l=iJ*zW+'kgxBDkjo<u/4{
+Xbc˴q=aFT8
+(tqFpaIz<>ZY0p*C@V-]St#ښݽ/= MX hZRZ]!˂' _Z>̞*|>7.ӟqoI9W%1%V!I<`~s$U7+7h^zAw{np6jM]Pl۪ιm`@ Rs"w!B1i.ތCePr DfK2
+lF׌X$Sa<} ^ V" ^2~KE;l%y
+0V4"w3?0[S5RL>?$r}c!-Vz~"R \yDmrڞk%4X; )9KcġDQ3D#践3nK:z+72* Pi2sLĒNuAGč97s 0In3HݶvY09<ĐZFv)3d쫮| 0(-HѪ RiL)!橨mI-
+ﺸ#<?H7ifR<qQ"@`]
+#.N|sZp*N n*%wM (ãyFu8-,*Ԉl)c&JC8XLրi
+xAuz~bm;к7z.3?KXnpk=
+&_ş*N7wK${=f|v
+4I&v&'֩o0vJ?X넻d~92 N1qBό,mx& .$/eÅwOx߷ʬv<wږ
+X
+yke`:g^Y1iJsJ{/WtsY;ݭe\`,?gUR|g#%3rVKvc]1\R," Bn(okX#%?,䘷3 D1o 5Sj]
+`"*_E<ja ?hBa#lrTwi垑1LR
+A3w܋M
+oiZa=x[{(sPԞ!HFZqSPLԢcXupYevw̃pi 1oN8Ȕ馿F`
+QZ#&k#.e
+8@b{I~x' б$O˥,Td+:><=>J&t}_{xU$A(蔕=rl`&BGUL/Od񗞠j^-1;^duӱֽHGtCCpgBiE"MDA j)ǿSmrsmF;9fE3n5O͟HEUzžX"݂[#E2G}BLMt_r@ꞜXe/YطA'Rf h `n*J XB%g3b<v
+ޜ{wcv0uz^q~֞s `*ޕ!]<4UF%4>fB%cfzف}+Bi[gcedI Sg&t)n.QPc88G~|Y8G!P֭.R'l
+
+
+A/*.0;Q%h;tC{ɖ7*M~Zbs`7-H$%n ÇUX3-Ε֋]PRfYu4 Fo@2+|pԠ
+ѱpO6Y\g+/w87 :< ΠF ]&<%sS/fmwdBnn(}W8 biaCjTuK3V_1~8kI17}]iS*9-3_e7=@\l䤒fJJͭ
+mu_W`•@!>_NrH1s8=zvJ f+}5BB0oxl>ism@čO!dߗ1(Q6A7AHWhSo6ّ)CN/O ^b.=>8W%@Ϋ$_|3 @.Kgwld(a/^7FiG@ȼH{_|M@U &Iҗt$,J^gAlIorfcJF;ҾymJeQ ҚF
+N2#WCj̵814Qq@ۧ*i;3vg2<N$Ԝ;g&n_/c]q1-'"O|n\4'JXad9&Cs9vh8\@ip39gmVvlt7s y` (Tv ̘W7 bd%X>i|E\[=cZ}_~hE8KMB4FC|q}f{*@%mM1¬- ;I=l=!yg(_JP!aٵdz]M+krX%֫l(e%uh}wFN1=-xAaݏwnl;ϊWwKRwEP,<AZ!)QOܓUϳUKw8Ls01Q .]Lh%Q2]c)BnMp;Lsd [Z8 FXދF)'Ҳ]^/j)ӵRJ3(~dI{ѯb8Yn _VLZ uErgB,Zw}:sRAG+&hrX?l|IYW׏ 皍9ʹщ%'!uT[נO層z 4B
+8-|<YQ Ȣ;a;mcV$_VfP-_!It*!ե}71m?~nZ|
+[x!Q%Z h_v:}R%:L_-b.LwSXzֽd w=o{gh/TZ: lK{srLR߹`(w֩Y3
+4NiĂAڰsW| :KqdƖR\դH"w'>Y&53T-:!ʰ0c,AĿBzLbUhӗ 5`sFD`!
+_`wk, wQ}܁,N-H{%y*1M~ 8xjeW ǀFͻ}:jZ _5/s]:ڲ 3G&.UB ~GLa468eEJh5CF#k
+1~S MQÈie
+c&0WAY9cC[9 |1EKވ|+iZPC~ >XͪY~*l7/$YX q7K\sy]bNQF+*<COsc%V?yNN<sbfyk:U[M-3ӗ%`kؤ4z0F9𽂆rqǭ$⒰30FS\MJ
+ ',<z}w}lN^Pa]!j6nYbɉF&陭*mz
+|X$
+̧9~5
+
+V>CÄbgq "OisG-'A ^K;3XIm/k lrrR(*j hSR.}t84$joH Z @x`2 jDdʍjP AI9DV czHRc:K\%T71mq6.3ۯ_
+D]-Q<,*,b *ˑ`3Sw$7COHNVLe_'*=8ǖ*"&amz7O{+l
+YM;I{fcpeӔV722Ջ1,˴VX<d\ӴK.b")iS}beIxx=J\\ wM_,;?dJ׫&woJ3&!p-宁Z%-(?M6F;7'X^3ϪSt>"2JDIjƍ:rj"E S\{@53X0mtJ1ƺ֎ ,'1|+Ew/P]@|*o35JWLmH3_("^aF!dQU_;׵ tf ,сJ7jx~P;EЕ-%V Bx5]C}M`T/B!d_)Y
+BJf@'+*P & ~~ *W򙜂/\)x성<ZsCW #fg4XٽzBӝ*zW v&4>d<ӵc޴ikq7ۥ2Ej-wPr3CjAVFMh"<<4`Bw(fNw_LgK`
+0ik֔^20;g(Uѳ)7IK[R & 62 s
+~((Osȫ6.c4Ef1-E$KiT~Q儀i.8d=:x Z{ii&Zr'ȃ6j Mðfܒ
+-aT Xcw ڣ@kd5nk>D!<UQz#WMluͦ'#kFЩCeFP*Q:ؤwArzԅk`3ȯ;WmOI6<˜X{FGR)2Oݘa 曾YG>&X wzPg}CjOѴOqmjbh8aVe ގL|Ѵ"UUj4Y(,!he6KQiU X| X#'V
+2* mIcl>/A '8{wvvc~RhH^e2 YR H7`s /<+fʯWͧLX*D3qj{A?lo>=X'O8xay:t.C cդk6j"zm+ߔO74)l;8LpUY/JJU@WorB
+Klk0ݻ{<~brV
+fTNf0A&>%]Dk`ݖW2%ϧ-At#e=A\.ɑ*CJJt4_A.2Jг}2Y;g[^wweՒ_5KAvrf¢ 1o fpAz+2}%.Ap䁰*;qDܾ. -R (Zi<PǺFN3'P?-Ҥ㶚00k&)vs%Ycf}]BcVTSVi֎~V;1+F{/R׷Ht LIK6; W)ڠ9-q^DKH[&7w%{h
+ >XO$H*ޤ~&;y6=&Q$Iwr0ddURxla k,=8d_wo<6~\5hj0嚞IaDC
+%?t
+b8 ?ؐ1iVvxn--ad]^AD
+}v!0EN;g|f];qd\@
+xW&8lSku%]qip{̈u K䧿ȍr
+u C1Oo!ciGnf.ނX|nLRF
+I^}Iܕyuiӣ4|6SdJmoQn;99/ 0gd7s8j/-%ύ2`0"fPik&mQ>U]^TB E[5iP;FHW5ռg0զ(wߧM3Y
+#Ʈ Mц<N,Wh!3_c_&`:&BhMX [ẕTgo\5͜9 s3SYnW༾bڅ.?
+V܄d#h͐0}0急@W`)&hz=5em۰cXvIP`ccz(370
+endobj
+1230 0 obj <<
+/Type /Font
+/Subtype /Type1
+/Encoding 5204 0 R
+/FirstChar 2
+/LastChar 125
+/Widths 5211 0 R
+/BaseFont /XKWUGA+NimbusRomNo9L-Medi
+/FontDescriptor 1228 0 R
+>> endobj
+1228 0 obj <<
+/Ascent 690
+/CapHeight 690
+/Descent -209
+/FontName /XKWUGA+NimbusRomNo9L-Medi
+/ItalicAngle 0
+/StemV 140
+/XHeight 461
+/FontBBox [-168 -341 1000 960]
+/Flags 4
+/CharSet (/fi/quotedbl/dollar/quoteright/comma/hyphen/period/slash/zero/one/two/three/four/five/six/seven/eight/nine/colon/greater/A/B/C/D/E/F/G/I/L/M/N/P/R/S/T/U/V/backslash/underscore/a/b/c/d/e/f/g/h/i/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/braceleft/bar/braceright)
+/FontFile 1229 0 R
+>> endobj
+5211 0 obj
+[556 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 555 0 500 0 0 333 0 0 0 0 250 333 250 278 500 500 500 500 500 500 500 500 500 500 333 0 0 0 570 0 0 722 667 722 722 667 611 778 0 389 0 0 667 944 722 0 611 0 722 556 667 722 722 0 0 0 0 0 278 0 0 500 0 500 556 444 556 444 333 500 556 278 0 556 278 833 556 500 556 556 444 389 333 556 500 722 500 500 444 394 220 394 ]
+endobj
+1221 0 obj <<
+/Length1 1630
+/Length2 20633
+/Length3 532
+/Length 21546
+/Filter /FlateDecode
+>>
+stream
+xڬctf]&b۸c۶mb۶mQz޷O}~1&؛XI^\HP7qsUqWp䖣W1tÒČ<
+t1v
+յ%ti7 _J7Q<#" gгp1fa / <]=fbWϓw0u4gtTf
+Q%_ oqܛ{®8ddgGt:79gDrWׇ2|vx-9}cQ_'(B٢= b4(G<׌^`8ܛVV1({$bu~ u/
+@'{rB3MoFnAm(>;H>y~黅8͋!uGpQà:LN;2s(Jfڼ8LDy$kgPqXiGI =XԸ6O,P7."?]up5a2u}̍.؈/uxӾ7G< G7KR* J+Jk
+{ש!yh}WurpI
+ :kߋ'I}Ettb]jPE\yk
+`Opfk)6O΄n`"+g1ϤikR6 !clZB-u?*A08xu`,hi/c^޲ i;xq&S6[ޅeeso&s:;h[Y^(
+k
+CځLܗ `3ocI.*Nos&U4៧{sSy֢'1G>)vr@'!`Db6K w
+[ gl>9A!A(`_<F
+m)D/xP~ڤlaA0TNs[Sfk@EqNne֥
+ J1H Z`7""7 UG2Yv"煉 Z;efmVB[uw fP4_I~ f̙?5LɔC|~h:x
+\bkLpcN&:fM=''4Å-%*0,s6 $9I^wvsiI#Dl.EFs6ďT8zQBܙF
+?l~AH 1
++'`+Q3ocPag  G5`l!~Qo2^b .8,<
+]A\$I͸C$g⢧(LSOB@57fy:n'ޤ3gTlX n日kp}&3r#4dhf0 he}A!"LCe&#~Qp!x_1wFBdN#BkBThM&^d ?g~ҽ]Ơ99C̸Hwo7Ԑm>qx-T4o&8[pWX xjdd6U\ssLyPO1BR?'mBK%a 'NxƦ׷[4%:+D~).e` uV*H\!XUFBۃGp%<4^o?=_A?+nVʾ
+=Oh]mz;T#9ҵF>ek)1W5TGzg!U25N%c
+iXӍw|,SSjFG @ׇ
+=v7H
+\Px
+fb&)WMCu%F7=t0
+tޣ 7yL
+2J "IX[P1MԦxZD2,6ca"#߳B J,ba$,b⼂3TJ䪴}ca*PKõpP`(ʧf jȗl]ߡ:ϑ:~ b9;KKߐ#r앆d5s!P{P+8)xu{{j1vprbٙmT{ƫ iQ~fqVG33]c,
+؄kAs5٧7}v-Tظa@*фCg34YI,fE
+a;=Q$QS˛G 7]Qk\-ՙl]#7߯z*W5ظȿ_ok_p]B7MN3'ᾬZ$1xr[Q)4DՉᛆ _2}3KC"s❾9sʍMa!=d wS[qȈhzKlpu ؅Ȭ' N@P
+78r9H+
+^q$~?5<vVf~1]+0Fn#-eM0Wudk'r\lkՂ/T,p:>i<"ߌAk.:~ˊTEg ,(k 052%2lԜS-Hbc}yG 虚c*glu
+bB؏xn.
+w 6}%\<#XvVǂ}h4BSeTBwM.MDa.rb&lZAq\i#VRH3,^T'{;eɔa82d|Nݰx{Mmֵ>L&|vY?Fv_?r*F?q'HۊyuZ&O-s3axi['nf0vI\Zk4,îV QQH]e%4Th_ے+[)^۝t TrN*ۯ_Z{ШԒCR{
+;>wM-ZJɫD%?)|x'?H~1} *) 30UD1czZņׁ^cʧ帟/.3h1u?8A[5(ݡ#U>tq)%_YO ?y#msoc?nr4<=O5T+C&vmD%{^1.q! ~MvVX+_emp/(:\g1+éx3 =x 4͖r~l;#nL ntԵ;<d& 5 :-hސ{|Gϼ~!*SD(:P) Ta %@X;3 *ZWG&+oAG~3rZ:9#0xxꕈ)Tٙn*gx-hV-M:Z*2֪ދw}ҬnO-̠kn8ېp+Mg)~I6anA !N,t̔C:߶ҞY0mmYTC:=Kt~W,qO\KXFӊqY {nsNTtR7#VfU.0G841'L)Si[ ;cYy,tߩL& m>V,𷸤f6iRVରKsk)o
+4Z
+VpVmԔC2s]UUq]p $
+52p.?ACikqK(V^3j[KHQ<[赽.]7k8f,0K>mNv?N(,2Pbm1ǭ5AcEbdZ+I"Wk蜭3VTSRb8Dy`b07#+$c%ssۍNF#K .ERKk>c@=EI9x#&PE rP_ݴ=μ% l"r{;}ist
+DKT0#
+ }KQr;~XPE:seΰ.xma:@9êK[]t_:0?fm-̞F(nTJ3R>#
+~ 7
+]?<HnZs0_ \s&%TcbZ  %K2
+ %vĨ֝ l9T94!؎EEY~w"6ȻI]Aw.03́TL.O`cʿ,`ice|K3)GriLMŢyK(M](.R)>PJjIYGKow,x4+o_;ͪbl2b1JM7I.`ϝnoKhB[Mrs#HSO`PBThY~I%%FBx𮀸u$Tő˱xLWO$|8̿)#:Y
+B2DXsC 1'#/x&N^ @xB'/fXYVOEzMpSA?^f!B{_!ԫYY^ <O
+*8#b2)cLKQs
+q 8?wDyw5L[AqPML'gZjw$D˗]ňk37`<
+҃sבmi*}ۃP嶽B#EJ1n9w,N*Äo{l{:ߞϓVW:I)|H}b[|-&OOL =uN~%v6QM#9ꎆqf)-)r@y)ߊ+ZT/ќ'Y"胠
+s)6/](b=~=?[Τ
+2lM@νK?
+ȯY{.yA?G{v)錩;)f
+7FoB5T3L-R8-wm?t<F.ͧu %KP`8>
+KqT&-217Cun}.5TBO4HZ/\Lh1 kbk%zWi^Lo=*~"bp{?eH\Yemmuq<cYk';xZX3L6Jyy 2%M<6efGWq9\H/Rxl0 l[rk.xGphB1@"|/߇ot
+&x"N1 E؀NadcP/Ջ%Հh˼s{'WǶOKLyUZ?+! 35]MU7o:l`@vwQNj?1+ʔPn`6FV07QsέVdy TS[#U?25'`dE& UIfB~k][Z?Gc*SdJvK7N{| +$a{!sN+<eFM6 Kvsq*Q
+T#1|LZfM8)>-]Tf49QA>U;uyPچCmL,q@HEIKZ5N-`%"޾a-|%<aOd8 i=FRߏclT+ABە6pS q6([ SfE[lEۢL㱭gj8 pq "̤v\W{ Y
+ oRN8E&MC}j0(l+EIi~OYj_<Z $lB0"?!A)9I K^z^hNYt)YVs([m`dˡ"j%G]@HA_w\Xk2~3/^5> uW5 ]yQ׎J08nBHRUY Ef~VoO
+tf\iY?kl)_>f
+s˖?"5P{k:I#h~b Tҕqˌ䀗rJ
+x˞.7'fwrDxŗg0fd^iRH(F) 6]Bi#gI,kS|`L qˬ/&- ^߄+@DP'9v꿉6?:KEm3 Eg"GiģmǪrf%5Ua\4۪9%Q;]|pa/͈E$6&
+WmBф edۆNх2Oit?xdrzrTQ˦9M]nElEa~xJ؁SiZPX[/&<:n>LD)UB_h'(eTQ&4 XW֞wzԣ wGsT Rׁ5s2@c
+>6XkVz^[IC_zJ.p8"@;ϣZ1"Px(գHDa! R߇m:kҡi!=IRH
+S3p'ƪnT<:RoUY kPhӃNe*3
+Bka۰s%te#VTEX@B %`2s5]i?m2Lo}ԝFs}ڶ4Qۀ~33%5hۗ v-`p^-պy{B9i|+q˔nT0{|6u
+kcc&p[YEḠX H緌;o]վF=J |;Fː? N3o]"sX䌰r3]C?inu%g XG,SE+H֓^(ү\a{6"
+g^gg,kE?P)Vבrd
+~ݥ5\\7\.*@(&Izl !
+9|zȯEjӾ쏥L3=?෍etU9qXc8c
+6V6h@8J ٲ(fC9}̧P_Etq[b@qw`M3?Z|mAWOʪ5LмΒWVA
+iqZ\V[׸73 $c%Xo V\wgsD2eRH&v"[J虑Bgw=ʹu>1 ,|Ҥ1/O,
+$"H
+x(3 x^F(mm~gOυS*
+~v: KU<K (hId(yv3P߰*d )bژ0zt c嫹NGYќɮ 8 wlH9漏ȳ:TM|6ӀK_.QS +SNV&H$L^2 &d|cӆ5
+*j'B]gM+W=&Jv7qAT8R!*|z'yЦuPǡ:j1ZQhEnO
+j~a!(GR ںƳiQ+(,lcض7Ğ]RVzDQZ<!J=OGi>wmfᙘ%M J9 l CE~b E6Bks&C/XehDta-&bT4L(QLOW~/d\禅p3BT{\c2 'U>kW#6X5)>5G+yݐ]#{GSiV23@`+4icŻ悯[dO~;&TT@1$K5c֛lgV1dsƲjgOZ]cHn/F"D_lEk`,m a{N(ŗIn=ntpc.ހs1a,>jRUYޅ !bBX&]}@5jR4ߚ*H^>8#r V\T eYE6s/b< @ljB_y/?bni2aXA
+0z`H9h kk0Da"GDưLo.ь8A14y"82Nm YOuo.:Y} קpP,? Q
+oEGf2X5l9bXc, y ES4/p}30c(=+Tș9-Mm i6Ĺ LFFO^`ZjyUʵYOЛגG{ՏW"$16]<Uѽ'KCR(6u}PD .Z;QZB?!<h\5[sC|jC: G.m3C:kp.j װG
+ϔ෾=Na68u~I鱋@7PR5ntDGП;z[\UҹÇE=lA{Q3WwmxxTEqUnl~52T&]c\Uѿ(YNGR, խ^XcF_=7KJ1GطIE\V)l7%V4=M=rbxsffof9_Wf>3yV(GQZm%Ҙ ΗrĎ*$HNꛨ,#qOh W:r54} i% 5WiouB`oF 9ߧ~:V|SIGݐ=&_?t[d~Z t
+Yx-e2nKWM[RS_U7R"}ֆs>; aEpNogl'e*m楪&½بC"+azaouiO/L [?h}7h=]|lf^ rgg~!jB 3g:jd/6e5 &kU^d\<rqyTy?⽫z:n2k~dCΆYO
+ uGB48Q#l~vqܵ#C-Y"CX43kjhHzw !$ptcr}a YE>;l@/^h鑯lƢe
+wvI 3pc*&R=?,m$7U~rC{#ci0tw렋@}ù<M$3Īx 4@He.W87.8s%`t9: PtxBx3L#^Q$ VL #[A:Oe 9`xpV,)zf$8B ㈁
+K rtzU?Msk,Sp *MfOl.
+I:x$ :*E5B+ctCg&] $תs% ޛc{Q,jc3'E מU_d2hizc}:6L-ZX C$~ ?iDȣP¥ ᳂xH8iOy޲@e}%U޼,$䱚';NK}驑BsjWgzi/pqQ ]NN L\w,`[qsk쥫B6BFP`2<`.zߴ 6ԅԲ+OQׅCd3FNf0CQ>68Qρ%5,Ϗ b?YVNRu/sfY'}*P?=dI0;ՋKD\ZXL"<^J^s Jיw!!7%]zkEV&W<N9e@fPtL֛yC456/?XRWTٝ!^80n9bf-WkxgbW3Yux Ζ/jHFeG
+Ya!ԌG
+"iv1 Oǩ&DRwXϸ;\%e AT<ETe-zq:L v^@sl>wx7Au"l fҨS/ [Hi] XPt'}u CߒxN<&-B*81Me܂kQi$ReM9RBLݰP"X5H5DTlň4SN;Rrsa9()~18^k'>ǕY.NLþcH%{[<P<tך{3B/_stn*6>h笚i(8J5S|G$:!D=i8$d+3#F#U[FST-]3\2C5PdJhj#1h)ppK |//]L<[s˭FR>ƩKIk`
+endobj
+1222 0 obj <<
+/Type /Font
+/Subtype /Type1
+/Encoding 5204 0 R
+/FirstChar 2
+/LastChar 151
+/Widths 5212 0 R
+/BaseFont /UYEFZQ+NimbusRomNo9L-Regu
+/FontDescriptor 1220 0 R
+>> endobj
+1220 0 obj <<
+/Ascent 678
+/CapHeight 651
+/Descent -216
+/FontName /UYEFZQ+NimbusRomNo9L-Regu
+/ItalicAngle 0
+/StemV 85
+/XHeight 450
+/FontBBox [-168 -281 1000 924]
+/Flags 4
+/CharSet (/fi/fl/exclam/quotedbl/numbersign/dollar/percent/ampersand/quoteright/parenleft/parenright/asterisk/plus/comma/hyphen/period/slash/zero/one/two/three/four/five/six/seven/eight/nine/colon/semicolon/less/equal/greater/question/at/A/B/C/D/E/F/G/H/I/J/K/L/M/N/O/P/Q/R/S/T/U/V/W/X/Y/Z/bracketleft/bracketright/underscore/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/quotedblleft/quotedblright/bullet/emdash)
+/FontFile 1221 0 R
+>> endobj
+5212 0 obj
+[556 556 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 333 408 500 500 833 778 333 333 333 500 564 250 333 250 278 500 500 500 500 500 500 500 500 500 500 278 278 564 564 564 444 921 722 667 667 722 611 556 722 722 333 389 722 611 889 722 722 556 722 667 556 611 722 722 944 722 722 611 333 0 333 0 500 0 444 500 444 500 444 333 500 500 278 278 500 278 778 500 500 500 500 333 389 278 500 500 722 500 500 444 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 444 444 350 0 1000 ]
+endobj
+1213 0 obj <<
+/Length1 1608
+/Length2 12632
+/Length3 532
+/Length 13487
+/Filter /FlateDecode
+>>
+stream
+xtePݖ.NFB,w$[.wfL;f׭U].zz4Z, YG7VvAAEl xw Hn@7 @d Y
+V
+P_@{[z0@܀cp~N[WY[/
+~~K cpKLᯡ
+9X+wyͦ%Y請i{;>T- CR `b{l\
+V74.z(̩A ڱ'0=@KMk[‘"H/'tğ
+:PKp)^/A@
+sZU5'΍DX@lYֺ|I8mx"V-w]Pv.\G-p{P!tK|RӸH bnw]aԃ]lRa`U@LGKtu%-jy[=$Q|v9K%uu%d#<*Ac1y,[:jrrr&L\?2lECr,4S]9IuFAI 4FLB+8~7,#Br?( |QgryFad} th55L̛8%h1nd~1S=~:ٟPCMhIrFU
+}b8Dǖ _>#ߥ2T zD
+%Lrc1n67Kl'#IE!FO JUʜ
+HZ"<qLpkbzb_!0#m1Fy!T󟰌)U2ln bZaApf6&awR{0WN/)L)I!tg1E3_&xu7QS(ؗ DRZOCTG_Ufa&Z9
+#ۗ-cdE GםXih4Rlzu3\ v9B-~)M^ [96 nx=g-{-zwޝ%OQx"J^XƖҕ Rś"j=n鏂V_(&k6̶FRߕ~a9#v^鴦m&vT
+W8,%{41}`sRB'
+҃gǼWq)w@+ dYi<?[x'`O[H43-'5}F^d>2Mnި.
+50sf_'m^{YFݔl lO3 6A uTwg_B;׾ϧ7SH% LuٖO^^AVs$ձD½j4>TNǷݝϋf,+`imp}ck"Fj"+y8#&%
+!1
+ͪGE@A TħKYR㆒5I؁ /]Zl(Z
+ [MD'C+H-VD3U E5uAN J=rc/+y]~~t!E%0kPv zB/VI3vx3DAVWw.s/:231D=c?bf!y\XF1x/NA&-:{ysUH \ctD80)yAI.`έeV" B<JBa{R_Hjuo.ao!$d0$WξO8J٨-#({&O<QS dñFD05ǓƉ `|G?e=zM 30'k<dM>+OE#qCF/%M!c%s2052.'
+I|Ɣwb+#G-Rg٬e~bHv
+ nmNU'.f88vDf3-!0'EP̈o9"p Fu@
+#'t*O7Fi閒tVN:sz
+b6}U֧OyqrjJ|Ģ(կmCCPkj0&la]"|zWA9~_hBIig< ATVE;6&Bpƕۆhgu;GAd(OZ咙d`wKnپl›= k8wMˀ1@Gݨ`@1+qewk`'p7VUv xKҬ+}_1H1)> l
+HÞV\甔a YY+ژySV@4N1:V+W<$g?8hJ]6$={ߠ &0BGg+mBO)<Xب"r}@ۥ`yŻ5}h-Wwiz̃zyA訂,هPF5W[hHi\6^Lj3♌'-M[Ec
+&kKܲA
+aHӄ=\"ǧȕ7$zB߻12_
+4^HY]ym?>oN !I$)Nj[Ro74S8E|H#np+$H
+3 qEz:}cJ2syid_كAE<qH$oVe&^ց3r|oon+Nyk/t3.eJ ,"eo}9\g-RRYv-UE"|H0&}xhNk._:uS4b%rVp _\׳ :m`usNGP::WRS
+*&W#+ꚤP'
+#,DE.η7>- 77!ѶҋCKfRpj*|%v7I¼R^|R^`@$S $ߎX<IC
+ZCٔ$}VX '^Q(TX9I<7<U4 'X7][ؖOxއ-ccPvaڿjη"MI JIL_@fJ4N}ዲ`^u_QNM{w^#vc>L xIL>H}בr_siiY;YcĠ^27_"H&~8WȱFo,"#'Jp
+إeB l|R tmumWrFYzRCVo.cmF"?^'nh%:@Zo'H?oZd4]{C5x[)JYܮX.@_6ӑ,hvf_ ԃFu..Q0f *,'i TR0jb{Sp|=a
+͓Pcjԫͯ7"U^m0>Gd9P_A/?7DzlbT ?IPSi%u-wI[3Yث%F|"u]:Yl@&ULi3F)/^B8=9?:<+xҟ'Eү}AJOÛ.u>
+pOLB$whKxT.S*q!m YeQ'Yv:g}~<mŵLHjG̐5byTBCF/;PiaNI~IRCv<CnVyStp\11
+9jN5ee`O:퓣S -"<G;4+PpV<16V5 x@];riLn7۠7EJs#_.=5w?٣lUZwKrb6%=^#Ck3ς:+s0|仏-V5p"3YݎOA^eD|ug:[3A|qڲ s\撅dN}9"y*:pJ&-a!&[_M{&~RknUAcFߑHPD`UgI۟{weE|ƨOBo\lj2};+۷
+bt4PmgCI d17+ۮ$[GBa*Z(B~+UBA/-0-17xe-B1VF̑9o:v.t<UU=O@`G!9'Ri{]|Qꍚj6:hC35+j*Ztˁc"(U/B}_L;!L{L9Էdb ]M-7RG
+#6%*w~n|9 (aOZ6gFO8uj5tlRU#}3#oj1zU^Z6 ,m*_IUh,c0#<]ΎX#QG(vNcFx܉ XzrϘN}I u}cPv=`R"mXrgxKyT#֗wTϮ;(DLH\*׹s#]0o!n(# Ijh #iA&,\TֱUbE|6bc,L,F#l'ThÔ#ayOѨ{VJkvz{g }p(bsrM=JuNOd=ۄ/_?Z?ppbv
+2$], GSfd;E/@Jy6H
+D1v;qvxB pffʘet6#ԁk)1-=>14]RD>ŻRsӪ]JLO졛DKBfʭ2sH2"v`=YGu1d(rI5T6BeG3W_Iw~`2=g@%zɓa~k _7y\˳9Yei#6whٮ Z ~8Ihhw׭x:D<ވ^'Q xC2u#RHi9E MȞ.,ȶhO߈ebtaq,nhN.دL19o ;{&j9`s=_+ ښ7:󫺋&t 9w
+?Oo!#(i##gJ X(:"N[?4p'4 /e$|KЮhOjWKa*?F@pٵBaJ|tmO~yu. X[ҝaij$W
+b1{?rN+Ho|beUVJ$؀Zgnu^^4Nw_㝠I\5C=n\qkszw6C\悶_j51pBj0WHa|74Zz{oyOOL*~\"Ǔ+w3CЯ'hQjJxgQt^<d:s;՞}3\_EMP<jmT~YxVkDDs]οS?v4
+G(F]P+MOi%<g`z;ZX˰-
+M#7tEYXw=07K8:ԘG}{JSz8>I>
+Y_PJ  60A:VRf?^oI,d>_
+{Kk}ƻdNM- ZcA/r:}Hh ؋<~} cn%./Io0FKSy.8ris1#:vU{Uk(ނ@)LG[wbS*\y#Y֩i
+5)Ž4gh +vyZX]ǎ4~NfH Q(6 eI٪*N9@V pK
+iL}2U+v%Pgǩ
+
+ ,KεsWf|Zn0<Fk')f.J
+#6n.ֺ^TU{vc8TC~5œ/Jm]ݕKJ/}+7q+uDİMP 9%|!MiQE)>0n4>G 7 FjV&nJaÝ*:d`t*Uk*
+0nf=d?M\YaT8R/%M /
+\G&iV ΋n=EA``
+`"(
+5xS_6iCc9edV ]ؿdP!93uik%#=u$.2{xAm+Q䋘h|<kE
+ sEӖ;] Yf?yݨ5~w"'`6Y(X4EI /;bQfab@٨&Ѕ܇H/ j}┋/Qҵb\8 R.-xM+:@`o֤ZV3O..obɲF(;6_Sh`W 92&" 6T)ߛu>>3O9-qbQ1ޟ_߆!9WKm>e&x~UQ(E_V+CŪ#LR79xD(KF=˭9DW[n!㭻Nj(.BH8-\{WLuK'ެx?-Uo̹xFy6ܱ][F}^KOlNh4pqϞ;%RL-*la)z#UMP/ه-CD|98HX~rIAJ I};|p|4`1l8p7.B|NXO~i"<kvrףyvGAFY"ʍ20+SAdW d!"4ZScau"Cqب!܍'*khd>0sDSy2UQ}<kEHza#4*E9'{ ĮkUŒsQȦ?dfm`^`*ERڱ{U 9*IQ+%>@+v3Ia?/k
+| p^[,ri
+W} ~#w'h-ήvmfAZ$> FsW Fҡ4eC@:S9"|^P5sЎVG%x;tbHrWƞČ
+[ik" wBz/,y~|כaЖ[ Py}G|}˟AÁ]%E6mu=|WHqڃ^5Ɋ2
+H}=2R s~;}}Ag @#H9_e Sfƒ, CH47%[^L̆E/v!T -%<lTJߺo*lUӢgK/X}6ޞ[ ;ە;Äy Ct1xAY'73dn\ p:)"{γW !j
+)a0љ:|;ڰNpQnݼQÉvJwy#γ0|ZsͅO4G*wjTTGWг+ Z)zm%~/OCrr$7SM,0
+hA`w$ .I"ߊm屑ZwF
+?S gZV-÷S_N&P!j( Qغ%=g H8U ~|qq^H1hp{ﬨ (Ҭփ
+98Qc= swal*}ZGytf#X_lf&0>zQqs8*=[]]ɳ\{yMF طl=t8Eq~X #8F bOI_1@֚뫘k quZq;a/_ .kwuC_!o#kG -"9G7(g/ c}\C&UFb޽&ʖmXӻA\ZH[t옴gy^KD >9EVQilG% ٙ#PoW.:vظ1quʆ㭲q[KRHp4k//~Y>_g'^~pR%<pe_J0/P?mڣQm7Iy[nI<e4˜KBw}ͭxk nRU:Q5_)%xA/%c6::G2lc/yn, d_qYAbT d;eW~+`
+ 嬠uUc/p4}75iX9u¹+ xuxZK5^dlPNM3Mץ}0ȜM۩1 >PDp}r$6TH,睙zH?ܻh4 e0xe`_9E5V+ip+j z%2!{[HC.ho|ެp! »:X?፿Fu<FIY\q pjCs0b2c̛,DaS-46XCkOe{j&Pb#%27 TVdRbcKuGd{i#l8 XrzÎɼbGi3SW/! T1īyOB/ocႅu
+/
+s
+1cyA]c_k~]T
+*6bXtG@{l>)2M7鰐5N6g~AA\2γuJ[:EKYaOZXtsJXݞ
+a݆NW )X/)03`
+d:N`$(}JkӲ 6aȳEgֆr>H;Zs} Jz|Z.PtN,t37'li90
+@\Z=MΞvA_ D>J>}ʭ/E#& oG4ETb/%*cO6XܱOgk$nڤS/Hi=CeOAPjћ sFJ!r :<TC]`fic3ُ%aGx9+psYE-a$VVmBjUtbhX,'I֫H;YVֺDiH+hn?8(MW p:IienTEN'_8QRtO"%qeш)bɁԞІBQ)-<ߪ `NC8 h28ӃQH#S\/eMF
+I- z"3EsL؄khe sU$?1)鿮#[H
+endobj
+1214 0 obj <<
+/Type /Font
+/Subtype /Type1
+/Encoding 5204 0 R
+/FirstChar 2
+/LastChar 122
+/Widths 5213 0 R
+/BaseFont /OSBOXU+NimbusSanL-Bold
+/FontDescriptor 1212 0 R
+>> endobj
+1212 0 obj <<
+/Ascent 722
+/CapHeight 722
+/Descent -217
+/FontName /OSBOXU+NimbusSanL-Bold
+/ItalicAngle 0
+/StemV 141
+/XHeight 532
+/FontBBox [-173 -307 1003 949]
+/Flags 4
+/CharSet (/fi/fl/exclam/quotedbl/ampersand/quoteright/parenleft/parenright/comma/hyphen/period/slash/zero/one/two/three/four/five/six/seven/eight/nine/colon/A/B/C/D/E/F/G/H/I/J/K/L/M/N/O/P/Q/R/S/T/U/V/W/X/Y/Z/underscore/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z)
+/FontFile 1213 0 R
+>> endobj
+5213 0 obj
+[611 611 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 333 474 0 0 0 722 278 333 333 0 0 278 333 278 278 556 556 556 556 556 556 556 556 556 556 333 0 0 0 0 0 0 722 722 722 722 667 611 778 722 278 556 722 611 833 722 778 667 778 722 667 611 722 667 944 667 667 611 0 0 0 0 556 0 556 611 556 611 556 333 611 611 278 278 556 278 889 611 611 611 611 389 556 333 611 556 778 556 556 500 ]
+endobj
+1215 0 obj <<
+/Type /Pages
+/Count 6
+/Parent 5214 0 R
+/Kids [1206 0 R 1217 0 R 1224 0 R 1369 0 R 1514 0 R 1662 0 R]
+>> endobj
+1899 0 obj <<
+/Type /Pages
+/Count 6
+/Parent 5214 0 R
+/Kids [1810 0 R 1944 0 R 1951 0 R 1971 0 R 2023 0 R 2030 0 R]
+>> endobj
+2086 0 obj <<
+/Type /Pages
+/Count 6
+/Parent 5214 0 R
+/Kids [2065 0 R 2088 0 R 2119 0 R 2173 0 R 2224 0 R 2256 0 R]
+>> endobj
+2330 0 obj <<
+/Type /Pages
+/Count 6
+/Parent 5214 0 R
+/Kids [2289 0 R 2332 0 R 2369 0 R 2392 0 R 2426 0 R 2472 0 R]
+>> endobj
+2548 0 obj <<
+/Type /Pages
+/Count 6
+/Parent 5214 0 R
+/Kids [2511 0 R 2550 0 R 2581 0 R 2605 0 R 2635 0 R 2664 0 R]
+>> endobj
+2708 0 obj <<
+/Type /Pages
+/Count 6
+/Parent 5214 0 R
+/Kids [2691 0 R 2710 0 R 2742 0 R 2768 0 R 2800 0 R 2833 0 R]
+>> endobj
+2899 0 obj <<
+/Type /Pages
+/Count 6
+/Parent 5215 0 R
+/Kids [2867 0 R 2901 0 R 2927 0 R 2965 0 R 3007 0 R 3030 0 R]
+>> endobj
+3076 0 obj <<
+/Type /Pages
+/Count 6
+/Parent 5215 0 R
+/Kids [3050 0 R 3079 0 R 3122 0 R 3158 0 R 3176 0 R 3193 0 R]
+>> endobj
+3278 0 obj <<
+/Type /Pages
+/Count 6
+/Parent 5215 0 R
+/Kids [3235 0 R 3280 0 R 3306 0 R 3350 0 R 3383 0 R 3408 0 R]
+>> endobj
+3468 0 obj <<
+/Type /Pages
+/Count 6
+/Parent 5215 0 R
+/Kids [3431 0 R 3470 0 R 3515 0 R 3554 0 R 3592 0 R 3642 0 R]
+>> endobj
+3703 0 obj <<
+/Type /Pages
+/Count 6
+/Parent 5215 0 R
+/Kids [3671 0 R 3705 0 R 3731 0 R 3758 0 R 3788 0 R 3817 0 R]
+>> endobj
+3873 0 obj <<
+/Type /Pages
+/Count 6
+/Parent 5215 0 R
+/Kids [3860 0 R 3875 0 R 3905 0 R 3978 0 R 3994 0 R 4022 0 R]
+>> endobj
+4108 0 obj <<
+/Type /Pages
+/Count 6
+/Parent 5216 0 R
+/Kids [4073 0 R 4110 0 R 4119 0 R 4152 0 R 4175 0 R 4198 0 R]
+>> endobj
+4242 0 obj <<
+/Type /Pages
+/Count 6
+/Parent 5216 0 R
+/Kids [4224 0 R 4244 0 R 4257 0 R 4272 0 R 4309 0 R 4349 0 R]
+>> endobj
+4417 0 obj <<
+/Type /Pages
+/Count 6
+/Parent 5216 0 R
+/Kids [4388 0 R 4419 0 R 4435 0 R 4451 0 R 4469 0 R 4487 0 R]
+>> endobj
+4521 0 obj <<
+/Type /Pages
+/Count 6
+/Parent 5216 0 R
+/Kids [4494 0 R 4523 0 R 4554 0 R 4582 0 R 4618 0 R 4643 0 R]
+>> endobj
+4670 0 obj <<
+/Type /Pages
+/Count 6
+/Parent 5216 0 R
+/Kids [4661 0 R 4672 0 R 4699 0 R 4724 0 R 4751 0 R 4760 0 R]
+>> endobj
+4833 0 obj <<
+/Type /Pages
+/Count 6
+/Parent 5216 0 R
+/Kids [4804 0 R 4835 0 R 4865 0 R 4896 0 R 4900 0 R 4916 0 R]
+>> endobj
+4963 0 obj <<
+/Type /Pages
+/Count 6
+/Parent 5217 0 R
+/Kids [4927 0 R 4965 0 R 4977 0 R 4990 0 R 4996 0 R 5046 0 R]
+>> endobj
+5104 0 obj <<
+/Type /Pages
+/Count 4
+/Parent 5217 0 R
+/Kids [5078 0 R 5106 0 R 5141 0 R 5169 0 R]
+>> endobj
+5214 0 obj <<
+/Type /Pages
+/Count 36
+/Parent 5218 0 R
+/Kids [1215 0 R 1899 0 R 2086 0 R 2330 0 R 2548 0 R 2708 0 R]
+>> endobj
+5215 0 obj <<
+/Type /Pages
+/Count 36
+/Parent 5218 0 R
+/Kids [2899 0 R 3076 0 R 3278 0 R 3468 0 R 3703 0 R 3873 0 R]
+>> endobj
+5216 0 obj <<
+/Type /Pages
+/Count 36
+/Parent 5218 0 R
+/Kids [4108 0 R 4242 0 R 4417 0 R 4521 0 R 4670 0 R 4833 0 R]
+>> endobj
+5217 0 obj <<
+/Type /Pages
+/Count 10
+/Parent 5218 0 R
+/Kids [4963 0 R 5104 0 R]
+>> endobj
+5218 0 obj <<
+/Type /Pages
+/Count 118
+/Kids [5214 0 R 5215 0 R 5216 0 R 5217 0 R]
+>> endobj
+5219 0 obj <<
+/Type /Outlines
+/First 3 0 R
+/Last 1199 0 R
+/Count 28
+>> endobj
+1203 0 obj <<
+/Title 1204 0 R
+/A 1201 0 R
+/Parent 1199 0 R
+>> endobj
+1199 0 obj <<
+/Title 1200 0 R
+/A 1197 0 R
+/Parent 5219 0 R
+/Prev 1187 0 R
+/First 1203 0 R
+/Last 1203 0 R
+/Count -1
+>> endobj
+1195 0 obj <<
+/Title 1196 0 R
+/A 1193 0 R
+/Parent 1191 0 R
+>> endobj
+1191 0 obj <<
+/Title 1192 0 R
+/A 1189 0 R
+/Parent 1187 0 R
+/First 1195 0 R
+/Last 1195 0 R
+/Count -1
+>> endobj
+1187 0 obj <<
+/Title 1188 0 R
+/A 1185 0 R
+/Parent 5219 0 R
+/Prev 1175 0 R
+/Next 1199 0 R
+/First 1191 0 R
+/Last 1191 0 R
+/Count -1
+>> endobj
+1183 0 obj <<
+/Title 1184 0 R
+/A 1181 0 R
+/Parent 1179 0 R
+>> endobj
+1179 0 obj <<
+/Title 1180 0 R
+/A 1177 0 R
+/Parent 1175 0 R
+/First 1183 0 R
+/Last 1183 0 R
+/Count -1
+>> endobj
+1175 0 obj <<
+/Title 1176 0 R
+/A 1173 0 R
+/Parent 5219 0 R
+/Prev 1163 0 R
+/Next 1187 0 R
+/First 1179 0 R
+/Last 1179 0 R
+/Count -1
+>> endobj
+1171 0 obj <<
+/Title 1172 0 R
+/A 1169 0 R
+/Parent 1167 0 R
+>> endobj
+1167 0 obj <<
+/Title 1168 0 R
+/A 1165 0 R
+/Parent 1163 0 R
+/First 1171 0 R
+/Last 1171 0 R
+/Count -1
+>> endobj
+1163 0 obj <<
+/Title 1164 0 R
+/A 1161 0 R
+/Parent 5219 0 R
+/Prev 1155 0 R
+/Next 1175 0 R
+/First 1167 0 R
+/Last 1167 0 R
+/Count -1
+>> endobj
+1159 0 obj <<
+/Title 1160 0 R
+/A 1157 0 R
+/Parent 1155 0 R
+>> endobj
+1155 0 obj <<
+/Title 1156 0 R
+/A 1153 0 R
+/Parent 5219 0 R
+/Prev 1139 0 R
+/Next 1163 0 R
+/First 1159 0 R
+/Last 1159 0 R
+/Count -1
+>> endobj
+1151 0 obj <<
+/Title 1152 0 R
+/A 1149 0 R
+/Parent 1143 0 R
+/Prev 1147 0 R
+>> endobj
+1147 0 obj <<
+/Title 1148 0 R
+/A 1145 0 R
+/Parent 1143 0 R
+/Next 1151 0 R
+>> endobj
+1143 0 obj <<
+/Title 1144 0 R
+/A 1141 0 R
+/Parent 1139 0 R
+/First 1147 0 R
+/Last 1151 0 R
+/Count -2
+>> endobj
+1139 0 obj <<
+/Title 1140 0 R
+/A 1137 0 R
+/Parent 5219 0 R
+/Prev 1127 0 R
+/Next 1155 0 R
+/First 1143 0 R
+/Last 1143 0 R
+/Count -1
+>> endobj
+1135 0 obj <<
+/Title 1136 0 R
+/A 1133 0 R
+/Parent 1131 0 R
+>> endobj
+1131 0 obj <<
+/Title 1132 0 R
+/A 1129 0 R
+/Parent 1127 0 R
+/First 1135 0 R
+/Last 1135 0 R
+/Count -1
+>> endobj
+1127 0 obj <<
+/Title 1128 0 R
+/A 1125 0 R
+/Parent 5219 0 R
+/Prev 1119 0 R
+/Next 1139 0 R
+/First 1131 0 R
+/Last 1131 0 R
+/Count -1
+>> endobj
+1123 0 obj <<
+/Title 1124 0 R
+/A 1121 0 R
+/Parent 1119 0 R
+>> endobj
+1119 0 obj <<
+/Title 1120 0 R
+/A 1117 0 R
+/Parent 5219 0 R
+/Prev 1111 0 R
+/Next 1127 0 R
+/First 1123 0 R
+/Last 1123 0 R
+/Count -1
+>> endobj
+1115 0 obj <<
+/Title 1116 0 R
+/A 1113 0 R
+/Parent 1111 0 R
+>> endobj
+1111 0 obj <<
+/Title 1112 0 R
+/A 1109 0 R
+/Parent 5219 0 R
+/Prev 1099 0 R
+/Next 1119 0 R
+/First 1115 0 R
+/Last 1115 0 R
+/Count -1
+>> endobj
+1107 0 obj <<
+/Title 1108 0 R
+/A 1105 0 R
+/Parent 1103 0 R
+>> endobj
+1103 0 obj <<
+/Title 1104 0 R
+/A 1101 0 R
+/Parent 1099 0 R
+/First 1107 0 R
+/Last 1107 0 R
+/Count -1
+>> endobj
+1099 0 obj <<
+/Title 1100 0 R
+/A 1097 0 R
+/Parent 5219 0 R
+/Prev 1079 0 R
+/Next 1111 0 R
+/First 1103 0 R
+/Last 1103 0 R
+/Count -1
+>> endobj
+1095 0 obj <<
+/Title 1096 0 R
+/A 1093 0 R
+/Parent 1083 0 R
+/Prev 1091 0 R
+>> endobj
+1091 0 obj <<
+/Title 1092 0 R
+/A 1089 0 R
+/Parent 1083 0 R
+/Prev 1087 0 R
+/Next 1095 0 R
+>> endobj
+1087 0 obj <<
+/Title 1088 0 R
+/A 1085 0 R
+/Parent 1083 0 R
+/Next 1091 0 R
+>> endobj
+1083 0 obj <<
+/Title 1084 0 R
+/A 1081 0 R
+/Parent 1079 0 R
+/First 1087 0 R
+/Last 1095 0 R
+/Count -3
+>> endobj
+1079 0 obj <<
+/Title 1080 0 R
+/A 1077 0 R
+/Parent 5219 0 R
+/Prev 1063 0 R
+/Next 1099 0 R
+/First 1083 0 R
+/Last 1083 0 R
+/Count -1
+>> endobj
+1075 0 obj <<
+/Title 1076 0 R
+/A 1073 0 R
+/Parent 1067 0 R
+/Prev 1071 0 R
+>> endobj
+1071 0 obj <<
+/Title 1072 0 R
+/A 1069 0 R
+/Parent 1067 0 R
+/Next 1075 0 R
+>> endobj
+1067 0 obj <<
+/Title 1068 0 R
+/A 1065 0 R
+/Parent 1063 0 R
+/First 1071 0 R
+/Last 1075 0 R
+/Count -2
+>> endobj
+1063 0 obj <<
+/Title 1064 0 R
+/A 1061 0 R
+/Parent 5219 0 R
+/Prev 1051 0 R
+/Next 1079 0 R
+/First 1067 0 R
+/Last 1067 0 R
+/Count -1
+>> endobj
+1059 0 obj <<
+/Title 1060 0 R
+/A 1057 0 R
+/Parent 1055 0 R
+>> endobj
+1055 0 obj <<
+/Title 1056 0 R
+/A 1053 0 R
+/Parent 1051 0 R
+/First 1059 0 R
+/Last 1059 0 R
+/Count -1
+>> endobj
+1051 0 obj <<
+/Title 1052 0 R
+/A 1049 0 R
+/Parent 5219 0 R
+/Prev 1043 0 R
+/Next 1063 0 R
+/First 1055 0 R
+/Last 1055 0 R
+/Count -1
+>> endobj
+1047 0 obj <<
+/Title 1048 0 R
+/A 1045 0 R
+/Parent 1043 0 R
+>> endobj
+1043 0 obj <<
+/Title 1044 0 R
+/A 1041 0 R
+/Parent 5219 0 R
+/Prev 1039 0 R
+/Next 1051 0 R
+/First 1047 0 R
+/Last 1047 0 R
+/Count -1
+>> endobj
+1039 0 obj <<
+/Title 1040 0 R
+/A 1037 0 R
+/Parent 5219 0 R
+/Prev 987 0 R
+/Next 1043 0 R
+>> endobj
+1035 0 obj <<
+/Title 1036 0 R
+/A 1033 0 R
+/Parent 987 0 R
+/Prev 1031 0 R
+>> endobj
+1031 0 obj <<
+/Title 1032 0 R
+/A 1029 0 R
+/Parent 987 0 R
+/Prev 1027 0 R
+/Next 1035 0 R
+>> endobj
+1027 0 obj <<
+/Title 1028 0 R
+/A 1025 0 R
+/Parent 987 0 R
+/Prev 1023 0 R
+/Next 1031 0 R
+>> endobj
+1023 0 obj <<
+/Title 1024 0 R
+/A 1021 0 R
+/Parent 987 0 R
+/Prev 1019 0 R
+/Next 1027 0 R
+>> endobj
+1019 0 obj <<
+/Title 1020 0 R
+/A 1017 0 R
+/Parent 987 0 R
+/Prev 1015 0 R
+/Next 1023 0 R
+>> endobj
+1015 0 obj <<
+/Title 1016 0 R
+/A 1013 0 R
+/Parent 987 0 R
+/Prev 1011 0 R
+/Next 1019 0 R
+>> endobj
+1011 0 obj <<
+/Title 1012 0 R
+/A 1009 0 R
+/Parent 987 0 R
+/Prev 1007 0 R
+/Next 1015 0 R
+>> endobj
+1007 0 obj <<
+/Title 1008 0 R
+/A 1005 0 R
+/Parent 987 0 R
+/Prev 1003 0 R
+/Next 1011 0 R
+>> endobj
+1003 0 obj <<
+/Title 1004 0 R
+/A 1001 0 R
+/Parent 987 0 R
+/Prev 999 0 R
+/Next 1007 0 R
+>> endobj
+999 0 obj <<
+/Title 1000 0 R
+/A 997 0 R
+/Parent 987 0 R
+/Prev 995 0 R
+/Next 1003 0 R
+>> endobj
+995 0 obj <<
+/Title 996 0 R
+/A 993 0 R
+/Parent 987 0 R
+/Prev 991 0 R
+/Next 999 0 R
+>> endobj
+991 0 obj <<
+/Title 992 0 R
+/A 989 0 R
+/Parent 987 0 R
+/Next 995 0 R
+>> endobj
+987 0 obj <<
+/Title 988 0 R
+/A 985 0 R
+/Parent 5219 0 R
+/Prev 971 0 R
+/Next 1039 0 R
+/First 991 0 R
+/Last 1035 0 R
+/Count -12
+>> endobj
+983 0 obj <<
+/Title 984 0 R
+/A 981 0 R
+/Parent 971 0 R
+/Prev 979 0 R
+>> endobj
+979 0 obj <<
+/Title 980 0 R
+/A 977 0 R
+/Parent 971 0 R
+/Prev 975 0 R
+/Next 983 0 R
+>> endobj
+975 0 obj <<
+/Title 976 0 R
+/A 973 0 R
+/Parent 971 0 R
+/Next 979 0 R
+>> endobj
+971 0 obj <<
+/Title 972 0 R
+/A 969 0 R
+/Parent 5219 0 R
+/Prev 959 0 R
+/Next 987 0 R
+/First 975 0 R
+/Last 983 0 R
+/Count -3
+>> endobj
+967 0 obj <<
+/Title 968 0 R
+/A 965 0 R
+/Parent 959 0 R
+/Prev 963 0 R
+>> endobj
+963 0 obj <<
+/Title 964 0 R
+/A 961 0 R
+/Parent 959 0 R
+/Next 967 0 R
+>> endobj
+959 0 obj <<
+/Title 960 0 R
+/A 957 0 R
+/Parent 5219 0 R
+/Prev 923 0 R
+/Next 971 0 R
+/First 963 0 R
+/Last 967 0 R
+/Count -2
+>> endobj
+955 0 obj <<
+/Title 956 0 R
+/A 953 0 R
+/Parent 923 0 R
+/Prev 951 0 R
+>> endobj
+951 0 obj <<
+/Title 952 0 R
+/A 949 0 R
+/Parent 923 0 R
+/Prev 947 0 R
+/Next 955 0 R
+>> endobj
+947 0 obj <<
+/Title 948 0 R
+/A 945 0 R
+/Parent 923 0 R
+/Prev 943 0 R
+/Next 951 0 R
+>> endobj
+943 0 obj <<
+/Title 944 0 R
+/A 941 0 R
+/Parent 923 0 R
+/Prev 939 0 R
+/Next 947 0 R
+>> endobj
+939 0 obj <<
+/Title 940 0 R
+/A 937 0 R
+/Parent 923 0 R
+/Prev 935 0 R
+/Next 943 0 R
+>> endobj
+935 0 obj <<
+/Title 936 0 R
+/A 933 0 R
+/Parent 923 0 R
+/Prev 931 0 R
+/Next 939 0 R
+>> endobj
+931 0 obj <<
+/Title 932 0 R
+/A 929 0 R
+/Parent 923 0 R
+/Prev 927 0 R
+/Next 935 0 R
+>> endobj
+927 0 obj <<
+/Title 928 0 R
+/A 925 0 R
+/Parent 923 0 R
+/Next 931 0 R
+>> endobj
+923 0 obj <<
+/Title 924 0 R
+/A 921 0 R
+/Parent 5219 0 R
+/Prev 875 0 R
+/Next 959 0 R
+/First 927 0 R
+/Last 955 0 R
+/Count -8
+>> endobj
+919 0 obj <<
+/Title 920 0 R
+/A 917 0 R
+/Parent 875 0 R
+/Prev 915 0 R
+>> endobj
+915 0 obj <<
+/Title 916 0 R
+/A 913 0 R
+/Parent 875 0 R
+/Prev 887 0 R
+/Next 919 0 R
+>> endobj
+911 0 obj <<
+/Title 912 0 R
+/A 909 0 R
+/Parent 887 0 R
+/Prev 907 0 R
+>> endobj
+907 0 obj <<
+/Title 908 0 R
+/A 905 0 R
+/Parent 887 0 R
+/Prev 903 0 R
+/Next 911 0 R
+>> endobj
+903 0 obj <<
+/Title 904 0 R
+/A 901 0 R
+/Parent 887 0 R
+/Prev 899 0 R
+/Next 907 0 R
+>> endobj
+899 0 obj <<
+/Title 900 0 R
+/A 897 0 R
+/Parent 887 0 R
+/Prev 895 0 R
+/Next 903 0 R
+>> endobj
+895 0 obj <<
+/Title 896 0 R
+/A 893 0 R
+/Parent 887 0 R
+/Prev 891 0 R
+/Next 899 0 R
+>> endobj
+891 0 obj <<
+/Title 892 0 R
+/A 889 0 R
+/Parent 887 0 R
+/Next 895 0 R
+>> endobj
+887 0 obj <<
+/Title 888 0 R
+/A 885 0 R
+/Parent 875 0 R
+/Prev 883 0 R
+/Next 915 0 R
+/First 891 0 R
+/Last 911 0 R
+/Count -6
+>> endobj
+883 0 obj <<
+/Title 884 0 R
+/A 881 0 R
+/Parent 875 0 R
+/Prev 879 0 R
+/Next 887 0 R
+>> endobj
+879 0 obj <<
+/Title 880 0 R
+/A 877 0 R
+/Parent 875 0 R
+/Next 883 0 R
+>> endobj
+875 0 obj <<
+/Title 876 0 R
+/A 873 0 R
+/Parent 5219 0 R
+/Prev 679 0 R
+/Next 923 0 R
+/First 879 0 R
+/Last 919 0 R
+/Count -5
+>> endobj
+871 0 obj <<
+/Title 872 0 R
+/A 869 0 R
+/Parent 855 0 R
+/Prev 867 0 R
+>> endobj
+867 0 obj <<
+/Title 868 0 R
+/A 865 0 R
+/Parent 855 0 R
+/Prev 863 0 R
+/Next 871 0 R
+>> endobj
+863 0 obj <<
+/Title 864 0 R
+/A 861 0 R
+/Parent 855 0 R
+/Prev 859 0 R
+/Next 867 0 R
+>> endobj
+859 0 obj <<
+/Title 860 0 R
+/A 857 0 R
+/Parent 855 0 R
+/Next 863 0 R
+>> endobj
+855 0 obj <<
+/Title 856 0 R
+/A 853 0 R
+/Parent 679 0 R
+/Prev 851 0 R
+/First 859 0 R
+/Last 871 0 R
+/Count -4
+>> endobj
+851 0 obj <<
+/Title 852 0 R
+/A 849 0 R
+/Parent 679 0 R
+/Prev 831 0 R
+/Next 855 0 R
+>> endobj
+847 0 obj <<
+/Title 848 0 R
+/A 845 0 R
+/Parent 839 0 R
+/Prev 843 0 R
+>> endobj
+843 0 obj <<
+/Title 844 0 R
+/A 841 0 R
+/Parent 839 0 R
+/Next 847 0 R
+>> endobj
+839 0 obj <<
+/Title 840 0 R
+/A 837 0 R
+/Parent 831 0 R
+/Prev 835 0 R
+/First 843 0 R
+/Last 847 0 R
+/Count -2
+>> endobj
+835 0 obj <<
+/Title 836 0 R
+/A 833 0 R
+/Parent 831 0 R
+/Next 839 0 R
+>> endobj
+831 0 obj <<
+/Title 832 0 R
+/A 829 0 R
+/Parent 679 0 R
+/Prev 807 0 R
+/Next 851 0 R
+/First 835 0 R
+/Last 839 0 R
+/Count -2
+>> endobj
+827 0 obj <<
+/Title 828 0 R
+/A 825 0 R
+/Parent 807 0 R
+/Prev 823 0 R
+>> endobj
+823 0 obj <<
+/Title 824 0 R
+/A 821 0 R
+/Parent 807 0 R
+/Prev 819 0 R
+/Next 827 0 R
+>> endobj
+819 0 obj <<
+/Title 820 0 R
+/A 817 0 R
+/Parent 807 0 R
+/Prev 815 0 R
+/Next 823 0 R
+>> endobj
+815 0 obj <<
+/Title 816 0 R
+/A 813 0 R
+/Parent 807 0 R
+/Prev 811 0 R
+/Next 819 0 R
+>> endobj
+811 0 obj <<
+/Title 812 0 R
+/A 809 0 R
+/Parent 807 0 R
+/Next 815 0 R
+>> endobj
+807 0 obj <<
+/Title 808 0 R
+/A 805 0 R
+/Parent 679 0 R
+/Prev 803 0 R
+/Next 831 0 R
+/First 811 0 R
+/Last 827 0 R
+/Count -5
+>> endobj
+803 0 obj <<
+/Title 804 0 R
+/A 801 0 R
+/Parent 679 0 R
+/Prev 783 0 R
+/Next 807 0 R
+>> endobj
+799 0 obj <<
+/Title 800 0 R
+/A 797 0 R
+/Parent 783 0 R
+/Prev 795 0 R
+>> endobj
+795 0 obj <<
+/Title 796 0 R
+/A 793 0 R
+/Parent 783 0 R
+/Prev 791 0 R
+/Next 799 0 R
+>> endobj
+791 0 obj <<
+/Title 792 0 R
+/A 789 0 R
+/Parent 783 0 R
+/Prev 787 0 R
+/Next 795 0 R
+>> endobj
+787 0 obj <<
+/Title 788 0 R
+/A 785 0 R
+/Parent 783 0 R
+/Next 791 0 R
+>> endobj
+783 0 obj <<
+/Title 784 0 R
+/A 781 0 R
+/Parent 679 0 R
+/Prev 747 0 R
+/Next 803 0 R
+/First 787 0 R
+/Last 799 0 R
+/Count -4
+>> endobj
+779 0 obj <<
+/Title 780 0 R
+/A 777 0 R
+/Parent 751 0 R
+/Prev 775 0 R
+>> endobj
+775 0 obj <<
+/Title 776 0 R
+/A 773 0 R
+/Parent 751 0 R
+/Prev 771 0 R
+/Next 779 0 R
+>> endobj
+771 0 obj <<
+/Title 772 0 R
+/A 769 0 R
+/Parent 751 0 R
+/Prev 767 0 R
+/Next 775 0 R
+>> endobj
+767 0 obj <<
+/Title 768 0 R
+/A 765 0 R
+/Parent 751 0 R
+/Prev 763 0 R
+/Next 771 0 R
+>> endobj
+763 0 obj <<
+/Title 764 0 R
+/A 761 0 R
+/Parent 751 0 R
+/Prev 759 0 R
+/Next 767 0 R
+>> endobj
+759 0 obj <<
+/Title 760 0 R
+/A 757 0 R
+/Parent 751 0 R
+/Prev 755 0 R
+/Next 763 0 R
+>> endobj
+755 0 obj <<
+/Title 756 0 R
+/A 753 0 R
+/Parent 751 0 R
+/Next 759 0 R
+>> endobj
+751 0 obj <<
+/Title 752 0 R
+/A 749 0 R
+/Parent 747 0 R
+/First 755 0 R
+/Last 779 0 R
+/Count -7
+>> endobj
+747 0 obj <<
+/Title 748 0 R
+/A 745 0 R
+/Parent 679 0 R
+/Prev 735 0 R
+/Next 783 0 R
+/First 751 0 R
+/Last 751 0 R
+/Count -1
+>> endobj
+743 0 obj <<
+/Title 744 0 R
+/A 741 0 R
+/Parent 735 0 R
+/Prev 739 0 R
+>> endobj
+739 0 obj <<
+/Title 740 0 R
+/A 737 0 R
+/Parent 735 0 R
+/Next 743 0 R
+>> endobj
+735 0 obj <<
+/Title 736 0 R
+/A 733 0 R
+/Parent 679 0 R
+/Prev 699 0 R
+/Next 747 0 R
+/First 739 0 R
+/Last 743 0 R
+/Count -2
+>> endobj
+731 0 obj <<
+/Title 732 0 R
+/A 729 0 R
+/Parent 699 0 R
+/Prev 727 0 R
+>> endobj
+727 0 obj <<
+/Title 728 0 R
+/A 725 0 R
+/Parent 699 0 R
+/Prev 723 0 R
+/Next 731 0 R
+>> endobj
+723 0 obj <<
+/Title 724 0 R
+/A 721 0 R
+/Parent 699 0 R
+/Prev 719 0 R
+/Next 727 0 R
+>> endobj
+719 0 obj <<
+/Title 720 0 R
+/A 717 0 R
+/Parent 699 0 R
+/Prev 703 0 R
+/Next 723 0 R
+>> endobj
+715 0 obj <<
+/Title 716 0 R
+/A 713 0 R
+/Parent 703 0 R
+/Prev 711 0 R
+>> endobj
+711 0 obj <<
+/Title 712 0 R
+/A 709 0 R
+/Parent 703 0 R
+/Prev 707 0 R
+/Next 715 0 R
+>> endobj
+707 0 obj <<
+/Title 708 0 R
+/A 705 0 R
+/Parent 703 0 R
+/Next 711 0 R
+>> endobj
+703 0 obj <<
+/Title 704 0 R
+/A 701 0 R
+/Parent 699 0 R
+/Next 719 0 R
+/First 707 0 R
+/Last 715 0 R
+/Count -3
+>> endobj
+699 0 obj <<
+/Title 700 0 R
+/A 697 0 R
+/Parent 679 0 R
+/Prev 695 0 R
+/Next 735 0 R
+/First 703 0 R
+/Last 731 0 R
+/Count -5
+>> endobj
+695 0 obj <<
+/Title 696 0 R
+/A 693 0 R
+/Parent 679 0 R
+/Prev 691 0 R
+/Next 699 0 R
+>> endobj
+691 0 obj <<
+/Title 692 0 R
+/A 689 0 R
+/Parent 679 0 R
+/Prev 687 0 R
+/Next 695 0 R
+>> endobj
+687 0 obj <<
+/Title 688 0 R
+/A 685 0 R
+/Parent 679 0 R
+/Prev 683 0 R
+/Next 691 0 R
+>> endobj
+683 0 obj <<
+/Title 684 0 R
+/A 681 0 R
+/Parent 679 0 R
+/Next 687 0 R
+>> endobj
+679 0 obj <<
+/Title 680 0 R
+/A 677 0 R
+/Parent 5219 0 R
+/Prev 643 0 R
+/Next 875 0 R
+/First 683 0 R
+/Last 855 0 R
+/Count -13
+>> endobj
+675 0 obj <<
+/Title 676 0 R
+/A 673 0 R
+/Parent 671 0 R
+>> endobj
+671 0 obj <<
+/Title 672 0 R
+/A 669 0 R
+/Parent 643 0 R
+/Prev 663 0 R
+/First 675 0 R
+/Last 675 0 R
+/Count -1
+>> endobj
+667 0 obj <<
+/Title 668 0 R
+/A 665 0 R
+/Parent 663 0 R
+>> endobj
+663 0 obj <<
+/Title 664 0 R
+/A 661 0 R
+/Parent 643 0 R
+/Prev 647 0 R
+/Next 671 0 R
+/First 667 0 R
+/Last 667 0 R
+/Count -1
+>> endobj
+659 0 obj <<
+/Title 660 0 R
+/A 657 0 R
+/Parent 647 0 R
+/Prev 655 0 R
+>> endobj
+655 0 obj <<
+/Title 656 0 R
+/A 653 0 R
+/Parent 647 0 R
+/Prev 651 0 R
+/Next 659 0 R
+>> endobj
+651 0 obj <<
+/Title 652 0 R
+/A 649 0 R
+/Parent 647 0 R
+/Next 655 0 R
+>> endobj
+647 0 obj <<
+/Title 648 0 R
+/A 645 0 R
+/Parent 643 0 R
+/Next 663 0 R
+/First 651 0 R
+/Last 659 0 R
+/Count -3
+>> endobj
+643 0 obj <<
+/Title 644 0 R
+/A 641 0 R
+/Parent 5219 0 R
+/Prev 331 0 R
+/Next 679 0 R
+/First 647 0 R
+/Last 671 0 R
+/Count -3
+>> endobj
+639 0 obj <<
+/Title 640 0 R
+/A 637 0 R
+/Parent 331 0 R
+/Prev 619 0 R
+>> endobj
+635 0 obj <<
+/Title 636 0 R
+/A 633 0 R
+/Parent 619 0 R
+/Prev 631 0 R
+>> endobj
+631 0 obj <<
+/Title 632 0 R
+/A 629 0 R
+/Parent 619 0 R
+/Prev 627 0 R
+/Next 635 0 R
+>> endobj
+627 0 obj <<
+/Title 628 0 R
+/A 625 0 R
+/Parent 619 0 R
+/Prev 623 0 R
+/Next 631 0 R
+>> endobj
+623 0 obj <<
+/Title 624 0 R
+/A 621 0 R
+/Parent 619 0 R
+/Next 627 0 R
+>> endobj
+619 0 obj <<
+/Title 620 0 R
+/A 617 0 R
+/Parent 331 0 R
+/Prev 615 0 R
+/Next 639 0 R
+/First 623 0 R
+/Last 635 0 R
+/Count -4
+>> endobj
+615 0 obj <<
+/Title 616 0 R
+/A 613 0 R
+/Parent 331 0 R
+/Prev 611 0 R
+/Next 619 0 R
+>> endobj
+611 0 obj <<
+/Title 612 0 R
+/A 609 0 R
+/Parent 331 0 R
+/Prev 607 0 R
+/Next 615 0 R
+>> endobj
+607 0 obj <<
+/Title 608 0 R
+/A 605 0 R
+/Parent 331 0 R
+/Prev 595 0 R
+/Next 611 0 R
+>> endobj
+603 0 obj <<
+/Title 604 0 R
+/A 601 0 R
+/Parent 595 0 R
+/Prev 599 0 R
+>> endobj
+599 0 obj <<
+/Title 600 0 R
+/A 597 0 R
+/Parent 595 0 R
+/Next 603 0 R
+>> endobj
+595 0 obj <<
+/Title 596 0 R
+/A 593 0 R
+/Parent 331 0 R
+/Prev 579 0 R
+/Next 607 0 R
+/First 599 0 R
+/Last 603 0 R
+/Count -2
+>> endobj
+591 0 obj <<
+/Title 592 0 R
+/A 589 0 R
+/Parent 579 0 R
+/Prev 587 0 R
+>> endobj
+587 0 obj <<
+/Title 588 0 R
+/A 585 0 R
+/Parent 579 0 R
+/Prev 583 0 R
+/Next 591 0 R
+>> endobj
+583 0 obj <<
+/Title 584 0 R
+/A 581 0 R
+/Parent 579 0 R
+/Next 587 0 R
+>> endobj
+579 0 obj <<
+/Title 580 0 R
+/A 577 0 R
+/Parent 331 0 R
+/Prev 575 0 R
+/Next 595 0 R
+/First 583 0 R
+/Last 591 0 R
+/Count -3
+>> endobj
+575 0 obj <<
+/Title 576 0 R
+/A 573 0 R
+/Parent 331 0 R
+/Prev 483 0 R
+/Next 579 0 R
+>> endobj
+571 0 obj <<
+/Title 572 0 R
+/A 569 0 R
+/Parent 515 0 R
+/Prev 523 0 R
+>> endobj
+567 0 obj <<
+/Title 568 0 R
+/A 565 0 R
+/Parent 523 0 R
+/Prev 563 0 R
+>> endobj
+563 0 obj <<
+/Title 564 0 R
+/A 561 0 R
+/Parent 523 0 R
+/Prev 559 0 R
+/Next 567 0 R
+>> endobj
+559 0 obj <<
+/Title 560 0 R
+/A 557 0 R
+/Parent 523 0 R
+/Prev 555 0 R
+/Next 563 0 R
+>> endobj
+555 0 obj <<
+/Title 556 0 R
+/A 553 0 R
+/Parent 523 0 R
+/Prev 551 0 R
+/Next 559 0 R
+>> endobj
+551 0 obj <<
+/Title 552 0 R
+/A 549 0 R
+/Parent 523 0 R
+/Prev 547 0 R
+/Next 555 0 R
+>> endobj
+547 0 obj <<
+/Title 548 0 R
+/A 545 0 R
+/Parent 523 0 R
+/Prev 543 0 R
+/Next 551 0 R
+>> endobj
+543 0 obj <<
+/Title 544 0 R
+/A 541 0 R
+/Parent 523 0 R
+/Prev 539 0 R
+/Next 547 0 R
+>> endobj
+539 0 obj <<
+/Title 540 0 R
+/A 537 0 R
+/Parent 523 0 R
+/Prev 535 0 R
+/Next 543 0 R
+>> endobj
+535 0 obj <<
+/Title 536 0 R
+/A 533 0 R
+/Parent 523 0 R
+/Prev 531 0 R
+/Next 539 0 R
+>> endobj
+531 0 obj <<
+/Title 532 0 R
+/A 529 0 R
+/Parent 523 0 R
+/Prev 527 0 R
+/Next 535 0 R
+>> endobj
+527 0 obj <<
+/Title 528 0 R
+/A 525 0 R
+/Parent 523 0 R
+/Next 531 0 R
+>> endobj
+523 0 obj <<
+/Title 524 0 R
+/A 521 0 R
+/Parent 515 0 R
+/Prev 519 0 R
+/Next 571 0 R
+/First 527 0 R
+/Last 567 0 R
+/Count -11
+>> endobj
+519 0 obj <<
+/Title 520 0 R
+/A 517 0 R
+/Parent 515 0 R
+/Next 523 0 R
+>> endobj
+515 0 obj <<
+/Title 516 0 R
+/A 513 0 R
+/Parent 483 0 R
+/Prev 503 0 R
+/First 519 0 R
+/Last 571 0 R
+/Count -3
+>> endobj
+511 0 obj <<
+/Title 512 0 R
+/A 509 0 R
+/Parent 503 0 R
+/Prev 507 0 R
+>> endobj
+507 0 obj <<
+/Title 508 0 R
+/A 505 0 R
+/Parent 503 0 R
+/Next 511 0 R
+>> endobj
+503 0 obj <<
+/Title 504 0 R
+/A 501 0 R
+/Parent 483 0 R
+/Prev 499 0 R
+/Next 515 0 R
+/First 507 0 R
+/Last 511 0 R
+/Count -2
+>> endobj
+499 0 obj <<
+/Title 500 0 R
+/A 497 0 R
+/Parent 483 0 R
+/Prev 491 0 R
+/Next 503 0 R
+>> endobj
+495 0 obj <<
+/Title 496 0 R
+/A 493 0 R
+/Parent 491 0 R
+>> endobj
+491 0 obj <<
+/Title 492 0 R
+/A 489 0 R
+/Parent 483 0 R
+/Prev 487 0 R
+/Next 499 0 R
+/First 495 0 R
+/Last 495 0 R
+/Count -1
+>> endobj
+487 0 obj <<
+/Title 488 0 R
+/A 485 0 R
+/Parent 483 0 R
+/Next 491 0 R
+>> endobj
+483 0 obj <<
+/Title 484 0 R
+/A 481 0 R
+/Parent 331 0 R
+/Prev 479 0 R
+/Next 575 0 R
+/First 487 0 R
+/Last 515 0 R
+/Count -5
+>> endobj
+479 0 obj <<
+/Title 480 0 R
+/A 477 0 R
+/Parent 331 0 R
+/Prev 475 0 R
+/Next 483 0 R
+>> endobj
+475 0 obj <<
+/Title 476 0 R
+/A 473 0 R
+/Parent 331 0 R
+/Prev 471 0 R
+/Next 479 0 R
+>> endobj
+471 0 obj <<
+/Title 472 0 R
+/A 469 0 R
+/Parent 331 0 R
+/Prev 447 0 R
+/Next 475 0 R
+>> endobj
+467 0 obj <<
+/Title 468 0 R
+/A 465 0 R
+/Parent 463 0 R
+>> endobj
+463 0 obj <<
+/Title 464 0 R
+/A 461 0 R
+/Parent 447 0 R
+/Prev 459 0 R
+/First 467 0 R
+/Last 467 0 R
+/Count -1
+>> endobj
+459 0 obj <<
+/Title 460 0 R
+/A 457 0 R
+/Parent 447 0 R
+/Prev 455 0 R
+/Next 463 0 R
+>> endobj
+455 0 obj <<
+/Title 456 0 R
+/A 453 0 R
+/Parent 447 0 R
+/Prev 451 0 R
+/Next 459 0 R
+>> endobj
+451 0 obj <<
+/Title 452 0 R
+/A 449 0 R
+/Parent 447 0 R
+/Next 455 0 R
+>> endobj
+447 0 obj <<
+/Title 448 0 R
+/A 445 0 R
+/Parent 331 0 R
+/Prev 443 0 R
+/Next 471 0 R
+/First 451 0 R
+/Last 463 0 R
+/Count -4
+>> endobj
+443 0 obj <<
+/Title 444 0 R
+/A 441 0 R
+/Parent 331 0 R
+/Prev 403 0 R
+/Next 447 0 R
+>> endobj
+439 0 obj <<
+/Title 440 0 R
+/A 437 0 R
+/Parent 411 0 R
+/Prev 435 0 R
+>> endobj
+435 0 obj <<
+/Title 436 0 R
+/A 433 0 R
+/Parent 411 0 R
+/Prev 431 0 R
+/Next 439 0 R
+>> endobj
+431 0 obj <<
+/Title 432 0 R
+/A 429 0 R
+/Parent 411 0 R
+/Prev 419 0 R
+/Next 435 0 R
+>> endobj
+427 0 obj <<
+/Title 428 0 R
+/A 425 0 R
+/Parent 419 0 R
+/Prev 423 0 R
+>> endobj
+423 0 obj <<
+/Title 424 0 R
+/A 421 0 R
+/Parent 419 0 R
+/Next 427 0 R
+>> endobj
+419 0 obj <<
+/Title 420 0 R
+/A 417 0 R
+/Parent 411 0 R
+/Prev 415 0 R
+/Next 431 0 R
+/First 423 0 R
+/Last 427 0 R
+/Count -2
+>> endobj
+415 0 obj <<
+/Title 416 0 R
+/A 413 0 R
+/Parent 411 0 R
+/Next 419 0 R
+>> endobj
+411 0 obj <<
+/Title 412 0 R
+/A 409 0 R
+/Parent 403 0 R
+/Prev 407 0 R
+/First 415 0 R
+/Last 439 0 R
+/Count -5
+>> endobj
+407 0 obj <<
+/Title 408 0 R
+/A 405 0 R
+/Parent 403 0 R
+/Next 411 0 R
+>> endobj
+403 0 obj <<
+/Title 404 0 R
+/A 401 0 R
+/Parent 331 0 R
+/Prev 335 0 R
+/Next 443 0 R
+/First 407 0 R
+/Last 411 0 R
+/Count -2
+>> endobj
+399 0 obj <<
+/Title 400 0 R
+/A 397 0 R
+/Parent 335 0 R
+/Prev 395 0 R
+>> endobj
+395 0 obj <<
+/Title 396 0 R
+/A 393 0 R
+/Parent 335 0 R
+/Prev 391 0 R
+/Next 399 0 R
+>> endobj
+391 0 obj <<
+/Title 392 0 R
+/A 389 0 R
+/Parent 335 0 R
+/Prev 387 0 R
+/Next 395 0 R
+>> endobj
+387 0 obj <<
+/Title 388 0 R
+/A 385 0 R
+/Parent 335 0 R
+/Prev 383 0 R
+/Next 391 0 R
+>> endobj
+383 0 obj <<
+/Title 384 0 R
+/A 381 0 R
+/Parent 335 0 R
+/Prev 379 0 R
+/Next 387 0 R
+>> endobj
+379 0 obj <<
+/Title 380 0 R
+/A 377 0 R
+/Parent 335 0 R
+/Prev 375 0 R
+/Next 383 0 R
+>> endobj
+375 0 obj <<
+/Title 376 0 R
+/A 373 0 R
+/Parent 335 0 R
+/Prev 371 0 R
+/Next 379 0 R
+>> endobj
+371 0 obj <<
+/Title 372 0 R
+/A 369 0 R
+/Parent 335 0 R
+/Prev 367 0 R
+/Next 375 0 R
+>> endobj
+367 0 obj <<
+/Title 368 0 R
+/A 365 0 R
+/Parent 335 0 R
+/Prev 363 0 R
+/Next 371 0 R
+>> endobj
+363 0 obj <<
+/Title 364 0 R
+/A 361 0 R
+/Parent 335 0 R
+/Prev 359 0 R
+/Next 367 0 R
+>> endobj
+359 0 obj <<
+/Title 360 0 R
+/A 357 0 R
+/Parent 335 0 R
+/Prev 355 0 R
+/Next 363 0 R
+>> endobj
+355 0 obj <<
+/Title 356 0 R
+/A 353 0 R
+/Parent 335 0 R
+/Prev 351 0 R
+/Next 359 0 R
+>> endobj
+351 0 obj <<
+/Title 352 0 R
+/A 349 0 R
+/Parent 335 0 R
+/Prev 347 0 R
+/Next 355 0 R
+>> endobj
+347 0 obj <<
+/Title 348 0 R
+/A 345 0 R
+/Parent 335 0 R
+/Prev 343 0 R
+/Next 351 0 R
+>> endobj
+343 0 obj <<
+/Title 344 0 R
+/A 341 0 R
+/Parent 335 0 R
+/Prev 339 0 R
+/Next 347 0 R
+>> endobj
+339 0 obj <<
+/Title 340 0 R
+/A 337 0 R
+/Parent 335 0 R
+/Next 343 0 R
+>> endobj
+335 0 obj <<
+/Title 336 0 R
+/A 333 0 R
+/Parent 331 0 R
+/Next 403 0 R
+/First 339 0 R
+/Last 399 0 R
+/Count -16
+>> endobj
+331 0 obj <<
+/Title 332 0 R
+/A 329 0 R
+/Parent 5219 0 R
+/Prev 39 0 R
+/Next 643 0 R
+/First 335 0 R
+/Last 639 0 R
+/Count -16
+>> endobj
+327 0 obj <<
+/Title 328 0 R
+/A 325 0 R
+/Parent 295 0 R
+/Prev 323 0 R
+>> endobj
+323 0 obj <<
+/Title 324 0 R
+/A 321 0 R
+/Parent 295 0 R
+/Prev 303 0 R
+/Next 327 0 R
+>> endobj
+319 0 obj <<
+/Title 320 0 R
+/A 317 0 R
+/Parent 303 0 R
+/Prev 315 0 R
+>> endobj
+315 0 obj <<
+/Title 316 0 R
+/A 313 0 R
+/Parent 303 0 R
+/Prev 311 0 R
+/Next 319 0 R
+>> endobj
+311 0 obj <<
+/Title 312 0 R
+/A 309 0 R
+/Parent 303 0 R
+/Prev 307 0 R
+/Next 315 0 R
+>> endobj
+307 0 obj <<
+/Title 308 0 R
+/A 305 0 R
+/Parent 303 0 R
+/Next 311 0 R
+>> endobj
+303 0 obj <<
+/Title 304 0 R
+/A 301 0 R
+/Parent 295 0 R
+/Prev 299 0 R
+/Next 323 0 R
+/First 307 0 R
+/Last 319 0 R
+/Count -4
+>> endobj
+299 0 obj <<
+/Title 300 0 R
+/A 297 0 R
+/Parent 295 0 R
+/Next 303 0 R
+>> endobj
+295 0 obj <<
+/Title 296 0 R
+/A 293 0 R
+/Parent 39 0 R
+/Prev 243 0 R
+/First 299 0 R
+/Last 327 0 R
+/Count -4
+>> endobj
+291 0 obj <<
+/Title 292 0 R
+/A 289 0 R
+/Parent 287 0 R
+>> endobj
+287 0 obj <<
+/Title 288 0 R
+/A 285 0 R
+/Parent 243 0 R
+/Prev 279 0 R
+/First 291 0 R
+/Last 291 0 R
+/Count -1
+>> endobj
+283 0 obj <<
+/Title 284 0 R
+/A 281 0 R
+/Parent 279 0 R
+>> endobj
+279 0 obj <<
+/Title 280 0 R
+/A 277 0 R
+/Parent 243 0 R
+/Prev 275 0 R
+/Next 287 0 R
+/First 283 0 R
+/Last 283 0 R
+/Count -1
+>> endobj
+275 0 obj <<
+/Title 276 0 R
+/A 273 0 R
+/Parent 243 0 R
+/Prev 271 0 R
+/Next 279 0 R
+>> endobj
+271 0 obj <<
+/Title 272 0 R
+/A 269 0 R
+/Parent 243 0 R
+/Prev 251 0 R
+/Next 275 0 R
+>> endobj
+267 0 obj <<
+/Title 268 0 R
+/A 265 0 R
+/Parent 255 0 R
+/Prev 263 0 R
+>> endobj
+263 0 obj <<
+/Title 264 0 R
+/A 261 0 R
+/Parent 255 0 R
+/Prev 259 0 R
+/Next 267 0 R
+>> endobj
+259 0 obj <<
+/Title 260 0 R
+/A 257 0 R
+/Parent 255 0 R
+/Next 263 0 R
+>> endobj
+255 0 obj <<
+/Title 256 0 R
+/A 253 0 R
+/Parent 251 0 R
+/First 259 0 R
+/Last 267 0 R
+/Count -3
+>> endobj
+251 0 obj <<
+/Title 252 0 R
+/A 249 0 R
+/Parent 243 0 R
+/Prev 247 0 R
+/Next 271 0 R
+/First 255 0 R
+/Last 255 0 R
+/Count -1
+>> endobj
+247 0 obj <<
+/Title 248 0 R
+/A 245 0 R
+/Parent 243 0 R
+/Next 251 0 R
+>> endobj
+243 0 obj <<
+/Title 244 0 R
+/A 241 0 R
+/Parent 39 0 R
+/Prev 203 0 R
+/Next 295 0 R
+/First 247 0 R
+/Last 287 0 R
+/Count -6
+>> endobj
+239 0 obj <<
+/Title 240 0 R
+/A 237 0 R
+/Parent 203 0 R
+/Prev 227 0 R
+>> endobj
+235 0 obj <<
+/Title 236 0 R
+/A 233 0 R
+/Parent 227 0 R
+/Prev 231 0 R
+>> endobj
+231 0 obj <<
+/Title 232 0 R
+/A 229 0 R
+/Parent 227 0 R
+/Next 235 0 R
+>> endobj
+227 0 obj <<
+/Title 228 0 R
+/A 225 0 R
+/Parent 203 0 R
+/Prev 207 0 R
+/Next 239 0 R
+/First 231 0 R
+/Last 235 0 R
+/Count -2
+>> endobj
+223 0 obj <<
+/Title 224 0 R
+/A 221 0 R
+/Parent 207 0 R
+/Prev 219 0 R
+>> endobj
+219 0 obj <<
+/Title 220 0 R
+/A 217 0 R
+/Parent 207 0 R
+/Prev 215 0 R
+/Next 223 0 R
+>> endobj
+215 0 obj <<
+/Title 216 0 R
+/A 213 0 R
+/Parent 207 0 R
+/Prev 211 0 R
+/Next 219 0 R
+>> endobj
+211 0 obj <<
+/Title 212 0 R
+/A 209 0 R
+/Parent 207 0 R
+/Next 215 0 R
+>> endobj
+207 0 obj <<
+/Title 208 0 R
+/A 205 0 R
+/Parent 203 0 R
+/Next 227 0 R
+/First 211 0 R
+/Last 223 0 R
+/Count -4
+>> endobj
+203 0 obj <<
+/Title 204 0 R
+/A 201 0 R
+/Parent 39 0 R
+/Prev 199 0 R
+/Next 243 0 R
+/First 207 0 R
+/Last 239 0 R
+/Count -3
+>> endobj
+199 0 obj <<
+/Title 200 0 R
+/A 197 0 R
+/Parent 39 0 R
+/Prev 179 0 R
+/Next 203 0 R
+>> endobj
+195 0 obj <<
+/Title 196 0 R
+/A 193 0 R
+/Parent 179 0 R
+/Prev 191 0 R
+>> endobj
+191 0 obj <<
+/Title 192 0 R
+/A 189 0 R
+/Parent 179 0 R
+/Prev 187 0 R
+/Next 195 0 R
+>> endobj
+187 0 obj <<
+/Title 188 0 R
+/A 185 0 R
+/Parent 179 0 R
+/Prev 183 0 R
+/Next 191 0 R
+>> endobj
+183 0 obj <<
+/Title 184 0 R
+/A 181 0 R
+/Parent 179 0 R
+/Next 187 0 R
+>> endobj
+179 0 obj <<
+/Title 180 0 R
+/A 177 0 R
+/Parent 39 0 R
+/Prev 87 0 R
+/Next 199 0 R
+/First 183 0 R
+/Last 195 0 R
+/Count -4
+>> endobj
+175 0 obj <<
+/Title 176 0 R
+/A 173 0 R
+/Parent 87 0 R
+/Prev 155 0 R
+>> endobj
+171 0 obj <<
+/Title 172 0 R
+/A 169 0 R
+/Parent 155 0 R
+/Prev 159 0 R
+>> endobj
+167 0 obj <<
+/Title 168 0 R
+/A 165 0 R
+/Parent 159 0 R
+/Prev 163 0 R
+>> endobj
+163 0 obj <<
+/Title 164 0 R
+/A 161 0 R
+/Parent 159 0 R
+/Next 167 0 R
+>> endobj
+159 0 obj <<
+/Title 160 0 R
+/A 157 0 R
+/Parent 155 0 R
+/Next 171 0 R
+/First 163 0 R
+/Last 167 0 R
+/Count -2
+>> endobj
+155 0 obj <<
+/Title 156 0 R
+/A 153 0 R
+/Parent 87 0 R
+/Prev 151 0 R
+/Next 175 0 R
+/First 159 0 R
+/Last 171 0 R
+/Count -2
+>> endobj
+151 0 obj <<
+/Title 152 0 R
+/A 149 0 R
+/Parent 87 0 R
+/Prev 95 0 R
+/Next 155 0 R
+>> endobj
+147 0 obj <<
+/Title 148 0 R
+/A 145 0 R
+/Parent 135 0 R
+/Prev 143 0 R
+>> endobj
+143 0 obj <<
+/Title 144 0 R
+/A 141 0 R
+/Parent 135 0 R
+/Prev 139 0 R
+/Next 147 0 R
+>> endobj
+139 0 obj <<
+/Title 140 0 R
+/A 137 0 R
+/Parent 135 0 R
+/Next 143 0 R
+>> endobj
+135 0 obj <<
+/Title 136 0 R
+/A 133 0 R
+/Parent 95 0 R
+/Prev 123 0 R
+/First 139 0 R
+/Last 147 0 R
+/Count -3
+>> endobj
+131 0 obj <<
+/Title 132 0 R
+/A 129 0 R
+/Parent 123 0 R
+/Prev 127 0 R
+>> endobj
+127 0 obj <<
+/Title 128 0 R
+/A 125 0 R
+/Parent 123 0 R
+/Next 131 0 R
+>> endobj
+123 0 obj <<
+/Title 124 0 R
+/A 121 0 R
+/Parent 95 0 R
+/Prev 103 0 R
+/Next 135 0 R
+/First 127 0 R
+/Last 131 0 R
+/Count -2
+>> endobj
+119 0 obj <<
+/Title 120 0 R
+/A 117 0 R
+/Parent 103 0 R
+/Prev 115 0 R
+>> endobj
+115 0 obj <<
+/Title 116 0 R
+/A 113 0 R
+/Parent 103 0 R
+/Prev 111 0 R
+/Next 119 0 R
+>> endobj
+111 0 obj <<
+/Title 112 0 R
+/A 109 0 R
+/Parent 103 0 R
+/Prev 107 0 R
+/Next 115 0 R
+>> endobj
+107 0 obj <<
+/Title 108 0 R
+/A 105 0 R
+/Parent 103 0 R
+/Next 111 0 R
+>> endobj
+103 0 obj <<
+/Title 104 0 R
+/A 101 0 R
+/Parent 95 0 R
+/Prev 99 0 R
+/Next 123 0 R
+/First 107 0 R
+/Last 119 0 R
+/Count -4
+>> endobj
+99 0 obj <<
+/Title 100 0 R
+/A 97 0 R
+/Parent 95 0 R
+/Next 103 0 R
+>> endobj
+95 0 obj <<
+/Title 96 0 R
+/A 93 0 R
+/Parent 87 0 R
+/Prev 91 0 R
+/Next 151 0 R
+/First 99 0 R
+/Last 135 0 R
+/Count -4
+>> endobj
+91 0 obj <<
+/Title 92 0 R
+/A 89 0 R
+/Parent 87 0 R
+/Next 95 0 R
+>> endobj
+87 0 obj <<
+/Title 88 0 R
+/A 85 0 R
+/Parent 39 0 R
+/Prev 43 0 R
+/Next 179 0 R
+/First 91 0 R
+/Last 175 0 R
+/Count -5
+>> endobj
+83 0 obj <<
+/Title 84 0 R
+/A 81 0 R
+/Parent 43 0 R
+/Prev 79 0 R
+>> endobj
+79 0 obj <<
+/Title 80 0 R
+/A 77 0 R
+/Parent 43 0 R
+/Prev 75 0 R
+/Next 83 0 R
+>> endobj
+75 0 obj <<
+/Title 76 0 R
+/A 73 0 R
+/Parent 43 0 R
+/Prev 71 0 R
+/Next 79 0 R
+>> endobj
+71 0 obj <<
+/Title 72 0 R
+/A 69 0 R
+/Parent 43 0 R
+/Prev 67 0 R
+/Next 75 0 R
+>> endobj
+67 0 obj <<
+/Title 68 0 R
+/A 65 0 R
+/Parent 43 0 R
+/Prev 51 0 R
+/Next 71 0 R
+>> endobj
+63 0 obj <<
+/Title 64 0 R
+/A 61 0 R
+/Parent 51 0 R
+/Prev 59 0 R
+>> endobj
+59 0 obj <<
+/Title 60 0 R
+/A 57 0 R
+/Parent 51 0 R
+/Prev 55 0 R
+/Next 63 0 R
+>> endobj
+55 0 obj <<
+/Title 56 0 R
+/A 53 0 R
+/Parent 51 0 R
+/Next 59 0 R
+>> endobj
+51 0 obj <<
+/Title 52 0 R
+/A 49 0 R
+/Parent 43 0 R
+/Prev 47 0 R
+/Next 67 0 R
+/First 55 0 R
+/Last 63 0 R
+/Count -3
+>> endobj
+47 0 obj <<
+/Title 48 0 R
+/A 45 0 R
+/Parent 43 0 R
+/Next 51 0 R
+>> endobj
+43 0 obj <<
+/Title 44 0 R
+/A 41 0 R
+/Parent 39 0 R
+/Next 87 0 R
+/First 47 0 R
+/Last 83 0 R
+/Count -7
+>> endobj
+39 0 obj <<
+/Title 40 0 R
+/A 37 0 R
+/Parent 5219 0 R
+/Prev 15 0 R
+/Next 331 0 R
+/First 43 0 R
+/Last 295 0 R
+/Count -7
+>> endobj
+35 0 obj <<
+/Title 36 0 R
+/A 33 0 R
+/Parent 15 0 R
+/Prev 31 0 R
+>> endobj
+31 0 obj <<
+/Title 32 0 R
+/A 29 0 R
+/Parent 15 0 R
+/Prev 27 0 R
+/Next 35 0 R
+>> endobj
+27 0 obj <<
+/Title 28 0 R
+/A 25 0 R
+/Parent 15 0 R
+/Prev 23 0 R
+/Next 31 0 R
+>> endobj
+23 0 obj <<
+/Title 24 0 R
+/A 21 0 R
+/Parent 15 0 R
+/Prev 19 0 R
+/Next 27 0 R
+>> endobj
+19 0 obj <<
+/Title 20 0 R
+/A 17 0 R
+/Parent 15 0 R
+/Next 23 0 R
+>> endobj
+15 0 obj <<
+/Title 16 0 R
+/A 13 0 R
+/Parent 5219 0 R
+/Prev 11 0 R
+/Next 39 0 R
+/First 19 0 R
+/Last 35 0 R
+/Count -5
+>> endobj
+11 0 obj <<
+/Title 12 0 R
+/A 9 0 R
+/Parent 5219 0 R
+/Prev 7 0 R
+/Next 15 0 R
+>> endobj
+7 0 obj <<
+/Title 8 0 R
+/A 5 0 R
+/Parent 5219 0 R
+/Prev 3 0 R
+/Next 11 0 R
+>> endobj
+3 0 obj <<
+/Title 4 0 R
+/A 1 0 R
+/Parent 5219 0 R
+/Next 7 0 R
+>> endobj
+5220 0 obj <<
+/Names [(1.0) 2 0 R (10.0) 922 0 R (10.50.1) 926 0 R (10.51.1) 930 0 R (10.52.1) 934 0 R (10.53.1) 938 0 R (10.54.1) 942 0 R (10.55.1) 946 0 R (10.56.1) 950 0 R (10.57.1) 954 0 R (1000) 2828 0 R (1003) 2829 0 R (1005) 2831 0 R (1008) 2837 0 R (1009) 2838 0 R (101) 2015 0 R (1010) 2839 0 R (1011) 2840 0 R (1012) 2841 0 R (1013) 2842 0 R (1014) 2843 0 R (1015) 2844 0 R (1016) 2845 0 R (1017) 2846 0 R (1018) 2847 0 R (1019) 2848 0 R (102) 2016 0 R (1022) 2849 0 R (1023) 2850 0 R (1024) 2851 0 R (1025) 2852 0 R (1026) 2853 0 R (1027) 2854 0 R (1028) 2855 0 R (1029) 2856 0 R (103) 2017 0 R (1030) 2857 0 R (1031) 2858 0 R (1032) 2859 0 R (1033) 2860 0 R (1034) 2861 0 R (1035) 2862 0 R (1036) 2863 0 R (1037) 2864 0 R (1038) 2865 0 R (1039) 2870 0 R (1040) 2871 0 R (1041) 2836 0 R (1044) 2872 0 R (1045) 2873 0 R (1046) 2874 0 R (1047) 2875 0 R (1048) 2876 0 R (1049) 2877 0 R (1050) 2878 0 R (1051) 2879 0 R (1052) 2880 0 R (1053) 2881 0 R (1054) 2882 0 R (1055) 2883 0 R (1056) 2884 0 R (1057) 2885 0 R (1061) 2887 0 R (1062) 2888 0 R (1063) 2889 0 R (1064) 2890 0 R (1065) 2891 0 R (1066) 2892 0 R (1067) 2893 0 R (1068) 2894 0 R (1070) 2896 0 R (1071) 2897 0 R (1072) 2898 0 R (1073) 2904 0 R (1074) 2905 0 R (1075) 2906 0 R (1076) 2907 0 R (1077) 2908 0 R (1078) 2909 0 R (1079) 2910 0 R (1080) 2911 0 R (1081) 2912 0 R (1082) 2913 0 R (1083) 2914 0 R (1084) 2915 0 R (1085) 2916 0 R (1087) 2918 0 R (1090) 2919 0 R (1091) 2920 0 R (1093) 2922 0 R (1094) 2923 0 R (1095) 2924 0 R (1096) 2925 0 R (11.0) 958 0 R (11.58.1) 962 0 R (11.59.1) 966 0 R (1101) 2930 0 R (1104) 2931 0 R (1105) 2932 0 R (1107) 2933 0 R (1109) 2934 0 R (1110) 2935 0 R (1111) 2936 0 R (1113) 2937 0 R (1114) 2938 0 R (1115) 2939 0 R (1116) 2940 0 R (1117) 2941 0 R (1118) 2942 0 R (1119) 2943 0 R (1121) 2944 0 R (1122) 2945 0 R (1123) 2946 0 R (1124) 2947 0 R (1125) 2948 0 R (1126) 2949 0 R (1127) 2950 0 R (1129) 2951 0 R (1130) 2952 0 R (1131) 2953 0 R (1132) 2954 0 R (1133) 2955 0 R (1134) 2956 0 R (1135) 2957 0 R (1137) 2958 0 R (1138) 2959 0 R (1139) 2960 0 R (1141) 2961 0 R (1142) 2962 0 R (1143) 2963 0 R (1144) 2968 0 R (1145) 2969 0 R (1146) 2970 0 R (1148) 2971 0 R (1149) 2972 0 R (1150) 2973 0 R (1151) 2974 0 R (1152) 2975 0 R (1153) 2976 0 R (1154) 2977 0 R (1156) 2978 0 R (1157) 2979 0 R (1158) 2980 0 R (1159) 2981 0 R (1160) 2982 0 R (1161) 2983 0 R (1162) 2984 0 R (1164) 2985 0 R (1165) 2986 0 R (1166) 2987 0 R (1167) 2988 0 R (1168) 2989 0 R (1169) 2990 0 R (1170) 2991 0 R (1172) 2992 0 R (1173) 2993 0 R (1174) 2994 0 R (1175) 2995 0 R (1176) 2996 0 R (1177) 2997 0 R (1179) 2998 0 R (1180) 2999 0 R (1181) 3000 0 R (1182) 3001 0 R (1183) 3002 0 R (1185) 3003 0 R (1186) 3004 0 R (1187) 3005 0 R (1190) 3010 0 R (1193) 3011 0 R (1194) 3012 0 R (1196) 3013 0 R (1197) 3014 0 R (1198) 3015 0 R (1199) 3016 0 R (12.0) 970 0 R (12.60.1) 974 0 R (12.61.1) 978 0 R (12.62.1) 982 0 R (1201) 3017 0 R (1202) 3018 0 R (1203) 3019 0 R (1204) 3020 0 R (1207) 3021 0 R (1210) 3022 0 R (1211) 3023 0 R (1213) 3024 0 R (1214) 3025 0 R (1215) 3026 0 R (1216) 3027 0 R (1217) 3028 0 R (1218) 3033 0 R (1220) 3034 0 R (1221) 3035 0 R (1222) 3036 0 R (1225) 3037 0 R (1226) 3038 0 R (1228) 3039 0 R (1229) 3040 0 R (1230) 3041 0 R (1232) 3042 0 R (1233) 3043 0 R (1234) 3044 0 R (1237) 3045 0 R (1238) 3046 0 R (1241) 3047 0 R (1242) 3048 0 R (1245) 3053 0 R (1247) 3055 0 R (1249) 3056 0 R (1250) 3057 0 R (1251) 3058 0 R (1253) 3059 0 R (1254) 3060 0 R (1255) 3061 0 R (1258) 3063 0 R (1259) 3064 0 R (1260) 3065 0 R (1264) 3067 0 R (1265) 3068 0 R (1266) 3069 0 R (1267) 3070 0 R (1268) 3071 0 R (1271) 3073 0 R (1272) 3074 0 R (1273) 3075 0 R (1276) 3083 0 R (1277) 3084 0 R (1278) 3085 0 R (1279) 3086 0 R (1280) 3087 0 R (1283) 3089 0 R (1284) 3090 0 R (1285) 3091 0 R (1286) 3092 0 R (1287) 3093 0 R (1288) 3094 0 R (1289) 3095 0 R (1290) 3096 0 R (1291) 3097 0 R (1292) 3098 0 R (1293) 3099 0 R (1294) 3100 0 R (1297) 3102 0 R (1298) 3103 0 R (1299) 3104 0 R (13.0) 986 0 R (13.63.1) 990 0 R (13.64.1) 994 0 R (13.65.1) 998 0 R (13.66.1) 1002 0 R (13.67.1) 1006 0 R (13.68.1) 1010 0 R (13.69.1) 1014 0 R (13.70.1) 1018 0 R (13.71.1) 1022 0 R (13.72.1) 1026 0 R (13.73.1) 1030 0 R (13.74.1) 1034 0 R (1300) 3105 0 R (1303) 3107 0 R (1304) 3108 0 R (1305) 3109 0 R (1306) 3110 0 R (1309) 3112 0 R (1310) 3113 0 R (1311) 3114 0 R (1312) 3115 0 R (1315) 3117 0 R (1316) 3118 0 R (1317) 3119 0 R (1318) 3120 0 R (1321) 3125 0 R (1322) 3126 0 R (1323) 3127 0 R (1325) 3129 0 R (1326) 3130 0 R (1329) 3132 0 R (1330) 3133 0 R (1331) 3134 0 R (1332) 3135 0 R (1333) 3136 0 R (1336) 3138 0 R (1337) 3139 0 R (1340) 3141 0 R (1341) 3142 0 R (1344) 3144 0 R (1345) 3145 0 R (1346) 3146 0 R (1347) 3147 0 R (1350) 3148 0 R (1351) 3149 0 R (1353) 3150 0 R (1354) 3151 0 R (1355) 3152 0 R (1356) 3153 0 R (1358) 3154 0 R (1359) 3155 0 R (1360) 3156 0 R (1362) 3161 0 R (1363) 3162 0 R (1364) 3163 0 R (1366) 3164 0 R (1367) 3165 0 R (1368) 3166 0 R (1371) 3167 0 R (1374) 3168 0 R (1377) 3169 0 R (1378) 3170 0 R (1379) 3171 0 R (1380) 3172 0 R (1381) 3173 0 R (1384) 3174 0 R (1389) 3180 0 R (1390) 3181 0 R (1391) 3182 0 R (1396) 3183 0 R (1397) 3184 0 R (1398) 3185 0 R (1399) 3186 0 R (14.0) 1038 0 R (1400) 3187 0 R (1401) 3188 0 R (1406) 3190 0 R (1407) 3191 0 R (1408) 3196 0 R (1409) 3197 0 R (1413) 3199 0 R (1414) 3200 0 R (1415) 3201 0 R (1416) 3202 0 R (1417) 3203 0 R (1418) 3204 0 R (1419) 3205 0 R (1420) 3206 0 R (1421) 3207 0 R (1422) 3208 0 R (1423) 3209 0 R (1426) 3210 0 R (1427) 3211 0 R (1428) 3212 0 R (1429) 3213 0 R (1430) 3214 0 R (1431) 3215 0 R (1432) 3216 0 R (1433) 3217 0 R (1434) 3218 0 R (1435) 3219 0 R (1436) 3220 0 R (1437) 3221 0 R (1438) 3222 0 R (1439) 3223 0 R (1440) 3224 0 R (1441) 3225 0 R (1442) 3226 0 R (1443) 3227 0 R (1444) 3228 0 R (1445) 3229 0 R (1446) 3230 0 R (1447) 3231 0 R (1448) 3232 0 R (1449) 3233 0 R (1450) 3239 0 R (1451) 3240 0 R (1452) 3241 0 R (1453) 3242 0 R (1454) 3243 0 R (1455) 3244 0 R (1456) 3245 0 R (1457) 3246 0 R (1458) 3247 0 R (1459) 3248 0 R (1460) 3249 0 R (1461) 3250 0 R (1462) 3251 0 R (1463) 3252 0 R (1464) 3253 0 R (1465) 3254 0 R (1466) 3255 0 R (1467) 3256 0 R (1468) 3257 0 R (1469) 3258 0 R (1470) 3259 0 R (1471) 3260 0 R (1472) 3261 0 R (1473) 3262 0 R (1474) 3263 0 R (1475) 3264 0 R (1476) 3265 0 R (1477) 3266 0 R (1478) 3267 0 R (1479) 3268 0 R (1480) 3269 0 R (1483) 3270 0 R (1484) 3271 0 R (1488) 3273 0 R (1489) 3274 0 R (1490) 3275 0 R (1491) 3276 0 R (1492) 3277 0 R (1493) 3238 0 R (1494) 3284 0 R (1495) 3285 0 R (1496) 3286 0 R (1499) 3287 0 R (15.0) 1042 0 R (15.74.100.2) 1046 0 R (1500) 3288 0 R (1501) 3289 0 R (1502) 3290 0 R (1503) 3291 0 R (1504) 3292 0 R (1507) 3293 0 R (1510) 3296 0 R (1511) 3297 0 R (1512) 3298 0 R (1513) 3299 0 R (1514) 3300 0 R (1516) 3301 0 R (1517) 3302 0 R (1518) 3303 0 R (1520) 3309 0 R (1521) 3310 0 R (1522) 3311 0 R (1524) 3283 0 R (1525) 3312 0 R (1526) 3313 0 R (1528) 3314 0 R (1529) 3315 0 R (1530) 3316 0 R (1532) 3317 0 R (1533) 3318 0 R (1534) 3319 0 R (1536) 3320 0 R (1537) 3321 0 R (1538) 3322 0 R (1540) 3323 0 R (1541) 3324 0 R (1542) 3325 0 R (1544) 3326 0 R (1545) 3327 0 R (1546) 3328 0 R (1548) 3329 0 R (1549) 3330 0 R (1550) 3331 0 R (1551) 3332 0 R (1555) 3334 0 R (1556) 3335 0 R (1557) 3336 0 R (1558) 3337 0 R (1559) 3338 0 R (1560) 3339 0 R (1561) 3340 0 R (1562) 3341 0 R (1563) 3342 0 R (1564) 3343 0 R (1565) 3344 0 R (1566) 3345 0 R (1567) 3346 0 R (1571) 3348 0 R (1574) 3353 0 R (1575) 3354 0 R (1577) 3356 0 R (1579) 3358 0 R (1583) 3360 0 R (1584) 3361 0 R (1585) 3362 0 R (1586) 3363 0 R (1588) 3365 0 R (1589) 3366 0 R (1591) 3368 0 R (1592) 3369 0 R (1593) 3370 0 R (1594) 3371 0 R (1595) 3372 0 R (1596) 3373 0 R (1597) 3374 0 R (1598) 3375 0 R (1599) 3376 0 R (16.0) 1050 0 R (16.74.101.2) 1054 0 R (16.74.101.47.3) 1058 0 R (1600) 3377 0 R (1601) 3378 0 R (1602) 3379 0 R (1603) 3380 0 R (1604) 3381 0 R (1605) 3386 0 R (1606) 3387 0 R (1607) 3388 0 R (1608) 3389 0 R (1609) 3390 0 R (1610) 3391 0 R (1611) 3392 0 R (1612) 3393 0 R (1613) 3394 0 R (1616) 3395 0 R (1617) 3396 0 R (1618) 3397 0 R (1619) 3398 0 R (1620) 3399 0 R (1621) 3400 0 R (1622) 3401 0 R (1623) 3402 0 R (1624) 3403 0 R (1625) 3404 0 R (1626) 3405 0 R (1627) 3406 0 R (1628) 3411 0 R (1629) 3412 0 R (1630) 3413 0 R (1631) 3414 0 R (1632) 3415 0 R (1633) 3416 0 R (1634) 3417 0 R (1635) 3418 0 R (1636) 3419 0 R (1637) 3420 0 R (1638) 3421 0 R (1639) 3422 0 R (164) 2026 0 R (1640) 3423 0 R (1641) 3424 0 R (1642) 3425 0 R (1643) 3426 0 R (1644) 3427 0 R (1645) 3428 0 R (1646) 3429 0 R (1647) 3434 0 R (165) 2027 0 R (1651) 3436 0 R (1652) 3437 0 R (1653) 3438 0 R (1654) 3439 0 R (1655) 3440 0 R (1656) 3441 0 R (1657) 3442 0 R (1658) 3443 0 R (1659) 3444 0 R (1660) 3445 0 R (1661) 3446 0 R (1662) 3447 0 R (1663) 3448 0 R (1664) 3449 0 R (1665) 3450 0 R (1666) 3451 0 R (1667) 3452 0 R (1668) 3453 0 R (1669) 3454 0 R (1670) 3455 0 R (1671) 3456 0 R (1674) 3457 0 R (1675) 3458 0 R (1676) 3459 0 R (1677) 3460 0 R (1678) 3461 0 R (1679) 3462 0 R (1680) 3463 0 R (1681) 3464 0 R (1682) 3465 0 R (1685) 3466 0 R (1686) 3467 0 R (1687) 3474 0 R (1688) 3475 0 R (1689) 3476 0 R (1690) 3477 0 R (1691) 3478 0 R (1692) 3479 0 R (1693) 3480 0 R (1694) 3481 0 R (1695) 3482 0 R (1698) 3483 0 R (1699) 3484 0 R (17.0) 1062 0 R (17.74.102.2) 1066 0 R (17.74.103.2) 1070 0 R (17.74.104.2) 1074 0 R (170) 2032 0 R (1700) 3485 0 R (1701) 3486 0 R (1704) 3487 0 R (1705) 3488 0 R (1706) 3489 0 R (1707) 3490 0 R (1708) 3491 0 R (1709) 3492 0 R (171) 2033 0 R (1710) 3493 0 R (1711) 3494 0 R (1712) 3495 0 R (1713) 3496 0 R (1714) 3497 0 R (1715) 3498 0 R (1716) 3499 0 R (1717) 3500 0 R (1718) 3501 0 R (1719) 3502 0 R (172) 2034 0 R (1720) 3503 0 R (1721) 3504 0 R (1722) 3505 0 R (1723) 3506 0 R (1724) 3507 0 R (1725) 3508 0 R (1726) 3509 0 R (1727) 3510 0 R (1728) 3511 0 R (1729) 3512 0 R (173) 2038 0 R (1730) 3513 0 R (1735) 3518 0 R (1736) 3519 0 R (1738) 3520 0 R (1739) 3521 0 R (1740) 3522 0 R (1741) 3523 0 R (1743) 3473 0 R (1745) 3524 0 R (1746) 3525 0 R (1747) 3526 0 R (1749) 3527 0 R (175) 2041 0 R (1750) 3528 0 R (1751) 3529 0 R (1752) 3530 0 R (1753) 3531 0 R (1754) 3532 0 R (1755) 3533 0 R (1758) 3534 0 R (1759) 3535 0 R (176) 2042 0 R (1760) 3536 0 R (1761) 3537 0 R (1762) 3538 0 R (1763) 3539 0 R (1764) 3540 0 R (1765) 3541 0 R (1766) 3542 0 R (1767) 3543 0 R (1768) 3544 0 R (177) 2043 0 R (1771) 3545 0 R (1774) 3546 0 R (1775) 3547 0 R (1776) 3548 0 R (1777) 3549 0 R (1778) 3550 0 R (1779) 3551 0 R (178) 2044 0 R (1780) 3552 0 R (1781) 3557 0 R (1782) 3558 0 R (1783) 3559 0 R (1784) 3560 0 R (1785) 3561 0 R (1786) 3562 0 R (1787) 3563 0 R (1788) 3564 0 R (1789) 3565 0 R (179) 2045 0 R (1790) 3566 0 R (1791) 3567 0 R (1792) 3568 0 R (1795) 3569 0 R (1796) 3570 0 R (1797) 3571 0 R (1798) 3572 0 R (1799) 3573 0 R (18.0) 1078 0 R (18.74.105.2) 1082 0 R (18.74.106.2) 1086 0 R (18.74.107.2) 1090 0 R (18.74.108.2) 1094 0 R (180) 2046 0 R (1802) 3574 0 R (1803) 3575 0 R (1804) 3576 0 R (1805) 3577 0 R (1806) 3578 0 R (1809) 3579 0 R (181) 2047 0 R (1813) 3582 0 R (1814) 3583 0 R (1817) 3585 0 R (1820) 3587 0 R (1821) 3588 0 R (1822) 3589 0 R (1825) 3595 0 R (1826) 3596 0 R (1827) 3597 0 R (1828) 3598 0 R (1829) 3599 0 R (1830) 3600 0 R (1831) 3601 0 R (1832) 3602 0 R (1833) 3603 0 R (1834) 3604 0 R (1835) 3605 0 R (1836) 3606 0 R (1837) 3607 0 R (1838) 3608 0 R (1839) 3609 0 R (184) 2049 0 R (1840) 3610 0 R (1841) 3611 0 R (1842) 3612 0 R (1843) 3613 0 R (1844) 3614 0 R (1845) 3615 0 R (1846) 3616 0 R (1847) 3617 0 R (1848) 3618 0 R (1849) 3619 0 R (1850) 3620 0 R (1851) 3621 0 R (1852) 3622 0 R (1853) 3623 0 R (1854) 3624 0 R (1855) 3625 0 R (1858) 3627 0 R (1859) 3628 0 R (1860) 3629 0 R (1863) 3631 0 R (1864) 3632 0 R (1867) 3634 0 R (1868) 3635 0 R (1869) 3636 0 R (187) 2051 0 R (1870) 3637 0 R (1871) 3638 0 R (1872) 3639 0 R (1875) 3645 0 R (1876) 3646 0 R (1879) 3648 0 R (1880) 3649 0 R (1881) 3650 0 R (1882) 3651 0 R (1885) 3653 0 R (1888) 3655 0 R (1889) 3656 0 R (1890) 3657 0 R (1891) 3658 0 R (1894) 3660 0 R (1895) 3661 0 R (1898) 3662 0 R (1899) 3663 0 R (19.0) 1098 0 R (19.74.109.2) 1102 0 R (19.74.110.2) 1106 0 R (190) 2053 0 R (1900) 3664 0 R (1901) 3665 0 R (1902) 3666 0 R (1903) 3667 0 R (1904) 3668 0 R (1905) 3669 0 R (1908) 3674 0 R (1909) 3675 0 R (1911) 3677 0 R (1914) 3678 0 R (1915) 3679 0 R (1916) 3680 0 R (1917) 3681 0 R (1918) 3682 0 R (1921) 3683 0 R (1922) 3684 0 R (1923) 3685 0 R (1924) 3686 0 R (1925) 3687 0 R (1926) 3688 0 R (1927) 3689 0 R (1928) 3690 0 R (1929) 3691 0 R (193) 2055 0 R (1930) 3692 0 R (1931) 3693 0 R (1932) 3694 0 R (1933) 3695 0 R (1934) 3696 0 R (1942) 3700 0 R (1943) 3701 0 R (1944) 3702 0 R (1945) 3708 0 R (1946) 3709 0 R (1947) 3710 0 R (1949) 3712 0 R (1950) 3713 0 R (1951) 3714 0 R (1952) 3715 0 R (1953) 3716 0 R (1954) 3717 0 R (1957) 3718 0 R (196) 2057 0 R (1961) 3720 0 R (1962) 3721 0 R (1965) 3722 0 R (1966) 3723 0 R (1969) 3724 0 R (1970) 3725 0 R (1971) 3726 0 R (1974) 3727 0 R (1975) 3728 0 R (1976) 3729 0 R (1977) 3734 0 R (1978) 3735 0 R (1979) 3736 0 R (1980) 3737 0 R (1983) 3738 0 R (1984) 3739 0 R (1987) 3740 0 R (1988) 3741 0 R (1989) 3742 0 R (199) 2059 0 R (1990) 3743 0 R (1991) 3744 0 R (1992) 3745 0 R (1993) 3746 0 R (1994) 3747 0 R (1995) 3748 0 R (1996) 3749 0 R (1997) 3750 0 R (1998) 3751 0 R (1999) 3752 0 R (2.0) 6 0 R (20.0) 1110 0 R (20.74.111.2) 1114 0 R (2000) 3753 0 R (2001) 3754 0 R (2002) 3755 0 R (2005) 3756 0 R (2006) 3761 0 R (2007) 3762 0 R (2008) 3763 0 R (2009) 3764 0 R (2010) 3765 0 R (2013) 3766 0 R (2014) 3767 0 R (2015) 3768 0 R (2016) 3769 0 R (2017) 3770 0 R (2018) 3771 0 R (2019) 3772 0 R (2021) 3775 0 R (2022) 3776 0 R (2024) 3778 0 R (2025) 3779 0 R (2027) 3781 0 R (2028) 3782 0 R (203) 2060 0 R (2030) 3784 0 R (2031) 3785 0 R (2032) 3786 0 R (2035) 3791 0 R (2036) 3792 0 R (2037) 3793 0 R (2038) 3794 0 R (2039) 3795 0 R (204) 2061 0 R (2040) 3796 0 R (2041) 3797 0 R (2042) 3798 0 R (2043) 3799 0 R (2044) 3800 0 R (2045) 3801 0 R (2046) 3802 0 R (2047) 3803 0 R (2048) 3804 0 R (2049) 3805 0 R (205) 2062 0 R (2050) 3806 0 R (2051) 3807 0 R (2052) 3808 0 R (2056) 3810 0 R (2057) 3811 0 R (2058) 3812 0 R (2059) 3813 0 R (206) 2063 0 R (2060) 3814 0 R (2062) 3820 0 R (2063) 3821 0 R (2065) 3822 0 R (2066) 3823 0 R (2067) 3824 0 R (2068) 3825 0 R (2070) 3826 0 R (2071) 3827 0 R (2072) 3828 0 R (2073) 3829 0 R (2075) 3830 0 R (2076) 3831 0 R (2077) 3832 0 R (2078) 3833 0 R (2080) 3834 0 R (2081) 3835 0 R (2082) 3836 0 R (2083) 3837 0 R (2085) 3838 0 R (2086) 3839 0 R (2087) 3840 0 R (2088) 3841 0 R (209) 2067 0 R (2091) 3843 0 R (2092) 3844 0 R (2093) 3845 0 R (2094) 3846 0 R (2098) 3848 0 R (2099) 3849 0 R (21.0) 1118 0 R (21.74.112.2) 1122 0 R (2100) 3850 0 R (2101) 3851 0 R (2103) 3853 0 R (2104) 3854 0 R (2106) 3856 0 R (2107) 3857 0 R (2109) 1650 0 R (2111) 3863 0 R (2115) 3865 0 R (2116) 3866 0 R (2117) 3867 0 R (2118) 3868 0 R (2119) 3869 0 R (212) 2068 0 R (2120) 3870 0 R (2121) 3871 0 R (2122) 3872 0 R (2125) 3878 0 R (2126) 3879 0 R (2127) 3880 0 R (213) 2069 0 R (2132) 3881 0 R (2135) 3882 0 R (2137) 3884 0 R (2138) 3885 0 R (2139) 3886 0 R (214) 2070 0 R (2140) 3887 0 R (2142) 3889 0 R (2143) 3890 0 R (2144) 3891 0 R (2145) 3892 0 R (2146) 3893 0 R (2147) 3894 0 R (2148) 3895 0 R (2149) 3896 0 R (215) 2071 0 R (2150) 3897 0 R (2151) 3898 0 R (2152) 3899 0 R (2156) 3900 0 R (2157) 3901 0 R (216) 2072 0 R (2162) 3908 0 R (2163) 3909 0 R (2164) 3910 0 R (2165) 3911 0 R (2169) 3914 0 R (217) 2073 0 R (2170) 3915 0 R (2171) 3916 0 R (2172) 3917 0 R (2173) 3918 0 R (2174) 3919 0 R (2176) 3920 0 R (2177) 3921 0 R (2178) 3922 0 R (2179) 3923 0 R (218) 2074 0 R (2180) 3924 0 R (2181) 3925 0 R (2182) 3926 0 R (2183) 3927 0 R (2184) 3928 0 R (2185) 3929 0 R (2186) 3930 0 R (2187) 3931 0 R (2188) 3932 0 R (2189) 3933 0 R (219) 2075 0 R (2190) 3934 0 R (2191) 3935 0 R (2192) 3936 0 R (2193) 3937 0 R (2194) 3938 0 R (2195) 3939 0 R (2196) 3940 0 R (2198) 3941 0 R (2199) 3942 0 R (22.0) 1126 0 R (22.74.113.2) 1130 0 R (22.74.114.2) 1134 0 R (220) 2076 0 R (2200) 3943 0 R (2201) 3944 0 R (2202) 3945 0 R (2203) 3946 0 R (2204) 3947 0 R (2205) 3948 0 R (2206) 3949 0 R (2208) 3950 0 R (2209) 3951 0 R (2210) 3952 0 R (2211) 3953 0 R (2212) 3954 0 R (2213) 3955 0 R (2214) 3956 0 R (2215) 3957 0 R (2216) 3958 0 R (2217) 3959 0 R (2218) 3960 0 R (2219) 3961 0 R (2220) 3962 0 R (2221) 3963 0 R (2222) 3964 0 R (2223) 3965 0 R (2224) 3966 0 R (2225) 3967 0 R (2226) 3968 0 R (2227) 3969 0 R (2228) 3970 0 R (2229) 3971 0 R (223) 2077 0 R (2230) 3972 0 R (2231) 3973 0 R (2232) 3974 0 R (2233) 3975 0 R (2234) 3981 0 R (2235) 3982 0 R (2236) 3983 0 R (2237) 3984 0 R (2238) 3985 0 R (224) 2078 0 R (2244) 3987 0 R (2245) 3988 0 R (2246) 3989 0 R (2247) 3990 0 R (2248) 3991 0 R (2249) 3992 0 R (225) 2079 0 R (2254) 3997 0 R (2255) 3998 0 R (2256) 3999 0 R (2257) 4000 0 R (226) 2080 0 R (2260) 4001 0 R (2261) 4002 0 R (2262) 4003 0 R (2263) 4004 0 R (2264) 4005 0 R (2265) 4006 0 R (2266) 4007 0 R (2267) 4008 0 R (2268) 4009 0 R (2269) 4010 0 R (227) 2081 0 R (2270) 4011 0 R (2271) 4012 0 R (2272) 4013 0 R (2273) 4014 0 R (2274) 4015 0 R (2275) 4016 0 R (2276) 4017 0 R (2277) 4018 0 R (2278) 4019 0 R (2279) 4020 0 R (2280) 4025 0 R (2281) 4026 0 R (2282) 4027 0 R (2283) 4028 0 R (2286) 4029 0 R (2287) 4030 0 R (2288) 4031 0 R (2289) 4032 0 R (2290) 4033 0 R (2291) 4034 0 R (2292) 4035 0 R (23.0) 1138 0 R (23.74.115.2) 1142 0 R (23.74.116.2) 1146 0 R (23.74.117.2) 1150 0 R (230) 2082 0 R (231) 2083 0 R (2313) 4037 0 R (2314) 4038 0 R (2315) 4039 0 R (2316) 4040 0 R (2317) 4041 0 R (2318) 4042 0 R (2319) 4043 0 R (232) 2084 0 R (2320) 4044 0 R (2321) 4045 0 R (2322) 4046 0 R (2323) 4047 0 R (2324) 4048 0 R (2325) 4049 0 R (2326) 4050 0 R (2327) 4051 0 R (2328) 4052 0 R (2329) 4053 0 R (233) 2085 0 R (2330) 4054 0 R (2331) 4055 0 R (2332) 4056 0 R (2333) 4057 0 R (2334) 4058 0 R (2335) 4059 0 R (2336) 4060 0 R (2337) 4061 0 R (2338) 4062 0 R (2339) 4063 0 R (234) 2090 0 R (2340) 4064 0 R (2341) 4065 0 R (2342) 4066 0 R (2343) 4067 0 R (2344) 4068 0 R (2345) 4069 0 R (2346) 4070 0 R (2347) 4076 0 R (2348) 4077 0 R (2349) 4078 0 R (2350) 4079 0 R (2351) 4080 0 R (2352) 4081 0 R (2353) 4082 0 R (2354) 4083 0 R (2355) 4084 0 R (2356) 4085 0 R (2357) 4086 0 R (2358) 4087 0 R (2359) 4088 0 R (237) 2091 0 R (238) 2092 0 R (2380) 4090 0 R (2381) 4091 0 R (2382) 4092 0 R (2383) 4093 0 R (2384) 4094 0 R (2385) 4095 0 R (2386) 4096 0 R (2387) 4097 0 R (2388) 4098 0 R (2389) 4099 0 R (2390) 4100 0 R (2391) 4101 0 R (2394) 4102 0 R (2397) 4106 0 R (2398) 4107 0 R (24) 1953 0 R (24.0) 1154 0 R (24.74.118.2) 1158 0 R (240) 2094 0 R (2401) 4113 0 R (2406) 4114 0 R (2407) 4115 0 R (2408) 4116 0 R (2409) 4117 0 R (241) 2095 0 R (2413) 4123 0 R (2414) 4124 0 R (2415) 4125 0 R (2416) 4126 0 R (2417) 4127 0 R (2418) 4128 0 R (2419) 4129 0 R (242) 2096 0 R (2420) 4130 0 R (2421) 4131 0 R (2422) 4132 0 R (2423) 4133 0 R (2424) 4134 0 R (2425) 4135 0 R (2426) 4136 0 R (2427) 4137 0 R (2428) 4138 0 R (2429) 4139 0 R (2432) 4140 0 R (2433) 4141 0 R (2436) 4142 0 R (2437) 4143 0 R (2438) 4144 0 R (2439) 4145 0 R (2440) 4146 0 R (2441) 4147 0 R (2442) 4148 0 R (2443) 4149 0 R (2444) 4150 0 R (2445) 4155 0 R (2446) 4156 0 R (2447) 4157 0 R (2448) 4158 0 R (245) 2097 0 R (2451) 4159 0 R (2452) 4160 0 R (2453) 4161 0 R (2454) 4162 0 R (2455) 4163 0 R (2456) 4164 0 R (2459) 4165 0 R (246) 2098 0 R (2460) 4166 0 R (2461) 4167 0 R (2462) 4168 0 R (2463) 4169 0 R (2466) 4170 0 R (2469) 4171 0 R (247) 2099 0 R (2470) 4172 0 R (2471) 4173 0 R (248) 2100 0 R (249) 2101 0 R (2492) 4180 0 R (2495) 4181 0 R (2496) 4182 0 R (2498) 4184 0 R (2499) 4185 0 R (25) 1954 0 R (25.0) 1162 0 R (25.74.119.2) 1166 0 R (25.74.120.2) 1170 0 R (250) 2102 0 R (2500) 4186 0 R (2501) 4187 0 R (2506) 4188 0 R (2507) 4189 0 R (2508) 4190 0 R (2509) 4191 0 R (251) 2103 0 R (2510) 4192 0 R (2511) 4193 0 R (2512) 4194 0 R (2513) 4195 0 R (2514) 4196 0 R (2515) 4178 0 R (2517) 4201 0 R (2518) 4202 0 R (2519) 4203 0 R (252) 2104 0 R (2520) 4204 0 R (2521) 4205 0 R (2522) 4206 0 R (2523) 4207 0 R (2524) 4208 0 R (2525) 4209 0 R (2526) 4210 0 R (2527) 4211 0 R (2528) 4212 0 R (2529) 4213 0 R (253) 2105 0 R (2530) 4214 0 R (2531) 4215 0 R (2532) 4216 0 R (2533) 4217 0 R (2534) 4218 0 R (2535) 4219 0 R (2538) 4220 0 R (2539) 4221 0 R (254) 2106 0 R (2540) 4222 0 R (2543) 4227 0 R (2544) 4228 0 R (2545) 4229 0 R (2546) 4230 0 R (2547) 4231 0 R (2548) 4232 0 R (2549) 4233 0 R (255) 2107 0 R (2550) 4234 0 R (2551) 4235 0 R (2552) 4236 0 R (2555) 4237 0 R (2556) 4238 0 R (2557) 4239 0 R (2567) 4241 0 R (2570) 4248 0 R (2573) 4249 0 R (2576) 4250 0 R (2579) 4251 0 R (258) 2108 0 R (2582) 4252 0 R (2583) 4253 0 R (2586) 4254 0 R (2589) 4255 0 R (259) 2109 0 R (2590) 1788 0 R (2592) 4261 0 R (2593) 4262 0 R (2594) 4247 0 R (26) 1955 0 R (26.0) 1174 0 R (26.74.121.2) 1178 0 R (26.74.122.2) 1182 0 R (2603) 4264 0 R (2606) 4265 0 R (2607) 4266 0 R (261) 2111 0 R (2610) 4267 0 R (2613) 4268 0 R (2614) 4269 0 R (2615) 4270 0 R (2618) 4275 0 R (2619) 4276 0 R (262) 2112 0 R (2620) 4260 0 R (2622) 4277 0 R (2623) 4278 0 R (2624) 4279 0 R (2627) 4280 0 R (263) 2113 0 R (2630) 4281 0 R (2631) 4282 0 R (2632) 4283 0 R (2633) 4284 0 R (2634) 4285 0 R (2635) 4286 0 R (2636) 4287 0 R (2637) 4288 0 R (2638) 4289 0 R (2639) 4290 0 R (264) 2114 0 R (2640) 4291 0 R (2641) 4292 0 R (2642) 4293 0 R (2643) 4294 0 R (2644) 4295 0 R (2645) 4296 0 R (2646) 4297 0 R (2647) 4298 0 R (2648) 4299 0 R (2649) 4300 0 R (265) 2115 0 R (2650) 4301 0 R (2651) 4302 0 R (2654) 4303 0 R (2655) 4304 0 R (2656) 4305 0 R (2657) 4306 0 R (2658) 4307 0 R (2659) 4312 0 R (266) 2116 0 R (2660) 4313 0 R (2661) 4314 0 R (2662) 4315 0 R (2663) 4316 0 R (2664) 4317 0 R (2665) 4318 0 R (2666) 4319 0 R (2667) 4320 0 R (2668) 4321 0 R (2669) 4322 0 R (267) 2121 0 R (2670) 4323 0 R (2671) 4324 0 R (2672) 4325 0 R (2673) 4326 0 R (2674) 4327 0 R (2675) 4328 0 R (2676) 4329 0 R (2677) 4330 0 R (2678) 4331 0 R (2679) 4332 0 R (268) 2122 0 R (2680) 4333 0 R (2681) 4334 0 R (2682) 4335 0 R (2683) 4336 0 R (2684) 4337 0 R (2685) 4338 0 R (2686) 4339 0 R (2687) 4340 0 R (2688) 4341 0 R (2689) 4342 0 R (269) 2123 0 R (2690) 4343 0 R (2691) 4344 0 R (2692) 4345 0 R (2693) 4346 0 R (2694) 4347 0 R (2695) 4353 0 R (2696) 4354 0 R (2697) 4355 0 R (2698) 4356 0 R (27.0) 1186 0 R (27.74.123.2) 1190 0 R (27.74.124.2) 1194 0 R (270) 2124 0 R (2701) 4357 0 R (2705) 4359 0 R (2706) 4360 0 R (2707) 4361 0 R (2710) 4362 0 R (2711) 4363 0 R (2712) 4364 0 R (2713) 4365 0 R (2715) 4366 0 R (2716) 4367 0 R (2717) 4368 0 R (2719) 4369 0 R (272) 2126 0 R (2720) 4370 0 R (2721) 4371 0 R (2723) 4372 0 R (2724) 4373 0 R (2725) 4374 0 R (2727) 4375 0 R (2728) 4376 0 R (2729) 4377 0 R (2731) 4378 0 R (2732) 4379 0 R (2733) 4380 0 R (2735) 4381 0 R (2736) 4382 0 R (2737) 4383 0 R (2739) 4384 0 R (274) 2129 0 R (2740) 4385 0 R (2741) 4386 0 R (2743) 4391 0 R (2744) 4392 0 R (2745) 4393 0 R (2747) 4352 0 R (2748) 4394 0 R (2749) 4395 0 R (275) 2130 0 R (2751) 4396 0 R (2752) 4397 0 R (2753) 4398 0 R (2755) 4399 0 R (2756) 4400 0 R (2757) 4401 0 R (2759) 4402 0 R (276) 2131 0 R (2760) 4403 0 R (2761) 4404 0 R (2763) 4405 0 R (2764) 4406 0 R (2765) 4407 0 R (2766) 4408 0 R (2767) 4409 0 R (277) 2132 0 R (2771) 4411 0 R (2774) 4412 0 R (2775) 4413 0 R (2776) 4414 0 R (2777) 4415 0 R (2778) 4416 0 R (278) 2133 0 R (2781) 4422 0 R (2782) 4423 0 R (2783) 4424 0 R (2784) 4425 0 R (2785) 4426 0 R (2786) 4427 0 R (2787) 1802 0 R (2789) 4428 0 R (279) 2134 0 R (2790) 4429 0 R (2791) 4430 0 R (2792) 4431 0 R (2793) 4432 0 R (2796) 4433 0 R (2797) 4439 0 R (28) 1957 0 R (28.0) 1198 0 R (28.74.125.2) 1202 0 R (280) 2135 0 R (2800) 4440 0 R (2801) 4441 0 R (2802) 4442 0 R (2803) 4443 0 R (2804) 4444 0 R (2805) 4445 0 R (2806) 4446 0 R (2807) 4447 0 R (281) 2136 0 R (2810) 4448 0 R (2811) 4449 0 R (2812) 4454 0 R (2813) 4455 0 R (2814) 4438 0 R (2815) 4456 0 R (2817) 4458 0 R (2818) 4459 0 R (282) 2137 0 R (2822) 4461 0 R (2823) 4462 0 R (2824) 4463 0 R (2827) 4464 0 R (2828) 4465 0 R (2829) 4466 0 R (283) 2138 0 R (2830) 4467 0 R (2831) 4472 0 R (2832) 4473 0 R (2833) 4474 0 R (2834) 4475 0 R (2837) 4476 0 R (2838) 4477 0 R (2839) 4478 0 R (284) 2139 0 R (2841) 4480 0 R (2842) 4481 0 R (2843) 4482 0 R (2844) 4483 0 R (2845) 4484 0 R (2846) 4485 0 R (2847) 1900 0 R (2849) 4490 0 R (285) 2140 0 R (2850) 4491 0 R (2851) 4492 0 R (2856) 4497 0 R (2857) 4498 0 R (2858) 4499 0 R (286) 2141 0 R (2861) 4500 0 R (2862) 4501 0 R (2863) 4502 0 R (2864) 4503 0 R (2865) 4504 0 R (2866) 4505 0 R (2867) 4506 0 R (2868) 4507 0 R (2869) 4508 0 R (287) 2142 0 R (2870) 4509 0 R (2871) 4510 0 R (2874) 4511 0 R (2875) 4512 0 R (2879) 4514 0 R (288) 2143 0 R (2880) 4515 0 R (2881) 4516 0 R (2882) 4517 0 R (2883) 4518 0 R (2884) 4519 0 R (2885) 4520 0 R (2886) 4526 0 R (2887) 4527 0 R (2888) 4528 0 R (289) 2144 0 R (2891) 4529 0 R (2892) 4530 0 R (2893) 4531 0 R (2894) 4532 0 R (2895) 4533 0 R (2896) 4534 0 R (2897) 4535 0 R (2898) 4536 0 R (2899) 4537 0 R (290) 2145 0 R (2900) 4538 0 R (2901) 4539 0 R (2902) 4540 0 R (2903) 4541 0 R (2904) 4542 0 R (2905) 4543 0 R (2906) 4544 0 R (2907) 4545 0 R (2908) 4546 0 R (2909) 4547 0 R (291) 2146 0 R (2910) 4548 0 R (2911) 4549 0 R (2912) 4550 0 R (2913) 4551 0 R (2914) 4552 0 R (2917) 4557 0 R (2918) 4558 0 R (2919) 4559 0 R (292) 2147 0 R (2920) 4560 0 R (2921) 4561 0 R (2922) 4562 0 R (2923) 4563 0 R (2924) 4564 0 R (2925) 4565 0 R (2926) 4566 0 R (2929) 4567 0 R (293) 2148 0 R (2930) 4568 0 R (2931) 4569 0 R (2932) 4570 0 R (2933) 4571 0 R (2934) 4572 0 R (2935) 4573 0 R (2936) 4574 0 R (2937) 4575 0 R (2938) 4576 0 R (2939) 4577 0 R (294) 2149 0 R (2940) 4578 0 R (2941) 4579 0 R (2942) 4580 0 R (2943) 4585 0 R (2944) 4586 0 R (2945) 4587 0 R (2948) 4588 0 R (2949) 4589 0 R (295) 2150 0 R (2950) 4590 0 R (2951) 4591 0 R (2952) 4592 0 R (2953) 4593 0 R (2954) 4594 0 R (2955) 4595 0 R (2956) 4596 0 R (2957) 4597 0 R (2958) 4598 0 R (2959) 4599 0 R (296) 2151 0 R (2960) 4600 0 R (2961) 4601 0 R (2962) 4602 0 R (2963) 4603 0 R (2964) 4604 0 R (2965) 4605 0 R (2966) 4606 0 R (2967) 4607 0 R (2968) 4608 0 R (2969) 4609 0 R (297) 2152 0 R (2970) 4610 0 R (2971) 4611 0 R (2972) 4612 0 R (2973) 4613 0 R (2974) 4614 0 R (2975) 4615 0 R (2976) 4616 0 R (2977) 4621 0 R (2978) 4622 0 R (2979) 4623 0 R (298) 2153 0 R (2980) 4624 0 R (2981) 4625 0 R (2982) 4626 0 R (2983) 4627 0 R (2984) 4628 0 R (2985) 4629 0 R (2986) 4630 0 R (2987) 4631 0 R (2988) 4632 0 R (2989) 4633 0 R (299) 2154 0 R (2990) 4634 0 R (2993) 4635 0 R (2994) 4636 0 R (2997) 4637 0 R (2998) 4638 0 R (2999) 4639 0 R (3.0) 10 0 R (300) 2155 0 R (3000) 4640 0 R (3001) 4641 0 R (3002) 4646 0 R (3003) 4647 0 R (3004) 4648 0 R (3005) 4649 0 R (3006) 4650 0 R (3007) 4651 0 R (3008) 4652 0 R (3009) 4653 0 R (301) 2156 0 R (3010) 4654 0 R (3011) 4655 0 R (3012) 4656 0 R (3013) 4657 0 R (3014) 4658 0 R (3015) 4659 0 R (3016) 4664 0 R (3017) 4665 0 R (3018) 4666 0 R (3019) 4667 0 R (302) 2157 0 R (3022) 4668 0 R (3023) 4669 0 R (3026) 4675 0 R (3029) 4676 0 R (303) 2158 0 R (3030) 4677 0 R (3031) 4678 0 R (3032) 4679 0 R (3035) 4682 0 R (3036) 4683 0 R (3037) 4684 0 R (3038) 4685 0 R (3039) 4686 0 R (304) 2159 0 R (3042) 4687 0 R (3043) 4688 0 R (3044) 4689 0 R (3045) 4690 0 R (3046) 4691 0 R (305) 2160 0 R (3050) 4692 0 R (3051) 4693 0 R (3052) 4694 0 R (3053) 4695 0 R (3054) 4696 0 R (3055) 4697 0 R (3056) 4702 0 R (3059) 4703 0 R (306) 2161 0 R (3060) 4704 0 R (3061) 4705 0 R (3062) 4706 0 R (3063) 4707 0 R (3064) 4708 0 R (3065) 4709 0 R (3066) 4710 0 R (3069) 4711 0 R (307) 2162 0 R (3070) 4712 0 R (3071) 4713 0 R (3072) 4714 0 R (3073) 4715 0 R (3074) 4716 0 R (3075) 4717 0 R (3076) 4718 0 R (3077) 4719 0 R (3078) 4720 0 R (308) 2163 0 R (3081) 4721 0 R (3082) 4722 0 R (3083) 4727 0 R (3084) 4728 0 R (3085) 4729 0 R (3086) 4730 0 R (3089) 4732 0 R (309) 2164 0 R (3090) 4733 0 R (3091) 4734 0 R (3092) 4735 0 R (3093) 4736 0 R (3096) 4738 0 R (3097) 4739 0 R (3098) 4740 0 R (3099) 4741 0 R (31) 1958 0 R (310) 2165 0 R (3100) 4742 0 R (3101) 4743 0 R (3102) 4744 0 R (3106) 4745 0 R (3107) 4746 0 R (3108) 4747 0 R (3109) 4748 0 R (311) 2166 0 R (3113) 4754 0 R (3114) 4755 0 R (3115) 4756 0 R (3116) 4757 0 R (3117) 4758 0 R (312) 2167 0 R (3120) 4763 0 R (3121) 4764 0 R (3124) 4765 0 R (3125) 4766 0 R (3126) 4767 0 R (3127) 4768 0 R (3128) 4769 0 R (3129) 4770 0 R (313) 2168 0 R (3130) 4771 0 R (3131) 4772 0 R (3132) 4773 0 R (3133) 4774 0 R (3134) 4775 0 R (3135) 4776 0 R (3136) 4777 0 R (3137) 4778 0 R (3138) 4779 0 R (3139) 4780 0 R (314) 2169 0 R (3140) 4781 0 R (3141) 4782 0 R (3142) 4783 0 R (3143) 4784 0 R (3144) 4785 0 R (3145) 4786 0 R (3146) 4787 0 R (3147) 4788 0 R (3148) 4789 0 R (3149) 4790 0 R (315) 2170 0 R (3150) 4791 0 R (3151) 4792 0 R (3152) 4793 0 R (3155) 4794 0 R (3156) 4795 0 R (3157) 4796 0 R (3158) 4797 0 R (3159) 4798 0 R (316) 2171 0 R (3160) 4799 0 R (3161) 4800 0 R (3162) 4801 0 R (3163) 4802 0 R (3168) 4807 0 R (3169) 4808 0 R (317) 2175 0 R (3170) 4809 0 R (3171) 4810 0 R (3172) 4811 0 R (3173) 4812 0 R (3174) 4813 0 R (3175) 4814 0 R (3176) 4815 0 R (3177) 4816 0 R (3178) 4817 0 R (3179) 4818 0 R (318) 2176 0 R (3180) 4819 0 R (3181) 4820 0 R (3185) 4822 0 R (3186) 4823 0 R (3187) 4824 0 R (3188) 4825 0 R (3189) 4826 0 R (319) 2177 0 R (3190) 4827 0 R (3191) 4828 0 R (3192) 4829 0 R (3193) 4830 0 R (3194) 4831 0 R (3195) 4832 0 R (3196) 4838 0 R (3197) 4839 0 R (3198) 4840 0 R (3199) 4841 0 R (32) 1959 0 R (320) 2178 0 R (3200) 4842 0 R (3201) 4843 0 R (3202) 4844 0 R (3203) 4845 0 R (3204) 4846 0 R (3205) 4847 0 R (3206) 4848 0 R (3207) 4849 0 R (3208) 4850 0 R (3209) 4851 0 R (321) 2179 0 R (3210) 4852 0 R (3211) 4853 0 R (3212) 4854 0 R (3213) 4855 0 R (3214) 4856 0 R (3215) 4857 0 R (3216) 4858 0 R (3217) 4859 0 R (3218) 4860 0 R (3219) 4861 0 R (322) 2180 0 R (3220) 4862 0 R (3221) 4863 0 R (3222) 4868 0 R (3223) 4869 0 R (3224) 4870 0 R (3225) 4871 0 R (3226) 4872 0 R (3227) 4873 0 R (3228) 4874 0 R (323) 2181 0 R (3231) 4875 0 R (3232) 4876 0 R (3233) 4877 0 R (3234) 4878 0 R (3235) 4879 0 R (3236) 4880 0 R (3237) 4881 0 R (3238) 4882 0 R (3239) 4883 0 R (324) 2182 0 R (3240) 4884 0 R (3241) 4885 0 R (3242) 4886 0 R (3243) 4887 0 R (3244) 4888 0 R (3245) 4889 0 R (3246) 4890 0 R (3247) 4891 0 R (3248) 4892 0 R (3249) 4893 0 R (325) 2183 0 R (3250) 4894 0 R (3253) 4903 0 R (3254) 4904 0 R (3255) 4905 0 R (3258) 4906 0 R (3259) 4907 0 R (326) 2184 0 R (3260) 4908 0 R (3263) 4909 0 R (3264) 4910 0 R (3265) 4911 0 R (3266) 4912 0 R (3267) 4913 0 R (3268) 4914 0 R (3269) 4919 0 R (327) 2185 0 R (3270) 4920 0 R (3273) 4921 0 R (3274) 4922 0 R (3277) 4923 0 R (3278) 4924 0 R (3279) 4925 0 R (328) 2186 0 R (3280) 4931 0 R (3283) 4932 0 R (3284) 4933 0 R (3285) 4934 0 R (3286) 4935 0 R (3287) 4936 0 R (3288) 4937 0 R (3289) 4938 0 R (329) 2187 0 R (3290) 4939 0 R (3291) 4940 0 R (3292) 4941 0 R (3293) 4942 0 R (3294) 4943 0 R (3295) 4944 0 R (3296) 4945 0 R (3297) 4946 0 R (3298) 4947 0 R (3299) 4948 0 R (33) 1960 0 R (330) 2188 0 R (3300) 4949 0 R (3301) 4950 0 R (3302) 4951 0 R (3303) 4952 0 R (3304) 4953 0 R (3305) 4954 0 R (3306) 4955 0 R (3307) 4956 0 R (3308) 4957 0 R (3309) 4958 0 R (331) 2189 0 R (3310) 4959 0 R (3311) 4960 0 R (3312) 4961 0 R (3313) 4962 0 R (3314) 4930 0 R (3315) 4968 0 R (3316) 4969 0 R (3319) 4970 0 R (332) 2190 0 R (3320) 4971 0 R (3321) 4972 0 R (3324) 4973 0 R (3325) 4974 0 R (3328) 4975 0 R (3329) 4980 0 R (333) 2191 0 R (3332) 4981 0 R (3335) 4982 0 R (3338) 4983 0 R (3339) 4984 0 R (334) 2192 0 R (3340) 4985 0 R (3343) 4986 0 R (3344) 4987 0 R (3345) 4988 0 R (3346) 4993 0 R (3347) 4994 0 R (3349) 4999 0 R (335) 2193 0 R (3353) 5000 0 R (3354) 5001 0 R (3355) 5002 0 R (3356) 5003 0 R (336) 2194 0 R (3361) 5006 0 R (3362) 5007 0 R (3363) 5008 0 R (3364) 5009 0 R (3365) 5010 0 R (3366) 5011 0 R (3368) 5012 0 R (3369) 5013 0 R (337) 2195 0 R (3370) 5014 0 R (3371) 5015 0 R (3372) 5016 0 R (3374) 5017 0 R (3375) 5018 0 R (3376) 5019 0 R (3377) 5020 0 R (3378) 5021 0 R (3379) 5022 0 R (338) 2196 0 R (3380) 5023 0 R (3381) 5024 0 R (3382) 5025 0 R (3384) 5026 0 R (3385) 5027 0 R (3386) 5028 0 R (3387) 5029 0 R (3388) 5030 0 R (3389) 5031 0 R (339) 2197 0 R (3390) 5032 0 R (3391) 5033 0 R (3392) 5034 0 R (3393) 5035 0 R (3394) 5036 0 R (3396) 5037 0 R (3397) 5038 0 R (3398) 5039 0 R (3399) 5040 0 R (340) 2198 0 R (3400) 5041 0 R (3401) 5042 0 R (3406) 5049 0 R (3407) 5050 0 R (3408) 5051 0 R (3409) 5052 0 R (341) 2199 0 R (3410) 5053 0 R (3411) 5054 0 R (3413) 5055 0 R (3414) 5056 0 R (3415) 5057 0 R (3418) 5058 0 R (3419) 5059 0 R (342) 2200 0 R (3425) 5061 0 R (3426) 5062 0 R (3427) 5063 0 R (3428) 5064 0 R (343) 2201 0 R (3431) 5066 0 R (3432) 5067 0 R (3436) 5068 0 R (3437) 5069 0 R (3438) 5070 0 R (3439) 5071 0 R (344) 2202 0 R (3440) 5072 0 R (3443) 5073 0 R (3444) 5074 0 R (3445) 5075 0 R (3446) 5076 0 R (3447) 5082 0 R (3448) 5083 0 R (3449) 5084 0 R (345) 2203 0 R (3454) 5086 0 R (3455) 5087 0 R (3456) 5088 0 R (3457) 5089 0 R (346) 2204 0 R (3460) 5091 0 R (3461) 5092 0 R (3466) 5095 0 R (3467) 5096 0 R (3468) 5097 0 R (3469) 5098 0 R (347) 2205 0 R (3470) 5099 0 R (3475) 5102 0 R (3476) 5103 0 R (348) 2206 0 R (3482) 5110 0 R (3483) 5111 0 R (3484) 5112 0 R (3485) 5113 0 R (3488) 5115 0 R (3489) 5116 0 R (349) 2207 0 R (3491) 5118 0 R (3492) 5119 0 R (3494) 5120 0 R (3495) 5121 0 R (3496) 5122 0 R (3497) 5123 0 R (3499) 5124 0 R (350) 2208 0 R (3500) 5125 0 R (3501) 5126 0 R (3502) 5127 0 R (3503) 5128 0 R (3505) 5129 0 R (3506) 5130 0 R (3507) 5131 0 R (3508) 5132 0 R (351) 2209 0 R (3514) 5134 0 R (3515) 5135 0 R (3516) 5136 0 R (3519) 5137 0 R (352) 2210 0 R (3520) 5138 0 R (3522) 5144 0 R (3523) 5145 0 R (3524) 5146 0 R (3525) 5147 0 R (3529) 5149 0 R (353) 2211 0 R (3530) 5150 0 R (3531) 5151 0 R (3532) 5152 0 R (3533) 5153 0 R (3534) 5154 0 R (3535) 5155 0 R (3536) 5156 0 R (354) 2212 0 R (3542) 5158 0 R (3543) 5159 0 R (3547) 5161 0 R (3548) 5162 0 R (3549) 5163 0 R (355) 2213 0 R (3554) 5165 0 R (3555) 5166 0 R (3556) 5167 0 R (3558) 5172 0 R (3559) 5173 0 R (356) 2214 0 R (3560) 5174 0 R (3561) 5175 0 R (3562) 5176 0 R (3563) 5177 0 R (3564) 5178 0 R (3565) 5179 0 R (3566) 5180 0 R (3567) 5181 0 R (3568) 5182 0 R (3569) 5183 0 R (357) 2215 0 R (3570) 5184 0 R (3571) 5185 0 R (3576) 5188 0 R (3577) 5189 0 R (3578) 5190 0 R (3582) 5192 0 R (3583) 5193 0 R (3588) 5196 0 R (3589) 5197 0 R (3590) 5198 0 R (3591) 5201 0 R (3592) 5199 0 R (3593) 5200 0 R (36) 1961 0 R (360) 2216 0 R (361) 2217 0 R (362) 2218 0 R (363) 2219 0 R (364) 2220 0 R (366) 2222 0 R (367) 2227 0 R (368) 2228 0 R (369) 2229 0 R (37) 1962 0 R (372) 2230 0 R (373) 2231 0 R (374) 2232 0 R (375) 2233 0 R (376) 2234 0 R (377) 2235 0 R (378) 2236 0 R (38) 1963 0 R (381) 2237 0 R (382) 2238 0 R (386) 2240 0 R (387) 2241 0 R (388) 2242 0 R (389) 2243 0 R (39) 1964 0 R (390) 2244 0 R (391) 2245 0 R (392) 2246 0 R (393) 2247 0 R (394) 2248 0 R (395) 2249 0 R (396) 2250 0 R (397) 2251 0 R (398) 2252 0 R (399) 2253 0 R (4.0) 14 0 R (4.1.1) 18 0 R (4.2.1) 22 0 R (4.3.1) 26 0 R (4.4.1) 30 0 R (4.5.1) 34 0 R (40) 1965 0 R (400) 2254 0 R (401) 2259 0 R (402) 2260 0 R (403) 2261 0 R (404) 2262 0 R (405) 2263 0 R (407) 2265 0 R (408) 2266 0 R (41) 1966 0 R (411) 2267 0 R (417) 2271 0 R (418) 2272 0 R (421) 2273 0 R (422) 2274 0 R (423) 2275 0 R (424) 2276 0 R (425) 2277 0 R (426) 2278 0 R (427) 2279 0 R (428) 2280 0 R (429) 2281 0 R (430) 2282 0 R (433) 2284 0 R (434) 2285 0 R (435) 2286 0 R (436) 2287 0 R (437) 2292 0 R (439) 2293 0 R (44) 1967 0 R (440) 2294 0 R (441) 2295 0 R (442) 2296 0 R (443) 2297 0 R (444) 2298 0 R (447) 2300 0 R (448) 2301 0 R (449) 2302 0 R (45) 1968 0 R (450) 2306 0 R (452) 2308 0 R (453) 2309 0 R (454) 2310 0 R (455) 2311 0 R (456) 2312 0 R (457) 2313 0 R (458) 2314 0 R (459) 2315 0 R (460) 2316 0 R (461) 2317 0 R (462) 2318 0 R (463) 2319 0 R (464) 2320 0 R (466) 2321 0 R (467) 2322 0 R (468) 2323 0 R (469) 2324 0 R (47) 1969 0 R (470) 2325 0 R (471) 2326 0 R (472) 2327 0 R (473) 2328 0 R (474) 2329 0 R (475) 2335 0 R (476) 2336 0 R (477) 2337 0 R (480) 2338 0 R (482) 2339 0 R (483) 2340 0 R (484) 2341 0 R (485) 2342 0 R (487) 2344 0 R (488) 2345 0 R (489) 2346 0 R (49) 1973 0 R (490) 2347 0 R (491) 2348 0 R (492) 2349 0 R (493) 2350 0 R (494) 2351 0 R (495) 2352 0 R (496) 2353 0 R (497) 2354 0 R (498) 2355 0 R (5.0) 38 0 R (5.10.1) 202 0 R (5.10.17.10.3) 210 0 R (5.10.17.11.3) 214 0 R (5.10.17.12.3) 218 0 R (5.10.17.13.3) 222 0 R (5.10.17.2) 206 0 R (5.10.18.14.3) 230 0 R (5.10.18.15.3) 234 0 R (5.10.18.2) 226 0 R (5.10.19.2) 238 0 R (5.11.1) 242 0 R (5.11.20.2) 246 0 R (5.11.21.16.12.4) 258 0 R (5.11.21.16.13.4) 262 0 R (5.11.21.16.14.4) 266 0 R (5.11.21.16.3) 254 0 R (5.11.21.2) 250 0 R (5.11.22.2) 270 0 R (5.11.23.2) 274 0 R (5.11.24.17.3) 282 0 R (5.11.24.2) 278 0 R (5.11.25.18.3) 290 0 R (5.11.25.2) 286 0 R (5.12.1) 294 0 R (5.12.26.2) 298 0 R (5.12.27.19.3) 306 0 R (5.12.27.2) 302 0 R (5.12.27.20.3) 310 0 R (5.12.27.21.3) 314 0 R (5.12.27.22.3) 318 0 R (5.12.28.2) 322 0 R (5.12.29.2) 326 0 R (5.6.1) 42 0 R (5.6.1.2) 46 0 R (5.6.2.1.3) 54 0 R (5.6.2.2) 50 0 R (5.6.2.2.3) 58 0 R (5.6.2.3.3) 62 0 R (5.6.3.2) 66 0 R (5.6.4.2) 70 0 R (5.6.5.2) 74 0 R (5.6.6.2) 78 0 R (5.6.7.2) 82 0 R (5.7.1) 86 0 R (5.7.10.2) 150 0 R (5.7.11.2) 154 0 R (5.7.11.8.10.4) 162 0 R (5.7.11.8.11.4) 166 0 R (5.7.11.8.3) 158 0 R (5.7.11.9.3) 170 0 R (5.7.12.2) 174 0 R (5.7.8.2) 90 0 R (5.7.9.2) 94 0 R (5.7.9.4.3) 98 0 R (5.7.9.5.1.4) 106 0 R (5.7.9.5.2.4) 110 0 R (5.7.9.5.3) 102 0 R (5.7.9.5.3.4) 114 0 R (5.7.9.5.4.4) 118 0 R (5.7.9.6.3) 122 0 R (5.7.9.6.5.4) 126 0 R (5.7.9.6.6.4) 130 0 R (5.7.9.7.3) 134 0 R (5.7.9.7.7.4) 138 0 R (5.7.9.7.8.4) 142 0 R (5.7.9.7.9.4) 146 0 R (5.8.1) 178 0 R (5.8.13.2) 182 0 R (5.8.14.2) 186 0 R (5.8.15.2) 190 0 R (5.8.16.2) 194 0 R (5.9.1) 198 0 R (50) 1974 0 R (500) 2356 0 R (501) 2357 0 R (502) 2358 0 R (503) 2359 0 R (504) 2360 0 R (505) 2361 0 R (506) 2362 0 R (507) 2363 0 R (508) 2364 0 R (509) 2365 0 R (510) 2366 0 R (511) 2367 0 R (514) 2372 0 R (516) 2373 0 R (517) 2374 0 R (518) 2375 0 R (519) 2376 0 R (52) 1975 0 R (520) 2377 0 R (521) 2378 0 R (522) 2379 0 R (524) 2380 0 R (525) 2381 0 R (526) 2382 0 R (527) 2383 0 R (528) 2384 0 R (529) 2385 0 R (53) 1976 0 R (530) 2386 0 R (532) 2387 0 R (533) 2388 0 R (534) 2389 0 R (535) 2390 0 R (536) 1348 0 R (538) 2395 0 R (539) 2396 0 R (54) 1977 0 R (540) 2397 0 R (541) 2398 0 R (542) 2399 0 R (543) 2400 0 R (546) 2401 0 R (547) 2402 0 R (549) 2404 0 R (55) 1978 0 R (552) 2405 0 R (558) 2409 0 R (559) 2410 0 R (560) 2411 0 R (562) 2412 0 R (563) 2413 0 R (564) 2414 0 R (566) 2415 0 R (567) 2416 0 R (568) 2417 0 R (569) 2418 0 R (57) 1979 0 R (570) 2419 0 R (571) 2420 0 R (572) 2421 0 R (573) 2422 0 R (574) 2423 0 R (575) 2429 0 R (576) 2430 0 R (577) 2431 0 R (579) 2433 0 R (58) 1980 0 R (581) 2434 0 R (582) 2435 0 R (583) 2436 0 R (584) 2437 0 R (585) 2438 0 R (586) 2439 0 R (587) 2440 0 R (589) 2441 0 R (59) 1981 0 R (590) 2442 0 R (591) 2443 0 R (592) 2444 0 R (596) 2445 0 R (597) 2446 0 R (598) 2447 0 R (6.0) 330 0 R (6.13.1) 334 0 R (6.13.30.2) 338 0 R (6.13.31.2) 342 0 R (6.13.32.2) 346 0 R (6.13.33.2) 350 0 R (6.13.34.2) 354 0 R (6.13.35.2) 358 0 R (6.13.36.2) 362 0 R (6.13.37.2) 366 0 R (6.13.38.2) 370 0 R (6.13.39.2) 374 0 R (6.13.40.2) 378 0 R (6.13.41.2) 382 0 R (6.13.42.2) 386 0 R (6.13.43.2) 390 0 R (6.13.44.2) 394 0 R (6.13.45.2) 398 0 R (6.14.1) 402 0 R (6.14.46.2) 406 0 R (6.14.47.2) 410 0 R (6.14.47.23.3) 414 0 R (6.14.47.24.15.4) 422 0 R (6.14.47.24.16.4) 426 0 R (6.14.47.24.3) 418 0 R (6.14.47.25.3) 430 0 R (6.14.47.26.3) 434 0 R (6.14.47.27.3) 438 0 R (6.15.1) 442 0 R (6.16.1) 446 0 R (6.16.48.2) 450 0 R (6.16.49.2) 454 0 R (6.16.50.2) 458 0 R (6.16.51.2) 462 0 R (6.16.51.28.3) 466 0 R (6.17.1) 470 0 R (6.18.1) 474 0 R (6.19.1) 478 0 R (6.20.1) 482 0 R (6.20.52.2) 486 0 R (6.20.53.2) 490 0 R (6.20.53.29.3) 494 0 R (6.20.54.2) 498 0 R (6.20.55.2) 502 0 R (6.20.55.30.3) 506 0 R (6.20.55.31.3) 510 0 R (6.20.56.2) 514 0 R (6.20.56.32.3) 518 0 R (6.20.56.33.17.4) 526 0 R (6.20.56.33.18.4) 530 0 R (6.20.56.33.19.4) 534 0 R (6.20.56.33.20.4) 538 0 R (6.20.56.33.21.4) 542 0 R (6.20.56.33.22.4) 546 0 R (6.20.56.33.23.4) 550 0 R (6.20.56.33.24.4) 554 0 R (6.20.56.33.25.4) 558 0 R (6.20.56.33.26.4) 562 0 R (6.20.56.33.27.4) 566 0 R (6.20.56.33.3) 522 0 R (6.20.56.34.3) 570 0 R (6.21.1) 574 0 R (6.22.1) 578 0 R (6.22.57.2) 582 0 R (6.22.58.2) 586 0 R (6.22.59.2) 590 0 R (6.23.1) 594 0 R (6.23.60.2) 598 0 R (6.23.61.2) 602 0 R (6.24.1) 606 0 R (6.25.1) 610 0 R (6.26.1) 614 0 R (6.27.1) 618 0 R (6.27.62.2) 622 0 R (6.27.63.2) 626 0 R (6.27.64.2) 630 0 R (6.27.65.2) 634 0 R (6.28.1) 638 0 R (60) 1982 0 R (600) 2448 0 R (601) 2449 0 R (602) 2450 0 R (604) 2451 0 R (605) 2452 0 R (606) 2453 0 R (607) 2454 0 R (608) 2455 0 R (609) 2456 0 R (610) 2457 0 R (611) 2458 0 R (612) 2459 0 R (614) 2460 0 R (615) 2461 0 R (616) 2462 0 R (617) 2463 0 R (618) 2464 0 R (619) 2465 0 R (62) 1983 0 R (620) 2466 0 R (621) 2467 0 R (622) 2468 0 R (623) 2469 0 R (624) 2470 0 R (625) 2475 0 R (626) 2476 0 R (627) 2477 0 R (628) 2478 0 R (629) 2479 0 R (63) 1984 0 R (630) 2480 0 R (631) 2481 0 R (632) 2482 0 R (633) 2483 0 R (634) 2484 0 R (635) 2485 0 R (636) 2486 0 R (64) 1985 0 R (640) 2487 0 R (641) 2488 0 R (642) 2489 0 R (643) 2490 0 R (644) 2491 0 R (645) 2492 0 R (646) 2493 0 R (647) 2494 0 R (648) 2495 0 R (649) 2496 0 R (65) 1986 0 R (650) 2497 0 R (651) 2498 0 R (652) 2499 0 R (653) 2500 0 R (654) 2501 0 R (655) 2502 0 R (656) 2503 0 R (657) 2504 0 R (658) 2505 0 R (659) 2506 0 R (660) 2507 0 R (661) 2508 0 R (662) 2509 0 R (663) 2514 0 R (664) 2515 0 R (665) 2516 0 R (666) 2517 0 R (67) 1987 0 R (670) 2519 0 R (671) 2520 0 R (673) 2522 0 R (674) 2523 0 R (675) 2524 0 R (676) 2525 0 R (678) 2527 0 R (679) 2528 0 R (68) 1988 0 R (680) 2529 0 R (681) 2530 0 R (682) 2531 0 R (686) 2533 0 R (687) 1354 0 R (689) 2534 0 R (69) 1989 0 R (690) 2535 0 R (691) 2536 0 R (692) 2537 0 R (693) 2541 0 R (694) 2542 0 R (695) 2543 0 R (696) 2544 0 R (697) 2545 0 R (698) 2546 0 R (699) 2547 0 R (7.0) 642 0 R (7.29.1) 646 0 R (7.29.66.2) 650 0 R (7.29.67.2) 654 0 R (7.29.68.2) 658 0 R (7.30.1) 662 0 R (7.30.69.2) 666 0 R (7.31.1) 670 0 R (7.31.70.2) 674 0 R (70) 1990 0 R (702) 2553 0 R (703) 2554 0 R (704) 2555 0 R (705) 2556 0 R (706) 2557 0 R (707) 2558 0 R (710) 2559 0 R (712) 2561 0 R (713) 2562 0 R (714) 2563 0 R (715) 2564 0 R (716) 2565 0 R (717) 2566 0 R (718) 2567 0 R (72) 1991 0 R (721) 2568 0 R (722) 2569 0 R (723) 2570 0 R (724) 2571 0 R (725) 2572 0 R (726) 2573 0 R (727) 2574 0 R (728) 2575 0 R (729) 2576 0 R (73) 1992 0 R (730) 2577 0 R (731) 2578 0 R (732) 2579 0 R (735) 2584 0 R (736) 2585 0 R (737) 2586 0 R (738) 2587 0 R (739) 2588 0 R (74) 1993 0 R (740) 2589 0 R (741) 2590 0 R (742) 2591 0 R (743) 2592 0 R (744) 2593 0 R (745) 2594 0 R (746) 2595 0 R (747) 2596 0 R (748) 2597 0 R (749) 2598 0 R (75) 1994 0 R (752) 2599 0 R (753) 2600 0 R (754) 2601 0 R (757) 2602 0 R (758) 2603 0 R (761) 2608 0 R (762) 2609 0 R (763) 2610 0 R (764) 2611 0 R (765) 2612 0 R (768) 2613 0 R (77) 1995 0 R (771) 2616 0 R (772) 2617 0 R (773) 2618 0 R (774) 2619 0 R (775) 2620 0 R (776) 2621 0 R (777) 2622 0 R (778) 2623 0 R (779) 2624 0 R (78) 1996 0 R (780) 2625 0 R (781) 2626 0 R (782) 2627 0 R (783) 2628 0 R (784) 2629 0 R (785) 2630 0 R (786) 2631 0 R (787) 2632 0 R (79) 1997 0 R (790) 2638 0 R (793) 2641 0 R (794) 2642 0 R (795) 2643 0 R (796) 2644 0 R (797) 2645 0 R (798) 2646 0 R (799) 2647 0 R (8.0) 678 0 R (8.32.1) 682 0 R (8.33.1) 686 0 R (8.34.1) 690 0 R (8.35.1) 694 0 R (8.36.1) 698 0 R (8.36.71.2) 702 0 R (8.36.71.35.3) 706 0 R (8.36.71.36.3) 710 0 R (8.36.71.37.3) 714 0 R (8.36.72.2) 718 0 R (8.36.73.2) 722 0 R (8.36.74.2) 726 0 R (8.36.75.2) 730 0 R (8.37.1) 734 0 R (8.37.76.2) 738 0 R (8.37.77.2) 742 0 R (8.38.1) 746 0 R (8.38.78.2) 750 0 R (8.38.78.38.3) 754 0 R (8.38.78.39.3) 758 0 R (8.38.78.40.3) 762 0 R (8.38.78.41.3) 766 0 R (8.38.78.42.3) 770 0 R (8.38.78.43.3) 774 0 R (8.38.78.44.3) 778 0 R (8.39.1) 782 0 R (8.39.79.2) 786 0 R (8.39.80.2) 790 0 R (8.39.81.2) 794 0 R (8.39.82.2) 798 0 R (8.40.1) 802 0 R (8.41.1) 806 0 R (8.41.83.2) 810 0 R (8.41.84.2) 814 0 R (8.41.85.2) 818 0 R (8.41.86.2) 822 0 R (8.41.87.2) 826 0 R (8.42.1) 830 0 R (8.42.88.2) 834 0 R (8.42.89.2) 838 0 R (8.42.89.45.3) 842 0 R (8.42.89.46.3) 846 0 R (8.43.1) 850 0 R (8.44.1) 854 0 R (8.44.90.2) 858 0 R (8.44.91.2) 862 0 R (8.44.92.2) 866 0 R (8.44.93.2) 870 0 R (80) 1998 0 R (800) 2648 0 R (803) 2649 0 R (807) 2650 0 R (81) 1999 0 R (810) 2651 0 R (811) 2652 0 R (814) 2653 0 R (815) 2654 0 R (816) 2655 0 R (817) 2656 0 R (818) 2657 0 R (819) 2658 0 R (820) 2659 0 R (822) 2661 0 R (823) 2667 0 R (824) 2668 0 R (825) 2669 0 R (826) 2670 0 R (827) 2671 0 R (828) 2672 0 R (829) 2673 0 R (83) 2000 0 R (830) 2674 0 R (831) 2675 0 R (832) 2676 0 R (833) 2677 0 R (837) 2681 0 R (838) 2682 0 R (84) 2001 0 R (840) 2683 0 R (842) 2684 0 R (845) 2685 0 R (846) 2686 0 R (847) 2687 0 R (85) 2002 0 R (850) 1468 0 R (852) 2688 0 R (854) 1469 0 R (856) 2694 0 R (857) 2695 0 R (858) 2696 0 R (859) 2697 0 R (86) 2003 0 R (860) 2698 0 R (861) 2699 0 R (862) 1470 0 R (864) 2700 0 R (866) 2701 0 R (867) 2702 0 R (868) 2703 0 R (87) 2004 0 R (870) 2704 0 R (871) 2705 0 R (872) 2706 0 R (873) 2707 0 R (875) 2713 0 R (876) 2714 0 R (877) 2715 0 R (878) 2716 0 R (879) 2717 0 R (88) 2005 0 R (880) 2718 0 R (881) 2719 0 R (882) 2720 0 R (883) 2721 0 R (884) 2722 0 R (885) 2723 0 R (886) 2724 0 R (887) 2725 0 R (888) 2726 0 R (889) 1471 0 R (89) 2006 0 R (891) 2727 0 R (892) 2728 0 R (893) 2729 0 R (894) 2730 0 R (895) 2731 0 R (896) 2732 0 R (897) 2733 0 R (898) 2734 0 R (899) 2735 0 R (9.0) 874 0 R (9.45.1) 878 0 R (9.46.1) 882 0 R (9.47.1) 886 0 R (9.47.94.2) 890 0 R (9.47.95.2) 894 0 R (9.47.96.2) 898 0 R (9.47.97.2) 902 0 R (9.47.98.2) 906 0 R (9.47.99.2) 910 0 R (9.48.1) 914 0 R (9.49.1) 918 0 R (90) 2007 0 R (900) 2736 0 R (901) 2737 0 R (902) 2738 0 R (903) 2739 0 R (904) 2740 0 R (907) 2746 0 R (908) 2747 0 R (909) 2748 0 R (91) 2008 0 R (910) 2749 0 R (911) 1473 0 R (913) 2750 0 R (914) 1474 0 R (916) 2751 0 R (917) 2752 0 R (918) 2753 0 R (919) 2754 0 R (92) 2009 0 R (920) 2755 0 R (921) 2756 0 R (922) 2757 0 R (923) 1475 0 R (925) 2758 0 R (926) 2759 0 R (927) 2760 0 R (929) 2762 0 R (93) 2010 0 R (930) 2763 0 R (931) 2764 0 R (932) 2765 0 R (933) 2766 0 R (936) 2772 0 R (937) 2773 0 R (938) 2774 0 R (939) 2775 0 R (94) 2011 0 R (940) 2776 0 R (941) 2777 0 R (942) 2745 0 R (945) 2778 0 R (946) 2779 0 R (947) 2780 0 R (948) 2781 0 R (949) 2782 0 R (95) 2012 0 R (952) 2783 0 R (953) 2784 0 R (954) 2785 0 R (955) 2786 0 R (956) 2787 0 R (957) 2788 0 R (958) 2789 0 R (96) 2013 0 R (960) 2791 0 R (961) 2792 0 R (963) 2795 0 R (964) 2796 0 R (965) 2797 0 R (966) 2798 0 R (967) 2803 0 R (968) 2771 0 R (969) 2804 0 R (971) 2805 0 R (972) 2806 0 R (973) 2807 0 R (974) 2808 0 R (976) 2809 0 R (977) 2810 0 R (978) 2811 0 R (979) 2812 0 R (982) 2813 0 R (983) 2814 0 R (985) 2815 0 R (987) 2817 0 R (988) 2818 0 R (989) 2819 0 R (99) 2014 0 R (991) 2820 0 R (993) 2822 0 R (994) 2823 0 R (996) 2824 0 R (998) 2826 0 R (999) 2827 0 R (Doc-Start) 1210 0 R (about) 1323 0 R (accountpreferences) 1797 0 R (add-custom-fields) 1637 0 R (admin-usermatching) 1503 0 R (administration) 1486 0 R (apache-addtype) 1357 0 R (attachments) 1778 0 R (boolean) 1767 0 R (bug_page) 1764 0 R (bug_status_workflow) 1643 0 R (bugreports) 1775 0 R (bzldap) 1497 0 R (bzradius) 1498 0 R (casesensitivity) 1772 0 R (charts) 1801 0 R (charts-new-series) 1803 0 R (classifications) 1512 0 R (cloningbugs) 1777 0 R (cmdline) 1923 0 R (cmdline-bugmail) 1924 0 R (comment-wrapping) 1790 0 R (commenting) 1789 0 R (components) 1620 0 R (comps-vers-miles-products) 1617 0 R (configuration) 1341 0 R (conventions) 1328 0 R (copyright) 1324 0 R (create-groups) 1647 0 R (create-product) 1615 0 R (createnewusers) 1508 0 R (credits) 1327 0 R (cust-change-permissions) 1911 0 R (cust-skins) 1903 0 R (cust-templates) 1904 0 R (custom-fields) 1636 0 R (customization) 1901 0 R (database-engine) 1343 0 R (database-schema) 1344 0 R (defaultuser) 1505 0 R (delete-custom-fields) 1639 0 R (dependencytree) 1791 0 R (disclaimer) 1325 0 R (edit-custom-fields) 1638 0 R (edit-groups) 1648 0 R (edit-products) 1616 0 R (edit-values) 1640 0 R (edit-values-delete) 1642 0 R (edit-values-list) 1641 0 R (emailpreferences) 1795 0 R (extensions) 1902 0 R (extraconfig) 1353 0 R (fillingbugs) 1776 0 R (flag-askto) 1627 0 R (flag-type-attachment) 1629 0 R (flag-type-bug) 1630 0 R (flag-types) 1628 0 R (flag-values) 1626 0 R (flags) 1804 0 R (flags-about) 1625 0 R (flags-admin) 1631 0 R (flags-create) 1633 0 R (flags-create-field-active) 3630 0 R (flags-create-field-category) 3590 0 R (flags-create-field-cclist) 3652 0 R (flags-create-field-description) 3586 0 R (flags-create-field-multiplicable) 3647 0 R (flags-create-field-name) 3584 0 R (flags-create-field-requestable) 3633 0 R (flags-create-field-sortkey) 3626 0 R (flags-create-field-specific) 3640 0 R (flags-create-grant-group) 3654 0 R (flags-create-request-group) 3659 0 R (flags-delete) 1634 0 R (flags-edit) 1632 0 R (flags-overview) 1623 0 R (flags-simpleexample) 1624 0 R (general-advice) 1914 0 R (generalpreferences) 1794 0 R (gfdl) 1929 0 R (gfdl-0) 1930 0 R (gfdl-1) 1931 0 R (gfdl-10) 1940 0 R (gfdl-2) 1932 0 R (gfdl-3) 1933 0 R (gfdl-4) 1934 0 R (gfdl-5) 1935 0 R (gfdl-6) 1936 0 R (gfdl-7) 1937 0 R (gfdl-8) 1938 0 R (gfdl-9) 1939 0 R (gfdl-howto) 1941 0 R (gloss-a) 5004 0 R (gloss-apache) 5005 0 R (gloss-b) 5044 0 R (gloss-bugzilla) 2028 0 R (gloss-c) 5060 0 R (gloss-cgi) 2117 0 R (gloss-component) 5065 0 R (gloss-contrib) 3077 0 R (gloss-cpan) 2662 0 R (gloss-d) 5085 0 R (gloss-daemon) 3902 0 R (gloss-dos) 5090 0 R (gloss-g) 5093 0 R (gloss-groups) 5094 0 R (gloss-htaccess) 3976 0 R (gloss-j) 5100 0 R (gloss-javascript) 5101 0 R (gloss-m) 5081 0 R (gloss-mta) 5109 0 R (gloss-mysql) 5114 0 R (gloss-p) 5133 0 R (gloss-ppm) 2633 0 R (gloss-product) 3304 0 R (gloss-q) 5148 0 R (gloss-r) 5157 0 R (gloss-rdbms) 5139 0 R (gloss-regexp) 5160 0 R (gloss-s) 5164 0 R (gloss-service) 3903 0 R (gloss-t) 5186 0 R (gloss-target-milestone) 5187 0 R (gloss-tcl) 5191 0 R (gloss-z) 5194 0 R (gloss-zarro) 5195 0 R (glossary) 1942 0 R (group-control-examples) 1619 0 R (groups) 1646 0 R (hintsandtips) 1787 0 R (http) 1349 0 R (http-apache) 1350 0 R (http-apache-mod_cgi) 2408 0 R (http-apache-mod_perl) 2424 0 R (http-iis) 1351 0 R (impersonatingusers) 1511 0 R (index) 1211 0 R (individual-buglists) 1774 0 R (install-MTA) 1339 0 R (install-bzfiles) 1337 0 R (install-config-bugzilla) 1352 0 R (install-database) 1332 0 R (install-mysql) 1333 0 R (install-oracle) 1335 0 R (install-perl) 1331 0 R (install-perlmodules) 1338 0 R (install-perlmodules-manual) 1925 0 R (install-perlmodules-nonroot) 1472 0 R (install-pg) 1334 0 R (install-setupdatabase-adduser) 2299 0 R (install-webserver) 1336 0 R (installation) 1330 0 R (installation-whining) 1356 0 R (installation-whining-cron) 1355 0 R (installing-bugzilla) 1329 0 R (integration) 1912 0 R (keywords) 1635 0 R (lifecycle) 1765 0 R (lifecycle-image) 1949 0 R (list) 1773 0 R (localconfig) 1342 0 R (macosx-libraries) 1367 0 R (macosx-sendmail) 1366 0 R (manageusers) 1506 0 R (milestones) 1622 0 R (modifyusers) 1509 0 R (modules-manual-download) 1927 0 R (modules-manual-instructions) 1926 0 R (modules-manual-optional) 1928 0 R (multiple-bz-dbs) 1358 0 R (multiplecharts) 1770 0 R (myaccount) 1763 0 R (mysql) 1345 0 R (mysql-max-allowed-packet) 2283 0 R (negation) 1769 0 R (newversions) 1326 0 R (nonroot) 1467 0 R (oracle) 1347 0 R (os-linux) 1466 0 R (os-macosx) 1365 0 R (os-specific) 1359 0 R (os-win32) 1360 0 R (page.1) 1209 0 R (page.10) 2258 0 R (page.100) 4918 0 R (page.101) 4929 0 R (page.102) 4967 0 R (page.103) 4979 0 R (page.104) 4992 0 R (page.105) 4998 0 R (page.106) 5048 0 R (page.107) 5080 0 R (page.108) 5108 0 R (page.109) 5143 0 R (page.11) 2291 0 R (page.110) 5171 0 R (page.12) 2334 0 R (page.13) 2371 0 R (page.14) 2394 0 R (page.15) 2428 0 R (page.16) 2474 0 R (page.17) 2513 0 R (page.18) 2552 0 R (page.19) 2583 0 R (page.2) 1219 0 R (page.20) 2607 0 R (page.21) 2637 0 R (page.22) 2666 0 R (page.23) 2693 0 R (page.24) 2712 0 R (page.25) 2744 0 R (page.26) 2770 0 R (page.27) 2802 0 R (page.28) 2835 0 R (page.29) 2869 0 R (page.3) 1226 0 R (page.30) 2903 0 R (page.31) 2929 0 R (page.32) 2967 0 R (page.33) 3009 0 R (page.34) 3032 0 R (page.35) 3052 0 R (page.36) 3081 0 R (page.37) 3124 0 R (page.38) 3160 0 R (page.39) 3178 0 R (page.4) 1371 0 R (page.40) 3195 0 R (page.41) 3237 0 R (page.42) 3282 0 R (page.43) 3308 0 R (page.44) 3352 0 R (page.45) 3385 0 R (page.46) 3410 0 R (page.47) 3433 0 R (page.48) 3472 0 R (page.49) 3517 0 R (page.5) 1516 0 R (page.50) 3556 0 R (page.51) 3594 0 R (page.52) 3644 0 R (page.53) 3673 0 R (page.54) 3707 0 R (page.55) 3733 0 R (page.56) 3760 0 R (page.57) 3790 0 R (page.58) 3819 0 R (page.59) 3862 0 R (page.6) 1664 0 R (page.60) 3877 0 R (page.61) 3907 0 R (page.62) 3980 0 R (page.63) 3996 0 R (page.64) 4024 0 R (page.65) 4075 0 R (page.66) 4112 0 R (page.67) 4121 0 R (page.68) 4154 0 R (page.69) 4177 0 R (page.7) 1812 0 R (page.70) 4200 0 R (page.71) 4226 0 R (page.72) 4246 0 R (page.73) 4259 0 R (page.74) 4274 0 R (page.75) 4311 0 R (page.76) 4351 0 R (page.77) 4390 0 R (page.78) 4421 0 R (page.79) 4437 0 R (page.8) 1946 0 R (page.80) 4453 0 R (page.81) 4471 0 R (page.82) 4489 0 R (page.83) 4496 0 R (page.84) 4525 0 R (page.85) 4556 0 R (page.86) 4584 0 R (page.87) 4620 0 R (page.88) 4645 0 R (page.89) 4663 0 R (page.9) 2226 0 R (page.90) 4674 0 R (page.91) 4701 0 R (page.92) 4726 0 R (page.93) 4753 0 R (page.94) 4762 0 R (page.95) 4806 0 R (page.96) 4837 0 R (page.97) 4867 0 R (page.98) 4898 0 R (page.99) 4902 0 R (param-LDAPBaseDN) 3106 0 R (param-LDAPbinddn) 3101 0 R (param-LDAPmailattribute) 3116 0 R (param-LDAPserver) 3088 0 R (param-LDAPuidattribute) 3111 0 R (param-RADIUS_email_suffix) 3143 0 R (param-RADIUS_secret) 3140 0 R (param-RADIUS_server) 3137 0 R (param-admin-policies) 1489 0 R (param-attachments) 1491 0 R (param-bug-change-policies) 1492 0 R (param-bugfields) 1493 0 R (param-bugmoving) 1494 0 R (param-dependency-graphs) 1495 0 R (param-email) 1499 0 R (param-group-security) 1496 0 R (param-patchviewer) 1500 0 R (param-querydefaults) 1501 0 R (param-requiredsettings) 1488 0 R (param-shadowdatabase) 1502 0 R (param-user-authentication) 1490 0 R (param-user_verify_class_for_ldap) 3082 0 R (param-user_verify_class_for_radius) 3131 0 R (parameters) 1487 0 R (paranoid-security) 1918 0 R (patches) 1922 0 R (patchviewer) 1779 0 R (patchviewer_bonsai_lxr) 1785 0 R (patchviewer_collapse) 1783 0 R (patchviewer_context) 1782 0 R (patchviewer_diff) 1781 0 R (patchviewer_link) 1784 0 R (patchviewer_unified_diff) 1786 0 R (patchviewer_view) 1780 0 R (permissionsettings) 1798 0 R (postgresql) 1346 0 R (product-group-controls) 1618 0 R (products) 1614 0 R (pronouns) 1768 0 R (query) 1766 0 R (quicksearch) 1771 0 R (quips) 1645 0 R (reporting) 1799 0 R (reports) 1800 0 R (sanitycheck) 1651 0 R (savedsearches) 1796 0 R (security) 1652 0 R (security-bugzilla) 1659 0 R (security-bugzilla-charset) 1660 0 R (security-os) 1653 0 R (security-os-accounts) 1655 0 R (security-os-chroot) 1656 0 R (security-os-ports) 1654 0 R (security-webserver) 1657 0 R (security-webserver-access) 1658 0 R (self-registration) 3189 0 R (suexec) 1476 0 R (table.1) 2018 0 R (table.2) 3697 0 R (table.3) 4036 0 R (table.4) 4089 0 R (table.5) 4179 0 R (table.6) 4240 0 R (table.7) 4263 0 R (template-directory) 1905 0 R (template-edit) 1907 0 R (template-formats) 1908 0 R (template-http-accept) 1910 0 R (template-method) 1906 0 R (template-specific) 1909 0 R (timetracking) 1792 0 R (trbl-dbdSponge) 1917 0 R (trbl-index) 1920 0 R (trbl-passwd-encryption) 1921 0 R (trbl-perlmodule) 1916 0 R (trbl-relogin-everyone) 1919 0 R (trbl-relogin-everyone-restrict) 4737 0 R (trbl-relogin-everyone-share) 4731 0 R (trbl-testserver) 1915 0 R (troubleshooting) 1913 0 R (upgrade) 1477 0 R (upgrade-before) 1478 0 R (upgrade-completion) 1484 0 R (upgrade-cvs) 1481 0 R (upgrade-files) 1479 0 R (upgrade-modified) 1480 0 R (upgrade-notifications) 1485 0 R (upgrade-patches) 1483 0 R (upgrade-tarball) 1482 0 R (user-account-creation) 3179 0 R (user-account-deletion) 1510 0 R (user-account-search) 1507 0 R (useradmin) 1504 0 R (userpreferences) 1793 0 R (users-and-groups) 1649 0 R (using) 1761 0 R (using-intro) 1762 0 R (using-mod_perl-with-bugzilla) 1340 0 R (versions) 1621 0 R (voting) 1644 0 R (whining) 1805 0 R (whining-overview) 1806 0 R (whining-query) 1808 0 R (whining-schedule) 1807 0 R (win32-email) 1364 0 R (win32-http) 1363 0 R (win32-perl) 1361 0 R (win32-perl-modules) 1362 0 R]
+/Limits [(1.0) (win32-perl-modules)]
+>> endobj
+5221 0 obj <<
+/Kids [5220 0 R]
+>> endobj
+5222 0 obj <<
+/Dests 5221 0 R
+>> endobj
+5223 0 obj <<
+/Type /Catalog
+/Pages 5218 0 R
+/Outlines 5219 0 R
+/Names 5222 0 R
+/PageMode /UseOutlines
+/OpenAction 1205 0 R
+>> endobj
+5224 0 obj <<
+/Author()/Title()/Subject()/Creator(LaTeX with hyperref package)/Producer(pdfeTeX-1.21a)/Keywords()
+/CreationDate (D:20110215092245-08'00')
+/PTEX.Fullbanner (This is pdfeTeX, Version 3.141592-1.21a-2.2 (Web2C 7.5.4) kpathsea version 3.5.4)
+>> endobj
+xref
+0 5225
+0000000000 65535 f
+0000000009 00000 n
+0000028287 00000 n
+0001119963 00000 n
+0000000048 00000 n
+0000000096 00000 n
+0000102966 00000 n
+0001119878 00000 n
+0000000135 00000 n
+0000000170 00000 n
+0000402379 00000 n
+0001119791 00000 n
+0000000209 00000 n
+0000000243 00000 n
+0000405285 00000 n
+0001119665 00000 n
+0000000283 00000 n
+0000000329 00000 n
+0000405411 00000 n
+0001119591 00000 n
+0000000371 00000 n
+0000000416 00000 n
+0000405795 00000 n
+0001119504 00000 n
+0000000458 00000 n
+0000000492 00000 n
+0000406112 00000 n
+0001119417 00000 n
+0000000534 00000 n
+0000000570 00000 n
+0000406627 00000 n
+0001119330 00000 n
+0000000612 00000 n
+0000000643 00000 n
+0000411972 00000 n
+0001119256 00000 n
+0000000685 00000 n
+0000000729 00000 n
+0000417984 00000 n
+0001119128 00000 n
+0000000769 00000 n
+0000000818 00000 n
+0000418111 00000 n
+0001119017 00000 n
+0000000860 00000 n
+0000000896 00000 n
+0000419327 00000 n
+0001118943 00000 n
+0000000940 00000 n
+0000000970 00000 n
+0000421583 00000 n
+0001118819 00000 n
+0000001014 00000 n
+0000001055 00000 n
+0000421773 00000 n
+0001118745 00000 n
+0000001101 00000 n
+0000001134 00000 n
+0000422483 00000 n
+0001118658 00000 n
+0000001180 00000 n
+0000001218 00000 n
+0000422933 00000 n
+0001118584 00000 n
+0000001264 00000 n
+0000001298 00000 n
+0000426659 00000 n
+0001118497 00000 n
+0000001342 00000 n
+0000001378 00000 n
+0000427111 00000 n
+0001118410 00000 n
+0000001422 00000 n
+0000001456 00000 n
+0000427953 00000 n
+0001118323 00000 n
+0000001500 00000 n
+0000001538 00000 n
+0000440600 00000 n
+0001118236 00000 n
+0000001582 00000 n
+0000001635 00000 n
+0000444458 00000 n
+0001118162 00000 n
+0000001679 00000 n
+0000001735 00000 n
+0000445042 00000 n
+0001118036 00000 n
+0000001777 00000 n
+0000001814 00000 n
+0000445299 00000 n
+0001117962 00000 n
+0000001858 00000 n
+0000001895 00000 n
+0000450673 00000 n
+0001117836 00000 n
+0000001939 00000 n
+0000001980 00000 n
+0000450865 00000 n
+0001117760 00000 n
+0000002026 00000 n
+0000002079 00000 n
+0000451123 00000 n
+0001117630 00000 n
+0000002126 00000 n
+0000002160 00000 n
+0000451906 00000 n
+0001117551 00000 n
+0000002209 00000 n
+0000002281 00000 n
+0000455164 00000 n
+0001117458 00000 n
+0000002330 00000 n
+0000002398 00000 n
+0000455681 00000 n
+0001117365 00000 n
+0000002447 00000 n
+0000002497 00000 n
+0000456854 00000 n
+0001117286 00000 n
+0000002546 00000 n
+0000002620 00000 n
+0000460431 00000 n
+0001117155 00000 n
+0000002667 00000 n
+0000002706 00000 n
+0000460559 00000 n
+0001117076 00000 n
+0000002755 00000 n
+0000002810 00000 n
+0000461660 00000 n
+0001116997 00000 n
+0000002859 00000 n
+0000002910 00000 n
+0000464543 00000 n
+0001116880 00000 n
+0000002957 00000 n
+0000002992 00000 n
+0000464672 00000 n
+0001116801 00000 n
+0000003041 00000 n
+0000003095 00000 n
+0000465190 00000 n
+0001116708 00000 n
+0000003144 00000 n
+0000003195 00000 n
+0000465708 00000 n
+0001116629 00000 n
+0000003244 00000 n
+0000003299 00000 n
+0000470001 00000 n
+0001116538 00000 n
+0000003345 00000 n
+0000003385 00000 n
+0000470517 00000 n
+0001116407 00000 n
+0000003431 00000 n
+0000003468 00000 n
+0000470841 00000 n
+0001116289 00000 n
+0000003516 00000 n
+0000003566 00000 n
+0000471034 00000 n
+0001116210 00000 n
+0000003617 00000 n
+0000003672 00000 n
+0000476033 00000 n
+0001116131 00000 n
+0000003723 00000 n
+0000003779 00000 n
+0000482178 00000 n
+0001116052 00000 n
+0000003827 00000 n
+0000003895 00000 n
+0000487541 00000 n
+0001115974 00000 n
+0000003941 00000 n
+0000003976 00000 n
+0000488383 00000 n
+0001115844 00000 n
+0000004019 00000 n
+0000004077 00000 n
+0000488576 00000 n
+0001115765 00000 n
+0000004123 00000 n
+0000004160 00000 n
+0000492176 00000 n
+0001115672 00000 n
+0000004206 00000 n
+0000004249 00000 n
+0000492690 00000 n
+0001115579 00000 n
+0000004295 00000 n
+0000004329 00000 n
+0000493334 00000 n
+0001115500 00000 n
+0000004375 00000 n
+0000004452 00000 n
+0000497132 00000 n
+0001115408 00000 n
+0000004495 00000 n
+0000004574 00000 n
+0000498238 00000 n
+0001115277 00000 n
+0000004618 00000 n
+0000004672 00000 n
+0000498561 00000 n
+0001115159 00000 n
+0000004719 00000 n
+0000004763 00000 n
+0000501583 00000 n
+0001115080 00000 n
+0000004813 00000 n
+0000004852 00000 n
+0000502034 00000 n
+0001114987 00000 n
+0000004902 00000 n
+0000004952 00000 n
+0000506572 00000 n
+0001114894 00000 n
+0000005002 00000 n
+0000005052 00000 n
+0000507282 00000 n
+0001114815 00000 n
+0000005102 00000 n
+0000005144 00000 n
+0000507475 00000 n
+0001114683 00000 n
+0000005191 00000 n
+0000005226 00000 n
+0000507668 00000 n
+0001114604 00000 n
+0000005276 00000 n
+0000005313 00000 n
+0000507927 00000 n
+0001114525 00000 n
+0000005363 00000 n
+0000005431 00000 n
+0000512599 00000 n
+0001114446 00000 n
+0000005478 00000 n
+0000005524 00000 n
+0000512919 00000 n
+0001114315 00000 n
+0000005568 00000 n
+0000005628 00000 n
+0000513048 00000 n
+0001114236 00000 n
+0000005675 00000 n
+0000005714 00000 n
+0000515348 00000 n
+0001114104 00000 n
+0000005761 00000 n
+0000005793 00000 n
+0000515866 00000 n
+0001114000 00000 n
+0000005843 00000 n
+0000005896 00000 n
+0000515995 00000 n
+0001113921 00000 n
+0000005949 00000 n
+0000006011 00000 n
+0000516251 00000 n
+0001113828 00000 n
+0000006064 00000 n
+0000006118 00000 n
+0000518685 00000 n
+0001113749 00000 n
+0000006171 00000 n
+0000006221 00000 n
+0000519722 00000 n
+0001113656 00000 n
+0000006268 00000 n
+0000006299 00000 n
+0000523993 00000 n
+0001113563 00000 n
+0000006346 00000 n
+0000006385 00000 n
+0000524382 00000 n
+0001113431 00000 n
+0000006432 00000 n
+0000006470 00000 n
+0000524575 00000 n
+0001113366 00000 n
+0000006520 00000 n
+0000006574 00000 n
+0000525155 00000 n
+0001113248 00000 n
+0000006621 00000 n
+0000006656 00000 n
+0000529444 00000 n
+0001113183 00000 n
+0000006706 00000 n
+0000006759 00000 n
+0000529963 00000 n
+0001113066 00000 n
+0000006803 00000 n
+0000006853 00000 n
+0000530415 00000 n
+0001112987 00000 n
+0000006900 00000 n
+0000006945 00000 n
+0000535597 00000 n
+0001112855 00000 n
+0000006992 00000 n
+0000007043 00000 n
+0000536562 00000 n
+0001112776 00000 n
+0000007093 00000 n
+0000007156 00000 n
+0000539260 00000 n
+0001112683 00000 n
+0000007206 00000 n
+0000007254 00000 n
+0000540170 00000 n
+0001112590 00000 n
+0000007304 00000 n
+0000007360 00000 n
+0000544756 00000 n
+0001112511 00000 n
+0000007410 00000 n
+0000007462 00000 n
+0000545791 00000 n
+0001112418 00000 n
+0000007509 00000 n
+0000007559 00000 n
+0000550319 00000 n
+0001112339 00000 n
+0000007606 00000 n
+0000007672 00000 n
+0000553107 00000 n
+0001112206 00000 n
+0000007713 00000 n
+0000007766 00000 n
+0000553235 00000 n
+0001112087 00000 n
+0000007810 00000 n
+0000007857 00000 n
+0000553426 00000 n
+0001112008 00000 n
+0000007904 00000 n
+0000007948 00000 n
+0000563742 00000 n
+0001111915 00000 n
+0000007995 00000 n
+0000008045 00000 n
+0000563935 00000 n
+0001111822 00000 n
+0000008092 00000 n
+0000008138 00000 n
+0000564709 00000 n
+0001111729 00000 n
+0000008185 00000 n
+0000008223 00000 n
+0000564902 00000 n
+0001111636 00000 n
+0000008270 00000 n
+0000008316 00000 n
+0000568163 00000 n
+0001111543 00000 n
+0000008363 00000 n
+0000008400 00000 n
+0000568803 00000 n
+0001111450 00000 n
+0000008447 00000 n
+0000008484 00000 n
+0000569061 00000 n
+0001111357 00000 n
+0000008531 00000 n
+0000008575 00000 n
+0000573422 00000 n
+0001111264 00000 n
+0000008622 00000 n
+0000008663 00000 n
+0000574258 00000 n
+0001111171 00000 n
+0000008710 00000 n
+0000008757 00000 n
+0000582404 00000 n
+0001111078 00000 n
+0000008804 00000 n
+0000008853 00000 n
+0000583950 00000 n
+0001110985 00000 n
+0000008900 00000 n
+0000008933 00000 n
+0000587744 00000 n
+0001110892 00000 n
+0000008980 00000 n
+0000009020 00000 n
+0000587936 00000 n
+0001110799 00000 n
+0000009067 00000 n
+0000009109 00000 n
+0000588128 00000 n
+0001110706 00000 n
+0000009156 00000 n
+0000009199 00000 n
+0000588577 00000 n
+0001110627 00000 n
+0000009246 00000 n
+0000009287 00000 n
+0000591473 00000 n
+0001110495 00000 n
+0000009331 00000 n
+0000009375 00000 n
+0000591601 00000 n
+0001110416 00000 n
+0000009422 00000 n
+0000009474 00000 n
+0000591921 00000 n
+0001110298 00000 n
+0000009521 00000 n
+0000009568 00000 n
+0000592050 00000 n
+0001110219 00000 n
+0000009618 00000 n
+0000009675 00000 n
+0000592568 00000 n
+0001110087 00000 n
+0000009725 00000 n
+0000009772 00000 n
+0000592697 00000 n
+0001110008 00000 n
+0000009825 00000 n
+0000009872 00000 n
+0000596281 00000 n
+0001109929 00000 n
+0000009925 00000 n
+0000009992 00000 n
+0000597122 00000 n
+0001109836 00000 n
+0000010042 00000 n
+0000010086 00000 n
+0000604668 00000 n
+0001109743 00000 n
+0000010136 00000 n
+0000010179 00000 n
+0000604925 00000 n
+0001109664 00000 n
+0000010229 00000 n
+0000010277 00000 n
+0000608791 00000 n
+0001109571 00000 n
+0000010321 00000 n
+0000010361 00000 n
+0000609304 00000 n
+0001109439 00000 n
+0000010405 00000 n
+0000010438 00000 n
+0000614172 00000 n
+0001109360 00000 n
+0000010485 00000 n
+0000010533 00000 n
+0000615143 00000 n
+0001109267 00000 n
+0000010580 00000 n
+0000010623 00000 n
+0000619322 00000 n
+0001109174 00000 n
+0000010670 00000 n
+0000010757 00000 n
+0000619707 00000 n
+0001109056 00000 n
+0000010804 00000 n
+0000010867 00000 n
+0000624256 00000 n
+0001108991 00000 n
+0000010917 00000 n
+0000010983 00000 n
+0000631064 00000 n
+0001108898 00000 n
+0000011027 00000 n
+0000011062 00000 n
+0000632560 00000 n
+0001108805 00000 n
+0000011106 00000 n
+0000011139 00000 n
+0000633267 00000 n
+0001108712 00000 n
+0000011183 00000 n
+0000011218 00000 n
+0000636741 00000 n
+0001108580 00000 n
+0000011262 00000 n
+0000011292 00000 n
+0000637132 00000 n
+0001108501 00000 n
+0000011339 00000 n
+0000011382 00000 n
+0000641302 00000 n
+0001108369 00000 n
+0000011429 00000 n
+0000011467 00000 n
+0000641431 00000 n
+0001108304 00000 n
+0000011517 00000 n
+0000011552 00000 n
+0000642594 00000 n
+0001108211 00000 n
+0000011599 00000 n
+0000011645 00000 n
+0000643438 00000 n
+0001108079 00000 n
+0000011692 00000 n
+0000011737 00000 n
+0000643630 00000 n
+0001108000 00000 n
+0000011787 00000 n
+0000011832 00000 n
+0000647782 00000 n
+0001107921 00000 n
+0000011882 00000 n
+0000011920 00000 n
+0000648237 00000 n
+0001107803 00000 n
+0000011967 00000 n
+0000012013 00000 n
+0000648691 00000 n
+0001107724 00000 n
+0000012063 00000 n
+0000012106 00000 n
+0000648882 00000 n
+0001107591 00000 n
+0000012156 00000 n
+0000012200 00000 n
+0000649140 00000 n
+0001107512 00000 n
+0000012253 00000 n
+0000012288 00000 n
+0000649332 00000 n
+0001107419 00000 n
+0000012341 00000 n
+0000012383 00000 n
+0000652836 00000 n
+0001107326 00000 n
+0000012436 00000 n
+0000012475 00000 n
+0000654983 00000 n
+0001107233 00000 n
+0000012528 00000 n
+0000012567 00000 n
+0000655304 00000 n
+0001107140 00000 n
+0000012620 00000 n
+0000012657 00000 n
+0000655563 00000 n
+0001107047 00000 n
+0000012710 00000 n
+0000012752 00000 n
+0000658654 00000 n
+0001106954 00000 n
+0000012805 00000 n
+0000012860 00000 n
+0000658911 00000 n
+0001106861 00000 n
+0000012913 00000 n
+0000012957 00000 n
+0000659302 00000 n
+0001106768 00000 n
+0000013010 00000 n
+0000013048 00000 n
+0000659494 00000 n
+0001106675 00000 n
+0000013101 00000 n
+0000013144 00000 n
+0000659879 00000 n
+0001106596 00000 n
+0000013197 00000 n
+0000013242 00000 n
+0000660138 00000 n
+0001106517 00000 n
+0000013292 00000 n
+0000013336 00000 n
+0000664528 00000 n
+0001106424 00000 n
+0000013380 00000 n
+0000013413 00000 n
+0000664849 00000 n
+0001106292 00000 n
+0000013457 00000 n
+0000013496 00000 n
+0000665298 00000 n
+0001106213 00000 n
+0000013543 00000 n
+0000013591 00000 n
+0000670169 00000 n
+0001106120 00000 n
+0000013638 00000 n
+0000013687 00000 n
+0000670360 00000 n
+0001106041 00000 n
+0000013734 00000 n
+0000013784 00000 n
+0000670619 00000 n
+0001105909 00000 n
+0000013828 00000 n
+0000013866 00000 n
+0000670875 00000 n
+0001105830 00000 n
+0000013913 00000 n
+0000013969 00000 n
+0000671198 00000 n
+0001105751 00000 n
+0000014016 00000 n
+0000014065 00000 n
+0000674641 00000 n
+0001105658 00000 n
+0000014109 00000 n
+0000014154 00000 n
+0000674898 00000 n
+0001105565 00000 n
+0000014198 00000 n
+0000014230 00000 n
+0000676056 00000 n
+0001105472 00000 n
+0000014274 00000 n
+0000014305 00000 n
+0000681125 00000 n
+0001105340 00000 n
+0000014349 00000 n
+0000014400 00000 n
+0000685894 00000 n
+0001105261 00000 n
+0000014447 00000 n
+0000014490 00000 n
+0000687189 00000 n
+0001105168 00000 n
+0000014537 00000 n
+0000014611 00000 n
+0000693113 00000 n
+0001105075 00000 n
+0000014658 00000 n
+0000014711 00000 n
+0000695958 00000 n
+0001104996 00000 n
+0000014758 00000 n
+0000014822 00000 n
+0000696151 00000 n
+0001104917 00000 n
+0000014866 00000 n
+0000014935 00000 n
+0000700055 00000 n
+0001104784 00000 n
+0000014976 00000 n
+0000015024 00000 n
+0000700375 00000 n
+0001104666 00000 n
+0000015068 00000 n
+0000015109 00000 n
+0000700504 00000 n
+0001104587 00000 n
+0000015156 00000 n
+0000015195 00000 n
+0000700697 00000 n
+0001104494 00000 n
+0000015242 00000 n
+0000015289 00000 n
+0000701869 00000 n
+0001104415 00000 n
+0000015336 00000 n
+0000015378 00000 n
+0000704993 00000 n
+0001104283 00000 n
+0000015422 00000 n
+0000015457 00000 n
+0000705122 00000 n
+0001104218 00000 n
+0000015504 00000 n
+0000015586 00000 n
+0000711788 00000 n
+0001104100 00000 n
+0000015630 00000 n
+0000015663 00000 n
+0000711917 00000 n
+0001104035 00000 n
+0000015710 00000 n
+0000015781 00000 n
+0000715234 00000 n
+0001103901 00000 n
+0000015822 00000 n
+0000015867 00000 n
+0000715362 00000 n
+0001103822 00000 n
+0000015911 00000 n
+0000015948 00000 n
+0000715751 00000 n
+0001103729 00000 n
+0000015992 00000 n
+0000016042 00000 n
+0000721072 00000 n
+0001103636 00000 n
+0000016086 00000 n
+0000016127 00000 n
+0000728960 00000 n
+0001103543 00000 n
+0000016171 00000 n
+0000016215 00000 n
+0000755035 00000 n
+0001103411 00000 n
+0000016259 00000 n
+0000016302 00000 n
+0000758186 00000 n
+0001103293 00000 n
+0000016349 00000 n
+0000016390 00000 n
+0000759412 00000 n
+0001103214 00000 n
+0000016440 00000 n
+0000016489 00000 n
+0000759669 00000 n
+0001103121 00000 n
+0000016539 00000 n
+0000016576 00000 n
+0000763046 00000 n
+0001103042 00000 n
+0000016626 00000 n
+0000016670 00000 n
+0000763561 00000 n
+0001102949 00000 n
+0000016717 00000 n
+0000016755 00000 n
+0000764015 00000 n
+0001102856 00000 n
+0000016802 00000 n
+0000016857 00000 n
+0000764208 00000 n
+0001102763 00000 n
+0000016904 00000 n
+0000016940 00000 n
+0000768641 00000 n
+0001102684 00000 n
+0000016987 00000 n
+0000017047 00000 n
+0000769160 00000 n
+0001102552 00000 n
+0000017091 00000 n
+0000017127 00000 n
+0000769289 00000 n
+0001102473 00000 n
+0000017174 00000 n
+0000017220 00000 n
+0000774732 00000 n
+0001102394 00000 n
+0000017267 00000 n
+0000017315 00000 n
+0000777901 00000 n
+0001102262 00000 n
+0000017359 00000 n
+0000017395 00000 n
+0000778674 00000 n
+0001102158 00000 n
+0000017442 00000 n
+0000017481 00000 n
+0000779058 00000 n
+0001102079 00000 n
+0000017531 00000 n
+0000017591 00000 n
+0000781607 00000 n
+0001101986 00000 n
+0000017641 00000 n
+0000017711 00000 n
+0000781800 00000 n
+0001101893 00000 n
+0000017761 00000 n
+0000017821 00000 n
+0000781991 00000 n
+0001101800 00000 n
+0000017871 00000 n
+0000017944 00000 n
+0000782183 00000 n
+0001101707 00000 n
+0000017994 00000 n
+0000018054 00000 n
+0000782376 00000 n
+0001101614 00000 n
+0000018104 00000 n
+0000018156 00000 n
+0000782633 00000 n
+0001101535 00000 n
+0000018206 00000 n
+0000018258 00000 n
+0000782826 00000 n
+0001101403 00000 n
+0000018302 00000 n
+0000018341 00000 n
+0000785388 00000 n
+0001101324 00000 n
+0000018388 00000 n
+0000018432 00000 n
+0000785775 00000 n
+0001101231 00000 n
+0000018479 00000 n
+0000018514 00000 n
+0000786032 00000 n
+0001101138 00000 n
+0000018561 00000 n
+0000018615 00000 n
+0000786225 00000 n
+0001101059 00000 n
+0000018662 00000 n
+0000018704 00000 n
+0000789664 00000 n
+0001100966 00000 n
+0000018748 00000 n
+0000018798 00000 n
+0000790118 00000 n
+0001100834 00000 n
+0000018842 00000 n
+0000018884 00000 n
+0000790311 00000 n
+0001100755 00000 n
+0000018931 00000 n
+0000018978 00000 n
+0000791853 00000 n
+0001100662 00000 n
+0000019025 00000 n
+0000019070 00000 n
+0000800658 00000 n
+0001100569 00000 n
+0000019117 00000 n
+0000019159 00000 n
+0000800851 00000 n
+0001100476 00000 n
+0000019206 00000 n
+0000019251 00000 n
+0000801176 00000 n
+0001100397 00000 n
+0000019298 00000 n
+0000019337 00000 n
+0000806567 00000 n
+0001100265 00000 n
+0000019381 00000 n
+0000019425 00000 n
+0000806758 00000 n
+0001100186 00000 n
+0000019472 00000 n
+0000019507 00000 n
+0000810142 00000 n
+0001100068 00000 n
+0000019554 00000 n
+0000019588 00000 n
+0000810655 00000 n
+0001099989 00000 n
+0000019638 00000 n
+0000019683 00000 n
+0000811103 00000 n
+0001099910 00000 n
+0000019733 00000 n
+0000019785 00000 n
+0000813883 00000 n
+0001099817 00000 n
+0000019829 00000 n
+0000019860 00000 n
+0000814523 00000 n
+0001099699 00000 n
+0000019904 00000 n
+0000019937 00000 n
+0000818371 00000 n
+0001099620 00000 n
+0000019984 00000 n
+0000020021 00000 n
+0000818691 00000 n
+0001099527 00000 n
+0000020068 00000 n
+0000020112 00000 n
+0000822642 00000 n
+0001099434 00000 n
+0000020159 00000 n
+0000020203 00000 n
+0000824409 00000 n
+0001099355 00000 n
+0000020250 00000 n
+0000020297 00000 n
+0000827367 00000 n
+0001099222 00000 n
+0000020338 00000 n
+0000020389 00000 n
+0000827495 00000 n
+0001099143 00000 n
+0000020433 00000 n
+0000020477 00000 n
+0000827817 00000 n
+0001099050 00000 n
+0000020521 00000 n
+0000020558 00000 n
+0000828657 00000 n
+0001098918 00000 n
+0000020602 00000 n
+0000020649 00000 n
+0000828914 00000 n
+0001098839 00000 n
+0000020696 00000 n
+0000020751 00000 n
+0000832842 00000 n
+0001098746 00000 n
+0000020798 00000 n
+0000020856 00000 n
+0000838045 00000 n
+0001098653 00000 n
+0000020903 00000 n
+0000020951 00000 n
+0000838821 00000 n
+0001098560 00000 n
+0000020998 00000 n
+0000021051 00000 n
+0000843646 00000 n
+0001098467 00000 n
+0000021098 00000 n
+0000021145 00000 n
+0000849388 00000 n
+0001098388 00000 n
+0000021192 00000 n
+0000021269 00000 n
+0000849645 00000 n
+0001098295 00000 n
+0000021313 00000 n
+0000021369 00000 n
+0000855338 00000 n
+0001098216 00000 n
+0000021413 00000 n
+0000021480 00000 n
+0000858788 00000 n
+0001098083 00000 n
+0000021522 00000 n
+0000021569 00000 n
+0000858980 00000 n
+0001098004 00000 n
+0000021614 00000 n
+0000021653 00000 n
+0000859694 00000 n
+0001097911 00000 n
+0000021698 00000 n
+0000021774 00000 n
+0000860143 00000 n
+0001097818 00000 n
+0000021819 00000 n
+0000021915 00000 n
+0000862877 00000 n
+0001097725 00000 n
+0000021960 00000 n
+0000022015 00000 n
+0000863519 00000 n
+0001097632 00000 n
+0000022060 00000 n
+0000022118 00000 n
+0000864297 00000 n
+0001097539 00000 n
+0000022163 00000 n
+0000022235 00000 n
+0000868408 00000 n
+0001097446 00000 n
+0000022280 00000 n
+0000022358 00000 n
+0000870093 00000 n
+0001097367 00000 n
+0000022403 00000 n
+0000022522 00000 n
+0000873888 00000 n
+0001097234 00000 n
+0000022564 00000 n
+0000022603 00000 n
+0000874145 00000 n
+0001097155 00000 n
+0000022648 00000 n
+0000022701 00000 n
+0000876163 00000 n
+0001097076 00000 n
+0000022746 00000 n
+0000022809 00000 n
+0000879069 00000 n
+0001096943 00000 n
+0000022851 00000 n
+0000022918 00000 n
+0000879198 00000 n
+0001096864 00000 n
+0000022963 00000 n
+0000023000 00000 n
+0000880227 00000 n
+0001096771 00000 n
+0000023045 00000 n
+0000023088 00000 n
+0000885205 00000 n
+0001096692 00000 n
+0000023133 00000 n
+0000023174 00000 n
+0000890136 00000 n
+0001096556 00000 n
+0000023216 00000 n
+0000023278 00000 n
+0000890454 00000 n
+0001096477 00000 n
+0000023323 00000 n
+0000023354 00000 n
+0000890771 00000 n
+0001096384 00000 n
+0000023399 00000 n
+0000023450 00000 n
+0000894768 00000 n
+0001096289 00000 n
+0000023495 00000 n
+0000023535 00000 n
+0000895024 00000 n
+0001096192 00000 n
+0000023581 00000 n
+0000023624 00000 n
+0000898942 00000 n
+0001096094 00000 n
+0000023670 00000 n
+0000023707 00000 n
+0000904137 00000 n
+0001095996 00000 n
+0000023753 00000 n
+0000023796 00000 n
+0000904459 00000 n
+0001095898 00000 n
+0000023842 00000 n
+0000023890 00000 n
+0000904716 00000 n
+0001095800 00000 n
+0000023936 00000 n
+0000023994 00000 n
+0000907773 00000 n
+0001095702 00000 n
+0000024040 00000 n
+0000024075 00000 n
+0000907967 00000 n
+0001095604 00000 n
+0000024121 00000 n
+0000024156 00000 n
+0000908160 00000 n
+0001095506 00000 n
+0000024202 00000 n
+0000024259 00000 n
+0000908483 00000 n
+0001095423 00000 n
+0000024305 00000 n
+0000024368 00000 n
+0000912449 00000 n
+0001095325 00000 n
+0000024411 00000 n
+0000024440 00000 n
+0000912577 00000 n
+0001095185 00000 n
+0000024483 00000 n
+0000024518 00000 n
+0000912707 00000 n
+0001095116 00000 n
+0000024568 00000 n
+0000024598 00000 n
+0000913094 00000 n
+0001094976 00000 n
+0000024641 00000 n
+0000024663 00000 n
+0000913223 00000 n
+0001094866 00000 n
+0000024713 00000 n
+0000024740 00000 n
+0000913676 00000 n
+0001094797 00000 n
+0000024793 00000 n
+0000024857 00000 n
+0000917663 00000 n
+0001094657 00000 n
+0000024900 00000 n
+0000024922 00000 n
+0000917792 00000 n
+0001094547 00000 n
+0000024972 00000 n
+0000024996 00000 n
+0000918244 00000 n
+0001094463 00000 n
+0000025046 00000 n
+0000025077 00000 n
+0000918502 00000 n
+0001094379 00000 n
+0000025127 00000 n
+0000025156 00000 n
+0000918759 00000 n
+0001094239 00000 n
+0000025199 00000 n
+0000025221 00000 n
+0000918888 00000 n
+0001094129 00000 n
+0000025271 00000 n
+0000025316 00000 n
+0000919274 00000 n
+0001094045 00000 n
+0000025366 00000 n
+0000025396 00000 n
+0000919531 00000 n
+0001093946 00000 n
+0000025446 00000 n
+0000025501 00000 n
+0000920048 00000 n
+0001093862 00000 n
+0000025551 00000 n
+0000025579 00000 n
+0000922422 00000 n
+0001093722 00000 n
+0000025622 00000 n
+0000025644 00000 n
+0000922551 00000 n
+0001093612 00000 n
+0000025694 00000 n
+0000025721 00000 n
+0000922939 00000 n
+0001093543 00000 n
+0000025771 00000 n
+0000025802 00000 n
+0000923197 00000 n
+0001093403 00000 n
+0000025845 00000 n
+0000025867 00000 n
+0000923325 00000 n
+0001093334 00000 n
+0000025917 00000 n
+0000025944 00000 n
+0000923780 00000 n
+0001093194 00000 n
+0000025987 00000 n
+0000026009 00000 n
+0000923908 00000 n
+0001093125 00000 n
+0000026059 00000 n
+0000026090 00000 n
+0000926066 00000 n
+0001092985 00000 n
+0000026133 00000 n
+0000026155 00000 n
+0000926195 00000 n
+0001092875 00000 n
+0000026205 00000 n
+0000026249 00000 n
+0000926584 00000 n
+0001092806 00000 n
+0000026299 00000 n
+0000026325 00000 n
+0000927807 00000 n
+0001092666 00000 n
+0000026368 00000 n
+0000026390 00000 n
+0000927936 00000 n
+0001092556 00000 n
+0000026440 00000 n
+0000026481 00000 n
+0000928258 00000 n
+0001092472 00000 n
+0000026531 00000 n
+0000026559 00000 n
+0000930208 00000 n
+0001092388 00000 n
+0000026609 00000 n
+0000026634 00000 n
+0000930530 00000 n
+0001092248 00000 n
+0000026677 00000 n
+0000026699 00000 n
+0000930659 00000 n
+0001092179 00000 n
+0000026749 00000 n
+0000026772 00000 n
+0000931244 00000 n
+0001092039 00000 n
+0000026815 00000 n
+0000026837 00000 n
+0000931373 00000 n
+0001091929 00000 n
+0000026887 00000 n
+0000026945 00000 n
+0000931629 00000 n
+0001091860 00000 n
+0000026995 00000 n
+0000027034 00000 n
+0000931951 00000 n
+0001091720 00000 n
+0000027077 00000 n
+0000027099 00000 n
+0000932080 00000 n
+0001091610 00000 n
+0000027149 00000 n
+0000027177 00000 n
+0000934815 00000 n
+0001091541 00000 n
+0000027227 00000 n
+0000027253 00000 n
+0000935729 00000 n
+0001091401 00000 n
+0000027296 00000 n
+0000027318 00000 n
+0000935857 00000 n
+0001091291 00000 n
+0000027368 00000 n
+0000027405 00000 n
+0000936181 00000 n
+0001091222 00000 n
+0000027455 00000 n
+0000027497 00000 n
+0000936439 00000 n
+0001091097 00000 n
+0000027540 00000 n
+0000027562 00000 n
+0000936568 00000 n
+0001091028 00000 n
+0000027612 00000 n
+0000027650 00000 n
+0000027969 00000 n
+0000028349 00000 n
+0000027704 00000 n
+0000028095 00000 n
+0000028159 00000 n
+0000028223 00000 n
+0001087023 00000 n
+0001073239 00000 n
+0001086849 00000 n
+0001087908 00000 n
+0000029207 00000 n
+0000029017 00000 n
+0000028423 00000 n
+0000029143 00000 n
+0001072112 00000 n
+0001050266 00000 n
+0001071935 00000 n
+0000103028 00000 n
+0000087576 00000 n
+0000029295 00000 n
+0000102902 00000 n
+0000088524 00000 n
+0001049401 00000 n
+0001033095 00000 n
+0001049224 00000 n
+0000088676 00000 n
+0000088828 00000 n
+0000088984 00000 n
+0000089140 00000 n
+0000089297 00000 n
+0000089454 00000 n
+0000089612 00000 n
+0000089770 00000 n
+0000089924 00000 n
+0000090078 00000 n
+0000090236 00000 n
+0000090394 00000 n
+0000090558 00000 n
+0000090723 00000 n
+0000090881 00000 n
+0000091039 00000 n
+0000091199 00000 n
+0000091358 00000 n
+0000091522 00000 n
+0000091685 00000 n
+0000091846 00000 n
+0000092006 00000 n
+0000092164 00000 n
+0000092321 00000 n
+0000092482 00000 n
+0000092643 00000 n
+0000092808 00000 n
+0000092972 00000 n
+0000093134 00000 n
+0000093295 00000 n
+0000093462 00000 n
+0000093628 00000 n
+0000093787 00000 n
+0000093945 00000 n
+0000094121 00000 n
+0000094296 00000 n
+0000094456 00000 n
+0000094616 00000 n
+0000094775 00000 n
+0000094933 00000 n
+0000095096 00000 n
+0000095259 00000 n
+0000095419 00000 n
+0000095580 00000 n
+0000095733 00000 n
+0000095886 00000 n
+0000096044 00000 n
+0000096202 00000 n
+0000096354 00000 n
+0000096507 00000 n
+0000096658 00000 n
+0000096809 00000 n
+0000096961 00000 n
+0000097113 00000 n
+0000097272 00000 n
+0000097431 00000 n
+0000097587 00000 n
+0000097743 00000 n
+0000097914 00000 n
+0000098085 00000 n
+0000098242 00000 n
+0000098401 00000 n
+0000098552 00000 n
+0000098703 00000 n
+0000098876 00000 n
+0000099049 00000 n
+0000099217 00000 n
+0000099385 00000 n
+0000099546 00000 n
+0000099708 00000 n
+0000099869 00000 n
+0000100031 00000 n
+0000100189 00000 n
+0000100348 00000 n
+0000100502 00000 n
+0000100656 00000 n
+0000100814 00000 n
+0000100972 00000 n
+0000101138 00000 n
+0000101304 00000 n
+0000101462 00000 n
+0000101620 00000 n
+0000101779 00000 n
+0000101938 00000 n
+0000102095 00000 n
+0000102252 00000 n
+0000102415 00000 n
+0000102578 00000 n
+0000102740 00000 n
+0001032249 00000 n
+0001014024 00000 n
+0001032068 00000 n
+0000405221 00000 n
+0000405348 00000 n
+0000405731 00000 n
+0000406049 00000 n
+0000406565 00000 n
+0000411908 00000 n
+0000417920 00000 n
+0000418047 00000 n
+0000419263 00000 n
+0000421519 00000 n
+0000421710 00000 n
+0000422419 00000 n
+0000422869 00000 n
+0000426595 00000 n
+0000427047 00000 n
+0000427889 00000 n
+0000440536 00000 n
+0000444394 00000 n
+0000444978 00000 n
+0000445235 00000 n
+0000450609 00000 n
+0000450801 00000 n
+0000451059 00000 n
+0000460368 00000 n
+0000462504 00000 n
+0000466031 00000 n
+0000470453 00000 n
+0000470777 00000 n
+0000482114 00000 n
+0000487477 00000 n
+0000488319 00000 n
+0000488512 00000 n
+0000489351 00000 n
+0000492626 00000 n
+0000493270 00000 n
+0000497068 00000 n
+0000498174 00000 n
+0000498497 00000 n
+0000498755 00000 n
+0000501970 00000 n
+0000506508 00000 n
+0000507218 00000 n
+0000507411 00000 n
+0000507604 00000 n
+0000507863 00000 n
+0000177182 00000 n
+0000160999 00000 n
+0000103144 00000 n
+0000177118 00000 n
+0000161983 00000 n
+0000162139 00000 n
+0000162295 00000 n
+0000162449 00000 n
+0000162604 00000 n
+0000162754 00000 n
+0000162904 00000 n
+0000163055 00000 n
+0000163206 00000 n
+0000163357 00000 n
+0000163508 00000 n
+0000163659 00000 n
+0000163810 00000 n
+0000163985 00000 n
+0000164160 00000 n
+0000164311 00000 n
+0000164462 00000 n
+0000164613 00000 n
+0000164764 00000 n
+0000164915 00000 n
+0000165066 00000 n
+0000165220 00000 n
+0000165374 00000 n
+0000165528 00000 n
+0000165683 00000 n
+0000165845 00000 n
+0000166007 00000 n
+0000166166 00000 n
+0000166326 00000 n
+0000166490 00000 n
+0000166654 00000 n
+0000166813 00000 n
+0000166972 00000 n
+0000167135 00000 n
+0000167298 00000 n
+0000167461 00000 n
+0000167624 00000 n
+0000167790 00000 n
+0000167956 00000 n
+0000168125 00000 n
+0000168294 00000 n
+0000168454 00000 n
+0000168616 00000 n
+0000168773 00000 n
+0000168931 00000 n
+0000169098 00000 n
+0000169266 00000 n
+0000169434 00000 n
+0000169602 00000 n
+0000169775 00000 n
+0000169948 00000 n
+0000170110 00000 n
+0000170273 00000 n
+0000170446 00000 n
+0000170619 00000 n
+0000170782 00000 n
+0000170945 00000 n
+0000171107 00000 n
+0000171270 00000 n
+0000171439 00000 n
+0000171608 00000 n
+0000171776 00000 n
+0000171944 00000 n
+0000172098 00000 n
+0000172252 00000 n
+0000172406 00000 n
+0000172560 00000 n
+0000172719 00000 n
+0000172878 00000 n
+0000173043 00000 n
+0000173208 00000 n
+0000173375 00000 n
+0000173542 00000 n
+0000173710 00000 n
+0000173878 00000 n
+0000174044 00000 n
+0000174210 00000 n
+0000174365 00000 n
+0000174522 00000 n
+0000174681 00000 n
+0000174840 00000 n
+0000174999 00000 n
+0000175158 00000 n
+0000175325 00000 n
+0000175492 00000 n
+0000175654 00000 n
+0000175816 00000 n
+0000175974 00000 n
+0000176132 00000 n
+0000176300 00000 n
+0000176469 00000 n
+0000176634 00000 n
+0000176799 00000 n
+0000176958 00000 n
+0000512535 00000 n
+0000512855 00000 n
+0000512984 00000 n
+0000513177 00000 n
+0000515802 00000 n
+0000519658 00000 n
+0000520703 00000 n
+0000524318 00000 n
+0000524511 00000 n
+0000525092 00000 n
+0000525743 00000 n
+0000529899 00000 n
+0000530350 00000 n
+0000535533 00000 n
+0000536498 00000 n
+0000536755 00000 n
+0000540106 00000 n
+0000544692 00000 n
+0000545729 00000 n
+0000550255 00000 n
+0000553043 00000 n
+0000553171 00000 n
+0000553363 00000 n
+0000563678 00000 n
+0000563871 00000 n
+0000564645 00000 n
+0000564838 00000 n
+0000568099 00000 n
+0000568739 00000 n
+0000568997 00000 n
+0000569256 00000 n
+0000574194 00000 n
+0000579658 00000 n
+0000583886 00000 n
+0000587680 00000 n
+0000587872 00000 n
+0000588064 00000 n
+0000588513 00000 n
+0000591409 00000 n
+0000591537 00000 n
+0000591858 00000 n
+0000591986 00000 n
+0000592504 00000 n
+0000597058 00000 n
+0000604604 00000 n
+0000604861 00000 n
+0000608727 00000 n
+0000253297 00000 n
+0000237060 00000 n
+0000177284 00000 n
+0000253233 00000 n
+0000238044 00000 n
+0000238199 00000 n
+0000238355 00000 n
+0000238517 00000 n
+0000238679 00000 n
+0000238840 00000 n
+0000239001 00000 n
+0000239174 00000 n
+0000239347 00000 n
+0000239517 00000 n
+0000239687 00000 n
+0000239857 00000 n
+0000240027 00000 n
+0000240183 00000 n
+0000240340 00000 n
+0000240495 00000 n
+0000240651 00000 n
+0000240808 00000 n
+0000240966 00000 n
+0000241127 00000 n
+0000241289 00000 n
+0000241456 00000 n
+0000241623 00000 n
+0000241782 00000 n
+0000241941 00000 n
+0000242100 00000 n
+0000242259 00000 n
+0000242416 00000 n
+0000242573 00000 n
+0000242731 00000 n
+0000242889 00000 n
+0000243057 00000 n
+0000243225 00000 n
+0000243386 00000 n
+0000243547 00000 n
+0000243706 00000 n
+0000243865 00000 n
+0000244023 00000 n
+0000244181 00000 n
+0000244341 00000 n
+0000244501 00000 n
+0000244660 00000 n
+0000244819 00000 n
+0000244974 00000 n
+0000245130 00000 n
+0000245289 00000 n
+0000245449 00000 n
+0000245614 00000 n
+0000245779 00000 n
+0000245945 00000 n
+0000246111 00000 n
+0000246279 00000 n
+0000246447 00000 n
+0000246605 00000 n
+0000246764 00000 n
+0000246928 00000 n
+0000247092 00000 n
+0000247258 00000 n
+0000247424 00000 n
+0000247588 00000 n
+0000247754 00000 n
+0000247907 00000 n
+0000248061 00000 n
+0000248213 00000 n
+0000248366 00000 n
+0000248519 00000 n
+0000248673 00000 n
+0000248834 00000 n
+0000248995 00000 n
+0000249154 00000 n
+0000249313 00000 n
+0000249477 00000 n
+0000249641 00000 n
+0000249791 00000 n
+0000249941 00000 n
+0000250099 00000 n
+0000250258 00000 n
+0000250412 00000 n
+0000250568 00000 n
+0000250726 00000 n
+0000250885 00000 n
+0000251050 00000 n
+0000251215 00000 n
+0000251383 00000 n
+0000251551 00000 n
+0001013042 00000 n
+0000992855 00000 n
+0001012867 00000 n
+0000251717 00000 n
+0000251883 00000 n
+0000252048 00000 n
+0000252214 00000 n
+0000252387 00000 n
+0000252560 00000 n
+0000252723 00000 n
+0000252887 00000 n
+0000253060 00000 n
+0000609240 00000 n
+0000614108 00000 n
+0000615079 00000 n
+0000619258 00000 n
+0000619643 00000 n
+0000624192 00000 n
+0000631001 00000 n
+0000632496 00000 n
+0000633203 00000 n
+0000636677 00000 n
+0000637068 00000 n
+0000638951 00000 n
+0000641367 00000 n
+0000642530 00000 n
+0000643374 00000 n
+0000643566 00000 n
+0000647718 00000 n
+0000648173 00000 n
+0000648627 00000 n
+0000648819 00000 n
+0000660074 00000 n
+0000660724 00000 n
+0000664785 00000 n
+0000665234 00000 n
+0000670105 00000 n
+0000670296 00000 n
+0000670555 00000 n
+0000670811 00000 n
+0000671134 00000 n
+0000674578 00000 n
+0000674834 00000 n
+0000675992 00000 n
+0000681061 00000 n
+0000682221 00000 n
+0000687125 00000 n
+0000693049 00000 n
+0000695894 00000 n
+0000696087 00000 n
+0000699991 00000 n
+0000700311 00000 n
+0000700440 00000 n
+0000700633 00000 n
+0000701805 00000 n
+0000702063 00000 n
+0000705058 00000 n
+0000711725 00000 n
+0000711853 00000 n
+0000330684 00000 n
+0000314274 00000 n
+0000253413 00000 n
+0000330620 00000 n
+0000315276 00000 n
+0000315427 00000 n
+0000315579 00000 n
+0000315737 00000 n
+0000315896 00000 n
+0000316052 00000 n
+0000316209 00000 n
+0000316364 00000 n
+0000316520 00000 n
+0000316676 00000 n
+0000316833 00000 n
+0000316985 00000 n
+0000317138 00000 n
+0000317293 00000 n
+0000317448 00000 n
+0000317604 00000 n
+0000317760 00000 n
+0000317916 00000 n
+0000318072 00000 n
+0000318234 00000 n
+0000318396 00000 n
+0000318555 00000 n
+0000318714 00000 n
+0000318877 00000 n
+0000319040 00000 n
+0000319192 00000 n
+0000319344 00000 n
+0000319510 00000 n
+0000319676 00000 n
+0000319833 00000 n
+0000319991 00000 n
+0000320148 00000 n
+0000320306 00000 n
+0000320465 00000 n
+0000320624 00000 n
+0000320782 00000 n
+0000320941 00000 n
+0000321100 00000 n
+0000321259 00000 n
+0000321423 00000 n
+0000321587 00000 n
+0000321750 00000 n
+0000321914 00000 n
+0000322081 00000 n
+0000322248 00000 n
+0000322415 00000 n
+0000322582 00000 n
+0000322746 00000 n
+0000322910 00000 n
+0000323080 00000 n
+0000323250 00000 n
+0000323422 00000 n
+0000323594 00000 n
+0000323753 00000 n
+0000323913 00000 n
+0000324065 00000 n
+0000324217 00000 n
+0000324375 00000 n
+0000324533 00000 n
+0000324695 00000 n
+0000324857 00000 n
+0000325019 00000 n
+0000325181 00000 n
+0000325340 00000 n
+0000325500 00000 n
+0000325661 00000 n
+0000325823 00000 n
+0000325989 00000 n
+0000326155 00000 n
+0000326319 00000 n
+0000326483 00000 n
+0000326644 00000 n
+0000326805 00000 n
+0000326970 00000 n
+0000327135 00000 n
+0000327301 00000 n
+0000327467 00000 n
+0000327623 00000 n
+0000327780 00000 n
+0000327935 00000 n
+0000328090 00000 n
+0000328244 00000 n
+0000328398 00000 n
+0000328550 00000 n
+0000328702 00000 n
+0000328867 00000 n
+0000329032 00000 n
+0000329183 00000 n
+0000329335 00000 n
+0000329489 00000 n
+0000329644 00000 n
+0000329808 00000 n
+0000329972 00000 n
+0000330136 00000 n
+0000330300 00000 n
+0000330460 00000 n
+0000715170 00000 n
+0000715298 00000 n
+0000715687 00000 n
+0000721008 00000 n
+0000728896 00000 n
+0000727149 00000 n
+0000758122 00000 n
+0000759348 00000 n
+0000759605 00000 n
+0000762983 00000 n
+0000763497 00000 n
+0000763952 00000 n
+0000764144 00000 n
+0000768577 00000 n
+0000769096 00000 n
+0000769225 00000 n
+0000774668 00000 n
+0000777837 00000 n
+0000778610 00000 n
+0000778994 00000 n
+0000779186 00000 n
+0000781736 00000 n
+0000781928 00000 n
+0000782119 00000 n
+0000782312 00000 n
+0000782569 00000 n
+0000782762 00000 n
+0000782955 00000 n
+0000785711 00000 n
+0000785968 00000 n
+0000786161 00000 n
+0000786483 00000 n
+0000790054 00000 n
+0000790247 00000 n
+0000791789 00000 n
+0000800594 00000 n
+0000800787 00000 n
+0000801112 00000 n
+0000806503 00000 n
+0000806695 00000 n
+0000805221 00000 n
+0000810591 00000 n
+0000811039 00000 n
+0000813819 00000 n
+0000814459 00000 n
+0000818306 00000 n
+0000818627 00000 n
+0000822578 00000 n
+0000399777 00000 n
+0000385042 00000 n
+0000330786 00000 n
+0000399713 00000 n
+0000385954 00000 n
+0000386106 00000 n
+0000386258 00000 n
+0000386418 00000 n
+0000386579 00000 n
+0000386736 00000 n
+0000386894 00000 n
+0000387051 00000 n
+0000387209 00000 n
+0000387370 00000 n
+0000387532 00000 n
+0000387698 00000 n
+0000387864 00000 n
+0000388024 00000 n
+0000388184 00000 n
+0000388345 00000 n
+0000388506 00000 n
+0000388669 00000 n
+0000388832 00000 n
+0000388997 00000 n
+0000389162 00000 n
+0000389330 00000 n
+0000389498 00000 n
+0000389668 00000 n
+0000389839 00000 n
+0000389997 00000 n
+0000390156 00000 n
+0000390318 00000 n
+0000390481 00000 n
+0000390642 00000 n
+0000390804 00000 n
+0000390965 00000 n
+0000391127 00000 n
+0000391289 00000 n
+0000391452 00000 n
+0000391613 00000 n
+0000391775 00000 n
+0000391939 00000 n
+0000392104 00000 n
+0000392272 00000 n
+0000392441 00000 n
+0000392598 00000 n
+0000392756 00000 n
+0000392925 00000 n
+0000393095 00000 n
+0000393249 00000 n
+0000393404 00000 n
+0000393558 00000 n
+0000393713 00000 n
+0000393874 00000 n
+0000394036 00000 n
+0000394209 00000 n
+0000394383 00000 n
+0000394557 00000 n
+0000394732 00000 n
+0000394902 00000 n
+0000395073 00000 n
+0000395243 00000 n
+0000395414 00000 n
+0000395564 00000 n
+0000395716 00000 n
+0000395868 00000 n
+0000396021 00000 n
+0000396174 00000 n
+0000396328 00000 n
+0000396481 00000 n
+0000396635 00000 n
+0000396787 00000 n
+0000396940 00000 n
+0000397092 00000 n
+0000397246 00000 n
+0000397399 00000 n
+0000397553 00000 n
+0000397706 00000 n
+0000397860 00000 n
+0000398013 00000 n
+0000398167 00000 n
+0000398320 00000 n
+0000398474 00000 n
+0000398627 00000 n
+0000398781 00000 n
+0000398934 00000 n
+0000399088 00000 n
+0000399244 00000 n
+0000399402 00000 n
+0000399557 00000 n
+0001088033 00000 n
+0000823283 00000 n
+0000827303 00000 n
+0000827431 00000 n
+0000827753 00000 n
+0000828593 00000 n
+0000828850 00000 n
+0000832778 00000 n
+0000837981 00000 n
+0000838757 00000 n
+0000843582 00000 n
+0000849324 00000 n
+0000849581 00000 n
+0000855274 00000 n
+0000858724 00000 n
+0000858916 00000 n
+0000859630 00000 n
+0000860080 00000 n
+0000862813 00000 n
+0000863455 00000 n
+0000864233 00000 n
+0000868344 00000 n
+0000868729 00000 n
+0000873824 00000 n
+0000874081 00000 n
+0000876099 00000 n
+0000879005 00000 n
+0000879134 00000 n
+0000880163 00000 n
+0000885141 00000 n
+0000890072 00000 n
+0000890390 00000 n
+0000890707 00000 n
+0000894704 00000 n
+0000894961 00000 n
+0000898878 00000 n
+0000904073 00000 n
+0000904395 00000 n
+0000904652 00000 n
+0000907709 00000 n
+0000907903 00000 n
+0000908097 00000 n
+0000908419 00000 n
+0000912385 00000 n
+0000402442 00000 n
+0000401834 00000 n
+0000399893 00000 n
+0000402315 00000 n
+0000401990 00000 n
+0000402152 00000 n
+0000729221 00000 n
+0000406880 00000 n
+0000404923 00000 n
+0000402544 00000 n
+0000405475 00000 n
+0000405539 00000 n
+0000405603 00000 n
+0000405070 00000 n
+0000405667 00000 n
+0000405859 00000 n
+0000405922 00000 n
+0000405986 00000 n
+0000406176 00000 n
+0000406240 00000 n
+0000406304 00000 n
+0000406370 00000 n
+0000406433 00000 n
+0000406499 00000 n
+0000406690 00000 n
+0000406754 00000 n
+0000406817 00000 n
+0000412356 00000 n
+0000409122 00000 n
+0000406982 00000 n
+0000409248 00000 n
+0000409312 00000 n
+0000409377 00000 n
+0000409441 00000 n
+0000409507 00000 n
+0000409569 00000 n
+0000409634 00000 n
+0000409698 00000 n
+0000409764 00000 n
+0000409828 00000 n
+0000409893 00000 n
+0000409957 00000 n
+0000410023 00000 n
+0000410087 00000 n
+0000410151 00000 n
+0000410215 00000 n
+0000410280 00000 n
+0000410344 00000 n
+0000410409 00000 n
+0000410473 00000 n
+0000410539 00000 n
+0000410603 00000 n
+0000410667 00000 n
+0000410731 00000 n
+0000410797 00000 n
+0000410861 00000 n
+0000410926 00000 n
+0000410990 00000 n
+0000411055 00000 n
+0000411121 00000 n
+0000411187 00000 n
+0000411253 00000 n
+0000411319 00000 n
+0000411385 00000 n
+0000411451 00000 n
+0000411517 00000 n
+0000411583 00000 n
+0000411647 00000 n
+0000411713 00000 n
+0000411779 00000 n
+0000411843 00000 n
+0000412036 00000 n
+0000412100 00000 n
+0000412164 00000 n
+0000412228 00000 n
+0000412292 00000 n
+0000991791 00000 n
+0000979578 00000 n
+0000991617 00000 n
+0000414033 00000 n
+0000413596 00000 n
+0000412486 00000 n
+0000413743 00000 n
+0000413903 00000 n
+0000413967 00000 n
+0000918438 00000 n
+0000419649 00000 n
+0000416428 00000 n
+0000414177 00000 n
+0000418175 00000 n
+0000418239 00000 n
+0000418303 00000 n
+0000978818 00000 n
+0000968798 00000 n
+0000978640 00000 n
+0000418369 00000 n
+0000416638 00000 n
+0000416797 00000 n
+0000418433 00000 n
+0000418497 00000 n
+0000418561 00000 n
+0000418627 00000 n
+0000418691 00000 n
+0000418754 00000 n
+0000418816 00000 n
+0000416953 00000 n
+0000418880 00000 n
+0000417112 00000 n
+0000418944 00000 n
+0000417274 00000 n
+0000419008 00000 n
+0000417438 00000 n
+0000419072 00000 n
+0000417599 00000 n
+0000419136 00000 n
+0000417763 00000 n
+0000419200 00000 n
+0000419391 00000 n
+0000419455 00000 n
+0000419519 00000 n
+0000419583 00000 n
+0000423253 00000 n
+0000421393 00000 n
+0000419793 00000 n
+0000421646 00000 n
+0000421837 00000 n
+0000421901 00000 n
+0000421965 00000 n
+0000422029 00000 n
+0000422095 00000 n
+0000422159 00000 n
+0000422223 00000 n
+0000422289 00000 n
+0000422355 00000 n
+0000422547 00000 n
+0000422611 00000 n
+0000422675 00000 n
+0000422739 00000 n
+0000422805 00000 n
+0000422997 00000 n
+0000423059 00000 n
+0000423123 00000 n
+0000423187 00000 n
+0001088158 00000 n
+0000428533 00000 n
+0000426057 00000 n
+0000423383 00000 n
+0000426531 00000 n
+0000426723 00000 n
+0000426787 00000 n
+0000426213 00000 n
+0000426851 00000 n
+0000426917 00000 n
+0000426981 00000 n
+0000427175 00000 n
+0000427239 00000 n
+0000427303 00000 n
+0000427369 00000 n
+0000427435 00000 n
+0000427501 00000 n
+0000427565 00000 n
+0000427631 00000 n
+0000427696 00000 n
+0000427761 00000 n
+0000427825 00000 n
+0000428017 00000 n
+0000428081 00000 n
+0000426370 00000 n
+0000428147 00000 n
+0000428211 00000 n
+0000428277 00000 n
+0000428341 00000 n
+0000428405 00000 n
+0000428469 00000 n
+0000918824 00000 n
+0000435039 00000 n
+0000431262 00000 n
+0000428663 00000 n
+0000431939 00000 n
+0000432003 00000 n
+0000432067 00000 n
+0000432133 00000 n
+0000431427 00000 n
+0000432198 00000 n
+0000431593 00000 n
+0000431767 00000 n
+0000432264 00000 n
+0000432328 00000 n
+0000432392 00000 n
+0000432456 00000 n
+0000432520 00000 n
+0000432583 00000 n
+0000432649 00000 n
+0000432715 00000 n
+0000432780 00000 n
+0000432844 00000 n
+0000432910 00000 n
+0000432974 00000 n
+0000433038 00000 n
+0000433104 00000 n
+0000433168 00000 n
+0000433233 00000 n
+0000433297 00000 n
+0000433362 00000 n
+0000433426 00000 n
+0000433491 00000 n
+0000433555 00000 n
+0000433620 00000 n
+0000433684 00000 n
+0000433749 00000 n
+0000433813 00000 n
+0000433878 00000 n
+0000433942 00000 n
+0000434007 00000 n
+0000434071 00000 n
+0000434136 00000 n
+0000434200 00000 n
+0000434265 00000 n
+0000434329 00000 n
+0000434394 00000 n
+0000434458 00000 n
+0000434523 00000 n
+0000434587 00000 n
+0000434652 00000 n
+0000434716 00000 n
+0000434781 00000 n
+0000434847 00000 n
+0000434910 00000 n
+0000434975 00000 n
+0000441049 00000 n
+0000437587 00000 n
+0000435183 00000 n
+0000437891 00000 n
+0000437956 00000 n
+0000438020 00000 n
+0000438085 00000 n
+0000438149 00000 n
+0000438214 00000 n
+0000438278 00000 n
+0000438343 00000 n
+0000438407 00000 n
+0000438472 00000 n
+0000438536 00000 n
+0000438601 00000 n
+0000438665 00000 n
+0000438730 00000 n
+0000438794 00000 n
+0000438859 00000 n
+0000438923 00000 n
+0000438988 00000 n
+0000439052 00000 n
+0000439117 00000 n
+0000439181 00000 n
+0000439246 00000 n
+0000439310 00000 n
+0000439375 00000 n
+0000439439 00000 n
+0000439504 00000 n
+0000439568 00000 n
+0000439633 00000 n
+0000439697 00000 n
+0000439762 00000 n
+0000439826 00000 n
+0000439891 00000 n
+0000439955 00000 n
+0000440020 00000 n
+0000440084 00000 n
+0000440149 00000 n
+0000440213 00000 n
+0000440278 00000 n
+0000440342 00000 n
+0000440407 00000 n
+0000440471 00000 n
+0000440664 00000 n
+0000440728 00000 n
+0000440792 00000 n
+0000440856 00000 n
+0000440920 00000 n
+0000437734 00000 n
+0000440985 00000 n
+0000446333 00000 n
+0000443836 00000 n
+0000441165 00000 n
+0000444138 00000 n
+0000444202 00000 n
+0000444266 00000 n
+0000444330 00000 n
+0000444521 00000 n
+0000444585 00000 n
+0000444650 00000 n
+0000444716 00000 n
+0000444782 00000 n
+0000444846 00000 n
+0000444912 00000 n
+0000445105 00000 n
+0000445169 00000 n
+0000443983 00000 n
+0000445362 00000 n
+0000445425 00000 n
+0000445491 00000 n
+0000445557 00000 n
+0000445620 00000 n
+0000445683 00000 n
+0000445747 00000 n
+0000445813 00000 n
+0000445879 00000 n
+0000445943 00000 n
+0000446009 00000 n
+0000446073 00000 n
+0000446137 00000 n
+0000446203 00000 n
+0000446267 00000 n
+0000452227 00000 n
+0000449296 00000 n
+0000446463 00000 n
+0000450089 00000 n
+0000450153 00000 n
+0000450219 00000 n
+0000450285 00000 n
+0000450349 00000 n
+0000450415 00000 n
+0000449470 00000 n
+0000450479 00000 n
+0000450543 00000 n
+0000450737 00000 n
+0000449624 00000 n
+0000449777 00000 n
+0000449935 00000 n
+0000450929 00000 n
+0000450993 00000 n
+0000451188 00000 n
+0000451252 00000 n
+0000451318 00000 n
+0000451384 00000 n
+0000451450 00000 n
+0000451516 00000 n
+0000451581 00000 n
+0000451647 00000 n
+0000451711 00000 n
+0000451777 00000 n
+0000451842 00000 n
+0000451970 00000 n
+0000452034 00000 n
+0000452097 00000 n
+0000452163 00000 n
+0000457502 00000 n
+0000454730 00000 n
+0000452371 00000 n
+0000455036 00000 n
+0000455100 00000 n
+0000455229 00000 n
+0000455293 00000 n
+0000455357 00000 n
+0000455423 00000 n
+0000455487 00000 n
+0000455551 00000 n
+0000455617 00000 n
+0000455746 00000 n
+0000455810 00000 n
+0000455876 00000 n
+0000968220 00000 n
+0000958238 00000 n
+0000968041 00000 n
+0000455941 00000 n
+0000454877 00000 n
+0000456006 00000 n
+0000456070 00000 n
+0000456136 00000 n
+0000456202 00000 n
+0000456268 00000 n
+0000456334 00000 n
+0000456400 00000 n
+0000456464 00000 n
+0000456530 00000 n
+0000456594 00000 n
+0000456659 00000 n
+0000456725 00000 n
+0000456790 00000 n
+0000456918 00000 n
+0000456981 00000 n
+0000457045 00000 n
+0000457110 00000 n
+0000457176 00000 n
+0000457242 00000 n
+0000457306 00000 n
+0000457371 00000 n
+0000457437 00000 n
+0001088283 00000 n
+0000462568 00000 n
+0000459806 00000 n
+0000457646 00000 n
+0000460112 00000 n
+0000460176 00000 n
+0000460240 00000 n
+0000460304 00000 n
+0000460495 00000 n
+0000460624 00000 n
+0000460688 00000 n
+0000460754 00000 n
+0000460820 00000 n
+0000459953 00000 n
+0000460886 00000 n
+0000460950 00000 n
+0000461014 00000 n
+0000461079 00000 n
+0000461143 00000 n
+0000461207 00000 n
+0000461272 00000 n
+0000461336 00000 n
+0000461402 00000 n
+0000461468 00000 n
+0000461532 00000 n
+0000461596 00000 n
+0000461725 00000 n
+0000461789 00000 n
+0000461855 00000 n
+0000461921 00000 n
+0000461985 00000 n
+0000462049 00000 n
+0000462113 00000 n
+0000462177 00000 n
+0000462242 00000 n
+0000462306 00000 n
+0000462372 00000 n
+0000462438 00000 n
+0000466095 00000 n
+0000464353 00000 n
+0000462726 00000 n
+0000464479 00000 n
+0000464608 00000 n
+0000464737 00000 n
+0000464801 00000 n
+0000464865 00000 n
+0000464931 00000 n
+0000464994 00000 n
+0000465060 00000 n
+0000465126 00000 n
+0000465255 00000 n
+0000465319 00000 n
+0000465384 00000 n
+0000465449 00000 n
+0000465514 00000 n
+0000465578 00000 n
+0000465644 00000 n
+0000465773 00000 n
+0000465837 00000 n
+0000465903 00000 n
+0000465967 00000 n
+0000472071 00000 n
+0000469265 00000 n
+0000466239 00000 n
+0000469937 00000 n
+0000470065 00000 n
+0000470129 00000 n
+0000470195 00000 n
+0000470259 00000 n
+0000470323 00000 n
+0000470387 00000 n
+0000470582 00000 n
+0000470646 00000 n
+0000469430 00000 n
+0000470711 00000 n
+0000470906 00000 n
+0000469602 00000 n
+0000469769 00000 n
+0000470970 00000 n
+0000471099 00000 n
+0000471163 00000 n
+0000471227 00000 n
+0000471291 00000 n
+0000471357 00000 n
+0000471423 00000 n
+0000471487 00000 n
+0000471553 00000 n
+0000471619 00000 n
+0000471683 00000 n
+0000471746 00000 n
+0000471812 00000 n
+0000471877 00000 n
+0000471941 00000 n
+0000472005 00000 n
+0000475970 00000 n
+0000477784 00000 n
+0000474625 00000 n
+0000472215 00000 n
+0000474930 00000 n
+0000474994 00000 n
+0000475059 00000 n
+0000475123 00000 n
+0000474772 00000 n
+0000475189 00000 n
+0000475254 00000 n
+0000475318 00000 n
+0000475384 00000 n
+0000475450 00000 n
+0000475516 00000 n
+0000475582 00000 n
+0000475648 00000 n
+0000475712 00000 n
+0000475777 00000 n
+0000475842 00000 n
+0000475906 00000 n
+0000476098 00000 n
+0000476162 00000 n
+0000476226 00000 n
+0000476290 00000 n
+0000476355 00000 n
+0000476420 00000 n
+0000476484 00000 n
+0000476548 00000 n
+0000476612 00000 n
+0000476678 00000 n
+0000476744 00000 n
+0000476809 00000 n
+0000476875 00000 n
+0000476941 00000 n
+0000477005 00000 n
+0000477069 00000 n
+0000477134 00000 n
+0000477200 00000 n
+0000477266 00000 n
+0000477332 00000 n
+0000477397 00000 n
+0000477462 00000 n
+0000477526 00000 n
+0000477590 00000 n
+0000477654 00000 n
+0000477720 00000 n
+0000483739 00000 n
+0000481141 00000 n
+0000477942 00000 n
+0000481267 00000 n
+0000481331 00000 n
+0000481397 00000 n
+0000481461 00000 n
+0000481527 00000 n
+0000481593 00000 n
+0000481659 00000 n
+0000481722 00000 n
+0000481788 00000 n
+0000481854 00000 n
+0000481918 00000 n
+0000481984 00000 n
+0000482048 00000 n
+0000482243 00000 n
+0000482307 00000 n
+0000482373 00000 n
+0000482439 00000 n
+0000482505 00000 n
+0000482570 00000 n
+0000482635 00000 n
+0000482701 00000 n
+0000482767 00000 n
+0000482833 00000 n
+0000482897 00000 n
+0000482961 00000 n
+0000483027 00000 n
+0000483093 00000 n
+0000483157 00000 n
+0000483223 00000 n
+0000483287 00000 n
+0000483351 00000 n
+0000483415 00000 n
+0000483479 00000 n
+0000483543 00000 n
+0000483607 00000 n
+0000483673 00000 n
+0000489415 00000 n
+0000486326 00000 n
+0000483883 00000 n
+0000487153 00000 n
+0000487217 00000 n
+0000487281 00000 n
+0000487345 00000 n
+0000487411 00000 n
+0000486500 00000 n
+0000487606 00000 n
+0000487670 00000 n
+0000486673 00000 n
+0000487736 00000 n
+0000487800 00000 n
+0000487864 00000 n
+0000487928 00000 n
+0000486836 00000 n
+0000487993 00000 n
+0000488059 00000 n
+0000488125 00000 n
+0000488191 00000 n
+0000488257 00000 n
+0000486994 00000 n
+0000488448 00000 n
+0000488641 00000 n
+0000488705 00000 n
+0000488769 00000 n
+0000488833 00000 n
+0000957559 00000 n
+0000941846 00000 n
+0000957384 00000 n
+0000488899 00000 n
+0000488963 00000 n
+0000489029 00000 n
+0000489093 00000 n
+0000489157 00000 n
+0000489221 00000 n
+0000489285 00000 n
+0001088408 00000 n
+0000494180 00000 n
+0000491811 00000 n
+0000489573 00000 n
+0000492112 00000 n
+0000492241 00000 n
+0000492305 00000 n
+0000492369 00000 n
+0000492433 00000 n
+0000492496 00000 n
+0000492560 00000 n
+0000492755 00000 n
+0000491958 00000 n
+0000492819 00000 n
+0000492883 00000 n
+0000492947 00000 n
+0000493011 00000 n
+0000493075 00000 n
+0000493140 00000 n
+0000493204 00000 n
+0000493399 00000 n
+0000493463 00000 n
+0000493529 00000 n
+0000493595 00000 n
+0000493661 00000 n
+0000493727 00000 n
+0000493793 00000 n
+0000493857 00000 n
+0000493923 00000 n
+0000493989 00000 n
+0000494053 00000 n
+0000494117 00000 n
+0000498819 00000 n
+0000496878 00000 n
+0000494310 00000 n
+0000497004 00000 n
+0000497197 00000 n
+0000497261 00000 n
+0000497327 00000 n
+0000497393 00000 n
+0000497459 00000 n
+0000497524 00000 n
+0000497589 00000 n
+0000497655 00000 n
+0000497719 00000 n
+0000497785 00000 n
+0000497851 00000 n
+0000497917 00000 n
+0000497982 00000 n
+0000498046 00000 n
+0000498110 00000 n
+0000498303 00000 n
+0000498367 00000 n
+0000498431 00000 n
+0000498626 00000 n
+0000498689 00000 n
+0000503255 00000 n
+0000501042 00000 n
+0000498949 00000 n
+0000501519 00000 n
+0000501648 00000 n
+0000501712 00000 n
+0000501778 00000 n
+0000501844 00000 n
+0000501907 00000 n
+0000502099 00000 n
+0000501198 00000 n
+0000501364 00000 n
+0000502163 00000 n
+0000502227 00000 n
+0000502293 00000 n
+0000502357 00000 n
+0000502421 00000 n
+0000502485 00000 n
+0000502549 00000 n
+0000502613 00000 n
+0000502677 00000 n
+0000502741 00000 n
+0000502805 00000 n
+0000502869 00000 n
+0000502933 00000 n
+0000502998 00000 n
+0000503061 00000 n
+0000503126 00000 n
+0000503191 00000 n
+0000927872 00000 n
+0000508508 00000 n
+0000505796 00000 n
+0000503399 00000 n
+0000506444 00000 n
+0000506637 00000 n
+0000505961 00000 n
+0000506134 00000 n
+0000506701 00000 n
+0000506764 00000 n
+0000506827 00000 n
+0000506893 00000 n
+0000506958 00000 n
+0000507022 00000 n
+0000507088 00000 n
+0000507152 00000 n
+0000507347 00000 n
+0000507540 00000 n
+0000507733 00000 n
+0000507797 00000 n
+0000507992 00000 n
+0000508056 00000 n
+0000508120 00000 n
+0000508185 00000 n
+0000508251 00000 n
+0000508315 00000 n
+0000508380 00000 n
+0000506286 00000 n
+0000508444 00000 n
+0000919467 00000 n
+0000513241 00000 n
+0000511190 00000 n
+0000508638 00000 n
+0000511497 00000 n
+0000511561 00000 n
+0000511625 00000 n
+0000511691 00000 n
+0000511757 00000 n
+0000511822 00000 n
+0000511888 00000 n
+0000511954 00000 n
+0000512020 00000 n
+0000512086 00000 n
+0000512150 00000 n
+0000512214 00000 n
+0000939617 00000 n
+0000937121 00000 n
+0000939448 00000 n
+0000512278 00000 n
+0000512342 00000 n
+0000512405 00000 n
+0000512469 00000 n
+0000512663 00000 n
+0000512727 00000 n
+0000512790 00000 n
+0000513113 00000 n
+0000511337 00000 n
+0000516576 00000 n
+0000515158 00000 n
+0000513385 00000 n
+0000515284 00000 n
+0000515413 00000 n
+0000515477 00000 n
+0000515540 00000 n
+0000515605 00000 n
+0000515670 00000 n
+0000515736 00000 n
+0000515931 00000 n
+0000516060 00000 n
+0000516124 00000 n
+0000516187 00000 n
+0000516316 00000 n
+0000516380 00000 n
+0000516446 00000 n
+0000516512 00000 n
+0001088533 00000 n
+0000520767 00000 n
+0000518495 00000 n
+0000516720 00000 n
+0000518621 00000 n
+0000518750 00000 n
+0000518814 00000 n
+0000518878 00000 n
+0000518943 00000 n
+0000519009 00000 n
+0000519073 00000 n
+0000519137 00000 n
+0000519203 00000 n
+0000519269 00000 n
+0000519333 00000 n
+0000519399 00000 n
+0000519463 00000 n
+0000519527 00000 n
+0000519592 00000 n
+0000519786 00000 n
+0000519850 00000 n
+0000519914 00000 n
+0000519980 00000 n
+0000520046 00000 n
+0000520112 00000 n
+0000520178 00000 n
+0000520243 00000 n
+0000520309 00000 n
+0000520375 00000 n
+0000520441 00000 n
+0000520507 00000 n
+0000520573 00000 n
+0000520637 00000 n
+0000525807 00000 n
+0000523543 00000 n
+0000520939 00000 n
+0000523865 00000 n
+0000523929 00000 n
+0000524058 00000 n
+0000524121 00000 n
+0000524186 00000 n
+0000524252 00000 n
+0000524447 00000 n
+0000524640 00000 n
+0000524703 00000 n
+0000524769 00000 n
+0000524833 00000 n
+0000524897 00000 n
+0000524961 00000 n
+0000525026 00000 n
+0000525220 00000 n
+0000525284 00000 n
+0000525350 00000 n
+0000523690 00000 n
+0000525416 00000 n
+0000525482 00000 n
+0000525546 00000 n
+0000525612 00000 n
+0000525677 00000 n
+0000531319 00000 n
+0000528677 00000 n
+0000525951 00000 n
+0000529316 00000 n
+0000529380 00000 n
+0000529509 00000 n
+0000529573 00000 n
+0000529637 00000 n
+0000529703 00000 n
+0000529769 00000 n
+0000529835 00000 n
+0000530028 00000 n
+0000530092 00000 n
+0000530156 00000 n
+0000530220 00000 n
+0000530284 00000 n
+0000530480 00000 n
+0000530544 00000 n
+0000530608 00000 n
+0000530672 00000 n
+0000530737 00000 n
+0000530803 00000 n
+0000530867 00000 n
+0000528842 00000 n
+0000530932 00000 n
+0000530996 00000 n
+0000529001 00000 n
+0000529159 00000 n
+0000531061 00000 n
+0000531125 00000 n
+0000531190 00000 n
+0000531256 00000 n
+0000536819 00000 n
+0000534000 00000 n
+0000531463 00000 n
+0000534819 00000 n
+0000534883 00000 n
+0000534949 00000 n
+0000535013 00000 n
+0000535077 00000 n
+0000535141 00000 n
+0000535207 00000 n
+0000535273 00000 n
+0000535337 00000 n
+0000535401 00000 n
+0000535467 00000 n
+0000535662 00000 n
+0000535726 00000 n
+0000535790 00000 n
+0000534174 00000 n
+0000535853 00000 n
+0000535917 00000 n
+0000535982 00000 n
+0000536048 00000 n
+0000534332 00000 n
+0000536112 00000 n
+0000536176 00000 n
+0000536241 00000 n
+0000534495 00000 n
+0000536305 00000 n
+0000536369 00000 n
+0000536434 00000 n
+0000536627 00000 n
+0000534656 00000 n
+0000536691 00000 n
+0000541342 00000 n
+0000539006 00000 n
+0000536949 00000 n
+0000539132 00000 n
+0000539196 00000 n
+0000539325 00000 n
+0000539389 00000 n
+0000539452 00000 n
+0000539516 00000 n
+0000539582 00000 n
+0000539648 00000 n
+0000539714 00000 n
+0000539780 00000 n
+0000539844 00000 n
+0000539908 00000 n
+0000539974 00000 n
+0000540040 00000 n
+0000540235 00000 n
+0000540299 00000 n
+0000540364 00000 n
+0000540428 00000 n
+0000540494 00000 n
+0000540558 00000 n
+0000540624 00000 n
+0000540690 00000 n
+0000540754 00000 n
+0000540820 00000 n
+0000540884 00000 n
+0000540950 00000 n
+0000541016 00000 n
+0000541082 00000 n
+0000541148 00000 n
+0000541214 00000 n
+0000541280 00000 n
+0000546567 00000 n
+0000544021 00000 n
+0000541500 00000 n
+0000544496 00000 n
+0000544560 00000 n
+0000544626 00000 n
+0000544821 00000 n
+0000544885 00000 n
+0000544949 00000 n
+0000545015 00000 n
+0000545079 00000 n
+0000545143 00000 n
+0000545209 00000 n
+0000545275 00000 n
+0000545338 00000 n
+0000545404 00000 n
+0000545469 00000 n
+0000545533 00000 n
+0000545597 00000 n
+0000545663 00000 n
+0000544177 00000 n
+0000545856 00000 n
+0000545920 00000 n
+0000545984 00000 n
+0000546048 00000 n
+0000546112 00000 n
+0000546177 00000 n
+0000546243 00000 n
+0000546307 00000 n
+0000544336 00000 n
+0000546372 00000 n
+0000546436 00000 n
+0000546501 00000 n
+0001088658 00000 n
+0000550775 00000 n
+0000548809 00000 n
+0000546739 00000 n
+0000549282 00000 n
+0000549346 00000 n
+0000549410 00000 n
+0000549476 00000 n
+0000549542 00000 n
+0000549606 00000 n
+0000549672 00000 n
+0000549738 00000 n
+0000549802 00000 n
+0000549868 00000 n
+0000549932 00000 n
+0000549997 00000 n
+0000550062 00000 n
+0000550126 00000 n
+0000548965 00000 n
+0000550191 00000 n
+0000550384 00000 n
+0000550448 00000 n
+0000549124 00000 n
+0000550512 00000 n
+0000550577 00000 n
+0000550643 00000 n
+0000550709 00000 n
+0000555622 00000 n
+0000552853 00000 n
+0000550919 00000 n
+0000552979 00000 n
+0000553299 00000 n
+0000553491 00000 n
+0000553555 00000 n
+0000553619 00000 n
+0000553683 00000 n
+0000553747 00000 n
+0000553811 00000 n
+0000553876 00000 n
+0000553940 00000 n
+0000554004 00000 n
+0000554069 00000 n
+0000554133 00000 n
+0000554199 00000 n
+0000554265 00000 n
+0000554331 00000 n
+0000554395 00000 n
+0000554459 00000 n
+0000554524 00000 n
+0000554588 00000 n
+0000554653 00000 n
+0000554719 00000 n
+0000554783 00000 n
+0000554847 00000 n
+0000554911 00000 n
+0000554976 00000 n
+0000555040 00000 n
+0000555105 00000 n
+0000555170 00000 n
+0000555236 00000 n
+0000555300 00000 n
+0000555364 00000 n
+0000555429 00000 n
+0000555493 00000 n
+0000555557 00000 n
+0000561157 00000 n
+0000558509 00000 n
+0000555738 00000 n
+0000558635 00000 n
+0000558699 00000 n
+0000558765 00000 n
+0000558831 00000 n
+0000558896 00000 n
+0000558960 00000 n
+0000559024 00000 n
+0000559089 00000 n
+0000559153 00000 n
+0000559218 00000 n
+0000559283 00000 n
+0000559348 00000 n
+0000559412 00000 n
+0000559475 00000 n
+0000559540 00000 n
+0000559606 00000 n
+0000559670 00000 n
+0000559734 00000 n
+0000559800 00000 n
+0000559864 00000 n
+0000559928 00000 n
+0000559992 00000 n
+0000560056 00000 n
+0000560120 00000 n
+0000560186 00000 n
+0000560252 00000 n
+0000560316 00000 n
+0000560380 00000 n
+0000560445 00000 n
+0000560511 00000 n
+0000560576 00000 n
+0000560642 00000 n
+0000560705 00000 n
+0000560769 00000 n
+0000560834 00000 n
+0000560900 00000 n
+0000560965 00000 n
+0000561029 00000 n
+0000561093 00000 n
+0000565415 00000 n
+0000563488 00000 n
+0000561301 00000 n
+0000563614 00000 n
+0000563807 00000 n
+0000563999 00000 n
+0000564063 00000 n
+0000564127 00000 n
+0000564191 00000 n
+0000564255 00000 n
+0000564320 00000 n
+0000564386 00000 n
+0000564450 00000 n
+0000564514 00000 n
+0000564579 00000 n
+0000564774 00000 n
+0000564967 00000 n
+0000565031 00000 n
+0000565095 00000 n
+0000565159 00000 n
+0000565223 00000 n
+0000565288 00000 n
+0000565352 00000 n
+0000569320 00000 n
+0000567652 00000 n
+0000565531 00000 n
+0000567778 00000 n
+0000567842 00000 n
+0000567906 00000 n
+0000567970 00000 n
+0000568034 00000 n
+0000568228 00000 n
+0000568292 00000 n
+0000568355 00000 n
+0000568417 00000 n
+0000568481 00000 n
+0000568546 00000 n
+0000568610 00000 n
+0000568674 00000 n
+0000568867 00000 n
+0000568931 00000 n
+0000569126 00000 n
+0000569190 00000 n
+0000574840 00000 n
+0000572551 00000 n
+0000569450 00000 n
+0000573358 00000 n
+0000573487 00000 n
+0000572725 00000 n
+0000573551 00000 n
+0000573615 00000 n
+0000573679 00000 n
+0000573743 00000 n
+0000573808 00000 n
+0000573872 00000 n
+0000573936 00000 n
+0000572878 00000 n
+0000574001 00000 n
+0000574065 00000 n
+0000574129 00000 n
+0000573037 00000 n
+0000574323 00000 n
+0000574386 00000 n
+0000574450 00000 n
+0000574514 00000 n
+0000574580 00000 n
+0000573198 00000 n
+0000574646 00000 n
+0000574712 00000 n
+0000574776 00000 n
+0001088783 00000 n
+0000919920 00000 n
+0000579722 00000 n
+0000576948 00000 n
+0000574984 00000 n
+0000577074 00000 n
+0000577138 00000 n
+0000577202 00000 n
+0000577266 00000 n
+0000577331 00000 n
+0000577397 00000 n
+0000577463 00000 n
+0000577529 00000 n
+0000577593 00000 n
+0000577656 00000 n
+0000577721 00000 n
+0000577785 00000 n
+0000577851 00000 n
+0000577917 00000 n
+0000577981 00000 n
+0000578045 00000 n
+0000578109 00000 n
+0000578173 00000 n
+0000578239 00000 n
+0000578304 00000 n
+0000578369 00000 n
+0000578433 00000 n
+0000578497 00000 n
+0000578562 00000 n
+0000578626 00000 n
+0000578692 00000 n
+0000578755 00000 n
+0000578819 00000 n
+0000578884 00000 n
+0000578948 00000 n
+0000579014 00000 n
+0000579076 00000 n
+0000579140 00000 n
+0000579205 00000 n
+0000579269 00000 n
+0000579335 00000 n
+0000579399 00000 n
+0000579463 00000 n
+0000579528 00000 n
+0000579592 00000 n
+0000584592 00000 n
+0000582040 00000 n
+0000579852 00000 n
+0000582340 00000 n
+0000582469 00000 n
+0000582533 00000 n
+0000582596 00000 n
+0000582187 00000 n
+0000582660 00000 n
+0000582724 00000 n
+0000582788 00000 n
+0000582852 00000 n
+0000582916 00000 n
+0000582981 00000 n
+0000583047 00000 n
+0000583113 00000 n
+0000583178 00000 n
+0000583242 00000 n
+0000583306 00000 n
+0000583371 00000 n
+0000583435 00000 n
+0000583499 00000 n
+0000583564 00000 n
+0000583628 00000 n
+0000583692 00000 n
+0000583757 00000 n
+0000583821 00000 n
+0000584014 00000 n
+0000584078 00000 n
+0000584142 00000 n
+0000584206 00000 n
+0000584270 00000 n
+0000584335 00000 n
+0000584401 00000 n
+0000584463 00000 n
+0000584527 00000 n
+0000588704 00000 n
+0000587104 00000 n
+0000584722 00000 n
+0000587230 00000 n
+0000587294 00000 n
+0000587358 00000 n
+0000587422 00000 n
+0000587487 00000 n
+0000587551 00000 n
+0000587615 00000 n
+0000587808 00000 n
+0000588001 00000 n
+0000588193 00000 n
+0000588257 00000 n
+0000588321 00000 n
+0000588385 00000 n
+0000588449 00000 n
+0000588641 00000 n
+0000592891 00000 n
+0000591155 00000 n
+0000588820 00000 n
+0000591281 00000 n
+0000591345 00000 n
+0000591666 00000 n
+0000591730 00000 n
+0000591794 00000 n
+0000592114 00000 n
+0000592178 00000 n
+0000592244 00000 n
+0000592310 00000 n
+0000592374 00000 n
+0000592440 00000 n
+0000592633 00000 n
+0000592762 00000 n
+0000592826 00000 n
+0000598734 00000 n
+0000595782 00000 n
+0000593021 00000 n
+0000596087 00000 n
+0000596151 00000 n
+0000596217 00000 n
+0000595929 00000 n
+0000596346 00000 n
+0000596410 00000 n
+0000596476 00000 n
+0000596540 00000 n
+0000596604 00000 n
+0000596669 00000 n
+0000596733 00000 n
+0000596798 00000 n
+0000596862 00000 n
+0000596926 00000 n
+0000596992 00000 n
+0000597187 00000 n
+0000597251 00000 n
+0000597315 00000 n
+0000597378 00000 n
+0000597443 00000 n
+0000597508 00000 n
+0000597574 00000 n
+0000597638 00000 n
+0000597703 00000 n
+0000597768 00000 n
+0000597832 00000 n
+0000597897 00000 n
+0000597962 00000 n
+0000598026 00000 n
+0000598091 00000 n
+0000598156 00000 n
+0000598220 00000 n
+0000598284 00000 n
+0000598348 00000 n
+0000598411 00000 n
+0000598476 00000 n
+0000598540 00000 n
+0000598604 00000 n
+0000598669 00000 n
+0000605314 00000 n
+0000602162 00000 n
+0000598864 00000 n
+0000602467 00000 n
+0000602531 00000 n
+0000602595 00000 n
+0000602661 00000 n
+0000602726 00000 n
+0000602791 00000 n
+0000602856 00000 n
+0000602920 00000 n
+0000602985 00000 n
+0000603050 00000 n
+0000603114 00000 n
+0000603179 00000 n
+0000603244 00000 n
+0000603308 00000 n
+0000603373 00000 n
+0000603438 00000 n
+0000603502 00000 n
+0000603567 00000 n
+0000603632 00000 n
+0000603695 00000 n
+0000603760 00000 n
+0000603825 00000 n
+0000603889 00000 n
+0000603954 00000 n
+0000604019 00000 n
+0000604083 00000 n
+0000604148 00000 n
+0000604213 00000 n
+0000604279 00000 n
+0000604343 00000 n
+0000604408 00000 n
+0000604473 00000 n
+0000604538 00000 n
+0000604733 00000 n
+0000604796 00000 n
+0000602309 00000 n
+0000604990 00000 n
+0000605054 00000 n
+0000605120 00000 n
+0000605184 00000 n
+0000605248 00000 n
+0001088908 00000 n
+0000609947 00000 n
+0000607926 00000 n
+0000605472 00000 n
+0000608405 00000 n
+0000608469 00000 n
+0000608533 00000 n
+0000608597 00000 n
+0000608661 00000 n
+0000608856 00000 n
+0000608920 00000 n
+0000608983 00000 n
+0000609046 00000 n
+0000609110 00000 n
+0000609176 00000 n
+0000609368 00000 n
+0000608082 00000 n
+0000608242 00000 n
+0000609432 00000 n
+0000609497 00000 n
+0000609561 00000 n
+0000609627 00000 n
+0000609691 00000 n
+0000609755 00000 n
+0000609818 00000 n
+0000609882 00000 n
+0000928194 00000 n
+0000615272 00000 n
+0000612024 00000 n
+0000610063 00000 n
+0000612506 00000 n
+0000612570 00000 n
+0000612634 00000 n
+0000612698 00000 n
+0000612763 00000 n
+0000612827 00000 n
+0000612892 00000 n
+0000612956 00000 n
+0000613020 00000 n
+0000613085 00000 n
+0000613149 00000 n
+0000613213 00000 n
+0000613278 00000 n
+0000613341 00000 n
+0000613405 00000 n
+0000613470 00000 n
+0000613534 00000 n
+0000613596 00000 n
+0000613661 00000 n
+0000613725 00000 n
+0000613789 00000 n
+0000613852 00000 n
+0000613916 00000 n
+0000613980 00000 n
+0000614045 00000 n
+0000612180 00000 n
+0000614237 00000 n
+0000614301 00000 n
+0000614365 00000 n
+0000614429 00000 n
+0000614494 00000 n
+0000614560 00000 n
+0000614626 00000 n
+0000614690 00000 n
+0000614755 00000 n
+0000614821 00000 n
+0000614885 00000 n
+0000614950 00000 n
+0000615014 00000 n
+0000612348 00000 n
+0000615208 00000 n
+0000621073 00000 n
+0000618220 00000 n
+0000615374 00000 n
+0000619194 00000 n
+0000619387 00000 n
+0000619451 00000 n
+0000618403 00000 n
+0000619515 00000 n
+0000618561 00000 n
+0000619579 00000 n
+0000618716 00000 n
+0000619772 00000 n
+0000619836 00000 n
+0000619902 00000 n
+0000619968 00000 n
+0000618874 00000 n
+0000620032 00000 n
+0000620095 00000 n
+0000619026 00000 n
+0000620159 00000 n
+0000620223 00000 n
+0000620287 00000 n
+0000620351 00000 n
+0000620417 00000 n
+0000620483 00000 n
+0000620549 00000 n
+0000620613 00000 n
+0000620679 00000 n
+0000620745 00000 n
+0000620811 00000 n
+0000620877 00000 n
+0000620943 00000 n
+0000621007 00000 n
+0000625089 00000 n
+0000623422 00000 n
+0000621189 00000 n
+0000623548 00000 n
+0000623612 00000 n
+0000623676 00000 n
+0000623742 00000 n
+0000623806 00000 n
+0000623871 00000 n
+0000623935 00000 n
+0000624000 00000 n
+0000624064 00000 n
+0000624127 00000 n
+0000624321 00000 n
+0000624385 00000 n
+0000624449 00000 n
+0000624513 00000 n
+0000624577 00000 n
+0000624641 00000 n
+0000624705 00000 n
+0000624769 00000 n
+0000624833 00000 n
+0000624897 00000 n
+0000624961 00000 n
+0000625025 00000 n
+0000628258 00000 n
+0000626851 00000 n
+0000625205 00000 n
+0000626977 00000 n
+0000627041 00000 n
+0000627105 00000 n
+0000627168 00000 n
+0000627232 00000 n
+0000627296 00000 n
+0000627359 00000 n
+0000627423 00000 n
+0000627488 00000 n
+0000627552 00000 n
+0000627617 00000 n
+0000627681 00000 n
+0000627746 00000 n
+0000627810 00000 n
+0000627874 00000 n
+0000627938 00000 n
+0000628002 00000 n
+0000628066 00000 n
+0000628130 00000 n
+0000628194 00000 n
+0000633457 00000 n
+0000630573 00000 n
+0000628360 00000 n
+0000630873 00000 n
+0000630937 00000 n
+0000630720 00000 n
+0000631129 00000 n
+0000631193 00000 n
+0000631257 00000 n
+0000631323 00000 n
+0000631387 00000 n
+0000631451 00000 n
+0000631515 00000 n
+0000631580 00000 n
+0000631646 00000 n
+0000631711 00000 n
+0000631775 00000 n
+0000631840 00000 n
+0000631906 00000 n
+0000631970 00000 n
+0000632035 00000 n
+0000632101 00000 n
+0000632167 00000 n
+0000632233 00000 n
+0000632299 00000 n
+0000632365 00000 n
+0000632431 00000 n
+0000632625 00000 n
+0000632689 00000 n
+0000632753 00000 n
+0000632817 00000 n
+0000632881 00000 n
+0000632946 00000 n
+0000633010 00000 n
+0000633075 00000 n
+0000633139 00000 n
+0000633329 00000 n
+0000633393 00000 n
+0001089033 00000 n
+0000639015 00000 n
+0000635846 00000 n
+0000633573 00000 n
+0000635972 00000 n
+0000636036 00000 n
+0000636100 00000 n
+0000636164 00000 n
+0000636227 00000 n
+0000636290 00000 n
+0000636354 00000 n
+0000636419 00000 n
+0000636483 00000 n
+0000636548 00000 n
+0000636612 00000 n
+0000636806 00000 n
+0000636870 00000 n
+0000636936 00000 n
+0000637002 00000 n
+0000637196 00000 n
+0000637260 00000 n
+0000637326 00000 n
+0000637391 00000 n
+0000637455 00000 n
+0000637521 00000 n
+0000637585 00000 n
+0000637650 00000 n
+0000637716 00000 n
+0000637780 00000 n
+0000637846 00000 n
+0000637910 00000 n
+0000637976 00000 n
+0000638042 00000 n
+0000638108 00000 n
+0000638172 00000 n
+0000638237 00000 n
+0000638303 00000 n
+0000638367 00000 n
+0000638432 00000 n
+0000638498 00000 n
+0000638564 00000 n
+0000638628 00000 n
+0000638693 00000 n
+0000638757 00000 n
+0000638822 00000 n
+0000638886 00000 n
+0000644147 00000 n
+0000641112 00000 n
+0000639145 00000 n
+0000641238 00000 n
+0000641496 00000 n
+0000641560 00000 n
+0000641626 00000 n
+0000641690 00000 n
+0000641754 00000 n
+0000641818 00000 n
+0000641883 00000 n
+0000641947 00000 n
+0000642012 00000 n
+0000642078 00000 n
+0000642142 00000 n
+0000642206 00000 n
+0000642270 00000 n
+0000642335 00000 n
+0000642401 00000 n
+0000642465 00000 n
+0000642659 00000 n
+0000642723 00000 n
+0000642789 00000 n
+0000642854 00000 n
+0000642920 00000 n
+0000642986 00000 n
+0000643048 00000 n
+0000643114 00000 n
+0000643178 00000 n
+0000643244 00000 n
+0000643309 00000 n
+0000643502 00000 n
+0000643695 00000 n
+0000643759 00000 n
+0000643823 00000 n
+0000643889 00000 n
+0000643955 00000 n
+0000644018 00000 n
+0000644082 00000 n
+0000649657 00000 n
+0000646400 00000 n
+0000644263 00000 n
+0000646874 00000 n
+0000646938 00000 n
+0000647002 00000 n
+0000647067 00000 n
+0000647133 00000 n
+0000647197 00000 n
+0000647262 00000 n
+0000647326 00000 n
+0000647392 00000 n
+0000647456 00000 n
+0000647521 00000 n
+0000647586 00000 n
+0000647652 00000 n
+0000647847 00000 n
+0000647911 00000 n
+0000647977 00000 n
+0000648043 00000 n
+0000648107 00000 n
+0000648302 00000 n
+0000648366 00000 n
+0000648432 00000 n
+0000648497 00000 n
+0000648563 00000 n
+0000648755 00000 n
+0000646556 00000 n
+0000646716 00000 n
+0000648947 00000 n
+0000649011 00000 n
+0000649076 00000 n
+0000649204 00000 n
+0000649268 00000 n
+0000649397 00000 n
+0000649461 00000 n
+0000649527 00000 n
+0000649593 00000 n
+0000656085 00000 n
+0000652646 00000 n
+0000649787 00000 n
+0000652772 00000 n
+0000652901 00000 n
+0000652965 00000 n
+0000653029 00000 n
+0000653095 00000 n
+0000653159 00000 n
+0000653223 00000 n
+0000653289 00000 n
+0000653355 00000 n
+0000653420 00000 n
+0000653485 00000 n
+0000653549 00000 n
+0000653615 00000 n
+0000653679 00000 n
+0000653745 00000 n
+0000653811 00000 n
+0000653875 00000 n
+0000653941 00000 n
+0000654007 00000 n
+0000654073 00000 n
+0000654139 00000 n
+0000654205 00000 n
+0000654269 00000 n
+0000654335 00000 n
+0000654399 00000 n
+0000654463 00000 n
+0000654527 00000 n
+0000654590 00000 n
+0000654656 00000 n
+0000654722 00000 n
+0000654788 00000 n
+0000654854 00000 n
+0000654919 00000 n
+0000655048 00000 n
+0000655112 00000 n
+0000655176 00000 n
+0000655240 00000 n
+0000655369 00000 n
+0000655433 00000 n
+0000655499 00000 n
+0000655628 00000 n
+0000655692 00000 n
+0000655758 00000 n
+0000655824 00000 n
+0000655890 00000 n
+0000655956 00000 n
+0000656022 00000 n
+0000660787 00000 n
+0000658464 00000 n
+0000656187 00000 n
+0000658590 00000 n
+0000658719 00000 n
+0000658783 00000 n
+0000658847 00000 n
+0000658976 00000 n
+0000659040 00000 n
+0000659106 00000 n
+0000659172 00000 n
+0000659238 00000 n
+0000659366 00000 n
+0000659430 00000 n
+0000659558 00000 n
+0000659622 00000 n
+0000659686 00000 n
+0000659750 00000 n
+0000659816 00000 n
+0000659944 00000 n
+0000660008 00000 n
+0000660203 00000 n
+0000660266 00000 n
+0000660332 00000 n
+0000660396 00000 n
+0000660462 00000 n
+0000660526 00000 n
+0000660592 00000 n
+0000660658 00000 n
+0000666523 00000 n
+0000663813 00000 n
+0000660917 00000 n
+0000664464 00000 n
+0000664593 00000 n
+0000664657 00000 n
+0000663978 00000 n
+0000664721 00000 n
+0000664913 00000 n
+0000664977 00000 n
+0000665040 00000 n
+0000665104 00000 n
+0000665168 00000 n
+0000665363 00000 n
+0000665427 00000 n
+0000665491 00000 n
+0000665557 00000 n
+0000665621 00000 n
+0000665685 00000 n
+0000665749 00000 n
+0000665814 00000 n
+0000665878 00000 n
+0000665943 00000 n
+0000666008 00000 n
+0000666072 00000 n
+0000666137 00000 n
+0000666202 00000 n
+0000666268 00000 n
+0000664137 00000 n
+0000664300 00000 n
+0000666332 00000 n
+0000666395 00000 n
+0000666459 00000 n
+0001089158 00000 n
+0000671454 00000 n
+0000668981 00000 n
+0000666639 00000 n
+0000669459 00000 n
+0000669523 00000 n
+0000669587 00000 n
+0000669652 00000 n
+0000669137 00000 n
+0000669717 00000 n
+0000669781 00000 n
+0000669846 00000 n
+0000669911 00000 n
+0000669975 00000 n
+0000670040 00000 n
+0000670232 00000 n
+0000669295 00000 n
+0000670425 00000 n
+0000670489 00000 n
+0000670683 00000 n
+0000670745 00000 n
+0000670940 00000 n
+0000671004 00000 n
+0000671070 00000 n
+0000671262 00000 n
+0000671326 00000 n
+0000671390 00000 n
+0000676185 00000 n
+0000674130 00000 n
+0000671570 00000 n
+0000674256 00000 n
+0000674320 00000 n
+0000674385 00000 n
+0000674449 00000 n
+0000674514 00000 n
+0000674706 00000 n
+0000674770 00000 n
+0000674963 00000 n
+0000675027 00000 n
+0000675091 00000 n
+0000675155 00000 n
+0000675219 00000 n
+0000675284 00000 n
+0000675348 00000 n
+0000675413 00000 n
+0000675478 00000 n
+0000675542 00000 n
+0000675607 00000 n
+0000675672 00000 n
+0000675736 00000 n
+0000675801 00000 n
+0000675866 00000 n
+0000675928 00000 n
+0000676121 00000 n
+0000682286 00000 n
+0000679658 00000 n
+0000676287 00000 n
+0000680675 00000 n
+0000680739 00000 n
+0000680803 00000 n
+0000680869 00000 n
+0000680933 00000 n
+0000680997 00000 n
+0000681190 00000 n
+0000681254 00000 n
+0000681318 00000 n
+0000681382 00000 n
+0000681446 00000 n
+0000681511 00000 n
+0000681575 00000 n
+0000679841 00000 n
+0000680009 00000 n
+0000681640 00000 n
+0000681704 00000 n
+0000680173 00000 n
+0000681769 00000 n
+0000681833 00000 n
+0000680342 00000 n
+0000681897 00000 n
+0000681961 00000 n
+0000680505 00000 n
+0000682027 00000 n
+0000682091 00000 n
+0000682155 00000 n
+0000687576 00000 n
+0000685359 00000 n
+0000682402 00000 n
+0000685830 00000 n
+0000685959 00000 n
+0000686023 00000 n
+0000686087 00000 n
+0000686151 00000 n
+0000686216 00000 n
+0000686282 00000 n
+0000686348 00000 n
+0000686412 00000 n
+0000686477 00000 n
+0000686543 00000 n
+0000686607 00000 n
+0000686672 00000 n
+0000686736 00000 n
+0000686800 00000 n
+0000686865 00000 n
+0000686930 00000 n
+0000686995 00000 n
+0000687060 00000 n
+0000685515 00000 n
+0000687253 00000 n
+0000687317 00000 n
+0000687383 00000 n
+0000687448 00000 n
+0000687512 00000 n
+0000685673 00000 n
+0000693691 00000 n
+0000690341 00000 n
+0000687692 00000 n
+0000691313 00000 n
+0000691377 00000 n
+0000691443 00000 n
+0000691507 00000 n
+0000691571 00000 n
+0000691635 00000 n
+0000691699 00000 n
+0000691764 00000 n
+0000691828 00000 n
+0000691892 00000 n
+0000691956 00000 n
+0000692021 00000 n
+0000692085 00000 n
+0000692149 00000 n
+0000692213 00000 n
+0000692278 00000 n
+0000692342 00000 n
+0000692406 00000 n
+0000692470 00000 n
+0000692535 00000 n
+0000692599 00000 n
+0000692663 00000 n
+0000692727 00000 n
+0000690524 00000 n
+0000692792 00000 n
+0000692856 00000 n
+0000692920 00000 n
+0000692984 00000 n
+0000690682 00000 n
+0000693177 00000 n
+0000693240 00000 n
+0000693304 00000 n
+0000693368 00000 n
+0000690839 00000 n
+0000693433 00000 n
+0000693497 00000 n
+0000690996 00000 n
+0000693562 00000 n
+0000693626 00000 n
+0000691154 00000 n
+0000696734 00000 n
+0000695514 00000 n
+0000693793 00000 n
+0000695830 00000 n
+0000696023 00000 n
+0000695661 00000 n
+0000696216 00000 n
+0000696280 00000 n
+0000696344 00000 n
+0000696408 00000 n
+0000696474 00000 n
+0000696540 00000 n
+0000696604 00000 n
+0000696668 00000 n
+0001089283 00000 n
+0000702127 00000 n
+0000699451 00000 n
+0000696878 00000 n
+0000699927 00000 n
+0000700119 00000 n
+0000700183 00000 n
+0000700248 00000 n
+0000700569 00000 n
+0000700761 00000 n
+0000699607 00000 n
+0000700825 00000 n
+0000700891 00000 n
+0000700957 00000 n
+0000701023 00000 n
+0000699766 00000 n
+0000701089 00000 n
+0000701154 00000 n
+0000701219 00000 n
+0000701284 00000 n
+0000701349 00000 n
+0000701415 00000 n
+0000701481 00000 n
+0000701545 00000 n
+0000701609 00000 n
+0000701674 00000 n
+0000701739 00000 n
+0000701933 00000 n
+0000701997 00000 n
+0000922487 00000 n
+0000932016 00000 n
+0000709470 00000 n
+0000704452 00000 n
+0000702271 00000 n
+0000704929 00000 n
+0000705187 00000 n
+0000705251 00000 n
+0000705317 00000 n
+0000705381 00000 n
+0000704608 00000 n
+0000704770 00000 n
+0000705445 00000 n
+0000705510 00000 n
+0000705574 00000 n
+0000705639 00000 n
+0000705703 00000 n
+0000705767 00000 n
+0000705832 00000 n
+0000705897 00000 n
+0000705962 00000 n
+0000706028 00000 n
+0000706094 00000 n
+0000706159 00000 n
+0000706224 00000 n
+0000706289 00000 n
+0000706353 00000 n
+0000706417 00000 n
+0000706481 00000 n
+0000706546 00000 n
+0000706611 00000 n
+0000706676 00000 n
+0000706740 00000 n
+0000706804 00000 n
+0000706869 00000 n
+0000706933 00000 n
+0000706997 00000 n
+0000707062 00000 n
+0000707126 00000 n
+0000707192 00000 n
+0000707258 00000 n
+0000707324 00000 n
+0000707389 00000 n
+0000707454 00000 n
+0000707518 00000 n
+0000707582 00000 n
+0000707648 00000 n
+0000707712 00000 n
+0000707778 00000 n
+0000707844 00000 n
+0000707910 00000 n
+0000707976 00000 n
+0000708042 00000 n
+0000708108 00000 n
+0000708174 00000 n
+0000708240 00000 n
+0000708306 00000 n
+0000708371 00000 n
+0000708436 00000 n
+0000708500 00000 n
+0000708564 00000 n
+0000708630 00000 n
+0000708695 00000 n
+0000708760 00000 n
+0000708825 00000 n
+0000708889 00000 n
+0000708953 00000 n
+0000709018 00000 n
+0000709083 00000 n
+0000709148 00000 n
+0000709213 00000 n
+0000709277 00000 n
+0000709341 00000 n
+0000709406 00000 n
+0000912643 00000 n
+0000712373 00000 n
+0000711037 00000 n
+0000709614 00000 n
+0000711335 00000 n
+0000711399 00000 n
+0000711465 00000 n
+0000711531 00000 n
+0000711597 00000 n
+0000711661 00000 n
+0000711184 00000 n
+0000711982 00000 n
+0000712046 00000 n
+0000712112 00000 n
+0000712177 00000 n
+0000712243 00000 n
+0000712307 00000 n
+0000717106 00000 n
+0000714980 00000 n
+0000712503 00000 n
+0000715106 00000 n
+0000715427 00000 n
+0000715491 00000 n
+0000715557 00000 n
+0000715621 00000 n
+0000715815 00000 n
+0000715878 00000 n
+0000715941 00000 n
+0000716005 00000 n
+0000716069 00000 n
+0000716134 00000 n
+0000716200 00000 n
+0000716266 00000 n
+0000716332 00000 n
+0000716398 00000 n
+0000716462 00000 n
+0000716526 00000 n
+0000716591 00000 n
+0000716655 00000 n
+0000716720 00000 n
+0000716784 00000 n
+0000716848 00000 n
+0000716913 00000 n
+0000716977 00000 n
+0000717042 00000 n
+0000723848 00000 n
+0000720557 00000 n
+0000717236 00000 n
+0000720683 00000 n
+0000720747 00000 n
+0000720812 00000 n
+0000720878 00000 n
+0000720944 00000 n
+0000721137 00000 n
+0000721201 00000 n
+0000721265 00000 n
+0000721329 00000 n
+0000721393 00000 n
+0000721458 00000 n
+0000721523 00000 n
+0000721588 00000 n
+0000721652 00000 n
+0000721716 00000 n
+0000721781 00000 n
+0000721846 00000 n
+0000721910 00000 n
+0000721975 00000 n
+0000722040 00000 n
+0000722104 00000 n
+0000722169 00000 n
+0000722234 00000 n
+0000722298 00000 n
+0000722363 00000 n
+0000722428 00000 n
+0000722491 00000 n
+0000722556 00000 n
+0000722621 00000 n
+0000722685 00000 n
+0000722750 00000 n
+0000722815 00000 n
+0000722879 00000 n
+0000722944 00000 n
+0000723009 00000 n
+0000723073 00000 n
+0000723138 00000 n
+0000723203 00000 n
+0000723267 00000 n
+0000723332 00000 n
+0000723397 00000 n
+0000723461 00000 n
+0000723526 00000 n
+0000723591 00000 n
+0000723655 00000 n
+0000723720 00000 n
+0000723785 00000 n
+0000730479 00000 n
+0000729285 00000 n
+0000726426 00000 n
+0000723950 00000 n
+0000727085 00000 n
+0000727213 00000 n
+0000727278 00000 n
+0000727343 00000 n
+0000727406 00000 n
+0000727471 00000 n
+0000727536 00000 n
+0000727600 00000 n
+0000727665 00000 n
+0000727730 00000 n
+0000727794 00000 n
+0000727859 00000 n
+0000727924 00000 n
+0000727990 00000 n
+0000728056 00000 n
+0000728120 00000 n
+0000728184 00000 n
+0000728249 00000 n
+0000728314 00000 n
+0000728378 00000 n
+0000728443 00000 n
+0000728508 00000 n
+0000728572 00000 n
+0000728637 00000 n
+0000728702 00000 n
+0000728766 00000 n
+0000728831 00000 n
+0000729025 00000 n
+0000726591 00000 n
+0000726758 00000 n
+0000726923 00000 n
+0000729089 00000 n
+0000729155 00000 n
+0001089408 00000 n
+0000755357 00000 n
+0000730353 00000 n
+0000729387 00000 n
+0000754907 00000 n
+0000754971 00000 n
+0000755100 00000 n
+0000755164 00000 n
+0000755229 00000 n
+0000755293 00000 n
+0000760315 00000 n
+0000757750 00000 n
+0000755510 00000 n
+0000758058 00000 n
+0000757897 00000 n
+0000758251 00000 n
+0000758315 00000 n
+0000758379 00000 n
+0000758443 00000 n
+0000758509 00000 n
+0000758574 00000 n
+0000758639 00000 n
+0000758703 00000 n
+0000758767 00000 n
+0000758830 00000 n
+0000758895 00000 n
+0000758960 00000 n
+0000759024 00000 n
+0000759089 00000 n
+0000759154 00000 n
+0000759218 00000 n
+0000759283 00000 n
+0000759477 00000 n
+0000759541 00000 n
+0000759734 00000 n
+0000759798 00000 n
+0000759863 00000 n
+0000759927 00000 n
+0000759993 00000 n
+0000760056 00000 n
+0000760122 00000 n
+0000760186 00000 n
+0000760252 00000 n
+0000764465 00000 n
+0000762534 00000 n
+0000760417 00000 n
+0000762660 00000 n
+0000762724 00000 n
+0000762790 00000 n
+0000762854 00000 n
+0000762920 00000 n
+0000763111 00000 n
+0000763175 00000 n
+0000763241 00000 n
+0000763305 00000 n
+0000763369 00000 n
+0000763433 00000 n
+0000763626 00000 n
+0000763690 00000 n
+0000763756 00000 n
+0000763822 00000 n
+0000763886 00000 n
+0000764080 00000 n
+0000764273 00000 n
+0000764337 00000 n
+0000764401 00000 n
+0000769937 00000 n
+0000768012 00000 n
+0000764581 00000 n
+0000768322 00000 n
+0000768386 00000 n
+0000768450 00000 n
+0000768514 00000 n
+0000768706 00000 n
+0000768770 00000 n
+0000768159 00000 n
+0000768835 00000 n
+0000768899 00000 n
+0000768965 00000 n
+0000769030 00000 n
+0000769354 00000 n
+0000769418 00000 n
+0000769484 00000 n
+0000769548 00000 n
+0000769612 00000 n
+0000769676 00000 n
+0000769741 00000 n
+0000769807 00000 n
+0000769873 00000 n
+0000774991 00000 n
+0000773251 00000 n
+0000770039 00000 n
+0000773377 00000 n
+0000773441 00000 n
+0000773506 00000 n
+0000773571 00000 n
+0000773634 00000 n
+0000773699 00000 n
+0000773764 00000 n
+0000773828 00000 n
+0000773893 00000 n
+0000773959 00000 n
+0000774023 00000 n
+0000774087 00000 n
+0000774152 00000 n
+0000774217 00000 n
+0000774281 00000 n
+0000774346 00000 n
+0000774410 00000 n
+0000774474 00000 n
+0000774540 00000 n
+0000774604 00000 n
+0000774796 00000 n
+0000774860 00000 n
+0000774926 00000 n
+0000779250 00000 n
+0000777647 00000 n
+0000775107 00000 n
+0000777773 00000 n
+0000777966 00000 n
+0000778030 00000 n
+0000778093 00000 n
+0000778157 00000 n
+0000778221 00000 n
+0000778284 00000 n
+0000778350 00000 n
+0000778415 00000 n
+0000778481 00000 n
+0000778545 00000 n
+0000778739 00000 n
+0000778802 00000 n
+0000778866 00000 n
+0000778930 00000 n
+0000779123 00000 n
+0001089533 00000 n
+0000783019 00000 n
+0000781353 00000 n
+0000779366 00000 n
+0000781479 00000 n
+0000781543 00000 n
+0000781672 00000 n
+0000781865 00000 n
+0000782055 00000 n
+0000782248 00000 n
+0000782441 00000 n
+0000782505 00000 n
+0000782698 00000 n
+0000782891 00000 n
+0000786547 00000 n
+0000785134 00000 n
+0000783121 00000 n
+0000785260 00000 n
+0000785324 00000 n
+0000785453 00000 n
+0000785517 00000 n
+0000785583 00000 n
+0000785647 00000 n
+0000785840 00000 n
+0000785904 00000 n
+0000786097 00000 n
+0000786290 00000 n
+0000786353 00000 n
+0000786419 00000 n
+0000792240 00000 n
+0000789474 00000 n
+0000786649 00000 n
+0000789600 00000 n
+0000789729 00000 n
+0000789793 00000 n
+0000789859 00000 n
+0000789925 00000 n
+0000789990 00000 n
+0000790183 00000 n
+0000790375 00000 n
+0000790438 00000 n
+0000790501 00000 n
+0000790565 00000 n
+0000790630 00000 n
+0000790694 00000 n
+0000790758 00000 n
+0000790822 00000 n
+0000790887 00000 n
+0000790951 00000 n
+0000791016 00000 n
+0000791080 00000 n
+0000791145 00000 n
+0000791208 00000 n
+0000791273 00000 n
+0000791337 00000 n
+0000791402 00000 n
+0000791466 00000 n
+0000791531 00000 n
+0000791595 00000 n
+0000791660 00000 n
+0000791724 00000 n
+0000791918 00000 n
+0000791982 00000 n
+0000792045 00000 n
+0000792110 00000 n
+0000792176 00000 n
+0000797925 00000 n
+0000795406 00000 n
+0000792356 00000 n
+0000795532 00000 n
+0000795596 00000 n
+0000795660 00000 n
+0000795726 00000 n
+0000795790 00000 n
+0000795856 00000 n
+0000795922 00000 n
+0000795986 00000 n
+0000796052 00000 n
+0000796116 00000 n
+0000796180 00000 n
+0000796244 00000 n
+0000796309 00000 n
+0000796372 00000 n
+0000796437 00000 n
+0000796503 00000 n
+0000796567 00000 n
+0000796632 00000 n
+0000796698 00000 n
+0000796761 00000 n
+0000796826 00000 n
+0000796891 00000 n
+0000796955 00000 n
+0000797019 00000 n
+0000797084 00000 n
+0000797148 00000 n
+0000797212 00000 n
+0000797276 00000 n
+0000797342 00000 n
+0000797407 00000 n
+0000797473 00000 n
+0000797537 00000 n
+0000797601 00000 n
+0000797667 00000 n
+0000797731 00000 n
+0000797797 00000 n
+0000797861 00000 n
+0000802848 00000 n
+0000799904 00000 n
+0000798041 00000 n
+0000800204 00000 n
+0000800268 00000 n
+0000800332 00000 n
+0000800396 00000 n
+0000800462 00000 n
+0000800528 00000 n
+0000800723 00000 n
+0000800051 00000 n
+0000800916 00000 n
+0000800980 00000 n
+0000801046 00000 n
+0000801241 00000 n
+0000801305 00000 n
+0000801369 00000 n
+0000801435 00000 n
+0000801499 00000 n
+0000801563 00000 n
+0000801627 00000 n
+0000801692 00000 n
+0000801756 00000 n
+0000801820 00000 n
+0000801885 00000 n
+0000801949 00000 n
+0000802013 00000 n
+0000802078 00000 n
+0000802142 00000 n
+0000802206 00000 n
+0000802271 00000 n
+0000802335 00000 n
+0000802399 00000 n
+0000802464 00000 n
+0000802526 00000 n
+0000802590 00000 n
+0000802655 00000 n
+0000802719 00000 n
+0000802783 00000 n
+0000807143 00000 n
+0000804839 00000 n
+0000802950 00000 n
+0000805157 00000 n
+0000805285 00000 n
+0000805349 00000 n
+0000805413 00000 n
+0000805478 00000 n
+0000805542 00000 n
+0000805607 00000 n
+0000805671 00000 n
+0000805735 00000 n
+0000805800 00000 n
+0000805864 00000 n
+0000805928 00000 n
+0000805993 00000 n
+0000806056 00000 n
+0000806120 00000 n
+0000806185 00000 n
+0000806249 00000 n
+0000806313 00000 n
+0000806378 00000 n
+0000806440 00000 n
+0000804986 00000 n
+0000806632 00000 n
+0000806823 00000 n
+0000806887 00000 n
+0000806951 00000 n
+0000807015 00000 n
+0000807079 00000 n
+0001089658 00000 n
+0000811232 00000 n
+0000809952 00000 n
+0000807259 00000 n
+0000810078 00000 n
+0000810207 00000 n
+0000810271 00000 n
+0000810335 00000 n
+0000810399 00000 n
+0000810463 00000 n
+0000810527 00000 n
+0000810719 00000 n
+0000810783 00000 n
+0000810847 00000 n
+0000810911 00000 n
+0000810975 00000 n
+0000811168 00000 n
+0000814716 00000 n
+0000813501 00000 n
+0000811348 00000 n
+0000813627 00000 n
+0000813691 00000 n
+0000813755 00000 n
+0000813948 00000 n
+0000814012 00000 n
+0000814076 00000 n
+0000814140 00000 n
+0000814204 00000 n
+0000814268 00000 n
+0000814331 00000 n
+0000814395 00000 n
+0000814588 00000 n
+0000814652 00000 n
+0000819014 00000 n
+0000817421 00000 n
+0000814818 00000 n
+0000817918 00000 n
+0000817982 00000 n
+0000818048 00000 n
+0000818114 00000 n
+0000817577 00000 n
+0000818178 00000 n
+0000818243 00000 n
+0000817745 00000 n
+0000818436 00000 n
+0000818499 00000 n
+0000818563 00000 n
+0000818756 00000 n
+0000818820 00000 n
+0000818884 00000 n
+0000818948 00000 n
+0000823346 00000 n
+0000821961 00000 n
+0000819130 00000 n
+0000822260 00000 n
+0000822324 00000 n
+0000822388 00000 n
+0000822452 00000 n
+0000822515 00000 n
+0000822707 00000 n
+0000822771 00000 n
+0000822835 00000 n
+0000822108 00000 n
+0000822899 00000 n
+0000822961 00000 n
+0000823025 00000 n
+0000823089 00000 n
+0000823153 00000 n
+0000823217 00000 n
+0000824665 00000 n
+0000824219 00000 n
+0000823462 00000 n
+0000824345 00000 n
+0000824474 00000 n
+0000824538 00000 n
+0000824601 00000 n
+0000829435 00000 n
+0000826926 00000 n
+0000824781 00000 n
+0000827239 00000 n
+0000827560 00000 n
+0000827624 00000 n
+0000827688 00000 n
+0000827882 00000 n
+0000827946 00000 n
+0000828012 00000 n
+0000828076 00000 n
+0000828141 00000 n
+0000828207 00000 n
+0000828271 00000 n
+0000828336 00000 n
+0000828402 00000 n
+0000828467 00000 n
+0000828530 00000 n
+0000828722 00000 n
+0000828786 00000 n
+0000827073 00000 n
+0000828979 00000 n
+0000829043 00000 n
+0000829109 00000 n
+0000829175 00000 n
+0000829241 00000 n
+0000829305 00000 n
+0000829371 00000 n
+0001089783 00000 n
+0000834465 00000 n
+0000832390 00000 n
+0000829551 00000 n
+0000832516 00000 n
+0000832580 00000 n
+0000832646 00000 n
+0000832712 00000 n
+0000832907 00000 n
+0000832971 00000 n
+0000833035 00000 n
+0000833100 00000 n
+0000833166 00000 n
+0000833230 00000 n
+0000833294 00000 n
+0000833358 00000 n
+0000833422 00000 n
+0000833488 00000 n
+0000833552 00000 n
+0000833616 00000 n
+0000833682 00000 n
+0000833746 00000 n
+0000833810 00000 n
+0000833874 00000 n
+0000833938 00000 n
+0000834004 00000 n
+0000834070 00000 n
+0000834136 00000 n
+0000834201 00000 n
+0000834267 00000 n
+0000834333 00000 n
+0000834399 00000 n
+0000839791 00000 n
+0000837791 00000 n
+0000834623 00000 n
+0000837917 00000 n
+0000838110 00000 n
+0000838174 00000 n
+0000838238 00000 n
+0000838304 00000 n
+0000838368 00000 n
+0000838434 00000 n
+0000838498 00000 n
+0000838562 00000 n
+0000838626 00000 n
+0000838691 00000 n
+0000838886 00000 n
+0000838950 00000 n
+0000839016 00000 n
+0000839080 00000 n
+0000839146 00000 n
+0000839210 00000 n
+0000839276 00000 n
+0000839340 00000 n
+0000839404 00000 n
+0000839468 00000 n
+0000839532 00000 n
+0000839597 00000 n
+0000839661 00000 n
+0000839725 00000 n
+0000845577 00000 n
+0000843196 00000 n
+0000839921 00000 n
+0000843322 00000 n
+0000843386 00000 n
+0000843450 00000 n
+0000843516 00000 n
+0000843711 00000 n
+0000843775 00000 n
+0000843839 00000 n
+0000843903 00000 n
+0000843967 00000 n
+0000844031 00000 n
+0000844095 00000 n
+0000844159 00000 n
+0000844225 00000 n
+0000844289 00000 n
+0000844353 00000 n
+0000844417 00000 n
+0000844481 00000 n
+0000844547 00000 n
+0000844613 00000 n
+0000844679 00000 n
+0000844743 00000 n
+0000844807 00000 n
+0000844871 00000 n
+0000844935 00000 n
+0000844999 00000 n
+0000845063 00000 n
+0000845127 00000 n
+0000845190 00000 n
+0000845253 00000 n
+0000845318 00000 n
+0000845384 00000 n
+0000845448 00000 n
+0000845512 00000 n
+0000850034 00000 n
+0000848227 00000 n
+0000845707 00000 n
+0000848353 00000 n
+0000848417 00000 n
+0000848483 00000 n
+0000848547 00000 n
+0000848611 00000 n
+0000848677 00000 n
+0000848743 00000 n
+0000848809 00000 n
+0000848873 00000 n
+0000848939 00000 n
+0000849003 00000 n
+0000849068 00000 n
+0000849132 00000 n
+0000849196 00000 n
+0000849260 00000 n
+0000849453 00000 n
+0000849517 00000 n
+0000849710 00000 n
+0000849774 00000 n
+0000849840 00000 n
+0000849904 00000 n
+0000849968 00000 n
+0000853405 00000 n
+0000852309 00000 n
+0000850164 00000 n
+0000852435 00000 n
+0000852499 00000 n
+0000852565 00000 n
+0000852629 00000 n
+0000852694 00000 n
+0000852760 00000 n
+0000852826 00000 n
+0000852890 00000 n
+0000852955 00000 n
+0000853019 00000 n
+0000853083 00000 n
+0000853149 00000 n
+0000853213 00000 n
+0000853277 00000 n
+0000853341 00000 n
+0000855533 00000 n
+0000854825 00000 n
+0000853507 00000 n
+0000854951 00000 n
+0000855015 00000 n
+0000855079 00000 n
+0000855145 00000 n
+0000855210 00000 n
+0000855403 00000 n
+0000855467 00000 n
+0001089908 00000 n
+0000860589 00000 n
+0000858184 00000 n
+0000855663 00000 n
+0000858660 00000 n
+0000858852 00000 n
+0000859045 00000 n
+0000859109 00000 n
+0000859175 00000 n
+0000859241 00000 n
+0000858340 00000 n
+0000858500 00000 n
+0000859305 00000 n
+0000859370 00000 n
+0000859434 00000 n
+0000859500 00000 n
+0000859564 00000 n
+0000859759 00000 n
+0000859823 00000 n
+0000859888 00000 n
+0000859954 00000 n
+0000860017 00000 n
+0000860206 00000 n
+0000860270 00000 n
+0000860334 00000 n
+0000860398 00000 n
+0000860463 00000 n
+0000860526 00000 n
+0000864490 00000 n
+0000862558 00000 n
+0000860733 00000 n
+0000862684 00000 n
+0000862748 00000 n
+0000862942 00000 n
+0000863006 00000 n
+0000863069 00000 n
+0000863133 00000 n
+0000863199 00000 n
+0000863263 00000 n
+0000863327 00000 n
+0000863391 00000 n
+0000863584 00000 n
+0000863648 00000 n
+0000863714 00000 n
+0000863778 00000 n
+0000863842 00000 n
+0000863908 00000 n
+0000863973 00000 n
+0000864039 00000 n
+0000864103 00000 n
+0000864167 00000 n
+0000864361 00000 n
+0000864425 00000 n
+0000868793 00000 n
+0000866811 00000 n
+0000864634 00000 n
+0000867116 00000 n
+0000867180 00000 n
+0000867244 00000 n
+0000867310 00000 n
+0000867374 00000 n
+0000867438 00000 n
+0000867502 00000 n
+0000867566 00000 n
+0000867630 00000 n
+0000867696 00000 n
+0000867762 00000 n
+0000867826 00000 n
+0000867889 00000 n
+0000867953 00000 n
+0000868017 00000 n
+0000868082 00000 n
+0000868148 00000 n
+0000868212 00000 n
+0000868278 00000 n
+0000868473 00000 n
+0000868537 00000 n
+0000868601 00000 n
+0000868665 00000 n
+0000866958 00000 n
+0000870478 00000 n
+0000869903 00000 n
+0000868937 00000 n
+0000870029 00000 n
+0000870158 00000 n
+0000870222 00000 n
+0000870285 00000 n
+0000870350 00000 n
+0000870414 00000 n
+0000876810 00000 n
+0000873634 00000 n
+0000870594 00000 n
+0000873760 00000 n
+0000873951 00000 n
+0000874015 00000 n
+0000874210 00000 n
+0000874274 00000 n
+0000874340 00000 n
+0000874406 00000 n
+0000874472 00000 n
+0000874538 00000 n
+0000874602 00000 n
+0000874668 00000 n
+0000874732 00000 n
+0000874796 00000 n
+0000874860 00000 n
+0000874926 00000 n
+0000874990 00000 n
+0000875054 00000 n
+0000875120 00000 n
+0000875186 00000 n
+0000875252 00000 n
+0000875318 00000 n
+0000875384 00000 n
+0000875450 00000 n
+0000875514 00000 n
+0000875580 00000 n
+0000875644 00000 n
+0000875708 00000 n
+0000875774 00000 n
+0000875838 00000 n
+0000875903 00000 n
+0000875967 00000 n
+0000876033 00000 n
+0000876228 00000 n
+0000876292 00000 n
+0000876358 00000 n
+0000876424 00000 n
+0000876488 00000 n
+0000876553 00000 n
+0000876618 00000 n
+0000876682 00000 n
+0000876746 00000 n
+0000881003 00000 n
+0000878623 00000 n
+0000876954 00000 n
+0000878941 00000 n
+0000879262 00000 n
+0000879326 00000 n
+0000879390 00000 n
+0000879454 00000 n
+0000879518 00000 n
+0000879582 00000 n
+0000879646 00000 n
+0000879710 00000 n
+0000879774 00000 n
+0000879838 00000 n
+0000879903 00000 n
+0000879967 00000 n
+0000880033 00000 n
+0000880099 00000 n
+0000878770 00000 n
+0000880292 00000 n
+0000880355 00000 n
+0000880419 00000 n
+0000880484 00000 n
+0000880549 00000 n
+0000880613 00000 n
+0000880677 00000 n
+0000880743 00000 n
+0000880809 00000 n
+0000880873 00000 n
+0000880937 00000 n
+0001090033 00000 n
+0000883627 00000 n
+0000881748 00000 n
+0000881133 00000 n
+0000881874 00000 n
+0000881938 00000 n
+0000882004 00000 n
+0000882068 00000 n
+0000882131 00000 n
+0000882197 00000 n
+0000882263 00000 n
+0000882327 00000 n
+0000882391 00000 n
+0000882457 00000 n
+0000882523 00000 n
+0000882587 00000 n
+0000882651 00000 n
+0000882717 00000 n
+0000882783 00000 n
+0000882847 00000 n
+0000882911 00000 n
+0000882977 00000 n
+0000883043 00000 n
+0000883107 00000 n
+0000883171 00000 n
+0000883237 00000 n
+0000883303 00000 n
+0000883367 00000 n
+0000883431 00000 n
+0000883497 00000 n
+0000883563 00000 n
+0000886566 00000 n
+0000884495 00000 n
+0000883715 00000 n
+0000884621 00000 n
+0000884685 00000 n
+0000884749 00000 n
+0000884815 00000 n
+0000884881 00000 n
+0000884945 00000 n
+0000885009 00000 n
+0000885075 00000 n
+0000885270 00000 n
+0000885334 00000 n
+0000885397 00000 n
+0000885463 00000 n
+0000885529 00000 n
+0000885593 00000 n
+0000885656 00000 n
+0000885722 00000 n
+0000885787 00000 n
+0000885851 00000 n
+0000885915 00000 n
+0000885981 00000 n
+0000886047 00000 n
+0000886111 00000 n
+0000886175 00000 n
+0000886241 00000 n
+0000886307 00000 n
+0000886371 00000 n
+0000886434 00000 n
+0000886500 00000 n
+0000887140 00000 n
+0000886950 00000 n
+0000886668 00000 n
+0000887076 00000 n
+0000891220 00000 n
+0000889882 00000 n
+0000887214 00000 n
+0000890008 00000 n
+0000890198 00000 n
+0000890262 00000 n
+0000890326 00000 n
+0000890517 00000 n
+0000890581 00000 n
+0000890645 00000 n
+0000890836 00000 n
+0000890900 00000 n
+0000890964 00000 n
+0000891028 00000 n
+0000891092 00000 n
+0000891156 00000 n
+0000895282 00000 n
+0000894386 00000 n
+0000891322 00000 n
+0000894512 00000 n
+0000894576 00000 n
+0000894640 00000 n
+0000894833 00000 n
+0000894897 00000 n
+0000895090 00000 n
+0000895154 00000 n
+0000895218 00000 n
+0000901002 00000 n
+0000898560 00000 n
+0000895384 00000 n
+0000898686 00000 n
+0000898750 00000 n
+0000898814 00000 n
+0000899008 00000 n
+0000899072 00000 n
+0000899136 00000 n
+0000899199 00000 n
+0000899264 00000 n
+0000899328 00000 n
+0000899393 00000 n
+0000899457 00000 n
+0000899522 00000 n
+0000899586 00000 n
+0000899651 00000 n
+0000899714 00000 n
+0000899779 00000 n
+0000899843 00000 n
+0000899908 00000 n
+0000899972 00000 n
+0000900037 00000 n
+0000900101 00000 n
+0000900166 00000 n
+0000900230 00000 n
+0000900295 00000 n
+0000900359 00000 n
+0000900424 00000 n
+0000900487 00000 n
+0000900552 00000 n
+0000900616 00000 n
+0000900681 00000 n
+0000900745 00000 n
+0000900810 00000 n
+0000900874 00000 n
+0000900938 00000 n
+0001090158 00000 n
+0000904846 00000 n
+0000903755 00000 n
+0000901104 00000 n
+0000903881 00000 n
+0000903945 00000 n
+0000904009 00000 n
+0000904203 00000 n
+0000904267 00000 n
+0000904331 00000 n
+0000904525 00000 n
+0000904589 00000 n
+0000904782 00000 n
+0000908740 00000 n
+0000907455 00000 n
+0000904948 00000 n
+0000907581 00000 n
+0000907645 00000 n
+0000907839 00000 n
+0000908033 00000 n
+0000908225 00000 n
+0000908289 00000 n
+0000908355 00000 n
+0000908549 00000 n
+0000908613 00000 n
+0000908676 00000 n
+0000910005 00000 n
+0000909687 00000 n
+0000908842 00000 n
+0000909813 00000 n
+0000909877 00000 n
+0000909941 00000 n
+0000915808 00000 n
+0000912015 00000 n
+0000910093 00000 n
+0000912321 00000 n
+0000912514 00000 n
+0000912771 00000 n
+0000912835 00000 n
+0000912899 00000 n
+0000912965 00000 n
+0000913030 00000 n
+0000913159 00000 n
+0000913289 00000 n
+0000913353 00000 n
+0000913417 00000 n
+0000913482 00000 n
+0000913548 00000 n
+0000913612 00000 n
+0000913742 00000 n
+0000913806 00000 n
+0000913870 00000 n
+0000913934 00000 n
+0000913998 00000 n
+0000914064 00000 n
+0000914128 00000 n
+0000914192 00000 n
+0000914256 00000 n
+0000914320 00000 n
+0000914384 00000 n
+0000914448 00000 n
+0000914512 00000 n
+0000914578 00000 n
+0000914644 00000 n
+0000914708 00000 n
+0000914772 00000 n
+0000914836 00000 n
+0000914900 00000 n
+0000914966 00000 n
+0000915032 00000 n
+0000915097 00000 n
+0000915162 00000 n
+0000915227 00000 n
+0000915292 00000 n
+0000915358 00000 n
+0000915422 00000 n
+0000915486 00000 n
+0000915550 00000 n
+0000915614 00000 n
+0000915680 00000 n
+0000912162 00000 n
+0000915744 00000 n
+0000920308 00000 n
+0000917473 00000 n
+0000915938 00000 n
+0000917599 00000 n
+0000917728 00000 n
+0000917857 00000 n
+0000917921 00000 n
+0000917985 00000 n
+0000918051 00000 n
+0000918115 00000 n
+0000918180 00000 n
+0000918310 00000 n
+0000918374 00000 n
+0000918568 00000 n
+0000918632 00000 n
+0000918696 00000 n
+0000918954 00000 n
+0000919017 00000 n
+0000919081 00000 n
+0000919145 00000 n
+0000919210 00000 n
+0000919340 00000 n
+0000919404 00000 n
+0000919597 00000 n
+0000919661 00000 n
+0000919725 00000 n
+0000919789 00000 n
+0000919854 00000 n
+0000919984 00000 n
+0000920114 00000 n
+0000920178 00000 n
+0000920242 00000 n
+0000924102 00000 n
+0000921908 00000 n
+0000920438 00000 n
+0000922034 00000 n
+0000922098 00000 n
+0000922162 00000 n
+0000922228 00000 n
+0000922292 00000 n
+0000922358 00000 n
+0000922617 00000 n
+0000922680 00000 n
+0000922744 00000 n
+0000922810 00000 n
+0000922875 00000 n
+0000923005 00000 n
+0000923069 00000 n
+0000923133 00000 n
+0000923262 00000 n
+0000923391 00000 n
+0000923455 00000 n
+0000923519 00000 n
+0000923585 00000 n
+0000923651 00000 n
+0000923716 00000 n
+0000923844 00000 n
+0000923974 00000 n
+0000924038 00000 n
+0001090283 00000 n
+0000928452 00000 n
+0000925696 00000 n
+0000924232 00000 n
+0000926002 00000 n
+0000926131 00000 n
+0000926261 00000 n
+0000926325 00000 n
+0000926389 00000 n
+0000926455 00000 n
+0000926520 00000 n
+0000926649 00000 n
+0000926713 00000 n
+0000925843 00000 n
+0000926777 00000 n
+0000926843 00000 n
+0000926906 00000 n
+0000926969 00000 n
+0000927032 00000 n
+0000927096 00000 n
+0000927162 00000 n
+0000927226 00000 n
+0000927290 00000 n
+0000927354 00000 n
+0000927420 00000 n
+0000927486 00000 n
+0000927550 00000 n
+0000927614 00000 n
+0000927678 00000 n
+0000927743 00000 n
+0000928002 00000 n
+0000928066 00000 n
+0000928130 00000 n
+0000928324 00000 n
+0000928388 00000 n
+0000931309 00000 n
+0000932339 00000 n
+0000929954 00000 n
+0000928568 00000 n
+0000930080 00000 n
+0000930144 00000 n
+0000930273 00000 n
+0000930337 00000 n
+0000930401 00000 n
+0000930466 00000 n
+0000930595 00000 n
+0000930724 00000 n
+0000930788 00000 n
+0000930852 00000 n
+0000930916 00000 n
+0000930982 00000 n
+0000931048 00000 n
+0000931114 00000 n
+0000931180 00000 n
+0000931439 00000 n
+0000931503 00000 n
+0000931566 00000 n
+0000931694 00000 n
+0000931758 00000 n
+0000931822 00000 n
+0000931888 00000 n
+0000932146 00000 n
+0000932210 00000 n
+0000932274 00000 n
+0000937019 00000 n
+0000934497 00000 n
+0000932441 00000 n
+0000934623 00000 n
+0000934687 00000 n
+0000934751 00000 n
+0000934881 00000 n
+0000934945 00000 n
+0000935009 00000 n
+0000935073 00000 n
+0000935139 00000 n
+0000935205 00000 n
+0000935271 00000 n
+0000935337 00000 n
+0000935403 00000 n
+0000935468 00000 n
+0000935533 00000 n
+0000935599 00000 n
+0000935665 00000 n
+0000935793 00000 n
+0000935923 00000 n
+0000935987 00000 n
+0000936051 00000 n
+0000936117 00000 n
+0000936247 00000 n
+0000936311 00000 n
+0000936375 00000 n
+0000936504 00000 n
+0000936634 00000 n
+0000936698 00000 n
+0000936761 00000 n
+0000936825 00000 n
+0000936889 00000 n
+0000936953 00000 n
+0000939859 00000 n
+0000939826 00000 n
+0000939957 00000 n
+0000957941 00000 n
+0000968543 00000 n
+0000979203 00000 n
+0000992363 00000 n
+0001013639 00000 n
+0001032692 00000 n
+0001049863 00000 n
+0001072731 00000 n
+0001087493 00000 n
+0001090390 00000 n
+0001090516 00000 n
+0001090642 00000 n
+0001090768 00000 n
+0001090858 00000 n
+0001090950 00000 n
+0001120035 00000 n
+0001176845 00000 n
+0001176886 00000 n
+0001176926 00000 n
+0001177061 00000 n
+trailer
+<<
+/Size 5225
+/Root 5223 0 R
+/Info 5224 0 R
+/ID [<2F32E2E8047B866BBD395592B49028DC> <2F32E2E8047B866BBD395592B49028DC>]
+>>
+startxref
+1177325
+%%EOF
diff --git a/docs/en/rel_notes.txt b/docs/en/rel_notes.txt
new file mode 100644
index 000000000..4014951f0
--- /dev/null
+++ b/docs/en/rel_notes.txt
@@ -0,0 +1,3028 @@
+Release Notes for Bugzilla version 3.0 and higher are available in HTML
+format, either on the bugzilla.org website, or in your current installation,
+linked from the index page.
+
+bugzilla.org links for release notes
+------------------------------------
+3.0.2: http://www.bugzilla.org/releases/3.0.2/release-notes.html
+
+***************************************
+*** The Bugzilla 2.22 Release Notes ***
+***************************************
+
+Table of Contents
+*****************
+
+- Introduction
+- Important Updates In This Point Release
+- Minimum Requirements
+ * Perl
+ * For MySQL Users
+ * For PostgreSQL Users
+ * Required Perl Modules
+ * Optional Perl Modules
+- What's New?
+ * Complete PostgreSQL Support
+ * Parameters In Sections
+ * One Codebase, Multiple Databases
+ * UTF-8 for New Installations
+ * Admins Can Impersonate Users
+ * Bug Import and Moving Improvements
+ * Adding Individual Bugs to Saved Searches
+ * Attach URLs
+ * Optional "Strict Isolation" for Groups
+ * "editcomponents" Change
+ * "shutdownhtml" Change
+ * Miscellaneous Improvements
+ * All Changes
+- Deprecated Features
+- Outstanding Issues (<======================== IMPORTANT, PLEASE READ)
+- How to Upgrade From An Older Bugzilla
+ * Steps for Upgrading
+- Code Changes Which May Affect Customizations
+ * CGI.pl is Gone
+ * Other Changes
+- Security Fixes In 2.22 Releases
+- Release Notes for Previous Versions
+
+Introduction
+************
+Bugzilla 2.22 is one of our most polished releases. We did a lot of
+small cleanups to make Bugzilla easier to use and more useful in
+many, many small ways, in addition to adding some major new features.
+
+This document contains the release notes for Bugzilla 2.22.
+In this document, recently added, changed, and removed features
+of Bugzilla are described. If you are upgrading from an older version,
+you will definitely want to read these release notes in detail, so that
+you have an idea of what has changed.
+
+If you are upgrading from a version before 2.20, also read the 2.20
+release notes (lower in this file) and any previous release notes.
+
+If you are installing a new Bugzilla, you will still want to look over
+the release notes to see if there is any particularly important
+information that affects your installation.
+
+If you would like to contribute code to Bugzilla, read our
+Contributor's Guide at:
+
+http://www.bugzilla.org/docs/contributor.html
+
+
+Important Updates In This Point Release
+***************************************
+
+This section describes bugs fixed in releases after the original 2.22
+release.
+
+Version 2.22.2
+--------------
+
++ Make Bugzilla compatible with Template Toolkit 2.15 (bug 357374)
+
++ Make Bugzilla compatible with versions of MySQL higher than 5.0.25
+ (bug 321645)
+
++ Sanity Check can now only be run by people with the "admin" privilege.
+ (bug 91761)
+
+Version 2.22.1
+--------------
+
++ When sending mail, Bugzilla could throw the error "Insecure dependency in
+ exec while running with -T switch" (bug 340538).
+
++ Using the public webdot server (for dependency graphs) should work
+ again (bug 351243).
+
++ The "I'm added to or removed from this capacity" email preference
+ wasn't working for new bugs (bug 349852).
+
++ The original release of 2.22 incorrectly said it required Template-Toolkit
+ version 2.08. In actual fact, Bugzilla requires version 2.10 (bug 351478).
+
++ votes.cgi would crash if your bug was the one confirming a bug (bug 351300).
+
++ checksetup.pl now correctly reports if your Template::Plugin::GD module
+ is missing. If missing, it could lead to charts and graphs not working
+ (bug 345389).
+
++ The "Keyword" field on buglist.cgi was not sorted alphabetically, so
+ it wasn't very useful for sorting (bug 342828).
+
++ Sendmail will no longer complain about there being a newline in the
+ email address, when Bugzilla sends mail (bug 331365).
+
++ contrib/bzdbcopy.pl would try to insert an invalid value into the
+ database, unnecessarily (bug 335572).
+
++ Deleting a bug now correctly deletes its attachments from the database
+ (bug 339667).
+
+
+Minimum Requirements
+********************
+
+Perl
+----
+
+ Perl v5.6.1 (Non-Windows platforms)
+ ActiveState Perl v5.8.1 (Windows only)
+
+ Note that this is the last release of Bugzilla to support perl 5.6.x--
+ future versions will require perl 5.8.
+
+For MySQL Users
+---------------
+
+ MySQL v4.0.14 (changed from 2.20)
+ perl module: DBD::mysql v2.9003 (changed from 2.18)
+
+For PostgreSQL Users
+--------------------
+
+ PostgreSQL 7.3.x
+ perl module: DBD::Pg 1.31 (1.41 required for PostgreSQL 8+)
+
+ WARNING: DBD::Pg 1.43 has a bug which causes checksetup.pl to fail
+ and corrupt the database. If you are using DBD::Pg 1.43, either downgrade
+ to 1.41 or upgrade to 1.45 (1.42 and 1.44 seem broken somehow too).
+
+ Note that this is the last release of Bugzilla to support PostgreSQL 7.x.
+ Future versions will require PostgreSQL 8.0 and DBD::Pg 1.45.
+
+Required Perl Modules
+---------------------
+
+ AppConfig v1.52
+ CGI v2.93
+ Data::Dumper (any)
+ Date::Format v2.21
+ DBI v1.38
+ File::Spec v0.84
+ File::Temp (any)
+ Template Toolkit v2.10 (changed from 2.20)
+ Text::Wrap v2001.0131
+ Mail::Mailer v1.67 (changed from 2.20)
+ MIME::Base64 v3.01 (new in 2.22)
+ MIME::Parser v5.406 (new in 2.22)
+ Storable (any)
+
+ Note: The SMTP support in Mail::Mailer 1.73 (the most recent version)
+ is broken. The last known working version is 1.67.
+
+Optional Perl Modules
+---------------------
+
+ Chart::Base v1.0
+ GD v1.20
+ GD::Graph (any)
+ GD::Text::Align (any)
+ Net::LDAP (any)
+ PatchReader v0.9.4
+ XML::Twig (any) (new in 2.22)
+ Image::Magick (new in 2.22)
+
+
+What's New?
+***********
+
+Complete PostgreSQL Support
+---------------------------
+Bugzilla 2.20 contained experimental support for PostgreSQL.
+In Bugzilla 2.22, PostgreSQL support is fully complete and stable. Using
+PostgreSQL with Bugzilla should be as stable as using MySQL, and if
+you experience any problems they will be taken as seriously as if you
+were running MySQL.
+
+There are no known remaining major problems with Bugzilla on PostgreSQL.
+All features of Bugzilla have been tested and work.
+
+
+Parameters In Sections
+----------------------
+Long-time users of Bugzilla know that over time the parameter list has
+grown quite large. It has now been split into sections to make it easier
+to use.
+
+
+One Codebase, Multiple Databases
+--------------------------------
+There is now limited support for having multiple projects use the
+same Bugzilla codebase, but all have separate databases.
+
+The different projects can have their own templates and their own
+bug database, but all use the same set of Bugzilla code in the same
+directory.
+
+To enable this, set an environment variable called PROJECT when
+calling the Bugzilla CGIs. Then for each project, you can have
+a localconfig.PROJECT (where "PROJECT" is the value of the PROJECT
+environment variable) file for the database parameters, and a
+template/en/PROJECT directory (where "PROJECT" is the value of the
+PROJECT environment variable)
+
+This feature isn't documented yet, but we hope to have documentation for
+it soon.
+
+
+UTF-8 For New Installations
+---------------------------
+If this is the first time you're installing Bugzilla, it will now use
+UTF-8 encoding for all pages, automatically. It will also send emails
+in UTF-8. This eliminates most of the internationalization problems
+users have experienced, as one Bugzilla page may now contain any number
+of languages simultaneously.
+
+If you are upgrading and you want to use UTF-8, just turn on the "utf8"
+Parameter. However, realize that if you have non-UTF-8 data in your
+Bugzilla, it will appear unreadable. (If you just have ASCII in your
+database, you're safe to turn on the "utf8" parameter, definitely.)
+
+
+Admins Can Impersonate Users
+----------------------------
+User impersonation (think of the su/sudo command on Unix) allows you
+to view pages and perform actions as if you are logged in as someone else,
+without having to know their password.
+
+A user in the new "bz_sudoers" group has the option of "becoming"
+any user in Bugzilla. Once they "become" that user, they *are* that user
+for the rest of the session, until they decide to switch back to being
+themselves.
+
+However, they cannot "become" any user in the "bz_sudo_protect" group.
+This group includes everybody in the "admin" and "bz_sudoers" groups by
+default.
+
+Any time a user is impersonated, they will get an email notifying them
+who has impersonated them.
+
+
+Bug Import and Moving Improvements
+----------------------------------
+The XML Import script, importxml.pl, has been completely re-written.
+
+It now:
+
+ * Correctly imports the "priority" field
+ * Understands when the "Reporter" or "CC List" security boxes
+ are unchecked on the bug.
+ * Places bugs in the appropriate groups
+ * Allows attachments to be imported
+ * Is much more forgiving about small problems in the XML
+
+
+Adding Individual Bugs to Saved Searches (Tagging)
+--------------------------------------------------
+Users now have the option of adding an individual bug to any
+particular Saved Search. Individual users that disagree with the site
+default can add or remove this feature (which appears as an entry box
+visible in the footer) by changing the General Preferences setting
+called "Enable tags for bugs".
+
+
+Attach URLs
+-----------
+Instead of attaching a file, you can now also attach a URL to a bug.
+This will show up just like an attachment on show_bug.cgi, but when
+you click on it, it will take you to the URL.
+
+To enable this, turn on the "allow_attach_url" parameter.
+
+
+Optional "Strict Isolation" for Groups
+--------------------------------------
+If you turn on the "strict_isolation" parameter in Bugzilla, you
+will *not* be able to add any user to the CC field (or set them
+as an Assignee or QA Contact) unless that user could normally see
+the bug. That is, you will no longer be able to "accidentally"
+(or intentionally) give somebody access to a bug that they
+otherwise couldn't see.
+
+
+"editcomponents" Change
+-----------------------
+Previously, all users who had "editcomponents" could see every Product,
+using the editcomponents.cgi script. Now, users with "editcomponents"
+can only see Products that they normally have access to.
+
+This restriction also affects editversions.cgi, editmilestones.cgi and
+editproducts.cgi.
+
+
+"shutdownhtml" Change
+---------------------
+All of Bugzilla is now affected by the "shutdownhtml" parameter,
+including command-line scripts. checksetup.pl is exempt. Many scripts
+(such as collectstats.pl and whine.pl) will just exit silently when
+"shutdownhtml" is turned on.
+
+
+Miscellaneous Improvements
+--------------------------
+
+- Added a frequently-requested user preference for whether or not to go
+ to the next bug in your list after submitting changes to a bug.
+
+- The ability to do relative date searches (like "1d" for "1 day" or "1w"
+ for "1 week") by hour now, in addition to days and other units of time.
+
+- "Alias" added to the New Bug form, for users with editbugs.
+
+- Users can now actually see the descriptions of flags that you enter
+ in editflagtypes.cgi. The description will appear as a tooltip
+ when a user places their mouse over the flag name on show_bug.cgi.
+
+- Bugzilla will optionally convert BMP attachments into PNGs for you.
+ See the "convert_uncompressed_images" in the "Attachments" section
+ of the Parameters.
+
+- You can now edit the Status Whiteboard when you are changing multiple
+ bugs at once.
+
+- The way that groups work in the database has changed, and large-scale
+ Bugzilla use with many concurrent users should be much faster, as a
+ result. (Technical Details: The need for Bugzilla to "derive groups"
+ has gone away pretty much entirely.)
+
+- Performance improvements on searching attachment information that's not
+ the actual content of the attachment (such as searching the Attachment
+ Description or the Attachment MIME Type)
+
+- You can now specify multiple email addresses, comma-separated, when
+ setting the requestee of a flag, and it will set the flag once for each
+ of those email addresses
+
+- "Bug Creation Time" is now searchable in the Boolean Charts.
+
+- When you mark a comment on a bug as private, the background color
+ of the comment will change immediately. However, in order for
+ Bugzilla to register that the comment is now private, you still
+ have to "submit" the changes.
+
+- Emails sent from Bugzilla now have "X-Bugzilla-Keywords" and
+ "X-Bugzilla-Severity" by default, containing the information
+ from the related Bugzilla fields.
+
+- You can now change the assignee and QA contact on multiple bugs at
+ once even when those bugs are in different products.
+
+- contrib/merge-users.pl allows you to merge two user accounts. This is
+ particulary useful when a user opened several accounts and only one should
+ be kept. It also lets you merge a deleted account with an existing one.
+
+All Changes
+-----------
+
+If you'd like to see all the changes between Bugzilla 2.20 and Bugzilla
+2.22, see:
+
+http://tinyurl.com/9p2tm
+
+
+Deprecated Features
+*******************
+
+- This is the last release of Bugzilla to support perl 5.6.x. All future
+ versions of Bugzilla will require at least perl 5.8.
+
+ This is the last release of Bugzilla to support PostgreSQL 7.x. Future
+ releases using PostgreSQL will require PostgreSQL 8.0 and DBD::Pg 1.45.
+
+Outstanding Issues
+******************
+
+- bug 305836: PostgreSQL users: do not use DBD::Pg version 1.43 with
+ Bugzilla. It has a bug which can corrupt the database. Version 1.41
+ is fine. Version 1.45 or higher is fine too.
+
+- (No Bug Number) VERY IMPORTANT: If you have customized the values in
+ your Status/Resolution field, you must edit checksetup.pl BEFORE YOU
+ RUN IT. Find the line that starts like this:
+
+ bug_status => ["UNCONFIRMED",
+
+ That's where you set the values for the Status field.
+
+ resolution => ["","FIXED",
+
+ And that's where you set values for the Resolution field.
+
+ Those are both near line 1826 in checksetup.pl.
+
+ If you forget to do this, you will have to manually edit the "bug_status"
+ and "resolution" tables in the database to contain the correct values.
+
+- bug 276230: The support for restricting access to particular Categories of
+ New Charts is not complete. You should treat the 'chartgroup' Param as the
+ only access mechanism available. However, additionally, charts migrated from
+ Old Charts will be restricted to the groups that are marked MANDATORY for
+ the corresponding Product. There is currently no way to change this
+ restriction, and the groupings will not be updated if the group configuration
+ for the Product changes.
+
+- bug 37765: If you use the "sendmail" support of Bugzilla,
+ and you use an MTA which is *not* Sendmail (such as Postfix, Exim, etc.)
+ make sure the "sendmailnow" parameter is ON or Bugzilla will not send
+ e-mail correctly.
+
+- bug 69621: If you rename or remove a keyword that is in use on bugs, you will
+ need to rebuild the "keyword cache" by running sanitycheck.cgi and choosing
+ the option to rebuild the cache when it asks. Otherwise keywords may not show
+ up properly in search results.
+
+- (No Bug Number) If you have a lot of non-ASCII data in your Bugzilla (for
+ example, if you use a translation of Bugzilla), don't enable the XS::Stash
+ option when you install the Template Toolkit, or your Bugzilla installation
+ may become slow. This problem is fixed in a not-yet-released version of the
+ Template Toolkit (after 2.14).
+
+- Bug 99215: Flags are not protected by "mid-air collision" detection.
+ Nor are any attachment changes.
+
+- Bug 89822: When changing multiple bugs at the same time, there is no
+ "mid-air collision" protection.
+
+- bug 322955: The email interface (bug_mail.pl) in the contrib/ directory
+ has not been maintained (as it has no maintainer), and does not work
+ properly. We hope to have this fixed in our next major release of
+ Bugzilla; however, any help or contributions in this area are very
+ welcome.
+
+
+How to Upgrade From An Older Bugzilla
+*************************************
+
+NOTE: Upgrading from a large installation (over 10,000 bugs) running 2.18
+ or before may take a significant amount of time. checksetup will
+ try to let you know how long it will take, but expect downtime
+ of an hour or more if you have many bugs, many attachments,
+ or many users.
+
+Steps for Upgrading
+-------------------
+
+1) Read these entire Release Notes, particularly the "Outstanding Issues"
+ and "Security Fixes" sections.
+
+2) View the Sanity Check (sanitycheck.cgi) page on your installation before
+ upgrading. Attempt to fix all warnings that the page produces before
+ you go any further, or you may experience problems during your upgrade.
+
+3) Make a backup of the Bugzilla database before you upgrade, perhaps
+ by using mysqldump. THIS IS VERY IMPORTANT. If anything goes wrong
+ during the upgrade, your installation can be corrupted beyond
+ recovery. Having a backup keeps you safe.
+
+ Example:
+
+ mysqldump -u root -p bugs > bugs-db.sql
+
+4) Replace the files in your installation with the new version of Bugzilla,
+ or you can try to use CVS to upgrade. The bugzilla.org website has
+ instructions on how to do the actual installation.
+
+ You can also use a brand-new Bugzilla directory, as long as you
+ copy over the old data/ directory and the "localconfig" file to the
+ new installation.
+
+5) Run checksetup.pl after you install the new version.
+
+7) View the Sanity Check page again after you run checksetup.pl.
+
+8) It is recommended that, if possible, you fix any problems you find
+ immediately. Failure to do this may mean that Bugzilla will not work
+ correctly. Be aware that if the sanity check page contains more errors after
+ an upgrade, it doesn't necessarily mean there are more errors in your
+ database, as additional tests are added to the sanity check over time, and
+ it is possible that those errors weren't being checked for in the old
+ version.
+
+9) This version of Bugzilla contains improvements to the email that
+ Bugzilla sends when a bug is changed. The template for that email
+ is contained in the "newchangedmail" parameter. If you would like
+ to take advantage of the email enhancements in this version of
+ Bugzilla, reset that parameter to its default. (You can customize
+ it after that again, if you want.)
+
+
+Code Changes Which May Affect Customizations
+********************************************
+
+CGI.pl is Gone
+--------------
+The CGI.pl file, which used to contain many global functions, and which
+also contained initialization code for every CGI, is gone. The functions
+have been moved to various places and sometimes renamed.
+
+The initialization code that used to happen inside CGI.pl is now inside
+of Bugzilla.pm. All CGIs must "use Bugzilla" in one way or another. (Some
+CGIs "use Bugzilla" by doing "require globals.pl".)
+
+
+Deriving Groups No Longer Happens
+---------------------------------
+Bugzilla no longer needs to "derive groups" in advance. That is, previously
+Bugzilla used to flatten the group heirarchy into the user_group_map
+table. (That is, show that a user was in every group they were in,
+even if they were only in that group because they belonged to *another*
+group.) Now the table only contains groups that the user is in directly,
+and groups that they are in because of a regexp.
+
+Instead, The Bugzilla::User->group function determines the groups a user
+is in when called.
+
+We did this because the group derivation was causing a lot of complexity
+in the code, and also deriving the groups was a slow process that
+frequently had to happen inside of a database lock while sending mail
+or viewing a bug list.
+
+See https://bugzilla.mozilla.org/show_bug.cgi?id=304583 for details.
+
+
+Other Changes
+-------------
+
+- The move.pl script's functionality has been merged into process_bug.cgi.
+
+- $::template and $::vars are gone from globals.pl. Instead of $::template,
+ use Bugzilla->template. Every script creates the $vars variable by itself
+ instead of using a global $::vars variable.
+
+- $::userid is gone. Instead use Bugzilla->user->id.
+
+- QuickSearch is now in perl instead of in JavaScript. The code is in
+ Bugzilla/Search/QuickSearch.pm. This makes it much easier to customize,
+ and it also fixes some long-standing issues that QuickSearch had.
+
+- Attachment data is now in the attach_data table. Other information
+ about attachments is still in the "attachments" table.
+
+- Much like the 2.20 release, many functions have been removed from
+ globals.pl and CGI.pl. They were moved elsewhere and renamed.
+ Search RESOLVED bugs in bugzilla.mozilla.org for the old
+ version of the function name, and that will usually show you
+ the bug where we moved the function, allowing you to find out
+ what the new name and location is.
+
+- This is the last release that contains the deprecated
+ SendSQL, SqlQuote, FetchSqlData, MoreSqlData, and FetchOneColumn
+ functions. Instead, you should use DBI functions. For a very brief
+ example, see:
+
+ http://www.bugzilla.org/docs/developer.html#sql-sendreceive
+
+
+Security Fixes in 2.22 Releases
+*******************************
+
+A long-standing, well-known security issue is finally resolved in Bugzilla
+2.22: Previously, the "Session ID" of each user could be easily guessed,
+given enough time. This could have allowed an attacker to take over a
+user's account, in certain circumstances. Now, the "Session ID" is totally
+random, resolving this issue. See bug 119524 in bugzilla.mozilla.org for
+details.
+
+If you are very concerned about the security of your Bugzilla installation,
+it would be a very good idea to run the following command on your
+database immediately after upgrading:
+
+TRUNCATE TABLE logincookies;
+
+This is actually safe to do at any time--it just forces a logout of
+every single user, even those with saved sessions. (It invalidates
+every login cookie Bugzilla has ever given out.)
+
+Version 2.22.2
+--------------
+
+A Cross-Site Scripting vulnerability is fixed in Bugzilla 2.22.2. You can
+read the details of the fix at:
+
+http://www.bugzilla.org/security/2.20.3/
+
+Version 2.22.1
+--------------
+
+The Bugzilla team fixed two Information Leaks and three Cross-Site
+Scripting vulnerabilities that existed in versions of Bugzilla
+prior to 2.22.1. We strongly recommend that you update any 2.22
+installation to 2.22.1, to be protected from these vulnerabilities.
+
+In addition, we have made an enhancement to security in this version
+of Bugzilla. In previous versions, it was possible for malicious
+users to exploit administrators in certain ways. Although this has
+never happened (to our knowledge) in the real world, we thought it
+was important that we protect administrators from this sort of attack.
+
+You can see details on all the vulnerabilities and enhancements at:
+
+http://www.bugzilla.org/security/2.18.5/
+
+
+Release Notes For Previous Versions
+************************************
+
+***************************************
+*** The Bugzilla 2.20 Release Notes ***
+***************************************
+
+Table of Contents
+*****************
+
+- Introduction
+- Important Updates in this Point Release
+ * Version 2.20.1
+ * Version 2.20.2
+- Minimum Requirements
+ * Perl
+ * For MySQL Users
+ * For PostgreSQL Users
+ * Required Perl Modules
+ * Optional Perl Modules
+- What's New?
+ * Experimental PostgreSQL Support
+ * New User-Interface Color/Style
+ * Higher-Level Categorization of Bugs (above "Product")
+ * Regular Reports by Email of Complex Queries ("Whining")
+ * "Environment Variable" Authentication Method
+ * User-List Drop-Down Menus
+ * Server-Side Comment Wrapping
+ * UI for Editing Priority, OS, Platform, and Severity
+ * Bugzilla Queries as RSS
+ * Choice of E-Mail Sending Methods
+ * "User Preferences"
+ * "Large Attachment" Storage
+ * "User Visibility" Controls
+ * Miscellaneous Improvements
+ * All Changes
+- Deprecated Features
+- Outstanding Issues (<======================== IMPORTANT, PLEASE READ)
+- How to Upgrade From An Older Bugzilla
+ * Steps for Upgrading
+- Code Changes Which May Affect Customizations
+ * The New Database-Compatibility Layer
+ * If You Customize Your Database...
+ * Many Functions Renamed
+ * User Preferences
+ * Other Changes
+- Security Fixes In 2.20 Releases
+- Release Notes for Previous Versions
+
+
+Introduction
+************
+
+This document contains the release notes for Bugzilla 2.20.
+In this document, recently added, changed, and removed features
+of Bugzilla are described. If you are upgrading from an older version,
+you will definitely want to read these release notes in detail, so that
+you have an idea of what has changed.
+
+If you are upgrading from a version before 2.18, also read the 2.18 release
+notes (lower in this file) and any previous release notes.
+
+If you are installing a new Bugzilla, you will still want to look over
+the release notes to see if there is any particularly important information
+that affects your installation.
+
+The 2.20 release has had about nine months of development since 2.18, but
+they were nearly the most active nine months in Bugzilla's history. We hope
+that users will appreciate our many external changes, and that Bugzilla
+administators will find that our internal changes make their lives easier.
+
+If you would like to contribute code to Bugzilla, read our
+Contributor's Guide at:
+
+http://www.bugzilla.org/docs/contributor.html
+
+
+Important Updates In This Point Release
+***************************************
+
+Version 2.20.1
+--------------
+
++ Many PostgreSQL fixes, including fixing whine.pl on Pg 8
+ (bug 301062) and fixing the --regenerate option of collectstats.pl
+ for all versions of Pg (bug 316971). However, users who want full
+ PostgreSQL support are encouraged to use the 2.22 series, as
+ certain PostgreSQL bugs were discovered that will not be fixed
+ in 2.20 (their fixes were too complex).
+
++ In Bugzilla 2.20, the "administrator" user created by checksetup.pl
+ would not ever be sent email, because their email preferences were
+ left blank. This has been fixed for 2.20.1. However, if you created
+ this administrative user with Bugzilla 2.20, make sure to go back
+ and enable their Email Preferences. (bug 317489)
+
++ The bzdbcopy.pl script mentioned in these release notes
+ has now actually been checked-in to the 2.20 branch, and so
+ it's included in this release. (bug 291776)
+
++ When there's only one Classification, you now won't be required
+ to pick a Classification on bug entry. (bug 311489)
+
++ You can no longer add dependencies on bugs you can't see.
+ (bug 141593)
+
++ The CC list is included in "New" bug emails, again. (bug 313661)
+
++ In the original 2.20, certain scripts were not correctly using
+ the "shadow database," if it was specified. This has been fixed
+ in 2.20.1. (bug 313695)
+
++ "Saved Searches" that were saved before Bugzilla 2.20, would throw
+ an error if they contained "Days Since Bug Changed." as part of their
+ criteria. This has been fixed in Bugzilla 2.20.1. (bug 302599)
+
++ You can now successfully delete a product even when Target Milestones
+ are turned off. (bug 317025)
+
++ checksetup.pl now correctly pre-compiles templates for languages other
+ than English. (bug 304417)
+
++ The "All Closed" chart that is created by default in New Charts
+ now actually represents all closed bugs, and not all bugs in the
+ product. (bug 300473)
+
++ CSV bug lists with more than 1000 dates now work properly. (bug 257813)
+
++ Various bugs with upgrading from previous versions of Bugzilla
+ have been fixed. (bug 307662, bug 311047, bug 310108)
+
++ Many, many other bug fixes. See http://www.bugzilla.org/status/changes.html
+ for details on what was fixed between 2.20 and 2.20.1.
+
+
+Version 2.20.2
+--------------
+
++ Adding a new attachment and taking the bug at the same time does not
+ create a referential integrity problem anymore if the bug was marked as
+ a duplicate (bug 332705).
+
++ Some additional admin links have been added to the sidebar (bug 282613).
+
++ A new test has been added to our test suite, named 012throwables.t.
+ It will now make sure that all tags used in ThrowUserError() and
+ ThrowCodeError() are defined, and that there are no unused tags (bug 312042).
+
++ whine.pl now works correctly on MySQL 4.0. MySQL 4.1 is not affected
+ (bug 327348).
+
++ contrib/merge-users.pl allows you to merge two user accounts. This is
+ especially useful when a user opened several accounts and only one
+ should be kept (bug 188264).
+
++ The login form on index.cgi again works correctly on a fresh installation
+ (bug 328108).
+
++ Email preferences are now set correctly when creating a new user account
+ using the ENV method (bug 327355).
+
+
+Minimum Requirements
+********************
+
+Perl
+----
+
+ Perl v5.6.1 (changed from 2.18) (Non-Windows platforms)
+ ActiveState Perl v5.8.1 (Windows only)
+
+For MySQL Users
+---------------
+
+ MySQL v3.23.41 (Note: 2.22 will require MySQL 4.x)
+ perl module: DBD::mysql v2.9003 (changed from 2.18)
+
+For PostgreSQL Users (new in 2.20)
+--------------------
+
+ PostgreSQL 7.3.x (8.x has received less testing)
+ perl module: DBD::Pg 1.31 (1.41 required for PostgreSQL 8+)
+
+Required Perl Modules
+---------------------
+
+ AppConfig v1.52
+ CGI v2.93
+ Data::Dumper (any)
+ Date::Format v2.21
+ DBI v1.38 (changed from 2.18)
+ File::Spec v0.84 (changed from 2.18)
+ File::Temp (any)
+ Template Toolkit v2.08
+ Text::Wrap v2001.0131
+ Mail::Mailer 1.65 (new in 2.20)
+ Storable (any) (new in 2.20)
+
+Optional Perl Modules
+---------------------
+
+ Chart::Base v1.0
+ GD v1.20
+ GD::Graph (any)
+ GD::Text::Align (any)
+ Net::LDAP (any)
+ PatchReader v0.9.4
+ XML::Parser (any)
+
+
+What's New?
+***********
+
+Experimental PostgreSQL Support
+-------------------------------
+
+In addition to MySQL, Bugzilla now also supports PostgreSQL. PostgreSQL
+support is still somewhat experimental. Although most major features of
+Bugzilla work on PostgreSQL in 2.20, there are probably still a few bugs
+that need to be worked out.
+
+PostgreSQL support in 2.20 is acceptable for smaller production
+environments that don't mind running into a bug or two now and then.
+
+
+New User-Interface Color/Style
+------------------------------
+
+You'll notice that Bugzilla looks a bit nicer, now! We've made a few
+color and style changes to update the overall "feel" of Bugzilla's
+User Inteface. We plan to do even more work on the UI for 2.22.
+
+
+Higher-Level Categorization of Bugs (above "Product")
+-----------------------------------------------------
+
+Previous Bugzillas had "Products" that you could file bugs in,
+and "Components" for those products. Now, "Products" can be grouped
+into "Classifications."
+
+To enable this, a Bugzilla administrator can turn on the
+"useclassification" parameter, using editparams.cgi.
+
+
+Regular Reports by Email of Complex Queries ("Whining")
+-------------------------------------------------------
+
+You can now tell Bugzilla to do a specific query (or set of queries)
+every X minutes/hours/days, and send you the results by email. This is
+great for keeping track on a daily basis of what's going on in
+your Bugzilla.
+
+
+"Environment Variable" Authentication Method
+--------------------------------------------
+
+You can now tell Bugzilla to accept a certain value passed in from
+Apache as authentication for Bugzilla users. This means that Bugzilla
+now "supports" any type of authentication that Apache supports.
+
+To use this, set the "user_info_class" parameter to "ENV" and, at a
+minimum, set the "auth_env_email" parameter to the name of the
+Environment variable that passes the authenticated user (usually
+"REMOTE_USER"). If your webserver knows users' real names as well, also
+set the "auth_env_realname" parameter. If you are using a true
+single-signon system that assigns an identifier uniquely to an
+individual, even across changes of email address, then set
+"auth_env_id" to the name of that variable.
+
+
+User-List Drop-Down Menus
+-------------------------
+
+Now, anywhere in Bugzilla where you previously had to type in an
+email address by hand, you have the choice of having Bugzilla instead
+display a drop-down menu of users to pick from.
+
+This feature is best for small installations with few users, because
+on large installations the list grows too large to be useful.
+
+To enable the feature, turn on the "usemenuforusers" parameter in
+editparams.cgi.
+
+
+Server-Side Comment Wrapping
+----------------------------
+
+In older Bugzillas, comments were wrapped to 80 characters by the
+user's web browser, and then stored in the database that way. This caused
+problems because some browsers did not wrap comments properly.
+
+Now, Bugzilla stores comments unwrapped and wraps them at display time, so
+all new comments should be properly wrapped. Also, when you upgrade, Bugzilla
+will look for old "mis-wrapped" comments and attempt to wrap them properly.
+
+Lines beginning with the ">" character are assumed to be quotes, and are
+*not* wrapped.
+
+
+UI for Editing Priority, OS, Platform, and Severity
+---------------------------------------------------
+
+Bugzilla now has a User Interface for adding and removing values
+from the OS, Platform, Priority, and Severity fields. You can also
+rename values. Any user in the "editcomponents" group can click
+on the "Field Values" link in their page footer to edit these fields.
+
+Also, the default list of choices for OS and Platform for new
+installations is now much smaller. Old installations will keep
+the same list they have now.
+
+
+Bugzilla Queries as RSS
+-----------------------
+
+You can now view a Bugzilla query as valid RSS 1.0. This means that you
+could add a particular query to your RSS aggregator, if you wanted, to
+keep track of changes in Bugzilla.
+
+To see a query as RSS, just click on the "RSS" link on the bottom of
+your query results. Your query must return at least 1 result in order
+for you to see the link.
+
+
+Choice of E-Mail Sending Methods
+--------------------------------
+
+Bugzilla now uses perl's Mail::Mailer to send e-mail. This means that
+you have several choices of how Bugzilla can send email. By default, it
+still uses sendmail, but it can also use SMTP, qmail, or send all email
+to a file instead of out to users.
+
+A Bugzilla administrator can change which method is used by setting the
+"mail_delivery_method" parameter in editparams.cgi.
+
+
+"User Preferences"
+------------------
+
+Bugzilla users will now notice a section in their Preferences called
+"General Preferences." Administrators will notice a new link called
+"User Preferences."
+
+The Preferences system allows Bugzilla developers to specify arbitrary
+"user preferences" that change the behavior of certain parts of Bugzilla.
+Administrators can control whether or not users are allowed to use these
+preferences, and what the default settings are for a user who is not
+logged in.
+
+The first two preferences that we have implemented are:
+ + "Show a quip at the top of each bug list"
+ + "When viewing a bug, show comments in this order..."
+
+We plan to implement more preferences in the future.
+
+
+"Large Attachment" Storage
+--------------------------
+
+Bugzilla can now store very large attachments on disk instead of in the
+database. These attachments can't be searched with Boolean Charts, but
+they also don't take up database space, and they can be deleted individually
+by the admin.
+
+When uploading an attachment, a user chooses if it's a "Big File." If so,
+it's stored on the disk instead of in the database.
+
+To enable this feature, set the "maxlocalattachmentsize" parameter to
+a non-zero value, in editparams.cgi.
+
+
+"User Visibility" Controls
+--------------------------
+
+It is now possible to prevent users from encountering all other users when
+using user-matching or drop-down userlists. To enable this restriction,
+enable the "usevisibilitygroups" parameter. Once this is enabled, each
+group's permissions will include a new column for "visible." The members
+of any group for which the group being edited is visible will be
+able to user-match this groups's users or see them in dropdown lists.
+
+This does not control who a user can CC on a bug, only who they can
+see in the user-matching lists or drop-downs.
+
+Miscellaneous Improvements
+--------------------------
+
+- Marking an attachment as obsolete will now cancel all pending flag
+ requests for that attachment. That is, any flag that was set to "?"
+ on that attachment will be cleared.
+
+- You can now see which users are "watching" you, on the email
+ preferences page.
+
+- You can tell Bugzilla to mark certain comments in a different
+ color by adding "&mark=1,2,3,5-7" to the end of the show_bug.cgi URL,
+ where "1,2,3,5-7" means "highlight comment 1, comment 2, comment 3, and
+ comments 5 through 7."
+
+- "QA Contact" now also appears on the New Bug page, if QA Contacts are
+ enabled on your installation.
+
+- Bugzilla email now has the "In-Reply-To" header added to it, so if
+ you use an email client that supports threads, you can view your
+ Bugzilla email in threads. If you are upgrading to a new version of
+ Bugzilla, and you want this support, please see the instructions at:
+ https://bugzilla.mozilla.org/attachment.cgi?id=172267
+
+- The email preferences system has been slightly updated. You will notice
+ the changes on your Email Preferences page.
+
+- You can now negate individual "boolean charts" (in the
+ "Advanced Searching" section at the bottom of the "Advanced
+ Search" page). That is, you can add "NOT" to the front of them.
+
+- You can add the words %assignee%, %reporter%, %user% (yourself), or
+ %qacontact% on the right-hand side of a Boolean Chart. For example, you
+ could make a Boolean Chart which said "Reporter" "does not equal"
+ "%assignee%". That would give you all bugs where the Reporter was not
+ the same as the Assignee.
+
+- You can now search Boolean Charts by "commenter."
+
+- If you have a group with no name, it will be re-named to "group_#" where
+ "#" is the numeric Bugzilla Group ID for that group.
+
+- If you are using time-tracking, you can now see a report of time spent
+ on bugs using summarize_time.cgi.
+
+- If you are using time-tracking, bugzilla will now set "hours remaining"
+ to "0" automatically if you RESOLVE a bug, whether you are in the
+ time-tracking group or not.
+
+
+Deprecated Features
+*******************
+
+- Bugzilla 2.20 is the last Bugzilla version to support MySQL 3.23.x.
+ Starting with Bugzilla 2.22, Bugzilla will require MySQL 4.0.x. This will
+ allow Bugzilla to take advantage of the advanced features of MySQL 4.
+
+
+Outstanding Issues
+******************
+
+- (No Bug Number) VERY IMPORTANT: If you have customized the values in
+ your Status/Resolution field, you must edit checksetup.pl BEFORE YOU
+ RUN IT. Find the line that starts like this:
+
+ bug_status => ["UNCONFIRMED",
+
+ That's where you set the values for the Status field.
+
+ resolution => ["","FIXED",
+
+ And that's where you set values for the Resolution field.
+
+ Those are both near line 1826 in checksetup.pl.
+
+ If you forget to do this, you will have to manually edit the "bug_status"
+ and "resolution" tables in the database to contain the correct values.
+
+- bug 37765: VERY IMPORTANT: If you use the "sendmail" support of Bugzilla,
+ and you use an MTA which is *not* Sendmail (such as Postfix, Exim, etc.)
+ you MUST turn on the "sendmailnow" parameter or Bugzilla will not send
+ e-mail correctly.
+
+- (No Bug Number) If you close your web browser while the process_bug.cgi
+ or post_bug.cgi screen is running, not all emails will be sent, and
+ the next time that that bug is updated, there will be two updates. This
+ is because of a behavior of Apache that is beyond our control.
+
+- bug 276230: The support for restricting access to particular Categories of
+ New Charts is not complete. You should treat the 'chartgroup' Param as the
+ only access mechanism available. However, additionally, charts migrated from
+ Old Charts will be restricted to the groups that are marked MANDATORY for
+ the corresponding Product. There is currently no way to change this
+ restriction, and the groupings will not be updated if the group configuration
+ for the Product changes. This will not be fixed in the 2.20 branch.
+
+- bug 69621: If you rename or remove a keyword that is in use on bugs, you will
+ need to rebuild the "keyword cache" by running sanitycheck.cgi and choosing
+ the option to rebuild the cache when it asks. Otherwise keywords may not show
+ up properly in search results.
+
+- (No Bug Number) If you have a lot of non-ASCII data in your Bugzilla (for
+ example, if you use a translation of Bugzilla), don't enable the XS::Stash
+ option when you install the Template Toolkit, or your Bugzilla installation
+ may become slow. This problem is fixed in a not-yet-released version of the
+ Template Toolkit (after 2.14).
+
+- If at any time you upgraded from a version of Bugzilla between 2.17.4 -
+ 2.17.7 to either 2.18rc3 or 2.19.1, you must manually fix your New Charts in
+ order for them to work. See the following link for instructions on how to do
+ this: https://bugzilla.mozilla.org/show_bug.cgi?id=276237#c18
+ If you are using 2.18rc3, but did not upgrade from version 2.17.4 or newer,
+ then you don't need to do this.
+
+- (No Bug Number) If your DBI is really, really old, Bugzilla might fail
+ with a strange error message when you try to run checksetup.pl. Try
+ upgrading your DBI using: perl -MCPAN -e'install DBI'
+
+- Bug 126266: Bugzilla does not use UTF-8 to display pages. This means
+ that if you enter non-ASCII characters into Bugzilla, they may
+ display strangely, or Bugzilla may have other problems. For a workaround,
+ see: http://www.bugzilla.org/docs/tip/html/security-bugzilla.html
+ This has been fixed in the 2.22 series.
+
+- Bug 99215: Flags are not protected by "mid-air collision" detection.
+ Nor are any attachment changes.
+
+- Bug 89822: When changing multiple bugs at the same time, there is no
+ "mid-air collision" protection.
+
+- Bug 285614: importxml.pl may be broken in many different ways.
+ It has been fixed and completely re-written in the 2.22 series.
+
+- (No Bug Number) Note that the email interface (bug_mail.pl) in the
+ contrib/ directory has not been maintained (as it has no maintainer),
+ and so may not be working properly. Contributions are welcome, if
+ anybody would like to work on it.
+
+
+Upgrading From An Older Bugzilla
+************************************
+
+NOTE: Running checksetup.pl to upgrade a large installation (over 10,000 bugs)
+ may take a significant amount of time. checksetup will try to let
+ you know how long it will take, but expect downtime of an hour or
+ more if you have many bugs, many attachments, or many users.
+
+Steps for Upgrading
+-------------------
+
+1) View the Sanity Check (sanitycheck.cgi) page on your installation before
+ upgrading. Attempt to fix all warnings that the page produces before
+ you go any further, or you may experience problems during your upgrade.
+
+2) Make a backup of the Bugzilla database before you upgrade, perhaps
+ by using mysqldump.
+
+ Example:
+
+ mysqldump -u root -p --databases bugs > bugs.db.backup
+
+3) Replace the files in your installation with the new version of Bugzilla,
+ or you can try to use CVS to upgrade. The Bugzilla.org website has
+ instructions on how to do the actual installation.
+
+4) Make sure that you run checksetup.pl after you install the new version.
+
+5) View the Sanity Check page again after you run checksetup.pl.
+
+6) It is recommended that, if possible, you fix any problems you find
+ immediately. Failure to do this may mean that Bugzilla will not work
+ correctly. Be aware that if the sanity check page contains more errors after
+ an upgrade, it doesn't necessarily mean there are more errors in your
+ database, as additional tests are added to the sanity check over time, and
+ it is possible that those errors weren't being checked for in the old
+ version.
+
+7) If you want threading support on your Bugzilla email (see the
+ "Miscellaneous Improvements" section above for a description),
+ you need to follow the instructions at:
+ https://bugzilla.mozilla.org/attachment.cgi?id=172267
+
+
+Code Changes Which May Affect Customizations
+********************************************
+
+The New Database-Compatibility Layer
+------------------------------------
+
+For most customizations, this should have no effect. However, you should
+be aware that Bugzilla->dbh is now an instance of "Bugzilla::DB" instead
+of being a DBI object directly. In fact, it's actually a
+Bugzilla::DB::Mysql for MySQL users, and a Bugzilla::DB::Pg for
+PostgreSQL users.
+
+Anything called from $dbh (like $dbh->bz_last_key) that starts with
+"bz_" or "sql_" is a custom Bugzilla function. Anything *not* starting
+with those two prefixes is a normal DBI function.
+
+Methods whose names start with "sql_" generate a piece of a SQL statement.
+They generate the correct version of the statement for whichever database
+you are using.
+
+Methods whose names start with "bz_" do something directly.
+
+You can see more documentation about this at:
+
+http://www.bugzilla.org/docs/2.20/pod/Bugzilla/DB.pm
+
+
+If You Customize Your Database...
+---------------------------------
+
+In order to support multiple databases, we had to do something sort of
+tricky. Bugzilla now stores what it *thinks* the current database schema
+is, in a table called bz_schema.
+
+This means that when checksetup changes the database, it updates the
+bz_schema table. When *you* update the database, without using
+checksetup to do it, the bz_schema table is *not* updated.
+
+So, if you're going to add/remove a new column/table to Bugzilla, or if you're
+going to change the definition of a column, try to do it by adding code to
+checksetup in the correct place. (It's one of the places where you find
+the word "--TABLE--".)
+
+You can see the documentation on the $dbh functions used to do this at:
+
+http://www.bugzilla.org/docs/2.20/pod/Bugzilla/DB.pm#schema_modification_methods
+
+
+Many Functions Renamed
+----------------------
+
+We are reorganizing the Bugzilla code so that it can support mod_perl. As
+part of this, we are moving all functions out of globals.pl and CGI.pl, and
+into modules in the Bugzilla/ directory.
+
+Sometimes when we moved them, we also renamed them. The new Bugzilla standard
+is to have functions_named_like_this, instead of FunctionsNamedLikeThis.
+
+So if you were using a FunctionNamedLikeThis that no longer works, try just
+using it as function_named_like_this. If that doesn't work, you may have to
+search for where we put it, and what we renamed it to. Most of the functions
+moved to logical places.
+
+If you really can't find it, search bugzilla.mozilla.org using the name
+of the old function. We usually moved one function per bug, so the new
+name will be somewhere in a bug report.
+
+
+User Preferences
+----------------
+
+Bugzilla now has a "User Preferences" system! These preferences are stored
+in the database, and specified by a Bugzilla developer. The Bugzilla
+developers actually call these "settings," but we called them "User
+Preferences" in the UI to make things clearer.
+
+You access a user's settings differently depending on if you are in a
+.cgi file or in a template file:
+
+CGI: Bugzilla->user->settings->{'setting_name'}->value
+Template: Bugzilla.user.settings.setting_name.value
+
+Where "setting_name" is the name of the setting. You can see the current
+setting names in the "setting" table in the database.
+
+Remember that sometimes you may want to check a user's settings when
+making a customization.
+
+To see how to add new settings, search for "add_setting" in checksetup.pl.
+Also see the template: template/en/default/global/setting-descs.none.tmpl.
+
+Other Changes
+-------------
+
+- The $::unconfirmedstate variable has been replaced by the actual string
+ "UNCONFIRMED" everywhere in Bugzilla code.
+
+- The %::FORM and %::MFORM variables are no longer used to access form
+ data. Instead, use $cgi->param(). There are many examples of how to do
+ this, all over the Bugzilla code.
+
+- SendSQL() and related calls are deprecated, and the various $dbh methods
+ should be used instead, such as $dbh->prepare() and $dbh->execute().
+ Bugzilla->dbh is the $dbh handle to use. For more information on how
+ to use the $dbh methods, see: http://search.cpan.org/dist/DBI/DBI.pm
+
+- The $::userid variable will be going away. Use Bugzilla->user->id instead.
+
+- All global variables (any that start with $::, @::, or %::) will
+ be entirely gone by Bugzilla 2.24.
+
+
+Security Fixes in 2.20 Releases
+*******************************
+
+2.20.1
+------
+
+There were three security issues discovered after the release of
+Bugzilla 2.20 that we resolved for Bugzilla 2.20.1. One SQL Injection
+(from an administrator only), one Cross-Site Scripting vulnerability
+(that mostly affects only the user who can exploit it), and one minor,
+extremely specific information leak.
+
+To see details on the vulnerabilities that were fixed, see the
+Security Advisory at:
+
+http://www.bugzilla.org/security/2.16.10/
+
+
+Release Notes for Previous Versions
+***********************************
+
+*****************************************
+*** The Bugzilla 2.18.x Release Notes ***
+*****************************************
+
+Table of Contents
+*****************
+
+- Introduction
+- Important Updates In This Point Release
+ * Version 2.18.1
+ * Version 2.18.2
+- Requirements
+ * Dependency Requirements
+- What's New?
+ * Generic Reporting
+ * Generic Charting
+ * Request System
+ * Enterprise Group Support
+ * User Wildcard Matching
+ * Support for "Insiders"
+ * Time Tracking
+ * Authentication module/LDAP improvements
+ * Improved localization support
+ * Patch Viewer
+ * Comment Reply Links
+ * Full-Text Search
+ * Email Address Munging
+ * Simple Search
+ * Miscellaneous Improvements
+ * All Changes
+- What's Changed?
+ * Flag Names
+ * New Saved Search User Interface
+ * Rules for changing fields
+- Removed Features
+- Code Changes Which May Affect Customizations
+- Recommended Practice for the Upgrade
+ * Note About Upgrading From MySQL With ISAM Tables
+ * Steps for Upgrading
+- Outstanding Issues (<======================== IMPORTANT, PLEASE READ)
+- Security Fixes In 2.18 Releases
+- Detailed Version-To-Version Release Notes
+
+
+Introduction
+************
+
+This document contains the release notes for Bugzilla 2.18 and
+the bugfix releases after 2.18. In this document, recently added,
+changed, and removed features of Bugzilla are described.
+
+The 2.18 release is our current stable series, containing the results
+of over two years of hard and dedicated work by volunteers all over
+the world under the lead of Dave Miller.
+
+
+Important Updates In This Point Release
+***************************************
+
+There are usually many other bug fixes than those listed below,
+but the below fixes are the ones that we thought System Administrators
+would like to specifically know about.
+
+To see a listing of all changes in this release, you can use the
+table available at:
+
+http://www.bugzilla.org/status/changes.html
+
+Version 2.18.1
+--------------
+
++ You can now enter a negative time for "Hours Worked"
+ in the time-tracking area. (Bug 271276)
+
++ The BugMail.pm customization required for Windows (as
+ described in the Bugzilla Guide) now actually works. (Bug 280911)
+
++ Users who were using Bugzilla 2.8 can now successfully upgrade
+ to 2.18.1 (they couldn't upgrade to 2.18). (Bug 283403)
+
++ Dependency mails are now properly sent during a mass-change of bugs.
+ (Bug 178157)
+
+
+Version 2.18.2
+--------------
+
++ You can now create accounts with createaccount.cgi even
+ when the "requirelogin" parameter is turned on. (Bug 294778)
+
++ Bugs that are in disabled groups may not show a padlock
+ on the bug list, or may otherwise behave strangely. You
+ can now fix this using sanitycheck.cgi. (Bug 277454)
+
++ If sendmail dies while you are marking a bug
+ as a duplicate, the duplicates table will no longer become
+ corrupted. (Bug 225042)
+
+
+Requirements
+************
+
+Dependency Requirements
+-----------------------
+
+Minimum software requirements:
+
+ MySQL v3.23.41 (changed from 2.16)
+ Perl v5.6.0 (changed from 2.16) (Non-Windows platforms)
+ ActiveState Perl v5.8.1 (Windows only)
+
+Required Perl modules:
+
+ AppConfig v1.52
+ CGI v2.93 (new since 2.16) (changed from 2.17.7)
+ Data::Dumper (any)
+ Date::Format v2.21 (changed from 2.16)
+ DBI v1.36 (changed from 2.16) (changed from 2.17.7)
+ DBD::mysql v2.1010 (changed from 2.16)
+ File::Spec v0.82
+ File::Temp (any)
+ Template Toolkit v2.08 (changed from 2.16)
+ Text::Wrap v2001.0131
+
+Optional Perl modules:
+
+ Chart::Base v1.0 (changed from 2.16) (changed from 2.17.7)
+ GD v1.20 (changed from 2.16)
+ GD::Graph (any) (new since 2.16)
+ GD::Text::Align (any) (new since 2.16)
+ Net::LDAP (any) (new since 2.16)
+ PatchReader v0.9.4 (new since 2.16) (changed from 2.17.7)
+ XML::Parser (any)
+
+
+What's New?
+***********
+
+Generic Reporting
+-----------------
+
+Bugzilla has a new mechanism for generating reports of the current state of
+the bug database. It has two related parts: a table-based view, and several
+graphical views.
+
+The table-based view allows you to specify an x, y and z (multiple tables of
+data) axis to plot, and then restrict the bugs plotted using the standard
+query form. You can view the resulting data as an HTML or CSV export (e.g.:
+for importing into a spreadsheet).
+
+There are also bar, line and pie charts, which are defined in a very similar
+way. These views may be more appropriate for particular data types, and are
+suitable for saving and then putting into presentations or web pages.
+
+
+Generic Charting
+----------------
+
+Bugzilla has a new mechanism for generating charts (graphs over time) of any
+arbitrary search. This is known as "New Charts." Legacy data from the previous
+charting mechanism ("Old Charts") is migrated into the "New Charts" when you
+upgrade. The Old Charts mechanism remains, but is deprecated and will be
+removed in a future version of Bugzilla.
+
+Individual users can see/create charts as long as they are a member of the
+group specified in the Param 'chartgroup'. Data can be collected for
+personal charts every seven days (or a longer period, as set by the user).
+Charts created by an administrator can be made public (visible to all). Data
+is collected for administrator charts every day (or a longer period, as set
+by the admin).
+
+The data is collected by the collectstats.pl script, which an administrator
+will need to arrange to be run once every day (see the manual). Chart data can
+be plotted in a number of different ways, and different data sets can be
+plotted on the same graph for comparison.
+
+Please see the Known Bugs section for some important limitations relating to
+access controls on charts.
+
+
+Request System
+---------------
+
+The Request System (RS) is a set of enhancements that adds powerful flag
+(superset of the old attachment status) features to the bugs.
+
+RS allows for four states: off, granted, denied, and (optionally) requested,
+where "granted" is the equivalent of "on". These additions mean it is no
+longer necessary to define a status to negate another status (e.g.
+"needs-work" to negate "has-review") because negation is built into each
+status via the status' "denied" state. Bug statuses: Previously only
+attachments could have these kinds of statuses. RS enables them for bugs as
+well. This feature can be used to request and grant/deny certain properties
+for a bug, such as inclusion for a specific milestone or approval for checkin.
+This way, Bugzilla supports the natural decision-making process in your
+organization.
+
+- Requests: Flags can now optionally be made requestable, which means users
+ can ask other users to set them. When a user requests a flag, Bugzilla
+ emails the requestee and adds the request to a browsable queue so both the
+ requester and the requestee can keep track of its status. Once the
+ requestee fulfills the request by setting the flag to either granted or
+ denied, Bugzilla emails the requestee and removes the request from the
+ queue. This feature supports workflow like the mozilla.org code review
+ and milestone approval processes, whereby code is peer reviewed before
+ being committed and patches get approved by product release managers for
+ inclusion in specific product releases.
+
+- Product/component specificity: Previously flags were product-specific, and
+ if you wanted the same flag for multiple products you had to define
+ multiple flags with the same name. Flags are now
+ product/component-specific, and a single flag can be enabled or disabled
+ for multiple product/component combinations via inclusions and exclusions
+ lists. Flags are enabled for all combinations on their inclusions list
+ except those that appear on their exclusions list.
+
+
+Enterprise Group Support
+------------------------
+
+Bugzilla is no longer limited to 55 access control groups. Administrators can
+define an arbitrary number of access groups composed of individual users or
+other groups. The groups can be configured via the web interface to achieve a
+wide variety of access control policies. See the documentation section on
+'Groups And Group Controls' for details.
+
+
+User Wildcard Matching
+----------------------
+
+Sites can now enable the use of wildcards and substrings in bug entry and
+editing forms. If the user enters an incomplete username, he'll get a list of
+users that matched the given username.
+
+
+Support for "Insiders"
+----------------------
+
+If the 'insidergroup' parameter is defined, a specific group of users can be
+designated insiders who can designate comments and attachments as private to
+other insiders. These comments and attachments will be invisible to other
+users who are not members of the insiders group even if the bugs to which they
+apply are visible. Other insiders will see the comments and attachments with a
+visual tinting indicating that they are private.
+
+
+Time Tracking
+-------------
+
+Controls for tracking time spent fixing bugs are included in the bug form for
+members of the group specified by the 'timetrackinggroup' parameter. Any time
+comments are added to the bug, members of the time tracking group can add an
+amount of time they spent, and it's figured into the total and displayed at
+the top of the bug. Shown in the bug are your original estimate, the amount of
+time spent so far, the revised estimate of how much time is remaining, and
+your gain/loss on the original estimate.
+
+
+Authentication module/LDAP improvements
+---------------------------------------
+
+Bugzilla's authentication mechanisms have been modularized, making pluggable
+authentication schemes for Bugzilla a reality. Both the existing database and
+LDAP systems were ported as part of modularization process. Additionally, the
+CGI portion of the backend was redesigned to allow for authentication from
+other sources, including (theoretically) email, which will help Bug 94850.
+
+As part of this conversion, LDAP logins now use Perl's standard Net::LDAP
+module, which has no external library dependencies.
+
+
+Improved localization support
+-----------------------------
+
+Bugzilla administrators can now configure which languages are supported by
+their installations and automatically serve correct, localized content to
+users based on the HTTP 'Accept-Language' header sent from users' browsers.
+
+There are currently localized templates available for: Arabic, Belarusian,
+Chinese, French, German, Italian, Korean, Portuguese (Brazil) Spanish (Spain
+or Mexico) and Russian. These localized template packs are third-party
+contributions, may only be available for specific versions, and may not be
+supported in the future. (http://www.bugzilla.org/download/#localizations)
+
+
+Patch Viewer
+------------
+
+Viewing and reviewing patches in Bugzilla is often difficult due to lack of
+context, improper format and the inherent readability issues that raw patches
+present. Patch Viewer is an enhancement to Bugzilla designed to fix that by
+offering increased context, linking to sections, and integrating with Bonsai,
+LXR and CVS.
+
+
+Comment Reply Links
+-------------------
+
+In Edit Bug, each bug comment now includes a convenient (reply) link that
+quotes the comment text into the textarea. This feature is only enabled in
+Javascript-capable browsers, but causes no inconvenience to other user agents.
+
+
+Full-Text Search
+----------------
+
+It is now possible to query the Bugzilla database using full-text searching,
+which spans comments and summaries, and which searches for substrings and stem
+variations of the search term. Basically, it's like using Google.
+
+
+Email Address Munging
+---------------------
+
+The fact that raw email addresses are displayed in Bugzilla makes it trivial
+for bots that spamharvest to spider through Bugzilla, in particular, through
+Bugzilla's buglists. This change adds HTML obfuscation of email addresses as
+they appear in the Bugzilla web pages.
+
+
+Google-like Bug Search
+----------------------
+
+Bugzilla now includes a very simple, Google-like "Find a Specific Bug" page,
+in addition to its advanced search page.
+
+
+Miscellaneous Improvements
+--------------------------
+
+- The "Assigned To" field on the new bug page is now prefilled with the default
+ component owner.
+
+- A bug alias column is now available in the buglist page.
+
+- Lists of bugs containing errors in the sanity check page now have a "view as
+ buglist" link in addition to the individual bug links.
+
+- Autolinkification Page - It's now possible to apply Bugzilla's comment
+ hyperlinking algorithm to any text you like. This should be useful for status
+ updates and other web pages which give lists of bugs. The bug links created
+ include the subject, status and resolution of the bug as a tooltip.
+
+- There are more <link> tags on the links toolbar for navigating quickly between
+ different areas.
+
+- Buglists are now available as comma-separated value files (CSV) and JavaScript
+ (JS) as well as HTML and RDF.
+
+- Keywords and dependencies can now be entered during initial bug entry.
+
+- A CSS id signature unique to each Bugzilla installation is now added to the
+ <body> tag on Bugzilla pages to allow custom end-user CSS to explicitly affect
+ Bugzilla.
+
+- Perl's path has been changed to a normal /usr/bin/perl from the original
+ legacy "bonsaitools" path specifier.
+
+- A new "always-require-login" parameter allows administrators to require a
+ login before being able to view any page, except the front page.
+
+- A developer may add an attachment, and also reassign a bug to himself as part
+ of that single action.
+
+- Bugzilla is now able to use the replication facilities provided by the
+ MySQL database to handle updates from the main database to the secondaries.
+
+- Mail handling is now between 125% to 175% faster.
+
+- Guided Bug Entry: You can see a sample enter_bug.cgi template at
+ enter_bug.cgi?format=guided that "guides" users through the process of
+ filing a "good" bug. It needs to be modified before use in your organization.
+
+- There is now a "Give me some help" link on the Advanced Search page that will
+ enable pop-up help for every field on the page.
+
+- The Bugzilla administrator can now forbid users from marking bugs RESOLVED
+ when there are unresolved dependencies.
+
+
+All Changes
+-----------
+
+To see a list of EVERY bug that was fixed between 2.16 and 2.18 (over 1000),
+see: http://tinyurl.com/6m3e4
+
+
+What's Changed?
+***************
+
+
+Flag names
+----------
+
+Prerelease versions of Bugzilla 2.17 and 2.18 inadvertantly allowed
+commas and spaces in the names of flags, which due to the way they're
+processed, caused lots of internal havoc if you named flags to have
+any commas or spaces in them. Having commas or spaces in the names
+can cause errors in the notification emails and in the bug activity
+log. The ability to create new flags with these characters has been
+removed. If you have any existing flags that you named that way,
+running checksetup will attempt to automatically rename them by
+replacing commas and spaces with underscores.
+
+
+New Saved Search User Interface
+-------------------------------
+
+In previous Bugzilla versions, you could specify on the search page that you
+wanted to save a search and store it as a link in your footer. This option has
+now moved to the search results page (buglist.cgi), where you will see a
+"Remember search" button with a box next to it to enter the name of the search.
+
+You can manage your saved searches on the Preferences page.
+
+
+Rules for changing fields
+-------------------------
+
+There have been some changes to the rules governing who can change which fields
+of a bug report. The rules for Bugzilla version 2.16 and 2.18, along with
+differences between them, are listed below. Bear in mind that there are other
+restrictions on bug manipulation besides the ones listed below. In particular,
+the groups system enforces restrictions on who can create, edit, or even see
+any given bug.
+
+Bugzilla 2.16 rules:
+
+- anyone can make a null change;
+- anyone can add a comment;
+- anyone in the editbugs group can make any change;
+- the reporter can make any change to the status;
+- anyone in the canconfirm group can change the status
+ to any opened state (NEW, REOPENED, ASSIGNED).
+- anyone can change the status to any opened state
+ if the everconfirmed flag is set;
+- the owner, QA contact, or reporter can make any change
+ *except* changing the status to an opened state;
+- No other changes are permitted.
+
+[Note that these rules combine to allow the reporter to make any change
+to the bug.]
+
+Bugzilla 2.18 rules:
+
+- anyone can make a null change;
+- anyone can add a comment;
+- anyone in the editbugs group can make any change;
+- anyone in the canconfirm group can change the status
+ from UNCONFIRMED to any opened state;
+- the owner or QA contact can make any change;
+- the reporter can make any change *except*:
+ - changing the status from UNCONFIRMED to any opened state; or
+ - changing the target milestone; or
+ - changing the priority (unless the letsubmitterchoosepriority
+ parameter is set).
+- No other changes are permitted.
+
+The effective differences in the rules:
+
+- In 2.16, the reporter could always change anything about a bug.
+
+ In 2.18, the reporter can't:
+
+ - confirm the bug unless he is in the canconfirm group;
+ - change the target milestone;
+ - change the priority (unless the 'letsubmitterchoosepriority'
+ parameter is set;
+
+ (unless he is also the owner, the QA contact, or in the editbugs
+ group, in which case he can do all these things).
+
+- In 2.16, the owner or QA contact (if the 'useqacontact' parameter
+ is set) can't change the bug status to an opened status unless they
+ are also the reporter, or have editbugs or canconfirm, or the
+ everconfirmed flag is set on the bug).
+
+ In 2.18 the owner or QA contact can make any change to a bug.
+
+- In 2.16, a member of the canconfirm group can set the status
+ to any opened status.
+
+ In 2.18 this is only possible if the status was previously
+ the unconfirmed status.
+
+- In 2.16, the status can be set to anything by anybody
+ if the 'everconfirmed' flag is set.
+
+ In 2.18, this authorization code does not pay any attention
+ to the 'everconfirmed' flag.
+
+
+Removed Features
+****************
+
+- Please note that Bugzilla no longer supports MySQL 3.22. The minimum required
+ version is now 3.23.41.
+
+- The "shadow database" mechanism is no longer used. Instead, use MySQL's
+ built-in replication feature.
+
+- If you have placed any comments in the localconfig file, they may be removed
+ by checksetup.pl.
+
+
+Code Changes Which May Affect Customizations
+********************************************
+
+- A mechanism (called "Template Hooks") for third party extensions to plug into
+ existing templates without having to patch or replace distributed templates
+ has been added. More information on this can be found in the documentation.
+
+- Header output now uses CGI.pm, in a step towards enabling mod_perl
+ compatibility. This change will affect users that had customized charsets in
+ their CGI files: previously the charset had to be added everywhere that
+ printed the Content-Type header; now it only needs changing in one spot, in
+ Bugzilla/CGI.pm.
+
+- $::FORM{} and $::COOKIE{} are deprecated. Use the $cgi methods to access
+ them.
+
+- $::userid is gone in favor of Bugzilla->user->id
+
+- ConnectToDatabase() is gone (it's done automatically when you initialize the
+ Bugzilla object)
+
+- quietly_check_login() and confirm_login() are gone, use Bugzilla->login()
+ with parameters for whether the login is required or not.
+
+- Use Bugzilla->user->login in place of $::COOKIE{Bugzilla_login}
+
+- You can tell if there's a user logged in or not by using
+ Bugzilla->user rather than looking for $::userid==0.
+ In new 2.18 code, use defined(Bugzilla->user) && (Bugzilla->user->id)
+ In 2.20, this will become just (Bugzilla->user->id)
+ In templates, always test [% IF user.id %] rather than [% IF user %]
+
+- SendSQL() and related calls are deprecated, and the various $dbh methods
+ should be used instead, such as $dbh->prepare() and $dbh->execute().
+ Bugzilla->dbh is the $dbh handle to use.
+
+
+Recommended Practice for the Upgrade
+************************************
+
+Note About Upgrading From MySQL With ISAM Tables
+------------------------------------------------
+As previously noted in the Dependency Requirements MySQL is now required
+to be at least version 3.23.41. This implies that all tables of type ISAM will
+be converted by the checksetup.pl script to MyISAM.
+
+
+Steps for Upgrading
+-------------------
+
+1) View the Sanity Check (sanitycheck.cgi) page on your installation before
+ upgrading.
+
+2) As with any upgrade it is recommended that you make a backup of the
+ Bugzilla database before you upgrade, perhaps by using mysqldump.
+
+ Example:
+
+ mysqldump -u root -p --databases bugs > bugs.db.backup
+
+3) Replace the files in your installation, or you can try to use CVS to upgrade.
+ The Bugzilla.org website has instructions on how to do the actual
+ installation.
+
+4) Make sure that you run checksetup.pl after you install the new version.
+
+5) View the Sanity Check page again after you run checksetup.pl.
+
+6) It is recommended that, if possible, you fix any problems you find
+ immediately. Failure to do this may mean that Bugzilla will not work
+ correctly. Be aware that if the sanity check page contains more errors after
+ an upgrade, it doesn't necessarily mean there are more errors in your
+ database, as additional tests are added to the sanity check over time, and
+ it is possible that those errors weren't being checked for in the old
+ version.
+
+
+Outstanding Issues
+******************
+
+These are known problems with the release that we think you should know about.
+They each have a bug number for http://bugzilla.mozilla.org/
+
+- If at any time you upgraded from a version of Bugzilla between 2.17.4 -
+ 2.17.7 to either 2.18rc3 or 2.19.1, you must manually fix your New Charts in
+ order for them to work. See the following link for instructions on how to do
+ this: https://bugzilla.mozilla.org/show_bug.cgi?id=276237#c18
+ If you are using 2.18rc3, but did not upgrade from version 2.17.4 or newer,
+ then you don't need to do this.
+
+- bug 37765: If you use an MTA other than sendmail (such as Postfix, Exim,
+ etc.) you MUST turn on the "sendmailnow" parameter or Bugzilla will not send
+ e-mail correctly.
+
+- bug 276230: The support for restricting access to particular Categories of
+ New Charts is not complete. You should treat the 'chartgroup' Param as the
+ only access mechanism available. However, additionally, charts migrated from
+ Old Charts will be restricted to the groups that are marked MANDATORY for
+ the corresponding Product. There is currently no way to change this
+ restriction, and the groupings will not be updated if the group configuration
+ for the Product changes.
+
+- bug 69621: If you rename or remove a keyword that is in use on bugs, you will
+ need to rebuild the "keyword cache" by running sanitycheck.cgi and choosing
+ the option to rebuild the cache when it asks. Otherwise keywords may not show
+ up properly in search results.
+
+- (No Bug Number) If you have a lot of non-ASCII data in your Bugzilla (for
+ example, if you use a translation of Bugzilla), don't enable the XS::Stash
+ option when you install the Template Toolkit, or your Bugzilla installation
+ may become slow. This problem is fixed in a not-yet-released version of the
+ Template Toolkit (after 2.14).
+
+- bug 266579: Users may be able to circumvent not having "canconfirm" privileges
+ in some circumstances. This is fixed starting with 2.19.3, but will not
+ be fixed in any 2.18 release, as the changes required to fix it are quite
+ large.
+
+- bug 99215: Attachment changes have no mid-air collision detection, unlike bug
+ changes.
+
+- bug 57350: Searching using the "commenter is" option may be VERY slow. Note
+ that searching for "field: comment, changed by: user@domain.com" is fast,
+ though.
+
+- bug 151509: Using the boolean chart option "contains the string" with the
+ "flag name" field or certain other fields will cause Bugzilla to emit an
+ error. This is fixed in 2.20rc1, but will not be fixed in the 2.18 series.
+
+- bug 234159: Bugzilla may sometimes send multiple notices in one email.
+
+- bug 237107: If you search for attachment information using the Boolean Charts
+ at the bottom of the Advanced Query page, bugs without attachments will not
+ show up in the result list.
+
+
+Security Fixes In 2.18 Releases
+*******************************
+
+Version 2.18
+------------
+
+Summary: XSS in Internal Error messages in Bugzilla 2.16.7 and 2.18rc3
+CVE Name: CAN-2004-1061
+Reference: https://bugzilla.mozilla.org/show_bug.cgi?id=272620
+Details:
+ It is possible to send a carefully crafted URL to Bugzilla designed to
+trigger an error message. The Internal Error message includes javascript code
+which displays the URL the user is visiting. The javascript code does not
+escape the URL before displaying it, allowing scripts contained in the URL to
+be executed by the browser. Many browsers do not allow unescaped URLs to be
+sent to a webserver (thus complying with RFC 2616 section 2.3.1 and RFC 2396
+section 2.4.3), and are thus immune to this issue.
+ Browsers which are known to be immune: Firefox 1.0, Mozilla 1.7.5,
+Camino 0.8.2, Netscape 7.2, Safari 1.2.4
+ Browsers known to be susceptible: Internet Explorer 6 SP2,
+Konqueror 3.2
+ Browsers not listed here have not been tested.
+
+
+Version 2.18.1
+--------------
+
+Two security issues were fixed in Bugzilla 2.18.1, neither of them
+critical.
+
+See http://www.bugzilla.org/security/2.16.8/ for details.
+
+
+Version 2.18.2
+--------------
+
+Two security issues were fixed in Bugzilla 2.18.2. One of them
+is a major Information Leak/Unauthorized Bug Change. The other
+is a minor Information Leak.
+
+See http://www.bugzilla.org/security/2.18.1/ for details.
+
+
+Detailed Version-To-Version Release Notes
+*****************************************
+
+*********************************************************
+*** USERS UPGRADING FROM ALL VERSIONS PRIOR TO 2.16.7 ***
+*********************************************************
+
+*** Security fixes ***
+
+- It is possible to send a carefully crafted HTTP POST message to
+ process_bug.cgi which will remove keywords from a bug even if you don't have
+ permissions to edit all bug fields (the "editbugs" permission). Such changes
+ are reported in "bug changed" email notifications, so they are easily
+ detected and reversed if someone abuses it. Users are now prevented from
+ making changes to keywords if they do not have editbugs privileges. (bug
+ 252638)
+
+*** Bug fixes of note ***
+
+- Enforce a minimum of 10 minutes between attempts to reset a password, so
+ we don't mailbomb the user if someone submits the form many times in a
+ row. (bug 250897)
+
+- Put products in alphabetical order on the create attachment status page.
+ (bug 251427)
+
+- Specify MyISAM as the table type when creating new tables. MySQL 4.1 and
+ up default to InnoDB, which doesn't support some of the indexing methods
+ that we use. (bug 263165)
+
+*********************************************************
+*** USERS UPGRADING FROM ALL VERSIONS PRIOR TO 2.16.6 ***
+*********************************************************
+
+*** Security fixes ***
+
+- If Bugzilla is configured to hide entire products from some users, both
+ duplicates.cgi and the form for mass-editing a list of bugs in buglist.cgi
+ can disclose the names of those hidden products to such users.
+ (bugs 234825 and 234855)
+
+- Several administration CGIs echo invalid data back to the user without
+ escaping it. (bug 235265)
+
+- A user with privileges to grant membership to any group (i.e. usually an
+ administrator) can trick editusers.cgi into executing arbitrary SQL.
+ (bug 244272)
+
+*** Bug fixes of note ***
+
+- Allow XML import to function when there are regexp metacharacters in product
+ names (bug 237591)
+
+- Allow the bug_email.pl contrib script to work with useqacontact (bug 239912)
+
+- Improve the error message used by checksetup.pl when the MySQL requirements
+ are not met (bug 240228)
+
+- Elimnate the warning in checksetup.pl about the minimum sendmail version (bug
+ 240060)
+
+- $webservergroup now defaults to group 'apache' in new installations (bug
+ 224477)
+
+- Correct a situation where a bugmail message could be sent twice to a user
+ being added to the CC list if the address was entered in a different case
+ than the user registered with. (bug 117297)
+
+- Various documentation updates
+
+*********************************************************
+*** USERS UPGRADING FROM ALL VERSIONS PRIOR TO 2.16.4 ***
+*********************************************************
+
+*** Bug fixes of note ***
+
+- Fix a "used only once" warning that ocurred only in perl 5.00503
+ (bug 2321691)
+
+- When a user is creating a new account and enters an invalid email
+ address, the error page sent the "Content-type" header twice, causing
+ the second one to be visible at the top of the page.
+ (bug 137121)
+
+- An HTML encoding issue which only affected Internet Explorer was
+ corrected in the "Change several bugs at once" page.
+ (bug 181106)
+
+- During initial setup, using invalid characters in the administrator
+ password would present an error message stating your password was
+ too long or too short instead of telling you it had invalid
+ characters.
+ (bug 166755)
+
+- When a user reset their own password via an emailed token, the new
+ password in the first field would be accepted if the second password
+ field was left blank.
+ (bug 123077)
+
+- Reopening bugs from the "change several bugs at once" page now works.
+ (bug 95430)
+
+- Fix a regression in xml.cgi caused by the previous bugfix for MySQL
+ SUM() changes. The original fix didn't work properly either.
+ (bug 225474)
+
+- No longer use server push with the "Safari" browser, which claims to
+ use the Mozilla layout engine but doesn't.
+ (bug 188712)
+
+- Creating a shadow database no longer fails with taint mode errors.
+ (bug 227510)
+
+- If you change your cookiepath setting at some stage (because you have
+ moved the directory Bugzilla resides on your webserver), users can
+ have login cookies with the old cookiepath, and their browsers will
+ send multiple logincookies. Bugzilla now uses the first rather than
+ the last in order to get the most specific cookie which will be the
+ correct one.
+ (bug 121419)
+
+- Fixed a regression caused by the previous DBD::mysql fixes, that
+ caused older versions of DBD::mysql to break due to not supporting
+ the new DBI syntax.
+ (bug 224815)
+
+- Bugzilla no longer sends out invalid dates for cookie expiry. This
+ bug had no known user visible ramifications.
+ (bug 228706)
+
+- Update the shadow database parameters description to tell the user
+ about permissions requirements for creating a shadow database.
+ (bug 227513)
+
+- Various documentation updates.
+
+*********************************************************
+*** USERS UPGRADING FROM ALL VERSIONS PRIOR TO 2.16.3 ***
+*********************************************************
+
+*** SECURITY ISSUES RESOLVED ***
+
+- A user with 'editproducts' privileges (i.e. usually an administrator)
+ can select arbitrary SQL to be run by the nightly statistics cron job
+ (collectstats.pl), by giving a product a special name.
+ (bug 214290)
+
+- A user with 'editkeywords' privileges (i.e. usually an administrator)
+ can inject arbitrary SQL via the URL used to edit an existing keyword.
+ (bug 219044)
+
+- When deleting products and the 'usebuggroups' parameter is on, the
+ privilege which allows someone to add people to the group which is
+ being deleted does not get removed, allowing people with that
+ privilege to get that privilege for the next group that is created
+ which reuses that group ID. Note that this only allows someone who
+ had been granted privileges in the past to retain them.
+ (bug 219690)
+
+- If you know the email address of someone who has voted on a secure
+ bug, you can access the summary of that bug even if you do not have
+ sufficient permissions to view the bug itself.
+ (bug 209376)
+
+*** Bug fixes of note ***
+
+Perl 5.8.0 Compatibility fixes:
+
+- Two taint errors were fixed, one in process_bug.cgi, and
+ another in post_bug.cgi.
+ (bugs 220332 and 177828)
+
+MySQL 4.0 Compatibility fixes:
+
+- A cosmetic fix was applied to votes.cgi (if there were no
+ votes, the "0" was not displayed) due to a change in semantics
+ in SUM() in MySQL 4.0.
+ (bug 217422)
+
+DBD::mysql > 2.1026 Compatibility fixes:
+
+- DBD::mysql versions after 2.1026 return the table list quoted, which
+ broke the existing "table exists" check in checksetup.pl, which caused
+ the second and subsequent attempts to run checksetup.pl to fail.
+ (bug 212095)
+
+Miscellaneous bug fixes:
+
+- A Mozilla-specific reference was removed from one of the report
+ templates.
+ (bug 221626)
+
+- It was possible to enter a situation where you were unable to get to
+ editparams.cgi to turn the shutdownhtml param back off after you
+ turned it on when Apache was configured to run Bugzilla in suexec
+ mode.
+ (bug 213384)
+
+- The processmail rescanall task would not send e-mails about more than
+ one bug to the same address.
+ (bug 219508)
+
+- If Bugzilla hadn't been accessed in the last hour when the
+ collectstats.pl or whineatnews.pl cron jobs ran, the versioncache
+ would get recreated with the file owner being the user the cron job
+ was running as (usually not the webserver user), causing subsequent
+ access to Bugzilla by the webserver to fail until the permissions were
+ fixed. Now if versioncache isn't readable when accessing from the
+ webserver, we pretend it doesn't exist and recreate it again.
+ (bug 160422)
+
+- The 'sendmailnow' param is now on by default in new installations
+ (this does not affect existing installations).
+ (bug 146087)
+
+- The 008filter.t test would fail if you had multiple language packs
+ installed. It now properly tests all of the installed language packs.
+ (bug 203318)
+
+- A few minor documentation changes were committed.
+
+*********************************************************
+*** USERS UPGRADING FROM ALL VERSIONS PRIOR TO 2.16.2 ***
+*********************************************************
+
+*** SECURITY ISSUES RESOLVED ***
+
+- A cross site scripting (XSS) vulnerability was fixed in which bug
+ summaries were not properly filtered when a user viewed a dependency graph
+ allowing JavaScript to be embedded on that page.
+ (bug 192661)
+
+- Several XSS vulnerabilities were fixed in which user
+ input was not escaped when being displayed. A new
+ test has been added to warn about unfiltered data in template
+ files (t/008filter.t).
+ (bug 192677)
+
+- An issue was fixed in which the QA contact was still treated as the QA
+ contact even after the 'useqacontact' setting was turned off. This also
+ allowed the QA contact to edit the security groups and view secured bugs that
+ he/she was allowed to access prior to the 'useqacontact' setting being
+ deactivated.
+ (bug 194394)
+
+- Fixed a situation where an attacker (with local access to the webserver)
+ could overwrite any file on the webserver to which the webserver user
+ has write access by creating appropriately named symbolic links in the
+ data and webdot directories (world-writable in many configurations).
+ Bugzilla now uses File::Temp to create secure temporary files. File::Temp
+ is part of the Perl distribution for Perl 5.6.1 and later, but if you're
+ using an older version of Perl you'll need to install it with CPAN.
+ (bug 197153)
+
+** IMPORTANT CHANGES ***
+
+- New module requirement: File::Temp, as mentioned above.
+
+*** Bug fixes of note ***
+
+- An issue was fixed in which administrator rights could be removed from an
+ administrator who deleted a product while the 'usebuggroups' setting is
+ activated.
+ (bug 157704)
+
+- Fixed an issue in which importxml.pl would fail the test suite when running
+ under perl 5.8.0 with the optional XML::Parse module.
+ (bug 172331)
+
+- There was previously a bug in CGI.pl in which the following warning
+ would be given under certain conditions:
+ "Character in "c" format wrapped at CGI.pl..."
+ This is now fixed. In some cases the warning was filling up web server log
+ files.
+ (bug 194125)
+
+- Fixed a bug in which long component names (in excess of 50 characters) would
+ be accepted when creating the component but would cause problems when trying
+ to use that component on a bug because it would get truncated. It is now no
+ longer possible to create components with names in excess of 50 characters.
+ (bug 197180)
+
+- Fixed a bug in checksetup.pl in which permissions were not being fixed
+ on the 'data/comments' file, the quip file.
+ (bug 160279)
+
+*****************************************************************
+*** USERS UPGRADING FROM 2.16.1 OR EARLIER, 2.14.4 OR EARLIER ***
+*****************************************************************
+
+*** SECURITY ISSUES RESOLVED ***
+
+- Fixed a cross site scriptability issue in quips. This is only a problem
+ if quips with HTML could have been inserted into your quips files. Bugzilla
+ has not allowed this since 2.12.
+ (bug 179329)
+- checksetup.pl will now attempt to prevent access to "editor backups" of
+ localconfig.
+ (bug 186383)
+- collectstats.pl no longer makes data/mining (which contains graphing
+ information) world writeable.
+ (bug 183188)
+
+***********************************************
+*** USERS UPGRADING FROM 2.16.0 OR EARLIER ***
+***********************************************
+
+*** SECURITY ISSUES RESOLVED ***
+
+- Apostrophes were not properly handled in email addresses. This was a
+ regression introduced in 2.16. It is not known whether this was
+ exploitable.
+ (bug 165221)
+
+See also next major section.
+
+*** Bug fixes of note ***
+
+- The VERSION cookie which allowed the previously entered version of a product
+ to be remembered was not correctly set. It was only set as a session
+ cookie, and under some circumstances could interfere with other cookies
+ (such as the login information) send at the same time.
+ (bug 160227)
+
+- importxml.pl would fail if the versioncache needed to be updated.
+ (bug 164464)
+
+- Bug changes going through intermediate pages would munge fields with
+ multiple fields, such as CCs.
+ (bug 161203)
+
+- On failure in template->new, Bugzilla will now die rather than futilely
+ attempt to use an error template.
+ (bug 166023)
+
+- Fixed a problem where checksetup had problems converting old installations
+ that didn't have a duplicates table.
+ (bug 151619)
+
+- Fixed a problem that caused taint errors when viewing or editing user
+ preferences with Perl 5.005 and Template 2.08.
+ (bug 160710)
+
+See also next section.
+
+******************************************************
+*** USERS UPGRADING FROM 2.16.0, 2.14.3 OR EARLIER ***
+******************************************************
+
+*** SECURITY ISSUES RESOLVED ***
+
+- When a new product is added to an installation with 47 groups or more and
+ "usebuggroups" is enabled, the new group will be assigned a groupset bit
+ using Perl math that is not exact beyond 2^48. This results in the new
+ group being defined with a "bit" that has several bits set. As users are
+ given access to the new group, those users will also gain access to
+ spurious lower group privileges. Also, group bits were not always reused
+ when groups were deleted.
+ (bug 167485)
+
+- The email interface had another insecure single parameter system call. This
+ could potentially allow arbitrary shell commands to be run. This file is
+ not supported at this time, but as long as we knew about the problem, we
+ couldn't overlook it.
+ (bug 163024)
+
+*** Bug fixes of note ***
+
+- The email interface was broken. This was a 2.14.3 regression. This file
+ is not supported at this time, but as long as we knew about the problem, we
+ couldn't overlook it.
+ (bug 160631)
+
+***********************************************
+*** USERS UPGRADING FROM 2.14.5 OR EARLIER ***
+***********************************************
+
+*** SECURITY ISSUES RESOLVED ***
+
+- The bug reporter could set the priority even when
+ 'letsubmitterchoosepriority' was off.
+ (bug 63018)
+
+- Most CGIs are now templatized. This helps to make it
+ easier to remember to HTML filter values and easier to spot
+ when they are not, preventing cross site scripting attacks.
+ (bug 86168)
+
+- Most CGIs now run in taint mode. This helps to prevent
+ failure to validate errors.
+ (bug 108982)
+
+*** IMPORTANT CHANGES ***
+
+- 2.16 introduces "templatization", a new feature that allows
+ administrators to easily customize the HTML output (the "look and feel")
+ of Bugzilla without altering Perl code. Bugzilla uses the
+ "Template Toolkit" for this. Please see the "Template Customization"
+ section of the Bugzilla Guide for more details.
+
+ Administrators who ran the 2.15 development version with custom
+ templates should check the templates are still valid, as file names
+ and file paths have changed.
+
+ Most output is now templatized. This process will be complete next
+ milestone.
+
+ For speed, compiled templates are cached on disk. If you modify the
+ templates, the toolkit will normally detect the changes, and recompile the
+ changed templates.
+
+ Adding new directories anywhere inside the template directory may cause
+ permission errors if you don't have a webservergroup specified in
+ localconfig. If you see these, rerun checksetup.pl as root. If you do not
+ have root access, or cannot get someone who does to do this for you, you can
+ rename the data/template directory to data/template.old (or any other name
+ Bugzilla doesn't use). Then rerun checksetup.pl to regenerate the compiled
+ templates.
+ (bug 86168, 97832)
+
+- Administrators can now configure maximum attachment sizes. These
+ should remain below the maximum size for your MySQL server, or you
+ will get obscure MySQL errors if you attach a bigger attachment.
+
+ To find out the current size attachment that MySQL can accept, type
+ the command 'mysqladmin variables' and find out the value of the
+ 'max_allowed_packet' varible in bytes.
+
+ To change the maximum size that MySQL can accept you can alter this
+ variable in your 'my.cnf' file.
+ (bug 91664)
+
+- Perl 5.004 is no longer supported because the Template Toolkit
+ requires 5.005.
+ (bug 97721)
+
+- New module requirements: Text::Wrap, Template [requires AppConfig],
+ File::Spec.
+ (bugs 97784, 84338, 103778)
+
+- The index page is now a CGI instead of an HTML page. You should remove
+ any existing index.html file and make sure your web server allows index.cgi
+ to be the default page in a directory. If you are not able to do that you
+ can instead set index_html in the 'localconfig' file to 1 and checksetup.pl
+ will create a redirect page for you.
+ (bug 80183)
+
+- It is now recommended that administrators run "processmail rescanall"
+ after upgrading to 2.16 or beyond.
+
+ This will send out notification emails for changes that were
+ made but not emailed, due to Bugzilla bugs. All known
+ causes of this have been fixed in this version (bug 104589 and 99519).
+
+ It is also recommended that this be run nightly to avoid
+ lengthy delays in future if this problem reoccurs.
+ (bug 106377)
+
+- In parallel with templatization, a lot of changes have been made to the HTML
+ output of the Bugzilla CGIs. This could break code that attempts to parse
+ such code. For example, this breaks mozbot.
+ (no bug number)
+
+- The "HTML template" parameters (headerhtml, bodyhtml, footerhtml,
+ errorhtml, bannerhtml, blurbhtml, mostfreqhtml, entryheaderhtml) have now
+ been moved to Template Toolkit templates. If you have modified these
+ parameters you will need to make corresponding changes to the corresponding
+ templates. Your old parameter values will be moved to a file called
+ old-params.txt by checksetup.pl.
+
+ The old parameters correspond to files in template/en/default as follows:
+
+ headerhtml: global/header.html.tmpl
+ footerhtml: global/footer.html.tmpl
+ bannerhtml: global/banner.html.tmpl
+ blurbhtml: global/banner.html.tmpl
+ mostfreqhtml: reports/duplicates*.html.tmpl
+ entryheaderhtml: bug/create/user-message.html.tmpl
+
+ (bug 140437)
+
+*** Other changes of note ***
+
+- The query page has been redesigned for better user friendliness.
+ (bug 98707)
+- Users can now change their email account.
+ (bug 23067)
+- "Dependent Bug Changed" notification emails now contain the
+ dependent bug's summary and URL.
+ (bug 28736, 113383)
+- Bugs with severity "critical", "blocker", and "enhancement" are
+ visually differentiated on bug lists for browsers with sufficient
+ CSS support.
+ (bug 28884)
+- Bugzilla now has a sidebar for the Mozilla browser.
+ (bug 37339)
+- A link to just created attachments now appears in notification
+ email.
+ (bug 66651)
+- Comments now have numbers and can be referenced with
+ autohyperlinkifying similar to bugs.
+ (bug 71840)
+- The attachment system has been rewritten, supporting new
+ "attachment statuses" (like keywords, but for attachments),
+ the ability to obsolete attachments, edit attachment MIME type,
+ and edit whether the attachment is a patch.
+ (bugs 84338, 75176)
+- syncshadowdb now supports a configurable temp file location,
+ and properly shuts down Bugzilla while running.
+ (bug 75840)
+- Dependency tree now lets you exclude resolved bugs and bugs
+ below a specified depth.
+ (bugs 83058)
+- The "strictvaluechecks" parameter has gone away. These checks
+ are now always done.
+ (bug 119715)
+- The midair collision page now shows all changes since the bug
+ page was loaded, not just the last one.
+ (bug 108312)
+- Added support for making dependency graphs with 'dot', which
+ is better at creating complex graphs than 'webdot'.
+ (bug 120537)
+
+*** Bug fixes of note ***
+
+- Bugzilla scripts are now usually not terminated when the browser
+ window they are running in is closed. This caused hard to
+ reproduce bugs.
+ (bug 104589)
+- On browsers that "reflow" the page, large component / milestone /
+ version fields were extremely slow to reflow when you altered
+ the product field.
+ (bug 96534)
+- The selection in the component / milestone / version fields is
+ no longer lost when you change the selection in the product
+ field or use the back/forward buttons in your browser to return
+ to the page.
+ (bug 97966)
+- You could not reverse dependencies in one step.
+ (bug 82143)
+- Mass reassignment of non-open bugs will no longer reopen them.
+ (bug 30731)
+- Attempting to bulk change no bugs will now give a user-friendly
+ error message.
+ (bug 90333)
+- If you make a change to a bug where you only add yourself to CC,
+ email notifications are now properly sent out for MySQL 3.23.
+ (bug 99519)
+- Bug entry now properly validates the data it has been sent.
+ (bug 107743)
+- Midair collision checks will now properly work in all situations
+ where dependencies have changed.
+ (bug 73502)
+- Browsers can no longer corrupt the params file if they use the "wrong"
+ end-of-line markers.
+ (bug 92500)
+- The MySQL port defined in localconfig is now properly honoured.
+ (bug 98368)
+- Apostrophes in component/milestone/version names no longer cause
+ a problem on the query page.
+ (bug 30689/42810)
+- File attachment comments will now wrap.
+ (bug 52060)
+- Saved queries are no longer mangled if you need to log in again,
+ for example if you had cookies off.
+ (bug 38835)
+- Bug counts (on reports.cgi) were very slow if you had to
+ count a lot of bugs.
+ (bug 63249)
+- 2.14 introduced options to let people see a bug when their name
+ is on it but who aren't in the groups the bug is restricted
+ to. These only allowed the people to view the bugs directly,
+ and not see them on buglists and receive email about them.
+ (bugs 95024, 97469)
+- A new 'cookiepath' parameter on editparams.cgi allows multiple
+ Bugzilla installations to exist on one host without problems.
+ (bug 19910)
+- whineatnews.pl now respects the 'sendmailnow' parameter.
+ (bug 52782)
+- The query page came up even when Bugzilla was shut down.
+ (bug 121747)
+- Quicksearch gave a weird error message when Bugzilla was
+ shut down.
+ (bug 121741)
+- Operating system detection fixes.
+ (bugs 92763, 135666)
+- QA contacts now receive emails when a new bug is created and
+ their only email preference was being added or removed from QA.
+ (bug 143091)
+
+***********************************************
+*** USERS UPGRADING FROM 2.14.4 OR EARLIER ***
+***********************************************
+
+See section above about users upgrading from 2.16.1 or earlier,
+2.14.4 or earlier.
+
+***********************************************
+*** USERS UPGRADING FROM 2.14.3 OR EARLIER ***
+***********************************************
+
+See section above about users upgrading from 2.16.0 or earlier.
+
+***********************************************
+*** USERS UPGRADING FROM 2.14.2 OR EARLIER ***
+***********************************************
+
+*** SECURITY ISSUES RESOLVED ***
+
+- Basic maintenance on contrib/bug_email.pl and
+ contrib/bugzilla_email_append.pl which also fixes a
+ possible security hole with a misuse of a system() call.
+ These files are not supported at this time, but as long
+ as we knew about the problem, we couldn't overlook it.
+ (bug 154008)
+
+*** Bug fixes of note ***
+
+- The fix for bug 130821 in 2.14.2 broke being able to sort
+ bug lists on more than one field. buglist.cgi now allows
+ you to sort on more than one field again.
+ (bug 152138)
+
+***********************************************
+*** USERS UPGRADING FROM 2.14.1 OR EARLIER ***
+***********************************************
+
+*** SECURITY ISSUES RESOLVED ***
+
+- queryhelp.cgi no longer shows confidential products to
+ people it shouldn't.
+ (bug 126801)
+
+- It was possible for a user to bypass the IP check by
+ setting up a fake reverse DNS, if the Bugzilla web server
+ was configured to do reverse DNS lookups. Apache is not
+ configured as such by default. This is not a complete
+ exploit, as the user's login cookie would also need to
+ be divulged for this to be a problem.
+ (bug 129466)
+
+- In some situations the data directory became world writeable.
+ (bug 134575)
+
+- Any user with access to editusers.cgi could delete a user
+ regardless of whether 'allowuserdeletion' is on.
+ (bug 141557)
+
+- Real names were not HTML filtered, causing possible cross
+ site scripting attacks.
+ (bug 146447, 147486)
+
+- Mass change would set the groupset of every bug to be the
+ groupset of the first bug.
+ (bug 107718)
+
+- Some browsers (eg NetPositive) interacted with Bugzilla
+ badly and could have various form problems, including
+ removing group restrictions on bugs.
+ (bug 148674)
+
+- It was possible for random confidential information to be
+ divulged, if the shadow database was in use and became
+ corrupted.
+ (bug 92263)
+
+- The bug list sort order is now stricter about the SQL it will accept,
+ ensuring you use correct column name syntax. Before this, there were
+ some syntax checks, so it is not known whether this problem was
+ exploitable.
+ (bug 130821)
+
+********************************************
+*** USERS UPGRADING FROM 2.14 OR EARLIER ***
+********************************************
+
+The 2.14.1 release fixes several security issues that became
+known to us after the Bugzilla 2.14 release.
+
+*** SECURITY ISSUES RESOLVED ***
+
+- If LDAP Authentication was being used, Bugzilla would allow
+ you to log in as anyone if you left the password blank.
+ (bug 54901)
+
+- It was possible to add comments or file a bug as someone else
+ by editing the HTML on the appropriate submission page before
+ submitting the form. User identity is checked now, and the
+ form values suggesting the user are now ignored.
+ (bug 108385, 108516)
+
+- The Product popup menu on the show_bug form listed all
+ products, even if the user didn't have access to all of them.
+ It now only shows products the user has access to (and the
+ product the bug is in, if the user is viewing it because of
+ some other override).
+ (bug 102141)
+
+- If a user had any blessgroupset privileges (the ability to
+ change only specific privileges for other users), it was
+ possible to change your own groupset (privileges) by
+ altering the page HTML before submitting on editusers.cgi.
+ (bug 108821)
+
+- An untrusted variable was echoed back to user in the HTML
+ output if there was a login error while editing votes.
+ (bug 98146)
+
+- buglist.cgi had an undocumented parameter that allowed you
+ to pass arbitrary SQL for the "WHERE" part of a query.
+ This has been disabled.
+ (bug 108812)
+
+- It was possible for a user to send arbitrary SQL by inserting
+ single quotes in the "mybugslink" field in the user
+ preferences.
+ (bug 108822)
+
+- buglist.cgi was not validating that the field names being
+ passed from the "boolean chart" query form were valid field
+ names, thus allowing arbitrary SQL to be inserted if you
+ edited the HTML by hand before submitting the form.
+ (bug 109679)
+
+- long_list.cgi was not validating that the bug ID parameter
+ was actually a number, allowing arbitrary SQL to be inserted
+ if you edited the HTML by hand.
+ (bug 109690)
+
+********************************************
+*** USERS UPGRADING FROM 2.12 OR EARLIER ***
+********************************************
+
+*** SECURITY ISSUES RESOLVED ***
+
+- Multiple instances of unauthorized access to confidential
+ bugs have been fixed.
+ (bug 39524, 39526, 39527, 39531, 39533, 70189, 82781)
+
+- Multiple instances of untrusted parameters not being
+ checked/escaped was fixed. These included definite security
+ holes.
+ (bug 38854, 38855, 38859, 39536, 87701, 95235)
+
+- After logging in passwords no longer appear in the URL.
+ (bug 15980)
+
+- Procedures to prevent unauthorized access to confidential
+ files are now simpler. In particular the shadow directory
+ no longer exists and the data/comments file no longer needs
+ to be directly accessible, so the entire data directory can
+ be blocked. However, no changes are required here if you
+ have a properly secured 2.12 installation as no new files
+ must be protected.
+ (bug 71552, 73191)
+
+- If they do not already exist, checksetup.pl will attempt to
+ write Apache .htaccess files by default, to prevent
+ unauthorized access to confidential files. You can turn this
+ off in the localconfig file.
+ (bug 76154)
+
+- Sanity check can now only be run by people in the 'editbugs'
+ group. Although it would be better to have a separate
+ group, this is not possible until the limitation on the
+ number of groups allowed has been removed.
+ (bug 54556)
+
+- The password is no longer stored in plaintext form. It will
+ be eradicated next time you run checksetup.pl. A user must
+ now change their password via a password change request that
+ gets validated at their e-mail account, rather than have it
+ mailed to them.
+ (bug 74032)
+
+- When you are using product groups and you move a bug between
+ products (single or mass change), the bug will no longer be
+ restricted to the old product's group (if it was) and will
+ be restricted to the new product's group.
+ (bug 66235)
+
+- There are now options on a bug to choose whether the
+ reporter, and CCs can access a bug even if they aren't in
+ groups the bug it is restricted to.
+ (bug 39816)
+
+- You can no longer mark a bug as a duplicate of a bug you
+ can't see, and if you mark a bug a duplicate of a bug
+ the reporter cannot see you will be given options as to
+ what to do regarding adding the reporter of the resolved
+ bug to the CC of the open bug.
+ (bug 96085)
+
+*** IMPORTANT CHANGES ***
+
+- Bugzilla 2.14 no longer supports old email tech. Upon
+ upgrading, all users will be moved over to new email tech.
+ This should speed up upgrading for installations with
+ a large number of bugs.
+ (bug 71552)
+
+- There is new functionality for people to see why they are
+ receiving notification mails.
+
+ Previously, some people filtered old email tech
+ notifications depending on whether they were in the To or the
+ CC header, in order to get a limited way of determining why
+ they were receiving the notification for filtering purposes.
+
+ Existing installations will need to make changes to support
+ this feature. The receive reasons can be added to the
+ notifications as a header and/or in the body. To add these
+ you will need to modify your newchangedmail parameter on
+ editparams.cgi, either by resetting it or appropriately
+ modifying it. The header value is specified by
+ %reasonsheader% and the body by %reasonsbody%. For example,
+ the new default parameter is:
+
+ --------------------------------------------------
+ From: bugzilla-daemon
+ To: %to%
+ Subject: [Bug %bugid%] %neworchanged%%summary%
+ X-Bugzilla-Reason: %reasonsheader%
+
+ %urlbase%show_bug.cgi?id=%bugid%
+
+ %diffs%
+
+
+
+ %reasonsbody%
+ --------------------------------------------------
+
+ (bug 26194)
+
+- Very long fields (especially multi-valued fields like keywords,
+ CCs, dependencies) on bug activity and notifications previously
+ could get truncated, resulting in useless notifications and data
+ loss on bug activity. Now the multi-valued fields only show
+ changes, and very big changes are split into multiple lines.
+ Where data loss has already occurred on bug activity, it is
+ indicated using question marks.
+ (bug 55161, 92266)
+
+- Previously, when a product's voting preferences changed all
+ votes were removed from all the bugs in the product. Also,
+ when a bug was moved to another product, all of its votes
+ were removed. This no longer occurs.
+
+ Instead, if the action would leave one or more bugs with
+ greater than the maximum number of votes per person per bug,
+ the number of votes will be reduced to the maximum. The
+ person will still be notified of this as before.
+
+ If the action would leave a user with more votes in a product
+ than is allowed, the limit will be breached so as to not lose
+ votes. However the user will not be able to update their
+ votes except to fix this situation. No further action is taken
+ in this version to make sure that the user does this.
+ (bug 28882, 92593)
+
+*** Other changes of note ***
+
+- Groups can now be marked inactive, so you can't add a new
+ restriction on that group to a bug, while leaving bugs that
+ were previously restricted on that group alone.
+ (bug 75482)
+- backdoor.cgi has been removed from the installation. It was
+ old code that was Netscape-specific and its name was scaring
+ people.
+ (bug 87983)
+- You can now add or remove from CC on the bulk change page.
+ (bug 12819)
+- New users created by administrators are now automatically
+ inserted into groups according to the group's regular
+ expression. Administrators must edit the user in a second
+ step to override these choices. Previously the
+ administrator specified these explicitly which could lead
+ to incorrect settings.
+ (bug 45164)
+- The userregexp of system groups can now be edited without
+ resorting to direct database access.
+ (bug 65290)
+
+*** Bug fixes of note ***
+
+- The bug list page was sometimes bringing up a not logged in
+ footer when the user was logged in and the installation was
+ using a shadow database.
+ (bug 47914)
+- You can now view the bug summary in your browser title for
+ a group-restricted bug if you have proper permissions.
+ (bug 71767)
+- Quick search for search terms did not work in IE5.
+ This has been worked around.
+ (bug 77699)
+- Quick search for search terms crashed NN4.76/4.77 for Unix.
+ This has been worked around.
+ (bug 83619)
+- Queries on bugs you have commented on using the "added
+ comment" feature should be a lot faster and not time out
+ on large installations due to the addition of an index.
+ (bug 57350)
+- You can now alter group settings on bulk change for groups
+ that aren't on for all bugs or off for all bugs.
+ (bug 84714)
+- New bug notifications now include the CC and QA fields.
+ (bug 28458)
+- Bugzilla is now more Windows friendly, although it is still
+ not an official platform.
+ (bug 88179, 29064)
+- Passwords are now encrypted using Perl's encrypt function.
+ This makes Bugzilla more portable to more operating systems.
+ (bug 77473)
+- Bugzilla didn't properly shut down when told to - some
+ queries could still be sent to the database.
+ (bug 95082)
+
+********************************************
+*** USERS UPGRADING FROM 2.10 OR EARLIER ***
+********************************************
+
+*** SECURITY ISSUES RESOLVED ***
+
+- Some security holes have been fixed where shell escape characters
+ could be passed to Bugzilla, allowing remote users to execute
+ system commands on the web server.
+
+*** IMPORTANT CHANGES ***
+
+- There is now a facility for users to choose the sort of
+ notifications they wish to receive. This facility will
+ probably be improved in future versions.
+ (bug 17464)
+
+- "Changed" will no longer appear on the subject line of
+ change notification emails. Because of this, you should
+ change the subject line in your 'changedmail' and
+ 'newchangedmail' params on editparams.cgi. The subject
+ line needs to be changed from
+
+ Subject: [Bug %bugid%] %neworchanged% - %summary%
+
+ to:
+
+ Subject: [Bug %bugid%] %neworchanged%%summary%
+
+ or whatever is appropriate for the subject you are using
+ on your system. Note the removal of the " - " in the
+ middle.
+ (bug 29820)
+
+*** Other changes of note ***
+
+- Bug titles now appear in the page title, and will hence
+ display in the user's browser's bookmarks and history.
+ (bug 22041)
+- Edit groups functionality (editgroups.cgi).
+ (bug 25010)
+- Support for moving bugs to other Bugzilla databases.
+ (bug 36133)
+- Bugzilla now can generate a frequently reported bugs list
+ based on what duplicates you receive.
+ (bug 25693)
+- When installing Bugzilla fresh, the administrator account is
+ now created in checksetup.pl.
+ (bug 17773)
+- Stored queries now show their name above the bug list, which
+ helps the user when they have multiple bug lists in multiple
+ browser windows. It also appears in the page title, and will
+ hence display in the user's browser's bookmarks and history.
+ (bug 52228)
+- All states and resolutions can now be collected for charting.
+ (bug 6682)
+- A new search-engine-like "quick search" feature appears on
+ the front page to try and making searching easier.
+ (bug 69793)
+- Querying on dependencies now works in the advanced query
+ section of the query page.
+ (bug 30823)
+- When a bug is marked as a duplicate, the reporter of the
+ resolved bug is automatically added to the CC list of the
+ open bug.
+ (bug 28676)
+
+*** Bug fixes of note ***
+
+- Notification emails will now always be sent to QA contacts.
+ Previously they wouldn't if you were using new email tech.
+ (bug 30826)
+- When marking a bug as a duplicate, the duplicate stamp marked
+ on the open bug will no longer be written too early (such as
+ on mid-air collisions).
+ (bug 7873)
+- Various bug fixes were made to the initial assignee and QA
+ of a component. It is no longer possible to enter an
+ invalid address. They will also now properly update when
+ a user's email address is changed. Sanity check will now
+ check these.
+ (bug 66876)
+- Administrators can no longer create an email accounts that do
+ not match the global email regular expression parameter.
+ Previously this could occur and would cause sanity check
+ errors.
+ (bug 32971)
+- The resolution field can no longer become empty when the
+ bug is resolved. This occurred because of midair collisions.
+ (bug 49306)
+
+*******************************************
+*** USERS UPGRADING FROM 2.8 OR EARLIER ***
+*******************************************
+
+This version of Bugzilla cannot upgrade from version 2.8 (released
+November 19, 1999). You will first have to upgrade to Bugzilla 3.6 and
+then upgrade to the latest release.
+
+If you are upgrading from a version earlier than 2.8, See the
+PGRADING-pre-2.8 file in Bugzilla 3.0 for information
+on upgrading from a version that is earlier than 2.8.
diff --git a/docs/en/txt/Bugzilla-Guide.txt b/docs/en/txt/Bugzilla-Guide.txt
new file mode 100644
index 000000000..14a748bdc
--- /dev/null
+++ b/docs/en/txt/Bugzilla-Guide.txt
@@ -0,0 +1,6041 @@
+
+The Bugzilla Guide - 4.0 Release
+
+The Bugzilla Team
+
+ 2011-02-15
+
+ This is the documentation for Bugzilla, a bug-tracking system from
+ mozilla.org. Bugzilla is an enterprise-class piece of software that tracks
+ millions of bugs and issues for hundreds of organizations around the world.
+
+ The most current version of this document can always be found on the
+ Bugzilla Documentation Page.
+ _________________________________________________________________
+
+ Table of Contents
+ 1. About This Guide
+
+ 1.1. Copyright Information
+ 1.2. Disclaimer
+ 1.3. New Versions
+ 1.4. Credits
+ 1.5. Document Conventions
+
+ 2. Installing Bugzilla
+
+ 2.1. Installation
+ 2.2. Configuration
+ 2.3. Optional Additional Configuration
+ 2.4. Multiple Bugzilla databases with a single installation
+ 2.5. OS-Specific Installation Notes
+ 2.6. UNIX (non-root) Installation Notes
+ 2.7. Upgrading to New Releases
+
+ 3. Administering Bugzilla
+
+ 3.1. Bugzilla Configuration
+ 3.2. User Administration
+ 3.3. Classifications
+ 3.4. Products
+ 3.5. Components
+ 3.6. Versions
+ 3.7. Milestones
+ 3.8. Flags
+ 3.9. Keywords
+ 3.10. Custom Fields
+ 3.11. Legal Values
+ 3.12. Bug Status Workflow
+ 3.13. Voting
+ 3.14. Quips
+ 3.15. Groups and Group Security
+ 3.16. Checking and Maintaining Database Integrity
+
+ 4. Bugzilla Security
+
+ 4.1. Operating System
+ 4.2. Web server
+ 4.3. Bugzilla
+
+ 5. Using Bugzilla
+
+ 5.1. Introduction
+ 5.2. Create a Bugzilla Account
+ 5.3. Anatomy of a Bug
+ 5.4. Life Cycle of a Bug
+ 5.5. Searching for Bugs
+ 5.6. Filing Bugs
+ 5.7. Attachments
+ 5.8. Hints and Tips
+ 5.9. Time Tracking Information
+ 5.10. User Preferences
+ 5.11. Reports and Charts
+ 5.12. Flags
+ 5.13. Whining
+
+ 6. Customizing Bugzilla
+
+ 6.1. Bugzilla Extensions
+ 6.2. Custom Skins
+ 6.3. Template Customization
+ 6.4. Customizing Who Can Change What
+ 6.5. Integrating Bugzilla with Third-Party Tools
+
+ A. Troubleshooting
+
+ A.1. General Advice
+ A.2. The Apache web server is not serving Bugzilla pages
+ A.3. I installed a Perl module, but checksetup.pl claims it's not
+ installed!
+
+ A.4. DBD::Sponge::db prepare failed
+ A.5. cannot chdir(/var/spool/mqueue)
+ A.6. Everybody is constantly being forced to relogin
+ A.7. index.cgi doesn't show up unless specified in the URL
+ A.8. checksetup.pl reports "Client does not support authentication
+ protocol requested by server..."
+
+ B. Contrib
+
+ B.1. Command-line Search Interface
+ B.2. Command-line 'Send Unsent Bug-mail' tool
+
+ C. Manual Installation of Perl Modules
+
+ C.1. Instructions
+ C.2. Download Locations
+ C.3. Optional Modules
+
+ D. GNU Free Documentation License
+
+ 0. Preamble
+ 1. Applicability and Definition
+ 2. Verbatim Copying
+ 3. Copying in Quantity
+ 4. Modifications
+ 5. Combining Documents
+ 6. Collections of Documents
+ 7. Aggregation with Independent Works
+ 8. Translation
+ 9. Termination
+ 10. Future Revisions of this License
+ How to use this License for your documents
+
+ Glossary
+
+ List of Figures
+ 5-1. Lifecycle of a Bugzilla Bug
+
+ List of Examples
+ A-1. Examples of urlbase/cookiepath pairs for sharing login cookies
+ A-2. Examples of urlbase/cookiepath pairs to restrict the login cookie
+ _________________________________________________________________
+
+Chapter 1. About This Guide
+
+1.1. Copyright Information
+
+ This document is copyright (c) 2000-2011 by the various Bugzilla
+ contributors who wrote it.
+
+ Permission is granted to copy, distribute and/or modify this document
+ under the terms of the GNU Free Documentation License, Version 1.1 or any
+ later version published by the Free Software Foundation; with no Invariant
+ Sections, no Front-Cover Texts, and with no Back-Cover Texts. A copy of
+ the license is included in Appendix D.
+
+ If you have any questions regarding this document, its copyright, or
+ publishing this document in non-electronic form, please contact the Bugzilla
+ Team.
+ _________________________________________________________________
+
+1.2. Disclaimer
+
+ No liability for the contents of this document can be accepted. Follow the
+ instructions herein at your own risk. This document may contain errors and
+ inaccuracies that may damage your system, cause your partner to leave you,
+ your boss to fire you, your cats to pee on your furniture and clothing, and
+ global thermonuclear war. Proceed with caution.
+
+ Naming of particular products or brands should not be seen as endorsements,
+ with the exception of the term "GNU/Linux". We wholeheartedly endorse the
+ use of GNU/Linux; it is an extremely versatile, stable, and robust operating
+ system that offers an ideal operating environment for Bugzilla.
+
+ Although the Bugzilla development team has taken great care to ensure that
+ all exploitable bugs have been fixed, security holes surely exist in any
+ piece of code. Great care should be taken both in the installation and usage
+ of this software. The Bugzilla development team members assume no liability
+ for your use of Bugzilla. You have the source code, and are responsible for
+ auditing it yourself to ensure your security needs are met.
+ _________________________________________________________________
+
+1.3. New Versions
+
+ This is the 4.0 version of The Bugzilla Guide. It is so named to match the
+ current version of Bugzilla.
+
+ The latest version of this guide can always be found at
+ http://www.bugzilla.org/docs/. However, you should read the version which
+ came with the Bugzilla release you are using.
+
+ In addition, there are Bugzilla template localization projects in several
+ languages. They may have translated documentation available. If you would
+ like to volunteer to translate the Guide into additional languages, please
+ visit the Bugzilla L10n team page.
+ _________________________________________________________________
+
+1.4. Credits
+
+ The people listed below have made enormous contributions to the creation of
+ this Guide, through their writing, dedicated hacking efforts, numerous
+ e-mail and IRC support sessions, and overall excellent contribution to the
+ Bugzilla community:
+
+ Matthew P. Barnson <mbarnson@sisna.com>
+ for the Herculean task of pulling together the Bugzilla Guide and
+ shepherding it to 2.14.
+
+ Terry Weissman <terry@mozilla.org>
+ for initially writing Bugzilla and creating the README upon which the
+ UNIX installation documentation is largely based.
+
+ Tara Hernandez <tara@tequilarists.org>
+ for keeping Bugzilla development going strong after Terry left
+ mozilla.org and for running landfill.
+
+ Dave Lawrence <dkl@redhat.com>
+ for providing insight into the key differences between Red Hat's
+ customized Bugzilla.
+
+ Dawn Endico <endico@mozilla.org>
+ for being a hacker extraordinaire and putting up with Matthew's
+ incessant questions and arguments on irc.mozilla.org in #mozwebtools
+
+ Jacob Steenhagen <jake@bugzilla.org>
+ for taking over documentation during the 2.17 development period.
+
+ Dave Miller <justdave@bugzilla.org>
+ for taking over as project lead when Tara stepped down and
+ continually pushing for the documentation to be the best it can be.
+
+ Thanks also go to the following people for significant contributions to this
+ documentation: Kevin Brannen, Vlad Dascalu, Ben FrantzDale, Eric Hanson,
+ Zach Lipton, Gervase Markham, Andrew Pearson, Joe Robins, Spencer Smith, Ron
+ Teitelbaum, Shane Travis, Martin Wulffeld.
+
+ Also, thanks are due to the members of the mozilla.support.bugzilla
+ newsgroup (and its predecessor, netscape.public.mozilla.webtools). Without
+ your discussions, insight, suggestions, and patches, this could never have
+ happened.
+ _________________________________________________________________
+
+1.5. Document Conventions
+
+ This document uses the following conventions:
+
+ Descriptions Appearance
+ Caution
+
+ Caution
+
+ Don't run with scissors!
+ Hint or Tip
+
+ Tip
+
+ For best results...
+ Note
+
+ Note
+
+ Dear John...
+ Warning
+
+ Warning
+
+ Read this or the cat gets it.
+ File or directory name filename
+ Command to be typed command
+ Application name application
+ Normal user's prompt under bash shell bash$
+ Root user's prompt under bash shell bash#
+ Normal user's prompt under tcsh shell tcsh$
+ Environment variables VARIABLE
+ Term found in the glossary Bugzilla
+ Code example
+<para>
+Beginning and end of paragraph
+</para>
+
+ This documentation is maintained in DocBook 4.1.2 XML format. Changes are
+ best submitted as plain text or XML diffs, attached to a bug filed in the
+ Bugzilla Documentation component.
+ _________________________________________________________________
+
+Chapter 2. Installing Bugzilla
+
+2.1. Installation
+
+ Note
+
+ If you just want to use Bugzilla, you do not need to install it. None of
+ this chapter is relevant to you. Ask your Bugzilla administrator for the URL
+ to access it from your web browser.
+
+ The Bugzilla server software is usually installed on Linux or Solaris. If
+ you are installing on another OS, check Section 2.5 before you start your
+ installation to see if there are any special instructions.
+
+ This guide assumes that you have administrative access to the Bugzilla
+ machine. It not possible to install and run Bugzilla itself without
+ administrative access except in the very unlikely event that every single
+ prerequisite is already installed.
+
+ Warning
+
+ The installation process may make your machine insecure for short periods of
+ time. Make sure there is a firewall between you and the Internet.
+
+ You are strongly recommended to make a backup of your system before
+ installing Bugzilla (and at regular intervals thereafter :-).
+
+ In outline, the installation proceeds as follows:
+ 1. Install Perl (5.8.1 or above)
+ 2. Install a Database Engine
+ 3. Install a Webserver
+ 4. Install Bugzilla
+ 5. Install Perl modules
+ 6. Install a Mail Transfer Agent (Sendmail 8.7 or above, or an MTA that is
+ Sendmail-compatible with at least this version)
+ 7. Configure all of the above.
+ _________________________________________________________________
+
+2.1.1. Perl
+
+ Installed Version Test:
+ perl -v
+
+ Any machine that doesn't have Perl on it is a sad machine indeed. If you
+ don't have it and your OS doesn't provide official packages, visit
+ http://www.perl.org. Although Bugzilla runs with Perl 5.8.1, it's a good
+ idea to be using the latest stable version.
+ _________________________________________________________________
+
+2.1.2. Database Engine
+
+ Bugzilla supports MySQL, PostgreSQL and Oracle as database servers. You only
+ require one of these systems to make use of Bugzilla.
+ _________________________________________________________________
+
+2.1.2.1. MySQL
+
+ Installed Version Test:
+ mysql -V
+
+ If you don't have it and your OS doesn't provide official packages, visit
+ http://www.mysql.com. You need MySQL version 4.1.2 or higher.
+
+ Note
+
+ Many of the binary versions of MySQL store their data files in /var. On some
+ Unix systems, this is part of a smaller root partition, and may not have
+ room for your bug database. To change the data directory, you have to build
+ MySQL from source yourself, and set it as an option to configure.
+
+ If you install from something other than a packaging/installation system,
+ such as .rpm (Redhat Package), .deb (Debian Package), .exe (Windows
+ Executable), or .msi (Microsoft Installer), make sure the MySQL server is
+ started when the machine boots.
+ _________________________________________________________________
+
+2.1.2.2. PostgreSQL
+
+ Installed Version Test:
+ psql -V
+
+ If you don't have it and your OS doesn't provide official packages, visit
+ http://www.postgresql.org/. You need PostgreSQL version 8.00.0000 or higher.
+
+ If you install from something other than a packaging/installation system,
+ such as .rpm (Redhat Package), .deb (Debian Package), .exe (Windows
+ Executable), or .msi (Microsoft Installer), make sure the PostgreSQL server
+ is started when the machine boots.
+ _________________________________________________________________
+
+2.1.2.3. Oracle
+
+ Installed Version Test:
+ select * from v$version
+
+ (you first have to log in into your DB)
+
+ If you don't have it and your OS doesn't provide official packages, visit
+ http://www.oracle.com/. You need Oracle version 10.02.0 or higher.
+
+ If you install from something other than a packaging/installation system,
+ such as .rpm (Redhat Package), .deb (Debian Package), .exe (Windows
+ Executable), or .msi (Microsoft Installer), make sure the Oracle server is
+ started when the machine boots.
+ _________________________________________________________________
+
+2.1.3. Web Server
+
+ Installed Version Test: view the default welcome page at
+ http://<your-machine>/
+
+ You have freedom of choice here, pretty much any web server that is capable
+ of running CGI scripts will work. However, we strongly recommend using the
+ Apache web server (either 1.3.x or 2.x), and the installation instructions
+ usually assume you are using it. If you have got Bugzilla working using
+ another web server, please share your experiences with us by filing a bug in
+ Bugzilla Documentation.
+
+ If you don't have Apache and your OS doesn't provide official packages,
+ visit http://httpd.apache.org/.
+ _________________________________________________________________
+
+2.1.4. Bugzilla
+
+ Download a Bugzilla tarball (or check it out from CVS) and place it in a
+ suitable directory, accessible by the default web server user (probably
+ "apache" or "www"). Good locations are either directly in the web server's
+ document directories or in /usr/local with a symbolic link to the web
+ server's document directories or an alias in the web server's configuration.
+
+ Caution
+
+ The default Bugzilla distribution is NOT designed to be placed in a cgi-bin
+ directory. This includes any directory which is configured using the
+ ScriptAlias directive of Apache.
+
+ Once all the files are in a web accessible directory, make that directory
+ writable by your web server's user. This is a temporary step until you run
+ the checksetup.pl script, which locks down your installation.
+ _________________________________________________________________
+
+2.1.5. Perl Modules
+
+ Bugzilla's installation process is based on a script called checksetup.pl.
+ The first thing it checks is whether you have appropriate versions of all
+ the required Perl modules. The aim of this section is to pass this check.
+ When it passes, proceed to Section 2.2.
+
+ At this point, you need to su to root. You should remain as root until the
+ end of the install. To check you have the required modules, run:
+ bash# ./checksetup.pl --check-modules
+
+ checksetup.pl will print out a list of the required and optional Perl
+ modules, together with the versions (if any) installed on your machine. The
+ list of required modules is reasonably long; however, you may already have
+ several of them installed.
+
+ The preferred way to install missing Perl modules is to use the package
+ manager provided by your operating system (e.g "rpm" or "yum" on Linux
+ distros, or "ppm" on Windows if using ActivePerl, see Section 2.5.1.2). If
+ some Perl modules are still missing or are too old, then we recommend using
+ the install-module.pl script (doesn't work with ActivePerl on Windows). If
+ for some reason you really need to install the Perl modules manually, see
+ Appendix C. For instance, on Unix, you invoke install-module.pl as follows:
+ bash# perl install-module.pl <modulename>
+
+ Tip
+
+ Many people complain that Perl modules will not install for them. Most
+ times, the error messages complain that they are missing a file in "@INC".
+ Virtually every time, this error is due to permissions being set too
+ restrictively for you to compile Perl modules or not having the necessary
+ Perl development libraries installed on your system. Consult your local UNIX
+ systems administrator for help solving these permissions issues; if you are
+ the local UNIX sysadmin, please consult the newsgroup/mailing list for
+ further assistance or hire someone to help you out.
+
+ Note
+
+ If you are using a package-based system, and attempting to install the Perl
+ modules from CPAN, you may need to install the "development" packages for
+ MySQL and GD before attempting to install the related Perl modules. The
+ names of these packages will vary depending on the specific distribution you
+ are using, but are often called <packagename>-devel.
+
+ Here is a complete list of modules and their minimum versions. Some modules
+ have special installation notes, which follow.
+
+ Required Perl modules:
+
+ 1. CGI (3.51)
+ 2. Date::Format (2.21)
+ 3. DateTime (0.28)
+ 4. DateTime::TimeZone (0.71)
+ 5. DBI (1.41)
+ 6. DBD::mysql (4.00) if using MySQL
+ 7. DBD::Pg (1.45) if using PostgreSQL
+ 8. DBD::Oracle (1.19) if using Oracle
+ 9. Digest::SHA (any)
+ 10. Email::Send (2.00)
+ 11. Email::MIME (1.904)
+ 12. Template (2.22)
+ 13. URI (any)
+
+ Optional Perl modules:
+
+ 1. GD (1.20) for bug charting
+ 2. Template::Plugin::GD::Image (any) for Graphical Reports
+ 3. Chart::Lines (2.1) for bug charting
+ 4. GD::Graph (any) for bug charting
+ 5. GD::Text (any) for bug charting
+ 6. XML::Twig (any) for bug import/export
+ 7. MIME::Parser (5.406) for bug import/export
+ 8. LWP::UserAgent (any) for Automatic Update Notifications
+ 9. PatchReader (0.9.4) for pretty HTML view of patches
+ 10. Net::LDAP (any) for LDAP Authentication
+ 11. Authen::SASL (any) for SASL Authentication
+ 12. Authen::Radius (any) for RADIUS Authentication
+ 13. SOAP::Lite (0.712) for the web service interface
+ 14. JSON::RPC (any) for the JSON-RPC interface
+ 15. Test::Taint (any) for the web service interface
+ 16. HTML::Parser (3.40) for More HTML in Product/Group Descriptions
+ 17. HTML::Scrubber (any) for More HTML in Product/Group Descriptions
+ 18. Email::MIME::Attachment::Stripper (any) for Inbound Email
+ 19. Email::Reply (any) for Inbound Email
+ 20. TheSchwartz (any) for Mail Queueing
+ 21. Daemon::Generic (any) for Mail Queueing
+ 22. mod_perl2 (1.999022) for mod_perl
+ _________________________________________________________________
+
+2.1.6. Mail Transfer Agent (MTA)
+
+ Bugzilla is dependent on the availability of an e-mail system for its user
+ authentication and for other tasks.
+
+ Note
+
+ This is not entirely true. It is possible to completely disable email
+ sending, or to have Bugzilla store email messages in a file instead of
+ sending them. However, this is mainly intended for testing, as disabling or
+ diverting email on a production machine would mean that users could miss
+ important events (such as bug changes or the creation of new accounts).
+
+ For more information, see the "mail_delivery_method" parameter in Section
+ 3.1.
+
+ On Linux, any Sendmail-compatible MTA (Mail Transfer Agent) will suffice.
+ Sendmail, Postfix, qmail and Exim are examples of common MTAs. Sendmail is
+ the original Unix MTA, but the others are easier to configure, and therefore
+ many people replace Sendmail with Postfix or Exim. They are drop-in
+ replacements, so Bugzilla will not distinguish between them.
+
+ If you are using Sendmail, version 8.7 or higher is required. If you are
+ using a Sendmail-compatible MTA, it must be congruent with at least version
+ 8.7 of Sendmail.
+
+ Consult the manual for the specific MTA you choose for detailed installation
+ instructions. Each of these programs will have their own configuration files
+ where you must configure certain parameters to ensure that the mail is
+ delivered properly. They are implemented as services, and you should ensure
+ that the MTA is in the auto-start list of services for the machine.
+
+ If a simple mail sent with the command-line 'mail' program succeeds, then
+ Bugzilla should also be fine.
+ _________________________________________________________________
+
+2.1.7. Installing Bugzilla on mod_perl
+
+ It is now possible to run the Bugzilla software under mod_perl on Apache.
+ mod_perl has some additional requirements to that of running Bugzilla under
+ mod_cgi (the standard and previous way).
+
+ Bugzilla requires mod_perl to be installed, which can be obtained from
+ http://perl.apache.org - Bugzilla requires version 1.999022 (AKA 2.0.0-RC5)
+ to be installed.
+ _________________________________________________________________
+
+2.2. Configuration
+
+ Warning
+
+ Poorly-configured MySQL and Bugzilla installations have given attackers full
+ access to systems in the past. Please take the security parts of these
+ guidelines seriously, even for Bugzilla machines hidden away behind your
+ firewall. Be certain to read Chapter 4 for some important security tips.
+ _________________________________________________________________
+
+2.2.1. localconfig
+
+ You should now run checksetup.pl again, this time without the
+ --check-modules switch.
+ bash# ./checksetup.pl
+
+ This time, checksetup.pl should tell you that all the correct modules are
+ installed and will display a message about, and write out a file called,
+ localconfig. This file contains the default settings for a number of
+ Bugzilla parameters.
+
+ Load this file in your editor. The only two values you need to change are
+ $db_driver and $db_pass, respectively the type of the database and the
+ password for the user you will create for your database. Pick a strong
+ password (for simplicity, it should not contain single quote characters) and
+ put it here. $db_driver can be either 'mysql', 'Pg' or 'oracle'.
+
+ Note
+
+ In Oracle, $db_name should actually be the SID name of your database (e.g.
+ "XE" if you are using Oracle XE).
+
+ You may need to change the value of webservergroup if your web server does
+ not run in the "apache" group. On Debian, for example, Apache runs in the
+ "www-data" group. If you are going to run Bugzilla on a machine where you do
+ not have root access (such as on a shared web hosting account), you will
+ need to leave webservergroup empty, ignoring the warnings that checksetup.pl
+ will subsequently display every time it is run.
+
+ Caution
+
+ If you are using suexec, you should use your own primary group for
+ webservergroup rather than leaving it empty, and see the additional
+ directions in the suexec section Section 2.6.6.1.
+
+ The other options in the localconfig file are documented by their
+ accompanying comments. If you have a slightly non-standard database setup,
+ you may wish to change one or more of the other "$db_*" parameters.
+ _________________________________________________________________
+
+2.2.2. Database Server
+
+ This section deals with configuring your database server for use with
+ Bugzilla. Currently, MySQL (Section 2.2.2.2), PostgreSQL (Section 2.2.2.3)
+ and Oracle (Section 2.2.2.4) are available.
+ _________________________________________________________________
+
+2.2.2.1. Bugzilla Database Schema
+
+ The Bugzilla database schema is available at Ravenbrook. This very valuable
+ tool can generate a written description of the Bugzilla database schema for
+ any version of Bugzilla. It can also generate a diff between two versions to
+ help someone see what has changed.
+ _________________________________________________________________
+
+2.2.2.2. MySQL
+
+ Caution
+
+ MySQL's default configuration is insecure. We highly recommend to run
+ mysql_secure_installation on Linux or the MySQL installer on Windows, and
+ follow the instructions. Important points to note are:
+
+ 1. Be sure that the root account has a secure password set.
+ 2. Do not create an anonymous account, and if it exists, say "yes" to
+ remove it.
+ 3. If your web server and MySQL server are on the same machine, you should
+ disable the network access.
+ _________________________________________________________________
+
+2.2.2.2.1. Allow large attachments and many comments
+
+ By default, MySQL will only allow you to insert things into the database
+ that are smaller than 1MB. Attachments may be larger than this. Also,
+ Bugzilla combines all comments on a single bug into one field for full-text
+ searching, and the combination of all comments on a single bug could in some
+ cases be larger than 1MB.
+
+ To change MySQL's default, you need to edit your MySQL configuration file,
+ which is usually /etc/my.cnf on Linux. We recommend that you allow at least
+ 4MB packets by adding the "max_allowed_packet" parameter to your MySQL
+ configuration in the "[mysqld]" section, like this:
+[mysqld]
+# Allow packets up to 4MB
+max_allowed_packet=4M
+ _________________________________________________________________
+
+2.2.2.2.2. Allow small words in full-text indexes
+
+ By default, words must be at least four characters in length in order to be
+ indexed by MySQL's full-text indexes. This causes a lot of Bugzilla specific
+ words to be missed, including "cc", "ftp" and "uri".
+
+ MySQL can be configured to index those words by setting the ft_min_word_len
+ param to the minimum size of the words to index. This can be done by
+ modifying the /etc/my.cnf according to the example below:
+ [mysqld]
+ # Allow small words in full-text indexes
+ ft_min_word_len=2
+
+ Rebuilding the indexes can be done based on documentation found at
+ http://www.mysql.com/doc/en/Fulltext_Fine-tuning.html.
+ _________________________________________________________________
+
+2.2.2.2.3. Add a user to MySQL
+
+ You need to add a new MySQL user for Bugzilla to use. (It's not safe to have
+ Bugzilla use the MySQL root account.) The following instructions assume the
+ defaults in localconfig; if you changed those, you need to modify the SQL
+ command appropriately. You will need the $db_pass password you set in
+ localconfig in Section 2.2.1.
+
+ We use an SQL GRANT command to create a "bugs" user. This also restricts the
+ "bugs"user to operations within a database called "bugs", and only allows
+ the account to connect from "localhost". Modify it to reflect your setup if
+ you will be connecting from another machine or as a different user.
+
+ Run the mysql command-line client and enter:
+ mysql> GRANT SELECT, INSERT,
+ UPDATE, DELETE, INDEX, ALTER, CREATE, LOCK TABLES,
+ CREATE TEMPORARY TABLES, DROP, REFERENCES ON bugs.*
+ TO bugs@localhost IDENTIFIED BY '$db_pass';
+ mysql> FLUSH PRIVILEGES;
+ _________________________________________________________________
+
+2.2.2.2.4. Permit attachments table to grow beyond 4GB
+
+ By default, MySQL will limit the size of a table to 4GB. This limit is
+ present even if the underlying filesystem has no such limit. To set a higher
+ limit, follow these instructions.
+
+ After you have completed the rest of the installation (or at least the
+ database setup parts), you should run the MySQL command-line client and
+ enter the following, replacing $bugs_db with your Bugzilla database name
+ (bugs by default):
+ mysql> use $bugs_db
+ mysql> ALTER TABLE attachments
+ AVG_ROW_LENGTH=1000000, MAX_ROWS=20000;
+
+ The above command will change the limit to 20GB. Mysql will have to make a
+ temporary copy of your entire table to do this. Ideally, you should do this
+ when your attachments table is still small.
+
+ Note
+
+ This does not affect Big Files, attachments that are stored directly on disk
+ instead of in the database.
+ _________________________________________________________________
+
+2.2.2.3. PostgreSQL
+
+2.2.2.3.1. Add a User to PostgreSQL
+
+ You need to add a new user to PostgreSQL for the Bugzilla application to use
+ when accessing the database. The following instructions assume the defaults
+ in localconfig; if you changed those, you need to modify the commands
+ appropriately. You will need the $db_pass password you set in localconfig in
+ Section 2.2.1.
+
+ On most systems, to create the user in PostgreSQL, you will need to login as
+ the root user, and then
+ bash# su - postgres
+
+ As the postgres user, you then need to create a new user:
+ bash$ createuser -U postgres -dRSP bugs
+
+ When asked for a password, provide the password which will be set as
+ $db_pass in localconfig. The created user will not be a superuser (-S) and
+ will not be able to create new users (-R). He will only have the ability to
+ create databases (-d).
+
+ Note
+
+ If your are running PostgreSQL 8.0, you must replace -dRSP by -dAP.
+ _________________________________________________________________
+
+2.2.2.3.2. Configure PostgreSQL
+
+ Now, you will need to edit pg_hba.conf which is usually located in
+ /var/lib/pgsql/data/. In this file, you will need to add a new line to it as
+ follows:
+
+ host all bugs 127.0.0.1 255.255.255.255 md5
+
+ This means that for TCP/IP (host) connections, allow connections from
+ '127.0.0.1' to 'all' databases on this server from the 'bugs' user, and use
+ password authentication (md5) for that user.
+
+ Now, you will need to restart PostgreSQL, but you will need to fully stop
+ and start the server rather than just restarting due to the possibility of a
+ change to postgresql.conf. After the server has restarted, you will need to
+ edit localconfig, finding the $db_driver variable and setting it to Pg and
+ changing the password in $db_pass to the one you picked previously, while
+ setting up the account.
+ _________________________________________________________________
+
+2.2.2.4. Oracle
+
+2.2.2.4.1. Create a New Tablespace
+
+ You can use the existing tablespace or create a new one for Bugzilla. To
+ create a new tablespace, run the following command:
+ CREATE TABLESPACE bugs
+ DATAFILE '$path_to_datafile' SIZE 500M
+ AUTOEXTEND ON NEXT 30M MAXSIZE UNLIMITED
+
+ Here, the name of the tablespace is 'bugs', but you can choose another name.
+ $path_to_datafile is the path to the file containing your database, for
+ instance /u01/oradata/bugzilla.dbf. The initial size of the database file is
+ set in this example to 500 Mb, with an increment of 30 Mb everytime we reach
+ the size limit of the file.
+ _________________________________________________________________
+
+2.2.2.4.2. Add a User to Oracle
+
+ The user name and password must match what you set in localconfig ($db_user
+ and $db_pass, respectively). Here, we assume that the user name is 'bugs'
+ and the tablespace name is the same as above.
+ CREATE USER bugs
+ IDENTIFIED BY "$db_pass"
+ DEFAULT TABLESPACE bugs
+ TEMPORARY TABLESPACE TEMP
+ PROFILE DEFAULT;
+ -- GRANT/REVOKE ROLE PRIVILEGES
+ GRANT CONNECT TO bugs;
+ GRANT RESOURCE TO bugs;
+ -- GRANT/REVOKE SYSTEM PRIVILEGES
+ GRANT UNLIMITED TABLESPACE TO bugs;
+ GRANT EXECUTE ON CTXSYS.CTX_DDL TO bugs;
+ _________________________________________________________________
+
+2.2.2.4.3. Configure the Web Server
+
+ If you use Apache, append these lines to httpd.conf to set ORACLE_HOME and
+ LD_LIBRARY_PATH. For instance:
+ SetEnv ORACLE_HOME /u01/app/oracle/product/10.2.0/
+ SetEnv LD_LIBRARY_PATH /u01/app/oracle/product/10.2.0/lib/
+
+ When this is done, restart your web server.
+ _________________________________________________________________
+
+2.2.3. checksetup.pl
+
+ Next, rerun checksetup.pl. It reconfirms that all the modules are present,
+ and notices the altered localconfig file, which it assumes you have edited
+ to your satisfaction. It compiles the UI templates, connects to the database
+ using the 'bugs' user you created and the password you defined, and creates
+ the 'bugs' database and the tables therein.
+
+ After that, it asks for details of an administrator account. Bugzilla can
+ have multiple administrators - you can create more later - but it needs one
+ to start off with. Enter the email address of an administrator, his or her
+ full name, and a suitable Bugzilla password.
+
+ checksetup.pl will then finish. You may rerun checksetup.pl at any time if
+ you wish.
+ _________________________________________________________________
+
+2.2.4. Web server
+
+ Configure your web server according to the instructions in the appropriate
+ section. (If it makes a difference in your choice, the Bugzilla Team
+ recommends Apache.) To check whether your web server is correctly
+ configured, try to access testagent.cgi from your web server. If "OK" is
+ displayed, then your configuration is successful. Regardless of which web
+ server you are using, however, ensure that sensitive information is not
+ remotely available by properly applying the access controls in Section
+ 4.2.1. You can run testserver.pl to check if your web server serves Bugzilla
+ files as expected.
+ _________________________________________________________________
+
+2.2.4.1. Bugzilla using Apache
+
+ You have two options for running Bugzilla under Apache - mod_cgi (the
+ default) and mod_perl (new in Bugzilla 2.23)
+ _________________________________________________________________
+
+2.2.4.1.1. Apache httpd with mod_cgi
+
+ To configure your Apache web server to work with Bugzilla while using
+ mod_cgi, do the following:
+ 1. Load httpd.conf in your editor. In Fedora and Red Hat Linux, this file
+ is found in /etc/httpd/conf.
+ 2. Apache uses <Directory> directives to permit fine-grained permission
+ setting. Add the following lines to a directive that applies to the
+ location of your Bugzilla installation. (If such a section does not
+ exist, you'll want to add one.) In this example, Bugzilla has been
+ installed at /var/www/html/bugzilla.
+
+ <Directory /var/www/html/bugzilla>
+ AddHandler cgi-script .cgi
+ Options +Indexes +ExecCGI
+ DirectoryIndex index.cgi
+ AllowOverride Limit FileInfo Indexes
+ </Directory>
+
+
+ These instructions: allow apache to run .cgi files found within the
+ bugzilla directory; instructs the server to look for a file called
+ index.cgi if someone only types the directory name into the browser; and
+ allows Bugzilla's .htaccess files to override global permissions.
+
+ Note
+
+ It is possible to make these changes globally, or to the directive
+ controlling Bugzilla's parent directory (e.g. <Directory /var/www/html/>).
+ Such changes would also apply to the Bugzilla directory... but they would
+ also apply to many other places where they may or may not be appropriate. In
+ most cases, including this one, it is better to be as restrictive as
+ possible when granting extra access.
+
+ Note
+
+ On Windows, you may have to also add the ScriptInterpreterSource
+ Registry-Strict line, see Windows specific notes.
+ 3. checksetup.pl can set tighter permissions on Bugzilla's files and
+ directories if it knows what group the web server runs as. Find the
+ Group line in httpd.conf, place the value found there in the
+ $webservergroup variable in localconfig, then rerun checksetup.pl.
+ 4. Optional: If Bugzilla does not actually reside in the webspace
+ directory, but instead has been symbolically linked there, you will need
+ to add the following to the Options line of the Bugzilla <Directory>
+ directive (the same one as in the step above):
+
+ +FollowSymLinks
+
+
+ Without this directive, Apache will not follow symbolic links to places
+ outside its own directory structure, and you will be unable to run
+ Bugzilla.
+ _________________________________________________________________
+
+2.2.4.1.2. Apache httpd with mod_perl
+
+ Some configuration is required to make Bugzilla work with Apache and
+ mod_perl
+ 1. Load httpd.conf in your editor. In Fedora and Red Hat Linux, this file
+ is found in /etc/httpd/conf.
+ 2. Add the following information to your httpd.conf file, substituting
+ where appropriate with your own local paths.
+
+ Note
+
+ This should be used instead of the <Directory> block shown above. This
+ should also be above any other mod_perl directives within the httpd.conf and
+ must be specified in the order as below.
+
+ Warning
+
+ You should also ensure that you have disabled KeepAlive support in your
+ Apache install when utilizing Bugzilla under mod_perl
+
+ PerlSwitches -w -T
+ PerlConfigRequire /var/www/html/bugzilla/mod_perl.pl
+
+
+ 3. checksetup.pl can set tighter permissions on Bugzilla's files and
+ directories if it knows what group the web server runs as. Find the
+ Group line in httpd.conf, place the value found there in the
+ $webservergroup variable in localconfig, then rerun checksetup.pl.
+
+ On restarting Apache, Bugzilla should now be running within the mod_perl
+ environment. Please ensure you have run checksetup.pl to set permissions
+ before you restart Apache.
+
+ Note
+
+ Please bear the following points in mind when looking at using Bugzilla
+ under mod_perl:
+
+ * mod_perl support in Bugzilla can take up a HUGE amount of RAM. You could
+ be looking at 30MB per httpd child, easily. Basically, you just need a
+ lot of RAM. The more RAM you can get, the better. mod_perl is basically
+ trading RAM for speed. At least 2GB total system RAM is recommended for
+ running Bugzilla under mod_perl.
+ * Under mod_perl, you have to restart Apache if you make any manual change
+ to any Bugzilla file. You can't just reload--you have to actually
+ restart the server (as in make sure it stops and starts again). You can
+ change localconfig and the params file manually, if you want, because
+ those are re-read every time you load a page.
+ * You must run in Apache's Prefork MPM (this is the default). The Worker
+ MPM may not work--we haven't tested Bugzilla's mod_perl support under
+ threads. (And, in fact, we're fairly sure it won't work.)
+ * Bugzilla generally expects to be the only mod_perl application running
+ on your entire server. It may or may not work if there are other
+ applications also running under mod_perl. It does try its best to play
+ nice with other mod_perl applications, but it still may have conflicts.
+ * It is recommended that you have one Bugzilla instance running under
+ mod_perl on your server. Bugzilla has not been tested with more than one
+ instance running.
+ _________________________________________________________________
+
+2.2.4.2. Microsoft Internet Information Services
+
+ If you are running Bugzilla on Windows and choose to use Microsoft's
+ Internet Information Services or Personal Web Server you will need to
+ perform a number of other configuration steps as explained below. You may
+ also want to refer to the following Microsoft Knowledge Base articles:
+ 245225 "HOW TO: Configure and Test a PERL Script with IIS 4.0, 5.0, and 5.1"
+ (for Internet Information Services) and 231998 "HOW TO: FP2000: How to Use
+ Perl with Microsoft Personal Web Server on Windows 95/98" (for Personal Web
+ Server).
+
+ You will need to create a virtual directory for the Bugzilla install. Put
+ the Bugzilla files in a directory that is named something other than what
+ you want your end-users accessing. That is, if you want your users to access
+ your Bugzilla installation through "http://<yourdomainname>/Bugzilla", then
+ do not put your Bugzilla files in a directory named "Bugzilla". Instead,
+ place them in a different location, and then use the IIS Administration tool
+ to create a Virtual Directory named "Bugzilla" that acts as an alias for the
+ actual location of the files. When creating that virtual directory, make
+ sure you add the "Execute (such as ISAPI applications or CGI)" access
+ permission.
+
+ You will also need to tell IIS how to handle Bugzilla's .cgi files. Using
+ the IIS Administration tool again, open up the properties for the new
+ virtual directory and select the Configuration option to access the Script
+ Mappings. Create an entry mapping .cgi to:
+<full path to perl.exe >\perl.exe -x<full path to Bugzilla> -wT "%s" %s
+
+ For example:
+c:\perl\bin\perl.exe -xc:\bugzilla -wT "%s" %s
+
+ Note
+
+ The ActiveState install may have already created an entry for .pl files that
+ is limited to "GET,HEAD,POST". If so, this mapping should be removed as
+ Bugzilla's .pl files are not designed to be run via a web server.
+
+ IIS will also need to know that the index.cgi should be treated as a default
+ document. On the Documents tab page of the virtual directory properties, you
+ need to add index.cgi as a default document type. If you wish, you may
+ remove the other default document types for this particular virtual
+ directory, since Bugzilla doesn't use any of them.
+
+ Also, and this can't be stressed enough, make sure that files such as
+ localconfig and your data directory are secured as described in Section
+ 4.2.1.
+ _________________________________________________________________
+
+2.2.5. Bugzilla
+
+ Your Bugzilla should now be working. Access http://<your-bugzilla-server>/ -
+ you should see the Bugzilla front page. If not, consult the Troubleshooting
+ section, Appendix A.
+
+ Note
+
+ The URL above may be incorrect if you installed Bugzilla into a subdirectory
+ or used a symbolic link from your web site root to the Bugzilla directory.
+
+ Log in with the administrator account you defined in the last checksetup.pl
+ run. You should go through the Parameters page and see if there are any you
+ wish to change. They key parameters are documented in Section 3.1; you
+ should certainly alter maintainer and urlbase; you may also want to alter
+ cookiepath or requirelogin.
+
+ Bugzilla has several optional features which require extra configuration.
+ You can read about those in Section 2.3.
+ _________________________________________________________________
+
+2.3. Optional Additional Configuration
+
+ Bugzilla has a number of optional features. This section describes how to
+ configure or enable them.
+ _________________________________________________________________
+
+2.3.1. Bug Graphs
+
+ If you have installed the necessary Perl modules you can start collecting
+ statistics for the nifty Bugzilla graphs.
+ bash# crontab -e
+
+ This should bring up the crontab file in your editor. Add a cron entry like
+ this to run collectstats.pl daily at 5 after midnight:
+ 5 0 * * * cd <your-bugzilla-directory> ; ./collectstats.pl
+
+ After two days have passed you'll be able to view bug graphs from the
+ Reports page.
+
+ Note
+
+ Windows does not have 'cron', but it does have the Task Scheduler, which
+ performs the same duties. There are also third-party tools that can be used
+ to implement cron, such as nncron.
+ _________________________________________________________________
+
+2.3.2. The Whining Cron
+
+ What good are bugs if they're not annoying? To help make them more so you
+ can set up Bugzilla's automatic whining system to complain at engineers
+ which leave their bugs in the CONFIRMED state without triaging them.
+
+ This can be done by adding the following command as a daily crontab entry,
+ in the same manner as explained above for bug graphs. This example runs it
+ at 12.55am.
+ 55 0 * * * cd <your-bugzilla-directory> ; ./whineatnews.pl
+
+ Note
+
+ Windows does not have 'cron', but it does have the Task Scheduler, which
+ performs the same duties. There are also third-party tools that can be used
+ to implement cron, such as nncron.
+ _________________________________________________________________
+
+2.3.3. Whining
+
+ As of Bugzilla 2.20, users can configure Bugzilla to regularly annoy them at
+ regular intervals, by having Bugzilla execute saved searches at certain
+ times and emailing the results to the user. This is known as "Whining". The
+ process of configuring Whining is described in Section 5.13, but for it to
+ work a Perl script must be executed at regular intervals.
+
+ This can be done by adding the following command as a daily crontab entry,
+ in the same manner as explained above for bug graphs. This example runs it
+ every 15 minutes.
+ */15 * * * * cd <your-bugzilla-directory> ; ./whine.pl
+
+ Note
+
+ Whines can be executed as often as every 15 minutes, so if you specify
+ longer intervals between executions of whine.pl, some users may not be
+ whined at as often as they would expect. Depending on the person, this can
+ either be a very Good Thing or a very Bad Thing.
+
+ Note
+
+ Windows does not have 'cron', but it does have the Task Scheduler, which
+ performs the same duties. There are also third-party tools that can be used
+ to implement cron, such as nncron.
+ _________________________________________________________________
+
+2.3.4. Serving Alternate Formats with the right MIME type
+
+ Some Bugzilla pages have alternate formats, other than just plain HTML. In
+ particular, a few Bugzilla pages can output their contents as either XUL (a
+ special Mozilla format, that looks like a program GUI) or RDF (a type of
+ structured XML that can be read by various programs).
+
+ In order for your users to see these pages correctly, Apache must send them
+ with the right MIME type. To do this, add the following lines to your Apache
+ configuration, either in the <VirtualHost> section for your Bugzilla, or in
+ the <Directory> section for your Bugzilla:
+
+AddType application/vnd.mozilla.xul+xml .xul
+AddType application/rdf+xml .rdf
+ _________________________________________________________________
+
+2.4. Multiple Bugzilla databases with a single installation
+
+ The previous instructions referred to a standard installation, with one
+ unique Bugzilla database. However, you may want to host several distinct
+ installations, without having several copies of the code. This is possible
+ by using the PROJECT environment variable. When accessed, Bugzilla checks
+ for the existence of this variable, and if present, uses its value to check
+ for an alternative configuration file named localconfig.<PROJECT> in the
+ same location as the default one (localconfig). It also checks for
+ customized templates in a directory named <PROJECT> in the same location as
+ the default one (template/<langcode>). By default this is
+ template/en/default so PROJECT's templates would be located at
+ template/en/PROJECT.
+
+ To set up an alternate installation, just export PROJECT=foo before running
+ checksetup.pl for the first time. It will result in a file called
+ localconfig.foo instead of localconfig. Edit this file as described above,
+ with reference to a new database, and re-run checksetup.pl to populate it.
+ That's all.
+
+ Now you have to configure the web server to pass this environment variable
+ when accessed via an alternate URL, such as virtual host for instance. The
+ following is an example of how you could do it in Apache, other Webservers
+ may differ.
+<VirtualHost 212.85.153.228:80>
+ ServerName foo.bar.baz
+ SetEnv PROJECT foo
+ Alias /bugzilla /var/www/bugzilla
+</VirtualHost>
+
+ Don't forget to also export this variable before accessing Bugzilla by other
+ means, such as cron tasks for instance.
+ _________________________________________________________________
+
+2.5. OS-Specific Installation Notes
+
+ Many aspects of the Bugzilla installation can be affected by the operating
+ system you choose to install it on. Sometimes it can be made easier and
+ others more difficult. This section will attempt to help you understand both
+ the difficulties of running on specific operating systems and the utilities
+ available to make it easier.
+
+ If you have anything to add or notes for an operating system not covered,
+ please file a bug in Bugzilla Documentation.
+ _________________________________________________________________
+
+2.5.1. Microsoft Windows
+
+ Making Bugzilla work on Windows is more difficult than making it work on
+ Unix. For that reason, we still recommend doing so on a Unix based system
+ such as GNU/Linux. That said, if you do want to get Bugzilla running on
+ Windows, you will need to make the following adjustments. A detailed
+ step-by-step installation guide for Windows is also available if you need
+ more help with your installation.
+ _________________________________________________________________
+
+2.5.1.1. Win32 Perl
+
+ Perl for Windows can be obtained from ActiveState. You should be able to
+ find a compiled binary at
+ http://aspn.activestate.com/ASPN/Downloads/ActivePerl/. The following
+ instructions assume that you are using version 5.8.1 of ActiveState.
+
+ Note
+
+ These instructions are for 32-bit versions of Windows. If you are using a
+ 64-bit version of Windows, you will need to install 32-bit Perl in order to
+ install the 32-bit modules as described below.
+ _________________________________________________________________
+
+2.5.1.2. Perl Modules on Win32
+
+ Bugzilla on Windows requires the same perl modules found in Section 2.1.5.
+ The main difference is that windows uses PPM instead of CPAN. ActiveState
+ provides a GUI to manage Perl modules. We highly recommend that you use it.
+ If you prefer to use ppm from the command-line, type:
+C:\perl> ppm install <module name>
+
+ The best source for the Windows PPM modules needed for Bugzilla is probably
+ the theory58S website, which you can add to your list of repositories as
+ follows (for Perl 5.8.x):
+ppm repo add theory58S http://theoryx5.uwinnipeg.ca/ppms/
+
+ If you are using Perl 5.10.x, you cannot use the same PPM modules as Perl
+ 5.8.x as they are incompatible. In this case, you should add the following
+ repository:
+ppm repo add theory58S http://cpan.uwinnipeg.ca/PPMPackages/10xx/
+
+ Note
+
+ In versions prior to 5.8.8 build 819 of PPM the command is
+ppm repository add theory58S http://theoryx5.uwinnipeg.ca/ppms/
+
+ Note
+
+ The PPM repository stores modules in 'packages' that may have a slightly
+ different name than the module. If retrieving these modules from there, you
+ will need to pay attention to the information provided when you run
+ checksetup.pl as it will tell you what package you'll need to install.
+
+ Tip
+
+ If you are behind a corporate firewall, you will need to let the ActiveState
+ PPM utility know how to get through it to access the repositories by setting
+ the HTTP_proxy system environmental variable. For more information on
+ setting that variable, see the ActiveState documentation.
+ _________________________________________________________________
+
+2.5.1.3. Serving the web pages
+
+ As is the case on Unix based systems, any web server should be able to
+ handle Bugzilla; however, the Bugzilla Team still recommends Apache whenever
+ asked. No matter what web server you choose, be sure to pay attention to the
+ security notes in Section 4.2.1. More information on configuring specific
+ web servers can be found in Section 2.2.4.
+
+ Note
+
+ The web server looks at /usr/bin/perl to call Perl. If you are using Apache
+ on windows, you can set the ScriptInterpreterSource directive in your Apache
+ config file to make it look at the right place: insert the line
+ ScriptInterpreterSource Registry-Strict
+
+ into your httpd.conf file, and create the key
+ HKEY_CLASSES_ROOT\.cgi\Shell\ExecCGI\Command
+
+ with C:\Perl\bin\perl.exe -T as value (adapt to your path if needed) in the
+ registry. When this is done, restart Apache.
+ _________________________________________________________________
+
+2.5.1.4. Sending Email
+
+ To enable Bugzilla to send email on Windows, the server running the Bugzilla
+ code must be able to connect to, or act as, an SMTP server.
+ _________________________________________________________________
+
+2.5.2. Mac OS X
+
+ Making Bugzilla work on Mac OS X requires the following adjustments.
+ _________________________________________________________________
+
+2.5.2.1. Sendmail
+
+ In Mac OS X 10.3 and later, Postfix is used as the built-in email server.
+ Postfix provides an executable that mimics sendmail enough to fool Bugzilla,
+ as long as Bugzilla can find it. Bugzilla is able to find the fake sendmail
+ executable without any assistance.
+ _________________________________________________________________
+
+2.5.2.2. Libraries & Perl Modules on Mac OS X
+
+ Apple does not include the GD library with Mac OS X. Bugzilla needs this for
+ bug graphs.
+
+ You can use DarwinPorts (http://darwinports.com/) or Fink
+ (http://sourceforge.net/projects/fink/), both of which are similar in nature
+ to the CPAN installer, but install common unix programs.
+
+ Follow the instructions for setting up DarwinPorts or Fink. Once you have
+ one installed, you'll want to use it to install the gd2 package.
+
+ Fink will prompt you for a number of dependencies, type 'y' and hit enter to
+ install all of the dependencies and then watch it work. You will then be
+ able to use CPAN to install the GD Perl module.
+
+ Note
+
+ To prevent creating conflicts with the software that Apple installs by
+ default, Fink creates its own directory tree at /sw where it installs most
+ of the software that it installs. This means your libraries and headers will
+ be at /sw/lib and /sw/include instead of /usr/lib and /usr/include. When the
+ Perl module config script asks where your libgd is, be sure to tell it
+ /sw/lib.
+
+ Also available via DarwinPorts and Fink is expat. After installing the expat
+ package, you will be able to install XML::Parser using CPAN. If you use
+ fink, there is one caveat. Unlike recent versions of the GD module,
+ XML::Parser doesn't prompt for the location of the required libraries. When
+ using CPAN, you will need to use the following command sequence:
+# perl -MCPAN -e'look XML::Parser' (1)
+# perl Makefile.PL EXPATLIBPATH=/sw/lib EXPATINCPATH=/sw/include
+# make; make test; make install (2)
+# exit (3)
+
+ (1) (3)
+ The look command will download the module and spawn a new shell with
+ the extracted files as the current working directory. The exit
+ command will return you to your original shell.
+ (2)
+ You should watch the output from these make commands, especially
+ "make test" as errors may prevent XML::Parser from functioning
+ correctly with Bugzilla.
+ _________________________________________________________________
+
+2.5.3. Linux Distributions
+
+ Many Linux distributions include Bugzilla and its dependencies in their
+ native package management systems. Installing Bugzilla with root access on
+ any Linux system should be as simple as finding the Bugzilla package in the
+ package management application and installing it using the normal command
+ syntax. Several distributions also perform the proper web server
+ configuration automatically on installation.
+
+ Please consult the documentation of your Linux distribution for instructions
+ on how to install packages, or for specific instructions on installing
+ Bugzilla with native package management tools. There is also a Bugzilla Wiki
+ Page for distro-specific installation notes.
+ _________________________________________________________________
+
+2.6. UNIX (non-root) Installation Notes
+
+2.6.1. Introduction
+
+ If you are running a *NIX OS as non-root, either due to lack of access (web
+ hosts, for example) or for security reasons, this will detail how to install
+ Bugzilla on such a setup. It is recommended that you read through the
+ Section 2.1 first to get an idea on the installation steps required. (These
+ notes will reference to steps in that guide.)
+ _________________________________________________________________
+
+2.6.2. MySQL
+
+ You may have MySQL installed as root. If you're setting up an account with a
+ web host, a MySQL account needs to be set up for you. From there, you can
+ create the bugs account, or use the account given to you.
+
+ Warning
+
+ You may have problems trying to set up GRANT permissions to the database. If
+ you're using a web host, chances are that you have a separate database which
+ is already locked down (or one big database with limited/no access to the
+ other areas), but you may want to ask your system administrator what the
+ security settings are set to, and/or run the GRANT command for you.
+
+ Also, you will probably not be able to change the MySQL root user password
+ (for obvious reasons), so skip that step.
+ _________________________________________________________________
+
+2.6.2.1. Running MySQL as Non-Root
+
+2.6.2.1.1. The Custom Configuration Method
+
+ Create a file .my.cnf in your home directory (using /home/foo in this
+ example) as follows....
+[mysqld]
+datadir=/home/foo/mymysql
+socket=/home/foo/mymysql/thesock
+port=8081
+
+[mysql]
+socket=/home/foo/mymysql/thesock
+port=8081
+
+[mysql.server]
+user=mysql
+basedir=/var/lib
+
+[safe_mysqld]
+err-log=/home/foo/mymysql/the.log
+pid-file=/home/foo/mymysql/the.pid
+ _________________________________________________________________
+
+2.6.2.1.2. The Custom Built Method
+
+ You can install MySQL as a not-root, if you really need to. Build it with
+ PREFIX set to /home/foo/mysql, or use pre-installed executables, specifying
+ that you want to put all of the data files in /home/foo/mysql/data. If there
+ is another MySQL server running on the system that you do not own, use the
+ -P option to specify a TCP port that is not in use.
+ _________________________________________________________________
+
+2.6.2.1.3. Starting the Server
+
+ After your mysqld program is built and any .my.cnf file is in place, you
+ must initialize the databases (ONCE).
+ bash$
+ mysql_install_db
+
+ Then start the daemon with
+ bash$
+ safe_mysql &
+
+ After you start mysqld the first time, you then connect to it as "root" and
+ GRANT permissions to other users. (Again, the MySQL root account has nothing
+ to do with the *NIX root account.)
+
+ Note
+
+ You will need to start the daemons yourself. You can either ask your system
+ administrator to add them to system startup files, or add a crontab entry
+ that runs a script to check on these daemons and restart them if needed.
+
+ Warning
+
+ Do NOT run daemons or other services on a server without first consulting
+ your system administrator! Daemons use up system resources and running one
+ may be in violation of your terms of service for any machine on which you
+ are a user!
+ _________________________________________________________________
+
+2.6.3. Perl
+
+ On the extremely rare chance that you don't have Perl on the machine, you
+ will have to build the sources yourself. The following commands should get
+ your system installed with your own personal version of Perl:
+ bash$
+ wget http://perl.org/CPAN/src/stable.tar.gz
+ bash$
+ tar zvxf stable.tar.gz
+ bash$
+ cd perl-5.8.1 (or whatever the version of Perl is called)
+ bash$
+ sh Configure -de -Dprefix=/home/foo/perl
+ bash$
+ make && make test && make install
+
+ Once you have Perl installed into a directory (probably in ~/perl/bin), you
+ will need to install the Perl Modules, described below.
+ _________________________________________________________________
+
+2.6.4. Perl Modules
+
+ Installing the Perl modules as a non-root user is accomplished by running
+ the install-module.pl script. For more details on this script, see
+ install-module.pl documentation
+ _________________________________________________________________
+
+2.6.5. HTTP Server
+
+ Ideally, this also needs to be installed as root and run under a special web
+ server account. As long as the web server will allow the running of *.cgi
+ files outside of a cgi-bin, and a way of denying web access to certain files
+ (such as a .htaccess file), you should be good in this department.
+ _________________________________________________________________
+
+2.6.5.1. Running Apache as Non-Root
+
+ You can run Apache as a non-root user, but the port will need to be set to
+ one above 1024. If you type httpd -V, you will get a list of the variables
+ that your system copy of httpd uses. One of those, namely HTTPD_ROOT, tells
+ you where that installation looks for its config information.
+
+ From there, you can copy the config files to your own home directory to
+ start editing. When you edit those and then use the -d option to override
+ the HTTPD_ROOT compiled into the web server, you get control of your own
+ customized web server.
+
+ Note
+
+ You will need to start the daemons yourself. You can either ask your system
+ administrator to add them to system startup files, or add a crontab entry
+ that runs a script to check on these daemons and restart them if needed.
+
+ Warning
+
+ Do NOT run daemons or other services on a server without first consulting
+ your system administrator! Daemons use up system resources and running one
+ may be in violation of your terms of service for any machine on which you
+ are a user!
+ _________________________________________________________________
+
+2.6.6. Bugzilla
+
+ When you run ./checksetup.pl to create the localconfig file, it will list
+ the Perl modules it finds. If one is missing, go back and double-check the
+ module installation from Section 2.6.4, then delete the localconfig file and
+ try again.
+
+ Warning
+
+ One option in localconfig you might have problems with is the web server
+ group. If you can't successfully browse to the index.cgi (like a Forbidden
+ error), you may have to relax your permissions, and blank out the web server
+ group. Of course, this may pose as a security risk. Having a properly jailed
+ shell and/or limited access to shell accounts may lessen the security risk,
+ but use at your own risk.
+ _________________________________________________________________
+
+2.6.6.1. suexec or shared hosting
+
+ If you are running on a system that uses suexec (most shared hosting
+ environments do this), you will need to set the webservergroup value in
+ localconfig to match your primary group, rather than the one the web server
+ runs under. You will need to run the following shell commands after running
+ ./checksetup.pl, every time you run it (or modify checksetup.pl to do them
+ for you via the system() command).
+ for i in docs graphs images js skins; do find $i -type d -exec chmod o+
+rx {} \; ; done
+ for i in jpg gif css js png html rdf xul; do find . -name \*.$i -exec c
+hmod o+r {} \; ; done
+ find . -name .htaccess -exec chmod o+r {} \;
+ chmod o+x . data data/webdot
+
+ Pay particular attention to the number of semicolons and dots. They are all
+ important. A future version of Bugzilla will hopefully be able to do this
+ for you out of the box.
+ _________________________________________________________________
+
+2.7. Upgrading to New Releases
+
+ Upgrading to new Bugzilla releases is very simple. There is a script
+ included with Bugzilla that will automatically do all of the database
+ migration for you.
+
+ The following sections explain how to upgrade from one version of Bugzilla
+ to another. Whether you are upgrading from one bug-fix version to another
+ (such as 3.0.1 to 3.0.2) or from one major version to another (such as from
+ 3.0 to 3.2), the instructions are always the same.
+
+ Note
+
+ Any examples in the following sections are written as though the user were
+ updating to version 2.22.1, but the procedures are the same no matter what
+ version you're updating to. Also, in the examples, the user's Bugzilla
+ installation is found at /var/www/html/bugzilla. If that is not the same as
+ the location of your Bugzilla installation, simply substitute the proper
+ paths where appropriate.
+ _________________________________________________________________
+
+2.7.1. Before You Upgrade
+
+ Before you start your upgrade, there are a few important steps to take:
+
+ 1. Read the Release Notes of the version you're upgrading to, particularly
+ the "Notes for Upgraders" section.
+ 2. View the Sanity Check (Section 3.16) page on your installation before
+ upgrading. Attempt to fix all warnings that the page produces before you
+ go any further, or you may experience problems during your upgrade.
+ 3. Shut down your Bugzilla installation by putting some HTML or text in the
+ shutdownhtml parameter (see Section 3.1).
+ 4. Make a backup of the Bugzilla database. THIS IS VERY IMPORTANT. If
+ anything goes wrong during the upgrade, your installation can be
+ corrupted beyond recovery. Having a backup keeps you safe.
+
+ Warning
+
+ Upgrading is a one-way process. You cannot "downgrade" an upgraded Bugzilla.
+ If you wish to revert to the old Bugzilla version for any reason, you will
+ have to restore your database from this backup.
+ Here are some sample commands you could use to backup your database,
+ depending on what database system you're using. You may have to modify
+ these commands for your particular setup.
+
+ MySQL:
+ mysqldump --opt -u bugs -p bugs > bugs.sql
+
+ PostgreSQL:
+ pg_dump --no-privileges --no-owner -h localhost -U bugs >
+ bugs.sql
+ _________________________________________________________________
+
+2.7.2. Getting The New Bugzilla
+
+ There are three ways to get the new version of Bugzilla. We'll list them
+ here briefly and then explain them more later.
+
+ CVS (Section 2.7.2.2)
+ If have cvs installed on your machine and you have Internet access,
+ this is the easiest way to upgrade, particularly if you have made
+ modifications to the code or templates of Bugzilla.
+
+ Download the tarball (Section 2.7.2.3)
+ This is a very simple way to upgrade, and good if you haven't made
+ many (or any) modifications to the code or templates of your
+ Bugzilla.
+
+ Patches (Section 2.7.2.4)
+ If you have made modifications to your Bugzilla, and you don't have
+ Internet access or you don't want to use cvs, then this is the best
+ way to upgrade.
+
+ You can only do minor upgrades (such as 3.0 to 3.0.1 or 3.0.1 to
+ 3.0.2) with patches.
+ _________________________________________________________________
+
+2.7.2.1. If you have modified your Bugzilla
+
+ If you have modified the code or templates of your Bugzilla, then upgrading
+ requires a bit more thought and effort. A discussion of the various methods
+ of updating compared with degree and methods of local customization can be
+ found in Section 6.3.2.
+
+ The larger the jump you are trying to make, the more difficult it is going
+ to be to upgrade if you have made local customizations. Upgrading from 3.0
+ to 3.0.1 should be fairly painless even if you are heavily customized, but
+ going from 2.18 to 3.0 is going to mean a fair bit of work re-writing your
+ local changes to use the new files, logic, templates, etc. If you have done
+ no local changes at all, however, then upgrading should be approximately the
+ same amount of work regardless of how long it has been since your version
+ was released.
+ _________________________________________________________________
+
+2.7.2.2. Upgrading using CVS
+
+ This requires that you have cvs installed (most Unix machines do), and
+ requires that you are able to access cvs-mirror.mozilla.org on port 2401,
+ which may not be an option if you are behind a highly restrictive firewall
+ or don't have Internet access.
+
+ The following shows the sequence of commands needed to update a Bugzilla
+ installation via CVS, and a typical series of results.
+bash$ cd /var/www/html/bugzilla
+bash$ cvs login
+Logging in to :pserver:anonymous@cvs-mirror.mozilla.org:2401/cvsroot
+CVS password: ('anonymous', or just leave it blank)
+bash$ cvs -q update -r BUGZILLA-2_22_1 -dP
+P checksetup.pl
+P collectstats.pl
+P docs/rel_notes.txt
+P template/en/default/list/quips.html.tmpl
+(etc.)
+
+ Caution
+
+ If a line in the output from cvs update begins with a C, then that
+ represents a file with local changes that CVS was unable to properly merge.
+ You need to resolve these conflicts manually before Bugzilla (or at least
+ the portion using that file) will be usable.
+ _________________________________________________________________
+
+2.7.2.3. Upgrading using the tarball
+
+ If you are unable (or unwilling) to use CVS, another option that's always
+ available is to obtain the latest tarball from the Download Page and create
+ a new Bugzilla installation from that.
+
+ This sequence of commands shows how to get the tarball from the
+ command-line; it is also possible to download it from the site directly in a
+ web browser. If you go that route, save the file to the /var/www/html
+ directory (or its equivalent, if you use something else) and omit the first
+ three lines of the example.
+bash$ cd /var/www/html
+bash$ wget http://ftp.mozilla.org/pub/mozilla.org/webtools/bugzilla-2.22.1.tar.
+gz
+(Output omitted)
+bash$ tar xzvf bugzilla-2.22.1.tar.gz
+bugzilla-2.22.1/
+bugzilla-2.22.1/.cvsignore
+(Output truncated)
+bash$ cd bugzilla-2.22.1
+bash$ cp ../bugzilla/localconfig* .
+bash$ cp -r ../bugzilla/data .
+bash$ cd ..
+bash$ mv bugzilla bugzilla.old
+bash$ mv bugzilla-2.22.1 bugzilla
+
+ Warning
+
+ The cp commands both end with periods which is a very important detail--it
+ means that the destination directory is the current working directory.
+
+ This upgrade method will give you a clean install of Bugzilla. That's fine
+ if you don't have any local customizations that you want to maintain. If you
+ do have customizations, then you will need to reapply them by hand to the
+ appropriate files.
+ _________________________________________________________________
+
+2.7.2.4. Upgrading using patches
+
+ A patch is a collection of all the bug fixes that have been made since the
+ last bug-fix release.
+
+ If you are doing a bug-fix upgrade—that is, one where only the last number
+ of the revision changes, such as from 2.22 to 2.22.1—then you have the
+ option of obtaining and applying a patch file from the Download Page.
+
+ As above, this example starts with obtaining the file via the command line.
+ If you have already downloaded it, you can omit the first two commands.
+bash$ cd /var/www/html/bugzilla
+bash$ wget http://ftp.mozilla.org/pub/mozilla.org/webtools/bugzilla-2.22-to-2.2
+2.1.diff.gz
+(Output omitted)
+bash$ gunzip bugzilla-2.22-to-2.22.1.diff.gz
+bash$ patch -p1 < bugzilla-2.22-to-2.22.1.diff
+patching file checksetup.pl
+patching file collectstats.pl
+(etc.)
+
+ Warning
+
+ Be aware that upgrading from a patch file does not change the entries in
+ your CVS directory. This could make it more difficult to upgrade using CVS
+ (Section 2.7.2.2) in the future.
+ _________________________________________________________________
+
+2.7.3. Completing Your Upgrade
+
+ Now that you have the new Bugzilla code, there are a few final steps to
+ complete your upgrade.
+
+ 1. If your new Bugzilla installation is in a different directory or on a
+ different machine than your old Bugzilla installation, make sure that
+ you have copied the data directory and the localconfig file from your
+ old Bugzilla installation. (If you followed the tarball instructions
+ above, this has already happened.)
+ 2. If this is a major update, check that the configuration (Section 2.2)
+ for your new Bugzilla is up-to-date. Sometimes the configuration
+ requirements change between major versions.
+ 3. If you didn't do it as part of the above configuration step, now you
+ need to run checksetup.pl, which will do everything required to convert
+ your existing database and settings for the new version:
+
+bash$ cd /var/www/html/bugzilla
+bash$ ./checksetup.pl
+
+
+ Warning
+
+ The period at the beginning of the command ./checksetup.pl is important and
+ can not be omitted.
+
+ Caution
+
+ If this is a major upgrade (say, 2.22 to 3.0 or similar), running
+ checksetup.pl on a large installation (75,000 or more bugs) can take a long
+ time, possibly several hours.
+ 4. Clear any HTML or text that you put into the shutdownhtml parameter, to
+ re-activate Bugzilla.
+ 5. View the Sanity Check (Section 3.16) page in your upgraded Bugzilla.
+ It is recommended that, if possible, you fix any problems you see,
+ immediately. Failure to do this may mean that Bugzilla will not work
+ correctly. Be aware that if the sanity check page contains more errors
+ after an upgrade, it doesn't necessarily mean there are more errors in
+ your database than there were before, as additional tests are added to
+ the sanity check over time, and it is possible that those errors weren't
+ being checked for in the old version.
+ _________________________________________________________________
+
+2.7.4. Automatic Notifications of New Releases
+
+ Bugzilla 3.0 introduced the ability to automatically notify administrators
+ when new releases are available, based on the upgrade_notification
+ parameter, see Section 3.1. Administrators will see these notifications when
+ they access the index.cgi page, i.e. generally when logging in. Bugzilla
+ will check once per day for new releases, unless the parameter is set to
+ "disabled". If you are behind a proxy, you may have to set the proxy_url
+ parameter accordingly. If the proxy requires authentication, use the
+ http://user:pass@proxy_url/ syntax.
+ _________________________________________________________________
+
+Chapter 3. Administering Bugzilla
+
+3.1. Bugzilla Configuration
+
+ Bugzilla is configured by changing various parameters, accessed from the
+ "Parameters" link in the Administration page (the Administration page can be
+ found by clicking the "Administration" link in the footer). The parameters
+ are divided into several categories, accessed via the menu on the left.
+ Following is a description of the different categories and important
+ parameters within those categories.
+ _________________________________________________________________
+
+3.1.1. Required Settings
+
+ The core required parameters for any Bugzilla installation are set here.
+ These parameters must be set before a new Bugzilla installation can be used.
+ Administrators should review this list before deploying a new Bugzilla
+ installation.
+
+ maintainer
+ Email address of the person responsible for maintaining this Bugzilla
+ installation. The address need not be that of a valid Bugzilla
+ account.
+
+ urlbase
+ Defines the fully qualified domain name and web server path to this
+ Bugzilla installation.
+
+ For example, if the Bugzilla query page is
+ http://www.foo.com/bugzilla/query.cgi, the "urlbase" should be set to
+ http://www.foo.com/bugzilla/.
+
+ docs_urlbase
+ Defines path to the Bugzilla documentation. This can be a fully
+ qualified domain name, or a path relative to "urlbase".
+
+ For example, if the "Bugzilla Configuration" page of the
+ documentation is
+ http://www.foo.com/bugzilla/docs/html/parameters.html, set the
+ "docs_urlbase" to http://www.foo.com/bugzilla/docs/html/.
+
+ sslbase
+ Defines the fully qualified domain name and web server path for HTTPS
+ (SSL) connections to this Bugzilla installation.
+
+ For example, if the Bugzilla main page is
+ https://www.foo.com/bugzilla/index.cgi, the "sslbase" should be set
+ to https://www.foo.com/bugzilla/.
+
+ ssl_redirect
+ If enabled, Bugzilla will force HTTPS (SSL) connections, by
+ automatically redirecting any users who try to use a non-SSL
+ connection.
+
+ cookiedomain
+ Defines the domain for Bugzilla cookies. This is typically left
+ blank. If there are multiple hostnames that point to the same
+ webserver, which require the same cookie, then this parameter can be
+ utilized. For example, If your website is at https://www.foo.com/,
+ setting this to .foo.com/ will also allow bar.foo.com/ to access
+ Bugzilla cookies.
+
+ cookiepath
+ Defines a path, relative to the web server root, that Bugzilla
+ cookies will be restricted to. For example, if the urlbase is set to
+ http://www.foo.com/bugzilla/, the cookiepath should be set to
+ /bugzilla/. Setting it to "/" will allow all sites served by this web
+ server or virtual host to read Bugzilla cookies.
+
+ utf8
+ Determines whether to use UTF-8 (Unicode) encoding for all text in
+ Bugzilla. New installations should set this to true to avoid
+ character encoding problems. Existing databases should set this to
+ true only after the data has been converted from existing legacy
+ character encoding to UTF-8, using the contrib/recode.pl script.
+
+ Note
+
+ If you turn this parameter from "off" to "on", you must re-run checksetup.pl
+ immediately afterward.
+
+ shutdownhtml
+ If there is any text in this field, this Bugzilla installation will
+ be completely disabled and this text will appear instead of all
+ Bugzilla pages for all users, including Admins. Used in the event of
+ site maintenance or outage situations.
+
+ Note
+
+ Although regular log-in capability is disabled while shutdownhtml is
+ enabled, safeguards are in place to protect the unfortunate admin who loses
+ connection to Bugzilla. Should this happen to you, go directly to the
+ editparams.cgi (by typing the URL in manually, if necessary). Doing this
+ will prompt you to log in, and your name/password will be accepted here (but
+ nowhere else).
+
+ announcehtml
+ Any text in this field will be displayed at the top of every HTML
+ page in this Bugzilla installation. The text is not wrapped in any
+ tags. For best results, wrap the text in a "<div>" tag. Any style
+ attributes from the CSS can be applied. For example, to make the text
+ green inside of a red box, add "id=message" to the "<div>" tag.
+
+ proxy_url
+ If this Bugzilla installation is behind a proxy, enter the proxy
+ information here to enable Bugzilla to access the Internet. Bugzilla
+ requires Internet access to utilize the upgrade_notification
+ parameter (below). If the proxy requires authentication, use the
+ syntax: http://user:pass@proxy_url/.
+
+ upgrade_notification
+ Enable or disable a notification on the homepage of this Bugzilla
+ installation when a newer version of Bugzilla is available. This
+ notification is only visible to administrators. Choose "disabled", to
+ turn off the notification. Otherwise, choose which version of
+ Bugzilla you want to be notified about: "development_snapshot" is the
+ latest release on the trunk; "latest_stable_release" is the most
+ recent release available on the most recent stable branch;
+ "stable_branch_release" the most recent release on the branch this
+ installation is based on.
+ _________________________________________________________________
+
+3.1.2. Administrative Policies
+
+ This page contains parameters for basic administrative functions. Options
+ include whether to allow the deletion of bugs and users, and whether to
+ allow users to change their email address.
+ _________________________________________________________________
+
+3.1.3. User Authentication
+
+ This page contains the settings that control how this Bugzilla installation
+ will do its authentication. Choose what authentication mechanism to use (the
+ Bugzilla database, or an external source such as LDAP), and set basic
+ behavioral parameters. For example, choose whether to require users to login
+ to browse bugs, the management of authentication cookies, and the regular
+ expression used to validate email addresses. Some parameters are highlighted
+ below.
+
+ emailregexp
+ Defines the regular expression used to validate email addresses used
+ for login names. The default attempts to match fully qualified email
+ addresses (i.e. 'user@example.com'). Some Bugzilla installations
+ allow only local user names (i.e 'user' instead of
+ 'user@example.com'). In that case, the emailsuffix parameter should
+ be used to define the email domain.
+
+ emailsuffix
+ This string is appended to login names when actually sending email to
+ a user. For example, If emailregexp has been set to allow local
+ usernames, then this parameter would contain the email domain for all
+ users (i.e. '@example.com').
+ _________________________________________________________________
+
+3.1.4. Attachments
+
+ This page allows for setting restrictions and other parameters regarding
+ attachments to bugs. For example, control size limitations and whether to
+ allow pointing to external files via a URI.
+ _________________________________________________________________
+
+3.1.5. Bug Change Policies
+
+ Set policy on default behavior for bug change events. For example, choose
+ which status to set a bug to when it is marked as a duplicate, and choose
+ whether to allow bug reporters to set the priority or target milestone. Also
+ allows for configuration of what changes should require the user to make a
+ comment, described below.
+
+ commenton*
+ All these fields allow you to dictate what changes can pass without
+ comment, and which must have a comment from the person who changed
+ them. Often, administrators will allow users to add themselves to the
+ CC list, accept bugs, or change the Status Whiteboard without adding
+ a comment as to their reasons for the change, yet require that most
+ other changes come with an explanation.
+
+ Set the "commenton" options according to your site policy. It is a
+ wise idea to require comments when users resolve, reassign, or reopen
+ bugs at the very least.
+
+ Note
+
+ It is generally far better to require a developer comment when resolving
+ bugs than not. Few things are more annoying to bug database users than
+ having a developer mark a bug "fixed" without any comment as to what the fix
+ was (or even that it was truly fixed!)
+
+ noresolveonopenblockers
+ This option will prevent users from resolving bugs as FIXED if they
+ have unresolved dependencies. Only the FIXED resolution is affected.
+ Users will be still able to resolve bugs to resolutions other than
+ FIXED if they have unresolved dependent bugs.
+ _________________________________________________________________
+
+3.1.6. Bug Fields
+
+ The parameters in this section determine the default settings of several
+ Bugzilla fields for new bugs, and also control whether certain fields are
+ used. For example, choose whether to use the "target milestone" field or the
+ "status whiteboard" field.
+
+ useqacontact
+ This allows you to define an email address for each component, in
+ addition to that of the default assignee, who will be sent carbon
+ copies of incoming bugs.
+
+ usestatuswhiteboard
+ This defines whether you wish to have a free-form, overwritable field
+ associated with each bug. The advantage of the Status Whiteboard is
+ that it can be deleted or modified with ease, and provides an
+ easily-searchable field for indexing some bugs that have some trait
+ in common.
+ _________________________________________________________________
+
+3.1.7. Bug Moving
+
+ This page controls whether this Bugzilla installation allows certain users
+ to move bugs to an external database. If bug moving is enabled, there are a
+ number of parameters that control bug moving behaviors. For example, choose
+ which users are allowed to move bugs, the location of the external database,
+ and the default product and component that bugs moved from other bug
+ databases to this Bugzilla installation are assigned to.
+ _________________________________________________________________
+
+3.1.8. Dependency Graphs
+
+ This page has one parameter that sets the location of a Web Dot server, or
+ of the Web Dot binary on the local system, that is used to generate
+ dependency graphs. Web Dot is a CGI program that creates images from .dot
+ graphic description files. If no Web Dot server or binary is specified, then
+ dependency graphs will be disabled.
+ _________________________________________________________________
+
+3.1.9. Group Security
+
+ Bugzilla allows for the creation of different groups, with the ability to
+ restrict the visibility of bugs in a group to a set of specific users.
+ Specific products can also be associated with groups, and users restricted
+ to only see products in their groups. Several parameters are described in
+ more detail below. Most of the configuration of groups and their
+ relationship to products is done on the "Groups" and "Product" pages of the
+ "Administration" area. The options on this page control global default
+ behavior. For more information on Groups and Group Security, see Section
+ 3.15
+
+ makeproductgroups
+ Determines whether or not to automatically create groups when new
+ products are created. If this is on, the groups will be used for
+ querying bugs.
+
+ usevisibilitygroups
+ If selected, user visibility will be restricted to members of groups,
+ as selected in the group configuration settings. Each user-defined
+ group can be allowed to see members of selected other groups. For
+ details on configuring groups (including the visibility restrictions)
+ see Section 3.15.2.
+
+ querysharegroup
+ The name of the group of users who are allowed to share saved
+ searches with one another. For more information on using saved
+ searches, see Saved Searches.
+ _________________________________________________________________
+
+3.1.10. LDAP Authentication
+
+ LDAP authentication is a module for Bugzilla's plugin authentication
+ architecture. This page contains all the parameters necessary to configure
+ Bugzilla for use with LDAP authentication.
+
+ The existing authentication scheme for Bugzilla uses email addresses as the
+ primary user ID, and a password to authenticate that user. All places within
+ Bugzilla that require a user ID (e.g assigning a bug) use the email address.
+ The LDAP authentication builds on top of this scheme, rather than replacing
+ it. The initial log-in is done with a username and password for the LDAP
+ directory. Bugzilla tries to bind to LDAP using those credentials and, if
+ successful, tries to map this account to a Bugzilla account. If an LDAP mail
+ attribute is defined, the value of this attribute is used, otherwise the
+ "emailsuffix" parameter is appended to LDAP username to form a full email
+ address. If an account for this address already exists in the Bugzilla
+ installation, it will log in to that account. If no account for that email
+ address exists, one is created at the time of login. (In this case, Bugzilla
+ will attempt to use the "displayName" or "cn" attribute to determine the
+ user's full name.) After authentication, all other user-related tasks are
+ still handled by email address, not LDAP username. For example, bugs are
+ still assigned by email address and users are still queried by email
+ address.
+
+ Caution
+
+ Because the Bugzilla account is not created until the first time a user logs
+ in, a user who has not yet logged is unknown to Bugzilla. This means they
+ cannot be used as an assignee or QA contact (default or otherwise), added to
+ any CC list, or any other such operation. One possible workaround is the
+ bugzilla_ldapsync.rb script in the contrib directory. Another possible
+ solution is fixing bug 201069.
+
+ Parameters required to use LDAP Authentication:
+
+ user_verify_class
+ If you want to list "LDAP" here, make sure to have set up the other
+ parameters listed below. Unless you have other (working)
+ authentication methods listed as well, you may otherwise not be able
+ to log back in to Bugzilla once you log out. If this happens to you,
+ you will need to manually edit data/params and set user_verify_class
+ to "DB".
+
+ LDAPserver
+ This parameter should be set to the name (and optionally the port) of
+ your LDAP server. If no port is specified, it assumes the default
+ LDAP port of 389.
+
+ For example: "ldap.company.com" or "ldap.company.com:3268"
+
+ You can also specify a LDAP URI, so as to use other protocols, such
+ as LDAPS or LDAPI. If port was not specified in the URI, the default
+ is either 389 or 636 for 'LDAP' and 'LDAPS' schemes respectively.
+
+ Tip
+
+ In order to use SSL with LDAP, specify a URI with "ldaps://". This will
+ force the use of SSL over port 636.
+
+ For example, normal LDAP: "ldap://ldap.company.com", LDAP over SSL:
+ "ldaps://ldap.company.com" or LDAP over a UNIX domain socket
+ "ldapi://%2fvar%2flib%2fldap_sock".
+
+ LDAPbinddn [Optional]
+ Some LDAP servers will not allow an anonymous bind to search the
+ directory. If this is the case with your configuration you should set
+ the LDAPbinddn parameter to the user account Bugzilla should use
+ instead of the anonymous bind.
+
+ Ex. "cn=default,cn=user:password"
+
+ LDAPBaseDN
+ The LDAPBaseDN parameter should be set to the location in your LDAP
+ tree that you would like to search for email addresses. Your uids
+ should be unique under the DN specified here.
+
+ Ex. "ou=People,o=Company"
+
+ LDAPuidattribute
+ The LDAPuidattribute parameter should be set to the attribute which
+ contains the unique UID of your users. The value retrieved from this
+ attribute will be used when attempting to bind as the user to confirm
+ their password.
+
+ Ex. "uid"
+
+ LDAPmailattribute
+ The LDAPmailattribute parameter should be the name of the attribute
+ which contains the email address your users will enter into the
+ Bugzilla login boxes.
+
+ Ex. "mail"
+ _________________________________________________________________
+
+3.1.11. RADIUS Authentication
+
+ RADIUS authentication is a module for Bugzilla's plugin authentication
+ architecture. This page contains all the parameters necessary for
+ configuring Bugzilla to use RADIUS authentication.
+
+ Note
+
+ Most caveats that apply to LDAP authentication apply to RADIUS
+ authentication as well. See Section 3.1.10 for details.
+
+ Parameters required to use RADIUS Authentication:
+
+ user_verify_class
+ If you want to list "RADIUS" here, make sure to have set up the other
+ parameters listed below. Unless you have other (working)
+ authentication methods listed as well, you may otherwise not be able
+ to log back in to Bugzilla once you log out. If this happens to you,
+ you will need to manually edit data/params and set user_verify_class
+ to "DB".
+
+ RADIUS_server
+ This parameter should be set to the name (and optionally the port) of
+ your RADIUS server.
+
+ RADIUS_secret
+ This parameter should be set to the RADIUS server's secret.
+
+ RADIUS_email_suffix
+ Bugzilla needs an e-mail address for each user account. Therefore, it
+ needs to determine the e-mail address corresponding to a RADIUS user.
+ Bugzilla offers only a simple way to do this: it can concatenate a
+ suffix to the RADIUS user name to convert it into an e-mail address.
+ You can specify this suffix in the RADIUS_email_suffix parameter.
+
+ If this simple solution does not work for you, you'll probably need
+ to modify Bugzilla/Auth/Verify/RADIUS.pm to match your requirements.
+ _________________________________________________________________
+
+3.1.12. Email
+
+ This page contains all of the parameters for configuring how Bugzilla deals
+ with the email notifications it sends. See below for a summary of important
+ options.
+
+ mail_delivery_method
+ This is used to specify how email is sent, or if it is sent at all.
+ There are several options included for different MTAs, along with two
+ additional options that disable email sending. "Test" does not send
+ mail, but instead saves it in data/mailer.testfile for later review.
+ "None" disables email sending entirely.
+
+ mailfrom
+ This is the email address that will appear in the "From" field of all
+ emails sent by this Bugzilla installation. Some email servers require
+ mail to be from a valid email address, therefore it is recommended to
+ choose a valid email address here.
+
+ whinedays
+ Set this to the number of days you want to let bugs go in the
+ CONFIRMED state before notifying people they have untouched new bugs.
+ If you do not plan to use this feature, simply do not set up the
+ whining cron job described in the installation instructions, or set
+ this value to "0" (never whine).
+
+ globalwatcher
+ This allows you to define specific users who will receive
+ notification each time a new bug in entered, or when an existing bug
+ changes, according to the normal groupset permissions. It may be
+ useful for sending notifications to a mailing-list, for instance.
+ _________________________________________________________________
+
+3.1.13. Patch Viewer
+
+ This page contains configuration parameters for the CVS server, Bonsai
+ server and LXR server that Bugzilla will use to enable the features of the
+ Patch Viewer. Bonsai is a tool that enables queries to a CVS tree. LXR is a
+ tool that can cross reference and index source code.
+ _________________________________________________________________
+
+3.1.14. Query Defaults
+
+ This page controls the default behavior of Bugzilla in regards to several
+ aspects of querying bugs. Options include what the default query options
+ are, what the "My Bugs" page returns, whether users can freely add bugs to
+ the quip list, and how many duplicate bugs are needed to add a bug to the
+ "most frequently reported" list.
+ _________________________________________________________________
+
+3.1.15. Shadow Database
+
+ This page controls whether a shadow database is used, and all the parameters
+ associated with the shadow database. Versions of Bugzilla prior to 3.2 used
+ the MyISAM table type, which supports only table-level write locking. With
+ MyISAM, any time someone is making a change to a bug, the entire table is
+ locked until the write operation is complete. Locking for write also blocks
+ reads until the write is complete.
+
+ The "shadowdb" parameter was designed to get around this limitation. While
+ only a single user is allowed to write to a table at a time, reads can
+ continue unimpeded on a read-only shadow copy of the database.
+
+ Note
+
+ As of version 3.2, Bugzilla no longer uses the MyISAM table type. Instead,
+ InnoDB is used, which can do transaction-based locking. Therefore, the
+ limitations the Shadow Database feature was designed to workaround no longer
+ exist.
+ _________________________________________________________________
+
+3.1.16. User Matching
+
+ The settings on this page control how users are selected and queried when
+ adding a user to a bug. For example, users need to be selected when choosing
+ who the bug is assigned to, adding to the CC list or selecting a QA contact.
+ With the "usemenuforusers" parameter, it is possible to configure Bugzilla
+ to display a list of users in the fields instead of an empty text field.
+ This should only be used in Bugzilla installations with a small number of
+ users. If users are selected via a text box, this page also contains
+ parameters for how user names can be queried and matched when entered.
+ _________________________________________________________________
+
+3.2. User Administration
+
+3.2.1. Creating the Default User
+
+ When you first run checksetup.pl after installing Bugzilla, it will prompt
+ you for the administrative username (email address) and password for this
+ "super user". If for some reason you delete the "super user" account,
+ re-running checksetup.pl will again prompt you for this username and
+ password.
+
+ Tip
+
+ If you wish to add more administrative users, add them to the "admin" group
+ and, optionally, edit the tweakparams, editusers, creategroups,
+ editcomponents, and editkeywords groups to add the entire admin group to
+ those groups (which is the case by default).
+ _________________________________________________________________
+
+3.2.2. Managing Other Users
+
+3.2.2.1. Searching for existing users
+
+ If you have "editusers" privileges or if you are allowed to grant privileges
+ for some groups, the "Users" link will appear in the Administration page.
+
+ The first screen is a search form to search for existing user accounts. You
+ can run searches based either on the user ID, real name or login name (i.e.
+ the email address, or just the first part of the email address if the
+ "emailsuffix" parameter is set). The search can be conducted in different
+ ways using the listbox to the right of the text entry box. You can match by
+ case-insensitive substring (the default), regular expression, a reverse
+ regular expression match (which finds every user name which does NOT match
+ the regular expression), or the exact string if you know exactly who you are
+ looking for. The search can be restricted to users who are in a specific
+ group. By default, the restriction is turned off.
+
+ The search returns a list of users matching your criteria. User properties
+ can be edited by clicking the login name. The Account History of a user can
+ be viewed by clicking the "View" link in the Account History column. The
+ Account History displays changes that have been made to the user account,
+ the time of the change and the user who made the change. For example, the
+ Account History page will display details of when a user was added or
+ removed from a group.
+ _________________________________________________________________
+
+3.2.2.2. Creating new users
+
+3.2.2.2.1. Self-registration
+
+ By default, users can create their own user accounts by clicking the "New
+ Account" link at the bottom of each page (assuming they aren't logged in as
+ someone else already). If you want to disable this self-registration, or if
+ you want to restrict who can create his own user account, you have to edit
+ the "createemailregexp" parameter in the "Configuration" page, see Section
+ 3.1.
+ _________________________________________________________________
+
+3.2.2.2.2. Accounts created by an administrator
+
+ Users with "editusers" privileges, such as administrators, can create user
+ accounts for other users:
+
+ 1. After logging in, click the "Users" link at the footer of the query
+ page, and then click "Add a new user".
+ 2. Fill out the form presented. This page is self-explanatory. When done,
+ click "Submit".
+
+ Note
+
+ Adding a user this way will not send an email informing them of their
+ username and password. While useful for creating dummy accounts (watchers
+ which shuttle mail to another system, for instance, or email addresses which
+ are a mailing list), in general it is preferable to log out and use the "New
+ Account" button to create users, as it will pre-populate all the required
+ fields and also notify the user of her account name and password.
+ _________________________________________________________________
+
+3.2.2.3. Modifying Users
+
+ Once you have found your user, you can change the following fields:
+
+ * Login Name: This is generally the user's full email address. However, if
+ you have are using the "emailsuffix" parameter, this may just be the
+ user's login name. Note that users can now change their login names
+ themselves (to any valid email address).
+ * Real Name: The user's real name. Note that Bugzilla does not require
+ this to create an account.
+ * Password: You can change the user's password here. Users can
+ automatically request a new password, so you shouldn't need to do this
+ often. If you want to disable an account, see Disable Text below.
+ * Bugmail Disabled: Mark this checkbox to disable bugmail and whinemail
+ completely for this account. This checkbox replaces the data/nomail file
+ which existed in older versions of Bugzilla.
+ * Disable Text: If you type anything in this box, including just a space,
+ the user is prevented from logging in, or making any changes to bugs via
+ the web interface. The HTML you type in this box is presented to the
+ user when they attempt to perform these actions, and should explain why
+ the account was disabled.
+ Users with disabled accounts will continue to receive mail from
+ Bugzilla; furthermore, they will not be able to log in themselves to
+ change their own preferences and stop it. If you want an account
+ (disabled or active) to stop receiving mail, simply check the "Bugmail
+ Disabled" checkbox above.
+
+ Note
+
+ Even users whose accounts have been disabled can still submit bugs via the
+ e-mail gateway, if one exists. The e-mail gateway should not be enabled for
+ secure installations of Bugzilla.
+
+ Warning
+
+ Don't disable all the administrator accounts!
+ * <groupname>: If you have created some groups, e.g. "securitysensitive",
+ then checkboxes will appear here to allow you to add users to, or remove
+ them from, these groups. The first checkbox gives the user the ability
+ to add and remove other users as members of this group. The second
+ checkbox adds the user himself as a member of the group.
+ * canconfirm: This field is only used if you have enabled the
+ "unconfirmed" status. If you enable this for a user, that user can then
+ move bugs from "Unconfirmed" to a "Confirmed" status (e.g.: "New"
+ status).
+ * creategroups: This option will allow a user to create and destroy groups
+ in Bugzilla.
+ * editbugs: Unless a user has this bit set, they can only edit those bugs
+ for which they are the assignee or the reporter. Even if this option is
+ unchecked, users can still add comments to bugs.
+ * editcomponents: This flag allows a user to create new products and
+ components, as well as modify and destroy those that have no bugs
+ associated with them. If a product or component has bugs associated with
+ it, those bugs must be moved to a different product or component before
+ Bugzilla will allow them to be destroyed.
+ * editkeywords: If you use Bugzilla's keyword functionality, enabling this
+ feature allows a user to create and destroy keywords. As always, the
+ keywords for existing bugs containing the keyword the user wishes to
+ destroy must be changed before Bugzilla will allow it to die.
+ * editusers: This flag allows a user to do what you're doing right now:
+ edit other users. This will allow those with the right to do so to
+ remove administrator privileges from other users or grant them to
+ themselves. Enable with care.
+ * tweakparams: This flag allows a user to change Bugzilla's Params (using
+ editparams.cgi.)
+ * <productname>: This allows an administrator to specify the products in
+ which a user can see bugs. If you turn on the "makeproductgroups"
+ parameter in the Group Security Panel in the Parameters page, then
+ Bugzilla creates one group per product (at the time you create the
+ product), and this group has exactly the same name as the product
+ itself. Note that for products that already exist when the parameter is
+ turned on, the corresponding group will not be created. The user must
+ still have the "editbugs" privilege to edit bugs in these products.
+ _________________________________________________________________
+
+3.2.2.4. Deleting Users
+
+ If the "allowuserdeletion" parameter is turned on, see Section 3.1, then you
+ can also delete user accounts. Note that this is most of the time not the
+ best thing to do. If only a warning in a yellow box is displayed, then the
+ deletion is safe. If a warning is also displayed in a red box, then you
+ should NOT try to delete the user account, else you will get referential
+ integrity problems in your database, which can lead to unexpected behavior,
+ such as bugs not appearing in bug lists anymore, or data displaying
+ incorrectly. You have been warned!
+ _________________________________________________________________
+
+3.2.2.5. Impersonating Users
+
+ There may be times when an administrator would like to do something as
+ another user. The sudo feature may be used to do this.
+
+ Note
+
+ To use the sudo feature, you must be in the bz_sudoers group. By default,
+ all administrators are in this group.
+
+ If you have access to this feature, you may start a session by going to the
+ Edit Users page, Searching for a user and clicking on their login. You
+ should see a link below their login name titled "Impersonate this user".
+ Click on the link. This will take you to a page where you will see a
+ description of the feature and instructions for using it. After reading the
+ text, simply enter the login of the user you would like to impersonate,
+ provide a short message explaining why you are doing this, and press the
+ button.
+
+ As long as you are using this feature, everything you do will be done as if
+ you were logged in as the user you are impersonating.
+
+ Warning
+
+ The user you are impersonating will not be told about what you are doing. If
+ you do anything that results in mail being sent, that mail will appear to be
+ from the user you are impersonating. You should be extremely careful while
+ using this feature.
+ _________________________________________________________________
+
+3.3. Classifications
+
+ Classifications tend to be used in order to group several related products
+ into one distinct entity.
+
+ The classifications layer is disabled by default; it can be turned on or off
+ using the useclassification parameter, in the Bug Fields section of the edit
+ parameters screen.
+
+ Access to the administration of classifications is controlled using the
+ editclassifications system group, which defines a privilege for creating,
+ destroying, and editing classifications.
+
+ When activated, classifications will introduce an additional step when
+ filling bugs (dedicated to classification selection), and they will also
+ appear in the advanced search form.
+ _________________________________________________________________
+
+3.4. Products
+
+ Products typically represent real-world shipping products. Products can be
+ given Classifications. For example, if a company makes computer games, they
+ could have a classification of "Games", and a separate product for each
+ game. This company might also have a "Common" product for units of
+ technology used in multiple games, and perhaps a few special products that
+ represent items that are not actually shipping products (for example,
+ "Website", or "Administration").
+
+ Many of Bugzilla's settings are configurable on a per-product basis. The
+ number of "votes" available to users is set per-product, as is the number of
+ votes required to move a bug automatically from the UNCONFIRMED status to
+ the CONFIRMED status.
+
+ When creating or editing products the following options are available:
+
+ Product
+ The name of the product
+
+ Description
+ A brief description of the product
+
+ Default milestone
+ Select the default milestone for this product.
+
+ Closed for bug entry
+ Select this box to prevent new bugs from being entered against this
+ product.
+
+ Maximum votes per person
+ Maximum votes a user is allowed to give for this product
+
+ Maximum votes a person can put on a single bug
+ Maximum votes a user is allowed to give for this product in a single
+ bug
+
+ Confirmation threshold
+ Number of votes needed to automatically remove any bug against this
+ product from the UNCONFIRMED state
+
+ Version
+ Specify which version of the product bugs will be entered against.
+
+ Create chart datasets for this product
+ Select to make chart datasets available for this product.
+
+ When editing a product there is also a link to edit Group Access Controls,
+ see Section 3.4.4.
+ _________________________________________________________________
+
+3.4.1. Creating New Products
+
+ To create a new product:
+
+ 1. Select "Administration" from the footer and then choose "Products" from
+ the main administration page.
+ 2. Select the "Add" link in the bottom right.
+ 3. Enter the name of the product and a description. The Description field
+ may contain HTML.
+ 4. When the product is created, Bugzilla will give a message stating that a
+ component must be created before any bugs can be entered against the new
+ product. Follow the link to create a new component. See Components for
+ more information.
+ _________________________________________________________________
+
+3.4.2. Editing Products
+
+ To edit an existing product, click the "Products" link from the
+ "Administration" page. If the 'useclassification' parameter is turned on, a
+ table of existing classifications is displayed, including an "Unclassified"
+ category. The table indicates how many products are in each classification.
+ Click on the classification name to see its products. If the
+ 'useclassification' parameter is not in use, the table lists all products
+ directly. The product table summarizes the information about the product
+ defined when the product was created. Click on the product name to edit
+ these properties, and to access links to other product attributes such as
+ the product's components, versions, milestones, and group access controls.
+ _________________________________________________________________
+
+3.4.3. Adding or Editing Components, Versions and Target Milestones
+
+ To edit existing, or add new, Components, Versions or Target Milestones to a
+ Product, select the "Edit Components", "Edit Versions" or "Edit Milestones"
+ links from the "Edit Product" page. A table of existing Components, Versions
+ or Milestones is displayed. Click on a item name to edit the properties of
+ that item. Below the table is a link to add a new Component, Version or
+ Milestone.
+
+ For more information on components, see Components.
+
+ For more information on versions, see Section 3.6.
+
+ For more information on milestones, see Section 3.7.
+ _________________________________________________________________
+
+3.4.4. Assigning Group Controls to Products
+
+ On the "Edit Product" page, there is a link called "Edit Group Access
+ Controls". The settings on this page control the relationship of the groups
+ to the product being edited.
+
+ Group Access Controls are an important aspect of using groups for isolating
+ products and restricting access to bugs filed against those products. For
+ more information on groups, including how to create, edit add users to, and
+ alter permission of, see Section 3.15.
+
+ After selecting the "Edit Group Access Controls" link from the "Edit
+ Product" page, a table containing all user-defined groups for this Bugzilla
+ installation is displayed. The system groups that are created when Bugzilla
+ is installed are not applicable to Group Access Controls. Below is
+ description of what each of these fields means.
+
+ Groups may be applicable (e.g bugs in this product can be associated with
+ this group) , default (e.g. bugs in this product are in this group by
+ default), and mandatory (e.g. bugs in this product must be associated with
+ this group) for each product. Groups can also control access to bugs for a
+ given product, or be used to make bugs for a product totally read-only
+ unless the group restrictions are met. The best way to understand these
+ relationships is by example. See Section 3.4.4.1 for examples of product and
+ group relationships.
+
+ Note
+
+ Products and Groups are not limited to a one-to-one relationship. Multiple
+ groups can be associated with the same product, and groups can be associated
+ with more than one product.
+
+ If any group has Entry selected, then the product will restrict bug entry to
+ only those users who are members of all the groups with Entry selected.
+
+ If any group has Canedit selected, then the product will be read-only for
+ any users who are not members of all of the groups with Canedit selected.
+ Only users who are members of all the Canedit groups will be able to edit
+ bugs for this product. This is an additional restriction that enables
+ finer-grained control over products rather than just all-or-nothing access
+ levels.
+
+ The following settings let you choose privileges on a per-product basis.
+ This is a convenient way to give privileges to some users for some products
+ only, without having to give them global privileges which would affect all
+ products.
+
+ Any group having editcomponents selected allows users who are in this group
+ to edit all aspects of this product, including components, milestones and
+ versions.
+
+ Any group having canconfirm selected allows users who are in this group to
+ confirm bugs in this product.
+
+ Any group having editbugs selected allows users who are in this group to
+ edit all fields of bugs in this product.
+
+ The MemberControl and OtherControl are used in tandem to determine which
+ bugs will be placed in this group. The only allowable combinations of these
+ two parameters are listed in a table on the "Edit Group Access Controls"
+ page. Consult this table for details on how these fields can be used.
+ Examples of different uses are described below.
+ _________________________________________________________________
+
+3.4.4.1. Common Applications of Group Controls
+
+ The use of groups is best explained by providing examples that illustrate
+ configurations for common use cases. The examples follow a common syntax:
+ Group: Entry, MemberControl, OtherControl, CanEdit, EditComponents,
+ CanConfirm, EditBugs. Where "Group" is the name of the group being edited
+ for this product. The other fields all correspond to the table on the "Edit
+ Group Access Controls" page. If any of these options are not listed, it
+ means they are not checked.
+
+ Basic Product/Group Restriction
+
+ Suppose there is a product called "Bar". The "Bar" product can only have
+ bugs entered against it by users in the group "Foo". Additionally, bugs
+ filed against product "Bar" must stay restricted to users to "Foo" at all
+ times. Furthermore, only members of group "Foo" can edit bugs filed against
+ product "Bar", even if other users could see the bug. This arrangement would
+ achieved by the following:
+Product Bar:
+foo: ENTRY, MANDATORY/MANDATORY, CANEDIT
+
+ Perhaps such strict restrictions are not needed for product "Bar". A more
+ lenient way to configure product "Bar" and group "Foo" would be:
+Product Bar:
+foo: ENTRY, SHOWN/SHOWN, EDITCOMPONENTS, CANCONFIRM, EDITBUGS
+
+ The above indicates that for product "Bar", members of group "Foo" can enter
+ bugs. Any one with permission to edit a bug against product "Bar" can put
+ the bug in group "Foo", even if they themselves are not in "Foo". Anyone in
+ group "Foo" can edit all aspects of the components of product "Bar", can
+ confirm bugs against product "Bar", and can edit all fields of any bug
+ against product "Bar".
+
+ General User Access With Security Group
+
+ To permit any user to file bugs against "Product A", and to permit any user
+ to submit those bugs into a group called "Security":
+
+Product A:
+security: SHOWN/SHOWN
+
+ General User Access With A Security Product
+
+ To permit any user to file bugs against product called "Security" while
+ keeping those bugs from becoming visible to anyone outside the group
+ "SecurityWorkers" (unless a member of the "SecurityWorkers" group removes
+ that restriction):
+
+Product Security:
+securityworkers: DEFAULT/MANDATORY
+
+ Product Isolation With a Common Group
+
+ To permit users of "Product A" to access the bugs for "Product A", users of
+ "Product B" to access the bugs for "Product B", and support staff, who are
+ members of the "Support Group" to access both, three groups are needed:
+
+ 1. Support Group: Contains members of the support staff.
+ 2. AccessA Group: Contains users of product A and the Support group.
+ 3. AccessB Group: Contains users of product B and the Support group.
+
+ Once these three groups are defined, the product group controls can be set
+ to:
+Product A:
+AccessA: ENTRY, MANDATORY/MANDATORY
+Product B:
+AccessB: ENTRY, MANDATORY/MANDATORY
+
+ Perhaps the "Support Group" wants more control. For example, the "Support
+ Group" could be permitted to make bugs inaccessible to users of both groups
+ "AccessA" and "AccessB". Then, the "Support Group" could be permitted to
+ publish bugs relevant to all users in a third product (let's call it
+ "Product Common") that is read-only to anyone outside the "Support Group".
+ In this way the "Support Group" could control bugs that should be seen by
+ both groups. That configuration would be:
+Product A:
+AccessA: ENTRY, MANDATORY/MANDATORY
+Support: SHOWN/NA
+Product B:
+AccessB: ENTRY, MANDATORY/MANDATORY
+Support: SHOWN/NA
+Product Common:
+Support: ENTRY, DEFAULT/MANDATORY, CANEDIT
+
+ Make a Product Read Only
+
+ Sometimes a product is retired and should no longer have new bugs filed
+ against it (for example, an older version of a software product that is no
+ longer supported). A product can be made read-only by creating a group
+ called "readonly" and adding products to the group as needed:
+Product A:
+ReadOnly: ENTRY, NA/NA, CANEDIT
+
+ Note
+
+ For more information on Groups outside of how they relate to products see
+ Section 3.15.
+ _________________________________________________________________
+
+3.5. Components
+
+ Components are subsections of a Product. E.g. the computer game you are
+ designing may have a "UI" component, an "API" component, a "Sound System"
+ component, and a "Plugins" component, each overseen by a different
+ programmer. It often makes sense to divide Components in Bugzilla according
+ to the natural divisions of responsibility within your Product or company.
+
+ Each component has a default assignee and (if you turned it on in the
+ parameters), a QA Contact. The default assignee should be the primary person
+ who fixes bugs in that component. The QA Contact should be the person who
+ will ensure these bugs are completely fixed. The Assignee, QA Contact, and
+ Reporter will get email when new bugs are created in this Component and when
+ these bugs change. Default Assignee and Default QA Contact fields only
+ dictate the default assignments; these can be changed on bug submission, or
+ at any later point in a bug's life.
+
+ To create a new Component:
+
+ 1. Select the "Edit components" link from the "Edit product" page
+ 2. Select the "Add" link in the bottom right.
+ 3. Fill out the "Component" field, a short "Description", the "Default
+ Assignee", "Default CC List" and "Default QA Contact" (if enabled). The
+ "Component Description" field may contain a limited subset of HTML tags.
+ The "Default Assignee" field must be a login name already existing in
+ the Bugzilla database.
+ _________________________________________________________________
+
+3.6. Versions
+
+ Versions are the revisions of the product, such as "Flinders 3.1", "Flinders
+ 95", and "Flinders 2000". Version is not a multi-select field; the usual
+ practice is to select the earliest version known to have the bug.
+
+ To create and edit Versions:
+
+ 1. From the "Edit product" screen, select "Edit Versions"
+ 2. You will notice that the product already has the default version
+ "undefined". Click the "Add" link in the bottom right.
+ 3. Enter the name of the Version. This field takes text only. Then click
+ the "Add" button.
+ _________________________________________________________________
+
+3.7. Milestones
+
+ Milestones are "targets" that you plan to get a bug fixed by. For example,
+ you have a bug that you plan to fix for your 3.0 release, it would be
+ assigned the milestone of 3.0.
+
+ Note
+
+ Milestone options will only appear for a Product if you turned on the
+ "usetargetmilestone" parameter in the "Bug Fields" tab of the "Parameters"
+ page.
+
+ To create new Milestones, and set Default Milestones:
+
+ 1. Select "Edit milestones" from the "Edit product" page.
+ 2. Select "Add" in the bottom right corner.
+ 3. Enter the name of the Milestone in the "Milestone" field. You can
+ optionally set the "sortkey", which is a positive or negative number
+ (-32768 to 32767) that defines where in the list this particular
+ milestone appears. This is because milestones often do not occur in
+ alphanumeric order For example, "Future" might be after "Release 1.2".
+ Select "Add".
+ _________________________________________________________________
+
+3.8. Flags
+
+ Flags are a way to attach a specific status to a bug or attachment, either
+ "+" or "-". The meaning of these symbols depends on the text the flag
+ itself, but contextually they could mean pass/fail, accept/reject,
+ approved/denied, or even a simple yes/no. If your site allows requestable
+ flags, then users may set a flag to "?" as a request to another user that
+ they look at the bug/attachment, and set the flag to its correct status.
+ _________________________________________________________________
+
+3.8.1. A Simple Example
+
+ A developer might want to ask their manager, "Should we fix this bug before
+ we release version 2.0?" They might want to do this for a lot of bugs, so it
+ would be nice to streamline the process...
+
+ In Bugzilla, it would work this way:
+
+ 1. The Bugzilla administrator creates a flag type called "blocking2.0" that
+ shows up on all bugs in your product.
+ It shows up on the "Show Bug" screen as the text "blocking2.0" with a
+ drop-down box next to it. The drop-down box contains four values: an
+ empty space, "?", "-", and "+".
+ 2. The developer sets the flag to "?".
+ 3. The manager sees the blocking2.0 flag with a "?" value.
+ 4. If the manager thinks the feature should go into the product before
+ version 2.0 can be released, he sets the flag to "+". Otherwise, he sets
+ it to "-".
+ 5. Now, every Bugzilla user who looks at the bug knows whether or not the
+ bug needs to be fixed before release of version 2.0.
+ _________________________________________________________________
+
+3.8.2. About Flags
+
+3.8.2.1. Values
+
+ Flags can have three values:
+
+ ?
+ A user is requesting that a status be set. (Think of it as 'A
+ question is being asked'.)
+
+ -
+ The status has been set negatively. (The question has been answered
+ "no".)
+
+ +
+ The status has been set positively. (The question has been answered
+ "yes".)
+
+ Actually, there's a fourth value a flag can have -- "unset" -- which shows
+ up as a blank space. This just means that nobody has expressed an opinion
+ (or asked someone else to express an opinion) about this bug or attachment.
+ _________________________________________________________________
+
+3.8.3. Using flag requests
+
+ If a flag has been defined as 'requestable', and a user has enough
+ privileges to request it (see below), the user can set the flag's status to
+ "?". This status indicates that someone (a.k.a. "the requester") is asking
+ someone else to set the flag to either "+" or "-".
+
+ If a flag has been defined as 'specifically requestable', a text box will
+ appear next to the flag into which the requester may enter a Bugzilla
+ username. That named person (a.k.a. "the requestee") will receive an email
+ notifying them of the request, and pointing them to the bug/attachment in
+ question.
+
+ If a flag has not been defined as 'specifically requestable', then no such
+ text-box will appear. A request to set this flag cannot be made of any
+ specific individual, but must be asked "to the wind". A requester may "ask
+ the wind" on any flag simply by leaving the text-box blank.
+ _________________________________________________________________
+
+3.8.4. Two Types of Flags
+
+ Flags can go in two places: on an attachment, or on a bug.
+ _________________________________________________________________
+
+3.8.4.1. Attachment Flags
+
+ Attachment flags are used to ask a question about a specific attachment on a
+ bug.
+
+ Many Bugzilla installations use this to request that one developer "review"
+ another developer's code before they check it in. They attach the code to a
+ bug report, and then set a flag on that attachment called "review" to
+ review?boss@domain.com. boss@domain.com is then notified by email that he
+ has to check out that attachment and approve it or deny it.
+
+ For a Bugzilla user, attachment flags show up in three places:
+
+ 1. On the list of attachments in the "Show Bug" screen, you can see the
+ current state of any flags that have been set to ?, +, or -. You can see
+ who asked about the flag (the requester), and who is being asked (the
+ requestee).
+ 2. When you "Edit" an attachment, you can see any settable flag, along with
+ any flags that have already been set. This "Edit Attachment" screen is
+ where you set flags to ?, -, +, or unset them.
+ 3. Requests are listed in the "Request Queue", which is accessible from the
+ "My Requests" link (if you are logged in) or "Requests" link (if you are
+ logged out) visible in the footer of all pages.
+ _________________________________________________________________
+
+3.8.4.2. Bug Flags
+
+ Bug flags are used to set a status on the bug itself. You can see Bug Flags
+ in the "Show Bug" and "Requests" screens, as described above.
+
+ Only users with enough privileges (see below) may set flags on bugs. This
+ doesn't necessarily include the assignee, reporter, or users with the
+ editbugs permission.
+ _________________________________________________________________
+
+3.8.5. Administering Flags
+
+ If you have the "editcomponents" permission, you can edit Flag Types from
+ the main administration page. Clicking the "Flags" link will bring you to
+ the "Administer Flag Types" page. Here, you can select whether you want to
+ create (or edit) a Bug flag, or an Attachment flag.
+
+ No matter which you choose, the interface is the same, so we'll just go over
+ it once.
+ _________________________________________________________________
+
+3.8.5.1. Editing a Flag
+
+ To edit a flag's properties, just click the flag's name. That will take you
+ to the same form as described below (Section 3.8.5.2).
+ _________________________________________________________________
+
+3.8.5.2. Creating a Flag
+
+ When you click on the "Create a Flag Type for..." link, you will be
+ presented with a form. Here is what the fields in the form mean:
+ _________________________________________________________________
+
+3.8.5.2.1. Name
+
+ This is the name of the flag. This will be displayed to Bugzilla users who
+ are looking at or setting the flag. The name may contain any valid Unicode
+ characters except commas and spaces.
+ _________________________________________________________________
+
+3.8.5.2.2. Description
+
+ The description describes the flag in more detail. It is visible in a
+ tooltip when hovering over a flag either in the "Show Bug" or "Edit
+ Attachment" pages. This field can be as long as you like, and can contain
+ any character you want.
+ _________________________________________________________________
+
+3.8.5.2.3. Category
+
+ Default behaviour for a newly-created flag is to appear on products and all
+ components, which is why "__Any__:__Any__" is already entered in the
+ "Inclusions" box. If this is not your desired behaviour, you must either set
+ some exclusions (for products on which you don't want the flag to appear),
+ or you must remove "__Any__:__Any__" from the Inclusions box and define
+ products/components specifically for this flag.
+
+ To create an Inclusion, select a Product from the top drop-down box. You may
+ also select a specific component from the bottom drop-down box. (Setting
+ "__Any__" for Product translates to, "all the products in this Bugzilla".
+ Selecting "__Any__" in the Component field means "all components in the
+ selected product.") Selections made, press "Include", and your
+ Product/Component pairing will show up in the "Inclusions" box on the right.
+
+ To create an Exclusion, the process is the same; select a Product from the
+ top drop-down box, select a specific component if you want one, and press
+ "Exclude". The Product/Component pairing will show up in the "Exclusions"
+ box on the right.
+
+ This flag will and can be set for any products/components that appearing in
+ the "Inclusions" box (or which fall under the appropriate "__Any__"). This
+ flag will not appear (and therefore cannot be set) on any products appearing
+ in the "Exclusions" box. IMPORTANT: Exclusions override inclusions.
+
+ You may select a Product without selecting a specific Component, but you
+ can't select a Component without a Product, or to select a Component that
+ does not belong to the named Product. If you do so, Bugzilla will display an
+ error message, even if all your products have a component by that name.
+
+ Example: Let's say you have a product called "Jet Plane" that has thousands
+ of components. You want to be able to ask if a problem should be fixed in
+ the next model of plane you release. We'll call the flag "fixInNext". But,
+ there's one component in "Jet Plane," called "Pilot." It doesn't make sense
+ to release a new pilot, so you don't want to have the flag show up in that
+ component. So, you include "Jet Plane:__Any__" and you exclude "Jet
+ Plane:Pilot".
+ _________________________________________________________________
+
+3.8.5.2.4. Sort Key
+
+ Flags normally show up in alphabetical order. If you want them to show up in
+ a different order, you can use this key set the order on each flag. Flags
+ with a lower sort key will appear before flags with a higher sort key. Flags
+ that have the same sort key will be sorted alphabetically, but they will
+ still be after flags with a lower sort key, and before flags with a higher
+ sort key.
+
+ Example: I have AFlag (Sort Key 100), BFlag (Sort Key 10), CFlag (Sort Key
+ 10), and DFlag (Sort Key 1). These show up in the order: DFlag, BFlag,
+ CFlag, AFlag.
+ _________________________________________________________________
+
+3.8.5.2.5. Active
+
+ Sometimes, you might want to keep old flag information in the Bugzilla
+ database, but stop users from setting any new flags of this type. To do
+ this, uncheck "active". Deactivated flags will still show up in the UI if
+ they are ?, +, or -, but they may only be cleared (unset), and cannot be
+ changed to a new value. Once a deactivated flag is cleared, it will
+ completely disappear from a bug/attachment, and cannot be set again.
+ _________________________________________________________________
+
+3.8.5.2.6. Requestable
+
+ New flags are, by default, "requestable", meaning that they offer users the
+ "?" option, as well as "+" and "-". To remove the ? option, uncheck
+ "requestable".
+ _________________________________________________________________
+
+3.8.5.2.7. Specifically Requestable
+
+ By default this box is checked for new flags, meaning that users may make
+ flag requests of specific individuals. Unchecking this box will remove the
+ text box next to a flag; if it is still requestable, then requests may only
+ be made "to the wind." Removing this after specific requests have been made
+ will not remove those requests; that data will stay in the database (though
+ it will no longer appear to the user).
+ _________________________________________________________________
+
+3.8.5.2.8. Multiplicable
+
+ Any flag with "Multiplicable" set (default for new flags is 'on') may be set
+ more than once. After being set once, an unset flag of the same type will
+ appear below it with "addl." (short for "additional") before the name. There
+ is no limit to the number of times a Multiplicable flags may be set on the
+ same bug/attachment.
+ _________________________________________________________________
+
+3.8.5.2.9. CC List
+
+ If you want certain users to be notified every time this flag is set to ?,
+ -, +, or unset, add them here. This is a comma-separated list of email
+ addresses that need not be restricted to Bugzilla usernames.
+ _________________________________________________________________
+
+3.8.5.2.10. Grant Group
+
+ When this field is set to some given group, only users in the group can set
+ the flag to "+" and "-". This field does not affect who can request or
+ cancel the flag. For that, see the "Request Group" field below. If this
+ field is left blank, all users can set or delete this flag. This field is
+ useful for restricting which users can approve or reject requests.
+ _________________________________________________________________
+
+3.8.5.2.11. Request Group
+
+ When this field is set to some given group, only users in the group can
+ request or cancel this flag. Note that this field has no effect if the
+ "grant group" field is empty. You can set the value of this field to a
+ different group, but both fields have to be set to a group for this field to
+ have an effect.
+ _________________________________________________________________
+
+3.8.5.3. Deleting a Flag
+
+ When you are at the "Administer Flag Types" screen, you will be presented
+ with a list of Bug flags and a list of Attachment Flags.
+
+ To delete a flag, click on the "Delete" link next to the flag description.
+
+ Warning
+
+ Once you delete a flag, it is gone from your Bugzilla. All the data for that
+ flag will be deleted. Everywhere that flag was set, it will disappear, and
+ you cannot get that data back. If you want to keep flag data, but don't want
+ anybody to set any new flags or change current flags, unset "active" in the
+ flag Edit form.
+ _________________________________________________________________
+
+3.9. Keywords
+
+ The administrator can define keywords which can be used to tag and
+ categorise bugs. For example, the keyword "regression" is commonly used. A
+ company might have a policy stating all regressions must be fixed by the
+ next release - this keyword can make tracking those bugs much easier.
+
+ Keywords are global, rather than per-product. If the administrator changes a
+ keyword currently applied to any bugs, the keyword cache must be rebuilt
+ using the Section 3.16 script. Currently keywords can not be marked obsolete
+ to prevent future usage.
+
+ Keywords can be created, edited or deleted by clicking the "Keywords" link
+ in the admin page. There are two fields for each keyword - the keyword
+ itself and a brief description. Once created, keywords can be selected and
+ applied to individual bugs in that bug's "Details" section.
+ _________________________________________________________________
+
+3.10. Custom Fields
+
+ The release of Bugzilla 3.0 added the ability to create Custom Fields.
+ Custom Fields are treated like any other field - they can be set in bugs and
+ used for search queries. Administrators should keep in mind that adding too
+ many fields can make the user interface more complicated and harder to use.
+ Custom Fields should be added only when necessary and with careful
+ consideration.
+
+ Tip
+
+ Before adding a Custom Field, make sure that Bugzilla can not already do the
+ desired behavior. Many Bugzilla options are not enabled by default, and many
+ times Administrators find that simply enabling certain options that already
+ exist is sufficient.
+
+ Administrators can manage Custom Fields using the "Custom Fields" link on
+ the Administration page. The Custom Fields administration page displays a
+ list of Custom Fields, if any exist, and a link to "Add a new custom field".
+ _________________________________________________________________
+
+3.10.1. Adding Custom Fields
+
+ To add a new Custom Field, click the "Add a new custom field" link. This
+ page displays several options for the new field, described below.
+
+ The following attributes must be set for each new custom field:
+
+ * Name: The name of the field in the database, used internally. This name
+ MUST begin with "cf_" to prevent confusion with standard fields. If this
+ string is omitted, it will be automatically added to the name entered.
+ * Description: A brief string which is used as the label for this Custom
+ Field. That is the string that users will see, and should be short and
+ explicit.
+ * Type: The type of field to create. There are several types available:
+
+ Large Text Box: A multiple line box for entering free text.
+ Free Text: A single line box for entering free text.
+ Multiple-Selection Box: A list box where multiple options can be selected.
+ After creating this field, it must be edited to add the selection options.
+ See Section 3.11.1 for information about editing legal values.
+ Drop Down: A list box where only one option can be selected. After creating
+ this field, it must be edited to add the selection options. See Section
+ 3.11.1 for information about editing legal values.
+ Date/Time: A date field. This field appears with a calendar widget for
+ choosing the date.
+ * Sortkey: Integer that determines in which order Custom Fields are
+ displayed in the User Interface, especially when viewing a bug. Fields
+ with lower values are displayed first.
+ * Can be set on bug creation: Boolean that determines whether this field
+ can be set on bug creation. If not selected, then a bug must be created
+ before this field can be set. See Section 5.6 for information about
+ filing bugs.
+ * Displayed in bugmail for new bugs: Boolean that determines whether the
+ value set on this field should appear in bugmail when the bug is filed.
+ This attribute has no effect if the field cannot be set on bug creation.
+ * Is obsolete: Boolean that determines whether this field should be
+ displayed at all. Obsolete Custom Fields are hidden.
+ _________________________________________________________________
+
+3.10.2. Editing Custom Fields
+
+ As soon as a Custom Field is created, its name and type cannot be changed.
+ If this field is a drop down menu, its legal values can be set as described
+ in Section 3.11.1. All other attributes can be edited as described above.
+ _________________________________________________________________
+
+3.10.3. Deleting Custom Fields
+
+ Only custom fields which are marked as obsolete, and which never have been
+ used, can be deleted completely (else the integrity of the bug history would
+ be compromised). For custom fields marked as obsolete, a "Delete" link will
+ appear in the "Action" column. If the custom field has been used in the
+ past, the deletion will be rejected. But marking the field as obsolete is
+ sufficient to hide it from the user interface entirely.
+ _________________________________________________________________
+
+3.11. Legal Values
+
+ Since Bugzilla 2.20 RC1, legal values for Operating Systems, platforms, bug
+ priorities and severities can be edited from the User Interface directly.
+ This means that it is no longer required to manually edit localconfig.
+ Starting with Bugzilla 2.23.3, the list of valid resolutions can be
+ customized from the same interface. Since Bugzilla 3.1.1 the list of valid
+ bug statuses can be customized as well.
+ _________________________________________________________________
+
+3.11.1. Viewing/Editing legal values
+
+ Editing legal values requires "admin" privileges. Select "Legal Values" from
+ the Administration page. A list of all fields, both system fields and Custom
+ Fields, for which legal values can be edited appears. Click a field name to
+ edit its legal values.
+
+ There is no limit to how many values a field can have, but each value must
+ be unique to that field. The sortkey is important to display these values in
+ the desired order.
+ _________________________________________________________________
+
+3.11.2. Deleting legal values
+
+ Legal values from Custom Fields can be deleted, but only if the following
+ two conditions are respected:
+
+ 1. The value is not used by default for the field.
+ 2. No bug is currently using this value.
+
+ If any of these conditions is not respected, the value cannot be deleted.
+ The only way to delete these values is to reassign bugs to another value and
+ to set another value as default for the field.
+ _________________________________________________________________
+
+3.12. Bug Status Workflow
+
+ The bug status workflow is no longer hardcoded but can be freely customized
+ from the web interface. Only one bug status cannot be renamed nor deleted,
+ UNCONFIRMED, but the workflow involving it is free. The configuration page
+ displays all existing bug statuses twice, first on the left for bug statuses
+ we come from and on the top for bug statuses we move to. If the checkbox is
+ checked, then the transition between the two bug statuses is legal, else
+ it's forbidden independently of your privileges. The bug status used for the
+ "duplicate_or_move_bug_status" parameter must be part of the workflow as
+ that is the bug status which will be used when duplicating or moving a bug,
+ so it must be available from each bug status.
+
+ When the workflow is set, the "View Current Triggers" link below the table
+ lets you set which transitions require a comment from the user.
+ _________________________________________________________________
+
+3.13. Voting
+
+ Voting allows users to be given a pot of votes which they can allocate to
+ bugs, to indicate that they'd like them fixed. This allows developers to
+ gauge user need for a particular enhancement or bugfix. By allowing bugs
+ with a certain number of votes to automatically move from "UNCONFIRMED" to
+ "CONFIRMED", users of the bug system can help high-priority bugs garner
+ attention so they don't sit for a long time awaiting triage.
+
+ To modify Voting settings:
+
+ 1. Navigate to the "Edit product" screen for the Product you wish to modify
+ 2. Maximum Votes per person: Setting this field to "0" disables voting.
+ 3. Maximum Votes a person can put on a single bug: It should probably be
+ some number lower than the "Maximum votes per person". Don't set this
+ field to "0" if "Maximum votes per person" is non-zero; that doesn't
+ make any sense.
+ 4. Number of votes a bug in this product needs to automatically get out of
+ the UNCONFIRMED state: Setting this field to "0" disables the automatic
+ move of bugs from UNCONFIRMED to CONFIRMED.
+ 5. Once you have adjusted the values to your preference, click "Update".
+ _________________________________________________________________
+
+3.14. Quips
+
+ Quips are small text messages that can be configured to appear next to
+ search results. A Bugzilla installation can have its own specific quips.
+ Whenever a quip needs to be displayed, a random selection is made from the
+ pool of already existing quips.
+
+ Quips are controlled by the enablequips parameter. It has several possible
+ values: on, approved, frozen or off. In order to enable quips approval you
+ need to set this parameter to "approved". In this way, users are free to
+ submit quips for addition but an administrator must explicitly approve them
+ before they are actually used.
+
+ In order to see the user interface for the quips, it is enough to click on a
+ quip when it is displayed together with the search results. Or it can be
+ seen directly in the browser by visiting the quips.cgi URL (prefixed with
+ the usual web location of the Bugzilla installation). Once the quip
+ interface is displayed, it is enough to click the "view and edit the whole
+ quip list" in order to see the administration page. A page with all the
+ quips available in the database will be displayed.
+
+ Next to each tip there is a checkbox, under the "Approved" column. Quips who
+ have this checkbox checked are already approved and will appear next to the
+ search results. The ones that have it unchecked are still preserved in the
+ database but they will not appear on search results pages. User submitted
+ quips have initially the checkbox unchecked.
+
+ Also, there is a delete link next to each quip, which can be used in order
+ to permanently delete a quip.
+ _________________________________________________________________
+
+3.15. Groups and Group Security
+
+ Groups allow for separating bugs into logical divisions. Groups are
+ typically used to isolate bugs that should only be seen by certain people.
+ For example, a company might create a different group for each one of its
+ customers or partners. Group permissions could be set so that each partner
+ or customer would only have access to their own bugs. Or, groups might be
+ used to create variable access controls for different departments within an
+ organization. Another common use of groups is to associate groups with
+ products, creating isolation and access control on a per-product basis.
+
+ Groups and group behaviors are controlled in several places:
+
+ 1. The group configuration page. To view or edit existing groups, or to
+ create new groups, access the "Groups" link from the "Administration"
+ page. This section of the manual deals primarily with the aspect of
+ group controls accessed on this page.
+ 2. Global configuration parameters. Bugzilla has several parameters that
+ control the overall default group behavior and restriction levels. For
+ more information on the parameters that control group behavior globally,
+ see Section 3.1.9.
+ 3. Product association with groups. Most of the functionality of groups and
+ group security is controlled at the product level. Some aspects of group
+ access controls for products are discussed in this section, but for more
+ detail see Section 3.4.4.
+ 4. Group access for users. See Section 3.15.3 for details on how users are
+ assigned group access.
+
+ Group permissions are such that if a bug belongs to a group, only members of
+ that group can see the bug. If a bug is in more than one group, only members
+ of all the groups that the bug is in can see the bug. For information on
+ granting read-only access to certain people and full edit access to others,
+ see Section 3.4.4.
+
+ Note
+
+ By default, bugs can also be seen by the Assignee, the Reporter, and by
+ everyone on the CC List, regardless of whether or not the bug would
+ typically be viewable by them. Visibility to the Reporter and CC List can be
+ overridden (on a per-bug basis) by bringing up the bug, finding the section
+ that starts with "Users in the roles selected below..." and un-checking the
+ box next to either 'Reporter' or 'CC List' (or both).
+ _________________________________________________________________
+
+3.15.1. Creating Groups
+
+ To create a new group, follow the steps below:
+
+ 1. Select the "Administration" link in the page footer, and then select the
+ "Groups" link from the Administration page.
+ 2. A table of all the existing groups is displayed. Below the table is a
+ description of all the fields. To create a new group, select the "Add
+ Group" link under the table of existing groups.
+ 3. There are five fields to fill out. These fields are documented below the
+ form. Choose a name and description for the group. Decide whether this
+ group should be used for bugs (in all likelihood this should be
+ selected). Optionally, choose a regular expression that will
+ automatically add any matching users to the group, and choose an icon
+ that will help identify user comments for the group. The regular
+ expression can be useful, for example, to automatically put all users
+ from the same company into one group (if the group is for a specific
+ customer or partner).
+
+ Note
+
+ If "User RegExp" is filled out, users whose email addresses match the
+ regular expression will automatically be members of the group as long as
+ their email addresses continue to match the regular expression. If their
+ email address changes and no longer matches the regular expression, they
+ will be removed from the group. Versions 2.16 and older of Bugzilla did not
+ automatically remove users who's email addresses no longer matched the
+ RegExp.
+
+ Warning
+
+ If specifying a domain in the regular expression, end the regexp with a "$".
+ Otherwise, when granting access to "@mycompany\.com", access will also be
+ granted to 'badperson@mycompany.com.cracker.net'. Use the syntax,
+ '@mycompany\.com$' for the regular expression.
+ 4. After the new group is created, it can be edited for additional options.
+ The "Edit Group" page allows for specifying other groups that should be
+ included in this group and which groups should be permitted to add and
+ delete users from this group. For more details, see Section 3.15.2.
+ _________________________________________________________________
+
+3.15.2. Editing Groups and Assigning Group Permissions
+
+ To access the "Edit Groups" page, select the "Administration" link in the
+ page footer, and then select the "Groups" link from the Administration page.
+ A table of all the existing groups is displayed. Click on a group name you
+ wish to edit or control permissions for.
+
+ The "Edit Groups" page contains the same five fields present when creating a
+ new group. Below that are two additional sections, "Group Permissions," and
+ "Mass Remove". The "Mass Remove" option simply removes all users from the
+ group who match the regular expression entered. The "Group Permissions"
+ section requires further explanation.
+
+ The "Group Permissions" section on the "Edit Groups" page contains four sets
+ of permissions that control the relationship of this group to other groups.
+ If the 'usevisibilitygroups' parameter is in use (see Section 3.1) two
+ additional sets of permissions are displayed. Each set consists of two
+ select boxes. On the left, a select box with a list of all existing groups.
+ On the right, a select box listing all groups currently selected for this
+ permission setting (this box will be empty for new groups). The way these
+ controls allow groups to relate to one another is called inheritance. Each
+ of the six permissions is described below.
+
+ Groups That Are a Member of This Group
+ Members of any groups selected here will automatically have
+ membership in this group. In other words, members of any selected
+ group will inherit membership in this group.
+
+ Groups That This Group Is a Member Of
+ Members of this group will inherit membership to any group selected
+ here. For example, suppose the group being edited is an Admin group.
+ If there are two products (Product1 and Product2) and each product
+ has its own group (Group1 and Group2), and the Admin group should
+ have access to both products, simply select both Group1 and Group2
+ here.
+
+ Groups That Can Grant Membership in This Group
+ The members of any group selected here will be able add users to this
+ group, even if they themselves are not in this group.
+
+ Groups That This Group Can Grant Membership In
+ Members of this group can add users to any group selected here, even
+ if they themselves are not in the selected groups.
+
+ Groups That Can See This Group
+ Members of any selected group can see the users in this group. This
+ setting is only visible if the 'usevisibilitygroups' parameter is
+ enabled on the Bugzilla Configuration page. See Section 3.1 for
+ information on configuring Bugzilla.
+
+ Groups That This Group Can See
+ Members of this group can see members in any of the selected groups.
+ This setting is only visible if the 'usevisibilitygroups' parameter
+ is enabled on the the Bugzilla Configuration page. See Section 3.1
+ for information on configuring Bugzilla.
+ _________________________________________________________________
+
+3.15.3. Assigning Users to Groups
+
+ A User can become a member of a group in several ways:
+
+ 1. The user can be explicitly placed in the group by editing the user's
+ profile. This can be done by accessing the "Users" page from the
+ "Administration" page. Use the search form to find the user you want to
+ edit group membership for, and click on their email address in the
+ search results to edit their profile. The profile page lists all the
+ groups, and indicates if the user is a member of the group either
+ directly or indirectly. More information on indirect group membership is
+ below. For more details on User administration, see Section 3.2.
+ 2. The group can include another group of which the user is a member. This
+ is indicated by square brackets around the checkbox next to the group
+ name in the user's profile. See Section 3.15.2 for details on group
+ inheritance.
+ 3. The user's email address can match the regular expression that has been
+ specified to automatically grant membership to the group. This is
+ indicated by "*" around the check box by the group name in the user's
+ profile. See Section 3.15.1 for details on the regular expression option
+ when creating groups.
+ _________________________________________________________________
+
+3.15.4. Assigning Group Controls to Products
+
+ The primary functionality of groups is derived from the relationship of
+ groups to products. The concepts around segregating access to bugs with
+ product group controls can be confusing. For details and examples on this
+ topic, see Section 3.4.4.
+ _________________________________________________________________
+
+3.16. Checking and Maintaining Database Integrity
+
+ Over time it is possible for the Bugzilla database to become corrupt or to
+ have anomalies. This could happen through normal usage of Bugzilla, manual
+ database administration outside of the Bugzilla user interface, or from some
+ other unexpected event. Bugzilla includes a "Sanity Check" script that can
+ perform several basic database checks, and repair certain problems or
+ inconsistencies.
+
+ To run the "Sanity Check" script, log in as an Administrator and click the
+ "Sanity Check" link in the admin page. Any problems that are found will be
+ displayed in red letters. If the script is capable of fixing a problem, it
+ will present a link to initiate the fix. If the script can not fix the
+ problem it will require manual database administration or recovery.
+
+ The "Sanity Check" script can also be run from the command line via the perl
+ script sanitycheck.pl. The script can also be run as a cron job. Results
+ will be delivered by email.
+
+ The "Sanity Check" script should be run on a regular basis as a matter of
+ best practice.
+
+ Warning
+
+ The "Sanity Check" script is no substitute for a competent database
+ administrator. It is only designed to check and repair basic database
+ problems.
+ _________________________________________________________________
+
+Chapter 4. Bugzilla Security
+
+ While some of the items in this chapter are related to the operating system
+ Bugzilla is running on or some of the support software required to run
+ Bugzilla, it is all related to protecting your data. This is not intended to
+ be a comprehensive guide to securing Linux, Apache, MySQL, or any other
+ piece of software mentioned. There is no substitute for active
+ administration and monitoring of a machine. The key to good security is
+ actually right in the middle of the word: U R It.
+
+ While programmers in general always strive to write secure code, accidents
+ can and do happen. The best approach to security is to always assume that
+ the program you are working with isn't 100% secure and restrict its access
+ to other parts of your machine as much as possible.
+ _________________________________________________________________
+
+4.1. Operating System
+
+4.1.1. TCP/IP Ports
+
+ The TCP/IP standard defines more than 65,000 ports for sending and receiving
+ traffic. Of those, Bugzilla needs exactly one to operate (different
+ configurations and options may require up to 3). You should audit your
+ server and make sure that you aren't listening on any ports you don't need
+ to be. It's also highly recommended that the server Bugzilla resides on,
+ along with any other machines you administer, be placed behind some kind of
+ firewall.
+ _________________________________________________________________
+
+4.1.2. System User Accounts
+
+ Many daemons, such as Apache's httpd or MySQL's mysqld, run as either "root"
+ or "nobody". This is even worse on Windows machines where the majority of
+ services run as "SYSTEM". While running as "root" or "SYSTEM" introduces
+ obvious security concerns, the problems introduced by running everything as
+ "nobody" may not be so obvious. Basically, if you run every daemon as
+ "nobody" and one of them gets compromised it can compromise every other
+ daemon running as "nobody" on your machine. For this reason, it is
+ recommended that you create a user account for each daemon.
+
+ Note
+
+ You will need to set the webservergroup option in localconfig to the group
+ your web server runs as. This will allow ./checksetup.pl to set file
+ permissions on Unix systems so that nothing is world-writable.
+ _________________________________________________________________
+
+4.1.3. The chroot Jail
+
+ If your system supports it, you may wish to consider running Bugzilla inside
+ of a chroot jail. This option provides unprecedented security by restricting
+ anything running inside the jail from accessing any information outside of
+ it. If you wish to use this option, please consult the documentation that
+ came with your system.
+ _________________________________________________________________
+
+4.2. Web server
+
+4.2.1. Disabling Remote Access to Bugzilla Configuration Files
+
+ There are many files that are placed in the Bugzilla directory area that
+ should not be accessible from the web server. Because of the way Bugzilla is
+ currently layed out, the list of what should and should not be accessible is
+ rather complicated. A quick way is to run testserver.pl to check if your web
+ server serves Bugzilla files as expected. If not, you may want to follow the
+ few steps below.
+
+ Tip
+
+ Bugzilla ships with the ability to create .htaccess files that enforce these
+ rules. Instructions for enabling these directives in Apache can be found in
+ Section 2.2.4.1
+
+ * In the main Bugzilla directory, you should:
+ + Block: *.pl, *localconfig*
+ * In data:
+ + Block everything
+ * In data/webdot:
+ + If you use a remote webdot server:
+ o Block everything
+ o But allow *.dot only for the remote webdot server
+ + Otherwise, if you use a local GraphViz:
+ o Block everything
+ o But allow: *.png, *.gif, *.jpg, *.map
+ + And if you don't use any dot:
+ o Block everything
+ * In Bugzilla:
+ + Block everything
+ * In template:
+ + Block everything
+
+ Be sure to test that data that should not be accessed remotely is properly
+ blocked. Of particular interest is the localconfig file which contains your
+ database password. Also, be aware that many editors create temporary and
+ backup files in the working directory and that those should also not be
+ accessible. For more information, see bug 186383 or Bugtraq ID 6501. To
+ test, simply run testserver.pl, as said above.
+
+ Tip
+
+ Be sure to check Section 2.2.4 for instructions specific to the web server
+ you use.
+ _________________________________________________________________
+
+4.3. Bugzilla
+
+4.3.1. Prevent users injecting malicious Javascript
+
+ If you installed Bugzilla version 2.22 or later from scratch, then the utf8
+ parameter is switched on by default. This makes Bugzilla explicitly set the
+ character encoding, following a CERT advisory recommending exactly this. The
+ following therefore does not apply to you; just keep utf8 turned on.
+
+ If you've upgraded from an older version, then it may be possible for a
+ Bugzilla user to take advantage of character set encoding ambiguities to
+ inject HTML into Bugzilla comments. This could include malicious scripts.
+ This is because due to internationalization concerns, we are unable to turn
+ the utf8 parameter on by default for upgraded installations. Turning it on
+ manually will prevent this problem.
+ _________________________________________________________________
+
+Chapter 5. Using Bugzilla
+
+5.1. Introduction
+
+ This section contains information for end-users of Bugzilla. There is a
+ Bugzilla test installation, called Landfill, which you are welcome to play
+ with (if it's up). However, not all of the Bugzilla installations there will
+ necessarily have all Bugzilla features enabled, and different installations
+ run different versions, so some things may not quite work as this document
+ describes.
+
+ Frequently Asked Questions (FAQ) are available and answered on
+ wiki.mozilla.org. They may cover some questions you have which are left
+ unanswered.
+ _________________________________________________________________
+
+5.2. Create a Bugzilla Account
+
+ If you want to use Bugzilla, first you need to create an account. Consult
+ with the administrator responsible for your installation of Bugzilla for the
+ URL you should use to access it. If you're test-driving Bugzilla, use this
+ URL: http://landfill.bugzilla.org/bugzilla-4.0-branch/.
+
+ 1. On the home page index.cgi, click the "Open a new Bugzilla account"
+ link, or the "New Account" link available in the footer of pages. Now
+ enter your email address, then click the "Send" button.
+
+ Note
+
+ If none of these links is available, this means that the administrator of
+ the installation has disabled self-registration. This means that only an
+ administrator can create accounts for other users. One reason could be that
+ this installation is private.
+
+ Note
+
+ Also, if only some users are allowed to create an account on the
+ installation, you may see these links but your registration may fail if your
+ email address doesn't match the ones accepted by the installation. This is
+ another way to restrict who can access and edit bugs in this installation.
+ 2. Within moments, and if your registration is accepted, you should receive
+ an email to the address you provided, which contains your login name
+ (generally the same as the email address), and two URLs with a token (a
+ random string generated by the installation) to confirm, respectively
+ cancel, your registration. This is a way to prevent users from abusing
+ the generation of user accounts, for instance by entering inexistent
+ email addresses, or email addresses which do not belong to them.
+ 3. By default, you have 3 days to confirm your registration. Past this
+ timeframe, the token is invalidated and the registration is
+ automatically canceled. You can also cancel this registration sooner by
+ using the appropriate URL in the email you got.
+ 4. If you confirm your registration, Bugzilla will ask you your real name
+ (optional, but recommended) and your password, which must be between 3
+ and 16 characters long.
+ 5. Now all you need to do is to click the "Log In" link in the footer at
+ the bottom of the page in your browser, enter your email address and
+ password you just chose into the login form, and click the "Log in"
+ button.
+
+ You are now logged in. Bugzilla uses cookies to remember you are logged in
+ so, unless you have cookies disabled or your IP address changes, you should
+ not have to log in again during your session.
+ _________________________________________________________________
+
+5.3. Anatomy of a Bug
+
+ The core of Bugzilla is the screen which displays a particular bug. It's a
+ good place to explain some Bugzilla concepts. Bug 1 on Landfill is a good
+ example. Note that the labels for most fields are hyperlinks; clicking them
+ will take you to context-sensitive help on that particular field. Fields
+ marked * may not be present on every installation of Bugzilla.
+
+ 1. Product and Component: Bugs are divided up by Product and Component,
+ with a Product having one or more Components in it. For example,
+ bugzilla.mozilla.org's "Bugzilla" Product is composed of several
+ Components:
+
+ Administration: Administration of a Bugzilla installation.
+ Bugzilla-General: Anything that doesn't fit in the other components, or
+ spans multiple components.
+ Creating/Changing Bugs: Creating, changing, and viewing bugs.
+ Documentation: The Bugzilla documentation, including The Bugzilla Guide.
+ Email: Anything to do with email sent by Bugzilla.
+ Installation: The installation process of Bugzilla.
+ Query/Buglist: Anything to do with searching for bugs and viewing the
+ buglists.
+ Reporting/Charting: Getting reports from Bugzilla.
+ User Accounts: Anything about managing a user account from the user's
+ perspective. Saved queries, creating accounts, changing passwords, logging
+ in, etc.
+ User Interface: General issues having to do with the user interface
+ cosmetics (not functionality) including cosmetic issues, HTML templates,
+ etc.
+ 2. Status and Resolution: These define exactly what state the bug is in -
+ from not even being confirmed as a bug, through to being fixed and the
+ fix confirmed by Quality Assurance. The different possible values for
+ Status and Resolution on your installation should be documented in the
+ context-sensitive help for those items.
+ 3. Assigned To: The person responsible for fixing the bug.
+ 4. *QA Contact: The person responsible for quality assurance on this bug.
+ 5. *URL: A URL associated with the bug, if any.
+ 6. Summary: A one-sentence summary of the problem.
+ 7. *Status Whiteboard: (a.k.a. Whiteboard) A free-form text area for adding
+ short notes and tags to a bug.
+ 8. *Keywords: The administrator can define keywords which you can use to
+ tag and categorise bugs - e.g. The Mozilla Project has keywords like
+ crash and regression.
+ 9. Platform and OS: These indicate the computing environment where the bug
+ was found.
+ 10. Version: The "Version" field is usually used for versions of a product
+ which have been released, and is set to indicate which versions of a
+ Component have the particular problem the bug report is about.
+ 11. Priority: The bug assignee uses this field to prioritize his or her
+ bugs. It's a good idea not to change this on other people's bugs.
+ 12. Severity: This indicates how severe the problem is - from blocker
+ ("application unusable") to trivial ("minor cosmetic issue"). You can
+ also use this field to indicate whether a bug is an enhancement request.
+ 13. *Target: (a.k.a. Target Milestone) A future version by which the bug is
+ to be fixed. e.g. The Bugzilla Project's milestones for future Bugzilla
+ versions are 2.18, 2.20, 3.0, etc. Milestones are not restricted to
+ numbers, thought - you can use any text strings, such as dates.
+ 14. Reporter: The person who filed the bug.
+ 15. CC list: A list of people who get mail when the bug changes.
+ 16. *Time Tracking: This form can be used for time tracking. To use this
+ feature, you have to be blessed group membership specified by the
+ "timetrackinggroup" parameter.
+
+ Orig. Est.: This field shows the original estimated time.
+ Current Est.: This field shows the current estimated time. This number is
+ calculated from "Hours Worked" and "Hours Left".
+ Hours Worked: This field shows the number of hours worked.
+ Hours Left: This field shows the "Current Est." - "Hours Worked". This value
+ + "Hours Worked" will become the new Current Est.
+ %Complete: This field shows what percentage of the task is complete.
+ Gain: This field shows the number of hours that the bug is ahead of the
+ "Orig. Est.".
+ Deadline: This field shows the deadline for this bug.
+ 17. Attachments: You can attach files (e.g. testcases or patches) to bugs.
+ If there are any attachments, they are listed in this section.
+ Attachments are normally stored in the Bugzilla database, unless they
+ are marked as Big Files, which are stored directly on disk.
+ 18. *Dependencies: If this bug cannot be fixed unless other bugs are fixed
+ (depends on), or this bug stops other bugs being fixed (blocks), their
+ numbers are recorded here.
+ 19. *Votes: Whether this bug has any votes.
+ 20. Additional Comments: You can add your two cents to the bug discussion
+ here, if you have something worthwhile to say.
+ _________________________________________________________________
+
+5.4. Life Cycle of a Bug
+
+ The life cycle of a bug, also known as workflow, is customizable to match
+ the needs of your organization, see Section 3.12. Figure 5-1 contains a
+ graphical representation of the default workflow using the default bug
+ statuses. If you wish to customize this image for your site, the diagram
+ file is available in Dia's native XML format.
+
+ Figure 5-1. Lifecycle of a Bugzilla Bug
+
+ [bzLifecycle.png]
+ _________________________________________________________________
+
+5.5. Searching for Bugs
+
+ The Bugzilla Search page is the interface where you can find any bug report,
+ comment, or patch currently in the Bugzilla system. You can play with it
+ here: http://landfill.bugzilla.org/bugzilla-4.0-branch/query.cgi.
+
+ The Search page has controls for selecting different possible values for all
+ of the fields in a bug, as described above. For some fields, multiple values
+ can be selected. In those cases, Bugzilla returns bugs where the content of
+ the field matches any one of the selected values. If none is selected, then
+ the field can take any value.
+
+ After a search is run, you can save it as a Saved Search, which will appear
+ in the page footer. If you are in the group defined by the "querysharegroup"
+ parameter, you may share your queries with other users, see Saved Searches
+ for more details.
+ _________________________________________________________________
+
+5.5.1. Boolean Charts
+
+ Highly advanced querying is done using Boolean Charts.
+
+ The boolean charts further restrict the set of results returned by a query.
+ It is possible to search for bugs based on elaborate combinations of
+ criteria.
+
+ The simplest boolean searches have only one term. These searches permit the
+ selected left field to be compared using a selectable operator to a
+ specified value. Using the "And," "Or," and "Add Another Boolean Chart"
+ buttons, additional terms can be included in the query, further altering the
+ list of bugs returned by the query.
+
+ There are three fields in each row of a boolean search.
+
+ * Field: the items being searched
+ * Operator: the comparison operator
+ * Value: the value to which the field is being compared
+ _________________________________________________________________
+
+5.5.1.1. Pronoun Substitution
+
+ Sometimes, a query needs to compare a user-related field (such as
+ ReportedBy) with a role-specific user (such as the user running the query or
+ the user to whom each bug is assigned). When the operator is either "equals"
+ or "notequals", the value can be "%reporter%", "%assignee%", "%qacontact%",
+ or "%user%". The user pronoun refers to the user who is executing the query
+ or, in the case of whining reports, the user who will be the recipient of
+ the report. The reporter, assignee, and qacontact pronouns refer to the
+ corresponding fields in the bug.
+
+ Boolean charts also let you type a group name in any user-related field if
+ the operator is either "equals", "notequals" or "anyexact". This will let
+ you query for any member belonging (or not) to the specified group. The
+ group name must be entered following the "%group.foo%" syntax, where "foo"
+ is the group name. So if you are looking for bugs reported by any user being
+ in the "editbugs" group, then you can type "%group.editbugs%".
+ _________________________________________________________________
+
+5.5.1.2. Negation
+
+ At first glance, negation seems redundant. Rather than searching for
+
+ NOT("summary" "contains the string" "foo"),
+
+ one could search for
+
+ ("summary" "does not contain the string" "foo").
+
+ However, the search
+
+ ("CC" "does not contain the string" "@mozilla.org")
+
+ would find every bug where anyone on the CC list did not contain
+ "@mozilla.org" while
+
+ NOT("CC" "contains the string" "@mozilla.org")
+
+ would find every bug where there was nobody on the CC list who did contain
+ the string. Similarly, the use of negation also permits complex expressions
+ to be built using terms OR'd together and then negated. Negation permits
+ queries such as
+
+ NOT(("product" "equals" "update") OR ("component" "equals"
+ "Documentation"))
+
+ to find bugs that are neither in the update product or in the documentation
+ component or
+
+ NOT(("commenter" "equals" "%assignee%") OR ("component" "equals"
+ "Documentation"))
+
+ to find non-documentation bugs on which the assignee has never commented.
+ _________________________________________________________________
+
+5.5.1.3. Multiple Charts
+
+ The terms within a single row of a boolean chart are all constraints on a
+ single piece of data. If you are looking for a bug that has two different
+ people cc'd on it, then you need to use two boolean charts. A search for
+
+ ("cc" "contains the string" "foo@") AND ("cc" "contains the string"
+ "@mozilla.org")
+
+ would return only bugs with "foo@mozilla.org" on the cc list. If you wanted
+ bugs where there is someone on the cc list containing "foo@" and someone
+ else containing "@mozilla.org", then you would need two boolean charts.
+
+ First chart: ("cc" "contains the string" "foo@")
+
+ Second chart: ("cc" "contains the string" "@mozilla.org")
+
+ The bugs listed will be only the bugs where ALL the charts are true.
+ _________________________________________________________________
+
+5.5.2. Quicksearch
+
+ Quicksearch is a single-text-box query tool which uses metacharacters to
+ indicate what is to be searched. For example, typing "foo|bar" into
+ Quicksearch would search for "foo" or "bar" in the summary and status
+ whiteboard of a bug; adding ":BazProduct" would search only in that product.
+ You can use it to find a bug by its number or its alias, too.
+
+ You'll find the Quicksearch box in Bugzilla's footer area. On Bugzilla's
+ front page, there is an additional Help link which details how to use it.
+ _________________________________________________________________
+
+5.5.3. Case Sensitivity in Searches
+
+ Bugzilla queries are case-insensitive and accent-insensitive, when used with
+ either MySQL or Oracle databases. When using Bugzilla with PostgreSQL,
+ however, some queries are case-sensitive. This is due to the way PostgreSQL
+ handles case and accent sensitivity.
+ _________________________________________________________________
+
+5.5.4. Bug Lists
+
+ If you run a search, a list of matching bugs will be returned.
+
+ The format of the list is configurable. For example, it can be sorted by
+ clicking the column headings. Other useful features can be accessed using
+ the links at the bottom of the list:
+
+ Long Format: this gives you a large page with a non-editable summary of the
+ fields of each bug.
+ XML: get the buglist in the XML format.
+ CSV: get the buglist as comma-separated values, for import into e.g. a
+ spreadsheet.
+ Feed: get the buglist as an Atom feed. Copy this link into your favorite
+ feed reader. If you are using Firefox, you can also save the list as a live
+ bookmark by clicking the live bookmark icon in the status bar. To limit the
+ number of bugs in the feed, add a limit=n parameter to the URL.
+ iCalendar: Get the buglist as an iCalendar file. Each bug is represented as
+ a to-do item in the imported calendar.
+ Change Columns: change the bug attributes which appear in the list.
+ Change several bugs at once: If your account is sufficiently empowered, and
+ more than one bug appear in the bug list, this link is displayed which lets
+ you make the same change to all the bugs in the list - for example, changing
+ their assignee.
+ Send mail to bug assignees: If more than one bug appear in the bug list and
+ there are at least two distinct bug assignees, this links is displayed which
+ lets you easily send a mail to the assignees of all bugs on the list.
+ Edit Search: If you didn't get exactly the results you were looking for, you
+ can return to the Query page through this link and make small revisions to
+ the query you just made so you get more accurate results.
+ Remember Search As: You can give a search a name and remember it; a link
+ will appear in your page footer giving you quick access to run it again
+ later.
+
+ If you would like to access the bug list from another program it is often
+ useful to have the list returned in something other than HTML. By adding the
+ ctype=type parameter into the bug list URL you can specify several alternate
+ formats. Besides the types described above, the following formats are also
+ supported: ECMAScript, also known as JavaScript (ctype=js), and Resource
+ Description Framework RDF/XML (ctype=rdf).
+ _________________________________________________________________
+
+5.5.5. Adding/removing tags to/from bugs
+
+ You can add and remove tags from individual bugs, which let you find and
+ manage them more easily. Creating a new tag automatically generates a saved
+ search - whose name is the name of the tag - which lists bugs with this tag.
+ This saved search will be displayed in the footer of pages by default, as
+ all other saved searches. The main difference between tags and normal saved
+ searches is that saved searches, as described in the previous section, are
+ stored in the form of a list of matching criteria, while the saved search
+ generated by tags is a list of bug numbers. Consequently, you can easily
+ edit this list by either adding or removing tags from bugs. To enable this
+ feature, you have to turn on the "Enable tags for bugs" user preference, see
+ Section 5.10. This feature is disabled by default.
+
+ This feature is useful when you want to keep track of several bugs, but for
+ different reasons. Instead of adding yourself to the CC list of all these
+ bugs and mixing all these reasons, you can now store these bugs in separate
+ lists, e.g. "Keep in mind", "Interesting bugs", or "Triage". One big
+ advantage of this way to manage bugs is that you can easily add or remove
+ bugs one by one, which is not possible to do with saved searches without
+ having to edit the search criteria again.
+ _________________________________________________________________
+
+5.6. Filing Bugs
+
+5.6.1. Reporting a New Bug
+
+ Years of bug writing experience has been distilled for your reading pleasure
+ into the Bug Writing Guidelines. While some of the advice is
+ Mozilla-specific, the basic principles of reporting Reproducible, Specific
+ bugs, isolating the Product you are using, the Version of the Product, the
+ Component which failed, the Hardware Platform, and Operating System you were
+ using at the time of the failure go a long way toward ensuring accurate,
+ responsible fixes for the bug that bit you.
+
+ The procedure for filing a bug is as follows:
+
+ 1. Click the "New" link available in the footer of pages, or the "Enter a
+ new bug report" link displayed on the home page of the Bugzilla
+ installation.
+
+ Note
+
+ If you want to file a test bug to see how Bugzilla works, you can do it on
+ one of our test installations on Landfill.
+ 2. You first have to select the product in which you found a bug.
+ 3. You now see a form where you can specify the component (part of the
+ product which is affected by the bug you discovered; if you have no
+ idea, just select "General" if such a component exists), the version of
+ the program you were using, the Operating System and platform your
+ program is running on and the severity of the bug (if the bug you found
+ crashes the program, it's probably a major or a critical bug; if it's a
+ typo somewhere, that's something pretty minor; if it's something you
+ would like to see implemented, then that's an enhancement).
+ 4. You now have to give a short but descriptive summary of the bug you
+ found. "My program is crashing all the time" is a very poor summary and
+ doesn't help developers at all. Try something more meaningful or your
+ bug will probably be ignored due to a lack of precision. The next step
+ is to give a very detailed list of steps to reproduce the problem you
+ encountered. Try to limit these steps to a minimum set required to
+ reproduce the problem. This will make the life of developers easier, and
+ the probability that they consider your bug in a reasonable timeframe
+ will be much higher.
+
+ Note
+
+ Try to make sure that everything in the summary is also in the first
+ comment. Summaries are often updated and this will ensure your original
+ information is easily accessible.
+ 5. As you file the bug, you can also attach a document (testcase, patch, or
+ screenshot of the problem).
+ 6. Depending on the Bugzilla installation you are using and the product in
+ which you are filing the bug, you can also request developers to
+ consider your bug in different ways (such as requesting review for the
+ patch you just attached, requesting your bug to block the next release
+ of the product, and many other product specific requests).
+ 7. Now is a good time to read your bug report again. Remove all
+ misspellings, otherwise your bug may not be found by developers running
+ queries for some specific words, and so your bug would not get any
+ attention. Also make sure you didn't forget any important information
+ developers should know in order to reproduce the problem, and make sure
+ your description of the problem is explicit and clear enough. When you
+ think your bug report is ready to go, the last step is to click the
+ "Commit" button to add your report into the database.
+
+ You do not need to put "any" or similar strings in the URL field. If there
+ is no specific URL associated with the bug, leave this field blank.
+
+ If you feel a bug you filed was incorrectly marked as a DUPLICATE of
+ another, please question it in your bug, not the bug it was duped to. Feel
+ free to CC the person who duped it if they are not already CCed.
+ _________________________________________________________________
+
+5.6.2. Clone an Existing Bug
+
+ Starting with version 2.20, Bugzilla has a feature that allows you to clone
+ an existing bug. The newly created bug will inherit most settings from the
+ old bug. This allows you to track more easily similar concerns in a new bug.
+ To use this, go to the bug that you want to clone, then click the "Clone
+ This Bug" link on the bug page. This will take you to the "Enter Bug" page
+ that is filled with the values that the old bug has. You can change those
+ values and/or texts if needed.
+ _________________________________________________________________
+
+5.7. Attachments
+
+ You should use attachments, rather than comments, for large chunks of ASCII
+ data, such as trace, debugging output files, or log files. That way, it
+ doesn't bloat the bug for everyone who wants to read it, and cause people to
+ receive fat, useless mails.
+
+ You should make sure to trim screenshots. There's no need to show the whole
+ screen if you are pointing out a single-pixel problem.
+
+ Bugzilla stores and uses a Content-Type for each attachment (e.g.
+ text/html). To download an attachment as a different Content-Type (e.g.
+ application/xhtml+xml), you can override this using a 'content_type'
+ parameter on the URL, e.g. &content_type=text/plain.
+
+ If you have a really large attachment, something that does not need to be
+ recorded forever (as most attachments are), or something that is too big for
+ your database, you can mark your attachment as a "Big File", assuming the
+ administrator of the installation has enabled this feature. Big Files are
+ stored directly on disk instead of in the database. The maximum size of a
+ "Big File" is normally larger than the maximum size of a regular attachment.
+ Independently of the storage system used, an administrator can delete these
+ attachments at any time. Nevertheless, if these files are stored in the
+ database, the "allow_attachment_deletion" parameter (which is turned off by
+ default) must be enabled in order to delete them.
+
+ Also, if the administrator turned on the "allow_attach_url" parameter, you
+ can enter the URL pointing to the attachment instead of uploading the
+ attachment itself. For example, this is useful if you want to point to an
+ external application, a website or a very large file. Note that there is no
+ guarantee that the source file will always be available, nor that its
+ content will remain unchanged.
+ _________________________________________________________________
+
+5.7.1. Patch Viewer
+
+ Viewing and reviewing patches in Bugzilla is often difficult due to lack of
+ context, improper format and the inherent readability issues that raw
+ patches present. Patch Viewer is an enhancement to Bugzilla designed to fix
+ that by offering increased context, linking to sections, and integrating
+ with Bonsai, LXR and CVS.
+
+ Patch viewer allows you to:
+
+ View patches in color, with side-by-side view rather than trying to
+ interpret the contents of the patch.
+ See the difference between two patches.
+ Get more context in a patch.
+ Collapse and expand sections of a patch for easy reading.
+ Link to a particular section of a patch for discussion or review
+ Go to Bonsai or LXR to see more context, blame, and cross-references for the
+ part of the patch you are looking at
+ Create a rawtext unified format diff out of any patch, no matter what format
+ it came from
+ _________________________________________________________________
+
+5.7.1.1. Viewing Patches in Patch Viewer
+
+ The main way to view a patch in patch viewer is to click on the "Diff" link
+ next to a patch in the Attachments list on a bug. You may also do this
+ within the edit window by clicking the "View Attachment As Diff" button in
+ the Edit Attachment screen.
+ _________________________________________________________________
+
+5.7.1.2. Seeing the Difference Between Two Patches
+
+ To see the difference between two patches, you must first view the newer
+ patch in Patch Viewer. Then select the older patch from the dropdown at the
+ top of the page ("Differences between [dropdown] and this patch") and click
+ the "Diff" button. This will show you what is new or changed in the newer
+ patch.
+ _________________________________________________________________
+
+5.7.1.3. Getting More Context in a Patch
+
+ To get more context in a patch, you put a number in the textbox at the top
+ of Patch Viewer ("Patch / File / [textbox]") and hit enter. This will give
+ you that many lines of context before and after each change. Alternatively,
+ you can click on the "File" link there and it will show each change in the
+ full context of the file. This feature only works against files that were
+ diffed using "cvs diff".
+ _________________________________________________________________
+
+5.7.1.4. Collapsing and Expanding Sections of a Patch
+
+ To view only a certain set of files in a patch (for example, if a patch is
+ absolutely huge and you want to only review part of it at a time), you can
+ click the "(+)" and "(-)" links next to each file (to expand it or collapse
+ it). If you want to collapse all files or expand all files, you can click
+ the "Collapse All" and "Expand All" links at the top of the page.
+ _________________________________________________________________
+
+5.7.1.5. Linking to a Section of a Patch
+
+ To link to a section of a patch (for example, if you want to be able to give
+ someone a URL to show them which part you are talking about) you simply
+ click the "Link Here" link on the section header. The resulting URL can be
+ copied and used in discussion.
+ _________________________________________________________________
+
+5.7.1.6. Going to Bonsai and LXR
+
+ To go to Bonsai to get blame for the lines you are interested in, you can
+ click the "Lines XX-YY" link on the section header you are interested in.
+ This works even if the patch is against an old version of the file, since
+ Bonsai stores all versions of the file.
+
+ To go to LXR, you click on the filename on the file header (unfortunately,
+ since LXR only does the most recent version, line numbers are likely to
+ rot).
+ _________________________________________________________________
+
+5.7.1.7. Creating a Unified Diff
+
+ If the patch is not in a format that you like, you can turn it into a
+ unified diff format by clicking the "Raw Unified" link at the top of the
+ page.
+ _________________________________________________________________
+
+5.8. Hints and Tips
+
+ This section distills some Bugzilla tips and best practices that have been
+ developed.
+ _________________________________________________________________
+
+5.8.1. Autolinkification
+
+ Bugzilla comments are plain text - so typing <U> will produce less-than, U,
+ greater-than rather than underlined text. However, Bugzilla will
+ automatically make hyperlinks out of certain sorts of text in comments. For
+ example, the text "http://www.bugzilla.org" will be turned into a link:
+ http://www.bugzilla.org. Other strings which get linkified in the obvious
+ manner are:
+
+ bug 12345
+ comment 7
+ bug 23456, comment 53
+ attachment 4321
+ mailto:george@example.com
+ george@example.com
+ ftp://ftp.mozilla.org
+ Most other sorts of URL
+
+ A corollary here is that if you type a bug number in a comment, you should
+ put the word "bug" before it, so it gets autolinkified for the convenience
+ of others.
+ _________________________________________________________________
+
+5.8.2. Comments
+
+ If you are changing the fields on a bug, only comment if either you have
+ something pertinent to say, or Bugzilla requires it. Otherwise, you may spam
+ people unnecessarily with bug mail. To take an example: a user can set up
+ their account to filter out messages where someone just adds themselves to
+ the CC field of a bug (which happens a lot.) If you come along, add yourself
+ to the CC field, and add a comment saying "Adding self to CC", then that
+ person gets a pointless piece of mail they would otherwise have avoided.
+
+ Don't use sigs in comments. Signing your name ("Bill") is acceptable, if you
+ do it out of habit, but full mail/news-style four line ASCII art creations
+ are not.
+ _________________________________________________________________
+
+5.8.3. Server-Side Comment Wrapping
+
+ Bugzilla stores comments unwrapped and wraps them at display time. This
+ ensures proper wrapping in all browsers. Lines beginning with the ">"
+ character are assumed to be quotes, and are not wrapped.
+ _________________________________________________________________
+
+5.8.4. Dependency Tree
+
+ On the "Dependency tree" page linked from each bug page, you can see the
+ dependency relationship from the bug as a tree structure.
+
+ You can change how much depth to show, and you can hide resolved bugs from
+ this page. You can also collaps/expand dependencies for each bug on the tree
+ view, using the [-]/[+] buttons that appear before its summary. This option
+ is not available for terminal bugs in the tree (that don't have further
+ dependencies).
+ _________________________________________________________________
+
+5.9. Time Tracking Information
+
+ Users who belong to the group specified by the "timetrackinggroup" parameter
+ have access to time-related fields. Developers can see deadlines and
+ estimated times to fix bugs, and can provide time spent on these bugs.
+
+ At any time, a summary of the time spent by developers on bugs is accessible
+ either from bug lists when clicking the "Time Summary" button or from
+ individual bugs when clicking the "Summarize time" link in the time tracking
+ table. The summarize_time.cgi page lets you view this information either per
+ developer or per bug, and can be split on a month basis to have greater
+ details on how time is spent by developers.
+
+ As soon as a bug is marked as RESOLVED, the remaining time expected to fix
+ the bug is set to zero. This lets QA people set it again for their own
+ usage, and it will be set to zero again when the bug will be marked as
+ CLOSED.
+ _________________________________________________________________
+
+5.10. User Preferences
+
+ Once logged in, you can customize various aspects of Bugzilla via the
+ "Preferences" link in the page footer. The preferences are split into five
+ tabs:
+ _________________________________________________________________
+
+5.10.1. General Preferences
+
+ This tab allows you to change several default settings of Bugzilla.
+
+ * Bugzilla's general appearance (skin) - select which skin to use.
+ Bugzilla supports adding custom skins.
+ * Quote the associated comment when you click on its reply link - sets the
+ behavior of the comment "Reply" link. Options include quoting the full
+ comment, just reference the comment number, or turn the link off.
+ * Language used in email - select which language email will be sent in,
+ from the list of available languages.
+ * After changing a bug - This controls what page is displayed after
+ changes to a bug are submitted. The options include to show the bug just
+ modified, to show the next bug in your list, or to do nothing.
+ * Enable tags for bugs - turn bug tagging on or off.
+ * Zoom textareas large when in use (requires JavaScript) - enable or
+ disable the automatic expanding of text areas when text is being entered
+ into them.
+ * Field separator character for CSV files - Select between a comma and
+ semi-colon for exported CSV bug lists.
+ * Automatically add me to the CC list of bugs I change - set default
+ behavior of CC list. Options include "Always", "Never", and "Only if I
+ have no role on them".
+ * When viewing a bug, show comments in this order - controls the order of
+ comments. Options include "Oldest to Newest", "Newest to Oldest" and
+ "Newest to Oldest, but keep the bug description at the top".
+ * Show a quip at the top of each bug list - controls whether a quip will
+ be shown on the Bug list page.
+ _________________________________________________________________
+
+5.10.2. Email Preferences
+
+ This tab allows you to enable or disable email notification on specific
+ events.
+
+ In general, users have almost complete control over how much (or how little)
+ email Bugzilla sends them. If you want to receive the maximum amount of
+ email possible, click the "Enable All Mail" button. If you don't want to
+ receive any email from Bugzilla at all, click the "Disable All Mail" button.
+
+ Note
+
+ A Bugzilla administrator can stop a user from receiving bugmail by clicking
+ the "Bugmail Disabled" checkbox when editing the user account. This is a
+ drastic step best taken only for disabled accounts, as it overrides the
+ user's individual mail preferences.
+
+ There are two global options -- "Email me when someone asks me to set a
+ flag" and "Email me when someone sets a flag I asked for". These define how
+ you want to receive bugmail with regards to flags. Their use is quite
+ straightforward; enable the checkboxes if you want Bugzilla to send you mail
+ under either of the above conditions.
+
+ If you'd like to set your bugmail to something besides 'Completely ON' and
+ 'Completely OFF', the "Field/recipient specific options" table allows you to
+ do just that. The rows of the table define events that can happen to a bug
+ -- things like attachments being added, new comments being made, the
+ priority changing, etc. The columns in the table define your relationship
+ with the bug:
+
+ * Reporter - Where you are the person who initially reported the bug. Your
+ name/account appears in the "Reporter:" field.
+ * Assignee - Where you are the person who has been designated as the one
+ responsible for the bug. Your name/account appears in the "Assigned To:"
+ field of the bug.
+ * QA Contact - You are one of the designated QA Contacts for the bug. Your
+ account appears in the "QA Contact:" text-box of the bug.
+ * CC - You are on the list CC List for the bug. Your account appears in
+ the "CC:" text box of the bug.
+ * Voter - You have placed one or more votes for the bug. Your account
+ appears only if someone clicks on the "Show votes for this bug" link on
+ the bug.
+
+ Note
+
+ Some columns may not be visible for your installation, depending on your
+ site's configuration.
+
+ To fine-tune your bugmail, decide the events for which you want to receive
+ bugmail; then decide if you want to receive it all the time (enable the
+ checkbox for every column), or only when you have a certain relationship
+ with a bug (enable the checkbox only for those columns). For example: if you
+ didn't want to receive mail when someone added themselves to the CC list,
+ you could uncheck all the boxes in the "CC Field Changes" line. As another
+ example, if you never wanted to receive email on bugs you reported unless
+ the bug was resolved, you would un-check all boxes in the "Reporter" column
+ except for the one on the "The bug is resolved or verified" row.
+
+ Note
+
+ Bugzilla adds the "X-Bugzilla-Reason" header to all bugmail it sends,
+ describing the recipient's relationship (AssignedTo, Reporter, QAContact,
+ CC, or Voter) to the bug. This header can be used to do further client-side
+ filtering.
+
+ Bugzilla has a feature called "Users Watching". When you enter one or more
+ comma-delineated user accounts (usually email addresses) into the text entry
+ box, you will receive a copy of all the bugmail those users are sent
+ (security settings permitting). This powerful functionality enables seamless
+ transitions as developers change projects or users go on holiday.
+
+ Note
+
+ The ability to watch other users may not be available in all Bugzilla
+ installations. If you don't see this feature, and feel that you need it,
+ speak to your administrator.
+
+ Each user listed in the "Users watching you" field has you listed in their
+ "Users to watch" list and can get bugmail according to your relationship to
+ the bug and their "Field/recipient specific options" setting.
+ _________________________________________________________________
+
+5.10.3. Saved Searches
+
+ On this tab you can view and run any Saved Searches that you have created,
+ and also any Saved Searches that other members of the group defined in the
+ "querysharegroup" parameter have shared. Saved Searches can be added to the
+ page footer from this screen. If somebody is sharing a Search with a group
+ she or he is allowed to assign users to, the sharer may opt to have the
+ Search show up in the footer of the group's direct members by default.
+ _________________________________________________________________
+
+5.10.4. Name and Password
+
+ On this tab, you can change your basic account information, including your
+ password, email address and real name. For security reasons, in order to
+ change anything on this page you must type your current password into the
+ "Password" field at the top of the page. If you attempt to change your email
+ address, a confirmation email is sent to both the old and new addresses,
+ with a link to use to confirm the change. This helps to prevent account
+ hijacking.
+ _________________________________________________________________
+
+5.10.5. Permissions
+
+ This is a purely informative page which outlines your current permissions on
+ this installation of Bugzilla.
+
+ A complete list of permissions is below. Only users with editusers
+ privileges can change the permissions of other users.
+
+ admin
+ Indicates user is an Administrator.
+
+ bz_canusewhineatothers
+ Indicates user can configure whine reports for other users.
+
+ bz_canusewhines
+ Indicates user can configure whine reports for self.
+
+ bz_sudoers
+ Indicates user can perform actions as other users.
+
+ bz_sudo_protect
+ Indicates user can not be impersonated by other users.
+
+ canconfirm
+ Indicates user can confirm a bug or mark it a duplicate.
+
+ creategroups
+ Indicates user can create and destroy groups.
+
+ editbugs
+ Indicates user can edit all bug fields.
+
+ editclassifications
+ Indicates user can create, destroy, and edit classifications.
+
+ editcomponents
+ Indicates user can create, destroy, and edit components.
+
+ editkeywords
+ Indicates user can create, destroy, and edit keywords.
+
+ editusers
+ Indicates user can edit or disable users.
+
+ tweakparams
+ Indicates user can change Parameters.
+
+ Note
+
+ For more information on how permissions work in Bugzilla (i.e. who can
+ change what), see Section 6.4.
+ _________________________________________________________________
+
+5.11. Reports and Charts
+
+ As well as the standard buglist, Bugzilla has two more ways of viewing sets
+ of bugs. These are the reports (which give different views of the current
+ state of the database) and charts (which plot the changes in particular sets
+ of bugs over time.)
+ _________________________________________________________________
+
+5.11.1. Reports
+
+ A report is a view of the current state of the bug database.
+
+ You can run either an HTML-table-based report, or a graphical
+ line/pie/bar-chart-based one. The two have different pages to define them,
+ but are close cousins - once you've defined and viewed a report, you can
+ switch between any of the different views of the data at will.
+
+ Both report types are based on the idea of defining a set of bugs using the
+ standard search interface, and then choosing some aspect of that set to plot
+ on the horizontal and/or vertical axes. You can also get a form of
+ 3-dimensional report by choosing to have multiple images or tables.
+
+ So, for example, you could use the search form to choose "all bugs in the
+ WorldControl product", and then plot their severity against their component
+ to see which component had had the largest number of bad bugs reported
+ against it.
+
+ Once you've defined your parameters and hit "Generate Report", you can
+ switch between HTML, CSV, Bar, Line and Pie. (Note: Pie is only available if
+ you didn't define a vertical axis, as pie charts don't have one.) The other
+ controls are fairly self-explanatory; you can change the size of the image
+ if you find text is overwriting other text, or the bars are too thin to see.
+ _________________________________________________________________
+
+5.11.2. Charts
+
+ A chart is a view of the state of the bug database over time.
+
+ Bugzilla currently has two charting systems - Old Charts and New Charts. Old
+ Charts have been part of Bugzilla for a long time; they chart each status
+ and resolution for each product, and that's all. They are deprecated, and
+ going away soon - we won't say any more about them. New Charts are the
+ future - they allow you to chart anything you can define as a search.
+
+ Note
+
+ Both charting forms require the administrator to set up the data-gathering
+ script. If you can't see any charts, ask them whether they have done so.
+
+ An individual line on a chart is called a data set. All data sets are
+ organised into categories and subcategories. The data sets that Bugzilla
+ defines automatically use the Product name as a Category and Component names
+ as Subcategories, but there is no need for you to follow that naming scheme
+ with your own charts if you don't want to.
+
+ Data sets may be public or private. Everyone sees public data sets in the
+ list, but only their creator sees private data sets. Only administrators can
+ make data sets public. No two data sets, even two private ones, can have the
+ same set of category, subcategory and name. So if you are creating private
+ data sets, one idea is to have the Category be your username.
+ _________________________________________________________________
+
+5.11.2.1. Creating Charts
+
+ You create a chart by selecting a number of data sets from the list, and
+ pressing Add To List for each. In the List Of Data Sets To Plot, you can
+ define the label that data set will have in the chart's legend, and also ask
+ Bugzilla to Sum a number of data sets (e.g. you could Sum data sets
+ representing RESOLVED, VERIFIED and CLOSED in a particular product to get a
+ data set representing all the resolved bugs in that product.)
+
+ If you've erroneously added a data set to the list, select it using the
+ checkbox and click Remove. Once you add more than one data set, a "Grand
+ Total" line automatically appears at the bottom of the list. If you don't
+ want this, simply remove it as you would remove any other line.
+
+ You may also choose to plot only over a certain date range, and to cumulate
+ the results - that is, to plot each one using the previous one as a
+ baseline, so the top line gives a sum of all the data sets. It's easier to
+ try than to explain :-)
+
+ Once a data set is in the list, one can also perform certain actions on it.
+ For example, one can edit the data set's parameters (name, frequency etc.)
+ if it's one you created or if you are an administrator.
+
+ Once you are happy, click Chart This List to see the chart.
+ _________________________________________________________________
+
+5.11.2.2. Creating New Data Sets
+
+ You may also create new data sets of your own. To do this, click the "create
+ a new data set" link on the Create Chart page. This takes you to a
+ search-like interface where you can define the search that Bugzilla will
+ plot. At the bottom of the page, you choose the category, sub-category and
+ name of your new data set.
+
+ If you have sufficient permissions, you can make the data set public, and
+ reduce the frequency of data collection to less than the default seven days.
+ _________________________________________________________________
+
+5.12. Flags
+
+ A flag is a kind of status that can be set on bugs or attachments to
+ indicate that the bugs/attachments are in a certain state. Each installation
+ can define its own set of flags that can be set on bugs or attachments.
+
+ If your installation has defined a flag, you can set or unset that flag, and
+ if your administrator has enabled requesting of flags, you can submit a
+ request for another user to set the flag.
+
+ To set a flag, select either "+" or "-" from the drop-down menu next to the
+ name of the flag in the "Flags" list. The meaning of these values are
+ flag-specific and thus cannot be described in this documentation, but by way
+ of example, setting a flag named "review" to "+" may indicate that the
+ bug/attachment has passed review, while setting it to "-" may indicate that
+ the bug/attachment has failed review.
+
+ To unset a flag, click its drop-down menu and select the blank value. Note
+ that marking an attachment as obsolete automatically cancels all pending
+ requests for the attachment.
+
+ If your administrator has enabled requests for a flag, request a flag by
+ selecting "?" from the drop-down menu and then entering the username of the
+ user you want to set the flag in the text field next to the menu.
+
+ A set flag appears in bug reports and on "edit attachment" pages with the
+ abbreviated username of the user who set the flag prepended to the flag
+ name. For example, if Jack sets a "review" flag to "+", it appears as Jack:
+ review [ + ]
+
+ A requested flag appears with the user who requested the flag prepended to
+ the flag name and the user who has been requested to set the flag appended
+ to the flag name within parentheses. For example, if Jack asks Jill for
+ review, it appears as Jack: review [ ? ] (Jill).
+
+ You can browse through open requests made of you and by you by selecting 'My
+ Requests' from the footer. You can also look at open requests limited by
+ other requesters, requestees, products, components, and flag names from this
+ page. Note that you can use '-' for requestee to specify flags with 'no
+ requestee' set.
+ _________________________________________________________________
+
+5.13. Whining
+
+ Whining is a feature in Bugzilla that can regularly annoy users at specified
+ times. Using this feature, users can execute saved searches at specific
+ times (i.e. the 15th of the month at midnight) or at regular intervals (i.e.
+ every 15 minutes on Sundays). The results of the searches are sent to the
+ user, either as a single email or as one email per bug, along with some
+ descriptive text.
+
+ Warning
+
+ Throughout this section it will be assumed that all users are members of the
+ bz_canusewhines group, membership in which is required in order to use the
+ Whining system. You can easily make all users members of the bz_canusewhines
+ group by setting the User RegExp to ".*" (without the quotes).
+
+ Also worth noting is the bz_canusewhineatothers group. Members of this group
+ can create whines for any user or group in Bugzilla using a extended form of
+ the whining interface. Features only available to members of the
+ bz_canusewhineatothers group will be noted in the appropriate places.
+
+ Note
+
+ For whining to work, a special Perl script must be executed at regular
+ intervals. More information on this is available in Section 2.3.3.
+
+ Note
+
+ This section does not cover the whineatnews.pl script. See Section 2.3.2 for
+ more information on The Whining Cron.
+ _________________________________________________________________
+
+5.13.1. The Event
+
+ The whining system defines an "Event" as one or more queries being executed
+ at regular intervals, with the results of said queries (if there are any)
+ being emailed to the user. Events are created by clicking on the "Add new
+ event" button.
+
+ Once a new event is created, the first thing to set is the "Email subject
+ line". The contents of this field will be used in the subject line of every
+ email generated by this event. In addition to setting a subject, space is
+ provided to enter some descriptive text that will be included at the top of
+ each message (to help you in understanding why you received the email in the
+ first place).
+
+ The next step is to specify when the Event is to be run (the Schedule) and
+ what searches are to be performed (the Searches).
+ _________________________________________________________________
+
+5.13.2. Whining Schedule
+
+ Each whining event is associated with zero or more schedules. A schedule is
+ used to specify when the query (specified below) is to be run. A new event
+ starts out with no schedules (which means it will never run, as it is not
+ scheduled to run). To add a schedule, press the "Add a new schedule" button.
+
+ Each schedule includes an interval, which you use to tell Bugzilla when the
+ event should be run. An event can be run on certain days of the week,
+ certain days of the month, during weekdays (defined as Monday through
+ Friday), or every day.
+
+ Warning
+
+ Be careful if you set your event to run on the 29th, 30th, or 31st of the
+ month, as your event may not run exactly when expected. If you want your
+ event to run on the last day of the month, select "Last day of the month" as
+ the interval.
+
+ Once you have specified the day(s) on which the event is to be run, you
+ should now specify the time at which the event is to be run. You can have
+ the event run at a certain hour on the specified day(s), or every hour,
+ half-hour, or quarter-hour on the specified day(s).
+
+ If a single schedule does not execute an event as many times as you would
+ want, you can create another schedule for the same event. For example, if
+ you want to run an event on days whose numbers are divisible by seven, you
+ would need to add four schedules to the event, setting the schedules to run
+ on the 7th, 14th, 21st, and 28th (one day per schedule) at whatever time (or
+ times) you choose.
+
+ Note
+
+ If you are a member of the bz_canusewhineatothers group, then you will be
+ presented with another option: "Mail to". Using this you can control who
+ will receive the emails generated by this event. You can choose to send the
+ emails to a single user (identified by email address) or a single group
+ (identified by group name). To send to multiple users or groups, create a
+ new schedule for each additional user/group.
+ _________________________________________________________________
+
+5.13.3. Whining Searches
+
+ Each whining event is associated with zero or more searches. A search is any
+ saved search to be run as part of the specified schedule (see above). You
+ start out without any searches associated with the event (which means that
+ the event will not run, as there will never be any results to return). To
+ add a search, press the "Include search" button.
+
+ The first field to examine in your newly added search is the Sort field.
+ Searches are run, and results included, in the order specified by the Sort
+ field. Searches with smaller Sort values will run before searches with
+ bigger Sort values.
+
+ The next field to examine is the Search field. This is where you choose the
+ actual search that is to be run. Instead of defining search parameters here,
+ you are asked to choose from the list of saved searches (the same list that
+ appears at the bottom of every Bugzilla page). You are only allowed to
+ choose from searches that you have saved yourself (the default saved search,
+ "My Bugs", is not a valid choice). If you do not have any saved searches,
+ you can take this opportunity to create one (see Section 5.5.4).
+
+ Note
+
+ When running queries, the whining system acts as if you are the user
+ executing the query. This means that the whining system will ignore bugs
+ that match your query, but that you can not access.
+
+ Once you have chosen the saved search to be executed, give the query a
+ descriptive title. This title will appear in the email, above the results of
+ the query. If you choose "One message per bug", the query title will appear
+ at the top of each email that contains a bug matching your query.
+
+ Finally, decide if the results of the query should be sent in a single
+ email, or if each bug should appear in its own email.
+
+ Warning
+
+ Think carefully before checking the "One message per bug" box. If you create
+ a query that matches thousands of bugs, you will receive thousands of
+ emails!
+ _________________________________________________________________
+
+5.13.4. Saving Your Changes
+
+ Once you have defined at least one schedule, and created at least one query,
+ go ahead and "Update/Commit". This will save your Event and make it
+ available for immediate execution.
+
+ Note
+
+ If you ever feel like deleting your event, you may do so using the "Remove
+ Event" button in the upper-right corner of each Event. You can also modify
+ an existing event, so long as you "Update/Commit" after completing your
+ modifications.
+ _________________________________________________________________
+
+Chapter 6. Customizing Bugzilla
+
+6.1. Bugzilla Extensions
+
+ One of the best ways to customize Bugzilla is by writing a Bugzilla
+ Extension. Bugzilla Extensions let you modify both the code and UI of
+ Bugzilla in a way that can be distributed to other Bugzilla users and ported
+ forward to future versions of Bugzilla with minimal effort.
+
+ See the Bugzilla Extension documentation for information on how to write an
+ Extension.
+ _________________________________________________________________
+
+6.2. Custom Skins
+
+ Bugzilla allows you to have multiple skins. These are custom CSS and
+ possibly also custom images for Bugzilla. To create a new custom skin, you
+ have two choices:
+
+ * Make a single CSS file, and put it in the skins/contrib directory.
+ * Make a directory that contains all the same CSS file names as
+ skins/standard/, and put your directory in skins/contrib/.
+
+ After you put the file or the directory there, make sure to run
+ checksetup.pl so that it can reset the file permissions correctly.
+
+ After you have installed the new skin, it will show up as an option in the
+ user's General Preferences. If you would like to force a particular skin on
+ all users, just select it in the Default Preferences and then uncheck
+ "Enabled" on the preference.
+ _________________________________________________________________
+
+6.3. Template Customization
+
+ Administrators can configure the look and feel of Bugzilla without having to
+ edit Perl files or face the nightmare of massive merge conflicts when they
+ upgrade to a newer version in the future.
+
+ Templatization also makes localized versions of Bugzilla possible, for the
+ first time. It's possible to have Bugzilla's UI language determined by the
+ user's browser. More information is available in Section 6.3.6.
+ _________________________________________________________________
+
+6.3.1. Template Directory Structure
+
+ The template directory structure starts with top level directory named
+ template, which contains a directory for each installed localization. The
+ next level defines the language used in the templates. Bugzilla comes with
+ English templates, so the directory name is en, and we will discuss
+ template/en throughout the documentation. Below template/en is the default
+ directory, which contains all the standard templates shipped with Bugzilla.
+
+ Warning
+
+ A directory data/templates also exists; this is where Template Toolkit puts
+ the compiled versions of the templates from either the default or custom
+ directories. Do not directly edit the files in this directory, or all your
+ changes will be lost the next time Template Toolkit recompiles the
+ templates.
+ _________________________________________________________________
+
+6.3.2. Choosing a Customization Method
+
+ If you want to edit Bugzilla's templates, the first decision you must make
+ is how you want to go about doing so. There are two choices, and which you
+ use depends mainly on the scope of your modifications, and the method you
+ plan to use to upgrade Bugzilla.
+
+ The first method of making customizations is to directly edit the templates
+ found in template/en/default. This is probably the best way to go about it
+ if you are going to be upgrading Bugzilla through CVS, because if you then
+ execute a cvs update, any changes you have made will be merged automagically
+ with the updated versions.
+
+ Note
+
+ If you use this method, and CVS conflicts occur during an update, the
+ conflicted templates (and possibly other parts of your installation) will
+ not work until they are resolved.
+
+ The second method is to copy the templates to be modified into a mirrored
+ directory structure under template/en/custom. Templates in this directory
+ structure automatically override any identically-named and
+ identically-located templates in the default directory.
+
+ Note
+
+ The custom directory does not exist at first and must be created if you want
+ to use it.
+
+ The second method of customization should be used if you use the overwriting
+ method of upgrade, because otherwise your changes will be lost. This method
+ may also be better if you are using the CVS method of upgrading and are
+ going to make major changes, because it is guaranteed that the contents of
+ this directory will not be touched during an upgrade, and you can then
+ decide whether to continue using your own templates, or make the effort to
+ merge your changes into the new versions by hand.
+
+ Using this method, your installation may break if incompatible changes are
+ made to the template interface. Such changes should be documented in the
+ release notes, provided you are using a stable release of Bugzilla. If you
+ use using unstable code, you will need to deal with this one yourself,
+ although if possible the changes will be mentioned before they occur in the
+ deprecations section of the previous stable release's release notes.
+
+ Note
+
+ Regardless of which method you choose, it is recommended that you run
+ ./checksetup.pl after editing any templates in the template/en/default
+ directory, and after creating or editing any templates in the custom
+ directory.
+
+ Warning
+
+ It is required that you run ./checksetup.pl after creating a new template in
+ the custom directory. Failure to do so will raise an incomprehensible error
+ message.
+ _________________________________________________________________
+
+6.3.3. How To Edit Templates
+
+ Note
+
+ If you are making template changes that you intend on submitting back for
+ inclusion in standard Bugzilla, you should read the relevant sections of the
+ Developers' Guide.
+
+ The syntax of the Template Toolkit language is beyond the scope of this
+ guide. It's reasonably easy to pick up by looking at the current templates;
+ or, you can read the manual, available on the Template Toolkit home page.
+
+ One thing you should take particular care about is the need to properly HTML
+ filter data that has been passed into the template. This means that if the
+ data can possibly contain special HTML characters such as <, and the data
+ was not intended to be HTML, they need to be converted to entity form, i.e.
+ &lt;. You use the 'html' filter in the Template Toolkit to do this. If you
+ forget, you may open up your installation to cross-site scripting attacks.
+
+ Also note that Bugzilla adds a few filters of its own, that are not in
+ standard Template Toolkit. In particular, the 'url_quote' filter can convert
+ characters that are illegal or have special meaning in URLs, such as &, to
+ the encoded form, i.e. %26. This actually encodes most characters (but not
+ the common ones such as letters and numbers and so on), including the
+ HTML-special characters, so there's never a need to HTML filter afterwards.
+
+ Editing templates is a good way of doing a "poor man's custom fields". For
+ example, if you don't use the Status Whiteboard, but want to have a
+ free-form text entry box for "Build Identifier", then you can just edit the
+ templates to change the field labels. It's still be called status_whiteboard
+ internally, but your users don't need to know that.
+ _________________________________________________________________
+
+6.3.4. Template Formats and Types
+
+ Some CGI's have the ability to use more than one template. For example,
+ buglist.cgi can output itself as RDF, or as two formats of HTML (complex and
+ simple). The mechanism that provides this feature is extensible.
+
+ Bugzilla can support different types of output, which again can have
+ multiple formats. In order to request a certain type, you can append the
+ &ctype=<contenttype> (such as rdf or html) to the <cginame>.cgi URL. If you
+ would like to retrieve a certain format, you can use the &format=<format>
+ (such as simple or complex) in the URL.
+
+ To see if a CGI supports multiple output formats and types, grep the CGI for
+ "get_format". If it's not present, adding multiple format/type support isn't
+ too hard - see how it's done in other CGIs, e.g. config.cgi.
+
+ To make a new format template for a CGI which supports this, open a current
+ template for that CGI and take note of the INTERFACE comment (if present.)
+ This comment defines what variables are passed into this template. If there
+ isn't one, I'm afraid you'll have to read the template and the code to find
+ out what information you get.
+
+ Write your template in whatever markup or text style is appropriate.
+
+ You now need to decide what content type you want your template served as.
+ The content types are defined in the Bugzilla/Constants.pm file in the
+ contenttypes constant. If your content type is not there, add it. Remember
+ the three- or four-letter tag assigned to your content type. This tag will
+ be part of the template filename.
+
+ Note
+
+ After adding or changing a content type, it's suitable to edit
+ Bugzilla/Constants.pm in order to reflect the changes. Also, the file should
+ be kept up to date after an upgrade if content types have been customized in
+ the past.
+
+ Save the template as <stubname>-<formatname>.<contenttypetag>.tmpl. Try out
+ the template by calling the CGI as
+ <cginame>.cgi?format=<formatname>&ctype=<type> .
+ _________________________________________________________________
+
+6.3.5. Particular Templates
+
+ There are a few templates you may be particularly interested in customizing
+ for your installation.
+
+ index.html.tmpl: This is the Bugzilla front page.
+
+ global/header.html.tmpl: This defines the header that goes on all Bugzilla
+ pages. The header includes the banner, which is what appears to users and is
+ probably what you want to edit instead. However the header also includes the
+ HTML HEAD section, so you could for example add a stylesheet or META tag by
+ editing the header.
+
+ global/banner.html.tmpl: This contains the "banner", the part of the header
+ that appears at the top of all Bugzilla pages. The default banner is
+ reasonably barren, so you'll probably want to customize this to give your
+ installation a distinctive look and feel. It is recommended you preserve the
+ Bugzilla version number in some form so the version you are running can be
+ determined, and users know what docs to read.
+
+ global/footer.html.tmpl: This defines the footer that goes on all Bugzilla
+ pages. Editing this is another way to quickly get a distinctive look and
+ feel for your Bugzilla installation.
+
+ global/variables.none.tmpl: This defines a list of terms that may be changed
+ in order to "brand" the Bugzilla instance In this way, terms like "bugs" can
+ be replaced with "issues" across the whole Bugzilla installation. The name
+ "Bugzilla" and other words can be customized as well.
+
+ list/table.html.tmpl: This template controls the appearance of the bug lists
+ created by Bugzilla. Editing this template allows per-column control of the
+ width and title of a column, the maximum display length of each entry, and
+ the wrap behaviour of long entries. For long bug lists, Bugzilla inserts a
+ 'break' every 100 bugs by default; this behaviour is also controlled by this
+ template, and that value can be modified here.
+
+ bug/create/user-message.html.tmpl: This is a message that appears near the
+ top of the bug reporting page. By modifying this, you can tell your users
+ how they should report bugs.
+
+ bug/process/midair.html.tmpl: This is the page used if two people submit
+ simultaneous changes to the same bug. The second person to submit their
+ changes will get this page to tell them what the first person did, and ask
+ if they wish to overwrite those changes or go back and revisit the bug. The
+ default title and header on this page read "Mid-air collision detected!" If
+ you work in the aviation industry, or other environment where this might be
+ found offensive (yes, we have true stories of this happening) you'll want to
+ change this to something more appropriate for your environment.
+
+ bug/create/create.html.tmpl and bug/create/comment.txt.tmpl: You may not
+ wish to go to the effort of creating custom fields in Bugzilla, yet you want
+ to make sure that each bug report contains a number of pieces of important
+ information for which there is not a special field. The bug entry system has
+ been designed in an extensible fashion to enable you to add arbitrary HTML
+ widgets, such as drop-down lists or textboxes, to the bug entry page and
+ have their values appear formatted in the initial comment. A hidden field
+ that indicates the format should be added inside the form in order to make
+ the template functional. Its value should be the suffix of the template
+ filename. For example, if the file is called create-cust.html.tmpl, then
+ <input type="hidden" name="format" value="cust">
+
+ should be used inside the form.
+
+ An example of this is the mozilla.org guided bug submission form. The code
+ for this comes with the Bugzilla distribution as an example for you to copy.
+ It can be found in the files create-guided.html.tmpl and
+ comment-guided.html.tmpl.
+
+ So to use this feature, create a custom template for enter_bug.cgi. The
+ default template, on which you could base it, is
+ custom/bug/create/create.html.tmpl. Call it create-<formatname>.html.tmpl,
+ and in it, add widgets for each piece of information you'd like collected -
+ such as a build number, or set of steps to reproduce.
+
+ Then, create a template like custom/bug/create/comment.txt.tmpl, and call it
+ comment-<formatname>.txt.tmpl. This template should reference the form
+ fields you have created using the syntax [% form.<fieldname> %]. When a bug
+ report is submitted, the initial comment attached to the bug report will be
+ formatted according to the layout of this template.
+
+ For example, if your custom enter_bug template had a field
+ <input type="text" name="buildid" size="30">
+
+ and then your comment.txt.tmpl had
+ BuildID: [% form.buildid %]
+
+ then something like
+ BuildID: 20020303
+
+ would appear in the initial comment.
+ _________________________________________________________________
+
+6.3.6. Configuring Bugzilla to Detect the User's Language
+
+ Bugzilla honours the user's Accept: HTTP header. You can install templates
+ in other languages, and Bugzilla will pick the most appropriate according to
+ a priority order defined by you. Many language templates can be obtained
+ from http://www.bugzilla.org/download.html#localizations. Instructions for
+ submitting new languages are also available from that location.
+ _________________________________________________________________
+
+6.4. Customizing Who Can Change What
+
+ Warning
+
+ This feature should be considered experimental; the Bugzilla code you will
+ be changing is not stable, and could change or move between versions. Be
+ aware that if you make modifications as outlined here, you may have to
+ re-make them or port them if Bugzilla changes internally between versions,
+ and you upgrade.
+
+ Companies often have rules about which employees, or classes of employees,
+ are allowed to change certain things in the bug system. For example, only
+ the bug's designated QA Contact may be allowed to VERIFY the bug. Bugzilla
+ has been designed to make it easy for you to write your own custom rules to
+ define who is allowed to make what sorts of value transition.
+
+ By default, assignees, QA owners and users with editbugs privileges can edit
+ all fields of bugs, except group restrictions (unless they are members of
+ the groups they are trying to change). Bug reporters also have the ability
+ to edit some fields, but in a more restrictive manner. Other users, without
+ editbugs privileges, can not edit bugs, except to comment and add themselves
+ to the CC list.
+
+ For maximum flexibility, customizing this means editing Bugzilla's Perl
+ code. This gives the administrator complete control over exactly who is
+ allowed to do what. The relevant method is called check_can_change_field(),
+ and is found in Bug.pm in your Bugzilla/ directory. If you open that file
+ and search for "sub check_can_change_field", you'll find it.
+
+ This function has been carefully commented to allow you to see exactly how
+ it works, and give you an idea of how to make changes to it. Certain marked
+ sections should not be changed - these are the "plumbing" which makes the
+ rest of the function work. In between those sections, you'll find snippets
+ of code like:
+ # Allow the assignee to change anything.
+ if ($ownerid eq $whoid) {
+ return 1;
+ }
+
+ It's fairly obvious what this piece of code does.
+
+ So, how does one go about changing this function? Well, simple changes can
+ be made just by removing pieces - for example, if you wanted to prevent any
+ user adding a comment to a bug, just remove the lines marked "Allow anyone
+ to change comments." If you don't want the Reporter to have any special
+ rights on bugs they have filed, just remove the entire section that deals
+ with the Reporter.
+
+ More complex customizations are not much harder. Basically, you add a check
+ in the right place in the function, i.e. after all the variables you are
+ using have been set up. So, don't look at $ownerid before $ownerid has been
+ obtained from the database. You can either add a positive check, which
+ returns 1 (allow) if certain conditions are true, or a negative check, which
+ returns 0 (deny.) E.g.:
+ if ($field eq "qacontact") {
+ if (Bugzilla->user->in_group("quality_assurance")) {
+ return 1;
+ }
+ else {
+ return 0;
+ }
+ }
+
+ This says that only users in the group "quality_assurance" can change the QA
+ Contact field of a bug.
+
+ Getting more weird:
+ if (($field eq "priority") &&
+ (Bugzilla->user->email =~ /.*\@example\.com$/))
+ {
+ if ($oldvalue eq "P1") {
+ return 1;
+ }
+ else {
+ return 0;
+ }
+ }
+
+ This says that if the user is trying to change the priority field, and their
+ email address is @example.com, they can only do so if the old value of the
+ field was "P1". Not very useful, but illustrative.
+
+ Warning
+
+ If you are modifying process_bug.cgi in any way, do not change the code that
+ is bounded by DO_NOT_CHANGE blocks. Doing so could compromise security, or
+ cause your installation to stop working entirely.
+
+ For a list of possible field names, look at the bugs table in the database.
+ If you need help writing custom rules for your organization, ask in the
+ newsgroup.
+ _________________________________________________________________
+
+6.5. Integrating Bugzilla with Third-Party Tools
+
+ Many utilities and applications can integrate with Bugzilla, either on the
+ client- or server-side. None of them are maintained by the Bugzilla
+ community, nor are they tested during our QA tests, so use them at your own
+ risk. They are listed at https://wiki.mozilla.org/Bugzilla:Addons.
+ _________________________________________________________________
+
+Appendix A. Troubleshooting
+
+ This section gives solutions to common Bugzilla installation problems. If
+ none of the section headings seems to match your problem, read the general
+ advice.
+ _________________________________________________________________
+
+A.1. General Advice
+
+ If you can't get checksetup.pl to run to completion, it normally explains
+ what's wrong and how to fix it. If you can't work it out, or if it's being
+ uncommunicative, post the errors in the mozilla.support.bugzilla newsgroup.
+
+ If you have made it all the way through Section 2.1 (Installation) and
+ Section 2.2 (Configuration) but accessing the Bugzilla URL doesn't work, the
+ first thing to do is to check your web server error log. For Apache, this is
+ often located at /etc/logs/httpd/error_log. The error messages you see may
+ be self-explanatory enough to enable you to diagnose and fix the problem. If
+ not, see below for some commonly-encountered errors. If that doesn't help,
+ post the errors to the newsgroup.
+
+ Bugzilla can also log all user-based errors (and many code-based errors)
+ that occur, without polluting the web server's error log. To enable Bugzilla
+ error logging, create a file that Bugzilla can write to, named errorlog, in
+ the Bugzilla data directory. Errors will be logged as they occur, and will
+ include the type of the error, the IP address and username (if available) of
+ the user who triggered the error, and the values of all environment
+ variables; if a form was being submitted, the data in the form will also be
+ included. To disable error logging, delete or rename the errorlog file.
+ _________________________________________________________________
+
+A.2. The Apache web server is not serving Bugzilla pages
+
+ After you have run checksetup.pl twice, run testserver.pl
+ http://yoursite.yourdomain/yoururl to confirm that your web server is
+ configured properly for Bugzilla.
+bash$ ./testserver.pl http://landfill.bugzilla.org/bugzilla-tip
+TEST-OK Webserver is running under group id in $webservergroup.
+TEST-OK Got ant picture.
+TEST-OK Webserver is executing CGIs.
+TEST-OK Webserver is preventing fetch of http://landfill.bugzilla.org/bugzilla-
+tip/localconfig.
+ _________________________________________________________________
+
+A.3. I installed a Perl module, but checksetup.pl claims it's not installed!
+
+ This could be caused by one of two things:
+
+ 1. You have two versions of Perl on your machine. You are installing
+ modules into one, and Bugzilla is using the other. Rerun the CPAN
+ commands (or manual compile) using the full path to Perl from the top of
+ checksetup.pl. This will make sure you are installing the modules in the
+ right place.
+ 2. The permissions on your library directories are set incorrectly. They
+ must, at the very least, be readable by the web server user or group. It
+ is recommended that they be world readable.
+ _________________________________________________________________
+
+A.4. DBD::Sponge::db prepare failed
+
+ The following error message may appear due to a bug in DBD::mysql (over
+ which the Bugzilla team have no control):
+ DBD::Sponge::db prepare failed: Cannot determine NUM_OF_FIELDS at D:/Perl/site
+/lib/DBD/mysql.pm line 248.
+ SV = NULL(0x0) at 0x20fc444
+ REFCNT = 1
+ FLAGS = (PADBUSY,PADMY)
+
+ To fix this, go to <path-to-perl>/lib/DBD/sponge.pm in your Perl
+ installation and replace
+ my $numFields;
+ if ($attribs->{'NUM_OF_FIELDS'}) {
+ $numFields = $attribs->{'NUM_OF_FIELDS'};
+ } elsif ($attribs->{'NAME'}) {
+ $numFields = @{$attribs->{NAME}};
+
+ with
+ my $numFields;
+ if ($attribs->{'NUM_OF_FIELDS'}) {
+ $numFields = $attribs->{'NUM_OF_FIELDS'};
+ } elsif ($attribs->{'NAMES'}) {
+ $numFields = @{$attribs->{NAMES}};
+
+ (note the S added to NAME.)
+ _________________________________________________________________
+
+A.5. cannot chdir(/var/spool/mqueue)
+
+ If you are installing Bugzilla on SuSE Linux, or some other distributions
+ with "paranoid" security options, it is possible that the checksetup.pl
+ script may fail with the error:
+ cannot chdir(/var/spool/mqueue): Permission denied
+
+ This is because your /var/spool/mqueue directory has a mode of drwx------.
+ Type chmod 755 /var/spool/mqueue as root to fix this problem. This will
+ allow any process running on your machine the ability to read the
+ /var/spool/mqueue directory.
+ _________________________________________________________________
+
+A.6. Everybody is constantly being forced to relogin
+
+ The most-likely cause is that the "cookiepath" parameter is not set
+ correctly in the Bugzilla configuration. You can change this (if you're a
+ Bugzilla administrator) from the editparams.cgi page via the web interface.
+
+ The value of the cookiepath parameter should be the actual directory
+ containing your Bugzilla installation, as seen by the end-user's web
+ browser. Leading and trailing slashes are mandatory. You can also set the
+ cookiepath to any directory which is a parent of the Bugzilla directory
+ (such as '/', the root directory). But you can't put something that isn't at
+ least a partial match or it won't work. What you're actually doing is
+ restricting the end-user's browser to sending the cookies back only to that
+ directory.
+
+ How do you know if you want your specific Bugzilla directory or the whole
+ site?
+
+ If you have only one Bugzilla running on the server, and you don't mind
+ having other applications on the same server with it being able to see the
+ cookies (you might be doing this on purpose if you have other things on your
+ site that share authentication with Bugzilla), then you'll want to have the
+ cookiepath set to "/", or to a sufficiently-high enough directory that all
+ of the involved apps can see the cookies.
+
+ Example A-1. Examples of urlbase/cookiepath pairs for sharing login cookies
+
+ urlbase is http://bugzilla.mozilla.org/
+ cookiepath is /
+ urlbase is http://tools.mysite.tld/bugzilla/
+ but you have http://tools.mysite.tld/someotherapp/ which s
+ hares
+ authentication with your Bugzilla
+ cookiepath is /
+
+ On the other hand, if you have more than one Bugzilla running on the server
+ (some people do - we do on landfill) then you need to have the cookiepath
+ restricted enough so that the different Bugzillas don't confuse their
+ cookies with one another.
+
+ Example A-2. Examples of urlbase/cookiepath pairs to restrict the login
+ cookie
+
+ urlbase is http://landfill.bugzilla.org/bugzilla-tip/
+ cookiepath is /bugzilla-tip/
+ urlbase is http://landfill.bugzilla.org/bugzilla-2.16-branch/
+ cookiepath is /bugzilla-2.16-branch/
+
+ If you had cookiepath set to "/" at any point in the past and need to set it
+ to something more restrictive (i.e. "/bugzilla/"), you can safely do this
+ without requiring users to delete their Bugzilla-related cookies in their
+ browser (this is true starting with Bugzilla 2.18 and Bugzilla 2.16.5).
+ _________________________________________________________________
+
+A.7. index.cgi doesn't show up unless specified in the URL
+
+ You probably need to set up your web server in such a way that it will serve
+ the index.cgi page as an index page.
+
+ If you are using Apache, you can do this by adding index.cgi to the end of
+ the DirectoryIndex line as mentioned in Section 2.2.4.1.
+ _________________________________________________________________
+
+A.8. checksetup.pl reports "Client does not support authentication protocol
+requested by server..."
+
+ This error is occurring because you are using the new password encryption
+ that comes with MySQL 4.1, while your DBD::mysql module was compiled against
+ an older version of MySQL. If you recompile DBD::mysql against the current
+ MySQL libraries (or just obtain a newer version of this module) then the
+ error may go away.
+
+ If that does not fix the problem, or if you cannot recompile the existing
+ module (e.g. you're running Windows) and/or don't want to replace it (e.g.
+ you want to keep using a packaged version), then a workaround is available
+ from the MySQL docs: http://dev.mysql.com/doc/mysql/en/Old_client.html
+ _________________________________________________________________
+
+Appendix B. Contrib
+
+ There are a number of unofficial Bugzilla add-ons in the
+ $BUGZILLA_ROOT/contrib/ directory. This section documents them.
+ _________________________________________________________________
+
+B.1. Command-line Search Interface
+
+ There are a suite of Unix utilities for searching Bugzilla from the command
+ line. They live in the contrib/cmdline directory. There are three files -
+ query.conf, buglist and bugs.
+
+ Warning
+
+ These files pre-date the templatization work done as part of the 2.16
+ release, and have not been updated.
+
+ query.conf contains the mapping from options to field names and comparison
+ types. Quoted option names are "grepped" for, so it should be easy to edit
+ this file. Comments (#) have no effect; you must make sure these lines do
+ not contain any quoted "option".
+
+ buglist is a shell script that submits a Bugzilla query and writes the
+ resulting HTML page to stdout. It supports both short options, (such as
+ "-Afoo" or "-Rbar") and long options (such as "--assignedto=foo" or
+ "--reporter=bar"). If the first character of an option is not "-", it is
+ treated as if it were prefixed with "--default=".
+
+ The column list is taken from the COLUMNLIST environment variable. This is
+ equivalent to the "Change Columns" option that is available when you list
+ bugs in buglist.cgi. If you have already used Bugzilla, grep for COLUMNLIST
+ in your cookies file to see your current COLUMNLIST setting.
+
+ bugs is a simple shell script which calls buglist and extracts the bug
+ numbers from the output. Adding the prefix
+ "http://bugzilla.mozilla.org/buglist.cgi?bug_id=" turns the bug list into a
+ working link if any bugs are found. Counting bugs is easy. Pipe the results
+ through sed -e 's/,/ /g' | wc | awk '{printf $2 "\n"}'
+
+ Akkana Peck says she has good results piping buglist output through w3m -T
+ text/html -dump
+ _________________________________________________________________
+
+B.2. Command-line 'Send Unsent Bug-mail' tool
+
+ Within the contrib directory exists a utility with the descriptive (if
+ compact) name of sendunsentbugmail.pl. The purpose of this script is,
+ simply, to send out any bug-related mail that should have been sent by now,
+ but for one reason or another has not.
+
+ To accomplish this task, sendunsentbugmail.pl uses the same mechanism as the
+ sanitycheck.cgi script; it scans through the entire database looking for
+ bugs with changes that were made more than 30 minutes ago, but where there
+ is no record of anyone related to that bug having been sent mail. Having
+ compiled a list, it then uses the standard rules to determine who gets mail,
+ and sends it out.
+
+ As the script runs, it indicates the bug for which it is currently sending
+ mail; when it has finished, it gives a numerical count of how many mails
+ were sent and how many people were excluded. (Individual user names are not
+ recorded or displayed.) If the script produces no output, that means no
+ unsent mail was detected.
+
+ Usage: move the sendunsentbugmail.pl script up into the main directory,
+ ensure it has execute permission, and run it from the command line (or from
+ a cron job) with no parameters.
+ _________________________________________________________________
+
+Appendix C. Manual Installation of Perl Modules
+
+C.1. Instructions
+
+ If you need to install Perl modules manually, here's how it's done. Download
+ the module using the link given in the next section, and then apply this
+ magic incantation, as root:
+
+bash# tar -xzvf <module>.tar.gz
+bash# cd <module>
+bash# perl Makefile.PL
+bash# make
+bash# make test
+bash# make install
+
+ Note
+
+ In order to compile source code under Windows you will need to obtain a
+ 'make' utility. The nmake utility provided with Microsoft Visual C++ may be
+ used. As an alternative, there is a utility called dmake available from CPAN
+ which is written entirely in Perl.
+
+ As described in Section C.2, however, most packages already exist and are
+ available from ActiveState or theory58S. We highly recommend that you
+ install them using the ppm GUI available with ActiveState and to add the
+ theory58S repository to your list of repositories.
+ _________________________________________________________________
+
+C.2. Download Locations
+
+ Note
+
+ Running Bugzilla on Windows requires the use of ActiveState Perl 5.8.1 or
+ higher. Many modules already exist in the core distribution of ActiveState
+ Perl. Additional modules can be downloaded from
+ http://theoryx5.uwinnipeg.ca/ppms/ if you use Perl 5.8.x or from
+ http://cpan.uwinnipeg.ca/PPMPackages/10xx/ if you use Perl 5.10.x.
+
+ CGI:
+
+ CPAN Download Page: http://search.cpan.org/dist/CGI.pm/
+ Documentation: http://perldoc.perl.org/CGI.html
+
+ Data-Dumper:
+
+ CPAN Download Page: http://search.cpan.org/dist/Data-Dumper/
+ Documentation: http://search.cpan.org/dist/Data-Dumper/Dumper.pm
+
+ Date::Format (part of TimeDate):
+
+ CPAN Download Page: http://search.cpan.org/dist/TimeDate/
+ Documentation: http://search.cpan.org/dist/TimeDate/lib/Date/Format.
+ pm
+
+ DBI:
+
+ CPAN Download Page: http://search.cpan.org/dist/DBI/
+ Documentation: http://dbi.perl.org/docs/
+
+ DBD::mysql:
+
+ CPAN Download Page: http://search.cpan.org/dist/DBD-mysql/
+ Documentation: http://search.cpan.org/dist/DBD-mysql/lib/DBD/mysql.p
+ m
+
+ DBD::Pg:
+
+ CPAN Download Page: http://search.cpan.org/dist/DBD-Pg/
+ Documentation: http://search.cpan.org/dist/DBD-Pg/Pg.pm
+
+ Template-Toolkit:
+
+ CPAN Download Page: http://search.cpan.org/dist/Template-Toolkit/
+ Documentation: http://www.template-toolkit.org/docs.html
+
+ GD:
+
+ CPAN Download Page: http://search.cpan.org/dist/GD/
+ Documentation: http://search.cpan.org/dist/GD/GD.pm
+
+ Template::Plugin::GD:
+
+ CPAN Download Page: http://search.cpan.org/dist/Template-GD/
+ Documentation: http://www.template-toolkit.org/docs/aqua/Modules/inde
+ x.html
+
+ MIME::Parser (part of MIME-tools):
+
+ CPAN Download Page: http://search.cpan.org/dist/MIME-tools/
+ Documentation: http://search.cpan.org/dist/MIME-tools/lib/MIME/Parse
+ r.pm
+ _________________________________________________________________
+
+C.3. Optional Modules
+
+ Chart::Lines:
+
+ CPAN Download Page: http://search.cpan.org/dist/Chart/
+ Documentation: http://search.cpan.org/dist/Chart/Chart.pod
+
+ GD::Graph:
+
+ CPAN Download Page: http://search.cpan.org/dist/GDGraph/
+ Documentation: http://search.cpan.org/dist/GDGraph/Graph.pm
+
+ GD::Text::Align (part of GD::Text::Util):
+
+ CPAN Download Page: http://search.cpan.org/dist/GDTextUtil/
+ Documentation: http://search.cpan.org/dist/GDTextUtil/Text/Align.pm
+
+ XML::Twig:
+
+ CPAN Download Page: http://search.cpan.org/dist/XML-Twig/
+ Documentation: http://standards.ieee.org/resources/spasystem/twig/tw
+ ig_stable.html
+
+ PatchReader:
+
+ CPAN Download Page: http://search.cpan.org/author/JKEISER/PatchReade
+ r/
+ Documentation: http://www.johnkeiser.com/mozilla/Patch_Viewer.html
+ _________________________________________________________________
+
+Appendix D. GNU Free Documentation License
+
+ Version 1.1, March 2000
+
+ Copyright (C) 2000 Free Software Foundation, Inc. 59 Temple Place, Suite
+ 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and
+ distribute verbatim copies of this license document, but changing it is
+ not allowed.
+ _________________________________________________________________
+
+0. Preamble
+
+ The purpose of this License is to make a manual, textbook, or other written
+ document "free" in the sense of freedom: to assure everyone the effective
+ freedom to copy and redistribute it, with or without modifying it, either
+ commercially or noncommercially. Secondarily, this License preserves for the
+ author and publisher a way to get credit for their work, while not being
+ considered responsible for modifications made by others.
+
+ This License is a kind of "copyleft", which means that derivative works of
+ the document must themselves be free in the same sense. It complements the
+ GNU General Public License, which is a copyleft license designed for free
+ software.
+
+ We have designed this License in order to use it for manuals for free
+ software, because free software needs free documentation: a free program
+ should come with manuals providing the same freedoms that the software does.
+ But this License is not limited to software manuals; it can be used for any
+ textual work, regardless of subject matter or whether it is published as a
+ printed book. We recommend this License principally for works whose purpose
+ is instruction or reference.
+ _________________________________________________________________
+
+1. Applicability and Definition
+
+ This License applies to any manual or other work that contains a notice
+ placed by the copyright holder saying it can be distributed under the terms
+ of this License. The "Document", below, refers to any such manual or work.
+ Any member of the public is a licensee, and is addressed as "you".
+
+ A "Modified Version" of the Document means any work containing the Document
+ or a portion of it, either copied verbatim, or with modifications and/or
+ translated into another language.
+
+ A "Secondary Section" is a named appendix or a front-matter section of the
+ Document that deals exclusively with the relationship of the publishers or
+ authors of the Document to the Document's overall subject (or to related
+ matters) and contains nothing that could fall directly within that overall
+ subject. (For example, if the Document is in part a textbook of mathematics,
+ a Secondary Section may not explain any mathematics.) The relationship could
+ be a matter of historical connection with the subject or with related
+ matters, or of legal, commercial, philosophical, ethical or political
+ position regarding them.
+
+ The "Invariant Sections" are certain Secondary Sections whose titles are
+ designated, as being those of Invariant Sections, in the notice that says
+ that the Document is released under this License.
+
+ The "Cover Texts" are certain short passages of text that are listed, as
+ Front-Cover Texts or Back-Cover Texts, in the notice that says that the
+ Document is released under this License.
+
+ A "Transparent" copy of the Document means a machine-readable copy,
+ represented in a format whose specification is available to the general
+ public, whose contents can be viewed and edited directly and
+ straightforwardly with generic text editors or (for images composed of
+ pixels) generic paint programs or (for drawings) some widely available
+ drawing editor, and that is suitable for input to text formatters or for
+ automatic translation to a variety of formats suitable for input to text
+ formatters. A copy made in an otherwise Transparent file format whose markup
+ has been designed to thwart or discourage subsequent modification by readers
+ is not Transparent. A copy that is not "Transparent" is called "Opaque".
+
+ Examples of suitable formats for Transparent copies include plain ASCII
+ without markup, Texinfo input format, LaTeX input format, SGML or XML using
+ a publicly available DTD, and standard-conforming simple HTML designed for
+ human modification. Opaque formats include PostScript, PDF, proprietary
+ formats that can be read and edited only by proprietary word processors,
+ SGML or XML for which the DTD and/or processing tools are not generally
+ available, and the machine-generated HTML produced by some word processors
+ for output purposes only.
+
+ The "Title Page" means, for a printed book, the title page itself, plus such
+ following pages as are needed to hold, legibly, the material this License
+ requires to appear in the title page. For works in formats which do not have
+ any title page as such, "Title Page" means the text near the most prominent
+ appearance of the work's title, preceding the beginning of the body of the
+ text.
+ _________________________________________________________________
+
+2. Verbatim Copying
+
+ You may copy and distribute the Document in any medium, either commercially
+ or noncommercially, provided that this License, the copyright notices, and
+ the license notice saying this License applies to the Document are
+ reproduced in all copies, and that you add no other conditions whatsoever to
+ those of this License. You may not use technical measures to obstruct or
+ control the reading or further copying of the copies you make or distribute.
+ However, you may accept compensation in exchange for copies. If you
+ distribute a large enough number of copies you must also follow the
+ conditions in section 3.
+
+ You may also lend copies, under the same conditions stated above, and you
+ may publicly display copies.
+ _________________________________________________________________
+
+3. Copying in Quantity
+
+ If you publish printed copies of the Document numbering more than 100, and
+ the Document's license notice requires Cover Texts, you must enclose the
+ copies in covers that carry, clearly and legibly, all these Cover Texts:
+ Front-Cover Texts on the front cover, and Back-Cover Texts on the back
+ cover. Both covers must also clearly and legibly identify you as the
+ publisher of these copies. The front cover must present the full title with
+ all words of the title equally prominent and visible. You may add other
+ material on the covers in addition. Copying with changes limited to the
+ covers, as long as they preserve the title of the Document and satisfy these
+ conditions, can be treated as verbatim copying in other respects.
+
+ If the required texts for either cover are too voluminous to fit legibly,
+ you should put the first ones listed (as many as fit reasonably) on the
+ actual cover, and continue the rest onto adjacent pages.
+
+ If you publish or distribute Opaque copies of the Document numbering more
+ than 100, you must either include a machine-readable Transparent copy along
+ with each Opaque copy, or state in or with each Opaque copy a
+ publicly-accessible computer-network location containing a complete
+ Transparent copy of the Document, free of added material, which the general
+ network-using public has access to download anonymously at no charge using
+ public-standard network protocols. If you use the latter option, you must
+ take reasonably prudent steps, when you begin distribution of Opaque copies
+ in quantity, to ensure that this Transparent copy will remain thus
+ accessible at the stated location until at least one year after the last
+ time you distribute an Opaque copy (directly or through your agents or
+ retailers) of that edition to the public.
+
+ It is requested, but not required, that you contact the authors of the
+ Document well before redistributing any large number of copies, to give them
+ a chance to provide you with an updated version of the Document.
+ _________________________________________________________________
+
+4. Modifications
+
+ You may copy and distribute a Modified Version of the Document under the
+ conditions of sections 2 and 3 above, provided that you release the Modified
+ Version under precisely this License, with the Modified Version filling the
+ role of the Document, thus licensing distribution and modification of the
+ Modified Version to whoever possesses a copy of it. In addition, you must do
+ these things in the Modified Version:
+
+ A. Use in the Title Page (and on the covers, if any) a title distinct from
+ that of the Document, and from those of previous versions (which should,
+ if there were any, be listed in the History section of the Document).
+ You may use the same title as a previous version if the original
+ publisher of that version gives permission.
+ B. List on the Title Page, as authors, one or more persons or entities
+ responsible for authorship of the modifications in the Modified Version,
+ together with at least five of the principal authors of the Document
+ (all of its principal authors, if it has less than five).
+ C. State on the Title page the name of the publisher of the Modified
+ Version, as the publisher.
+ D. Preserve all the copyright notices of the Document.
+ E. Add an appropriate copyright notice for your modifications adjacent to
+ the other copyright notices.
+ F. Include, immediately after the copyright notices, a license notice
+ giving the public permission to use the Modified Version under the terms
+ of this License, in the form shown in the Addendum below.
+ G. Preserve in that license notice the full lists of Invariant Sections and
+ required Cover Texts given in the Document's license notice.
+ H. Include an unaltered copy of this License.
+ I. Preserve the section entitled "History", and its title, and add to it an
+ item stating at least the title, year, new authors, and publisher of the
+ Modified Version as given on the Title Page. If there is no section
+ entitled "History" in the Document, create one stating the title, year,
+ authors, and publisher of the Document as given on its Title Page, then
+ add an item describing the Modified Version as stated in the previous
+ sentence.
+ J. Preserve the network location, if any, given in the Document for public
+ access to a Transparent copy of the Document, and likewise the network
+ locations given in the Document for previous versions it was based on.
+ These may be placed in the "History" section. You may omit a network
+ location for a work that was published at least four years before the
+ Document itself, or if the original publisher of the version it refers
+ to gives permission.
+ K. In any section entitled "Acknowledgements" or "Dedications", preserve
+ the section's title, and preserve in the section all the substance and
+ tone of each of the contributor acknowledgements and/or dedications
+ given therein.
+ L. Preserve all the Invariant Sections of the Document, unaltered in their
+ text and in their titles. Section numbers or the equivalent are not
+ considered part of the section titles.
+ M. Delete any section entitled "Endorsements". Such a section may not be
+ included in the Modified Version.
+ N. Do not retitle any existing section as "Endorsements" or to conflict in
+ title with any Invariant Section.
+
+ If the Modified Version includes new front-matter sections or appendices
+ that qualify as Secondary Sections and contain no material copied from the
+ Document, you may at your option designate some or all of these sections as
+ invariant. To do this, add their titles to the list of Invariant Sections in
+ the Modified Version's license notice. These titles must be distinct from
+ any other section titles.
+
+ You may add a section entitled "Endorsements", provided it contains nothing
+ but endorsements of your Modified Version by various parties--for example,
+ statements of peer review or that the text has been approved by an
+ organization as the authoritative definition of a standard.
+
+ You may add a passage of up to five words as a Front-Cover Text, and a
+ passage of up to 25 words as a Back-Cover Text, to the end of the list of
+ Cover Texts in the Modified Version. Only one passage of Front-Cover Text
+ and one of Back-Cover Text may be added by (or through arrangements made by)
+ any one entity. If the Document already includes a cover text for the same
+ cover, previously added by you or by arrangement made by the same entity you
+ are acting on behalf of, you may not add another; but you may replace the
+ old one, on explicit permission from the previous publisher that added the
+ old one.
+
+ The author(s) and publisher(s) of the Document do not by this License give
+ permission to use their names for publicity for or to assert or imply
+ endorsement of any Modified Version.
+ _________________________________________________________________
+
+5. Combining Documents
+
+ You may combine the Document with other documents released under this
+ License, under the terms defined in section 4 above for modified versions,
+ provided that you include in the combination all of the Invariant Sections
+ of all of the original documents, unmodified, and list them all as Invariant
+ Sections of your combined work in its license notice.
+
+ The combined work need only contain one copy of this License, and multiple
+ identical Invariant Sections may be replaced with a single copy. If there
+ are multiple Invariant Sections with the same name but different contents,
+ make the title of each such section unique by adding at the end of it, in
+ parentheses, the name of the original author or publisher of that section if
+ known, or else a unique number. Make the same adjustment to the section
+ titles in the list of Invariant Sections in the license notice of the
+ combined work.
+
+ In the combination, you must combine any sections entitled "History" in the
+ various original documents, forming one section entitled "History"; likewise
+ combine any sections entitled "Acknowledgements", and any sections entitled
+ "Dedications". You must delete all sections entitled "Endorsements."
+ _________________________________________________________________
+
+6. Collections of Documents
+
+ You may make a collection consisting of the Document and other documents
+ released under this License, and replace the individual copies of this
+ License in the various documents with a single copy that is included in the
+ collection, provided that you follow the rules of this License for verbatim
+ copying of each of the documents in all other respects.
+
+ You may extract a single document from such a collection, and distribute it
+ individually under this License, provided you insert a copy of this License
+ into the extracted document, and follow this License in all other respects
+ regarding verbatim copying of that document.
+ _________________________________________________________________
+
+7. Aggregation with Independent Works
+
+ A compilation of the Document or its derivatives with other separate and
+ independent documents or works, in or on a volume of a storage or
+ distribution medium, does not as a whole count as a Modified Version of the
+ Document, provided no compilation copyright is claimed for the compilation.
+ Such a compilation is called an "aggregate", and this License does not apply
+ to the other self-contained works thus compiled with the Document, on
+ account of their being thus compiled, if they are not themselves derivative
+ works of the Document.
+
+ If the Cover Text requirement of section 3 is applicable to these copies of
+ the Document, then if the Document is less than one quarter of the entire
+ aggregate, the Document's Cover Texts may be placed on covers that surround
+ only the Document within the aggregate. Otherwise they must appear on covers
+ around the whole aggregate.
+ _________________________________________________________________
+
+8. Translation
+
+ Translation is considered a kind of modification, so you may distribute
+ translations of the Document under the terms of section 4. Replacing
+ Invariant Sections with translations requires special permission from their
+ copyright holders, but you may include translations of some or all Invariant
+ Sections in addition to the original versions of these Invariant Sections.
+ You may include a translation of this License provided that you also include
+ the original English version of this License. In case of a disagreement
+ between the translation and the original English version of this License,
+ the original English version will prevail.
+ _________________________________________________________________
+
+9. Termination
+
+ You may not copy, modify, sublicense, or distribute the Document except as
+ expressly provided for under this License. Any other attempt to copy,
+ modify, sublicense or distribute the Document is void, and will
+ automatically terminate your rights under this License. However, parties who
+ have received copies, or rights, from you under this License will not have
+ their licenses terminated so long as such parties remain in full compliance.
+ _________________________________________________________________
+
+10. Future Revisions of this License
+
+ The Free Software Foundation may publish new, revised versions of the GNU
+ Free Documentation License from time to time. Such new versions will be
+ similar in spirit to the present version, but may differ in detail to
+ address new problems or concerns. See http://www.gnu.org/copyleft/.
+
+ Each version of the License is given a distinguishing version number. If the
+ Document specifies that a particular numbered version of this License "or
+ any later version" applies to it, you have the option of following the terms
+ and conditions either of that specified version or of any later version that
+ has been published (not as a draft) by the Free Software Foundation. If the
+ Document does not specify a version number of this License, you may choose
+ any version ever published (not as a draft) by the Free Software Foundation.
+ _________________________________________________________________
+
+How to use this License for your documents
+
+ To use this License in a document you have written, include a copy of the
+ License in the document and put the following copyright and license notices
+ just after the title page:
+
+ Copyright (c) YEAR YOUR NAME. Permission is granted to copy, distribute
+ and/or modify this document under the terms of the GNU Free Documentation
+ License, Version 1.1 or any later version published by the Free Software
+ Foundation; with the Invariant Sections being LIST THEIR TITLES, with the
+ Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST. A
+ copy of the license is included in the section entitled "GNU Free
+ Documentation License".
+
+ If you have no Invariant Sections, write "with no Invariant Sections"
+ instead of saying which ones are invariant. If you have no Front-Cover
+ Texts, write "no Front-Cover Texts" instead of "Front-Cover Texts being
+ LIST"; likewise for Back-Cover Texts.
+
+ If your document contains nontrivial examples of program code, we recommend
+ releasing these examples in parallel under your choice of free software
+ license, such as the GNU General Public License, to permit their use in free
+ software.
+
+Glossary
+
+0-9, high ascii
+
+ .htaccess
+ Apache web server, and other NCSA-compliant web servers, observe the
+ convention of using files in directories called .htaccess to restrict
+ access to certain files. In Bugzilla, they are used to keep secret
+ files which would otherwise compromise your installation - e.g. the
+ localconfig file contains the password to your database. curious.
+
+A
+
+ Apache
+ In this context, Apache is the web server most commonly used for
+ serving up Bugzilla pages. Contrary to popular belief, the apache web
+ server has nothing to do with the ancient and noble Native American
+ tribe, but instead derived its name from the fact that it was "a
+ patchy" version of the original NCSA world-wide-web server.
+
+ Useful Directives when configuring Bugzilla
+
+ AddHandler
+ Tell Apache that it's OK to run CGI scripts.
+
+ AllowOverride, Options
+ These directives are used to tell Apache many things about the
+ directory they apply to. For Bugzilla's purposes, we need them
+ to allow script execution and .htaccess overrides.
+
+ DirectoryIndex
+ Used to tell Apache what files are indexes. If you can not add
+ index.cgi to the list of valid files, you'll need to set
+ $index_html to 1 in localconfig so ./checksetup.pl will create
+ an index.html that redirects to index.cgi.
+
+ ScriptInterpreterSource
+ Used when running Apache on windows so the shebang line doesn't
+ have to be changed in every Bugzilla script.
+
+ For more information about how to configure Apache for Bugzilla, see
+ Section 2.2.4.1.
+
+B
+
+ Bug
+ A "bug" in Bugzilla refers to an issue entered into the database
+ which has an associated number, assignments, comments, etc. Some also
+ refer to a "tickets" or "issues"; in the context of Bugzilla, they
+ are synonymous.
+
+ Bug Number
+ Each Bugzilla bug is assigned a number that uniquely identifies that
+ bug. The bug associated with a bug number can be pulled up via a
+ query, or easily from the very front page by typing the number in the
+ "Find" box.
+
+ Bugzilla
+ Bugzilla is the world-leading free software bug tracking system.
+
+C
+
+ Common Gateway Interface (CGI)
+ CGI is an acronym for Common Gateway Interface. This is a standard
+ for interfacing an external application with a web server. Bugzilla
+ is an example of a CGI application.
+
+ Component
+ A Component is a subsection of a Product. It should be a narrow
+ category, tailored to your organization. All Products must contain at
+ least one Component (and, as a matter of fact, creating a Product
+ with no Components will create an error in Bugzilla).
+
+ Comprehensive Perl Archive Network (CPAN)
+ CPAN stands for the "Comprehensive Perl Archive Network". CPAN
+ maintains a large number of extremely useful Perl modules -
+ encapsulated chunks of code for performing a particular task.
+
+ contrib
+ The contrib directory is a location to put scripts that have been
+ contributed to Bugzilla but are not a part of the official
+ distribution. These scripts are written by third parties and may be
+ in languages other than perl. For those that are in perl, there may
+ be additional modules or other requirements than those of the
+ official distribution.
+
+ Note
+
+ Scripts in the contrib directory are not officially supported by the
+ Bugzilla team and may break in between versions.
+
+D
+
+ daemon
+ A daemon is a computer program which runs in the background. In
+ general, most daemons are started at boot time via System V init
+ scripts, or through RC scripts on BSD-based systems. mysqld, the
+ MySQL server, and apache, a web server, are generally run as daemons.
+
+ DOS Attack
+ A DOS, or Denial of Service attack, is when a user attempts to deny
+ access to a web server by repeatedly accessing a page or sending
+ malformed requests to a webserver. A D-DOS, or Distributed Denial of
+ Service attack, is when these requests come from multiple sources at
+ the same time. Unfortunately, these are much more difficult to defend
+ against.
+
+G
+
+ Groups
+ The word "Groups" has a very special meaning to Bugzilla. Bugzilla's
+ main security mechanism comes by placing users in groups, and
+ assigning those groups certain privileges to view bugs in particular
+ Products in the Bugzilla database.
+
+J
+
+ JavaScript
+ JavaScript is cool, we should talk about it.
+
+M
+
+ Message Transport Agent (MTA)
+ A Message Transport Agent is used to control the flow of email on a
+ system. The Email::Send Perl module, which Bugzilla uses to send
+ email, can be configured to use many different underlying
+ implementations for actually sending the mail using the
+ mail_delivery_method parameter.
+
+ MySQL
+ MySQL is currently the required RDBMS for Bugzilla. MySQL can be
+ downloaded from http://www.mysql.com. While you should familiarize
+ yourself with all of the documentation, some high points are:
+
+ Backup
+ Methods for backing up your Bugzilla database.
+
+ Option Files
+ Information about how to configure MySQL using my.cnf.
+
+ Privilege System
+ Information about how to protect your MySQL server.
+
+P
+
+ Perl Package Manager (PPM)
+ http://aspn.activestate.com/ASPN/Downloads/ActivePerl/PPM/
+
+ Product
+ A Product is a broad category of types of bugs, normally representing
+ a single piece of software or entity. In general, there are several
+ Components to a Product. A Product may define a group (used for
+ security) for all bugs entered into its Components.
+
+ Perl
+ First written by Larry Wall, Perl is a remarkable program language.
+ It has the benefits of the flexibility of an interpreted scripting
+ language (such as shell script), combined with the speed and power of
+ a compiled language, such as C. Bugzilla is maintained in Perl.
+
+Q
+
+ QA
+ "QA", "Q/A", and "Q.A." are short for "Quality Assurance". In most
+ large software development organizations, there is a team devoted to
+ ensuring the product meets minimum standards before shipping. This
+ team will also generally want to track the progress of bugs over
+ their life cycle, thus the need for the "QA Contact" field in a bug.
+
+R
+
+ Relational DataBase Management System (RDBMS)
+ A relational database management system is a database system that
+ stores information in tables that are related to each other.
+
+ Regular Expression (regexp)
+ A regular expression is an expression used for pattern matching.
+ Documentation
+
+S
+
+ Service
+ In Windows NT environment, a boot-time background application is
+ referred to as a service. These are generally managed through the
+ control panel while logged in as an account with "Administrator"
+ level capabilities. For more information, consult your Windows manual
+ or the MSKB.
+
+ SGML
+ SGML stands for "Standard Generalized Markup Language". Created in
+ the 1980's to provide an extensible means to maintain documentation
+ based upon content instead of presentation, SGML has withstood the
+ test of time as a robust, powerful language. XML is the "baby
+ brother" of SGML; any valid XML document it, by definition, a valid
+ SGML document. The document you are reading is written and maintained
+ in SGML, and is also valid XML if you modify the Document Type
+ Definition.
+
+T
+
+ Target Milestone
+ Target Milestones are Product goals. They are configurable on a
+ per-Product basis. Most software development houses have a concept of
+ "milestones" where the people funding a project expect certain
+ functionality on certain dates. Bugzilla facilitates meeting these
+ milestones by giving you the ability to declare by which milestone a
+ bug will be fixed, or an enhancement will be implemented.
+
+ Tool Command Language (TCL)
+ TCL is an open source scripting language available for Windows,
+ Macintosh, and Unix based systems. Bugzilla 1.0 was written in TCL
+ but never released. The first release of Bugzilla was 2.0, which was
+ when it was ported to perl.
+
+Z
+
+ Zarro Boogs Found
+ This is just a goofy way of saying that there were no bugs found
+ matching your query. When asked to explain this message, Terry had
+ the following to say:
+
+
+
+ I've been asked to explain this ... way back when, when Netscape released
+ version 4.0 of its browser, we had a release party. Naturally, there had
+ been a big push to try and fix every known bug before the release.
+ Naturally, that hadn't actually happened. (This is not unique to Netscape or
+ to 4.0; the same thing has happened with every software project I've ever
+ seen.) Anyway, at the release party, T-shirts were handed out that said
+ something like "Netscape 4.0: Zarro Boogs". Just like the software, the
+ T-shirt had no known bugs. Uh-huh.
+ So, when you query for a list of bugs, and it gets no results, you can think
+ of this as a friendly reminder. Of *course* there are bugs matching your
+ query, they just aren't in the bugsystem yet...
+
+ --Terry Weissman
diff --git a/docs/en/xml/Bugzilla-Guide.xml b/docs/en/xml/Bugzilla-Guide.xml
new file mode 100644
index 000000000..671700bc9
--- /dev/null
+++ b/docs/en/xml/Bugzilla-Guide.xml
@@ -0,0 +1,174 @@
+<?xml version="1.0"?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" [
+ <!ENTITY % myents SYSTEM "bugzilla.ent">
+ %myents;
+
+<!-- Include macros -->
+<!ENTITY about SYSTEM "about.xml">
+<!ENTITY conventions SYSTEM "conventions.xml">
+<!ENTITY doc-index SYSTEM "index.xml">
+<!ENTITY gfdl SYSTEM "gfdl.xml">
+<!ENTITY glossary SYSTEM "glossary.xml">
+<!ENTITY installation SYSTEM "installation.xml">
+<!ENTITY administration SYSTEM "administration.xml">
+<!ENTITY security SYSTEM "security.xml">
+<!ENTITY using SYSTEM "using.xml">
+<!ENTITY index SYSTEM "index.xml">
+<!ENTITY customization SYSTEM "customization.xml">
+<!ENTITY troubleshooting SYSTEM "troubleshooting.xml">
+<!ENTITY patches SYSTEM "patches.xml">
+<!ENTITY modules SYSTEM "modules.xml">
+
+<!-- Things to change for a stable release:
+ * bz-ver to current stable
+ * bz-nexver to next stable
+ * bz-date to the release date
+ * landfillbase to the branch install
+ * Remove the BZ-DEVEL comments
+ - COMPILE DOCS AND CHECKIN -
+ Also, tag and tarball before completing
+ * bz-ver to devel version
+
+ For a devel release, simple bump bz-ver and bz-date
+-->
+
+<!ENTITY bz-ver "4.0">
+<!ENTITY bz-nextver "4.2">
+<!ENTITY bz-date "2011-02-15">
+<!ENTITY current-year "2011">
+
+<!ENTITY landfillbase "http://landfill.bugzilla.org/bugzilla-4.0-branch/">
+<!ENTITY bz "http://www.bugzilla.org/">
+<!ENTITY bzg-bugs "<ulink url='https://bugzilla.mozilla.org/enter_bug.cgi?product=Bugzilla&amp;component=Documentation'>Bugzilla Documentation</ulink>">
+<!ENTITY mysql "http://www.mysql.com/">
+
+<!ENTITY min-perl-ver "5.8.1">
+]>
+
+
+<!-- Coding standards for this document
+
+* Other than the GFDL, please use the "section" tag instead of "sect1",
+ "sect2", etc.
+* Use Entities to include files for new chapters in Bugzilla-Guide.xml.
+* Try to use Entities for frequently-used passages of text as well.
+* Ensure all documents compile cleanly to HTML after modification.
+ The warning, "DTDDECL catalog types not supported" is normal.
+* Try to index important terms wherever possible.
+* Use "glossterm" whenever you introduce a new term.
+* Follow coding standards at http://www.tldp.org, and
+ check out the KDE guidelines (they are nice, too)
+ http://i18n.kde.org/doc/markup.html
+* All tags should be lowercase.
+* Please use sensible spacing. The comments at the very end of each
+ file define reasonable defaults for PSGML mode in EMACS.
+* Double-indent tags, use double spacing whenever possible, and
+ try to avoid clutter and feel free to waste space in the code to make it
+ more readable.
+
+-->
+
+<book id="index">
+
+<!-- Header -->
+
+ <bookinfo>
+ <title>The Bugzilla Guide - &bz-ver;
+ Release</title>
+
+ <authorgroup>
+ <corpauthor>The Bugzilla Team</corpauthor>
+ </authorgroup>
+
+ <pubdate>&bz-date;</pubdate>
+
+ <abstract>
+ <para>
+ This is the documentation for Bugzilla, a
+ bug-tracking system from mozilla.org.
+ Bugzilla is an enterprise-class piece of software
+ that tracks millions of bugs and issues for hundreds of
+ organizations around the world.
+ </para>
+
+ <para>
+ The most current version of this document can always be found on the
+ <ulink url="http://www.bugzilla.org/docs/">Bugzilla
+ Documentation Page</ulink>.
+ </para>
+
+ </abstract>
+
+ <keywordset>
+ <keyword>Bugzilla</keyword>
+ <keyword>Guide</keyword>
+ <keyword>installation</keyword>
+ <keyword>FAQ</keyword>
+ <keyword>administration</keyword>
+ <keyword>integration</keyword>
+ <keyword>MySQL</keyword>
+ <keyword>Mozilla</keyword>
+ <keyword>webtools</keyword>
+ </keywordset>
+ </bookinfo>
+
+<!-- About This Guide -->
+&about;
+
+<!-- Installing Bugzilla -->
+&installation;
+
+<!-- Administering Bugzilla -->
+&administration;
+
+<!-- Securing Bugzilla -->
+&security;
+
+<!-- Using Bugzilla -->
+&using;
+
+<!-- Customizing Bugzilla -->
+&customization;
+
+<!-- Appendix: Troubleshooting -->
+&troubleshooting;
+
+<!-- Appendix: Custom Patches -->
+&patches;
+
+<!-- Appendix: Manually Installing Perl Modules -->
+&modules;
+
+<!-- Appendix: GNU Free Documentation License -->
+&gfdl;
+
+<!-- Glossary -->
+&glossary;
+
+<!-- Index -->
+&index;
+
+
+</book>
+
+<!-- Keep this comment at the end of the file
+Local variables:
+mode: sgml
+sgml-always-quote-attributes:t
+sgml-auto-insert-required-elements:t
+sgml-balanced-tag-edit:t
+sgml-exposed-tags:nil
+sgml-general-insert-case:lower
+sgml-indent-data:t
+sgml-indent-step:2
+sgml-local-catalogs:nil
+sgml-local-ecat-files:nil
+sgml-minimize-attributes:nil
+sgml-namecase-general:t
+sgml-omittag:t
+sgml-parent-document:("Bugzilla-Guide.xml" "book" "chapter")
+sgml-shorttag:t
+sgml-tag-region-if-active:t
+End:
+-->
+
diff --git a/docs/en/xml/about.xml b/docs/en/xml/about.xml
new file mode 100644
index 000000000..0de584013
--- /dev/null
+++ b/docs/en/xml/about.xml
@@ -0,0 +1,213 @@
+<!-- <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook V4.1//EN" [
+<!ENTITY conventions SYSTEM "conventions.xml"> ] > -->
+
+<chapter id="about">
+<title>About This Guide</title>
+
+ <section id="copyright">
+ <title>Copyright Information</title>
+
+ <para>This document is copyright (c) 2000-&current-year; by the various
+ Bugzilla contributors who wrote it.</para>
+
+ <blockquote>
+ <para>
+ Permission is granted to copy, distribute and/or modify this
+ document under the terms of the GNU Free Documentation
+ License, Version 1.1 or any later version published by the
+ Free Software Foundation; with no Invariant Sections, no
+ Front-Cover Texts, and with no Back-Cover Texts. A copy of
+ the license is included in <xref linkend="gfdl"/>.
+ </para>
+ </blockquote>
+ <para>
+ If you have any questions regarding this document, its
+ copyright, or publishing this document in non-electronic form,
+ please contact the Bugzilla Team.
+ </para>
+ </section>
+
+ <section id="disclaimer">
+ <title>Disclaimer</title>
+ <para>
+ No liability for the contents of this document can be accepted.
+ Follow the instructions herein at your own risk.
+ This document may contain errors
+ and inaccuracies that may damage your system, cause your partner
+ to leave you, your boss to fire you, your cats to
+ pee on your furniture and clothing, and global thermonuclear
+ war. Proceed with caution.
+ </para>
+ <para>
+ Naming of particular products or brands should not be seen as
+ endorsements, with the exception of the term "GNU/Linux". We
+ wholeheartedly endorse the use of GNU/Linux; it is an extremely
+ versatile, stable,
+ and robust operating system that offers an ideal operating
+ environment for Bugzilla.
+ </para>
+ <para>
+ Although the Bugzilla development team has taken great care to
+ ensure that all exploitable bugs have been fixed, security holes surely
+ exist in any piece of code. Great care should be taken both in
+ the installation and usage of this software. The Bugzilla development
+ team members assume no liability for your use of Bugzilla. You have
+ the source code, and are responsible for auditing it yourself to ensure
+ your security needs are met.
+ </para>
+ </section>
+
+<!-- Section 2: New Versions -->
+
+ <section id="newversions">
+ <title>New Versions</title>
+ <para>
+ This is the &bz-ver; version of The Bugzilla Guide. It is so named
+ to match the current version of Bugzilla.
+ </para>
+ <para>
+ The latest version of this guide can always be found at <ulink
+ url="http://www.bugzilla.org/docs/"/>. However, you should read
+ the version which came with the Bugzilla release you are using.
+ </para>
+
+ <para>
+ In addition, there are Bugzilla template localization projects in
+ <ulink url="http://www.bugzilla.org/download/#localizations">several languages</ulink>.
+ They may have translated documentation available. If you would like to
+ volunteer to translate the Guide into additional languages, please visit the
+ <ulink url="https://wiki.mozilla.org/Bugzilla:L10n">Bugzilla L10n team</ulink>
+ page.
+ </para>
+ </section>
+
+ <section id="credits">
+ <title>Credits</title>
+ <para>
+ The people listed below have made enormous contributions to the
+ creation of this Guide, through their writing, dedicated hacking efforts,
+ numerous e-mail and IRC support sessions, and overall excellent
+ contribution to the Bugzilla community:
+ </para>
+
+ <!-- TODO: This is evil... there has to be a valid way to get this look -->
+ <variablelist>
+ <varlistentry>
+ <term>Matthew P. Barnson <email>mbarnson@sisna.com</email></term>
+ <listitem>
+ <para>for the Herculean task of pulling together the Bugzilla Guide
+ and shepherding it to 2.14.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>Terry Weissman <email>terry@mozilla.org</email></term>
+ <listitem>
+ <para>for initially writing Bugzilla and creating the README upon
+ which the UNIX installation documentation is largely based.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>Tara Hernandez <email>tara@tequilarists.org</email></term>
+ <listitem>
+ <para>for keeping Bugzilla development going strong after Terry left
+ mozilla.org and for running landfill.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>Dave Lawrence <email>dkl@redhat.com</email></term>
+ <listitem>
+ <para>for providing insight into the key differences between Red
+ Hat's customized Bugzilla.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>Dawn Endico <email>endico@mozilla.org</email></term>
+ <listitem>
+ <para>for being a hacker extraordinaire and putting up with Matthew's
+ incessant questions and arguments on irc.mozilla.org in #mozwebtools
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>Jacob Steenhagen <email>jake@bugzilla.org</email></term>
+ <listitem>
+ <para>for taking over documentation during the 2.17 development
+ period.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>Dave Miller <email>justdave@bugzilla.org</email></term>
+ <listitem>
+ <para>for taking over as project lead when Tara stepped down and
+ continually pushing for the documentation to be the best it can be.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+
+
+ <para>
+ Thanks also go to the following people for significant contributions
+ to this documentation:
+ <simplelist type="inline">
+ <member>Kevin Brannen</member>
+ <member>Vlad Dascalu</member>
+ <member>Ben FrantzDale</member>
+ <member>Eric Hanson</member>
+ <member>Zach Lipton</member>
+ <member>Gervase Markham</member>
+ <member>Andrew Pearson</member>
+ <member>Joe Robins</member>
+ <member>Spencer Smith</member>
+ <member>Ron Teitelbaum</member>
+ <member>Shane Travis</member>
+ <member>Martin Wulffeld</member>
+ </simplelist>.
+ </para>
+
+ <para>
+ Also, thanks are due to the members of the
+ <ulink url="news://news.mozilla.org/mozilla.support.bugzilla">
+ mozilla.support.bugzilla</ulink>
+ newsgroup (and its predecessor, netscape.public.mozilla.webtools).
+ Without your discussions, insight, suggestions, and patches,
+ this could never have happened.
+ </para>
+ </section>
+
+ <!-- conventions used here (didn't want to give it a chapter of its own) -->
+&conventions;
+ </chapter>
+
+<!-- Keep this comment at the end of the file
+Local variables:
+mode: sgml
+sgml-always-quote-attributes:t
+sgml-auto-insert-required-elements:t
+sgml-balanced-tag-edit:t
+sgml-exposed-tags:nil
+sgml-general-insert-case:lower
+sgml-indent-data:t
+sgml-indent-step:2
+sgml-local-catalogs:nil
+sgml-local-ecat-files:nil
+sgml-minimize-attributes:nil
+sgml-namecase-general:t
+sgml-omittag:t
+sgml-parent-document:("Bugzilla-Guide.xml" "book" "chapter")
+sgml-shorttag:t
+sgml-tag-region-if-active:t
+End: -->
+
diff --git a/docs/en/xml/administration.xml b/docs/en/xml/administration.xml
new file mode 100644
index 000000000..c50ccbb3b
--- /dev/null
+++ b/docs/en/xml/administration.xml
@@ -0,0 +1,3029 @@
+<!-- <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook V4.1//EN"> -->
+<chapter id="administration">
+ <title>Administering Bugzilla</title>
+
+ <section id="parameters">
+ <title>Bugzilla Configuration</title>
+
+ <para>
+ Bugzilla is configured by changing various parameters, accessed
+ from the "Parameters" link in the Administration page (the
+ Administration page can be found by clicking the "Administration"
+ link in the footer). The parameters are divided into several categories,
+ accessed via the menu on the left. Following is a description of the
+ different categories and important parameters within those categories.
+ </para>
+
+ <section id="param-requiredsettings">
+ <title>Required Settings</title>
+
+ <para>
+ The core required parameters for any Bugzilla installation are set
+ here. These parameters must be set before a new Bugzilla installation
+ can be used. Administrators should review this list before
+ deploying a new Bugzilla installation.
+ </para>
+
+ <indexterm>
+ <primary>checklist</primary>
+ </indexterm>
+
+ <variablelist>
+
+ <varlistentry>
+ <term>
+ maintainer
+ </term>
+ <listitem>
+ <para>
+ Email address of the person
+ responsible for maintaining this Bugzilla installation.
+ The address need not be that of a valid Bugzilla account.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ urlbase
+ </term>
+ <listitem>
+ <para>
+ Defines the fully qualified domain name and web
+ server path to this Bugzilla installation.
+ </para>
+ <para>
+ For example, if the Bugzilla query page is
+ <filename>http://www.foo.com/bugzilla/query.cgi</filename>,
+ the <quote>urlbase</quote> should be set
+ to <filename>http://www.foo.com/bugzilla/</filename>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ docs_urlbase
+ </term>
+ <listitem>
+ <para>
+ Defines path to the Bugzilla documentation. This can be a fully
+ qualified domain name, or a path relative to "urlbase".
+ </para>
+ <para>
+ For example, if the "Bugzilla Configuration" page
+ of the documentation is
+ <filename>http://www.foo.com/bugzilla/docs/html/parameters.html</filename>,
+ set the <quote>docs_urlbase</quote>
+ to <filename>http://www.foo.com/bugzilla/docs/html/</filename>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ sslbase
+ </term>
+ <listitem>
+ <para>
+ Defines the fully qualified domain name and web
+ server path for HTTPS (SSL) connections to this Bugzilla installation.
+ </para>
+ <para>
+ For example, if the Bugzilla main page is
+ <filename>https://www.foo.com/bugzilla/index.cgi</filename>,
+ the <quote>sslbase</quote> should be set
+ to <filename>https://www.foo.com/bugzilla/</filename>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ ssl_redirect
+ </term>
+ <listitem>
+ <para>
+ If enabled, Bugzilla will force HTTPS (SSL) connections, by
+ automatically redirecting any users who try to use a non-SSL
+ connection.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ cookiedomain
+ </term>
+ <listitem>
+ <para>
+ Defines the domain for Bugzilla cookies. This is typically left blank.
+ If there are multiple hostnames that point to the same webserver, which
+ require the same cookie, then this parameter can be utilized. For
+ example, If your website is at
+ <filename>https://www.foo.com/</filename>, setting this to
+ <filename>.foo.com/</filename> will also allow
+ <filename>bar.foo.com/</filename> to access Bugzilla cookies.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ cookiepath
+ </term>
+ <listitem>
+ <para>
+ Defines a path, relative to the web server root, that Bugzilla
+ cookies will be restricted to. For example, if the
+ <command>urlbase</command> is set to
+ <filename>http://www.foo.com/bugzilla/</filename>, the
+ <command>cookiepath</command> should be set to
+ <filename>/bugzilla/</filename>. Setting it to "/" will allow all sites
+ served by this web server or virtual host to read Bugzilla cookies.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ utf8
+ </term>
+ <listitem>
+ <para>
+ Determines whether to use UTF-8 (Unicode) encoding for all text in
+ Bugzilla. New installations should set this to true to avoid character
+ encoding problems. Existing databases should set this to true only
+ after the data has been converted from existing legacy character
+ encoding to UTF-8, using the
+ <filename>contrib/recode.pl</filename> script.
+ </para>
+ <note>
+ <para>
+ If you turn this parameter from "off" to "on", you must re-run
+ <filename>checksetup.pl</filename> immediately afterward.
+ </para>
+ </note>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ shutdownhtml
+ </term>
+ <listitem>
+ <para>
+ If there is any text in this field, this Bugzilla installation will
+ be completely disabled and this text will appear instead of all
+ Bugzilla pages for all users, including Admins. Used in the event
+ of site maintenance or outage situations.
+ </para>
+ <note>
+ <para>
+ Although regular log-in capability is disabled while
+ <command>shutdownhtml</command>
+ is enabled, safeguards are in place to protect the unfortunate
+ admin who loses connection to Bugzilla. Should this happen to you,
+ go directly to the <filename>editparams.cgi</filename> (by typing
+ the URL in manually, if necessary). Doing this will prompt you to
+ log in, and your name/password will be accepted here (but nowhere
+ else).
+ </para>
+ </note>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ announcehtml
+ </term>
+ <listitem>
+ <para>
+ Any text in this field will be displayed at the top of every HTML
+ page in this Bugzilla installation. The text is not wrapped in any
+ tags. For best results, wrap the text in a <quote>&lt;div&gt;</quote>
+ tag. Any style attributes from the CSS can be applied. For example,
+ to make the text green inside of a red box, add <quote>id=message</quote>
+ to the <quote>&lt;div&gt;</quote> tag.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ proxy_url
+ </term>
+ <listitem>
+ <para>
+ If this Bugzilla installation is behind a proxy, enter the proxy
+ information here to enable Bugzilla to access the Internet. Bugzilla
+ requires Internet access to utilize the
+ <command>upgrade_notification</command> parameter (below). If the
+ proxy requires authentication, use the syntax:
+ <filename>http://user:pass@proxy_url/</filename>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ upgrade_notification
+ </term>
+ <listitem>
+ <para>
+ Enable or disable a notification on the homepage of this Bugzilla
+ installation when a newer version of Bugzilla is available. This
+ notification is only visible to administrators. Choose "disabled",
+ to turn off the notification. Otherwise, choose which version of
+ Bugzilla you want to be notified about: "development_snapshot" is the
+ latest release on the trunk; "latest_stable_release" is the most
+ recent release available on the most recent stable branch;
+ "stable_branch_release" the most recent release on the branch
+ this installation is based on.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+
+ </section>
+
+ <section id="param-admin-policies">
+ <title>Administrative Policies</title>
+ <para>
+ This page contains parameters for basic administrative functions.
+ Options include whether to allow the deletion of bugs and users,
+ and whether to allow users to change their email address.
+ </para>
+ </section>
+
+ <section id="param-user-authentication">
+ <title>User Authentication</title>
+ <para>
+ This page contains the settings that control how this Bugzilla
+ installation will do its authentication. Choose what authentication
+ mechanism to use (the Bugzilla database, or an external source such
+ as LDAP), and set basic behavioral parameters. For example, choose
+ whether to require users to login to browse bugs, the management
+ of authentication cookies, and the regular expression used to
+ validate email addresses. Some parameters are highlighted below.
+ </para>
+
+ <variablelist>
+
+ <varlistentry>
+ <term>
+ emailregexp
+ </term>
+ <listitem>
+ <para>
+ Defines the regular expression used to validate email addresses
+ used for login names. The default attempts to match fully
+ qualified email addresses (i.e. 'user@example.com'). Some
+ Bugzilla installations allow only local user names (i.e 'user'
+ instead of 'user@example.com'). In that case, the
+ <command>emailsuffix</command> parameter should be used to define
+ the email domain.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ emailsuffix
+ </term>
+ <listitem>
+ <para>
+ This string is appended to login names when actually sending
+ email to a user. For example,
+ If <command>emailregexp</command> has been set to allow
+ local usernames,
+ then this parameter would contain the email domain for all users
+ (i.e. '@example.com').
+ </para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+
+ </section>
+
+ <section id="param-attachments">
+ <title>Attachments</title>
+ <para>
+ This page allows for setting restrictions and other parameters
+ regarding attachments to bugs. For example, control size limitations
+ and whether to allow pointing to external files via a URI.
+ </para>
+ </section>
+
+ <section id="param-bug-change-policies">
+ <title>Bug Change Policies</title>
+ <para>
+ Set policy on default behavior for bug change events. For example,
+ choose which status to set a bug to when it is marked as a duplicate,
+ and choose whether to allow bug reporters to set the priority or
+ target milestone. Also allows for configuration of what changes
+ should require the user to make a comment, described below.
+ </para>
+
+ <variablelist>
+
+ <varlistentry>
+ <term>
+ commenton*
+ </term>
+ <listitem>
+ <para>
+ All these fields allow you to dictate what changes can pass
+ without comment, and which must have a comment from the
+ person who changed them. Often, administrators will allow
+ users to add themselves to the CC list, accept bugs, or
+ change the Status Whiteboard without adding a comment as to
+ their reasons for the change, yet require that most other
+ changes come with an explanation.
+ </para>
+
+ <para>
+ Set the "commenton" options according to your site policy. It
+ is a wise idea to require comments when users resolve, reassign, or
+ reopen bugs at the very least.
+ </para>
+
+ <note>
+ <para>
+ It is generally far better to require a developer comment
+ when resolving bugs than not. Few things are more annoying to bug
+ database users than having a developer mark a bug "fixed" without
+ any comment as to what the fix was (or even that it was truly
+ fixed!)
+ </para>
+ </note>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ noresolveonopenblockers
+ </term>
+ <listitem>
+ <para>
+ This option will prevent users from resolving bugs as FIXED if
+ they have unresolved dependencies. Only the FIXED resolution
+ is affected. Users will be still able to resolve bugs to
+ resolutions other than FIXED if they have unresolved dependent
+ bugs.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+
+ </section>
+
+ <section id="param-bugfields">
+ <title>Bug Fields</title>
+ <para>
+ The parameters in this section determine the default settings of
+ several Bugzilla fields for new bugs, and also control whether
+ certain fields are used. For example, choose whether to use the
+ "target milestone" field or the "status whiteboard" field.
+ </para>
+
+ <variablelist>
+
+ <varlistentry>
+ <term>
+ useqacontact
+ </term>
+ <listitem>
+ <para>
+ This allows you to define an email address for each component,
+ in addition to that of the default assignee, who will be sent
+ carbon copies of incoming bugs.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ usestatuswhiteboard
+ </term>
+ <listitem>
+ <para>
+ This defines whether you wish to have a free-form, overwritable field
+ associated with each bug. The advantage of the Status Whiteboard is
+ that it can be deleted or modified with ease, and provides an
+ easily-searchable field for indexing some bugs that have some trait
+ in common.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+
+ </section>
+
+ <section id="param-bugmoving">
+ <title>Bug Moving</title>
+ <para>
+ This page controls whether this Bugzilla installation allows certain
+ users to move bugs to an external database. If bug moving is enabled,
+ there are a number of parameters that control bug moving behaviors.
+ For example, choose which users are allowed to move bugs, the location
+ of the external database, and the default product and component that
+ bugs moved <emphasis>from</emphasis> other bug databases to this
+ Bugzilla installation are assigned to.
+ </para>
+
+ </section>
+
+ <section id="param-dependency-graphs">
+ <title>Dependency Graphs</title>
+ <para>
+ This page has one parameter that sets the location of a Web Dot
+ server, or of the Web Dot binary on the local system, that is used
+ to generate dependency graphs. Web Dot is a CGI program that creates
+ images from <filename>.dot</filename> graphic description files. If
+ no Web Dot server or binary is specified, then dependency graphs will
+ be disabled.
+ </para>
+ </section>
+
+ <section id="param-group-security">
+ <title>Group Security</title>
+ <para>
+ Bugzilla allows for the creation of different groups, with the
+ ability to restrict the visibility of bugs in a group to a set of
+ specific users. Specific products can also be associated with
+ groups, and users restricted to only see products in their groups.
+ Several parameters are described in more detail below. Most of the
+ configuration of groups and their relationship to products is done
+ on the "Groups" and "Product" pages of the "Administration" area.
+ The options on this page control global default behavior.
+ For more information on Groups and Group Security, see
+ <xref linkend="groups"/>
+ </para>
+
+ <variablelist>
+
+ <varlistentry>
+ <term>
+ makeproductgroups
+ </term>
+ <listitem>
+ <para>
+ Determines whether or not to automatically create groups
+ when new products are created. If this is on, the groups will be
+ used for querying bugs.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ usevisibilitygroups
+ </term>
+ <listitem>
+ <para>
+ If selected, user visibility will be restricted to members of
+ groups, as selected in the group configuration settings.
+ Each user-defined group can be allowed to see members of selected
+ other groups.
+ For details on configuring groups (including the visibility
+ restrictions) see <xref linkend="edit-groups"/>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ querysharegroup
+ </term>
+ <listitem>
+ <para>
+ The name of the group of users who are allowed to share saved
+ searches with one another. For more information on using
+ saved searches, see <xref linkend="savedsearches"/>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+
+ </section>
+
+ <section id="bzldap">
+ <title>LDAP Authentication</title>
+
+ <para>LDAP authentication is a module for Bugzilla's plugin
+ authentication architecture. This page contains all the parameters
+ necessary to configure Bugzilla for use with LDAP authentication.
+ </para>
+
+ <para>
+ The existing authentication
+ scheme for Bugzilla uses email addresses as the primary user ID, and a
+ password to authenticate that user. All places within Bugzilla that
+ require a user ID (e.g assigning a bug) use the email
+ address. The LDAP authentication builds on top of this scheme, rather
+ than replacing it. The initial log-in is done with a username and
+ password for the LDAP directory. Bugzilla tries to bind to LDAP using
+ those credentials and, if successful, tries to map this account to a
+ Bugzilla account. If an LDAP mail attribute is defined, the value of this
+ attribute is used, otherwise the "emailsuffix" parameter is appended to LDAP
+ username to form a full email address. If an account for this address
+ already exists in the Bugzilla installation, it will log in to that account.
+ If no account for that email address exists, one is created at the time
+ of login. (In this case, Bugzilla will attempt to use the "displayName"
+ or "cn" attribute to determine the user's full name.) After
+ authentication, all other user-related tasks are still handled by email
+ address, not LDAP username. For example, bugs are still assigned by
+ email address and users are still queried by email address.
+ </para>
+
+ <caution>
+ <para>Because the Bugzilla account is not created until the first time
+ a user logs in, a user who has not yet logged is unknown to Bugzilla.
+ This means they cannot be used as an assignee or QA contact (default or
+ otherwise), added to any CC list, or any other such operation. One
+ possible workaround is the <filename>bugzilla_ldapsync.rb</filename>
+ script in the
+ <glossterm linkend="gloss-contrib">
+ <filename class="directory">contrib</filename></glossterm>
+ directory. Another possible solution is fixing
+ <ulink url="https://bugzilla.mozilla.org/show_bug.cgi?id=201069">bug
+ 201069</ulink>.
+ </para>
+ </caution>
+
+ <para>Parameters required to use LDAP Authentication:</para>
+
+ <variablelist>
+ <varlistentry id="param-user_verify_class_for_ldap">
+ <term>user_verify_class</term>
+ <listitem>
+ <para>If you want to list <quote>LDAP</quote> here,
+ make sure to have set up the other parameters listed below.
+ Unless you have other (working) authentication methods listed as
+ well, you may otherwise not be able to log back in to Bugzilla once
+ you log out.
+ If this happens to you, you will need to manually edit
+ <filename>data/params</filename> and set user_verify_class to
+ <quote>DB</quote>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="param-LDAPserver">
+ <term>LDAPserver</term>
+ <listitem>
+ <para>This parameter should be set to the name (and optionally the
+ port) of your LDAP server. If no port is specified, it assumes
+ the default LDAP port of 389.
+ </para>
+ <para>For example: <quote>ldap.company.com</quote>
+ or <quote>ldap.company.com:3268</quote>
+ </para>
+ <para>You can also specify a LDAP URI, so as to use other
+ protocols, such as LDAPS or LDAPI. If port was not specified in
+ the URI, the default is either 389 or 636 for 'LDAP' and 'LDAPS'
+ schemes respectively.
+ </para>
+ <tip>
+ <para>
+ In order to use SSL with LDAP, specify a URI with "ldaps://".
+ This will force the use of SSL over port 636.
+ </para>
+ </tip>
+ <para>For example, normal LDAP:
+ <quote>ldap://ldap.company.com</quote>, LDAP over SSL:
+ <quote>ldaps://ldap.company.com</quote> or LDAP over a UNIX
+ domain socket <quote>ldapi://%2fvar%2flib%2fldap_sock</quote>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="param-LDAPbinddn">
+ <term>LDAPbinddn [Optional]</term>
+ <listitem>
+ <para>Some LDAP servers will not allow an anonymous bind to search
+ the directory. If this is the case with your configuration you
+ should set the LDAPbinddn parameter to the user account Bugzilla
+ should use instead of the anonymous bind.
+ </para>
+ <para>Ex. <quote>cn=default,cn=user:password</quote></para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="param-LDAPBaseDN">
+ <term>LDAPBaseDN</term>
+ <listitem>
+ <para>The LDAPBaseDN parameter should be set to the location in
+ your LDAP tree that you would like to search for email addresses.
+ Your uids should be unique under the DN specified here.
+ </para>
+ <para>Ex. <quote>ou=People,o=Company</quote></para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="param-LDAPuidattribute">
+ <term>LDAPuidattribute</term>
+ <listitem>
+ <para>The LDAPuidattribute parameter should be set to the attribute
+ which contains the unique UID of your users. The value retrieved
+ from this attribute will be used when attempting to bind as the
+ user to confirm their password.
+ </para>
+ <para>Ex. <quote>uid</quote></para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="param-LDAPmailattribute">
+ <term>LDAPmailattribute</term>
+ <listitem>
+ <para>The LDAPmailattribute parameter should be the name of the
+ attribute which contains the email address your users will enter
+ into the Bugzilla login boxes.
+ </para>
+ <para>Ex. <quote>mail</quote></para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+ </section>
+
+ <section id="bzradius">
+ <title>RADIUS Authentication</title>
+
+ <para>
+ RADIUS authentication is a module for Bugzilla's plugin
+ authentication architecture. This page contains all the parameters
+ necessary for configuring Bugzilla to use RADIUS authentication.
+ </para>
+ <note>
+ <para>
+ Most caveats that apply to LDAP authentication apply to RADIUS
+ authentication as well. See <xref linkend="bzldap"/> for details.
+ </para>
+ </note>
+
+ <para>Parameters required to use RADIUS Authentication:</para>
+
+ <variablelist>
+ <varlistentry id="param-user_verify_class_for_radius">
+ <term>user_verify_class</term>
+ <listitem>
+ <para>If you want to list <quote>RADIUS</quote> here,
+ make sure to have set up the other parameters listed below.
+ Unless you have other (working) authentication methods listed as
+ well, you may otherwise not be able to log back in to Bugzilla once
+ you log out.
+ If this happens to you, you will need to manually edit
+ <filename>data/params</filename> and set user_verify_class to
+ <quote>DB</quote>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="param-RADIUS_server">
+ <term>RADIUS_server</term>
+ <listitem>
+ <para>This parameter should be set to the name (and optionally the
+ port) of your RADIUS server.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="param-RADIUS_secret">
+ <term>RADIUS_secret</term>
+ <listitem>
+ <para>This parameter should be set to the RADIUS server's secret.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="param-RADIUS_email_suffix">
+ <term>RADIUS_email_suffix</term>
+ <listitem>
+ <para>Bugzilla needs an e-mail address for each user account.
+ Therefore, it needs to determine the e-mail address corresponding
+ to a RADIUS user.
+ Bugzilla offers only a simple way to do this: it can concatenate
+ a suffix to the RADIUS user name to convert it into an e-mail
+ address.
+ You can specify this suffix in the RADIUS_email_suffix parameter.
+ </para>
+ <para>If this simple solution does not work for you, you'll
+ probably need to modify
+ <filename>Bugzilla/Auth/Verify/RADIUS.pm</filename> to match your
+ requirements.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+ </section>
+
+ <section id="param-email">
+ <title>Email</title>
+ <para>
+ This page contains all of the parameters for configuring how
+ Bugzilla deals with the email notifications it sends. See below
+ for a summary of important options.
+ </para>
+
+ <variablelist>
+
+ <varlistentry>
+ <term>
+ mail_delivery_method
+ </term>
+ <listitem>
+ <para>
+ This is used to specify how email is sent, or if it is sent at
+ all. There are several options included for different MTAs,
+ along with two additional options that disable email sending.
+ "Test" does not send mail, but instead saves it in
+ <filename>data/mailer.testfile</filename> for later review.
+ "None" disables email sending entirely.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ mailfrom
+ </term>
+ <listitem>
+ <para>
+ This is the email address that will appear in the "From" field
+ of all emails sent by this Bugzilla installation. Some email
+ servers require mail to be from a valid email address, therefore
+ it is recommended to choose a valid email address here.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ whinedays
+ </term>
+ <listitem>
+ <para>
+ Set this to the number of days you want to let bugs go
+ in the CONFIRMED state before notifying people they have
+ untouched new bugs. If you do not plan to use this feature, simply
+ do not set up the whining cron job described in the installation
+ instructions, or set this value to "0" (never whine).
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ globalwatcher
+ </term>
+ <listitem>
+ <para>
+ This allows you to define specific users who will
+ receive notification each time a new bug in entered, or when
+ an existing bug changes, according to the normal groupset
+ permissions. It may be useful for sending notifications to a
+ mailing-list, for instance.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+
+ </section>
+
+ <section id="param-patchviewer">
+ <title>Patch Viewer</title>
+ <para>
+ This page contains configuration parameters for the CVS server,
+ Bonsai server and LXR server that Bugzilla will use to enable the
+ features of the Patch Viewer. Bonsai is a tool that enables queries
+ to a CVS tree. LXR is a tool that can cross reference and index source
+ code.
+ </para>
+ </section>
+
+ <section id="param-querydefaults">
+ <title>Query Defaults</title>
+ <para>
+ This page controls the default behavior of Bugzilla in regards to
+ several aspects of querying bugs. Options include what the default
+ query options are, what the "My Bugs" page returns, whether users
+ can freely add bugs to the quip list, and how many duplicate bugs are
+ needed to add a bug to the "most frequently reported" list.
+ </para>
+ </section>
+
+ <section id="param-shadowdatabase">
+ <title>Shadow Database</title>
+ <para>
+ This page controls whether a shadow database is used, and all the
+ parameters associated with the shadow database. Versions of Bugzilla
+ prior to 3.2 used the MyISAM table type, which supports
+ only table-level write locking. With MyISAM, any time someone is making a change to
+ a bug, the entire table is locked until the write operation is complete.
+ Locking for write also blocks reads until the write is complete.
+ </para>
+ <para>
+ The <quote>shadowdb</quote> parameter was designed to get around
+ this limitation. While only a single user is allowed to write to
+ a table at a time, reads can continue unimpeded on a read-only
+ shadow copy of the database.
+ </para>
+
+ <note>
+ <para>
+ As of version 3.2, Bugzilla no longer uses the MyISAM table type.
+ Instead, InnoDB is used, which can do transaction-based locking.
+ Therefore, the limitations the Shadow Database feature was designed
+ to workaround no longer exist.
+ </para>
+ </note>
+
+ </section>
+
+ <section id="admin-usermatching">
+ <title>User Matching</title>
+ <para>
+ The settings on this page control how users are selected and queried
+ when adding a user to a bug. For example, users need to be selected
+ when choosing who the bug is assigned to, adding to the CC list or
+ selecting a QA contact. With the "usemenuforusers" parameter, it is
+ possible to configure Bugzilla to
+ display a list of users in the fields instead of an empty text field.
+ This should only be used in Bugzilla installations with a small number
+ of users. If users are selected via a text box, this page also
+ contains parameters for how user names can be queried and matched
+ when entered.
+ </para>
+
+ </section>
+
+ </section>
+
+ <section id="useradmin">
+ <title>User Administration</title>
+
+ <section id="defaultuser">
+ <title>Creating the Default User</title>
+
+ <para>When you first run checksetup.pl after installing Bugzilla, it
+ will prompt you for the administrative username (email address) and
+ password for this "super user". If for some reason you delete
+ the "super user" account, re-running checksetup.pl will again prompt
+ you for this username and password.</para>
+
+ <tip>
+ <para>If you wish to add more administrative users, add them to
+ the "admin" group and, optionally, edit the tweakparams, editusers,
+ creategroups, editcomponents, and editkeywords groups to add the
+ entire admin group to those groups (which is the case by default).
+ </para>
+ </tip>
+ </section>
+
+ <section id="manageusers">
+ <title>Managing Other Users</title>
+
+ <section id="user-account-search">
+ <title>Searching for existing users</title>
+
+ <para>
+ If you have <quote>editusers</quote> privileges or if you are allowed
+ to grant privileges for some groups, the <quote>Users</quote> link
+ will appear in the Administration page.
+ </para>
+
+ <para>
+ The first screen is a search form to search for existing user
+ accounts. You can run searches based either on the user ID, real
+ name or login name (i.e. the email address, or just the first part
+ of the email address if the "emailsuffix" parameter is set).
+ The search can be conducted
+ in different ways using the listbox to the right of the text entry
+ box. You can match by case-insensitive substring (the default),
+ regular expression, a <emphasis>reverse</emphasis> regular expression
+ match (which finds every user name which does NOT match the regular
+ expression), or the exact string if you know exactly who you are
+ looking for. The search can be restricted to users who are in a
+ specific group. By default, the restriction is turned off.
+ </para>
+
+ <para>
+ The search returns a list of
+ users matching your criteria. User properties can be edited by clicking
+ the login name. The Account History of a user can be viewed by clicking
+ the "View" link in the Account History column. The Account History
+ displays changes that have been made to the user account, the time of
+ the change and the user who made the change. For example, the Account
+ History page will display details of when a user was added or removed
+ from a group.
+ </para>
+ </section>
+
+ <section id="createnewusers">
+ <title>Creating new users</title>
+
+ <section id="self-registration">
+ <title>Self-registration</title>
+
+ <para>
+ By default, users can create their own user accounts by clicking the
+ <quote>New Account</quote> link at the bottom of each page (assuming
+ they aren't logged in as someone else already). If you want to disable
+ this self-registration, or if you want to restrict who can create his
+ own user account, you have to edit the <quote>createemailregexp</quote>
+ parameter in the <quote>Configuration</quote> page, see
+ <xref linkend="parameters" />.
+ </para>
+ </section>
+
+ <section id="user-account-creation">
+ <title>Accounts created by an administrator</title>
+
+ <para>
+ Users with <quote>editusers</quote> privileges, such as administrators,
+ can create user accounts for other users:
+ </para>
+
+ <orderedlist>
+ <listitem>
+ <para>After logging in, click the "Users" link at the footer of
+ the query page, and then click "Add a new user".</para>
+ </listitem>
+
+ <listitem>
+ <para>Fill out the form presented. This page is self-explanatory.
+ When done, click "Submit".</para>
+
+ <note>
+ <para>Adding a user this way will <emphasis>not</emphasis>
+ send an email informing them of their username and password.
+ While useful for creating dummy accounts (watchers which
+ shuttle mail to another system, for instance, or email
+ addresses which are a mailing list), in general it is
+ preferable to log out and use the <quote>New Account</quote>
+ button to create users, as it will pre-populate all the
+ required fields and also notify the user of her account name
+ and password.</para>
+ </note>
+ </listitem>
+ </orderedlist>
+ </section>
+ </section>
+
+ <section id="modifyusers">
+ <title>Modifying Users</title>
+
+ <para>Once you have found your user, you can change the following
+ fields:</para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <emphasis>Login Name</emphasis>:
+ This is generally the user's full email address. However, if you
+ have are using the <quote>emailsuffix</quote> parameter, this may
+ just be the user's login name. Note that users can now change their
+ login names themselves (to any valid email address).
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <emphasis>Real Name</emphasis>: The user's real name. Note that
+ Bugzilla does not require this to create an account.</para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <emphasis>Password</emphasis>:
+ You can change the user's password here. Users can automatically
+ request a new password, so you shouldn't need to do this often.
+ If you want to disable an account, see Disable Text below.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <emphasis>Bugmail Disabled</emphasis>:
+ Mark this checkbox to disable bugmail and whinemail completely
+ for this account. This checkbox replaces the data/nomail file
+ which existed in older versions of Bugzilla.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <emphasis>Disable Text</emphasis>:
+ If you type anything in this box, including just a space, the
+ user is prevented from logging in, or making any changes to
+ bugs via the web interface.
+ The HTML you type in this box is presented to the user when
+ they attempt to perform these actions, and should explain
+ why the account was disabled.
+ </para>
+ <para>
+ Users with disabled accounts will continue to receive
+ mail from Bugzilla; furthermore, they will not be able
+ to log in themselves to change their own preferences and
+ stop it. If you want an account (disabled or active) to
+ stop receiving mail, simply check the
+ <quote>Bugmail Disabled</quote> checkbox above.
+ </para>
+ <note>
+ <para>
+ Even users whose accounts have been disabled can still
+ submit bugs via the e-mail gateway, if one exists.
+ The e-mail gateway should <emphasis>not</emphasis> be
+ enabled for secure installations of Bugzilla.
+ </para>
+ </note>
+ <warning>
+ <para>
+ Don't disable all the administrator accounts!
+ </para>
+ </warning>
+ </listitem>
+
+ <listitem>
+ <para>
+ <emphasis>&lt;groupname&gt;</emphasis>:
+ If you have created some groups, e.g. "securitysensitive", then
+ checkboxes will appear here to allow you to add users to, or
+ remove them from, these groups. The first checkbox gives the
+ user the ability to add and remove other users as members of
+ this group. The second checkbox adds the user himself as a member
+ of the group.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <emphasis>canconfirm</emphasis>:
+ This field is only used if you have enabled the "unconfirmed"
+ status. If you enable this for a user,
+ that user can then move bugs from "Unconfirmed" to a "Confirmed"
+ status (e.g.: "New" status).</para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <emphasis>creategroups</emphasis>:
+ This option will allow a user to create and destroy groups in
+ Bugzilla.</para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <emphasis>editbugs</emphasis>:
+ Unless a user has this bit set, they can only edit those bugs
+ for which they are the assignee or the reporter. Even if this
+ option is unchecked, users can still add comments to bugs.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <emphasis>editcomponents</emphasis>:
+ This flag allows a user to create new products and components,
+ as well as modify and destroy those that have no bugs associated
+ with them. If a product or component has bugs associated with it,
+ those bugs must be moved to a different product or component
+ before Bugzilla will allow them to be destroyed.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <emphasis>editkeywords</emphasis>:
+ If you use Bugzilla's keyword functionality, enabling this
+ feature allows a user to create and destroy keywords. As always,
+ the keywords for existing bugs containing the keyword the user
+ wishes to destroy must be changed before Bugzilla will allow it
+ to die.</para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <emphasis>editusers</emphasis>:
+ This flag allows a user to do what you're doing right now: edit
+ other users. This will allow those with the right to do so to
+ remove administrator privileges from other users or grant them to
+ themselves. Enable with care.</para>
+ </listitem>
+
+
+ <listitem>
+ <para>
+ <emphasis>tweakparams</emphasis>:
+ This flag allows a user to change Bugzilla's Params
+ (using <filename>editparams.cgi</filename>.)</para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <emphasis>&lt;productname&gt;</emphasis>:
+ This allows an administrator to specify the products
+ in which a user can see bugs. If you turn on the
+ <quote>makeproductgroups</quote> parameter in
+ the Group Security Panel in the Parameters page,
+ then Bugzilla creates one group per product (at the time you create
+ the product), and this group has exactly the same name as the
+ product itself. Note that for products that already exist when
+ the parameter is turned on, the corresponding group will not be
+ created. The user must still have the <quote>editbugs</quote>
+ privilege to edit bugs in these products.</para>
+ </listitem>
+ </itemizedlist>
+ </section>
+
+ <section id="user-account-deletion">
+ <title>Deleting Users</title>
+ <para>
+ If the <quote>allowuserdeletion</quote> parameter is turned on, see
+ <xref linkend="parameters" />, then you can also delete user accounts.
+ Note that this is most of the time not the best thing to do. If only
+ a warning in a yellow box is displayed, then the deletion is safe.
+ If a warning is also displayed in a red box, then you should NOT try
+ to delete the user account, else you will get referential integrity
+ problems in your database, which can lead to unexpected behavior,
+ such as bugs not appearing in bug lists anymore, or data displaying
+ incorrectly. You have been warned!
+ </para>
+ </section>
+
+ <section id="impersonatingusers">
+ <title>Impersonating Users</title>
+
+ <para>
+ There may be times when an administrator would like to do something as
+ another user. The <command>sudo</command> feature may be used to do
+ this.
+ </para>
+
+ <note>
+ <para>
+ To use the sudo feature, you must be in the
+ <emphasis>bz_sudoers</emphasis> group. By default, all
+ administrators are in this group.</para>
+ </note>
+
+ <para>
+ If you have access to this feature, you may start a session by
+ going to the Edit Users page, Searching for a user and clicking on
+ their login. You should see a link below their login name titled
+ "Impersonate this user". Click on the link. This will take you
+ to a page where you will see a description of the feature and
+ instructions for using it. After reading the text, simply
+ enter the login of the user you would like to impersonate, provide
+ a short message explaining why you are doing this, and press the
+ button.</para>
+
+ <para>
+ As long as you are using this feature, everything you do will be done
+ as if you were logged in as the user you are impersonating.</para>
+
+ <warning>
+ <para>
+ The user you are impersonating will not be told about what you are
+ doing. If you do anything that results in mail being sent, that
+ mail will appear to be from the user you are impersonating. You
+ should be extremely careful while using this feature.</para>
+ </warning>
+ </section>
+ </section>
+ </section>
+
+ <section id="classifications" xreflabel="Classifications">
+ <title>Classifications</title>
+
+ <para>Classifications tend to be used in order to group several related
+ products into one distinct entity.</para>
+
+ <para>The classifications layer is disabled by default; it can be turned
+ on or off using the useclassification parameter,
+ in the <emphasis>Bug Fields</emphasis> section of the edit parameters screen.</para>
+
+ <para>Access to the administration of classifications is controlled using
+ the <emphasis>editclassifications</emphasis> system group, which defines
+ a privilege for creating, destroying, and editing classifications.</para>
+
+ <para>When activated, classifications will introduce an additional
+ step when filling bugs (dedicated to classification selection), and they
+ will also appear in the advanced search form.</para>
+ </section>
+
+ <section id="products" xreflabel="Products">
+ <title>Products</title>
+
+ <para>
+ <glossterm linkend="gloss-product" baseform="product">
+ Products</glossterm> typically represent real-world
+ shipping products. Products can be given
+ <xref linkend="classifications"/>.
+ For example, if a company makes computer games,
+ they could have a classification of "Games", and a separate
+ product for each game. This company might also have a
+ <quote>Common</quote> product for units of technology used
+ in multiple games, and perhaps a few special products that
+ represent items that are not actually shipping products
+ (for example, "Website", or "Administration").
+ </para>
+
+ <para>
+ Many of Bugzilla's settings are configurable on a per-product
+ basis. The number of <quote>votes</quote> available to
+ users is set per-product, as is the number of votes
+ required to move a bug automatically from the UNCONFIRMED
+ status to the CONFIRMED status.
+ </para>
+
+ <para>
+ When creating or editing products the following options are
+ available:
+ </para>
+
+ <variablelist>
+
+ <varlistentry>
+ <term>
+ Product
+ </term>
+ <listitem>
+ <para>
+ The name of the product
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ Description
+ </term>
+ <listitem>
+ <para>
+ A brief description of the product
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ Default milestone
+ </term>
+ <listitem>
+ <para>
+ Select the default milestone for this product.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ Closed for bug entry
+ </term>
+ <listitem>
+ <para>
+ Select this box to prevent new bugs from being
+ entered against this product.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ Maximum votes per person
+ </term>
+ <listitem>
+ <para>
+ Maximum votes a user is allowed to give for this
+ product
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ Maximum votes a person can put on a single bug
+ </term>
+ <listitem>
+ <para>
+ Maximum votes a user is allowed to give for this
+ product in a single bug
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ Confirmation threshold
+ </term>
+ <listitem>
+ <para>
+ Number of votes needed to automatically remove any
+ bug against this product from the UNCONFIRMED state
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ Version
+ </term>
+ <listitem>
+ <para>
+ Specify which version of the product bugs will be
+ entered against.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ Create chart datasets for this product
+ </term>
+ <listitem>
+ <para>
+ Select to make chart datasets available for this product.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+
+ <para>
+ When editing a product there is also a link to edit Group Access Controls,
+ see <xref linkend="product-group-controls"/>.
+ </para>
+
+ <section id="create-product">
+ <title>Creating New Products</title>
+
+ <para>
+ To create a new product:
+ </para>
+
+ <orderedlist>
+ <listitem>
+ <para>
+ Select <quote>Administration</quote> from the footer and then
+ choose <quote>Products</quote> from the main administration page.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Select the <quote>Add</quote> link in the bottom right.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Enter the name of the product and a description. The
+ Description field may contain HTML.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ When the product is created, Bugzilla will give a message
+ stating that a component must be created before any bugs can
+ be entered against the new product. Follow the link to create
+ a new component. See <xref linkend="components"/> for more
+ information.
+ </para>
+ </listitem>
+ </orderedlist>
+
+ </section>
+
+ <section id="edit-products">
+ <title>Editing Products</title>
+
+ <para>
+ To edit an existing product, click the "Products" link from the
+ "Administration" page. If the 'useclassification' parameter is
+ turned on, a table of existing classifications is displayed,
+ including an "Unclassified" category. The table indicates how many products
+ are in each classification. Click on the classification name to see its
+ products. If the 'useclassification' parameter is not in use, the table
+ lists all products directly. The product table summarizes the information
+ about the product defined
+ when the product was created. Click on the product name to edit these
+ properties, and to access links to other product attributes such as the
+ product's components, versions, milestones, and group access controls.
+ </para>
+
+ </section>
+
+ <section id="comps-vers-miles-products">
+ <title>Adding or Editing Components, Versions and Target Milestones</title>
+ <para>
+ To edit existing, or add new, Components, Versions or Target Milestones
+ to a Product, select the "Edit Components", "Edit Versions" or "Edit
+ Milestones" links from the "Edit Product" page. A table of existing
+ Components, Versions or Milestones is displayed. Click on a item name
+ to edit the properties of that item. Below the table is a link to add
+ a new Component, Version or Milestone.
+ </para>
+ <para>
+ For more information on components, see <xref linkend="components"/>.
+ </para>
+ <para>
+ For more information on versions, see <xref linkend="versions"/>.
+ </para>
+ <para>
+ For more information on milestones, see <xref linkend="milestones"/>.
+ </para>
+ </section>
+
+ <section id="product-group-controls">
+ <title>Assigning Group Controls to Products</title>
+
+ <para>
+ On the <quote>Edit Product</quote> page, there is a link called
+ <quote>Edit Group Access Controls</quote>. The settings on this page
+ control the relationship of the groups to the product being edited.
+ </para>
+
+ <para>
+ Group Access Controls are an important aspect of using groups for
+ isolating products and restricting access to bugs filed against those
+ products. For more information on groups, including how to create, edit
+ add users to, and alter permission of, see <xref linkend="groups"/>.
+ </para>
+
+ <para>
+ After selecting the "Edit Group Access Controls" link from the "Edit
+ Product" page, a table containing all user-defined groups for this
+ Bugzilla installation is displayed. The system groups that are created
+ when Bugzilla is installed are not applicable to Group Access Controls.
+ Below is description of what each of these fields means.
+ </para>
+
+ <para>
+ Groups may be applicable (e.g bugs in this product can be associated
+ with this group) , default (e.g. bugs in this product are in this group
+ by default), and mandatory (e.g. bugs in this product must be associated
+ with this group) for each product. Groups can also control access
+ to bugs for a given product, or be used to make bugs for a product
+ totally read-only unless the group restrictions are met. The best way to
+ understand these relationships is by example. See
+ <xref linkend="group-control-examples"/> for examples of
+ product and group relationships.
+ </para>
+
+ <note>
+ <para>
+ Products and Groups are not limited to a one-to-one relationship.
+ Multiple groups can be associated with the same product, and groups
+ can be associated with more than one product.
+ </para>
+ </note>
+
+ <para>
+ If any group has <emphasis>Entry</emphasis> selected, then the
+ product will restrict bug entry to only those users
+ who are members of <emphasis>all</emphasis> the groups with
+ <emphasis>Entry</emphasis> selected.
+ </para>
+
+ <para>
+ If any group has <emphasis>Canedit</emphasis> selected,
+ then the product will be read-only for any users
+ who are not members of <emphasis>all</emphasis> of the groups with
+ <emphasis>Canedit</emphasis> selected. <emphasis>Only</emphasis> users who
+ are members of all the <emphasis>Canedit</emphasis> groups
+ will be able to edit bugs for this product. This is an additional
+ restriction that enables finer-grained control over products rather
+ than just all-or-nothing access levels.
+ </para>
+
+ <para>
+ The following settings let you
+ choose privileges on a <emphasis>per-product basis</emphasis>.
+ This is a convenient way to give privileges to
+ some users for some products only, without having
+ to give them global privileges which would affect
+ all products.
+ </para>
+
+ <para>
+ Any group having <emphasis>editcomponents</emphasis>
+ selected allows users who are in this group to edit all
+ aspects of this product, including components, milestones
+ and versions.
+ </para>
+
+ <para>
+ Any group having <emphasis>canconfirm</emphasis> selected
+ allows users who are in this group to confirm bugs
+ in this product.
+ </para>
+
+ <para>
+ Any group having <emphasis>editbugs</emphasis> selected allows
+ users who are in this group to edit all fields of
+ bugs in this product.
+ </para>
+
+ <para>
+ The <emphasis>MemberControl</emphasis> and
+ <emphasis>OtherControl</emphasis> are used in tandem to determine which
+ bugs will be placed in this group. The only allowable combinations of
+ these two parameters are listed in a table on the "Edit Group Access Controls"
+ page. Consult this table for details on how these fields can be used.
+ Examples of different uses are described below.
+ </para>
+
+ <section id="group-control-examples">
+ <title>Common Applications of Group Controls</title>
+
+ <para>
+ The use of groups is best explained by providing examples that illustrate
+ configurations for common use cases. The examples follow a common syntax:
+ <emphasis>Group: Entry, MemberControl, OtherControl, CanEdit,
+ EditComponents, CanConfirm, EditBugs</emphasis>. Where "Group" is the name
+ of the group being edited for this product. The other fields all
+ correspond to the table on the "Edit Group Access Controls" page. If any
+ of these options are not listed, it means they are not checked.
+ </para>
+
+ <para>
+ Basic Product/Group Restriction
+ </para>
+
+ <para>
+ Suppose there is a product called "Bar". The
+ "Bar" product can only have bugs entered against it by users in the
+ group "Foo". Additionally, bugs filed against product "Bar" must stay
+ restricted to users to "Foo" at all times. Furthermore, only members
+ of group "Foo" can edit bugs filed against product "Bar", even if other
+ users could see the bug. This arrangement would achieved by the
+ following:
+ </para>
+
+ <programlisting>
+Product Bar:
+foo: ENTRY, MANDATORY/MANDATORY, CANEDIT
+ </programlisting>
+
+ <para>
+ Perhaps such strict restrictions are not needed for product "Bar". A
+ more lenient way to configure product "Bar" and group "Foo" would be:
+ </para>
+
+ <programlisting>
+Product Bar:
+foo: ENTRY, SHOWN/SHOWN, EDITCOMPONENTS, CANCONFIRM, EDITBUGS
+ </programlisting>
+
+ <para>
+ The above indicates that for product "Bar", members of group "Foo" can
+ enter bugs. Any one with permission to edit a bug against product "Bar"
+ can put the bug
+ in group "Foo", even if they themselves are not in "Foo". Anyone in group
+ "Foo" can edit all aspects of the components of product "Bar", can confirm
+ bugs against product "Bar", and can edit all fields of any bug against
+ product "Bar".
+ </para>
+
+ <para>
+ General User Access With Security Group
+ </para>
+
+ <para>
+ To permit any user to file bugs against "Product A",
+ and to permit any user to submit those bugs into a
+ group called "Security":
+ </para>
+
+ <programlisting>
+Product A:
+security: SHOWN/SHOWN
+ </programlisting>
+
+ <para>
+ General User Access With A Security Product
+ </para>
+
+ <para>
+ To permit any user to file bugs against product called "Security"
+ while keeping those bugs from becoming visible to anyone
+ outside the group "SecurityWorkers" (unless a member of the
+ "SecurityWorkers" group removes that restriction):
+ </para>
+
+ <programlisting>
+Product Security:
+securityworkers: DEFAULT/MANDATORY
+ </programlisting>
+
+ <para>
+ Product Isolation With a Common Group
+ </para>
+
+ <para>
+ To permit users of "Product A" to access the bugs for
+ "Product A", users of "Product B" to access the bugs for
+ "Product B", and support staff, who are members of the "Support
+ Group" to access both, three groups are needed:
+ </para>
+
+ <orderedlist>
+
+ <listitem>
+ <para>Support Group: Contains members of the support staff.</para>
+ </listitem>
+
+ <listitem>
+ <para>AccessA Group: Contains users of product A and the Support group.</para>
+ </listitem>
+
+ <listitem>
+ <para>AccessB Group: Contains users of product B and the Support group.</para>
+ </listitem>
+
+ </orderedlist>
+
+ <para>
+ Once these three groups are defined, the product group controls
+ can be set to:
+ </para>
+
+ <programlisting>
+Product A:
+AccessA: ENTRY, MANDATORY/MANDATORY
+Product B:
+AccessB: ENTRY, MANDATORY/MANDATORY
+ </programlisting>
+
+ <para>
+ Perhaps the "Support Group" wants more control. For example,
+ the "Support Group" could be permitted to make bugs inaccessible to
+ users of both groups "AccessA" and "AccessB".
+ Then, the "Support Group" could be permitted to publish
+ bugs relevant to all users in a third product (let's call it
+ "Product Common") that is read-only
+ to anyone outside the "Support Group". In this way the "Support Group"
+ could control bugs that should be seen by both groups.
+ That configuration would be:
+ </para>
+
+ <programlisting>
+Product A:
+AccessA: ENTRY, MANDATORY/MANDATORY
+Support: SHOWN/NA
+Product B:
+AccessB: ENTRY, MANDATORY/MANDATORY
+Support: SHOWN/NA
+Product Common:
+Support: ENTRY, DEFAULT/MANDATORY, CANEDIT
+ </programlisting>
+
+ <para>
+ Make a Product Read Only
+ </para>
+
+ <para>
+ Sometimes a product is retired and should no longer have
+ new bugs filed against it (for example, an older version of a software
+ product that is no longer supported). A product can be made read-only
+ by creating a group called "readonly" and adding products to the
+ group as needed:
+ </para>
+
+ <programlisting>
+Product A:
+ReadOnly: ENTRY, NA/NA, CANEDIT
+ </programlisting>
+
+ <note>
+ <para>
+ For more information on Groups outside of how they relate to products
+ see <xref linkend="groups"/>.
+ </para>
+ </note>
+
+ </section>
+
+ </section>
+
+ </section>
+
+ <section id="components" xreflabel="Components">
+ <title>Components</title>
+
+ <para>Components are subsections of a Product. E.g. the computer game
+ you are designing may have a "UI"
+ component, an "API" component, a "Sound System" component, and a
+ "Plugins" component, each overseen by a different programmer. It
+ often makes sense to divide Components in Bugzilla according to the
+ natural divisions of responsibility within your Product or
+ company.</para>
+
+ <para>
+ Each component has a default assignee and (if you turned it on in the parameters),
+ a QA Contact. The default assignee should be the primary person who fixes bugs in
+ that component. The QA Contact should be the person who will ensure
+ these bugs are completely fixed. The Assignee, QA Contact, and Reporter
+ will get email when new bugs are created in this Component and when
+ these bugs change. Default Assignee and Default QA Contact fields only
+ dictate the
+ <emphasis>default assignments</emphasis>;
+ these can be changed on bug submission, or at any later point in
+ a bug's life.</para>
+
+ <para>To create a new Component:</para>
+
+ <orderedlist>
+ <listitem>
+ <para>Select the <quote>Edit components</quote> link
+ from the <quote>Edit product</quote> page</para>
+ </listitem>
+
+ <listitem>
+ <para>Select the <quote>Add</quote> link in the bottom right.</para>
+ </listitem>
+
+ <listitem>
+ <para>Fill out the <quote>Component</quote> field, a
+ short <quote>Description</quote>, the
+ <quote>Default Assignee</quote>, <quote>Default CC List</quote>
+ and <quote>Default QA Contact</quote> (if enabled).
+ The <quote>Component Description</quote> field may contain a
+ limited subset of HTML tags. The <quote>Default Assignee</quote>
+ field must be a login name already existing in the Bugzilla database.
+ </para>
+ </listitem>
+ </orderedlist>
+ </section>
+
+ <section id="versions">
+ <title>Versions</title>
+
+ <para>Versions are the revisions of the product, such as "Flinders
+ 3.1", "Flinders 95", and "Flinders 2000". Version is not a multi-select
+ field; the usual practice is to select the earliest version known to have
+ the bug.
+ </para>
+
+ <para>To create and edit Versions:</para>
+
+ <orderedlist>
+ <listitem>
+ <para>From the "Edit product" screen, select "Edit Versions"</para>
+ </listitem>
+
+ <listitem>
+ <para>You will notice that the product already has the default
+ version "undefined". Click the "Add" link in the bottom right.</para>
+ </listitem>
+
+ <listitem>
+ <para>Enter the name of the Version. This field takes text only.
+ Then click the "Add" button.</para>
+ </listitem>
+
+ </orderedlist>
+ </section>
+
+ <section id="milestones">
+ <title>Milestones</title>
+
+ <para>Milestones are "targets" that you plan to get a bug fixed by. For
+ example, you have a bug that you plan to fix for your 3.0 release, it
+ would be assigned the milestone of 3.0.</para>
+
+ <note>
+ <para>Milestone options will only appear for a Product if you turned
+ on the "usetargetmilestone" parameter in the "Bug Fields" tab of the
+ "Parameters" page.
+ </para>
+ </note>
+
+ <para>To create new Milestones, and set Default Milestones:</para>
+
+ <orderedlist>
+ <listitem>
+ <para>Select "Edit milestones" from the "Edit product" page.</para>
+ </listitem>
+
+ <listitem>
+ <para>Select "Add" in the bottom right corner.</para>
+ </listitem>
+
+ <listitem>
+ <para>Enter the name of the Milestone in the "Milestone" field. You
+ can optionally set the "sortkey", which is a positive or negative
+ number (-32768 to 32767) that defines where in the list this particular
+ milestone appears. This is because milestones often do not
+ occur in alphanumeric order For example, "Future" might be
+ after "Release 1.2". Select "Add".</para>
+ </listitem>
+ </orderedlist>
+ </section>
+
+ <section id="flags-overview">
+ <title>Flags</title>
+
+ <para>
+ Flags are a way to attach a specific status to a bug or attachment,
+ either <quote>+</quote> or <quote>-</quote>. The meaning of these symbols depends on the text
+ the flag itself, but contextually they could mean pass/fail,
+ accept/reject, approved/denied, or even a simple yes/no. If your site
+ allows requestable flags, then users may set a flag to <quote>?</quote> as a
+ request to another user that they look at the bug/attachment, and set
+ the flag to its correct status.
+ </para>
+
+ <section id="flags-simpleexample">
+ <title>A Simple Example</title>
+
+ <para>
+ A developer might want to ask their manager,
+ <quote>Should we fix this bug before we release version 2.0?</quote>
+ They might want to do this for a <emphasis>lot</emphasis> of bugs,
+ so it would be nice to streamline the process...
+ </para>
+ <para>
+ In Bugzilla, it would work this way:
+ <orderedlist>
+ <listitem>
+ <para>
+ The Bugzilla administrator creates a flag type called
+ <quote>blocking2.0</quote> that shows up on all bugs in
+ your product.
+ </para>
+
+ <para>
+ It shows up on the <quote>Show Bug</quote> screen
+ as the text <quote>blocking2.0</quote> with a drop-down box next
+ to it. The drop-down box contains four values: an empty space,
+ <quote>?</quote>, <quote>-</quote>, and <quote>+</quote>.
+ </para>
+ </listitem>
+ <listitem>
+ <para>The developer sets the flag to <quote>?</quote>.</para>
+ </listitem>
+ <listitem>
+ <para>
+ The manager sees the <computeroutput>blocking2.0</computeroutput>
+ flag with a <quote>?</quote> value.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ If the manager thinks the feature should go into the product
+ before version 2.0 can be released, he sets the flag to
+ <quote>+</quote>. Otherwise, he sets it to <quote>-</quote>.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Now, every Bugzilla user who looks at the bug knows whether or
+ not the bug needs to be fixed before release of version 2.0.
+ </para>
+ </listitem>
+ </orderedlist>
+ </para>
+
+ </section>
+
+ <section id="flags-about">
+ <title>About Flags</title>
+
+ <section id="flag-values">
+ <title>Values</title>
+ <para>
+ Flags can have three values:
+ <variablelist>
+ <varlistentry>
+ <term><computeroutput>?</computeroutput></term>
+ <listitem><simpara>
+ A user is requesting that a status be set. (Think of it as 'A question is being asked'.)
+ </simpara></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><computeroutput>-</computeroutput></term>
+ <listitem><simpara>
+ The status has been set negatively. (The question has been answered <quote>no</quote>.)
+ </simpara></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><computeroutput>+</computeroutput></term>
+ <listitem><simpara>
+ The status has been set positively.
+ (The question has been answered <quote>yes</quote>.)
+ </simpara></listitem>
+ </varlistentry>
+ </variablelist>
+ </para>
+ <para>
+ Actually, there's a fourth value a flag can have --
+ <quote>unset</quote> -- which shows up as a blank space. This
+ just means that nobody has expressed an opinion (or asked
+ someone else to express an opinion) about this bug or attachment.
+ </para>
+ </section>
+ </section>
+
+ <section id="flag-askto">
+ <title>Using flag requests</title>
+ <para>
+ If a flag has been defined as 'requestable', and a user has enough privileges
+ to request it (see below), the user can set the flag's status to <quote>?</quote>.
+ This status indicates that someone (a.k.a. <quote>the requester</quote>) is asking
+ someone else to set the flag to either <quote>+</quote> or <quote>-</quote>.
+ </para>
+ <para>
+ If a flag has been defined as 'specifically requestable',
+ a text box will appear next to the flag into which the requester may
+ enter a Bugzilla username. That named person (a.k.a. <quote>the requestee</quote>)
+ will receive an email notifying them of the request, and pointing them
+ to the bug/attachment in question.
+ </para>
+ <para>
+ If a flag has <emphasis>not</emphasis> been defined as 'specifically requestable',
+ then no such text-box will appear. A request to set this flag cannot be made of
+ any specific individual, but must be asked <quote>to the wind</quote>.
+ A requester may <quote>ask the wind</quote> on any flag simply by leaving the text-box blank.
+ </para>
+ </section>
+
+ <section id="flag-types">
+ <title>Two Types of Flags</title>
+
+ <para>
+ Flags can go in two places: on an attachment, or on a bug.
+ </para>
+
+ <section id="flag-type-attachment">
+ <title>Attachment Flags</title>
+
+ <para>
+ Attachment flags are used to ask a question about a specific
+ attachment on a bug.
+ </para>
+ <para>
+ Many Bugzilla installations use this to
+ request that one developer <quote>review</quote> another
+ developer's code before they check it in. They attach the code to
+ a bug report, and then set a flag on that attachment called
+ <quote>review</quote> to
+ <computeroutput>review?boss@domain.com</computeroutput>.
+ boss@domain.com is then notified by email that
+ he has to check out that attachment and approve it or deny it.
+ </para>
+
+ <para>
+ For a Bugzilla user, attachment flags show up in three places:
+ <orderedlist>
+ <listitem>
+ <para>
+ On the list of attachments in the <quote>Show Bug</quote>
+ screen, you can see the current state of any flags that
+ have been set to ?, +, or -. You can see who asked about
+ the flag (the requester), and who is being asked (the
+ requestee).
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ When you <quote>Edit</quote> an attachment, you can
+ see any settable flag, along with any flags that have
+ already been set. This <quote>Edit Attachment</quote>
+ screen is where you set flags to ?, -, +, or unset them.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Requests are listed in the <quote>Request Queue</quote>, which
+ is accessible from the <quote>My Requests</quote> link (if you are
+ logged in) or <quote>Requests</quote> link (if you are logged out)
+ visible in the footer of all pages.
+ </para>
+ </listitem>
+ </orderedlist>
+ </para>
+
+ </section>
+
+ <section id="flag-type-bug">
+ <title>Bug Flags</title>
+
+ <para>
+ Bug flags are used to set a status on the bug itself. You can
+ see Bug Flags in the <quote>Show Bug</quote> and <quote>Requests</quote>
+ screens, as described above.
+ </para>
+ <para>
+ Only users with enough privileges (see below) may set flags on bugs.
+ This doesn't necessarily include the assignee, reporter, or users with the
+ <computeroutput>editbugs</computeroutput> permission.
+ </para>
+ </section>
+
+ </section>
+
+ <section id="flags-admin">
+ <title>Administering Flags</title>
+
+ <para>
+ If you have the <quote>editcomponents</quote> permission, you can
+ edit Flag Types from the main administration page. Clicking the
+ <quote>Flags</quote> link will bring you to the <quote>Administer
+ Flag Types</quote> page. Here, you can select whether you want
+ to create (or edit) a Bug flag, or an Attachment flag.
+ </para>
+ <para>
+ No matter which you choose, the interface is the same, so we'll
+ just go over it once.
+ </para>
+
+ <section id="flags-edit">
+ <title>Editing a Flag</title>
+ <para>
+ To edit a flag's properties, just click the flag's name.
+ That will take you to the same
+ form as described below (<xref linkend="flags-create"/>).
+ </para>
+ </section>
+
+ <section id="flags-create">
+ <title>Creating a Flag</title>
+
+ <para>
+ When you click on the <quote>Create a Flag Type for...</quote>
+ link, you will be presented with a form. Here is what the fields in
+ the form mean:
+ </para>
+
+ <section id="flags-create-field-name">
+ <title>Name</title>
+ <para>
+ This is the name of the flag. This will be displayed
+ to Bugzilla users who are looking at or setting the flag.
+ The name may contain any valid Unicode characters except commas
+ and spaces.
+ </para>
+ </section>
+
+ <section id="flags-create-field-description">
+ <title>Description</title>
+ <para>
+ The description describes the flag in more detail. It is visible
+ in a tooltip when hovering over a flag either in the <quote>Show Bug</quote>
+ or <quote>Edit Attachment</quote> pages. This field can be as
+ long as you like, and can contain any character you want.
+ </para>
+ </section>
+
+ <section id="flags-create-field-category">
+ <title>Category</title>
+
+ <para>
+ Default behaviour for a newly-created flag is to appear on
+ products and all components, which is why <quote>__Any__:__Any__</quote>
+ is already entered in the <quote>Inclusions</quote> box.
+ If this is not your desired behaviour, you must either set some
+ exclusions (for products on which you don't want the flag to appear),
+ or you must remove <quote>__Any__:__Any__</quote> from the Inclusions box
+ and define products/components specifically for this flag.
+ </para>
+
+ <para>
+ To create an Inclusion, select a Product from the top drop-down box.
+ You may also select a specific component from the bottom drop-down box.
+ (Setting <quote>__Any__</quote> for Product translates to,
+ <quote>all the products in this Bugzilla</quote>.
+ Selecting <quote>__Any__</quote> in the Component field means
+ <quote>all components in the selected product.</quote>)
+ Selections made, press <quote>Include</quote>, and your
+ Product/Component pairing will show up in the <quote>Inclusions</quote> box on the right.
+ </para>
+
+ <para>
+ To create an Exclusion, the process is the same; select a Product from the
+ top drop-down box, select a specific component if you want one, and press
+ <quote>Exclude</quote>. The Product/Component pairing will show up in the
+ <quote>Exclusions</quote> box on the right.
+ </para>
+
+ <para>
+ This flag <emphasis>will</emphasis> and <emphasis>can</emphasis> be set for any
+ products/components that appearing in the <quote>Inclusions</quote> box
+ (or which fall under the appropriate <quote>__Any__</quote>).
+ This flag <emphasis>will not</emphasis> appear (and therefore cannot be set) on
+ any products appearing in the <quote>Exclusions</quote> box.
+ <emphasis> IMPORTANT: Exclusions override inclusions.</emphasis>
+ </para>
+
+ <para>
+ You may select a Product without selecting a specific Component,
+ but you can't select a Component without a Product, or to select a
+ Component that does not belong to the named Product. If you do so,
+ Bugzilla will display an error message, even if all your products
+ have a component by that name.
+ </para>
+
+ <para><emphasis>Example:</emphasis> Let's say you have a product called
+ <quote>Jet Plane</quote> that has thousands of components. You want
+ to be able to ask if a problem should be fixed in the next model of
+ plane you release. We'll call the flag <quote>fixInNext</quote>.
+ But, there's one component in <quote>Jet Plane,</quote>
+ called <quote>Pilot.</quote> It doesn't make sense to release a
+ new pilot, so you don't want to have the flag show up in that component.
+ So, you include <quote>Jet Plane:__Any__</quote> and you exclude
+ <quote>Jet Plane:Pilot</quote>.
+ </para>
+ </section>
+
+ <section id="flags-create-field-sortkey">
+ <title>Sort Key</title>
+ <para>
+ Flags normally show up in alphabetical order. If you want them to
+ show up in a different order, you can use this key set the order on each flag.
+ Flags with a lower sort key will appear before flags with a higher
+ sort key. Flags that have the same sort key will be sorted alphabetically,
+ but they will still be after flags with a lower sort key, and before flags
+ with a higher sort key.
+ </para>
+ <para>
+ <emphasis>Example:</emphasis> I have AFlag (Sort Key 100), BFlag (Sort Key 10),
+ CFlag (Sort Key 10), and DFlag (Sort Key 1). These show up in
+ the order: DFlag, BFlag, CFlag, AFlag.
+ </para>
+ </section>
+
+ <section id="flags-create-field-active">
+ <title>Active</title>
+ <para>
+ Sometimes, you might want to keep old flag information in the
+ Bugzilla database, but stop users from setting any new flags of this type.
+ To do this, uncheck <quote>active</quote>. Deactivated
+ flags will still show up in the UI if they are ?, +, or -, but they
+ may only be cleared (unset), and cannot be changed to a new value.
+ Once a deactivated flag is cleared, it will completely disappear from a
+ bug/attachment, and cannot be set again.
+ </para>
+ </section>
+
+ <section id="flags-create-field-requestable">
+ <title>Requestable</title>
+ <para>
+ New flags are, by default, <quote>requestable</quote>, meaning that they
+ offer users the <quote>?</quote> option, as well as <quote>+</quote>
+ and <quote>-</quote>.
+ To remove the ? option, uncheck <quote>requestable</quote>.
+ </para>
+ </section>
+
+ <section id="flags-create-field-specific">
+ <title>Specifically Requestable</title>
+ <para>
+ By default this box is checked for new flags, meaning that users may make
+ flag requests of specific individuals. Unchecking this box will remove the
+ text box next to a flag; if it is still requestable, then requests may
+ only be made <quote>to the wind.</quote> Removing this after specific
+ requests have been made will not remove those requests; that data will
+ stay in the database (though it will no longer appear to the user).
+ </para>
+ </section>
+
+ <section id="flags-create-field-multiplicable">
+ <title>Multiplicable</title>
+ <para>
+ Any flag with <quote>Multiplicable</quote> set (default for new flags is 'on')
+ may be set more than once. After being set once, an unset flag
+ of the same type will appear below it with <quote>addl.</quote> (short for
+ <quote>additional</quote>) before the name. There is no limit to the number of
+ times a Multiplicable flags may be set on the same bug/attachment.
+ </para>
+ </section>
+
+ <section id="flags-create-field-cclist">
+ <title>CC List</title>
+
+ <para>
+ If you want certain users to be notified every time this flag is
+ set to ?, -, +, or unset, add them here. This is a comma-separated
+ list of email addresses that need not be restricted to Bugzilla usernames.
+ </para>
+ </section>
+
+ <section id="flags-create-grant-group">
+ <title>Grant Group</title>
+ <para>
+ When this field is set to some given group, only users in the group
+ can set the flag to <quote>+</quote> and <quote>-</quote>. This
+ field does not affect who can request or cancel the flag. For that,
+ see the <quote>Request Group</quote> field below. If this field
+ is left blank, all users can set or delete this flag. This field is
+ useful for restricting which users can approve or reject requests.
+ </para>
+ </section>
+
+ <section id="flags-create-request-group">
+ <title>Request Group</title>
+ <para>
+ When this field is set to some given group, only users in the group
+ can request or cancel this flag. Note that this field has no effect
+ if the <quote>grant group</quote> field is empty. You can set the
+ value of this field to a different group, but both fields have to be
+ set to a group for this field to have an effect.
+ </para>
+ </section>
+ </section> <!-- flags-create -->
+
+ <section id="flags-delete">
+ <title>Deleting a Flag</title>
+
+ <para>
+ When you are at the <quote>Administer Flag Types</quote> screen,
+ you will be presented with a list of Bug flags and a list of Attachment
+ Flags.
+ </para>
+ <para>
+ To delete a flag, click on the <quote>Delete</quote> link next to
+ the flag description.
+ </para>
+ <warning>
+ <para>
+ Once you delete a flag, it is <emphasis>gone</emphasis> from
+ your Bugzilla. All the data for that flag will be deleted.
+ Everywhere that flag was set, it will disappear,
+ and you cannot get that data back. If you want to keep flag data,
+ but don't want anybody to set any new flags or change current flags,
+ unset <quote>active</quote> in the flag Edit form.
+ </para>
+ </warning>
+ </section>
+
+ </section> <!-- flags-admin -->
+
+ <!-- XXX We should add a "Uses of Flags" section, here, with examples. -->
+
+ </section> <!-- flags -->
+
+ <section id="keywords">
+ <title>Keywords</title>
+
+ <para>
+ The administrator can define keywords which can be used to tag and
+ categorise bugs. For example, the keyword "regression" is commonly used.
+ A company might have a policy stating all regressions
+ must be fixed by the next release - this keyword can make tracking those
+ bugs much easier.
+ </para>
+ <para>
+ Keywords are global, rather than per-product. If the administrator changes
+ a keyword currently applied to any bugs, the keyword cache must be rebuilt
+ using the <xref linkend="sanitycheck"/> script. Currently keywords can not
+ be marked obsolete to prevent future usage.
+ </para>
+ <para>
+ Keywords can be created, edited or deleted by clicking the "Keywords"
+ link in the admin page. There are two fields for each keyword - the keyword
+ itself and a brief description. Once created, keywords can be selected
+ and applied to individual bugs in that bug's "Details" section.
+ </para>
+ </section>
+
+ <section id="custom-fields">
+ <title>Custom Fields</title>
+
+ <para>
+ The release of Bugzilla 3.0 added the ability to create Custom Fields.
+ Custom Fields are treated like any other field - they can be set in bugs
+ and used for search queries. Administrators should keep in mind that
+ adding too many fields can make the user interface more complicated and
+ harder to use. Custom Fields should be added only when necessary and with
+ careful consideration.
+ </para>
+ <tip>
+ <para>
+ Before adding a Custom Field, make sure that Bugzilla can not already
+ do the desired behavior. Many Bugzilla options are not enabled by
+ default, and many times Administrators find that simply enabling
+ certain options that already exist is sufficient.
+ </para>
+ </tip>
+ <para>
+ Administrators can manage Custom Fields using the
+ <quote>Custom Fields</quote> link on the Administration page. The Custom
+ Fields administration page displays a list of Custom Fields, if any exist,
+ and a link to "Add a new custom field".
+ </para>
+
+ <section id="add-custom-fields">
+ <title>Adding Custom Fields</title>
+
+ <para>
+ To add a new Custom Field, click the "Add a new custom field" link. This
+ page displays several options for the new field, described below.
+ </para>
+
+ <para>
+ The following attributes must be set for each new custom field:
+ <itemizedlist>
+ <listitem>
+ <para>
+ <emphasis>Name:</emphasis>
+ The name of the field in the database, used internally. This name
+ MUST begin with <quote>cf_</quote> to prevent confusion with
+ standard fields. If this string is omitted, it will
+ be automatically added to the name entered.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <emphasis>Description:</emphasis>
+ A brief string which is used as the label for this Custom Field.
+ That is the string that users will see, and should be
+ short and explicit.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <emphasis>Type:</emphasis>
+ The type of field to create. There are
+ several types available:
+ <simplelist>
+ <member>
+ Large Text Box: A multiple line box for entering free text.
+ </member>
+ <member>
+ Free Text: A single line box for entering free text.
+ </member>
+ <member>
+ Multiple-Selection Box: A list box where multiple options
+ can be selected. After creating this field, it must be edited
+ to add the selection options. See
+ <xref linkend="edit-values-list" /> for information about
+ editing legal values.
+ </member>
+ <member>
+ Drop Down: A list box where only one option can be selected.
+ After creating this field, it must be edited to add the
+ selection options. See
+ <xref linkend="edit-values-list" /> for information about
+ editing legal values.
+ </member>
+ <member>
+ Date/Time: A date field. This field appears with a
+ calendar widget for choosing the date.
+ </member>
+ </simplelist>
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <emphasis>Sortkey:</emphasis>
+ Integer that determines in which order Custom Fields are
+ displayed in the User Interface, especially when viewing a bug.
+ Fields with lower values are displayed first.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <emphasis>Can be set on bug creation:</emphasis>
+ Boolean that determines whether this field can be set on
+ bug creation. If not selected, then a bug must be created
+ before this field can be set. See <xref linkend="bugreports" />
+ for information about filing bugs.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <emphasis>Displayed in bugmail for new bugs:</emphasis>
+ Boolean that determines whether the value set on this field
+ should appear in bugmail when the bug is filed. This attribute
+ has no effect if the field cannot be set on bug creation.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <emphasis>Is obsolete:</emphasis>
+ Boolean that determines whether this field should
+ be displayed at all. Obsolete Custom Fields are hidden.
+ </para>
+ </listitem>
+ </itemizedlist>
+ </para>
+ </section>
+
+ <section id="edit-custom-fields">
+ <title>Editing Custom Fields</title>
+
+ <para>
+ As soon as a Custom Field is created, its name and type cannot be
+ changed. If this field is a drop down menu, its legal values can
+ be set as described in <xref linkend="edit-values-list" />. All
+ other attributes can be edited as described above.
+ </para>
+ </section>
+
+ <section id="delete-custom-fields">
+ <title>Deleting Custom Fields</title>
+
+ <para>
+ Only custom fields which are marked as obsolete, and which never
+ have been used, can be deleted completely (else the integrity
+ of the bug history would be compromised). For custom fields marked
+ as obsolete, a "Delete" link will appear in the <quote>Action</quote>
+ column. If the custom field has been used in the past, the deletion
+ will be rejected. But marking the field as obsolete is sufficient
+ to hide it from the user interface entirely.
+ </para>
+ </section>
+ </section>
+
+ <section id="edit-values">
+ <title>Legal Values</title>
+
+ <para>
+ Since Bugzilla 2.20 RC1, legal values for Operating Systems, platforms,
+ bug priorities and severities can be edited from the User Interface
+ directly. This means that it is no longer required to manually edit
+ <filename>localconfig</filename>. Starting with Bugzilla 2.23.3,
+ the list of valid resolutions can be customized from the same interface.
+ Since Bugzilla 3.1.1 the list of valid bug statuses can be customized
+ as well.
+ </para>
+
+ <section id="edit-values-list">
+ <title>Viewing/Editing legal values</title>
+ <para>
+ Editing legal values requires <quote>admin</quote> privileges.
+ Select "Legal Values" from the Administration page. A list of all
+ fields, both system fields and Custom Fields, for which legal values
+ can be edited appears. Click a field name to edit its legal values.
+ </para>
+ <para>
+ There is no limit to how many values a field can have, but each value
+ must be unique to that field. The sortkey is important to display these
+ values in the desired order.
+ </para>
+ </section>
+
+ <section id="edit-values-delete">
+ <title>Deleting legal values</title>
+ <para>
+ Legal values from Custom Fields can be deleted, but only if the
+ following two conditions are respected:
+ </para>
+
+ <orderedlist>
+ <listitem>
+ <para>The value is not used by default for the field.</para>
+ </listitem>
+
+ <listitem>
+ <para>No bug is currently using this value.</para>
+ </listitem>
+ </orderedlist>
+
+ <para>
+ If any of these conditions is not respected, the value cannot be deleted.
+ The only way to delete these values is to reassign bugs to another value
+ and to set another value as default for the field.
+ </para>
+ </section>
+ </section>
+
+ <section id="bug_status_workflow">
+ <title>Bug Status Workflow</title>
+
+ <para>
+ The bug status workflow is no longer hardcoded but can be freely customized
+ from the web interface. Only one bug status cannot be renamed nor deleted,
+ UNCONFIRMED, but the workflow involving it is free. The configuration
+ page displays all existing bug statuses twice, first on the left for bug
+ statuses we come from and on the top for bug statuses we move to.
+ If the checkbox is checked, then the transition between the two bug statuses
+ is legal, else it's forbidden independently of your privileges. The bug status
+ used for the "duplicate_or_move_bug_status" parameter must be part of the
+ workflow as that is the bug status which will be used when duplicating or
+ moving a bug, so it must be available from each bug status.
+ </para>
+ <para>
+ When the workflow is set, the "View Current Triggers" link below the table
+ lets you set which transitions require a comment from the user.
+ </para>
+ </section>
+
+ <section id="voting">
+ <title>Voting</title>
+
+ <para>Voting allows users to be given a pot of votes which they can allocate
+ to bugs, to indicate that they'd like them fixed.
+ This allows developers to gauge
+ user need for a particular enhancement or bugfix. By allowing bugs with
+ a certain number of votes to automatically move from "UNCONFIRMED" to
+ "CONFIRMED", users of the bug system can help high-priority bugs garner
+ attention so they don't sit for a long time awaiting triage.</para>
+
+ <para>To modify Voting settings:</para>
+
+ <orderedlist>
+ <listitem>
+ <para>Navigate to the "Edit product" screen for the Product you
+ wish to modify</para>
+ </listitem>
+
+ <listitem>
+ <para><emphasis>Maximum Votes per person</emphasis>:
+ Setting this field to "0" disables voting.</para>
+ </listitem>
+
+ <listitem>
+ <para><emphasis>Maximum Votes a person can put on a single
+ bug</emphasis>:
+ It should probably be some number lower than the
+ "Maximum votes per person". Don't set this field to "0" if
+ "Maximum votes per person" is non-zero; that doesn't make
+ any sense.</para>
+ </listitem>
+
+ <listitem>
+ <para><emphasis>Number of votes a bug in this product needs to
+ automatically get out of the UNCONFIRMED state</emphasis>:
+ Setting this field to "0" disables the automatic move of
+ bugs from UNCONFIRMED to CONFIRMED.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>Once you have adjusted the values to your preference, click
+ "Update".</para>
+ </listitem>
+ </orderedlist>
+ </section>
+
+ <section id="quips">
+ <title>Quips</title>
+
+ <para>
+ Quips are small text messages that can be configured to appear
+ next to search results. A Bugzilla installation can have its own specific
+ quips. Whenever a quip needs to be displayed, a random selection
+ is made from the pool of already existing quips.
+ </para>
+
+ <para>
+ Quips are controlled by the <emphasis>enablequips</emphasis> parameter.
+ It has several possible values: on, approved, frozen or off.
+ In order to enable quips approval you need to set this parameter
+ to "approved". In this way, users are free to submit quips for
+ addition but an administrator must explicitly approve them before
+ they are actually used.
+ </para>
+
+ <para>
+ In order to see the user interface for the quips, it is enough to click
+ on a quip when it is displayed together with the search results. Or
+ it can be seen directly in the browser by visiting the quips.cgi URL
+ (prefixed with the usual web location of the Bugzilla installation).
+ Once the quip interface is displayed, it is enough to click the
+ "view and edit the whole quip list" in order to see the administration
+ page. A page with all the quips available in the database will
+ be displayed.
+ </para>
+
+ <para>
+ Next to each tip there is a checkbox, under the
+ "Approved" column. Quips who have this checkbox checked are
+ already approved and will appear next to the search results.
+ The ones that have it unchecked are still preserved in the
+ database but they will not appear on search results pages.
+ User submitted quips have initially the checkbox unchecked.
+ </para>
+
+ <para>
+ Also, there is a delete link next to each quip,
+ which can be used in order to permanently delete a quip.
+ </para>
+ </section>
+
+ <section id="groups">
+ <title>Groups and Group Security</title>
+
+ <para>
+ Groups allow for separating bugs into logical divisions.
+ Groups are typically used
+ to isolate bugs that should only be seen by certain people. For
+ example, a company might create a different group for each one of its customers
+ or partners. Group permissions could be set so that each partner or customer would
+ only have access to their own bugs. Or, groups might be used to create
+ variable access controls for different departments within an organization.
+ Another common use of groups is to associate groups with products,
+ creating isolation and access control on a per-product basis.
+ </para>
+
+ <para>
+ Groups and group behaviors are controlled in several places:
+ </para>
+
+ <orderedlist>
+
+ <listitem>
+ <para>
+ The group configuration page. To view or edit existing groups, or to
+ create new groups, access the "Groups" link from the "Administration"
+ page. This section of the manual deals primarily with the aspect of
+ group controls accessed on this page.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Global configuration parameters. Bugzilla has several parameters
+ that control the overall default group behavior and restriction
+ levels. For more information on the parameters that control
+ group behavior globally, see <xref linkend="param-group-security"/>.
+ </para>
+
+ </listitem>
+
+ <listitem>
+ <para>
+ Product association with groups. Most of the functionality of groups
+ and group security is controlled at the product level. Some aspects
+ of group access controls for products are discussed in this section,
+ but for more detail see <xref linkend="product-group-controls"/>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Group access for users. See <xref linkend="users-and-groups"/> for
+ details on how users are assigned group access.
+ </para>
+ </listitem>
+
+ </orderedlist>
+
+ <para>
+ Group permissions are such that if a bug belongs to a group, only members
+ of that group can see the bug. If a bug is in more than one group, only
+ members of <emphasis>all</emphasis> the groups that the bug is in can see
+ the bug. For information on granting read-only access to certain people and
+ full edit access to others, see <xref linkend="product-group-controls"/>.
+ </para>
+
+ <note>
+ <para>
+ By default, bugs can also be seen by the Assignee, the Reporter, and
+ by everyone on the CC List, regardless of whether or not the bug would
+ typically be viewable by them. Visibility to the Reporter and CC List can
+ be overridden (on a per-bug basis) by bringing up the bug, finding the
+ section that starts with <quote>Users in the roles selected below...</quote>
+ and un-checking the box next to either 'Reporter' or 'CC List' (or both).
+ </para>
+ </note>
+
+ <section id="create-groups">
+ <title>Creating Groups</title>
+
+ <para>
+ To create a new group, follow the steps below:
+ </para>
+
+ <orderedlist>
+
+ <listitem>
+ <para>
+ Select the <quote>Administration</quote> link in the page footer,
+ and then select the <quote>Groups</quote> link from the
+ Administration page.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ A table of all the existing groups is displayed. Below the table is a
+ description of all the fields. To create a new group, select the
+ <quote>Add Group</quote> link under the table of existing groups.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ There are five fields to fill out. These fields are documented below
+ the form. Choose a name and description for the group. Decide whether
+ this group should be used for bugs (in all likelihood this should be
+ selected). Optionally, choose a regular expression that will
+ automatically add any matching users to the group, and choose an
+ icon that will help identify user comments for the group. The regular
+ expression can be useful, for example, to automatically put all users
+ from the same company into one group (if the group is for a specific
+ customer or partner).
+ </para>
+ <note>
+ <para>
+ If <quote>User RegExp</quote> is filled out, users whose email
+ addresses match the regular expression will automatically be
+ members of the group as long as their email addresses continue
+ to match the regular expression. If their email address changes
+ and no longer matches the regular expression, they will be removed
+ from the group. Versions 2.16 and older of Bugzilla did not automatically
+ remove users who's email addresses no longer matched the RegExp.
+ </para>
+ </note>
+ <warning>
+ <para>
+ If specifying a domain in the regular expression, end
+ the regexp with a "$". Otherwise, when granting access to
+ "@mycompany\.com", access will also be granted to
+ 'badperson@mycompany.com.cracker.net'. Use the syntax,
+ '@mycompany\.com$' for the regular expression.
+ </para>
+ </warning>
+ </listitem>
+
+ <listitem>
+ <para>
+ After the new group is created, it can be edited for additional options.
+ The "Edit Group" page allows for specifying other groups that should be included
+ in this group and which groups should be permitted to add and delete
+ users from this group. For more details, see <xref linkend="edit-groups"/>.
+ </para>
+ </listitem>
+ </orderedlist>
+
+ </section>
+
+ <section id="edit-groups">
+ <title>Editing Groups and Assigning Group Permissions</title>
+
+ <para>
+ To access the "Edit Groups" page, select the
+ <quote>Administration</quote> link in the page footer,
+ and then select the <quote>Groups</quote> link from the Administration page.
+ A table of all the existing groups is displayed. Click on a group name
+ you wish to edit or control permissions for.
+ </para>
+
+ <para>
+ The "Edit Groups" page contains the same five fields present when
+ creating a new group. Below that are two additional sections, "Group
+ Permissions," and "Mass Remove". The "Mass Remove" option simply removes
+ all users from the group who match the regular expression entered. The
+ "Group Permissions" section requires further explanation.
+ </para>
+
+ <para>
+ The "Group Permissions" section on the "Edit Groups" page contains four sets
+ of permissions that control the relationship of this group to other
+ groups. If the 'usevisibilitygroups' parameter is in use (see
+ <xref linkend="parameters"/>) two additional sets of permissions are displayed.
+ Each set consists of two select boxes. On the left, a select box
+ with a list of all existing groups. On the right, a select box listing
+ all groups currently selected for this permission setting (this box will
+ be empty for new groups). The way these controls allow groups to relate
+ to one another is called <emphasis>inheritance</emphasis>.
+ Each of the six permissions is described below.
+ </para>
+
+ <variablelist>
+
+ <varlistentry>
+
+ <term>
+ <emphasis>Groups That Are a Member of This Group</emphasis>
+ </term>
+
+ <listitem>
+ <para>
+ Members of any groups selected here will automatically have
+ membership in this group. In other words, members of any selected
+ group will inherit membership in this group.
+ </para>
+ </listitem>
+
+ </varlistentry>
+
+ <varlistentry>
+
+ <term>
+ <emphasis>Groups That This Group Is a Member Of</emphasis>
+ </term>
+
+ <listitem>
+ <para>
+ Members of this group will inherit membership to any group
+ selected here. For example, suppose the group being edited is
+ an Admin group. If there are two products (Product1 and Product2)
+ and each product has its
+ own group (Group1 and Group2), and the Admin group
+ should have access to both products,
+ simply select both Group1 and Group2 here.
+ </para>
+ </listitem>
+
+ </varlistentry>
+
+ <varlistentry>
+
+ <term>
+ <emphasis>Groups That Can Grant Membership in This Group</emphasis>
+ </term>
+
+ <listitem>
+ <para>
+ The members of any group selected here will be able add users
+ to this group, even if they themselves are not in this group.
+ </para>
+ </listitem>
+
+ </varlistentry>
+
+ <varlistentry>
+
+ <term>
+ <emphasis>Groups That This Group Can Grant Membership In</emphasis>
+ </term>
+
+ <listitem>
+ <para>
+ Members of this group can add users to any group selected here,
+ even if they themselves are not in the selected groups.
+ </para>
+ </listitem>
+
+ </varlistentry>
+
+ <varlistentry>
+
+ <term>
+ <emphasis>Groups That Can See This Group</emphasis>
+ </term>
+
+ <listitem>
+ <para>
+ Members of any selected group can see the users in this group.
+ This setting is only visible if the 'usevisibilitygroups' parameter
+ is enabled on the Bugzilla Configuration page. See
+ <xref linkend="parameters"/> for information on configuring Bugzilla.
+ </para>
+ </listitem>
+
+ </varlistentry>
+
+ <varlistentry>
+
+ <term>
+ <emphasis>Groups That This Group Can See</emphasis>
+ </term>
+
+ <listitem>
+ <para>
+ Members of this group can see members in any of the selected groups.
+ This setting is only visible if the 'usevisibilitygroups' parameter
+ is enabled on the the Bugzilla Configuration page. See
+ <xref linkend="parameters"/> for information on configuring Bugzilla.
+ </para>
+ </listitem>
+
+ </varlistentry>
+
+ </variablelist>
+
+ </section>
+
+ <section id="users-and-groups">
+ <title>Assigning Users to Groups</title>
+
+ <para>
+ A User can become a member of a group in several ways:
+ </para>
+
+ <orderedlist>
+
+ <listitem>
+ <para>
+ The user can be explicitly placed in the group by editing
+ the user's profile. This can be done by accessing the "Users" page
+ from the "Administration" page. Use the search form to find the user
+ you want to edit group membership for, and click on their email
+ address in the search results to edit their profile. The profile
+ page lists all the groups, and indicates if the user is a member of
+ the group either directly or indirectly. More information on indirect
+ group membership is below. For more details on User administration,
+ see <xref linkend="useradmin"/>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ The group can include another group of which the user is
+ a member. This is indicated by square brackets around the checkbox
+ next to the group name in the user's profile.
+ See <xref linkend="edit-groups"/> for details on group inheritance.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ The user's email address can match the regular expression
+ that has been specified to automatically grant membership to
+ the group. This is indicated by "*" around the check box by the
+ group name in the user's profile.
+ See <xref linkend="create-groups"/> for details on
+ the regular expression option when creating groups.
+ </para>
+ </listitem>
+
+ </orderedlist>
+
+ </section>
+
+ <section>
+ <title>Assigning Group Controls to Products</title>
+
+ <para>
+ The primary functionality of groups is derived from the relationship of
+ groups to products. The concepts around segregating access to bugs with
+ product group controls can be confusing. For details and examples on this
+ topic, see <xref linkend="product-group-controls" />.
+ </para>
+
+ </section>
+ </section>
+
+ <section id="sanitycheck">
+ <title>Checking and Maintaining Database Integrity</title>
+
+ <para>
+ Over time it is possible for the Bugzilla database to become corrupt
+ or to have anomalies.
+ This could happen through normal usage of Bugzilla, manual database
+ administration outside of the Bugzilla user interface, or from some
+ other unexpected event. Bugzilla includes a "Sanity Check" script that
+ can perform several basic database checks, and repair certain problems or
+ inconsistencies.
+ </para>
+ <para>
+ To run the "Sanity Check" script, log in as an Administrator and click the
+ "Sanity Check" link in the admin page. Any problems that are found will be
+ displayed in red letters. If the script is capable of fixing a problem,
+ it will present a link to initiate the fix. If the script can not
+ fix the problem it will require manual database administration or recovery.
+ </para>
+ <para>
+ The "Sanity Check" script can also be run from the command line via the perl
+ script <filename>sanitycheck.pl</filename>. The script can also be run as
+ a <command>cron</command> job. Results will be delivered by email.
+ </para>
+ <para>
+ The "Sanity Check" script should be run on a regular basis as a matter of
+ best practice.
+ </para>
+ <warning>
+ <para>
+ The "Sanity Check" script is no substitute for a competent database
+ administrator. It is only designed to check and repair basic database
+ problems.
+ </para>
+ </warning>
+
+ </section>
+
+
+</chapter>
+
+<!-- Keep this comment at the end of the file
+Local variables:
+mode: sgml
+sgml-always-quote-attributes:t
+sgml-auto-insert-required-elements:t
+sgml-balanced-tag-edit:t
+sgml-exposed-tags:nil
+sgml-general-insert-case:lower
+sgml-indent-data:t
+sgml-indent-step:2
+sgml-local-catalogs:nil
+sgml-local-ecat-files:nil
+sgml-minimize-attributes:nil
+sgml-namecase-general:t
+sgml-omittag:t
+sgml-parent-document:("Bugzilla-Guide.xml" "book" "chapter")
+sgml-shorttag:t
+sgml-tag-region-if-active:t
+End:
+-->
+
diff --git a/docs/en/xml/bugzilla.ent b/docs/en/xml/bugzilla.ent
new file mode 100644
index 000000000..2bba8fd26
--- /dev/null
+++ b/docs/en/xml/bugzilla.ent
@@ -0,0 +1,47 @@
+<?xml version="1.0"?>
+
+<!-- Module Versions -->
+<!ENTITY min-cgi-ver "3.51">
+<!ENTITY min-digest-sha-ver "any">
+<!ENTITY min-date-format-ver "2.21">
+<!ENTITY min-datetime-ver "0.28">
+<!ENTITY min-datetime-timezone-ver "0.71">
+<!ENTITY min-dbi-ver "1.41">
+<!ENTITY min-template-ver "2.22">
+<!ENTITY min-email-send-ver "2.00">
+<!ENTITY min-email-mime-ver "1.904">
+<!ENTITY min-uri-ver "any">
+<!ENTITY min-list-moreutils-ver "0.22">
+<!ENTITY min-gd-ver "1.20">
+<!ENTITY min-chart-lines-ver "2.1">
+<!ENTITY min-template-plugin-gd-image-ver "any">
+<!ENTITY min-gd-text-ver "any">
+<!ENTITY min-gd-graph-ver "any">
+<!ENTITY min-xml-twig-ver "any">
+<!ENTITY min-mime-parser-ver "5.406">
+<!ENTITY min-lwp-useragent-ver "any">
+<!ENTITY min-patchreader-ver "0.9.4">
+<!ENTITY min-net-ldap-ver "any">
+<!ENTITY min-authen-sasl-ver "any">
+<!ENTITY min-authen-radius-ver "any">
+<!ENTITY min-soap-lite-ver "0.712">
+<!ENTITY min-json-rpc-ver "any">
+<!ENTITY min-json-xs-ver "2.0">
+<!ENTITY min-test-taint-ver "any">
+<!ENTITY min-html-parser-ver "3.40">
+<!ENTITY min-html-scrubber-ver "any">
+<!ENTITY min-email-mime-attachment-stripper-ver "any">
+<!ENTITY min-email-reply-ver "any">
+<!ENTITY min-theschwartz-ver "any">
+<!ENTITY min-daemon-generic-ver "any">
+<!ENTITY min-mod_perl2-ver "1.999022">
+<!ENTITY min-apache2-sizelimit-ver "0.93">
+<!ENTITY min-math-random-secure-ver "0.05">
+
+ <!-- Database Versions -->
+<!ENTITY min-dbd-pg-ver "1.45">
+<!ENTITY min-pg-ver "8.00.0000">
+<!ENTITY min-dbd-mysql-ver "4.00">
+<!ENTITY min-mysql-ver "4.1.2">
+<!ENTITY min-dbd-oracle-ver "1.19">
+<!ENTITY min-oracle-ver "10.02.0">
diff --git a/docs/en/xml/conventions.xml b/docs/en/xml/conventions.xml
new file mode 100644
index 000000000..70e6624f7
--- /dev/null
+++ b/docs/en/xml/conventions.xml
@@ -0,0 +1,164 @@
+<!-- <!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook V4.1//EN"> -->
+<section id="conventions">
+ <title>Document Conventions</title>
+
+ <indexterm zone="conventions">
+ <primary>conventions</primary>
+ </indexterm>
+
+ <para>This document uses the following conventions:</para>
+
+ <informaltable frame="none">
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>Descriptions</entry>
+
+ <entry>Appearance</entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry>Caution</entry>
+
+ <entry>
+ <caution>
+ <para>Don't run with scissors!</para>
+ </caution>
+ </entry>
+ </row>
+
+ <row>
+ <entry>Hint or Tip</entry>
+
+ <entry>
+ <tip>
+ <para>For best results... </para>
+ </tip>
+ </entry>
+ </row>
+
+ <row>
+ <entry>Note</entry>
+
+ <entry>
+ <note>
+ <para>Dear John...</para>
+ </note>
+ </entry>
+ </row>
+
+ <row>
+ <entry>Warning</entry>
+
+ <entry>
+ <warning>
+ <para>Read this or the cat gets it.</para>
+ </warning>
+ </entry>
+ </row>
+
+ <row>
+ <entry>File or directory name</entry>
+
+ <entry>
+ <filename>filename</filename>
+ </entry>
+ </row>
+
+ <row>
+ <entry>Command to be typed</entry>
+
+ <entry>
+ <command>command</command>
+ </entry>
+ </row>
+
+ <row>
+ <entry>Application name</entry>
+
+ <entry>
+ <application>application</application>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ Normal user's prompt under bash shell</entry>
+
+ <entry>bash$</entry>
+ </row>
+
+ <row>
+ <entry>
+ Root user's prompt under bash shell</entry>
+
+ <entry>bash#</entry>
+ </row>
+
+ <row>
+ <entry>
+ Normal user's prompt under tcsh shell</entry>
+
+ <entry>tcsh$</entry>
+ </row>
+
+ <row>
+ <entry>Environment variables</entry>
+
+ <entry>
+ <envar>VARIABLE</envar>
+ </entry>
+ </row>
+
+ <row>
+ <entry>Term found in the glossary</entry>
+
+ <entry>
+ <glossterm linkend="gloss-bugzilla">Bugzilla</glossterm>
+ </entry>
+ </row>
+
+ <row>
+ <entry>Code example</entry>
+
+ <entry>
+ <programlisting><sgmltag class="starttag">para</sgmltag>
+Beginning and end of paragraph
+<sgmltag class="endtag">para</sgmltag></programlisting>
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </informaltable>
+
+ <para>
+ This documentation is maintained in DocBook 4.1.2 XML format.
+ Changes are best submitted as plain text or XML diffs, attached
+ to a bug filed in the &bzg-bugs; component.
+ </para>
+
+</section>
+
+<!-- Keep this comment at the end of the file
+Local variables:
+mode: sgml
+sgml-always-quote-attributes:t
+sgml-auto-insert-required-elements:t
+sgml-balanced-tag-edit:t
+sgml-exposed-tags:nil
+sgml-general-insert-case:lower
+sgml-indent-data:t
+sgml-indent-step:2
+sgml-local-catalogs:nil
+sgml-local-ecat-files:nil
+sgml-minimize-attributes:nil
+sgml-namecase-general:t
+sgml-omittag:t
+sgml-parent-document:("Bugzilla-Guide.xml" "book" "chapter")
+sgml-shorttag:t
+sgml-tag-region-if-active:t
+End:
+-->
+
diff --git a/docs/en/xml/customization.xml b/docs/en/xml/customization.xml
new file mode 100644
index 000000000..f397cff53
--- /dev/null
+++ b/docs/en/xml/customization.xml
@@ -0,0 +1,609 @@
+<!-- <!DOCTYPE appendix PUBLIC "-//OASIS//DTD DocBook V4.1//EN"> -->
+<chapter id="customization">
+ <title>Customizing Bugzilla</title>
+
+ <section id="extensions">
+ <title>Bugzilla Extensions</title>
+
+ <para>
+ One of the best ways to customize Bugzilla is by writing a Bugzilla
+ Extension. Bugzilla Extensions let you modify both the code and
+ UI of Bugzilla in a way that can be distributed to other Bugzilla
+ users and ported forward to future versions of Bugzilla with minimal
+ effort.
+ </para>
+
+ <para>
+ See the <ulink url="api/Bugzilla/Extension.html">Bugzilla Extension
+ documentation</ulink> for information on how to write an Extension.
+ </para>
+ </section>
+
+ <section id="cust-skins">
+ <title>Custom Skins</title>
+
+ <para>
+ Bugzilla allows you to have multiple skins. These are custom CSS and possibly
+ also custom images for Bugzilla. To create a new custom skin, you have two
+ choices:
+ <itemizedlist>
+ <listitem>
+ <para>
+ Make a single CSS file, and put it in the
+ <filename>skins/contrib</filename> directory.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Make a directory that contains all the same CSS file
+ names as <filename>skins/standard/</filename>, and put
+ your directory in <filename>skins/contrib/</filename>.
+ </para>
+ </listitem>
+ </itemizedlist>
+ </para>
+
+ <para>
+ After you put the file or the directory there, make sure to run checksetup.pl
+ so that it can reset the file permissions correctly.
+ </para>
+ <para>
+ After you have installed the new skin, it will show up as an option in the
+ user's General Preferences. If you would like to force a particular skin on all
+ users, just select it in the Default Preferences and then uncheck "Enabled" on
+ the preference.
+ </para>
+ </section>
+
+ <section id="cust-templates">
+ <title>Template Customization</title>
+
+ <para>
+ Administrators can configure the look and feel of Bugzilla without
+ having to edit Perl files or face the nightmare of massive merge
+ conflicts when they upgrade to a newer version in the future.
+ </para>
+
+ <para>
+ Templatization also makes localized versions of Bugzilla possible,
+ for the first time. It's possible to have Bugzilla's UI language
+ determined by the user's browser. More information is available in
+ <xref linkend="template-http-accept"/>.
+ </para>
+
+ <section id="template-directory">
+ <title>Template Directory Structure</title>
+ <para>
+ The template directory structure starts with top level directory
+ named <filename>template</filename>, which contains a directory
+ for each installed localization. The next level defines the
+ language used in the templates. Bugzilla comes with English
+ templates, so the directory name is <filename>en</filename>,
+ and we will discuss <filename>template/en</filename> throughout
+ the documentation. Below <filename>template/en</filename> is the
+ <filename>default</filename> directory, which contains all the
+ standard templates shipped with Bugzilla.
+ </para>
+
+ <warning>
+ <para>
+ A directory <filename>data/templates</filename> also exists;
+ this is where Template Toolkit puts the compiled versions of
+ the templates from either the default or custom directories.
+ <emphasis>Do not</emphasis> directly edit the files in this
+ directory, or all your changes will be lost the next time
+ Template Toolkit recompiles the templates.
+ </para>
+ </warning>
+ </section>
+
+ <section id="template-method">
+ <title>Choosing a Customization Method</title>
+ <para>
+ If you want to edit Bugzilla's templates, the first decision
+ you must make is how you want to go about doing so. There are two
+ choices, and which you use depends mainly on the scope of your
+ modifications, and the method you plan to use to upgrade Bugzilla.
+ </para>
+
+ <para>
+ The first method of making customizations is to directly edit the
+ templates found in <filename>template/en/default</filename>.
+ This is probably the best way to go about it if you are going to
+ be upgrading Bugzilla through CVS, because if you then execute
+ a <command>cvs update</command>, any changes you have made will
+ be merged automagically with the updated versions.
+ </para>
+
+ <note>
+ <para>
+ If you use this method, and CVS conflicts occur during an
+ update, the conflicted templates (and possibly other parts
+ of your installation) will not work until they are resolved.
+ </para>
+ </note>
+
+ <para>
+ The second method is to copy the templates to be modified
+ into a mirrored directory structure under
+ <filename>template/en/custom</filename>. Templates in this
+ directory structure automatically override any identically-named
+ and identically-located templates in the
+ <filename>default</filename> directory.
+ </para>
+
+ <note>
+ <para>
+ The <filename>custom</filename> directory does not exist
+ at first and must be created if you want to use it.
+ </para>
+ </note>
+
+ <para>
+ The second method of customization should be used if you
+ use the overwriting method of upgrade, because otherwise
+ your changes will be lost. This method may also be better if
+ you are using the CVS method of upgrading and are going to make major
+ changes, because it is guaranteed that the contents of this directory
+ will not be touched during an upgrade, and you can then decide whether
+ to continue using your own templates, or make the effort to merge your
+ changes into the new versions by hand.
+ </para>
+
+ <para>
+ Using this method, your installation may break if incompatible
+ changes are made to the template interface. Such changes should
+ be documented in the release notes, provided you are using a
+ stable release of Bugzilla. If you use using unstable code, you will
+ need to deal with this one yourself, although if possible the changes
+ will be mentioned before they occur in the deprecations section of the
+ previous stable release's release notes.
+ </para>
+
+ <note>
+ <para>
+ Regardless of which method you choose, it is recommended that
+ you run <command>./checksetup.pl</command> after
+ editing any templates in the <filename>template/en/default</filename>
+ directory, and after creating or editing any templates in the
+ <filename>custom</filename> directory.
+ </para>
+ </note>
+
+ <warning>
+ <para>
+ It is <emphasis>required</emphasis> that you run
+ <command>./checksetup.pl</command> after creating a new
+ template in the <filename>custom</filename> directory. Failure
+ to do so will raise an incomprehensible error message.
+ </para>
+ </warning>
+ </section>
+
+ <section id="template-edit">
+ <title>How To Edit Templates</title>
+
+ <note>
+ <para>
+ If you are making template changes that you intend on submitting back
+ for inclusion in standard Bugzilla, you should read the relevant
+ sections of the
+ <ulink url="http://www.bugzilla.org/docs/developer.html">Developers'
+ Guide</ulink>.
+ </para>
+ </note>
+
+ <para>
+ The syntax of the Template Toolkit language is beyond the scope of
+ this guide. It's reasonably easy to pick up by looking at the current
+ templates; or, you can read the manual, available on the
+ <ulink url="http://www.template-toolkit.org">Template Toolkit home
+ page</ulink>.
+ </para>
+
+ <para>
+ One thing you should take particular care about is the need
+ to properly HTML filter data that has been passed into the template.
+ This means that if the data can possibly contain special HTML characters
+ such as &lt;, and the data was not intended to be HTML, they need to be
+ converted to entity form, i.e. &amp;lt;. You use the 'html' filter in the
+ Template Toolkit to do this. If you forget, you may open up
+ your installation to cross-site scripting attacks.
+ </para>
+
+ <para>
+ Also note that Bugzilla adds a few filters of its own, that are not
+ in standard Template Toolkit. In particular, the 'url_quote' filter
+ can convert characters that are illegal or have special meaning in URLs,
+ such as &amp;, to the encoded form, i.e. %26. This actually encodes most
+ characters (but not the common ones such as letters and numbers and so
+ on), including the HTML-special characters, so there's never a need to
+ HTML filter afterwards.
+ </para>
+
+ <para>
+ Editing templates is a good way of doing a <quote>poor man's custom
+ fields</quote>.
+ For example, if you don't use the Status Whiteboard, but want to have
+ a free-form text entry box for <quote>Build Identifier</quote>,
+ then you can just
+ edit the templates to change the field labels. It's still be called
+ status_whiteboard internally, but your users don't need to know that.
+ </para>
+
+ </section>
+
+
+ <section id="template-formats">
+ <title>Template Formats and Types</title>
+
+ <para>
+ Some CGI's have the ability to use more than one template. For example,
+ <filename>buglist.cgi</filename> can output itself as RDF, or as two
+ formats of HTML (complex and simple). The mechanism that provides this
+ feature is extensible.
+ </para>
+
+ <para>
+ Bugzilla can support different types of output, which again can have
+ multiple formats. In order to request a certain type, you can append
+ the &amp;ctype=&lt;contenttype&gt; (such as rdf or html) to the
+ <filename>&lt;cginame&gt;.cgi</filename> URL. If you would like to
+ retrieve a certain format, you can use the &amp;format=&lt;format&gt;
+ (such as simple or complex) in the URL.
+ </para>
+
+ <para>
+ To see if a CGI supports multiple output formats and types, grep the
+ CGI for <quote>get_format</quote>. If it's not present, adding
+ multiple format/type support isn't too hard - see how it's done in
+ other CGIs, e.g. config.cgi.
+ </para>
+
+ <para>
+ To make a new format template for a CGI which supports this,
+ open a current template for
+ that CGI and take note of the INTERFACE comment (if present.) This
+ comment defines what variables are passed into this template. If
+ there isn't one, I'm afraid you'll have to read the template and
+ the code to find out what information you get.
+ </para>
+
+ <para>
+ Write your template in whatever markup or text style is appropriate.
+ </para>
+
+ <para>
+ You now need to decide what content type you want your template
+ served as. The content types are defined in the
+ <filename>Bugzilla/Constants.pm</filename> file in the
+ <filename>contenttypes</filename>
+ constant. If your content type is not there, add it. Remember
+ the three- or four-letter tag assigned to your content type.
+ This tag will be part of the template filename.
+ </para>
+
+ <note>
+ <para>
+ After adding or changing a content type, it's suitable to edit
+ <filename>Bugzilla/Constants.pm</filename> in order to reflect
+ the changes. Also, the file should be kept up to date after an
+ upgrade if content types have been customized in the past.
+ </para>
+ </note>
+
+ <para>
+ Save the template as <filename>&lt;stubname&gt;-&lt;formatname&gt;.&lt;contenttypetag&gt;.tmpl</filename>.
+ Try out the template by calling the CGI as
+ <filename>&lt;cginame&gt;.cgi?format=&lt;formatname&gt;&amp;ctype=&lt;type&gt;</filename> .
+ </para>
+ </section>
+
+
+ <section id="template-specific">
+ <title>Particular Templates</title>
+
+ <para>
+ There are a few templates you may be particularly interested in
+ customizing for your installation.
+ </para>
+
+ <para>
+ <command>index.html.tmpl</command>:
+ This is the Bugzilla front page.
+ </para>
+
+ <para>
+ <command>global/header.html.tmpl</command>:
+ This defines the header that goes on all Bugzilla pages.
+ The header includes the banner, which is what appears to users
+ and is probably what you want to edit instead. However the
+ header also includes the HTML HEAD section, so you could for
+ example add a stylesheet or META tag by editing the header.
+ </para>
+
+ <para>
+ <command>global/banner.html.tmpl</command>:
+ This contains the <quote>banner</quote>, the part of the header
+ that appears
+ at the top of all Bugzilla pages. The default banner is reasonably
+ barren, so you'll probably want to customize this to give your
+ installation a distinctive look and feel. It is recommended you
+ preserve the Bugzilla version number in some form so the version
+ you are running can be determined, and users know what docs to read.
+ </para>
+
+ <para>
+ <command>global/footer.html.tmpl</command>:
+ This defines the footer that goes on all Bugzilla pages. Editing
+ this is another way to quickly get a distinctive look and feel for
+ your Bugzilla installation.
+ </para>
+
+ <para>
+ <command>global/variables.none.tmpl</command>:
+ This defines a list of terms that may be changed in order to
+ <quote>brand</quote> the Bugzilla instance In this way, terms
+ like <quote>bugs</quote> can be replaced with <quote>issues</quote>
+ across the whole Bugzilla installation. The name
+ <quote>Bugzilla</quote> and other words can be customized as well.
+ </para>
+
+ <para>
+ <command>list/table.html.tmpl</command>:
+ This template controls the appearance of the bug lists created
+ by Bugzilla. Editing this template allows per-column control of
+ the width and title of a column, the maximum display length of
+ each entry, and the wrap behaviour of long entries.
+ For long bug lists, Bugzilla inserts a 'break' every 100 bugs by
+ default; this behaviour is also controlled by this template, and
+ that value can be modified here.
+ </para>
+
+ <para>
+ <command>bug/create/user-message.html.tmpl</command>:
+ This is a message that appears near the top of the bug reporting page.
+ By modifying this, you can tell your users how they should report
+ bugs.
+ </para>
+
+ <para>
+ <command>bug/process/midair.html.tmpl</command>:
+ This is the page used if two people submit simultaneous changes to the
+ same bug. The second person to submit their changes will get this page
+ to tell them what the first person did, and ask if they wish to
+ overwrite those changes or go back and revisit the bug. The default
+ title and header on this page read "Mid-air collision detected!" If
+ you work in the aviation industry, or other environment where this
+ might be found offensive (yes, we have true stories of this happening)
+ you'll want to change this to something more appropriate for your
+ environment.
+ </para>
+
+ <para>
+ <command>bug/create/create.html.tmpl</command> and
+ <command>bug/create/comment.txt.tmpl</command>:
+ You may not wish to go to the effort of creating custom fields in
+ Bugzilla, yet you want to make sure that each bug report contains
+ a number of pieces of important information for which there is not
+ a special field. The bug entry system has been designed in an
+ extensible fashion to enable you to add arbitrary HTML widgets,
+ such as drop-down lists or textboxes, to the bug entry page
+ and have their values appear formatted in the initial comment.
+ A hidden field that indicates the format should be added inside
+ the form in order to make the template functional. Its value should
+ be the suffix of the template filename. For example, if the file
+ is called <filename>create-cust.html.tmpl</filename>, then
+ <programlisting>&lt;input type="hidden" name="format" value="cust"&gt;</programlisting>
+ should be used inside the form.
+ </para>
+
+ <para>
+ An example of this is the mozilla.org
+ <ulink url="http://landfill.bugzilla.org/bugzilla-tip/enter_bug.cgi?product=WorldControl&amp;format=guided">guided
+ bug submission form</ulink>. The code for this comes with the Bugzilla
+ distribution as an example for you to copy. It can be found in the
+ files
+ <filename>create-guided.html.tmpl</filename> and
+ <filename>comment-guided.html.tmpl</filename>.
+ </para>
+
+ <para>
+ So to use this feature, create a custom template for
+ <filename>enter_bug.cgi</filename>. The default template, on which you
+ could base it, is
+ <filename>custom/bug/create/create.html.tmpl</filename>.
+ Call it <filename>create-&lt;formatname&gt;.html.tmpl</filename>, and
+ in it, add widgets for each piece of information you'd like
+ collected - such as a build number, or set of steps to reproduce.
+ </para>
+
+ <para>
+ Then, create a template like
+ <filename>custom/bug/create/comment.txt.tmpl</filename>, and call it
+ <filename>comment-&lt;formatname&gt;.txt.tmpl</filename>. This
+ template should reference the form fields you have created using
+ the syntax <filename>[% form.&lt;fieldname&gt; %]</filename>. When a
+ bug report is
+ submitted, the initial comment attached to the bug report will be
+ formatted according to the layout of this template.
+ </para>
+
+ <para>
+ For example, if your custom enter_bug template had a field
+ <programlisting>&lt;input type="text" name="buildid" size="30"&gt;</programlisting>
+ and then your comment.txt.tmpl had
+ <programlisting>BuildID: [% form.buildid %]</programlisting>
+ then something like
+ <programlisting>BuildID: 20020303</programlisting>
+ would appear in the initial comment.
+ </para>
+ </section>
+
+
+ <section id="template-http-accept">
+ <title>Configuring Bugzilla to Detect the User's Language</title>
+
+ <para>Bugzilla honours the user's Accept: HTTP header. You can install
+ templates in other languages, and Bugzilla will pick the most appropriate
+ according to a priority order defined by you. Many
+ language templates can be obtained from <ulink
+ url="http://www.bugzilla.org/download.html#localizations"/>. Instructions
+ for submitting new languages are also available from that location.
+ </para>
+ </section>
+
+ </section>
+
+ <section id="cust-change-permissions">
+ <title>Customizing Who Can Change What</title>
+
+ <warning>
+ <para>
+ This feature should be considered experimental; the Bugzilla code you
+ will be changing is not stable, and could change or move between
+ versions. Be aware that if you make modifications as outlined here,
+ you may have
+ to re-make them or port them if Bugzilla changes internally between
+ versions, and you upgrade.
+ </para>
+ </warning>
+
+ <para>
+ Companies often have rules about which employees, or classes of employees,
+ are allowed to change certain things in the bug system. For example,
+ only the bug's designated QA Contact may be allowed to VERIFY the bug.
+ Bugzilla has been
+ designed to make it easy for you to write your own custom rules to define
+ who is allowed to make what sorts of value transition.
+ </para>
+
+ <para>
+ By default, assignees, QA owners and users
+ with <emphasis>editbugs</emphasis> privileges can edit all fields of bugs,
+ except group restrictions (unless they are members of the groups they
+ are trying to change). Bug reporters also have the ability to edit some
+ fields, but in a more restrictive manner. Other users, without
+ <emphasis>editbugs</emphasis> privileges, can not edit
+ bugs, except to comment and add themselves to the CC list.
+ </para>
+
+ <para>
+ For maximum flexibility, customizing this means editing Bugzilla's Perl
+ code. This gives the administrator complete control over exactly who is
+ allowed to do what. The relevant method is called
+ <filename>check_can_change_field()</filename>,
+ and is found in <filename>Bug.pm</filename> in your
+ Bugzilla/ directory. If you open that file and search for
+ <quote>sub check_can_change_field</quote>, you'll find it.
+ </para>
+
+ <para>
+ This function has been carefully commented to allow you to see exactly
+ how it works, and give you an idea of how to make changes to it.
+ Certain marked sections should not be changed - these are
+ the <quote>plumbing</quote> which makes the rest of the function work.
+ In between those sections, you'll find snippets of code like:
+ <programlisting> # Allow the assignee to change anything.
+ if ($ownerid eq $whoid) {
+ return 1;
+ }</programlisting>
+ It's fairly obvious what this piece of code does.
+ </para>
+
+ <para>
+ So, how does one go about changing this function? Well, simple changes
+ can be made just by removing pieces - for example, if you wanted to
+ prevent any user adding a comment to a bug, just remove the lines marked
+ <quote>Allow anyone to change comments.</quote> If you don't want the
+ Reporter to have any special rights on bugs they have filed, just
+ remove the entire section that deals with the Reporter.
+ </para>
+
+ <para>
+ More complex customizations are not much harder. Basically, you add
+ a check in the right place in the function, i.e. after all the variables
+ you are using have been set up. So, don't look at $ownerid before
+ $ownerid has been obtained from the database. You can either add a
+ positive check, which returns 1 (allow) if certain conditions are true,
+ or a negative check, which returns 0 (deny.) E.g.:
+ <programlisting> if ($field eq "qacontact") {
+ if (Bugzilla->user->in_group("quality_assurance")) {
+ return 1;
+ }
+ else {
+ return 0;
+ }
+ }</programlisting>
+ This says that only users in the group "quality_assurance" can change
+ the QA Contact field of a bug.
+ </para>
+
+ <para>
+ Getting more weird:
+ <programlisting><![CDATA[ if (($field eq "priority") &&
+ (Bugzilla->user->email =~ /.*\@example\.com$/))
+ {
+ if ($oldvalue eq "P1") {
+ return 1;
+ }
+ else {
+ return 0;
+ }
+ }]]></programlisting>
+ This says that if the user is trying to change the priority field,
+ and their email address is @example.com, they can only do so if the
+ old value of the field was "P1". Not very useful, but illustrative.
+ </para>
+
+ <warning>
+ <para>
+ If you are modifying <filename>process_bug.cgi</filename> in any
+ way, do not change the code that is bounded by DO_NOT_CHANGE blocks.
+ Doing so could compromise security, or cause your installation to
+ stop working entirely.
+ </para>
+ </warning>
+
+ <para>
+ For a list of possible field names, look at the bugs table in the
+ database. If you need help writing custom rules for your organization,
+ ask in the newsgroup.
+ </para>
+ </section>
+
+ <section id="integration">
+ <title>Integrating Bugzilla with Third-Party Tools</title>
+
+ <para>
+ Many utilities and applications can integrate with Bugzilla,
+ either on the client- or server-side. None of them are maintained
+ by the Bugzilla community, nor are they tested during our
+ QA tests, so use them at your own risk. They are listed at
+ <ulink url="https://wiki.mozilla.org/Bugzilla:Addons" />.
+ </para>
+ </section>
+
+</chapter>
+
+<!-- Keep this comment at the end of the file
+Local variables:
+mode: sgml
+sgml-always-quote-attributes:t
+sgml-auto-insert-required-elements:t
+sgml-balanced-tag-edit:t
+sgml-exposed-tags:nil
+sgml-general-insert-case:lower
+sgml-indent-data:t
+sgml-indent-step:2
+sgml-local-catalogs:nil
+sgml-local-ecat-files:nil
+sgml-minimize-attributes:nil
+sgml-namecase-general:t
+sgml-omittag:t
+sgml-parent-document:("Bugzilla-Guide.xml" "book" "chapter")
+sgml-shorttag:t
+sgml-tag-region-if-active:t
+End:
+-->
+
diff --git a/docs/en/xml/gfdl.xml b/docs/en/xml/gfdl.xml
new file mode 100644
index 000000000..1d84d1255
--- /dev/null
+++ b/docs/en/xml/gfdl.xml
@@ -0,0 +1,445 @@
+<!-- <!DOCTYPE appendix PUBLIC "-//OASIS//DTD DocBook V4.1//EN"> -->
+<appendix id="gfdl">
+ <title>GNU Free Documentation License</title>
+
+<!-- - GNU Project - Free Software Foundation (FSF) -->
+<!-- LINK REV="made" HREF="mailto:webmasters@gnu.org" -->
+<!-- section>
+ <title>GNU Free Documentation License</title -->
+ <para>Version 1.1, March 2000</para>
+
+ <blockquote>
+ <para>Copyright (C) 2000 Free Software Foundation, Inc. 59 Temple Place,
+ Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and
+ distribute verbatim copies of this license document, but changing it is
+ not allowed.</para>
+ </blockquote>
+
+ <section label="0" id="gfdl-0">
+ <title>Preamble</title>
+
+ <para>The purpose of this License is to make a manual, textbook, or other
+ written document "free" in the sense of freedom: to assure everyone the
+ effective freedom to copy and redistribute it, with or without modifying
+ it, either commercially or noncommercially. Secondarily, this License
+ preserves for the author and publisher a way to get credit for their
+ work, while not being considered responsible for modifications made by
+ others.</para>
+
+ <para>This License is a kind of "copyleft", which means that derivative
+ works of the document must themselves be free in the same sense. It
+ complements the GNU General Public License, which is a copyleft license
+ designed for free software.</para>
+
+ <para>We have designed this License in order to use it for manuals for
+ free software, because free software needs free documentation: a free
+ program should come with manuals providing the same freedoms that the
+ software does. But this License is not limited to software manuals; it
+ can be used for any textual work, regardless of subject matter or whether
+ it is published as a printed book. We recommend this License principally
+ for works whose purpose is instruction or reference.</para>
+ </section>
+
+ <section label="1" id="gfdl-1">
+ <title>Applicability and Definition</title>
+
+ <para>This License applies to any manual or other work that contains a
+ notice placed by the copyright holder saying it can be distributed under
+ the terms of this License. The "Document", below, refers to any such
+ manual or work. Any member of the public is a licensee, and is addressed
+ as "you".</para>
+
+ <para>A "Modified Version" of the Document means any work containing the
+ Document or a portion of it, either copied verbatim, or with
+ modifications and/or translated into another language.</para>
+
+ <para>A "Secondary Section" is a named appendix or a front-matter section
+ of the Document that deals exclusively with the relationship of the
+ publishers or authors of the Document to the Document's overall subject
+ (or to related matters) and contains nothing that could fall directly
+ within that overall subject. (For example, if the Document is in part a
+ textbook of mathematics, a Secondary Section may not explain any
+ mathematics.) The relationship could be a matter of historical connection
+ with the subject or with related matters, or of legal, commercial,
+ philosophical, ethical or political position regarding them.</para>
+
+ <para>The "Invariant Sections" are certain Secondary Sections whose
+ titles are designated, as being those of Invariant Sections, in the
+ notice that says that the Document is released under this License.</para>
+
+ <para>The "Cover Texts" are certain short passages of text that are
+ listed, as Front-Cover Texts or Back-Cover Texts, in the notice that says
+ that the Document is released under this License.</para>
+
+ <para>A "Transparent" copy of the Document means a machine-readable copy,
+ represented in a format whose specification is available to the general
+ public, whose contents can be viewed and edited directly and
+ straightforwardly with generic text editors or (for images composed of
+ pixels) generic paint programs or (for drawings) some widely available
+ drawing editor, and that is suitable for input to text formatters or for
+ automatic translation to a variety of formats suitable for input to text
+ formatters. A copy made in an otherwise Transparent file format whose
+ markup has been designed to thwart or discourage subsequent modification
+ by readers is not Transparent. A copy that is not "Transparent" is called
+ "Opaque".</para>
+
+ <para>Examples of suitable formats for Transparent copies include plain
+ ASCII without markup, Texinfo input format, LaTeX input format, SGML or
+ XML using a publicly available DTD, and standard-conforming simple HTML
+ designed for human modification. Opaque formats include PostScript, PDF,
+ proprietary formats that can be read and edited only by proprietary word
+ processors, SGML or XML for which the DTD and/or processing tools are not
+ generally available, and the machine-generated HTML produced by some word
+ processors for output purposes only.</para>
+
+ <para>The "Title Page" means, for a printed book, the title page itself,
+ plus such following pages as are needed to hold, legibly, the material
+ this License requires to appear in the title page. For works in formats
+ which do not have any title page as such, "Title Page" means the text
+ near the most prominent appearance of the work's title, preceding the
+ beginning of the body of the text.</para>
+ </section>
+
+ <section label="2" id="gfdl-2">
+ <title>Verbatim Copying</title>
+
+ <para>You may copy and distribute the Document in any medium, either
+ commercially or noncommercially, provided that this License, the
+ copyright notices, and the license notice saying this License applies to
+ the Document are reproduced in all copies, and that you add no other
+ conditions whatsoever to those of this License. You may not use technical
+ measures to obstruct or control the reading or further copying of the
+ copies you make or distribute. However, you may accept compensation in
+ exchange for copies. If you distribute a large enough number of copies
+ you must also follow the conditions in section 3.</para>
+
+ <para>You may also lend copies, under the same conditions stated above,
+ and you may publicly display copies.</para>
+ </section>
+
+ <section label="3" id="gfdl-3">
+ <title>Copying in Quantity</title>
+
+ <para>If you publish printed copies of the Document numbering more than
+ 100, and the Document's license notice requires Cover Texts, you must
+ enclose the copies in covers that carry, clearly and legibly, all these
+ Cover Texts: Front-Cover Texts on the front cover, and Back-Cover Texts
+ on the back cover. Both covers must also clearly and legibly identify you
+ as the publisher of these copies. The front cover must present the full
+ title with all words of the title equally prominent and visible. You may
+ add other material on the covers in addition. Copying with changes
+ limited to the covers, as long as they preserve the title of the Document
+ and satisfy these conditions, can be treated as verbatim copying in other
+ respects.</para>
+
+ <para>If the required texts for either cover are too voluminous to fit
+ legibly, you should put the first ones listed (as many as fit reasonably)
+ on the actual cover, and continue the rest onto adjacent pages.</para>
+
+ <para>If you publish or distribute Opaque copies of the Document
+ numbering more than 100, you must either include a machine-readable
+ Transparent copy along with each Opaque copy, or state in or with each
+ Opaque copy a publicly-accessible computer-network location containing a
+ complete Transparent copy of the Document, free of added material, which
+ the general network-using public has access to download anonymously at no
+ charge using public-standard network protocols. If you use the latter
+ option, you must take reasonably prudent steps, when you begin
+ distribution of Opaque copies in quantity, to ensure that this
+ Transparent copy will remain thus accessible at the stated location until
+ at least one year after the last time you distribute an Opaque copy
+ (directly or through your agents or retailers) of that edition to the
+ public.</para>
+
+ <para>It is requested, but not required, that you contact the authors of
+ the Document well before redistributing any large number of copies, to
+ give them a chance to provide you with an updated version of the
+ Document.</para>
+ </section>
+
+ <section label="4" id="gfdl-4">
+ <title>Modifications</title>
+
+ <para>You may copy and distribute a Modified Version of the Document
+ under the conditions of sections 2 and 3 above, provided that you release
+ the Modified Version under precisely this License, with the Modified
+ Version filling the role of the Document, thus licensing distribution and
+ modification of the Modified Version to whoever possesses a copy of it.
+ In addition, you must do these things in the Modified Version:</para>
+
+ <orderedlist numeration="upperalpha">
+ <listitem>
+ <para>Use in the Title Page (and on the covers, if any) a title
+ distinct from that of the Document, and from those of previous
+ versions (which should, if there were any, be listed in the History
+ section of the Document). You may use the same title as a previous
+ version if the original publisher of that version gives
+ permission.</para>
+ </listitem>
+
+ <listitem>
+ <para>List on the Title Page, as authors, one or more persons or
+ entities responsible for authorship of the modifications in the
+ Modified Version, together with at least five of the principal
+ authors of the Document (all of its principal authors, if it has less
+ than five).</para>
+ </listitem>
+
+ <listitem>
+ <para>State on the Title page the name of the publisher of the
+ Modified Version, as the publisher.</para>
+ </listitem>
+
+ <listitem>
+ <para>Preserve all the copyright notices of the Document.</para>
+ </listitem>
+
+ <listitem>
+ <para>Add an appropriate copyright notice for your modifications
+ adjacent to the other copyright notices.</para>
+ </listitem>
+
+ <listitem>
+ <para>Include, immediately after the copyright notices, a license
+ notice giving the public permission to use the Modified Version under
+ the terms of this License, in the form shown in the Addendum
+ below.</para>
+ </listitem>
+
+ <listitem>
+ <para>Preserve in that license notice the full lists of Invariant
+ Sections and required Cover Texts given in the Document's license
+ notice.</para>
+ </listitem>
+
+ <listitem>
+ <para>Include an unaltered copy of this License.</para>
+ </listitem>
+
+ <listitem>
+ <para>Preserve the section entitled "History", and its title, and add
+ to it an item stating at least the title, year, new authors, and
+ publisher of the Modified Version as given on the Title Page. If
+ there is no section entitled "History" in the Document, create one
+ stating the title, year, authors, and publisher of the Document as
+ given on its Title Page, then add an item describing the Modified
+ Version as stated in the previous sentence.</para>
+ </listitem>
+
+ <listitem>
+ <para>Preserve the network location, if any, given in the Document
+ for public access to a Transparent copy of the Document, and likewise
+ the network locations given in the Document for previous versions it
+ was based on. These may be placed in the "History" section. You may
+ omit a network location for a work that was published at least four
+ years before the Document itself, or if the original publisher of the
+ version it refers to gives permission.</para>
+ </listitem>
+
+ <listitem>
+ <para>In any section entitled "Acknowledgements" or "Dedications",
+ preserve the section's title, and preserve in the section all the
+ substance and tone of each of the contributor acknowledgements and/or
+ dedications given therein.</para>
+ </listitem>
+
+ <listitem>
+ <para>Preserve all the Invariant Sections of the Document, unaltered
+ in their text and in their titles. Section numbers or the equivalent
+ are not considered part of the section titles.</para>
+ </listitem>
+
+ <listitem>
+ <para>Delete any section entitled "Endorsements". Such a section may
+ not be included in the Modified Version.</para>
+ </listitem>
+
+ <listitem>
+ <para>Do not retitle any existing section as "Endorsements" or to
+ conflict in title with any Invariant Section.</para>
+ </listitem>
+ </orderedlist>
+
+ <para>If the Modified Version includes new front-matter sections or
+ appendices that qualify as Secondary Sections and contain no material
+ copied from the Document, you may at your option designate some or all of
+ these sections as invariant. To do this, add their titles to the list of
+ Invariant Sections in the Modified Version's license notice. These titles
+ must be distinct from any other section titles.</para>
+
+ <para>You may add a section entitled "Endorsements", provided it contains
+ nothing but endorsements of your Modified Version by various parties--for
+ example, statements of peer review or that the text has been approved by
+ an organization as the authoritative definition of a standard.</para>
+
+ <para>You may add a passage of up to five words as a Front-Cover Text,
+ and a passage of up to 25 words as a Back-Cover Text, to the end of the
+ list of Cover Texts in the Modified Version. Only one passage of
+ Front-Cover Text and one of Back-Cover Text may be added by (or through
+ arrangements made by) any one entity. If the Document already includes a
+ cover text for the same cover, previously added by you or by arrangement
+ made by the same entity you are acting on behalf of, you may not add
+ another; but you may replace the old one, on explicit permission from the
+ previous publisher that added the old one.</para>
+
+ <para>The author(s) and publisher(s) of the Document do not by this
+ License give permission to use their names for publicity for or to assert
+ or imply endorsement of any Modified Version.</para>
+ </section>
+
+ <section label="5" id="gfdl-5">
+ <title>Combining Documents</title>
+
+ <para>You may combine the Document with other documents released under
+ this License, under the terms defined in section 4 above for modified
+ versions, provided that you include in the combination all of the
+ Invariant Sections of all of the original documents, unmodified, and list
+ them all as Invariant Sections of your combined work in its license
+ notice.</para>
+
+ <para>The combined work need only contain one copy of this License, and
+ multiple identical Invariant Sections may be replaced with a single copy.
+ If there are multiple Invariant Sections with the same name but different
+ contents, make the title of each such section unique by adding at the end
+ of it, in parentheses, the name of the original author or publisher of
+ that section if known, or else a unique number. Make the same adjustment
+ to the section titles in the list of Invariant Sections in the license
+ notice of the combined work.</para>
+
+ <para>In the combination, you must combine any sections entitled
+ "History" in the various original documents, forming one section entitled
+ "History"; likewise combine any sections entitled "Acknowledgements", and
+ any sections entitled "Dedications". You must delete all sections
+ entitled "Endorsements."</para>
+ </section>
+
+ <section label="6" id="gfdl-6">
+ <title>Collections of Documents</title>
+
+ <para>You may make a collection consisting of the Document and other
+ documents released under this License, and replace the individual copies
+ of this License in the various documents with a single copy that is
+ included in the collection, provided that you follow the rules of this
+ License for verbatim copying of each of the documents in all other
+ respects.</para>
+
+ <para>You may extract a single document from such a collection, and
+ distribute it individually under this License, provided you insert a copy
+ of this License into the extracted document, and follow this License in
+ all other respects regarding verbatim copying of that document.</para>
+ </section>
+
+ <section label="7" id="gfdl-7">
+ <title>Aggregation with Independent Works</title>
+
+ <para>A compilation of the Document or its derivatives with other
+ separate and independent documents or works, in or on a volume of a
+ storage or distribution medium, does not as a whole count as a Modified
+ Version of the Document, provided no compilation copyright is claimed for
+ the compilation. Such a compilation is called an "aggregate", and this
+ License does not apply to the other self-contained works thus compiled
+ with the Document, on account of their being thus compiled, if they are
+ not themselves derivative works of the Document.</para>
+
+ <para>If the Cover Text requirement of section 3 is applicable to these
+ copies of the Document, then if the Document is less than one quarter of
+ the entire aggregate, the Document's Cover Texts may be placed on covers
+ that surround only the Document within the aggregate. Otherwise they must
+ appear on covers around the whole aggregate.</para>
+ </section>
+
+ <section label="8" id="gfdl-8">
+ <title>Translation</title>
+
+ <para>Translation is considered a kind of modification, so you may
+ distribute translations of the Document under the terms of section 4.
+ Replacing Invariant Sections with translations requires special
+ permission from their copyright holders, but you may include translations
+ of some or all Invariant Sections in addition to the original versions of
+ these Invariant Sections. You may include a translation of this License
+ provided that you also include the original English version of this
+ License. In case of a disagreement between the translation and the
+ original English version of this License, the original English version
+ will prevail.</para>
+ </section>
+
+ <section label="9" id="gfdl-9">
+ <title>Termination</title>
+
+ <para>You may not copy, modify, sublicense, or distribute the Document
+ except as expressly provided for under this License. Any other attempt to
+ copy, modify, sublicense or distribute the Document is void, and will
+ automatically terminate your rights under this License. However, parties
+ who have received copies, or rights, from you under this License will not
+ have their licenses terminated so long as such parties remain in full
+ compliance.</para>
+ </section>
+
+ <section label="10" id="gfdl-10">
+ <title>Future Revisions of this License</title>
+
+ <para>The Free Software Foundation may publish new, revised versions of
+ the GNU Free Documentation License from time to time. Such new versions
+ will be similar in spirit to the present version, but may differ in
+ detail to address new problems or concerns. See
+ <ulink url="http://www.gnu.org/copyleft/"/>.</para>
+
+ <para>Each version of the License is given a distinguishing version
+ number. If the Document specifies that a particular numbered version of
+ this License "or any later version" applies to it, you have the option of
+ following the terms and conditions either of that specified version or of
+ any later version that has been published (not as a draft) by the Free
+ Software Foundation. If the Document does not specify a version number of
+ this License, you may choose any version ever published (not as a draft)
+ by the Free Software Foundation.</para>
+ </section>
+
+ <section label="" id="gfdl-howto">
+ <title>How to use this License for your documents</title>
+
+ <para>To use this License in a document you have written, include a copy
+ of the License in the document and put the following copyright and
+ license notices just after the title page:</para>
+
+ <blockquote>
+ <para>Copyright (c) YEAR YOUR NAME. Permission is granted to copy,
+ distribute and/or modify this document under the terms of the GNU Free
+ Documentation License, Version 1.1 or any later version published by
+ the Free Software Foundation; with the Invariant Sections being LIST
+ THEIR TITLES, with the Front-Cover Texts being LIST, and with the
+ Back-Cover Texts being LIST. A copy of the license is included in the
+ section entitled "GNU Free Documentation License".</para>
+ </blockquote>
+
+ <para>If you have no Invariant Sections, write "with no Invariant
+ Sections" instead of saying which ones are invariant. If you have no
+ Front-Cover Texts, write "no Front-Cover Texts" instead of "Front-Cover
+ Texts being LIST"; likewise for Back-Cover Texts.</para>
+
+ <para>If your document contains nontrivial examples of program code, we
+ recommend releasing these examples in parallel under your choice of free
+ software license, such as the GNU General Public License, to permit their
+ use in free software.</para>
+ </section>
+</appendix>
+
+<!-- Keep this comment at the end of the file
+Local variables:
+mode: sgml
+sgml-always-quote-attributes:t
+sgml-auto-insert-required-elements:t
+sgml-balanced-tag-edit:t
+sgml-exposed-tags:nil
+sgml-general-insert-case:lower
+sgml-indent-data:t
+sgml-indent-step:2
+sgml-local-catalogs:nil
+sgml-local-ecat-files:nil
+sgml-minimize-attributes:nil
+sgml-namecase-general:t
+sgml-omittag:t
+sgml-parent-document:("Bugzilla-Guide.xml" "book" "chapter")
+sgml-shorttag:t
+sgml-tag-region-if-active:t
+End:
+-->
+
diff --git a/docs/en/xml/glossary.xml b/docs/en/xml/glossary.xml
new file mode 100644
index 000000000..48730b248
--- /dev/null
+++ b/docs/en/xml/glossary.xml
@@ -0,0 +1,548 @@
+<!-- <!DOCTYPE glossary PUBLIC "-//OASIS//DTD DocBook V4.1//EN" > -->
+<glossary id="glossary">
+ <glossdiv>
+ <title>0-9, high ascii</title>
+
+ <glossentry id="gloss-htaccess">
+ <glossterm>.htaccess</glossterm>
+
+ <glossdef>
+ <para>Apache web server, and other NCSA-compliant web servers,
+ observe the convention of using files in directories called
+ <filename>.htaccess</filename>
+
+ to restrict access to certain files. In Bugzilla, they are used
+ to keep secret files which would otherwise
+ compromise your installation - e.g. the
+ <filename>localconfig</filename>
+ file contains the password to your database.
+ curious.</para>
+ </glossdef>
+ </glossentry>
+ </glossdiv>
+
+ <glossdiv id="gloss-a">
+ <title>A</title>
+
+ <glossentry id="gloss-apache">
+ <glossterm>Apache</glossterm>
+
+ <glossdef>
+ <para>In this context, Apache is the web server most commonly used
+ for serving up Bugzilla
+ pages. Contrary to popular belief, the apache web server has nothing
+ to do with the ancient and noble Native American tribe, but instead
+ derived its name from the fact that it was
+ <quote>a patchy</quote>
+ version of the original
+ <acronym>NCSA</acronym>
+ world-wide-web server.</para>
+
+ <variablelist>
+ <title>Useful Directives when configuring Bugzilla</title>
+
+ <varlistentry>
+ <term><computeroutput><ulink url="http://httpd.apache.org/docs-2.0/mod/core.html#addhandler">AddHandler</ulink></computeroutput></term>
+ <listitem>
+ <para>Tell Apache that it's OK to run CGI scripts.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><computeroutput><ulink url="http://httpd.apache.org/docs-2.0/mod/core.html#allowoverride">AllowOverride</ulink></computeroutput></term>
+ <term><computeroutput><ulink url="http://httpd.apache.org/docs-2.0/mod/core.html#options">Options</ulink></computeroutput></term>
+ <listitem>
+ <para>These directives are used to tell Apache many things about
+ the directory they apply to. For Bugzilla's purposes, we need
+ them to allow script execution and <filename>.htaccess</filename>
+ overrides.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><computeroutput><ulink url="http://httpd.apache.org/docs-2.0/mod/mod_dir.html#directoryindex">DirectoryIndex</ulink></computeroutput></term>
+ <listitem>
+ <para>Used to tell Apache what files are indexes. If you can
+ not add <filename>index.cgi</filename> to the list of valid files,
+ you'll need to set <computeroutput>$index_html</computeroutput> to
+ 1 in <filename>localconfig</filename> so
+ <command>./checksetup.pl</command> will create an
+ <filename>index.html</filename> that redirects to
+ <filename>index.cgi</filename>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><computeroutput><ulink url="http://httpd.apache.org/docs-2.0/mod/core.html#scriptinterpretersource">ScriptInterpreterSource</ulink></computeroutput></term>
+ <listitem>
+ <para>Used when running Apache on windows so the shebang line
+ doesn't have to be changed in every Bugzilla script.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+ <para>For more information about how to configure Apache for Bugzilla,
+ see <xref linkend="http-apache"/>.
+ </para>
+ </glossdef>
+ </glossentry>
+ </glossdiv>
+
+ <glossdiv id="gloss-b">
+ <title>B</title>
+
+ <glossentry>
+ <glossterm>Bug</glossterm>
+
+ <glossdef>
+ <para>A
+ <quote>bug</quote>
+
+ in Bugzilla refers to an issue entered into the database which has an
+ associated number, assignments, comments, etc. Some also refer to a
+ <quote>tickets</quote>
+ or
+ <quote>issues</quote>;
+ in the context of Bugzilla, they are synonymous.</para>
+ </glossdef>
+ </glossentry>
+
+ <glossentry>
+ <glossterm>Bug Number</glossterm>
+
+ <glossdef>
+ <para>Each Bugzilla bug is assigned a number that uniquely identifies
+ that bug. The bug associated with a bug number can be pulled up via a
+ query, or easily from the very front page by typing the number in the
+ "Find" box.</para>
+ </glossdef>
+ </glossentry>
+
+ <glossentry id="gloss-bugzilla">
+ <glossterm>Bugzilla</glossterm>
+
+ <glossdef>
+ <para>Bugzilla is the world-leading free software bug tracking system.
+ </para>
+ </glossdef>
+ </glossentry>
+ </glossdiv>
+
+ <glossdiv id="gloss-c">
+ <title>C</title>
+
+ <glossentry id="gloss-cgi">
+ <glossterm>Common Gateway Interface</glossterm>
+ <acronym>CGI</acronym>
+ <glossdef>
+ <para><acronym>CGI</acronym> is an acronym for Common Gateway Interface. This is
+ a standard for interfacing an external application with a web server. Bugzilla
+ is an example of a <acronym>CGI</acronym> application.
+ </para>
+ </glossdef>
+ </glossentry>
+
+ <glossentry id="gloss-component">
+ <glossterm>Component</glossterm>
+
+ <glossdef>
+ <para>A Component is a subsection of a Product. It should be a narrow
+ category, tailored to your organization. All Products must contain at
+ least one Component (and, as a matter of fact, creating a Product
+ with no Components will create an error in Bugzilla).</para>
+ </glossdef>
+ </glossentry>
+
+ <glossentry id="gloss-cpan">
+ <glossterm>Comprehensive Perl Archive Network</glossterm>
+ <acronym>CPAN</acronym>
+
+ <!-- TODO: Rewrite def for CPAN -->
+ <glossdef>
+ <para>
+ <acronym>CPAN</acronym>
+
+ stands for the
+ <quote>Comprehensive Perl Archive Network</quote>.
+ CPAN maintains a large number of extremely useful
+ <glossterm>Perl</glossterm>
+ modules - encapsulated chunks of code for performing a
+ particular task.</para>
+ </glossdef>
+ </glossentry>
+
+ <glossentry id="gloss-contrib">
+ <glossterm><filename class="directory">contrib</filename></glossterm>
+
+ <glossdef>
+ <para>The <filename class="directory">contrib</filename> directory is
+ a location to put scripts that have been contributed to Bugzilla but
+ are not a part of the official distribution. These scripts are written
+ by third parties and may be in languages other than perl. For those
+ that are in perl, there may be additional modules or other requirements
+ than those of the official distribution.
+ <note>
+ <para>Scripts in the <filename class="directory">contrib</filename>
+ directory are not officially supported by the Bugzilla team and may
+ break in between versions.
+ </para>
+ </note>
+ </para>
+ </glossdef>
+ </glossentry>
+ </glossdiv>
+
+ <glossdiv id="gloss-d">
+ <title>D</title>
+
+ <glossentry id="gloss-daemon">
+ <glossterm>daemon</glossterm>
+
+ <glossdef>
+ <para>A daemon is a computer program which runs in the background. In
+ general, most daemons are started at boot time via System V init
+ scripts, or through RC scripts on BSD-based systems.
+ <glossterm>mysqld</glossterm>,
+ the MySQL server, and
+ <glossterm>apache</glossterm>,
+ a web server, are generally run as daemons.</para>
+ </glossdef>
+ </glossentry>
+
+ <glossentry id="gloss-dos">
+ <glossterm>DOS Attack</glossterm>
+
+ <glossdef>
+ <para>A DOS, or Denial of Service attack, is when a user attempts to
+ deny access to a web server by repeatedly accessing a page or sending
+ malformed requests to a webserver. A D-DOS, or
+ Distributed Denial of Service attack, is when these requests come
+ from multiple sources at the same time. Unfortunately, these are much
+ more difficult to defend against.
+ </para>
+ </glossdef>
+ </glossentry>
+
+ </glossdiv>
+
+ <glossdiv id="gloss-g">
+ <title>G</title>
+
+ <glossentry id="gloss-groups">
+ <glossterm>Groups</glossterm>
+
+ <glossdef>
+ <para>The word
+ <quote>Groups</quote>
+
+ has a very special meaning to Bugzilla. Bugzilla's main security
+ mechanism comes by placing users in groups, and assigning those
+ groups certain privileges to view bugs in particular
+ <glossterm>Products</glossterm>
+ in the
+ <glossterm>Bugzilla</glossterm>
+ database.</para>
+ </glossdef>
+ </glossentry>
+ </glossdiv>
+
+ <glossdiv id="gloss-j">
+ <title>J</title>
+
+ <glossentry id="gloss-javascript">
+ <glossterm>JavaScript</glossterm>
+ <glossdef>
+ <para>JavaScript is cool, we should talk about it.
+ </para>
+ </glossdef>
+ </glossentry>
+ </glossdiv>
+
+ <glossdiv id="gloss-m">
+ <title>M</title>
+
+ <glossentry id="gloss-mta">
+ <glossterm>Message Transport Agent</glossterm>
+ <acronym>MTA</acronym>
+
+ <glossdef>
+ <para>A Message Transport Agent is used to control the flow of email on a system.
+ The <ulink url="http://search.cpan.org/dist/Email-Send/lib/Email/Send.pm">Email::Send</ulink>
+ Perl module, which Bugzilla uses to send email, can be configured to
+ use many different underlying implementations for actually sending the
+ mail using the <option>mail_delivery_method</option> parameter.
+ </para>
+ </glossdef>
+ </glossentry>
+
+ <glossentry id="gloss-mysql">
+ <glossterm>MySQL</glossterm>
+
+ <glossdef>
+ <para>MySQL is currently the required
+ <glossterm linkend="gloss-rdbms">RDBMS</glossterm> for Bugzilla. MySQL
+ can be downloaded from <ulink url="http://www.mysql.com"/>. While you
+ should familiarize yourself with all of the documentation, some high
+ points are:
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term><ulink url="http://www.mysql.com/doc/en/Backup.html">Backup</ulink></term>
+ <listitem>
+ <para>Methods for backing up your Bugzilla database.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><ulink url="http://www.mysql.com/doc/en/Option_files.html">Option Files</ulink></term>
+ <listitem>
+ <para>Information about how to configure MySQL using
+ <filename>my.cnf</filename>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><ulink url="http://www.mysql.com/doc/en/Privilege_system.html">Privilege System</ulink></term>
+ <listitem>
+ <para>Information about how to protect your MySQL server.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </glossdef>
+ </glossentry>
+ </glossdiv>
+
+ <glossdiv id="gloss-p">
+ <title>P</title>
+
+ <glossentry id="gloss-ppm">
+ <glossterm>Perl Package Manager</glossterm>
+ <acronym>PPM</acronym>
+
+ <glossdef>
+ <para><ulink url="http://aspn.activestate.com/ASPN/Downloads/ActivePerl/PPM/"/>
+ </para>
+ </glossdef>
+ </glossentry>
+
+ <glossentry>
+ <glossterm id="gloss-product">Product</glossterm>
+
+ <glossdef>
+ <para>A Product is a broad category of types of bugs, normally
+ representing a single piece of software or entity. In general,
+ there are several Components to a Product. A Product may define a
+ group (used for security) for all bugs entered into
+ its Components.</para>
+ </glossdef>
+ </glossentry>
+
+ <glossentry>
+ <glossterm>Perl</glossterm>
+
+ <glossdef>
+ <para>First written by Larry Wall, Perl is a remarkable program
+ language. It has the benefits of the flexibility of an interpreted
+ scripting language (such as shell script), combined with the speed
+ and power of a compiled language, such as C.
+ <glossterm>Bugzilla</glossterm>
+
+ is maintained in Perl.</para>
+ </glossdef>
+ </glossentry>
+ </glossdiv>
+
+ <glossdiv id="gloss-q">
+ <title>Q</title>
+
+ <glossentry>
+ <glossterm>QA</glossterm>
+
+ <glossdef>
+ <para>
+ <quote>QA</quote>,
+ <quote>Q/A</quote>, and
+ <quote>Q.A.</quote>
+ are short for
+ <quote>Quality Assurance</quote>.
+ In most large software development organizations, there is a team
+ devoted to ensuring the product meets minimum standards before
+ shipping. This team will also generally want to track the progress of
+ bugs over their life cycle, thus the need for the
+ <quote>QA Contact</quote>
+
+ field in a bug.</para>
+ </glossdef>
+ </glossentry>
+ </glossdiv>
+
+ <glossdiv id="gloss-r">
+ <title>R</title>
+
+ <glossentry id="gloss-rdbms">
+ <glossterm>Relational DataBase Management System</glossterm>
+ <acronym>RDBMS</acronym>
+
+ <glossdef>
+ <para>A relational database management system is a database system
+ that stores information in tables that are related to each other.
+ </para>
+ </glossdef>
+ </glossentry>
+
+ <glossentry id="gloss-regexp">
+ <glossterm>Regular Expression</glossterm>
+ <acronym>regexp</acronym>
+
+ <glossdef>
+ <para>A regular expression is an expression used for pattern matching.
+ <ulink url="http://perldoc.com/perl5.6/pod/perlre.html#Regular-Expressions">Documentation</ulink>
+ </para>
+ </glossdef>
+ </glossentry>
+ </glossdiv>
+
+ <glossdiv id="gloss-s">
+ <title>S</title>
+
+ <glossentry id="gloss-service">
+ <glossterm>Service</glossterm>
+
+ <glossdef>
+ <para>In Windows NT environment, a boot-time background application
+ is referred to as a service. These are generally managed through the
+ control panel while logged in as an account with
+ <quote>Administrator</quote> level capabilities. For more
+ information, consult your Windows manual or the MSKB.
+ </para>
+ </glossdef>
+ </glossentry>
+
+ <glossentry>
+ <glossterm>
+ <acronym>SGML</acronym>
+ </glossterm>
+
+ <glossdef>
+ <para>
+ <acronym>SGML</acronym>
+
+ stands for
+ <quote>Standard Generalized Markup Language</quote>.
+ Created in the 1980's to provide an extensible means to maintain
+ documentation based upon content instead of presentation,
+ <acronym>SGML</acronym>
+
+ has withstood the test of time as a robust, powerful language.
+ <glossterm>
+ <acronym>XML</acronym>
+ </glossterm>
+
+ is the
+ <quote>baby brother</quote>
+
+ of SGML; any valid
+ <acronym>XML</acronym>
+
+ document it, by definition, a valid
+ <acronym>SGML</acronym>
+
+ document. The document you are reading is written and maintained in
+ <acronym>SGML</acronym>,
+ and is also valid
+ <acronym>XML</acronym>
+
+ if you modify the Document Type Definition.</para>
+ </glossdef>
+ </glossentry>
+ </glossdiv>
+
+ <glossdiv id="gloss-t">
+ <title>T</title>
+
+ <glossentry id="gloss-target-milestone" xreflabel="Target Milestone">
+ <glossterm>Target Milestone</glossterm>
+
+ <glossdef>
+ <para>Target Milestones are Product goals. They are configurable on a
+ per-Product basis. Most software development houses have a concept of
+
+ <quote>milestones</quote>
+
+ where the people funding a project expect certain functionality on
+ certain dates. Bugzilla facilitates meeting these milestones by
+ giving you the ability to declare by which milestone a bug will be
+ fixed, or an enhancement will be implemented.</para>
+ </glossdef>
+ </glossentry>
+
+ <glossentry id="gloss-tcl">
+ <glossterm>Tool Command Language</glossterm>
+ <acronym>TCL</acronym>
+ <glossdef>
+ <para>TCL is an open source scripting language available for Windows,
+ Macintosh, and Unix based systems. Bugzilla 1.0 was written in TCL but
+ never released. The first release of Bugzilla was 2.0, which was when
+ it was ported to perl.
+ </para>
+ </glossdef>
+ </glossentry>
+ </glossdiv>
+
+ <glossdiv id="gloss-z">
+ <title>Z</title>
+
+ <glossentry id="gloss-zarro">
+ <glossterm>Zarro Boogs Found</glossterm>
+
+ <glossdef>
+ <para>This is just a goofy way of saying that there were no bugs
+ found matching your query. When asked to explain this message,
+ Terry had the following to say:
+ </para>
+
+ <blockquote>
+ <attribution>Terry Weissman</attribution>
+ <para>I've been asked to explain this ... way back when, when
+ Netscape released version 4.0 of its browser, we had a release
+ party. Naturally, there had been a big push to try and fix every
+ known bug before the release. Naturally, that hadn't actually
+ happened. (This is not unique to Netscape or to 4.0; the same thing
+ has happened with every software project I've ever seen.) Anyway,
+ at the release party, T-shirts were handed out that said something
+ like "Netscape 4.0: Zarro Boogs". Just like the software, the
+ T-shirt had no known bugs. Uh-huh.
+ </para>
+
+ <para>So, when you query for a list of bugs, and it gets no results,
+ you can think of this as a friendly reminder. Of *course* there are
+ bugs matching your query, they just aren't in the bugsystem yet...
+ </para>
+ </blockquote>
+
+ </glossdef>
+ </glossentry>
+ </glossdiv>
+</glossary>
+
+<!-- Keep this comment at the end of the file
+Local variables:
+mode: sgml
+sgml-always-quote-attributes:t
+sgml-auto-insert-required-elements:t
+sgml-balanced-tag-edit:t
+sgml-exposed-tags:nil
+sgml-general-insert-case:lower
+sgml-indent-data:t
+sgml-indent-step:2
+sgml-local-catalogs:nil
+sgml-local-ecat-files:nil
+sgml-minimize-attributes:nil
+sgml-namecase-general:t
+sgml-omittag:t
+sgml-parent-document:("Bugzilla-Guide.xml" "book" "chapter")
+sgml-shorttag:t
+sgml-tag-region-if-active:t
+End:
+-->
diff --git a/docs/en/xml/index.xml b/docs/en/xml/index.xml
new file mode 100644
index 000000000..7fc1a4c14
--- /dev/null
+++ b/docs/en/xml/index.xml
@@ -0,0 +1,21 @@
+<!-- Keep this comment at the end of the file
+Local variables:
+mode: sgml
+sgml-always-quote-attributes:t
+sgml-auto-insert-required-elements:t
+sgml-balanced-tag-edit:t
+sgml-exposed-tags:nil
+sgml-general-insert-case:lower
+sgml-indent-data:t
+sgml-indent-step:2
+sgml-local-catalogs:nil
+sgml-local-ecat-files:nil
+sgml-minimize-attributes:nil
+sgml-namecase-general:t
+sgml-omittag:t
+sgml-parent-document:("Bugzilla-Guide.xml" "book" "chapter")
+sgml-shorttag:t
+sgml-tag-region-if-active:t
+End:
+-->
+
diff --git a/docs/en/xml/installation.xml b/docs/en/xml/installation.xml
new file mode 100644
index 000000000..2193dfe6f
--- /dev/null
+++ b/docs/en/xml/installation.xml
@@ -0,0 +1,2445 @@
+<!-- <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN"> -->
+<chapter id="installing-bugzilla">
+ <title>Installing Bugzilla</title>
+
+ <section id="installation">
+ <title>Installation</title>
+
+ <note>
+ <para>If you just want to <emphasis>use</emphasis> Bugzilla,
+ you do not need to install it. None of this chapter is relevant to
+ you. Ask your Bugzilla administrator for the URL to access it from
+ your web browser.
+ </para>
+ </note>
+
+ <para>The Bugzilla server software is usually installed on Linux or
+ Solaris.
+ If you are installing on another OS, check <xref linkend="os-specific"/>
+ before you start your installation to see if there are any special
+ instructions.
+ </para>
+
+ <para>This guide assumes that you have administrative access to the
+ Bugzilla machine. It not possible to
+ install and run Bugzilla itself without administrative access except
+ in the very unlikely event that every single prerequisite is
+ already installed.
+ </para>
+
+ <warning>
+ <para>The installation process may make your machine insecure for
+ short periods of time. Make sure there is a firewall between you
+ and the Internet.
+ </para>
+ </warning>
+
+ <para>
+ You are strongly recommended to make a backup of your system
+ before installing Bugzilla (and at regular intervals thereafter :-).
+ </para>
+
+ <para>In outline, the installation proceeds as follows:
+ </para>
+
+ <procedure>
+ <step>
+ <para><link linkend="install-perl">Install Perl</link>
+ (&min-perl-ver; or above)
+ </para>
+ </step>
+ <step>
+ <para><link linkend="install-database">Install a Database Engine</link>
+ </para>
+ </step>
+ <step>
+ <para><link linkend="install-webserver">Install a Webserver</link>
+ </para>
+ </step>
+ <step>
+ <para><link linkend="install-bzfiles">Install Bugzilla</link>
+ </para>
+ </step>
+ <step>
+ <para><link linkend="install-perlmodules">Install Perl modules</link>
+ </para>
+ </step>
+ <step>
+ <para>
+ <link linkend="install-MTA">Install a Mail Transfer Agent</link>
+ (Sendmail 8.7 or above, or an MTA that is Sendmail-compatible with at least this version)
+ </para>
+ </step>
+ <step>
+ <para>Configure all of the above.
+ </para>
+ </step>
+ </procedure>
+
+ <section id="install-perl">
+ <title>Perl</title>
+
+ <para>Installed Version Test: <programlisting>perl -v</programlisting></para>
+
+ <para>Any machine that doesn't have Perl on it is a sad machine indeed.
+ If you don't have it and your OS doesn't provide official packages,
+ visit <ulink url="http://www.perl.org"/>.
+ Although Bugzilla runs with Perl &min-perl-ver;,
+ it's a good idea to be using the latest stable version.
+ </para>
+ </section>
+
+ <section id="install-database">
+ <title>Database Engine</title>
+
+ <para>
+ Bugzilla supports MySQL, PostgreSQL and Oracle as database servers.
+ You only require one of these systems to make use of Bugzilla.
+ </para>
+
+ <section id="install-mysql">
+ <title>MySQL</title>
+ <para>Installed Version Test: <programlisting>mysql -V</programlisting></para>
+
+ <para>
+ If you don't have it and your OS doesn't provide official packages,
+ visit <ulink url="http://www.mysql.com"/>. You need MySQL version
+ &min-mysql-ver; or higher.
+ </para>
+
+ <note>
+ <para> Many of the binary
+ versions of MySQL store their data files in
+ <filename class="directory">/var</filename>.
+ On some Unix systems, this is part of a smaller root partition,
+ and may not have room for your bug database. To change the data
+ directory, you have to build MySQL from source yourself, and
+ set it as an option to <filename>configure</filename>.</para>
+ </note>
+
+ <para>If you install from something other than a packaging/installation
+ system, such as .rpm (Redhat Package), .deb (Debian Package), .exe
+ (Windows Executable), or .msi (Microsoft Installer), make sure the MySQL
+ server is started when the machine boots.
+ </para>
+ </section>
+
+ <section id="install-pg">
+ <title>PostgreSQL</title>
+ <para>Installed Version Test: <programlisting>psql -V</programlisting></para>
+
+ <para>
+ If you don't have it and your OS doesn't provide official packages,
+ visit <ulink url="http://www.postgresql.org/"/>. You need PostgreSQL
+ version &min-pg-ver; or higher.
+ </para>
+
+ <para>If you install from something other than a packaging/installation
+ system, such as .rpm (Redhat Package), .deb (Debian Package), .exe
+ (Windows Executable), or .msi (Microsoft Installer), make sure the
+ PostgreSQL server is started when the machine boots.
+ </para>
+ </section>
+
+ <section id="install-oracle">
+ <title>Oracle</title>
+ <para>
+ Installed Version Test: <programlisting>select * from v$version</programlisting>
+ (you first have to log in into your DB)
+ </para>
+
+ <para>
+ If you don't have it and your OS doesn't provide official packages,
+ visit <ulink url="http://www.oracle.com/"/>. You need Oracle
+ version &min-oracle-ver; or higher.
+ </para>
+
+ <para>
+ If you install from something other than a packaging/installation
+ system, such as .rpm (Redhat Package), .deb (Debian Package), .exe
+ (Windows Executable), or .msi (Microsoft Installer), make sure the
+ Oracle server is started when the machine boots.
+ </para>
+ </section>
+ </section>
+
+ <section id="install-webserver">
+ <title>Web Server</title>
+
+ <para>Installed Version Test: view the default welcome page at
+ http://&lt;your-machine&gt;/</para>
+
+ <para>You have freedom of choice here, pretty much any web server that
+ is capable of running <glossterm linkend="gloss-cgi">CGI</glossterm>
+ scripts will work.
+ However, we strongly recommend using the Apache web server
+ (either 1.3.x or 2.x), and
+ the installation instructions usually assume you are
+ using it. If you have got Bugzilla working using another web server,
+ please share your experiences with us by filing a bug in &bzg-bugs;.
+ </para>
+
+ <para>
+ If you don't have Apache and your OS doesn't provide official packages,
+ visit <ulink url="http://httpd.apache.org/"/>.
+ </para>
+
+ </section>
+
+ <section id="install-bzfiles">
+ <title>Bugzilla</title>
+
+ <para>
+ <ulink url="http://www.bugzilla.org/download/">Download a Bugzilla tarball</ulink>
+ (or check it out from CVS) and place
+ it in a suitable directory, accessible by the default web server user
+ (probably <quote>apache</quote> or <quote>www</quote>).
+ Good locations are either directly in the web server's document directories or
+ in <filename>/usr/local</filename> with a symbolic link to the web server's
+ document directories or an alias in the web server's configuration.
+ </para>
+
+ <caution>
+ <para>The default Bugzilla distribution is NOT designed to be placed
+ in a <filename class="directory">cgi-bin</filename> directory. This
+ includes any directory which is configured using the
+ <option>ScriptAlias</option> directive of Apache.
+ </para>
+ </caution>
+
+ <para>Once all the files are in a web accessible directory, make that
+ directory writable by your web server's user. This is a temporary step
+ until you run the
+ <filename>checksetup.pl</filename>
+ script, which locks down your installation.</para>
+ </section>
+
+ <section id="install-perlmodules">
+ <title>Perl Modules</title>
+
+ <para>Bugzilla's installation process is based
+ on a script called <filename>checksetup.pl</filename>.
+ The first thing it checks is whether you have appropriate
+ versions of all the required
+ Perl modules. The aim of this section is to pass this check.
+ When it passes, proceed to <xref linkend="configuration"/>.
+ </para>
+
+ <para>
+ At this point, you need to <filename>su</filename> to root. You should
+ remain as root until the end of the install. To check you have the
+ required modules, run:
+ </para>
+
+ <screen><prompt>bash#</prompt> ./checksetup.pl --check-modules</screen>
+
+ <para>
+ <filename>checksetup.pl</filename> will print out a list of the
+ required and optional Perl modules, together with the versions
+ (if any) installed on your machine.
+ The list of required modules is reasonably long; however, you
+ may already have several of them installed.
+ </para>
+
+ <para>
+ The preferred way to install missing Perl modules is to use the package
+ manager provided by your operating system (e.g <quote>rpm</quote> or
+ <quote>yum</quote> on Linux distros, or <quote>ppm</quote> on Windows
+ if using ActivePerl, see <xref linkend="win32-perl-modules"/>).
+ If some Perl modules are still missing or are too old, then we recommend
+ using the <filename>install-module.pl</filename> script (doesn't work
+ with ActivePerl on Windows). If for some reason you really need to
+ install the Perl modules manually, see
+ <xref linkend="install-perlmodules-manual"/>. For instance, on Unix,
+ you invoke <filename>install-module.pl</filename> as follows:
+ </para>
+
+ <screen><prompt>bash#</prompt> perl install-module.pl &lt;modulename&gt;</screen>
+
+ <tip>
+ <para>Many people complain that Perl modules will not install for
+ them. Most times, the error messages complain that they are missing a
+ file in
+ <quote>@INC</quote>.
+ Virtually every time, this error is due to permissions being set too
+ restrictively for you to compile Perl modules or not having the
+ necessary Perl development libraries installed on your system.
+ Consult your local UNIX systems administrator for help solving these
+ permissions issues; if you
+ <emphasis>are</emphasis>
+ the local UNIX sysadmin, please consult the newsgroup/mailing list
+ for further assistance or hire someone to help you out.</para>
+ </tip>
+
+ <note>
+ <para>If you are using a package-based system, and attempting to install the
+ Perl modules from CPAN, you may need to install the "development" packages for
+ MySQL and GD before attempting to install the related Perl modules. The names of
+ these packages will vary depending on the specific distribution you are using,
+ but are often called <filename>&lt;packagename&gt;-devel</filename>.</para>
+ </note>
+
+ <para>
+ Here is a complete list of modules and their minimum versions.
+ Some modules have special installation notes, which follow.
+ </para>
+
+ <para>Required Perl modules:
+ <orderedlist>
+
+ <listitem>
+ <para>
+ CGI (&min-cgi-ver;)
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Date::Format (&min-date-format-ver;)
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ DateTime (&min-datetime-ver;)
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ DateTime::TimeZone (&min-datetime-timezone-ver;)
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ DBI (&min-dbi-ver;)
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ DBD::mysql (&min-dbd-mysql-ver;) if using MySQL
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ DBD::Pg (&min-dbd-pg-ver;) if using PostgreSQL
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ DBD::Oracle (&min-dbd-oracle-ver;) if using Oracle
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Digest::SHA (&min-digest-sha-ver;)
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Email::Send (&min-email-send-ver;)
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Email::MIME (&min-email-mime-ver;)
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Template (&min-template-ver;)
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ URI (&min-uri-ver;)
+ </para>
+ </listitem>
+ </orderedlist>
+
+ Optional Perl modules:
+ <orderedlist>
+ <listitem>
+ <para>
+ GD (&min-gd-ver;) for bug charting
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Template::Plugin::GD::Image
+ (&min-template-plugin-gd-image-ver;) for Graphical Reports
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Chart::Lines (&min-chart-lines-ver;) for bug charting
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ GD::Graph (&min-gd-graph-ver;) for bug charting
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ GD::Text (&min-gd-text-ver;) for bug charting
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ XML::Twig (&min-xml-twig-ver;) for bug import/export
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ MIME::Parser (&min-mime-parser-ver;) for bug import/export
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ LWP::UserAgent
+ (&min-lwp-useragent-ver;) for Automatic Update Notifications
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ PatchReader (&min-patchreader-ver;) for pretty HTML view of patches
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Net::LDAP
+ (&min-net-ldap-ver;) for LDAP Authentication
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Authen::SASL
+ (&min-authen-sasl-ver;) for SASL Authentication
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Authen::Radius
+ (&min-authen-radius-ver;) for RADIUS Authentication
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ SOAP::Lite (&min-soap-lite-ver;) for the web service interface
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ JSON::RPC
+ (&min-json-rpc-ver;) for the JSON-RPC interface
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Test::Taint
+ (&min-test-taint-ver;) for the web service interface
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ HTML::Parser
+ (&min-html-parser-ver;) for More HTML in Product/Group Descriptions
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ HTML::Scrubber
+ (&min-html-scrubber-ver;) for More HTML in Product/Group Descriptions
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Email::MIME::Attachment::Stripper
+ (&min-email-mime-attachment-stripper-ver;) for Inbound Email
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Email::Reply
+ (&min-email-reply-ver;) for Inbound Email
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ TheSchwartz
+ (&min-theschwartz-ver;) for Mail Queueing
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Daemon::Generic
+ (&min-daemon-generic-ver;) for Mail Queueing
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ mod_perl2
+ (&min-mod_perl2-ver;) for mod_perl
+ </para>
+ </listitem>
+ </orderedlist>
+ </para>
+ </section>
+
+ <section id="install-MTA">
+ <title>Mail Transfer Agent (MTA)</title>
+
+ <para>
+ Bugzilla is dependent on the availability of an e-mail system for its
+ user authentication and for other tasks.
+ </para>
+
+ <note>
+ <para>
+ This is not entirely true. It is possible to completely disable
+ email sending, or to have Bugzilla store email messages in a
+ file instead of sending them. However, this is mainly intended
+ for testing, as disabling or diverting email on a production
+ machine would mean that users could miss important events (such
+ as bug changes or the creation of new accounts).
+ </para>
+
+ <para>
+ For more information, see the <quote>mail_delivery_method</quote> parameter
+ in <xref linkend="parameters" />.
+ </para>
+ </note>
+
+ <para>
+ On Linux, any Sendmail-compatible MTA (Mail Transfer Agent) will
+ suffice. Sendmail, Postfix, qmail and Exim are examples of common
+ MTAs. Sendmail is the original Unix MTA, but the others are easier to
+ configure, and therefore many people replace Sendmail with Postfix or
+ Exim. They are drop-in replacements, so Bugzilla will not
+ distinguish between them.
+ </para>
+
+ <para>
+ If you are using Sendmail, version 8.7 or higher is required.
+ If you are using a Sendmail-compatible MTA, it must be congruent with
+ at least version 8.7 of Sendmail.
+ </para>
+
+ <para>
+ Consult the manual for the specific MTA you choose for detailed
+ installation instructions. Each of these programs will have their own
+ configuration files where you must configure certain parameters to
+ ensure that the mail is delivered properly. They are implemented
+ as services, and you should ensure that the MTA is in the auto-start
+ list of services for the machine.
+ </para>
+
+ <para>
+ If a simple mail sent with the command-line 'mail' program
+ succeeds, then Bugzilla should also be fine.
+ </para>
+
+ </section>
+ <section id="using-mod_perl-with-bugzilla">
+ <title>Installing Bugzilla on mod_perl</title>
+ <para>It is now possible to run the Bugzilla software under <literal>mod_perl</literal> on
+ Apache. <literal>mod_perl</literal> has some additional requirements to that of running
+ Bugzilla under <literal>mod_cgi</literal> (the standard and previous way).</para>
+
+ <para>Bugzilla requires <literal>mod_perl</literal> to be installed, which can be
+ obtained from <ulink url="http://perl.apache.org"/> - Bugzilla requires
+ version &min-mod_perl2-ver; (AKA 2.0.0-RC5) to be installed.</para>
+ </section>
+ </section>
+
+ <section id="configuration">
+ <title>Configuration</title>
+
+ <warning>
+ <para>
+ Poorly-configured MySQL and Bugzilla installations have
+ given attackers full access to systems in the past. Please take the
+ security parts of these guidelines seriously, even for Bugzilla
+ machines hidden away behind your firewall. Be certain to read
+ <xref linkend="security"/> for some important security tips.
+ </para>
+ </warning>
+
+ <section id="localconfig">
+ <title>localconfig</title>
+
+ <para>
+ You should now run <filename>checksetup.pl</filename> again, this time
+ without the <literal>--check-modules</literal> switch.
+ </para>
+ <screen><prompt>bash#</prompt> ./checksetup.pl</screen>
+ <para>
+ This time, <filename>checksetup.pl</filename> should tell you that all
+ the correct modules are installed and will display a message about, and
+ write out a file called, <filename>localconfig</filename>. This file
+ contains the default settings for a number of Bugzilla parameters.
+ </para>
+
+ <para>
+ Load this file in your editor. The only two values you
+ <emphasis>need</emphasis> to change are $db_driver and $db_pass,
+ respectively the type of the database and the password for
+ the user you will create for your database. Pick a strong
+ password (for simplicity, it should not contain single quote
+ characters) and put it here. $db_driver can be either 'mysql',
+ 'Pg' or 'oracle'.
+ </para>
+
+ <note>
+ <para>
+ In Oracle, <literal>$db_name</literal> should actually be
+ the SID name of your database (e.g. "XE" if you are using Oracle XE).
+ </para>
+ </note>
+
+ <para>
+ You may need to change the value of
+ <emphasis>webservergroup</emphasis> if your web server does not
+ run in the "apache" group. On Debian, for example, Apache runs in
+ the "www-data" group. If you are going to run Bugzilla on a
+ machine where you do not have root access (such as on a shared web
+ hosting account), you will need to leave
+ <emphasis>webservergroup</emphasis> empty, ignoring the warnings
+ that <filename>checksetup.pl</filename> will subsequently display
+ every time it is run.
+ </para>
+
+ <caution>
+ <para>
+ If you are using suexec, you should use your own primary group
+ for <emphasis>webservergroup</emphasis> rather than leaving it
+ empty, and see the additional directions in the suexec section
+ <xref linkend="suexec" />.
+ </para>
+ </caution>
+
+ <para>
+ The other options in the <filename>localconfig</filename> file
+ are documented by their accompanying comments. If you have a slightly
+ non-standard database setup, you may wish to change one or more of
+ the other "$db_*" parameters.
+ </para>
+ </section>
+
+ <section id="database-engine">
+ <title>Database Server</title>
+ <para>
+ This section deals with configuring your database server for use
+ with Bugzilla. Currently, MySQL (<xref linkend="mysql"/>),
+ PostgreSQL (<xref linkend="postgresql"/>) and Oracle (<xref linkend="oracle"/>)
+ are available.
+ </para>
+
+ <section id="database-schema">
+ <title>Bugzilla Database Schema</title>
+
+ <para>
+ The Bugzilla database schema is available at
+ <ulink url="http://www.ravenbrook.com/project/p4dti/tool/cgi/bugzilla-schema/">Ravenbrook</ulink>.
+ This very valuable tool can generate a written description of
+ the Bugzilla database schema for any version of Bugzilla. It
+ can also generate a diff between two versions to help someone
+ see what has changed.
+ </para>
+ </section>
+
+ <section id="mysql">
+ <title>MySQL</title>
+
+ <caution>
+ <para>
+ MySQL's default configuration is insecure.
+ We highly recommend to run <filename>mysql_secure_installation</filename>
+ on Linux or the MySQL installer on Windows, and follow the instructions.
+ Important points to note are:
+ <orderedlist>
+ <listitem>
+ <para>Be sure that the root account has a secure password set.</para>
+ </listitem>
+ <listitem>
+ <para>Do not create an anonymous account, and if it exists, say "yes"
+ to remove it.</para>
+ </listitem>
+ <listitem>
+ <para>If your web server and MySQL server are on the same machine,
+ you should disable the network access.</para>
+ </listitem>
+ </orderedlist>
+ </para>
+ </caution>
+
+ <section id="mysql-max-allowed-packet">
+ <title>Allow large attachments and many comments</title>
+
+ <para>By default, MySQL will only allow you to insert things
+ into the database that are smaller than 1MB. Attachments
+ may be larger than this. Also, Bugzilla combines all comments
+ on a single bug into one field for full-text searching, and the
+ combination of all comments on a single bug could in some cases
+ be larger than 1MB.</para>
+
+ <para>To change MySQL's default, you need to edit your MySQL
+ configuration file, which is usually <filename>/etc/my.cnf</filename>
+ on Linux. We recommend that you allow at least 4MB packets by
+ adding the "max_allowed_packet" parameter to your MySQL
+ configuration in the "[mysqld]" section, like this:</para>
+
+ <screen>[mysqld]
+# Allow packets up to 4MB
+max_allowed_packet=4M
+ </screen>
+ </section>
+
+ <section>
+ <title>Allow small words in full-text indexes</title>
+
+ <para>By default, words must be at least four characters in length
+ in order to be indexed by MySQL's full-text indexes. This causes
+ a lot of Bugzilla specific words to be missed, including "cc",
+ "ftp" and "uri".</para>
+
+ <para>MySQL can be configured to index those words by setting the
+ ft_min_word_len param to the minimum size of the words to index.
+ This can be done by modifying the <filename>/etc/my.cnf</filename>
+ according to the example below:</para>
+
+ <screen> [mysqld]
+ # Allow small words in full-text indexes
+ ft_min_word_len=2</screen>
+
+ <para>Rebuilding the indexes can be done based on documentation found at
+ <ulink url="http://www.mysql.com/doc/en/Fulltext_Fine-tuning.html"/>.
+ </para>
+ </section>
+
+ <section id="install-setupdatabase-adduser">
+ <title>Add a user to MySQL</title>
+
+ <para>
+ You need to add a new MySQL user for Bugzilla to use.
+ (It's not safe to have Bugzilla use the MySQL root account.)
+ The following instructions assume the defaults in
+ <filename>localconfig</filename>; if you changed those,
+ you need to modify the SQL command appropriately. You will
+ need the <replaceable>$db_pass</replaceable> password you
+ set in <filename>localconfig</filename> in
+ <xref linkend="localconfig"/>.
+ </para>
+
+ <para>
+ We use an SQL <command>GRANT</command> command to create
+ a <quote>bugs</quote> user. This also restricts the
+ <quote>bugs</quote>user to operations within a database
+ called <quote>bugs</quote>, and only allows the account
+ to connect from <quote>localhost</quote>. Modify it to
+ reflect your setup if you will be connecting from another
+ machine or as a different user.
+ </para>
+
+ <para>
+ Run the <filename>mysql</filename> command-line client and enter:
+ </para>
+
+ <screen>
+ <prompt>mysql&gt;</prompt> GRANT SELECT, INSERT,
+ UPDATE, DELETE, INDEX, ALTER, CREATE, LOCK TABLES,
+ CREATE TEMPORARY TABLES, DROP, REFERENCES ON bugs.*
+ TO bugs@localhost IDENTIFIED BY '<replaceable>$db_pass</replaceable>';
+ <prompt>mysql&gt;</prompt> FLUSH PRIVILEGES;
+ </screen>
+ </section>
+
+ <section>
+ <title>Permit attachments table to grow beyond 4GB</title>
+
+ <para>
+ By default, MySQL will limit the size of a table to 4GB.
+ This limit is present even if the underlying filesystem
+ has no such limit. To set a higher limit, follow these
+ instructions.
+ </para>
+
+ <para>
+ After you have completed the rest of the installation (or at least the
+ database setup parts), you should run the <filename>MySQL</filename>
+ command-line client and enter the following, replacing <literal>$bugs_db</literal>
+ with your Bugzilla database name (<emphasis>bugs</emphasis> by default):
+ </para>
+
+ <screen>
+ <prompt>mysql&gt;</prompt> use <replaceable>$bugs_db</replaceable>
+ <prompt>mysql&gt;</prompt> ALTER TABLE attachments
+ AVG_ROW_LENGTH=1000000, MAX_ROWS=20000;
+ </screen>
+
+ <para>
+ The above command will change the limit to 20GB. Mysql will have
+ to make a temporary copy of your entire table to do this. Ideally,
+ you should do this when your attachments table is still small.
+ </para>
+
+ <note>
+ <para>
+ This does not affect Big Files, attachments that are stored directly
+ on disk instead of in the database.
+ </para>
+ </note>
+ </section>
+ </section>
+
+ <section id="postgresql">
+ <title>PostgreSQL</title>
+ <section>
+ <title>Add a User to PostgreSQL</title>
+
+ <para>You need to add a new user to PostgreSQL for the Bugzilla
+ application to use when accessing the database. The following instructions
+ assume the defaults in <filename>localconfig</filename>; if you
+ changed those, you need to modify the commands appropriately. You will
+ need the <replaceable>$db_pass</replaceable> password you
+ set in <filename>localconfig</filename> in
+ <xref linkend="localconfig"/>.</para>
+
+ <para>On most systems, to create the user in PostgreSQL, you will need to
+ login as the root user, and then</para>
+
+ <screen> <prompt>bash#</prompt> su - postgres</screen>
+
+ <para>As the postgres user, you then need to create a new user: </para>
+
+ <screen> <prompt>bash$</prompt> createuser -U postgres -dRSP bugs</screen>
+
+ <para>When asked for a password, provide the password which will be set as
+ <replaceable>$db_pass</replaceable> in <filename>localconfig</filename>.
+ The created user will not be a superuser (-S) and will not be able to create
+ new users (-R). He will only have the ability to create databases (-d).</para>
+
+ <note>
+ <para>If your are running PostgreSQL 8.0, you must replace -dRSP by -dAP.</para>
+ </note>
+ </section>
+
+ <section>
+ <title>Configure PostgreSQL</title>
+
+ <para>Now, you will need to edit <filename>pg_hba.conf</filename> which is
+ usually located in <filename>/var/lib/pgsql/data/</filename>. In this file,
+ you will need to add a new line to it as follows:</para>
+
+ <para>
+ <computeroutput>host all bugs 127.0.0.1 255.255.255.255 md5</computeroutput>
+ </para>
+
+ <para>This means that for TCP/IP (host) connections, allow connections from
+ '127.0.0.1' to 'all' databases on this server from the 'bugs' user, and use
+ password authentication (md5) for that user.</para>
+
+ <para>Now, you will need to restart PostgreSQL, but you will need to fully
+ stop and start the server rather than just restarting due to the possibility
+ of a change to <filename>postgresql.conf</filename>. After the server has
+ restarted, you will need to edit <filename>localconfig</filename>, finding
+ the <literal>$db_driver</literal> variable and setting it to
+ <literal>Pg</literal> and changing the password in <literal>$db_pass</literal>
+ to the one you picked previously, while setting up the account.</para>
+ </section>
+ </section>
+
+ <section id="oracle">
+ <title>Oracle</title>
+ <section>
+ <title>Create a New Tablespace</title>
+
+ <para>
+ You can use the existing tablespace or create a new one for Bugzilla.
+ To create a new tablespace, run the following command:
+ </para>
+
+ <programlisting>
+ CREATE TABLESPACE bugs
+ DATAFILE '<replaceable>$path_to_datafile</replaceable>' SIZE 500M
+ AUTOEXTEND ON NEXT 30M MAXSIZE UNLIMITED
+ </programlisting>
+
+ <para>
+ Here, the name of the tablespace is 'bugs', but you can
+ choose another name. <replaceable>$path_to_datafile</replaceable> is
+ the path to the file containing your database, for instance
+ <filename>/u01/oradata/bugzilla.dbf</filename>.
+ The initial size of the database file is set in this example to 500 Mb,
+ with an increment of 30 Mb everytime we reach the size limit of the file.
+ </para>
+ </section>
+
+ <section>
+ <title>Add a User to Oracle</title>
+
+ <para>
+ The user name and password must match what you set in
+ <filename>localconfig</filename> (<literal>$db_user</literal>
+ and <literal>$db_pass</literal>, respectively). Here, we assume that
+ the user name is 'bugs' and the tablespace name is the same
+ as above.
+ </para>
+
+ <programlisting>
+ CREATE USER bugs
+ IDENTIFIED BY "<replaceable>$db_pass</replaceable>"
+ DEFAULT TABLESPACE bugs
+ TEMPORARY TABLESPACE TEMP
+ PROFILE DEFAULT;
+ -- GRANT/REVOKE ROLE PRIVILEGES
+ GRANT CONNECT TO bugs;
+ GRANT RESOURCE TO bugs;
+ -- GRANT/REVOKE SYSTEM PRIVILEGES
+ GRANT UNLIMITED TABLESPACE TO bugs;
+ GRANT EXECUTE ON CTXSYS.CTX_DDL TO bugs;
+ </programlisting>
+ </section>
+
+ <section>
+ <title>Configure the Web Server</title>
+
+ <para>
+ If you use Apache, append these lines to <filename>httpd.conf</filename>
+ to set ORACLE_HOME and LD_LIBRARY_PATH. For instance:
+ </para>
+
+ <programlisting>
+ SetEnv ORACLE_HOME /u01/app/oracle/product/10.2.0/
+ SetEnv LD_LIBRARY_PATH /u01/app/oracle/product/10.2.0/lib/
+ </programlisting>
+
+ <para>
+ When this is done, restart your web server.
+ </para>
+ </section>
+ </section>
+ </section>
+
+ <section>
+ <title>checksetup.pl</title>
+
+ <para>
+ Next, rerun <filename>checksetup.pl</filename>. It reconfirms
+ that all the modules are present, and notices the altered
+ localconfig file, which it assumes you have edited to your
+ satisfaction. It compiles the UI templates,
+ connects to the database using the 'bugs'
+ user you created and the password you defined, and creates the
+ 'bugs' database and the tables therein.
+ </para>
+
+ <para>
+ After that, it asks for details of an administrator account. Bugzilla
+ can have multiple administrators - you can create more later - but
+ it needs one to start off with.
+ Enter the email address of an administrator, his or her full name,
+ and a suitable Bugzilla password.
+ </para>
+
+ <para>
+ <filename>checksetup.pl</filename> will then finish. You may rerun
+ <filename>checksetup.pl</filename> at any time if you wish.
+ </para>
+ </section>
+
+
+ <section id="http">
+ <title>Web server</title>
+ <para>
+ Configure your web server according to the instructions in the
+ appropriate section. (If it makes a difference in your choice,
+ the Bugzilla Team recommends Apache.) To check whether your web server
+ is correctly configured, try to access <filename>testagent.cgi</filename>
+ from your web server. If "OK" is displayed, then your configuration
+ is successful. Regardless of which web server
+ you are using, however, ensure that sensitive information is
+ not remotely available by properly applying the access controls in
+ <xref linkend="security-webserver-access"/>. You can run
+ <filename>testserver.pl</filename> to check if your web server serves
+ Bugzilla files as expected.
+ </para>
+
+ <section id="http-apache">
+ <title>Bugzilla using Apache</title>
+ <para>You have two options for running Bugzilla under Apache -
+ <link linkend="http-apache-mod_cgi">mod_cgi</link> (the default) and
+ <link linkend="http-apache-mod_perl">mod_perl</link> (new in Bugzilla
+ 2.23)
+ </para>
+ <section id="http-apache-mod_cgi">
+ <title>Apache <productname>httpd</productname> with mod_cgi</title>
+
+ <para>
+ To configure your Apache web server to work with Bugzilla while using
+ mod_cgi, do the following:
+ </para>
+
+ <procedure>
+ <step>
+ <para>
+ Load <filename>httpd.conf</filename> in your editor.
+ In Fedora and Red Hat Linux, this file is found in
+ <filename class="directory">/etc/httpd/conf</filename>.
+ </para>
+ </step>
+
+ <step>
+ <para>
+ Apache uses <computeroutput>&lt;Directory&gt;</computeroutput>
+ directives to permit fine-grained permission setting. Add the
+ following lines to a directive that applies to the location
+ of your Bugzilla installation. (If such a section does not
+ exist, you'll want to add one.) In this example, Bugzilla has
+ been installed at
+ <filename class="directory">/var/www/html/bugzilla</filename>.
+ </para>
+
+ <programlisting>
+ &lt;Directory /var/www/html/bugzilla&gt;
+ AddHandler cgi-script .cgi
+ Options +Indexes +ExecCGI
+ DirectoryIndex index.cgi
+ AllowOverride Limit FileInfo Indexes
+ &lt;/Directory&gt;
+ </programlisting>
+
+ <para>
+ These instructions: allow apache to run .cgi files found
+ within the bugzilla directory; instructs the server to look
+ for a file called <filename>index.cgi</filename> if someone
+ only types the directory name into the browser; and allows
+ Bugzilla's <filename>.htaccess</filename> files to override
+ global permissions.
+ </para>
+
+ <note>
+ <para>
+ It is possible to make these changes globally, or to the
+ directive controlling Bugzilla's parent directory (e.g.
+ <computeroutput>&lt;Directory /var/www/html/&gt;</computeroutput>).
+ Such changes would also apply to the Bugzilla directory...
+ but they would also apply to many other places where they
+ may or may not be appropriate. In most cases, including
+ this one, it is better to be as restrictive as possible
+ when granting extra access.
+ </para>
+ </note>
+
+ <note>
+ <para>
+ On Windows, you may have to also add the
+ <computeroutput>ScriptInterpreterSource Registry-Strict</computeroutput>
+ line, see <link linkend="win32-http">Windows specific notes</link>.
+ </para>
+ </note>
+ </step>
+
+ <step>
+ <para>
+ <filename>checksetup.pl</filename> can set tighter permissions
+ on Bugzilla's files and directories if it knows what group the
+ web server runs as. Find the <computeroutput>Group</computeroutput>
+ line in <filename>httpd.conf</filename>, place the value found
+ there in the <replaceable>$webservergroup</replaceable> variable
+ in <filename>localconfig</filename>, then rerun
+ <filename>checksetup.pl</filename>.
+ </para>
+ </step>
+
+ <step>
+ <para>
+ Optional: If Bugzilla does not actually reside in the webspace
+ directory, but instead has been symbolically linked there, you
+ will need to add the following to the
+ <computeroutput>Options</computeroutput> line of the Bugzilla
+ <computeroutput>&lt;Directory&gt;</computeroutput> directive
+ (the same one as in the step above):
+ </para>
+
+ <programlisting>
+ +FollowSymLinks
+ </programlisting>
+
+ <para>
+ Without this directive, Apache will not follow symbolic links
+ to places outside its own directory structure, and you will be
+ unable to run Bugzilla.
+ </para>
+ </step>
+ </procedure>
+ </section>
+ <section id="http-apache-mod_perl">
+ <title>Apache <productname>httpd</productname> with mod_perl</title>
+
+ <para>Some configuration is required to make Bugzilla work with Apache
+ and mod_perl</para>
+
+ <procedure>
+ <step>
+ <para>
+ Load <filename>httpd.conf</filename> in your editor.
+ In Fedora and Red Hat Linux, this file is found in
+ <filename class="directory">/etc/httpd/conf</filename>.
+ </para>
+ </step>
+
+ <step>
+ <para>Add the following information to your httpd.conf file, substituting
+ where appropriate with your own local paths.</para>
+
+ <note>
+ <para>This should be used instead of the &lt;Directory&gt; block
+ shown above. This should also be above any other <literal>mod_perl</literal>
+ directives within the <filename>httpd.conf</filename> and must be specified
+ in the order as below.</para>
+ </note>
+ <warning>
+ <para>You should also ensure that you have disabled <literal>KeepAlive</literal>
+ support in your Apache install when utilizing Bugzilla under mod_perl</para>
+ </warning>
+
+ <programlisting>
+ PerlSwitches -w -T
+ PerlConfigRequire /var/www/html/bugzilla/mod_perl.pl
+ </programlisting>
+ </step>
+
+ <step>
+ <para>
+ <filename>checksetup.pl</filename> can set tighter permissions
+ on Bugzilla's files and directories if it knows what group the
+ web server runs as. Find the <computeroutput>Group</computeroutput>
+ line in <filename>httpd.conf</filename>, place the value found
+ there in the <replaceable>$webservergroup</replaceable> variable
+ in <filename>localconfig</filename>, then rerun
+ <filename>checksetup.pl</filename>.
+ </para>
+ </step>
+ </procedure>
+
+ <para>On restarting Apache, Bugzilla should now be running within the
+ mod_perl environment. Please ensure you have run checksetup.pl to set
+ permissions before you restart Apache.</para>
+
+ <note>
+ <para>Please bear the following points in mind when looking at using
+ Bugzilla under mod_perl:
+ <itemizedlist>
+ <listitem>
+ <para>
+ mod_perl support in Bugzilla can take up a HUGE amount of RAM. You could be
+ looking at 30MB per httpd child, easily. Basically, you just need a lot of RAM.
+ The more RAM you can get, the better. mod_perl is basically trading RAM for
+ speed. At least 2GB total system RAM is recommended for running Bugzilla under
+ mod_perl.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Under mod_perl, you have to restart Apache if you make any manual change to
+ any Bugzilla file. You can't just reload--you have to actually
+ <emphasis>restart</emphasis> the server (as in make sure it stops and starts
+ again). You <emphasis>can</emphasis> change localconfig and the params file
+ manually, if you want, because those are re-read every time you load a page.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ You must run in Apache's Prefork MPM (this is the default). The Worker MPM
+ may not work--we haven't tested Bugzilla's mod_perl support under threads.
+ (And, in fact, we're fairly sure it <emphasis>won't</emphasis> work.)
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Bugzilla generally expects to be the only mod_perl application running on
+ your entire server. It may or may not work if there are other applications also
+ running under mod_perl. It does try its best to play nice with other mod_perl
+ applications, but it still may have conflicts.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ It is recommended that you have one Bugzilla instance running under mod_perl
+ on your server. Bugzilla has not been tested with more than one instance running.
+ </para>
+ </listitem>
+ </itemizedlist>
+ </para>
+ </note>
+ </section>
+ </section>
+
+ <section id="http-iis">
+ <title>Microsoft <productname>Internet Information Services</productname></title>
+
+ <para>
+ If you are running Bugzilla on Windows and choose to use
+ Microsoft's <productname>Internet Information Services</productname>
+ or <productname>Personal Web Server</productname> you will need
+ to perform a number of other configuration steps as explained below.
+ You may also want to refer to the following Microsoft Knowledge
+ Base articles:
+ <ulink url="http://support.microsoft.com/default.aspx?scid=kb;en-us;245225">245225</ulink>
+ <quote>HOW TO: Configure and Test a PERL Script with IIS 4.0,
+ 5.0, and 5.1</quote> (for <productname>Internet Information
+ Services</productname>) and
+ <ulink url="http://support.microsoft.com/default.aspx?scid=kb;en-us;231998">231998</ulink>
+ <quote>HOW TO: FP2000: How to Use Perl with Microsoft Personal Web
+ Server on Windows 95/98</quote> (for <productname>Personal Web
+ Server</productname>).
+ </para>
+
+ <para>
+ You will need to create a virtual directory for the Bugzilla
+ install. Put the Bugzilla files in a directory that is named
+ something <emphasis>other</emphasis> than what you want your
+ end-users accessing. That is, if you want your users to access
+ your Bugzilla installation through
+ <quote>http://&lt;yourdomainname&gt;/Bugzilla</quote>, then do
+ <emphasis>not</emphasis> put your Bugzilla files in a directory
+ named <quote>Bugzilla</quote>. Instead, place them in a different
+ location, and then use the IIS Administration tool to create a
+ Virtual Directory named "Bugzilla" that acts as an alias for the
+ actual location of the files. When creating that virtual directory,
+ make sure you add the <quote>Execute (such as ISAPI applications or
+ CGI)</quote> access permission.
+ </para>
+
+ <para>
+ You will also need to tell IIS how to handle Bugzilla's
+ .cgi files. Using the IIS Administration tool again, open up
+ the properties for the new virtual directory and select the
+ Configuration option to access the Script Mappings. Create an
+ entry mapping .cgi to:
+ </para>
+
+ <programlisting>
+&lt;full path to perl.exe &gt;\perl.exe -x&lt;full path to Bugzilla&gt; -wT "%s" %s
+ </programlisting>
+
+ <para>
+ For example:
+ </para>
+
+ <programlisting>
+c:\perl\bin\perl.exe -xc:\bugzilla -wT "%s" %s
+ </programlisting>
+
+ <note>
+ <para>
+ The ActiveState install may have already created an entry for
+ .pl files that is limited to <quote>GET,HEAD,POST</quote>. If
+ so, this mapping should be <emphasis>removed</emphasis> as
+ Bugzilla's .pl files are not designed to be run via a web server.
+ </para>
+ </note>
+
+ <para>
+ IIS will also need to know that the index.cgi should be treated
+ as a default document. On the Documents tab page of the virtual
+ directory properties, you need to add index.cgi as a default
+ document type. If you wish, you may remove the other default
+ document types for this particular virtual directory, since Bugzilla
+ doesn't use any of them.
+ </para>
+
+ <para>
+ Also, and this can't be stressed enough, make sure that files
+ such as <filename>localconfig</filename> and your
+ <filename class="directory">data</filename> directory are
+ secured as described in <xref linkend="security-webserver-access"/>.
+ </para>
+
+ </section>
+
+ </section>
+
+ <section id="install-config-bugzilla">
+ <title>Bugzilla</title>
+
+ <para>
+ Your Bugzilla should now be working. Access
+ <filename>http://&lt;your-bugzilla-server&gt;/</filename> -
+ you should see the Bugzilla
+ front page. If not, consult the Troubleshooting section,
+ <xref linkend="troubleshooting"/>.
+ </para>
+
+ <note>
+ <para>
+ The URL above may be incorrect if you installed Bugzilla into a
+ subdirectory or used a symbolic link from your web site root to
+ the Bugzilla directory.
+ </para>
+ </note>
+
+ <para>
+ Log in with the administrator account you defined in the last
+ <filename>checksetup.pl</filename> run. You should go through
+ the Parameters page and see if there are any you wish to change.
+ They key parameters are documented in <xref linkend="parameters"/>;
+ you should certainly alter
+ <command>maintainer</command> and <command>urlbase</command>;
+ you may also want to alter
+ <command>cookiepath</command> or <command>requirelogin</command>.
+ </para>
+
+ <para>
+ Bugzilla has several optional features which require extra
+ configuration. You can read about those in
+ <xref linkend="extraconfig"/>.
+ </para>
+ </section>
+ </section>
+
+ <section id="extraconfig">
+ <title>Optional Additional Configuration</title>
+
+ <para>
+ Bugzilla has a number of optional features. This section describes how
+ to configure or enable them.
+ </para>
+
+ <section>
+ <title>Bug Graphs</title>
+
+ <para>If you have installed the necessary Perl modules you
+ can start collecting statistics for the nifty Bugzilla
+ graphs.</para>
+
+ <screen><prompt>bash#</prompt> <command>crontab -e</command></screen>
+
+ <para>
+ This should bring up the crontab file in your editor.
+ Add a cron entry like this to run
+ <filename>collectstats.pl</filename>
+ daily at 5 after midnight:
+ </para>
+
+ <programlisting>5 0 * * * cd &lt;your-bugzilla-directory&gt; ; ./collectstats.pl</programlisting>
+
+ <para>
+ After two days have passed you'll be able to view bug graphs from
+ the Reports page.
+ </para>
+
+ <note>
+ <para>
+ Windows does not have 'cron', but it does have the Task
+ Scheduler, which performs the same duties. There are also
+ third-party tools that can be used to implement cron, such as
+ <ulink url="http://www.nncron.ru/">nncron</ulink>.
+ </para>
+ </note>
+ </section>
+
+ <section id="installation-whining-cron">
+ <title>The Whining Cron</title>
+
+ <para>What good are
+ bugs if they're not annoying? To help make them more so you
+ can set up Bugzilla's automatic whining system to complain at engineers
+ which leave their bugs in the CONFIRMED state without triaging them.
+ </para>
+ <para>
+ This can be done by adding the following command as a daily
+ crontab entry, in the same manner as explained above for bug
+ graphs. This example runs it at 12.55am.
+ </para>
+
+ <programlisting>55 0 * * * cd &lt;your-bugzilla-directory&gt; ; ./whineatnews.pl</programlisting>
+
+ <note>
+ <para>
+ Windows does not have 'cron', but it does have the Task
+ Scheduler, which performs the same duties. There are also
+ third-party tools that can be used to implement cron, such as
+ <ulink url="http://www.nncron.ru/">nncron</ulink>.
+ </para>
+ </note>
+ </section>
+
+ <section id="installation-whining">
+ <title>Whining</title>
+
+ <para>
+ As of Bugzilla 2.20, users can configure Bugzilla to regularly annoy
+ them at regular intervals, by having Bugzilla execute saved searches
+ at certain times and emailing the results to the user. This is known
+ as "Whining". The process of configuring Whining is described
+ in <xref linkend="whining"/>, but for it to work a Perl script must be
+ executed at regular intervals.
+ </para>
+
+ <para>
+ This can be done by adding the following command as a daily
+ crontab entry, in the same manner as explained above for bug
+ graphs. This example runs it every 15 minutes.
+ </para>
+
+ <programlisting>*/15 * * * * cd &lt;your-bugzilla-directory&gt; ; ./whine.pl</programlisting>
+
+ <note>
+ <para>
+ Whines can be executed as often as every 15 minutes, so if you specify
+ longer intervals between executions of whine.pl, some users may not
+ be whined at as often as they would expect. Depending on the person,
+ this can either be a very Good Thing or a very Bad Thing.
+ </para>
+ </note>
+
+ <note>
+ <para>
+ Windows does not have 'cron', but it does have the Task
+ Scheduler, which performs the same duties. There are also
+ third-party tools that can be used to implement cron, such as
+ <ulink url="http://www.nncron.ru/">nncron</ulink>.
+ </para>
+ </note>
+ </section>
+
+ <section id="apache-addtype">
+ <title>Serving Alternate Formats with the right MIME type</title>
+
+ <para>
+ Some Bugzilla pages have alternate formats, other than just plain
+ <acronym>HTML</acronym>. In particular, a few Bugzilla pages can
+ output their contents as either <acronym>XUL</acronym> (a special
+ Mozilla format, that looks like a program <acronym>GUI</acronym>)
+ or <acronym>RDF</acronym> (a type of structured <acronym>XML</acronym>
+ that can be read by various programs).
+ </para>
+ <para>
+ In order for your users to see these pages correctly, Apache must
+ send them with the right <acronym>MIME</acronym> type. To do this,
+ add the following lines to your Apache configuration, either in the
+ <computeroutput>&lt;VirtualHost&gt;</computeroutput> section for your
+ Bugzilla, or in the <computeroutput>&lt;Directory&gt;</computeroutput>
+ section for your Bugzilla:
+ </para>
+ <para>
+ <screen>AddType application/vnd.mozilla.xul+xml .xul
+AddType application/rdf+xml .rdf</screen>
+ </para>
+ </section>
+ </section>
+
+ <section id="multiple-bz-dbs">
+ <title>Multiple Bugzilla databases with a single installation</title>
+
+ <para>The previous instructions referred to a standard installation, with
+ one unique Bugzilla database. However, you may want to host several
+ distinct installations, without having several copies of the code. This is
+ possible by using the PROJECT environment variable. When accessed,
+ Bugzilla checks for the existence of this variable, and if present, uses
+ its value to check for an alternative configuration file named
+ <filename>localconfig.&lt;PROJECT&gt;</filename> in the same location as
+ the default one (<filename>localconfig</filename>). It also checks for
+ customized templates in a directory named
+ <filename>&lt;PROJECT&gt;</filename> in the same location as the
+ default one (<filename>template/&lt;langcode&gt;</filename>). By default
+ this is <filename>template/en/default</filename> so PROJECT's templates
+ would be located at <filename>template/en/PROJECT</filename>.</para>
+
+ <para>To set up an alternate installation, just export PROJECT=foo before
+ running <command>checksetup.pl</command> for the first time. It will
+ result in a file called <filename>localconfig.foo</filename> instead of
+ <filename>localconfig</filename>. Edit this file as described above, with
+ reference to a new database, and re-run <command>checksetup.pl</command>
+ to populate it. That's all.</para>
+
+ <para>Now you have to configure the web server to pass this environment
+ variable when accessed via an alternate URL, such as virtual host for
+ instance. The following is an example of how you could do it in Apache,
+ other Webservers may differ.
+<programlisting>
+&lt;VirtualHost 212.85.153.228:80&gt;
+ ServerName foo.bar.baz
+ SetEnv PROJECT foo
+ Alias /bugzilla /var/www/bugzilla
+&lt;/VirtualHost&gt;
+</programlisting>
+ </para>
+
+ <para>Don't forget to also export this variable before accessing Bugzilla
+ by other means, such as cron tasks for instance.</para>
+ </section>
+
+ <section id="os-specific">
+ <title>OS-Specific Installation Notes</title>
+
+ <para>Many aspects of the Bugzilla installation can be affected by the
+ operating system you choose to install it on. Sometimes it can be made
+ easier and others more difficult. This section will attempt to help you
+ understand both the difficulties of running on specific operating systems
+ and the utilities available to make it easier.
+ </para>
+
+ <para>If you have anything to add or notes for an operating system not
+ covered, please file a bug in &bzg-bugs;.
+ </para>
+
+ <section id="os-win32">
+ <title>Microsoft Windows</title>
+ <para>
+ Making Bugzilla work on Windows is more difficult than making it
+ work on Unix. For that reason, we still recommend doing so on a Unix
+ based system such as GNU/Linux. That said, if you do want to get
+ Bugzilla running on Windows, you will need to make the following
+ adjustments. A detailed step-by-step
+ <ulink url="https://wiki.mozilla.org/Bugzilla:Win32Install">
+ installation guide for Windows</ulink> is also available
+ if you need more help with your installation.
+ </para>
+
+ <section id="win32-perl">
+ <title>Win32 Perl</title>
+ <para>
+ Perl for Windows can be obtained from
+ <ulink url="http://www.activestate.com/">ActiveState</ulink>.
+ You should be able to find a compiled binary at <ulink
+ url="http://aspn.activestate.com/ASPN/Downloads/ActivePerl/" />.
+ The following instructions assume that you are using version
+ 5.8.1 of ActiveState.
+ </para>
+
+ <note>
+ <para>
+ These instructions are for 32-bit versions of Windows. If you are
+ using a 64-bit version of Windows, you will need to install 32-bit
+ Perl in order to install the 32-bit modules as described below.
+ </para>
+ </note>
+
+ </section>
+
+ <section id="win32-perl-modules">
+ <title>Perl Modules on Win32</title>
+
+ <para>
+ Bugzilla on Windows requires the same perl modules found in
+ <xref linkend="install-perlmodules"/>. The main difference is that
+ windows uses <glossterm linkend="gloss-ppm">PPM</glossterm> instead
+ of CPAN. ActiveState provides a GUI to manage Perl modules. We highly
+ recommend that you use it. If you prefer to use ppm from the
+ command-line, type:
+ </para>
+
+ <programlisting>
+C:\perl&gt; <command>ppm install &lt;module name&gt;</command>
+ </programlisting>
+
+ <para>
+ The best source for the Windows PPM modules needed for Bugzilla
+ is probably the theory58S website, which you can add to your list
+ of repositories as follows (for Perl 5.8.x):
+ </para>
+
+ <programlisting>
+<command>ppm repo add theory58S http://theoryx5.uwinnipeg.ca/ppms/</command>
+ </programlisting>
+
+ <para>
+ If you are using Perl 5.10.x, you cannot use the same PPM modules as Perl
+ 5.8.x as they are incompatible. In this case, you should add the following
+ repository:
+ </para>
+ <programlisting>
+<command>ppm repo add theory58S http://cpan.uwinnipeg.ca/PPMPackages/10xx/</command>
+ </programlisting>
+
+ <note>
+ <para>
+ In versions prior to 5.8.8 build 819 of PPM the command is
+ <programlisting>
+<command>ppm repository add theory58S http://theoryx5.uwinnipeg.ca/ppms/</command>
+ </programlisting>
+ </para>
+ </note>
+ <note>
+ <para>
+ The PPM repository stores modules in 'packages' that may have
+ a slightly different name than the module. If retrieving these
+ modules from there, you will need to pay attention to the information
+ provided when you run <command>checksetup.pl</command> as it will
+ tell you what package you'll need to install.
+ </para>
+ </note>
+
+ <tip>
+ <para>
+ If you are behind a corporate firewall, you will need to let the
+ ActiveState PPM utility know how to get through it to access
+ the repositories by setting the HTTP_proxy system environmental
+ variable. For more information on setting that variable, see
+ the ActiveState documentation.
+ </para>
+ </tip>
+ </section>
+
+ <section id="win32-http">
+ <title>Serving the web pages</title>
+
+ <para>
+ As is the case on Unix based systems, any web server should
+ be able to handle Bugzilla; however, the Bugzilla Team still
+ recommends Apache whenever asked. No matter what web server
+ you choose, be sure to pay attention to the security notes
+ in <xref linkend="security-webserver-access"/>. More
+ information on configuring specific web servers can be found
+ in <xref linkend="http"/>.
+ </para>
+
+ <note>
+ <para>
+ The web server looks at <filename>/usr/bin/perl</filename> to
+ call Perl. If you are using Apache on windows, you can set the
+ <ulink url="http://httpd.apache.org/docs-2.0/mod/core.html#scriptinterpretersource">ScriptInterpreterSource</ulink>
+ directive in your Apache config file to make it look at the
+ right place: insert the line
+ <programlisting>ScriptInterpreterSource Registry-Strict</programlisting>
+ into your <filename>httpd.conf</filename> file, and create the key
+ <programlisting>HKEY_CLASSES_ROOT\.cgi\Shell\ExecCGI\Command</programlisting>
+ with <option>C:\Perl\bin\perl.exe -T</option> as value (adapt to your
+ path if needed) in the registry. When this is done, restart Apache.
+ </para>
+ </note>
+
+ </section>
+
+ <section id="win32-email">
+ <title>Sending Email</title>
+
+ <para>
+ To enable Bugzilla to send email on Windows, the server running the
+ Bugzilla code must be able to connect to, or act as, an SMTP server.
+ </para>
+
+ </section>
+ </section>
+
+ <section id="os-macosx">
+ <title><productname>Mac OS X</productname></title>
+
+ <para>Making Bugzilla work on Mac OS X requires the following
+ adjustments.</para>
+
+ <section id="macosx-sendmail">
+ <title>Sendmail</title>
+
+ <para>In Mac OS X 10.3 and later,
+ <ulink url="http://www.postfix.org/">Postfix</ulink>
+ is used as the built-in email server. Postfix provides an executable
+ that mimics sendmail enough to fool Bugzilla, as long as Bugzilla can
+ find it. Bugzilla is able to find the fake sendmail executable without
+ any assistance.</para>
+
+ </section>
+
+ <section id="macosx-libraries">
+ <title>Libraries &amp; Perl Modules on Mac OS X</title>
+
+ <para>Apple does not include the GD library with Mac OS X. Bugzilla
+ needs this for bug graphs.</para>
+
+ <para>You can use DarwinPorts (<ulink url="http://darwinports.com/"/>)
+ or Fink (<ulink url="http://sourceforge.net/projects/fink/"/>), both
+ of which are similar in nature to the CPAN installer, but install
+ common unix programs.</para>
+
+ <para>Follow the instructions for setting up DarwinPorts or Fink.
+ Once you have one installed, you'll want to use it to install the
+ <filename>gd2</filename> package.
+ </para>
+
+ <para>Fink will prompt you for a number of dependencies, type 'y' and hit
+ enter to install all of the dependencies and then watch it work. You will
+ then be able to use <glossterm linkend="gloss-cpan">CPAN</glossterm> to
+ install the GD Perl module.
+ </para>
+
+ <note>
+ <para>To prevent creating conflicts with the software that Apple
+ installs by default, Fink creates its own directory tree at
+ <filename class="directory">/sw</filename> where it installs most of
+ the software that it installs. This means your libraries and headers
+ will be at <filename class="directory">/sw/lib</filename> and
+ <filename class="directory">/sw/include</filename> instead of
+ <filename class="directory">/usr/lib</filename> and
+ <filename class="directory">/usr/include</filename>. When the
+ Perl module config script asks where your <filename>libgd</filename>
+ is, be sure to tell it
+ <filename class="directory">/sw/lib</filename>.
+ </para>
+ </note>
+
+ <para>Also available via DarwinPorts and Fink is
+ <filename>expat</filename>. After installing the expat package, you
+ will be able to install XML::Parser using CPAN. If you use fink, there
+ is one caveat. Unlike recent versions of
+ the GD module, XML::Parser doesn't prompt for the location of the
+ required libraries. When using CPAN, you will need to use the following
+ command sequence:
+ </para>
+
+ <screen>
+# perl -MCPAN -e'look XML::Parser' <co id="macosx-look"/>
+# perl Makefile.PL EXPATLIBPATH=/sw/lib EXPATINCPATH=/sw/include
+# make; make test; make install <co id="macosx-make"/>
+# exit <co id="macosx-exit"/>
+ </screen>
+ <calloutlist>
+ <callout arearefs="macosx-look macosx-exit">
+ <para>The look command will download the module and spawn a
+ new shell with the extracted files as the current working directory.
+ The exit command will return you to your original shell.
+ </para>
+ </callout>
+ <callout arearefs="macosx-make">
+ <para>You should watch the output from these make commands,
+ especially <quote>make test</quote> as errors may prevent
+ XML::Parser from functioning correctly with Bugzilla.
+ </para>
+ </callout>
+ </calloutlist>
+ </section>
+ </section>
+
+ <section id="os-linux">
+ <title>Linux Distributions</title>
+ <para>Many Linux distributions include Bugzilla and its
+ dependencies in their native package management systems.
+ Installing Bugzilla with root access on any Linux system
+ should be as simple as finding the Bugzilla package in the
+ package management application and installing it using the
+ normal command syntax. Several distributions also perform
+ the proper web server configuration automatically on installation.
+ </para>
+ <para>Please consult the documentation of your Linux
+ distribution for instructions on how to install packages,
+ or for specific instructions on installing Bugzilla with
+ native package management tools. There is also a
+ <ulink url="http://wiki.mozilla.org/Bugzilla:Linux_Distro_Installation">
+ Bugzilla Wiki Page</ulink> for distro-specific installation
+ notes.
+ </para>
+ </section>
+ </section>
+
+
+ <section id="nonroot">
+ <title>UNIX (non-root) Installation Notes</title>
+
+ <section>
+ <title>Introduction</title>
+
+ <para>If you are running a *NIX OS as non-root, either due
+ to lack of access (web hosts, for example) or for security
+ reasons, this will detail how to install Bugzilla on such
+ a setup. It is recommended that you read through the
+ <xref linkend="installation" />
+ first to get an idea on the installation steps required.
+ (These notes will reference to steps in that guide.)</para>
+
+ </section>
+
+ <section>
+ <title>MySQL</title>
+
+ <para>You may have MySQL installed as root. If you're
+ setting up an account with a web host, a MySQL account
+ needs to be set up for you. From there, you can create
+ the bugs account, or use the account given to you.</para>
+
+ <warning>
+ <para>You may have problems trying to set up
+ <command>GRANT</command> permissions to the database.
+ If you're using a web host, chances are that you have a
+ separate database which is already locked down (or one big
+ database with limited/no access to the other areas), but you
+ may want to ask your system administrator what the security
+ settings are set to, and/or run the <command>GRANT</command>
+ command for you.</para>
+
+ <para>Also, you will probably not be able to change the MySQL
+ root user password (for obvious reasons), so skip that
+ step.</para>
+ </warning>
+
+ <section>
+ <title>Running MySQL as Non-Root</title>
+ <section>
+ <title>The Custom Configuration Method</title>
+ <para>Create a file .my.cnf in your
+ home directory (using /home/foo in this example)
+ as follows....</para>
+ <programlisting>
+[mysqld]
+datadir=/home/foo/mymysql
+socket=/home/foo/mymysql/thesock
+port=8081
+
+[mysql]
+socket=/home/foo/mymysql/thesock
+port=8081
+
+[mysql.server]
+user=mysql
+basedir=/var/lib
+
+[safe_mysqld]
+err-log=/home/foo/mymysql/the.log
+pid-file=/home/foo/mymysql/the.pid
+ </programlisting>
+ </section>
+ <section>
+ <title>The Custom Built Method</title>
+
+ <para>You can install MySQL as a not-root, if you really need to.
+ Build it with PREFIX set to <filename class="directory">/home/foo/mysql</filename>,
+ or use pre-installed executables, specifying that you want
+ to put all of the data files in <filename class="directory">/home/foo/mysql/data</filename>.
+ If there is another MySQL server running on the system that you
+ do not own, use the -P option to specify a TCP port that is not
+ in use.</para>
+ </section>
+
+ <section>
+ <title>Starting the Server</title>
+ <para>After your mysqld program is built and any .my.cnf file is
+ in place, you must initialize the databases (ONCE).</para>
+ <screen>
+ <prompt>bash$</prompt>
+ <command>mysql_install_db</command>
+ </screen>
+ <para>Then start the daemon with</para>
+ <screen>
+ <prompt>bash$</prompt>
+ <command>safe_mysql &amp;</command>
+ </screen>
+ <para>After you start mysqld the first time, you then connect to
+ it as "root" and <command>GRANT</command> permissions to other
+ users. (Again, the MySQL root account has nothing to do with
+ the *NIX root account.)</para>
+
+ <note>
+ <para>You will need to start the daemons yourself. You can either
+ ask your system administrator to add them to system startup files, or
+ add a crontab entry that runs a script to check on these daemons
+ and restart them if needed.</para>
+ </note>
+
+ <warning>
+ <para>Do NOT run daemons or other services on a server without first
+ consulting your system administrator! Daemons use up system resources
+ and running one may be in violation of your terms of service for any
+ machine on which you are a user!</para>
+ </warning>
+ </section>
+ </section>
+
+ </section>
+
+ <section>
+ <title>Perl</title>
+
+ <para>
+ On the extremely rare chance that you don't have Perl on
+ the machine, you will have to build the sources
+ yourself. The following commands should get your system
+ installed with your own personal version of Perl:
+ </para>
+
+ <screen>
+ <prompt>bash$</prompt>
+ <command>wget http://perl.org/CPAN/src/stable.tar.gz</command>
+ <prompt>bash$</prompt>
+ <command>tar zvxf stable.tar.gz</command>
+ <prompt>bash$</prompt>
+ <command>cd perl-5.8.1</command> (or whatever the version of Perl is called)
+ <prompt>bash$</prompt>
+ <command>sh Configure -de -Dprefix=/home/foo/perl</command>
+ <prompt>bash$</prompt>
+ <command>make &amp;&amp; make test &amp;&amp; make install</command>
+ </screen>
+
+ <para>
+ Once you have Perl installed into a directory (probably
+ in <filename class="directory">~/perl/bin</filename>), you will need to
+ install the Perl Modules, described below.
+ </para>
+ </section>
+
+ <section id="install-perlmodules-nonroot">
+ <title>Perl Modules</title>
+
+ <para>
+ Installing the Perl modules as a non-root user is accomplished by
+ running the <filename>install-module.pl</filename>
+ script. For more details on this script, see
+ <ulink url="api/install-module.html"><filename>install-module.pl</filename>
+ documentation</ulink>
+ </para>
+ </section>
+
+ <section>
+ <title>HTTP Server</title>
+
+ <para>Ideally, this also needs to be installed as root and
+ run under a special web server account. As long as
+ the web server will allow the running of *.cgi files outside of a
+ cgi-bin, and a way of denying web access to certain files (such as a
+ .htaccess file), you should be good in this department.</para>
+
+ <section>
+ <title>Running Apache as Non-Root</title>
+
+ <para>You can run Apache as a non-root user, but the port will need
+ to be set to one above 1024. If you type <command>httpd -V</command>,
+ you will get a list of the variables that your system copy of httpd
+ uses. One of those, namely HTTPD_ROOT, tells you where that
+ installation looks for its config information.</para>
+
+ <para>From there, you can copy the config files to your own home
+ directory to start editing. When you edit those and then use the -d
+ option to override the HTTPD_ROOT compiled into the web server, you
+ get control of your own customized web server.</para>
+
+ <note>
+ <para>You will need to start the daemons yourself. You can either
+ ask your system administrator to add them to system startup files, or
+ add a crontab entry that runs a script to check on these daemons
+ and restart them if needed.</para>
+ </note>
+
+ <warning>
+ <para>Do NOT run daemons or other services on a server without first
+ consulting your system administrator! Daemons use up system resources
+ and running one may be in violation of your terms of service for any
+ machine on which you are a user!</para>
+ </warning>
+ </section>
+ </section>
+
+ <section>
+ <title>Bugzilla</title>
+
+ <para>
+ When you run <command>./checksetup.pl</command> to create
+ the <filename>localconfig</filename> file, it will list the Perl
+ modules it finds. If one is missing, go back and double-check the
+ module installation from <xref linkend="install-perlmodules-nonroot"/>,
+ then delete the <filename>localconfig</filename> file and try again.
+ </para>
+
+ <warning>
+ <para>One option in <filename>localconfig</filename> you
+ might have problems with is the web server group. If you can't
+ successfully browse to the <filename>index.cgi</filename> (like
+ a Forbidden error), you may have to relax your permissions,
+ and blank out the web server group. Of course, this may pose
+ as a security risk. Having a properly jailed shell and/or
+ limited access to shell accounts may lessen the security risk,
+ but use at your own risk.</para>
+ </warning>
+
+ <section id="suexec">
+ <title>suexec or shared hosting</title>
+
+ <para>If you are running on a system that uses suexec (most shared
+ hosting environments do this), you will need to set the
+ <emphasis>webservergroup</emphasis> value in <filename>localconfig</filename>
+ to match <emphasis>your</emphasis> primary group, rather than the one
+ the web server runs under. You will need to run the following
+ shell commands after running <command>./checksetup.pl</command>,
+ every time you run it (or modify <filename>checksetup.pl</filename>
+ to do them for you via the system() command).
+ <programlisting> for i in docs graphs images js skins; do find $i -type d -exec chmod o+rx {} \; ; done
+ for i in jpg gif css js png html rdf xul; do find . -name \*.$i -exec chmod o+r {} \; ; done
+ find . -name .htaccess -exec chmod o+r {} \;
+ chmod o+x . data data/webdot</programlisting>
+ Pay particular attention to the number of semicolons and dots.
+ They are all important. A future version of Bugzilla will
+ hopefully be able to do this for you out of the box.</para>
+ </section>
+ </section>
+ </section>
+
+
+ <section id="upgrade">
+ <title>Upgrading to New Releases</title>
+
+ <para>Upgrading to new Bugzilla releases is very simple. There is
+ a script included with Bugzilla that will automatically
+ do all of the database migration for you.</para>
+
+ <para>The following sections explain how to upgrade from one
+ version of Bugzilla to another. Whether you are upgrading
+ from one bug-fix version to another (such as 3.0.1 to 3.0.2)
+ or from one major version to another (such as from 3.0 to 3.2),
+ the instructions are always the same.</para>
+
+ <note>
+ <para>
+ Any examples in the following sections are written as though the
+ user were updating to version 2.22.1, but the procedures are the
+ same no matter what version you're updating to. Also, in the
+ examples, the user's Bugzilla installation is found at
+ <filename>/var/www/html/bugzilla</filename>. If that is not the
+ same as the location of your Bugzilla installation, simply
+ substitute the proper paths where appropriate.
+ </para>
+ </note>
+
+ <section id="upgrade-before">
+ <title>Before You Upgrade</title>
+
+ <para>Before you start your upgrade, there are a few important
+ steps to take:</para>
+
+ <orderedlist>
+ <listitem>
+ <para>
+ Read the <ulink url="http://www.bugzilla.org/releases/">Release
+ Notes</ulink> of the version you're upgrading to,
+ particularly the "Notes for Upgraders" section.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ View the Sanity Check (<xref linkend="sanitycheck"/>) page
+ on your installation before upgrading. Attempt to fix all warnings
+ that the page produces before you go any further, or you may
+ experience problems during your upgrade.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Shut down your Bugzilla installation by putting some HTML or
+ text in the shutdownhtml parameter
+ (see <xref linkend="parameters"/>).
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Make a backup of the Bugzilla database.
+ <emphasis>THIS IS VERY IMPORTANT</emphasis>. If
+ anything goes wrong during the upgrade, your installation
+ can be corrupted beyond recovery. Having a backup keeps you safe.
+ </para>
+
+ <warning>
+ <para>
+ Upgrading is a one-way process. You cannot "downgrade" an
+ upgraded Bugzilla. If you wish to revert to the old Bugzilla
+ version for any reason, you will have to restore your database
+ from this backup.
+ </para>
+ </warning>
+
+ <para>Here are some sample commands you could use to backup
+ your database, depending on what database system you're
+ using. You may have to modify these commands for your
+ particular setup.</para>
+
+ <variablelist>
+ <varlistentry>
+ <term>MySQL:</term>
+ <listitem>
+ <para>
+ <command>mysqldump --opt -u bugs -p bugs > bugs.sql</command>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>PostgreSQL:</term>
+ <listitem>
+ <para>
+ <command>pg_dump --no-privileges --no-owner -h localhost -U bugs
+ > bugs.sql</command>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </listitem>
+ </orderedlist>
+ </section>
+
+ <section id="upgrade-files">
+ <title>Getting The New Bugzilla</title>
+
+ <para>There are three ways to get the new version of Bugzilla.
+ We'll list them here briefly and then explain them
+ more later.</para>
+
+ <variablelist>
+ <varlistentry>
+ <term>CVS (<xref linkend="upgrade-cvs"/>)</term>
+ <listitem>
+ <para>
+ If have <command>cvs</command> installed on your machine
+ and you have Internet access, this is the easiest way to
+ upgrade, particularly if you have made modifications
+ to the code or templates of Bugzilla.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>Download the tarball (<xref linkend="upgrade-tarball"/>)</term>
+ <listitem>
+ <para>
+ This is a very simple way to upgrade, and good if you
+ haven't made many (or any) modifications to the code or
+ templates of your Bugzilla.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>Patches (<xref linkend="upgrade-patches"/>)</term>
+ <listitem>
+ <para>
+ If you have made modifications to your Bugzilla, and
+ you don't have Internet access or you don't want to use
+ cvs, then this is the best way to upgrade.
+ </para>
+
+ <para>
+ You can only do minor upgrades (such as 3.0 to 3.0.1 or
+ 3.0.1 to 3.0.2) with patches.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+ <section id="upgrade-modified">
+ <title>If you have modified your Bugzilla</title>
+
+ <para>
+ If you have modified the code or templates of your Bugzilla,
+ then upgrading requires a bit more thought and effort.
+ A discussion of the various methods of updating compared with
+ degree and methods of local customization can be found in
+ <xref linkend="template-method"/>.
+ </para>
+
+ <para>
+ The larger the jump you are trying to make, the more difficult it
+ is going to be to upgrade if you have made local customizations.
+ Upgrading from 3.0 to 3.0.1 should be fairly painless even if
+ you are heavily customized, but going from 2.18 to 3.0 is going
+ to mean a fair bit of work re-writing your local changes to use
+ the new files, logic, templates, etc. If you have done no local
+ changes at all, however, then upgrading should be approximately
+ the same amount of work regardless of how long it has been since
+ your version was released.
+ </para>
+ </section>
+
+ <section id="upgrade-cvs">
+ <title>Upgrading using CVS</title>
+
+ <para>
+ This requires that you have cvs installed (most Unix machines do),
+ and requires that you are able to access cvs-mirror.mozilla.org
+ on port 2401, which may not be an option if you are behind a
+ highly restrictive firewall or don't have Internet access.
+ </para>
+
+ <para>
+ The following shows the sequence of commands needed to update a
+ Bugzilla installation via CVS, and a typical series of results.
+ </para>
+
+ <programlisting>
+bash$ <command>cd /var/www/html/bugzilla</command>
+bash$ <command>cvs login</command>
+Logging in to :pserver:anonymous@cvs-mirror.mozilla.org:2401/cvsroot
+CVS password: <emphasis>('anonymous', or just leave it blank)</emphasis>
+bash$ <command>cvs -q update -r BUGZILLA-2_22_1 -dP</command>
+P checksetup.pl
+P collectstats.pl
+P docs/rel_notes.txt
+P template/en/default/list/quips.html.tmpl
+<emphasis>(etc.)</emphasis>
+ </programlisting>
+
+ <caution>
+ <para>
+ If a line in the output from <command>cvs update</command> begins
+ with a <computeroutput>C</computeroutput>, then that represents a
+ file with local changes that CVS was unable to properly merge. You
+ need to resolve these conflicts manually before Bugzilla (or at
+ least the portion using that file) will be usable.
+ </para>
+ </caution>
+ </section>
+
+ <section id="upgrade-tarball">
+ <title>Upgrading using the tarball</title>
+
+ <para>
+ If you are unable (or unwilling) to use CVS, another option that's
+ always available is to obtain the latest tarball from the <ulink
+ url="http://www.bugzilla.org/download/">Download Page</ulink> and
+ create a new Bugzilla installation from that.
+ </para>
+
+ <para>
+ This sequence of commands shows how to get the tarball from the
+ command-line; it is also possible to download it from the site
+ directly in a web browser. If you go that route, save the file
+ to the <filename class="directory">/var/www/html</filename>
+ directory (or its equivalent, if you use something else) and
+ omit the first three lines of the example.
+ </para>
+
+ <programlisting>
+bash$ <command>cd /var/www/html</command>
+bash$ <command>wget http://ftp.mozilla.org/pub/mozilla.org/webtools/bugzilla-2.22.1.tar.gz</command>
+<emphasis>(Output omitted)</emphasis>
+bash$ <command>tar xzvf bugzilla-2.22.1.tar.gz</command>
+bugzilla-2.22.1/
+bugzilla-2.22.1/.cvsignore
+<emphasis>(Output truncated)</emphasis>
+bash$ <command>cd bugzilla-2.22.1</command>
+bash$ <command>cp ../bugzilla/localconfig* .</command>
+bash$ <command>cp -r ../bugzilla/data .</command>
+bash$ <command>cd ..</command>
+bash$ <command>mv bugzilla bugzilla.old</command>
+bash$ <command>mv bugzilla-2.22.1 bugzilla</command>
+ </programlisting>
+
+ <warning>
+ <para>
+ The <command>cp</command> commands both end with periods which
+ is a very important detail--it means that the destination
+ directory is the current working directory.
+ </para>
+ </warning>
+
+ <para>
+ This upgrade method will give you a clean install of Bugzilla.
+ That's fine if you don't have any local customizations that you
+ want to maintain. If you do have customizations, then you will
+ need to reapply them by hand to the appropriate files.
+ </para>
+ </section>
+
+ <section id="upgrade-patches">
+ <title>Upgrading using patches</title>
+
+ <para>
+ A patch is a collection of all the bug fixes that have been made
+ since the last bug-fix release.
+ </para>
+
+ <para>
+ If you are doing a bug-fix upgrade&mdash;that is, one where only the
+ last number of the revision changes, such as from 2.22 to
+ 2.22.1&mdash;then you have the option of obtaining and applying a
+ patch file from the <ulink
+ url="http://www.bugzilla.org/download/">Download Page</ulink>.
+ </para>
+
+ <para>
+ As above, this example starts with obtaining the file via the
+ command line. If you have already downloaded it, you can omit the
+ first two commands.
+ </para>
+
+ <programlisting>
+bash$ <command>cd /var/www/html/bugzilla</command>
+bash$ <command>wget http://ftp.mozilla.org/pub/mozilla.org/webtools/bugzilla-2.22-to-2.22.1.diff.gz</command>
+<emphasis>(Output omitted)</emphasis>
+bash$ <command>gunzip bugzilla-2.22-to-2.22.1.diff.gz</command>
+bash$ <command>patch -p1 &lt; bugzilla-2.22-to-2.22.1.diff</command>
+patching file checksetup.pl
+patching file collectstats.pl
+<emphasis>(etc.)</emphasis>
+ </programlisting>
+
+ <warning>
+ <para>
+ Be aware that upgrading from a patch file does not change the
+ entries in your <filename class="directory">CVS</filename> directory.
+ This could make it more difficult to upgrade using CVS
+ (<xref linkend="upgrade-cvs"/>) in the future.
+ </para>
+ </warning>
+
+ </section>
+ </section>
+
+ <section id="upgrade-completion">
+ <title>Completing Your Upgrade</title>
+
+ <para>
+ Now that you have the new Bugzilla code, there are a few final
+ steps to complete your upgrade.
+ </para>
+
+ <orderedlist>
+ <listitem>
+ <para>
+ If your new Bugzilla installation is in a different
+ directory or on a different machine than your old Bugzilla
+ installation, make sure that you have copied the
+ <filename>data</filename> directory and the
+ <filename>localconfig</filename> file from your old Bugzilla
+ installation. (If you followed the tarball instructions
+ above, this has already happened.)
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ If this is a major update, check that the configuration
+ (<xref linkend="configuration"/>) for your new Bugzilla is
+ up-to-date. Sometimes the configuration requirements change
+ between major versions.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ If you didn't do it as part of the above configuration step,
+ now you need to run <command>checksetup.pl</command>, which
+ will do everything required to convert your existing database
+ and settings for the new version:
+ </para>
+
+ <programlisting>
+bash$ <command>cd /var/www/html/bugzilla</command>
+bash$ <command>./checksetup.pl</command>
+ </programlisting>
+
+ <warning>
+ <para>
+ The period at the beginning of the command
+ <command>./checksetup.pl</command> is important and can not
+ be omitted.
+ </para>
+ </warning>
+
+ <caution>
+ <para>
+ If this is a major upgrade (say, 2.22 to 3.0 or similar),
+ running <command>checksetup.pl</command> on a large
+ installation (75,000 or more bugs) can take a long time,
+ possibly several hours.
+ </para>
+ </caution>
+ </listitem>
+
+ <listitem>
+ <para>
+ Clear any HTML or text that you put into the shutdownhtml
+ parameter, to re-activate Bugzilla.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ View the Sanity Check (<xref linkend="sanitycheck"/>) page in your
+ upgraded Bugzilla.
+ </para>
+ <para>
+ It is recommended that, if possible, you fix any problems
+ you see, immediately. Failure to do this may mean that Bugzilla
+ will not work correctly. Be aware that if the sanity check page
+ contains more errors after an upgrade, it doesn't necessarily
+ mean there are more errors in your database than there were
+ before, as additional tests are added to the sanity check over
+ time, and it is possible that those errors weren't being
+ checked for in the old version.
+ </para>
+ </listitem>
+ </orderedlist>
+
+ </section>
+
+ <section id="upgrade-notifications">
+ <title>Automatic Notifications of New Releases</title>
+
+ <para>
+ Bugzilla 3.0 introduced the ability to automatically notify
+ administrators when new releases are available, based on the
+ <literal>upgrade_notification</literal> parameter, see
+ <xref linkend="parameters"/>. Administrators will see these
+ notifications when they access the <filename>index.cgi</filename>
+ page, i.e. generally when logging in. Bugzilla will check once per
+ day for new releases, unless the parameter is set to
+ <quote>disabled</quote>. If you are behind a proxy, you may have to set
+ the <literal>proxy_url</literal> parameter accordingly. If the proxy
+ requires authentication, use the
+ <literal>http://user:pass@proxy_url/</literal> syntax.
+ </para>
+ </section>
+ </section>
+
+</chapter>
+
+<!-- Keep this comment at the end of the file
+Local variables:
+mode: sgml
+sgml-always-quote-attributes:t
+sgml-auto-insert-required-elements:t
+sgml-balanced-tag-edit:t
+sgml-exposed-tags:nil
+sgml-general-insert-case:lower
+sgml-indent-data:t
+sgml-indent-step:2
+sgml-local-catalogs:nil
+sgml-local-ecat-files:nil
+sgml-minimize-attributes:nil
+sgml-namecase-general:t
+sgml-omittag:t
+sgml-parent-document:("Bugzilla-Guide.xml" "book" "chapter")
+sgml-shorttag:t
+sgml-tag-region-if-active:t
+End:
+-->
diff --git a/docs/en/xml/modules.xml b/docs/en/xml/modules.xml
new file mode 100644
index 000000000..933c9de5b
--- /dev/null
+++ b/docs/en/xml/modules.xml
@@ -0,0 +1,177 @@
+<!-- <!DOCTYPE appendix PUBLIC "-//OASIS//DTD DocBook V4.1//EN"> -->
+<appendix id="install-perlmodules-manual">
+ <title>Manual Installation of Perl Modules</title>
+
+ <section id="modules-manual-instructions">
+ <title>Instructions</title>
+ <para>
+ If you need to install Perl modules manually, here's how it's done.
+ Download the module using the link given in the next section, and then
+ apply this magic incantation, as root:
+ </para>
+
+ <para>
+ <screen><prompt>bash#</prompt> tar -xzvf &lt;module&gt;.tar.gz
+<prompt>bash#</prompt> cd &lt;module&gt;
+<prompt>bash#</prompt> perl Makefile.PL
+<prompt>bash#</prompt> make
+<prompt>bash#</prompt> make test
+<prompt>bash#</prompt> make install</screen>
+ </para>
+ <note>
+ <para>
+ In order to compile source code under Windows you will need to obtain
+ a 'make' utility. The <command>nmake</command> utility provided with
+ Microsoft Visual C++ may be used. As an alternative, there is a
+ utility called <command>dmake</command> available from CPAN which is
+ written entirely in Perl.
+ </para>
+ <para>
+ As described in <xref linkend="modules-manual-download" />, however, most
+ packages already exist and are available from ActiveState or theory58S.
+ We highly recommend that you install them using the ppm GUI available with
+ ActiveState and to add the theory58S repository to your list of repositories.
+ </para>
+ </note>
+ </section>
+
+ <section id="modules-manual-download">
+ <title>Download Locations</title>
+
+ <note>
+ <para>
+ Running Bugzilla on Windows requires the use of ActiveState
+ Perl 5.8.1 or higher. Many modules already exist in the core
+ distribution of ActiveState Perl. Additional modules can be downloaded
+ from <ulink url="http://theoryx5.uwinnipeg.ca/ppms/" /> if you use
+ Perl 5.8.x or from <ulink url="http://cpan.uwinnipeg.ca/PPMPackages/10xx/" />
+ if you use Perl 5.10.x.
+ </para>
+ </note>
+
+ <para>
+ CGI:
+ <literallayout>
+ CPAN Download Page: <ulink url="http://search.cpan.org/dist/CGI.pm/"/>
+ Documentation: <ulink url="http://perldoc.perl.org/CGI.html"/>
+ </literallayout>
+ </para>
+
+ <para>
+ Data-Dumper:
+ <literallayout>
+ CPAN Download Page: <ulink url="http://search.cpan.org/dist/Data-Dumper/"/>
+ Documentation: <ulink url="http://search.cpan.org/dist/Data-Dumper/Dumper.pm"/>
+ </literallayout>
+ </para>
+
+ <para>
+ Date::Format (part of TimeDate):
+ <literallayout>
+ CPAN Download Page: <ulink url="http://search.cpan.org/dist/TimeDate/"/>
+ Documentation: <ulink url="http://search.cpan.org/dist/TimeDate/lib/Date/Format.pm"/>
+ </literallayout>
+ </para>
+
+ <para>
+ DBI:
+ <literallayout>
+ CPAN Download Page: <ulink url="http://search.cpan.org/dist/DBI/"/>
+ Documentation: <ulink url="http://dbi.perl.org/docs/"/>
+ </literallayout>
+ </para>
+
+ <para>
+ DBD::mysql:
+ <literallayout>
+ CPAN Download Page: <ulink url="http://search.cpan.org/dist/DBD-mysql/"/>
+ Documentation: <ulink url="http://search.cpan.org/dist/DBD-mysql/lib/DBD/mysql.pm"/>
+ </literallayout>
+ </para>
+
+ <para>
+ DBD::Pg:
+ <literallayout>
+ CPAN Download Page: <ulink url="http://search.cpan.org/dist/DBD-Pg/"/>
+ Documentation: <ulink url="http://search.cpan.org/dist/DBD-Pg/Pg.pm"/>
+ </literallayout>
+ </para>
+
+ <para>
+ Template-Toolkit:
+ <literallayout>
+ CPAN Download Page: <ulink url="http://search.cpan.org/dist/Template-Toolkit/"/>
+ Documentation: <ulink url="http://www.template-toolkit.org/docs.html"/>
+ </literallayout>
+ </para>
+
+ <para>
+ GD:
+ <literallayout>
+ CPAN Download Page: <ulink url="http://search.cpan.org/dist/GD/"/>
+ Documentation: <ulink url="http://search.cpan.org/dist/GD/GD.pm"/>
+ </literallayout>
+ </para>
+
+ <para>
+ Template::Plugin::GD:
+ <literallayout>
+ CPAN Download Page: <ulink url="http://search.cpan.org/dist/Template-GD/" />
+ Documentation: <ulink url="http://www.template-toolkit.org/docs/aqua/Modules/index.html" />
+ </literallayout>
+ </para>
+
+ <para>
+ MIME::Parser (part of MIME-tools):
+ <literallayout>
+ CPAN Download Page: <ulink url="http://search.cpan.org/dist/MIME-tools/"/>
+ Documentation: <ulink url="http://search.cpan.org/dist/MIME-tools/lib/MIME/Parser.pm"/>
+ </literallayout>
+ </para>
+
+ </section>
+
+ <section id="modules-manual-optional">
+ <title>Optional Modules</title>
+
+ <para>
+ Chart::Lines:
+ <literallayout>
+ CPAN Download Page: <ulink url="http://search.cpan.org/dist/Chart/"/>
+ Documentation: <ulink url="http://search.cpan.org/dist/Chart/Chart.pod"/>
+ </literallayout>
+ </para>
+
+ <para>
+ GD::Graph:
+ <literallayout>
+ CPAN Download Page: <ulink url="http://search.cpan.org/dist/GDGraph/"/>
+ Documentation: <ulink url="http://search.cpan.org/dist/GDGraph/Graph.pm"/>
+ </literallayout>
+ </para>
+
+ <para>
+ GD::Text::Align (part of GD::Text::Util):
+ <literallayout>
+ CPAN Download Page: <ulink url="http://search.cpan.org/dist/GDTextUtil/"/>
+ Documentation: <ulink url="http://search.cpan.org/dist/GDTextUtil/Text/Align.pm"/>
+ </literallayout>
+ </para>
+
+ <para>
+ XML::Twig:
+ <literallayout>
+ CPAN Download Page: <ulink url="http://search.cpan.org/dist/XML-Twig/"/>
+ Documentation: <ulink url="http://standards.ieee.org/resources/spasystem/twig/twig_stable.html"/>
+ </literallayout>
+ </para>
+
+ <para>
+ PatchReader:
+ <literallayout>
+ CPAN Download Page: <ulink url="http://search.cpan.org/author/JKEISER/PatchReader/"/>
+ Documentation: <ulink url="http://www.johnkeiser.com/mozilla/Patch_Viewer.html"/>
+ </literallayout>
+ </para>
+ </section>
+</appendix>
diff --git a/docs/en/xml/patches.xml b/docs/en/xml/patches.xml
new file mode 100644
index 000000000..12efb0ca4
--- /dev/null
+++ b/docs/en/xml/patches.xml
@@ -0,0 +1,131 @@
+<!-- <!DOCTYPE appendix PUBLIC "-//OASIS//DTD DocBook V4.1//EN"> -->
+<appendix id="patches" xreflabel="Useful Patches and Utilities for Bugzilla">
+ <title>Contrib</title>
+
+ <para>
+ There are a number of unofficial Bugzilla add-ons in the
+ <filename class="directory">$BUGZILLA_ROOT/contrib/</filename>
+ directory. This section documents them.
+ </para>
+
+ <section id="cmdline">
+ <title>Command-line Search Interface</title>
+
+ <para>
+ There are a suite of Unix utilities for searching Bugzilla from the
+ command line. They live in the
+ <filename class="directory">contrib/cmdline</filename> directory.
+ There are three files - <filename>query.conf</filename>,
+ <filename>buglist</filename> and <filename>bugs</filename>.
+ </para>
+
+ <warning>
+ <para>
+ These files pre-date the templatization work done as part of the
+ 2.16 release, and have not been updated.
+ </para>
+ </warning>
+
+ <para>
+ <filename>query.conf</filename> contains the mapping from
+ options to field names and comparison types. Quoted option names
+ are <quote>grepped</quote> for, so it should be easy to edit this
+ file. Comments (#) have no effect; you must make sure these lines
+ do not contain any quoted <quote>option</quote>.
+ </para>
+
+ <para>
+ <filename>buglist</filename> is a shell script that submits a
+ Bugzilla query and writes the resulting HTML page to stdout.
+ It supports both short options, (such as <quote>-Afoo</quote>
+ or <quote>-Rbar</quote>) and long options (such
+ as <quote>--assignedto=foo</quote> or <quote>--reporter=bar</quote>).
+ If the first character of an option is not <quote>-</quote>, it is
+ treated as if it were prefixed with <quote>--default=</quote>.
+ </para>
+
+ <para>
+ The column list is taken from the COLUMNLIST environment variable.
+ This is equivalent to the <quote>Change Columns</quote> option
+ that is available when you list bugs in buglist.cgi. If you have
+ already used Bugzilla, grep for COLUMNLIST in your cookies file
+ to see your current COLUMNLIST setting.
+ </para>
+
+ <para>
+ <filename>bugs</filename> is a simple shell script which calls
+ <filename>buglist</filename> and extracts the
+ bug numbers from the output. Adding the prefix
+ <quote>http://bugzilla.mozilla.org/buglist.cgi?bug_id=</quote>
+ turns the bug list into a working link if any bugs are found.
+ Counting bugs is easy. Pipe the results through
+ <command>sed -e 's/,/ /g' | wc | awk '{printf $2 "\n"}'</command>
+ </para>
+
+ <para>
+ Akkana Peck says she has good results piping
+ <filename>buglist</filename> output through
+ <command>w3m -T text/html -dump</command>
+ </para>
+
+ </section>
+
+ <section id="cmdline-bugmail">
+ <title>Command-line 'Send Unsent Bug-mail' tool</title>
+
+ <para>
+ Within the <filename class="directory">contrib</filename> directory
+ exists a utility with the descriptive (if compact) name
+ of <filename>sendunsentbugmail.pl</filename>. The purpose of this
+ script is, simply, to send out any bug-related mail that should
+ have been sent by now, but for one reason or another has not.
+ </para>
+
+ <para>
+ To accomplish this task, <filename>sendunsentbugmail.pl</filename> uses
+ the same mechanism as the <filename>sanitycheck.cgi</filename> script;
+ it scans through the entire database looking for bugs with changes that
+ were made more than 30 minutes ago, but where there is no record of
+ anyone related to that bug having been sent mail. Having compiled a list,
+ it then uses the standard rules to determine who gets mail, and sends it
+ out.
+ </para>
+
+ <para>
+ As the script runs, it indicates the bug for which it is currently
+ sending mail; when it has finished, it gives a numerical count of how
+ many mails were sent and how many people were excluded. (Individual
+ user names are not recorded or displayed.) If the script produces
+ no output, that means no unsent mail was detected.
+ </para>
+
+ <para>
+ <emphasis>Usage</emphasis>: move the sendunsentbugmail.pl script
+ up into the main directory, ensure it has execute permission, and run it
+ from the command line (or from a cron job) with no parameters.
+ </para>
+ </section>
+
+</appendix>
+
+<!-- Keep this comment at the end of the file
+Local variables:
+mode: sgml
+sgml-always-quote-attributes:t
+sgml-auto-insert-required-elements:t
+sgml-balanced-tag-edit:t
+sgml-exposed-tags:nil
+sgml-general-insert-case:lower
+sgml-indent-data:t
+sgml-indent-step:2
+sgml-local-catalogs:nil
+sgml-local-ecat-files:nil
+sgml-minimize-attributes:nil
+sgml-namecase-general:t
+sgml-omittag:t
+sgml-parent-document:("Bugzilla-Guide.xml" "book" "chapter")
+sgml-shorttag:t
+sgml-tag-region-if-active:t
+End:
+-->
+
diff --git a/docs/en/xml/security.xml b/docs/en/xml/security.xml
new file mode 100644
index 000000000..b234dd993
--- /dev/null
+++ b/docs/en/xml/security.xml
@@ -0,0 +1,270 @@
+<!-- <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN"> -->
+
+<chapter id="security">
+<title>Bugzilla Security</title>
+
+ <para>While some of the items in this chapter are related to the operating
+ system Bugzilla is running on or some of the support software required to
+ run Bugzilla, it is all related to protecting your data. This is not
+ intended to be a comprehensive guide to securing Linux, Apache, MySQL, or
+ any other piece of software mentioned. There is no substitute for active
+ administration and monitoring of a machine. The key to good security is
+ actually right in the middle of the word: <emphasis>U R It</emphasis>.
+ </para>
+
+ <para>While programmers in general always strive to write secure code,
+ accidents can and do happen. The best approach to security is to always
+ assume that the program you are working with isn't 100% secure and restrict
+ its access to other parts of your machine as much as possible.
+ </para>
+
+ <section id="security-os">
+ <title>Operating System</title>
+
+ <section id="security-os-ports">
+ <title>TCP/IP Ports</title>
+
+ <!-- TODO: Get exact number of ports -->
+ <para>The TCP/IP standard defines more than 65,000 ports for sending
+ and receiving traffic. Of those, Bugzilla needs exactly one to operate
+ (different configurations and options may require up to 3). You should
+ audit your server and make sure that you aren't listening on any ports
+ you don't need to be. It's also highly recommended that the server
+ Bugzilla resides on, along with any other machines you administer, be
+ placed behind some kind of firewall.
+ </para>
+
+ </section>
+
+ <section id="security-os-accounts">
+ <title>System User Accounts</title>
+
+ <para>Many <glossterm linkend="gloss-daemon">daemons</glossterm>, such
+ as Apache's <filename>httpd</filename> or MySQL's
+ <filename>mysqld</filename>, run as either <quote>root</quote> or
+ <quote>nobody</quote>. This is even worse on Windows machines where the
+ majority of <glossterm linkend="gloss-service">services</glossterm>
+ run as <quote>SYSTEM</quote>. While running as <quote>root</quote> or
+ <quote>SYSTEM</quote> introduces obvious security concerns, the
+ problems introduced by running everything as <quote>nobody</quote> may
+ not be so obvious. Basically, if you run every daemon as
+ <quote>nobody</quote> and one of them gets compromised it can
+ compromise every other daemon running as <quote>nobody</quote> on your
+ machine. For this reason, it is recommended that you create a user
+ account for each daemon.
+ </para>
+
+ <note>
+ <para>You will need to set the <option>webservergroup</option> option
+ in <filename>localconfig</filename> to the group your web server runs
+ as. This will allow <filename>./checksetup.pl</filename> to set file
+ permissions on Unix systems so that nothing is world-writable.
+ </para>
+ </note>
+
+ </section>
+
+ <section id="security-os-chroot">
+ <title>The <filename>chroot</filename> Jail</title>
+
+ <para>
+ If your system supports it, you may wish to consider running
+ Bugzilla inside of a <filename>chroot</filename> jail. This option
+ provides unprecedented security by restricting anything running
+ inside the jail from accessing any information outside of it. If you
+ wish to use this option, please consult the documentation that came
+ with your system.
+ </para>
+
+ </section>
+
+ </section>
+
+ <section id="security-webserver">
+ <title>Web server</title>
+
+ <section id="security-webserver-access">
+ <title>Disabling Remote Access to Bugzilla Configuration Files</title>
+
+ <para>
+ There are many files that are placed in the Bugzilla directory
+ area that should not be accessible from the web server. Because of the way
+ Bugzilla is currently layed out, the list of what should and should not
+ be accessible is rather complicated. A quick way is to run
+ <filename>testserver.pl</filename> to check if your web server serves
+ Bugzilla files as expected. If not, you may want to follow the few
+ steps below.
+ </para>
+
+ <tip>
+ <para>Bugzilla ships with the ability to create
+ <glossterm linkend="gloss-htaccess"><filename>.htaccess</filename></glossterm>
+ files that enforce these rules. Instructions for enabling these
+ directives in Apache can be found in <xref linkend="http-apache"/>
+ </para>
+ </tip>
+
+ <itemizedlist spacing="compact">
+ <listitem>
+ <para>In the main Bugzilla directory, you should:</para>
+ <itemizedlist spacing="compact">
+ <listitem>
+ <para>Block:
+ <simplelist type="inline">
+ <member><filename>*.pl</filename></member>
+ <member><filename>*localconfig*</filename></member>
+ </simplelist>
+ </para>
+ </listitem>
+ </itemizedlist>
+ </listitem>
+
+ <listitem>
+ <para>In <filename class="directory">data</filename>:</para>
+ <itemizedlist spacing="compact">
+ <listitem>
+ <para>Block everything</para>
+ </listitem>
+ </itemizedlist>
+ </listitem>
+
+ <listitem>
+ <para>In <filename class="directory">data/webdot</filename>:</para>
+ <itemizedlist spacing="compact">
+ <listitem>
+ <para>If you use a remote webdot server:</para>
+ <itemizedlist spacing="compact">
+ <listitem>
+ <para>Block everything</para>
+ </listitem>
+ <listitem>
+ <para>But allow
+ <simplelist type="inline">
+ <member><filename>*.dot</filename></member>
+ </simplelist>
+ only for the remote webdot server</para>
+ </listitem>
+ </itemizedlist>
+ </listitem>
+ <listitem>
+ <para>Otherwise, if you use a local GraphViz:</para>
+ <itemizedlist spacing="compact">
+ <listitem>
+ <para>Block everything</para>
+ </listitem>
+ <listitem>
+ <para>But allow:
+ <simplelist type="inline">
+ <member><filename>*.png</filename></member>
+ <member><filename>*.gif</filename></member>
+ <member><filename>*.jpg</filename></member>
+ <member><filename>*.map</filename></member>
+ </simplelist>
+ </para>
+ </listitem>
+ </itemizedlist>
+ </listitem>
+ <listitem>
+ <para>And if you don't use any dot:</para>
+ <itemizedlist spacing="compact">
+ <listitem>
+ <para>Block everything</para>
+ </listitem>
+ </itemizedlist>
+ </listitem>
+ </itemizedlist>
+ </listitem>
+
+ <listitem>
+ <para>In <filename class="directory">Bugzilla</filename>:</para>
+ <itemizedlist spacing="compact">
+ <listitem>
+ <para>Block everything</para>
+ </listitem>
+ </itemizedlist>
+ </listitem>
+
+ <listitem>
+ <para>In <filename class="directory">template</filename>:</para>
+ <itemizedlist spacing="compact">
+ <listitem>
+ <para>Block everything</para>
+ </listitem>
+ </itemizedlist>
+ </listitem>
+ </itemizedlist>
+
+ <para>Be sure to test that data that should not be accessed remotely is
+ properly blocked. Of particular interest is the localconfig file which
+ contains your database password. Also, be aware that many editors
+ create temporary and backup files in the working directory and that
+ those should also not be accessible. For more information, see
+ <ulink url="http://bugzilla.mozilla.org/show_bug.cgi?id=186383">bug 186383</ulink>
+ or
+ <ulink url="http://online.securityfocus.com/bid/6501">Bugtraq ID 6501</ulink>.
+ To test, simply run <filename>testserver.pl</filename>, as said above.
+ </para>
+
+ <tip>
+ <para>Be sure to check <xref linkend="http"/> for instructions
+ specific to the web server you use.
+ </para>
+ </tip>
+
+ </section>
+
+
+ </section>
+
+
+ <section id="security-bugzilla">
+ <title>Bugzilla</title>
+
+ <section id="security-bugzilla-charset">
+ <title>Prevent users injecting malicious Javascript</title>
+
+ <para>If you installed Bugzilla version 2.22 or later from scratch,
+ then the <emphasis>utf8</emphasis> parameter is switched on by default.
+ This makes Bugzilla explicitly set the character encoding, following
+ <ulink
+ url="http://www.cert.org/tech_tips/malicious_code_mitigation.html#3">a
+ CERT advisory</ulink> recommending exactly this.
+ The following therefore does not apply to you; just keep
+ <emphasis>utf8</emphasis> turned on.
+ </para>
+
+ <para>If you've upgraded from an older version, then it may be possible
+ for a Bugzilla user to take advantage of character set encoding
+ ambiguities to inject HTML into Bugzilla comments.
+ This could include malicious scripts.
+ This is because due to internationalization concerns, we are unable to
+ turn the <emphasis>utf8</emphasis> parameter on by default for upgraded
+ installations.
+ Turning it on manually will prevent this problem.
+ </para>
+ </section>
+
+ </section>
+
+</chapter>
+
+<!-- Keep this comment at the end of the file
+Local variables:
+mode: sgml
+sgml-always-quote-attributes:t
+sgml-auto-insert-required-elements:t
+sgml-balanced-tag-edit:t
+sgml-exposed-tags:nil
+sgml-general-insert-case:lower
+sgml-indent-data:t
+sgml-indent-step:2
+sgml-local-catalogs:nil
+sgml-local-ecat-files:nil
+sgml-minimize-attributes:nil
+sgml-namecase-general:t
+sgml-omittag:t
+sgml-parent-document:("Bugzilla-Guide.xml" "book" "chapter")
+sgml-shorttag:t
+sgml-tag-region-if-active:t
+End: -->
+
diff --git a/docs/en/xml/troubleshooting.xml b/docs/en/xml/troubleshooting.xml
new file mode 100644
index 000000000..fff90a9e1
--- /dev/null
+++ b/docs/en/xml/troubleshooting.xml
@@ -0,0 +1,277 @@
+<!-- <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN"> -->
+
+<appendix id="troubleshooting">
+<title>Troubleshooting</title>
+
+ <para>This section gives solutions to common Bugzilla installation
+ problems. If none of the section headings seems to match your
+ problem, read the general advice.
+ </para>
+
+ <section id="general-advice">
+ <title>General Advice</title>
+ <para>If you can't get <filename>checksetup.pl</filename> to run to
+ completion, it normally explains what's wrong and how to fix it.
+ If you can't work it out, or if it's being uncommunicative, post
+ the errors in the
+ <ulink url="news://news.mozilla.org/mozilla.support.bugzilla">mozilla.support.bugzilla</ulink>
+ newsgroup.
+ </para>
+
+ <para>If you have made it all the way through
+ <xref linkend="installation"/> (Installation) and
+ <xref linkend="configuration"/> (Configuration) but accessing the Bugzilla
+ URL doesn't work, the first thing to do is to check your web server error
+ log. For Apache, this is often located at
+ <filename>/etc/logs/httpd/error_log</filename>. The error messages
+ you see may be self-explanatory enough to enable you to diagnose and
+ fix the problem. If not, see below for some commonly-encountered
+ errors. If that doesn't help, post the errors to the newsgroup.
+ </para>
+
+ <para>
+ Bugzilla can also log all user-based errors (and many code-based errors)
+ that occur, without polluting the web server's error log. To enable
+ Bugzilla error logging, create a file that Bugzilla can write to, named
+ <filename>errorlog</filename>, in the Bugzilla <filename>data</filename>
+ directory. Errors will be logged as they occur, and will include the type
+ of the error, the IP address and username (if available) of the user who
+ triggered the error, and the values of all environment variables; if a
+ form was being submitted, the data in the form will also be included.
+ To disable error logging, delete or rename the
+ <filename>errorlog</filename> file.
+ </para>
+ </section>
+
+ <section id="trbl-testserver">
+ <title>The Apache web server is not serving Bugzilla pages</title>
+ <para>After you have run <command>checksetup.pl</command> twice,
+ run <command>testserver.pl http://yoursite.yourdomain/yoururl</command>
+ to confirm that your web server is configured properly for
+ Bugzilla.
+ </para>
+ <programlisting>
+<prompt>bash$</prompt> ./testserver.pl http://landfill.bugzilla.org/bugzilla-tip
+TEST-OK Webserver is running under group id in $webservergroup.
+TEST-OK Got ant picture.
+TEST-OK Webserver is executing CGIs.
+TEST-OK Webserver is preventing fetch of http://landfill.bugzilla.org/bugzilla-tip/localconfig.
+</programlisting>
+ </section>
+
+ <section id="trbl-perlmodule">
+ <title>I installed a Perl module, but
+ <filename>checksetup.pl</filename> claims it's not installed!</title>
+
+ <para>This could be caused by one of two things:</para>
+ <orderedlist>
+ <listitem>
+ <para>You have two versions of Perl on your machine. You are installing
+ modules into one, and Bugzilla is using the other. Rerun the CPAN
+ commands (or manual compile) using the full path to Perl from the
+ top of <filename>checksetup.pl</filename>. This will make sure you
+ are installing the modules in the right place.
+ </para>
+ </listitem>
+ <listitem>
+ <para>The permissions on your library directories are set incorrectly.
+ They must, at the very least, be readable by the web server user or
+ group. It is recommended that they be world readable.
+ </para>
+ </listitem>
+ </orderedlist>
+ </section>
+
+ <section id="trbl-dbdSponge">
+ <title>DBD::Sponge::db prepare failed</title>
+
+ <para>The following error message may appear due to a bug in DBD::mysql
+ (over which the Bugzilla team have no control):
+ </para>
+
+<programlisting><![CDATA[ DBD::Sponge::db prepare failed: Cannot determine NUM_OF_FIELDS at D:/Perl/site/lib/DBD/mysql.pm line 248.
+ SV = NULL(0x0) at 0x20fc444
+ REFCNT = 1
+ FLAGS = (PADBUSY,PADMY)
+]]></programlisting>
+
+ <para>To fix this, go to
+ <filename>&lt;path-to-perl&gt;/lib/DBD/sponge.pm</filename>
+ in your Perl installation and replace
+ </para>
+
+<programlisting><![CDATA[ my $numFields;
+ if ($attribs->{'NUM_OF_FIELDS'}) {
+ $numFields = $attribs->{'NUM_OF_FIELDS'};
+ } elsif ($attribs->{'NAME'}) {
+ $numFields = @{$attribs->{NAME}};
+]]></programlisting>
+
+ <para>with</para>
+
+<programlisting><![CDATA[ my $numFields;
+ if ($attribs->{'NUM_OF_FIELDS'}) {
+ $numFields = $attribs->{'NUM_OF_FIELDS'};
+ } elsif ($attribs->{'NAMES'}) {
+ $numFields = @{$attribs->{NAMES}};
+]]></programlisting>
+
+ <para>(note the S added to NAME.)</para>
+ </section>
+
+ <section id="paranoid-security">
+ <title>cannot chdir(/var/spool/mqueue)</title>
+
+ <para>If you are installing Bugzilla on SuSE Linux, or some other
+ distributions with <quote>paranoid</quote> security options, it is
+ possible that the checksetup.pl script may fail with the error:
+<programlisting><![CDATA[cannot chdir(/var/spool/mqueue): Permission denied
+]]></programlisting>
+ </para>
+
+ <para>This is because your <filename>/var/spool/mqueue</filename>
+ directory has a mode of <computeroutput>drwx------</computeroutput>.
+ Type <command>chmod 755 <filename>/var/spool/mqueue</filename></command>
+ as root to fix this problem. This will allow any process running on your
+ machine the ability to <emphasis>read</emphasis> the
+ <filename>/var/spool/mqueue</filename> directory.
+ </para>
+ </section>
+
+ <section id="trbl-relogin-everyone">
+ <title>Everybody is constantly being forced to relogin</title>
+
+ <para>The most-likely cause is that the <quote>cookiepath</quote> parameter
+ is not set correctly in the Bugzilla configuration. You can change this (if
+ you're a Bugzilla administrator) from the editparams.cgi page via the web interface.
+ </para>
+
+ <para>The value of the cookiepath parameter should be the actual directory
+ containing your Bugzilla installation, <emphasis>as seen by the end-user's
+ web browser</emphasis>. Leading and trailing slashes are mandatory. You can
+ also set the cookiepath to any directory which is a parent of the Bugzilla
+ directory (such as '/', the root directory). But you can't put something
+ that isn't at least a partial match or it won't work. What you're actually
+ doing is restricting the end-user's browser to sending the cookies back only
+ to that directory.
+ </para>
+
+ <para>How do you know if you want your specific Bugzilla directory or the
+ whole site?
+ </para>
+
+ <para>If you have only one Bugzilla running on the server, and you don't
+ mind having other applications on the same server with it being able to see
+ the cookies (you might be doing this on purpose if you have other things on
+ your site that share authentication with Bugzilla), then you'll want to have
+ the cookiepath set to "/", or to a sufficiently-high enough directory that
+ all of the involved apps can see the cookies.
+ </para>
+
+ <example id="trbl-relogin-everyone-share">
+ <title>Examples of urlbase/cookiepath pairs for sharing login cookies</title>
+
+ <blockquote>
+ <literallayout>
+ urlbase is <ulink url="http://bugzilla.mozilla.org/"/>
+ cookiepath is /
+
+ urlbase is <ulink url="http://tools.mysite.tld/bugzilla/"/>
+ but you have http://tools.mysite.tld/someotherapp/ which shares
+ authentication with your Bugzilla
+ cookiepath is /
+ </literallayout>
+ </blockquote>
+ </example>
+
+ <para>On the other hand, if you have more than one Bugzilla running on the
+ server (some people do - we do on landfill) then you need to have the
+ cookiepath restricted enough so that the different Bugzillas don't
+ confuse their cookies with one another.
+ </para>
+
+
+ <example id="trbl-relogin-everyone-restrict">
+ <title>Examples of urlbase/cookiepath pairs to restrict the login cookie</title>
+ <blockquote>
+ <literallayout>
+ urlbase is <ulink url="http://landfill.bugzilla.org/bugzilla-tip/"/>
+ cookiepath is /bugzilla-tip/
+
+ urlbase is <ulink url="http://landfill.bugzilla.org/bugzilla-2.16-branch/"/>
+ cookiepath is /bugzilla-2.16-branch/
+ </literallayout>
+ </blockquote>
+ </example>
+
+ <para>If you had cookiepath set to <quote>/</quote> at any point in the
+ past and need to set it to something more restrictive
+ (i.e. <quote>/bugzilla/</quote>), you can safely do this without
+ requiring users to delete their Bugzilla-related cookies in their
+ browser (this is true starting with Bugzilla 2.18 and Bugzilla 2.16.5).
+ </para>
+ </section>
+
+ <section id="trbl-index">
+ <title><filename>index.cgi</filename> doesn't show up unless specified in the URL</title>
+ <para>
+ You probably need to set up your web server in such a way that it
+ will serve the index.cgi page as an index page.
+ </para>
+ <para>
+ If you are using Apache, you can do this by adding
+ <filename>index.cgi</filename> to the end of the
+ <computeroutput>DirectoryIndex</computeroutput> line
+ as mentioned in <xref linkend="http-apache"/>.
+ </para>
+
+ </section>
+
+ <section id="trbl-passwd-encryption">
+ <title>
+ checksetup.pl reports "Client does not support authentication protocol
+ requested by server..."
+ </title>
+
+ <para>
+ This error is occurring because you are using the new password
+ encryption that comes with MySQL 4.1, while your
+ <filename>DBD::mysql</filename> module was compiled against an
+ older version of MySQL. If you recompile <filename>DBD::mysql</filename>
+ against the current MySQL libraries (or just obtain a newer version
+ of this module) then the error may go away.
+ </para>
+
+ <para>
+ If that does not fix the problem, or if you cannot recompile the
+ existing module (e.g. you're running Windows) and/or don't want to
+ replace it (e.g. you want to keep using a packaged version), then a
+ workaround is available from the MySQL docs:
+ <ulink url="http://dev.mysql.com/doc/mysql/en/Old_client.html"/>
+ </para>
+
+ </section>
+
+</appendix>
+
+<!-- Keep this comment at the end of the file
+Local variables:
+mode: sgml
+sgml-always-quote-attributes:t
+sgml-auto-insert-required-elements:t
+sgml-balanced-tag-edit:t
+sgml-exposed-tags:nil
+sgml-general-insert-case:lower
+sgml-indent-data:t
+sgml-indent-step:2
+sgml-local-catalogs:nil
+sgml-local-ecat-files:nil
+sgml-minimize-attributes:nil
+sgml-namecase-general:t
+sgml-omittag:t
+sgml-parent-document:("Bugzilla-Guide.xml" "book" "chapter")
+sgml-shorttag:t
+sgml-tag-region-if-active:t
+End: -->
+
+
diff --git a/docs/en/xml/using.xml b/docs/en/xml/using.xml
new file mode 100644
index 000000000..e26de4230
--- /dev/null
+++ b/docs/en/xml/using.xml
@@ -0,0 +1,1959 @@
+<!-- <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook V4.1//EN"> -->
+
+<chapter id="using">
+ <title>Using Bugzilla</title>
+
+ <section id="using-intro">
+ <title>Introduction</title>
+ <para>This section contains information for end-users of Bugzilla. There
+ is a Bugzilla test installation, called
+ <ulink url="http://landfill.bugzilla.org/">Landfill</ulink>, which you are
+ welcome to play with (if it's up). However, not all of the Bugzilla
+ installations there will necessarily have all Bugzilla features enabled,
+ and different installations run different versions, so some things may not
+ quite work as this document describes.</para>
+
+ <para>
+ Frequently Asked Questions (FAQ) are available and answered on
+ <ulink url="http://wiki.mozilla.org/Bugzilla:FAQ">wiki.mozilla.org</ulink>.
+ They may cover some questions you have which are left unanswered.
+ </para>
+ </section>
+
+ <section id="myaccount">
+ <title>Create a Bugzilla Account</title>
+
+ <para>If you want to use Bugzilla, first you need to create an account.
+ Consult with the administrator responsible for your installation of
+ Bugzilla for the URL you should use to access it. If you're
+ test-driving Bugzilla, use this URL:
+ <ulink url="&landfillbase;"/>.
+ </para>
+
+ <orderedlist>
+ <listitem>
+ <para>
+ On the home page <filename>index.cgi</filename>, click the
+ <quote>Open a new Bugzilla account</quote> link, or the
+ <quote>New Account</quote> link available in the footer of pages.
+ Now enter your email address, then click the <quote>Send</quote>
+ button.
+ </para>
+
+ <note>
+ <para>
+ If none of these links is available, this means that the
+ administrator of the installation has disabled self-registration.
+ This means that only an administrator can create accounts
+ for other users. One reason could be that this installation is
+ private.
+ </para>
+ </note>
+
+ <note>
+ <para>
+ Also, if only some users are allowed to create an account on
+ the installation, you may see these links but your registration
+ may fail if your email address doesn't match the ones accepted
+ by the installation. This is another way to restrict who can
+ access and edit bugs in this installation.
+ </para>
+ </note>
+ </listitem>
+
+ <listitem>
+ <para>
+ Within moments, and if your registration is accepted, you should
+ receive an email to the address you provided, which contains your
+ login name (generally the same as the email address), and two URLs
+ with a token (a random string generated by the installation) to
+ confirm, respectively cancel, your registration. This is a way to
+ prevent users from abusing the generation of user accounts, for
+ instance by entering inexistent email addresses, or email addresses
+ which do not belong to them.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ By default, you have 3 days to confirm your registration. Past this
+ timeframe, the token is invalidated and the registration is
+ automatically canceled. You can also cancel this registration sooner
+ by using the appropriate URL in the email you got.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ If you confirm your registration, Bugzilla will ask you your real name
+ (optional, but recommended) and your password, which must be between
+ 3 and 16 characters long.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Now all you need to do is to click the <quote>Log In</quote>
+ link in the footer at the bottom of the page in your browser,
+ enter your email address and password you just chose into the
+ login form, and click the <quote>Log in</quote> button.
+ </para>
+ </listitem>
+ </orderedlist>
+
+ <para>
+ You are now logged in. Bugzilla uses cookies to remember you are
+ logged in so, unless you have cookies disabled or your IP address changes,
+ you should not have to log in again during your session.
+ </para>
+ </section>
+
+ <section id="bug_page">
+ <title>Anatomy of a Bug</title>
+
+ <para>The core of Bugzilla is the screen which displays a particular
+ bug. It's a good place to explain some Bugzilla concepts.
+ <ulink
+ url="&landfillbase;show_bug.cgi?id=1">
+ Bug 1 on Landfill</ulink>
+
+ is a good example. Note that the labels for most fields are hyperlinks;
+ clicking them will take you to context-sensitive help on that
+ particular field. Fields marked * may not be present on every
+ installation of Bugzilla.</para>
+
+ <orderedlist>
+ <listitem>
+ <para>
+ <emphasis>Product and Component</emphasis>:
+ Bugs are divided up by Product and Component, with a Product
+ having one or more Components in it. For example,
+ bugzilla.mozilla.org's "Bugzilla" Product is composed of several
+ Components:
+ <simplelist>
+ <member>
+ <emphasis>Administration:</emphasis>
+ Administration of a Bugzilla installation.</member>
+
+ <member>
+ <emphasis>Bugzilla-General:</emphasis>
+ Anything that doesn't fit in the other components, or spans
+ multiple components.</member>
+
+ <member>
+ <emphasis>Creating/Changing Bugs:</emphasis>
+ Creating, changing, and viewing bugs.</member>
+
+ <member>
+ <emphasis>Documentation:</emphasis>
+ The Bugzilla documentation, including The Bugzilla Guide.</member>
+
+ <member>
+ <emphasis>Email:</emphasis>
+ Anything to do with email sent by Bugzilla.</member>
+
+ <member>
+ <emphasis>Installation:</emphasis>
+ The installation process of Bugzilla.</member>
+
+ <member>
+ <emphasis>Query/Buglist:</emphasis>
+ Anything to do with searching for bugs and viewing the
+ buglists.</member>
+
+ <member>
+ <emphasis>Reporting/Charting:</emphasis>
+ Getting reports from Bugzilla.</member>
+
+ <member>
+ <emphasis>User Accounts:</emphasis>
+ Anything about managing a user account from the user's perspective.
+ Saved queries, creating accounts, changing passwords, logging in,
+ etc.</member>
+
+ <member>
+ <emphasis>User Interface:</emphasis>
+ General issues having to do with the user interface cosmetics (not
+ functionality) including cosmetic issues, HTML templates,
+ etc.</member>
+ </simplelist>
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <emphasis>Status and Resolution:</emphasis>
+
+ These define exactly what state the bug is in - from not even
+ being confirmed as a bug, through to being fixed and the fix
+ confirmed by Quality Assurance. The different possible values for
+ Status and Resolution on your installation should be documented in the
+ context-sensitive help for those items.</para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <emphasis>Assigned To:</emphasis>
+ The person responsible for fixing the bug.</para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <emphasis>*QA Contact:</emphasis>
+ The person responsible for quality assurance on this bug.</para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <emphasis>*URL:</emphasis>
+ A URL associated with the bug, if any.</para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <emphasis>Summary:</emphasis>
+ A one-sentence summary of the problem.</para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <emphasis>*Status Whiteboard:</emphasis>
+ (a.k.a. Whiteboard) A free-form text area for adding short notes
+ and tags to a bug.</para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <emphasis>*Keywords:</emphasis>
+ The administrator can define keywords which you can use to tag and
+ categorise bugs - e.g. The Mozilla Project has keywords like crash
+ and regression.</para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <emphasis>Platform and OS:</emphasis>
+ These indicate the computing environment where the bug was
+ found.</para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <emphasis>Version:</emphasis>
+ The "Version" field is usually used for versions of a product which
+ have been released, and is set to indicate which versions of a
+ Component have the particular problem the bug report is
+ about.</para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <emphasis>Priority:</emphasis>
+ The bug assignee uses this field to prioritize his or her bugs.
+ It's a good idea not to change this on other people's bugs.</para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <emphasis>Severity:</emphasis>
+ This indicates how severe the problem is - from blocker
+ ("application unusable") to trivial ("minor cosmetic issue"). You
+ can also use this field to indicate whether a bug is an enhancement
+ request.</para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <emphasis>*Target:</emphasis>
+ (a.k.a. Target Milestone) A future version by which the bug is to
+ be fixed. e.g. The Bugzilla Project's milestones for future
+ Bugzilla versions are 2.18, 2.20, 3.0, etc. Milestones are not
+ restricted to numbers, thought - you can use any text strings, such
+ as dates.</para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <emphasis>Reporter:</emphasis>
+ The person who filed the bug.</para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <emphasis>CC list:</emphasis>
+ A list of people who get mail when the bug changes.</para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <emphasis>*Time Tracking:</emphasis>
+ This form can be used for time tracking.
+ To use this feature, you have to be blessed group membership
+ specified by the <quote>timetrackinggroup</quote> parameter.
+ <simplelist>
+ <member>
+ <emphasis>Orig. Est.:</emphasis>
+ This field shows the original estimated time.</member>
+
+ <member>
+ <emphasis>Current Est.:</emphasis>
+ This field shows the current estimated time.
+ This number is calculated from <quote>Hours Worked</quote>
+ and <quote>Hours Left</quote>.</member>
+
+ <member>
+ <emphasis>Hours Worked:</emphasis>
+ This field shows the number of hours worked.</member>
+
+ <member>
+ <emphasis>Hours Left:</emphasis>
+ This field shows the <quote>Current Est.</quote> -
+ <quote>Hours Worked</quote>.
+ This value + <quote>Hours Worked</quote> will become the
+ new Current Est.</member>
+
+ <member>
+ <emphasis>%Complete:</emphasis>
+ This field shows what percentage of the task is complete.</member>
+
+ <member>
+ <emphasis>Gain:</emphasis>
+ This field shows the number of hours that the bug is ahead of the
+ <quote>Orig. Est.</quote>.</member>
+
+ <member>
+ <emphasis>Deadline:</emphasis>
+ This field shows the deadline for this bug.</member>
+ </simplelist>
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <emphasis>Attachments:</emphasis>
+ You can attach files (e.g. testcases or patches) to bugs. If there
+ are any attachments, they are listed in this section. Attachments are
+ normally stored in the Bugzilla database, unless they are marked as
+ Big Files, which are stored directly on disk.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <emphasis>*Dependencies:</emphasis>
+ If this bug cannot be fixed unless other bugs are fixed (depends
+ on), or this bug stops other bugs being fixed (blocks), their
+ numbers are recorded here.</para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <emphasis>*Votes:</emphasis>
+ Whether this bug has any votes.</para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <emphasis>Additional Comments:</emphasis>
+ You can add your two cents to the bug discussion here, if you have
+ something worthwhile to say.</para>
+ </listitem>
+ </orderedlist>
+ </section>
+
+ <section id="lifecycle">
+ <title>Life Cycle of a Bug</title>
+
+ <para>
+ The life cycle of a bug, also known as workflow, is customizable to match
+ the needs of your organization, see <xref linkend="bug_status_workflow"/>.
+ <xref linkend="lifecycle-image"/> contains a graphical representation of
+ the default workflow using the default bug statuses. If you wish to
+ customize this image for your site, the
+ <ulink url="../images/bzLifecycle.xml">diagram file</ulink>
+ is available in <ulink url="http://www.gnome.org/projects/dia">Dia's</ulink>
+ native XML format.
+ </para>
+
+ <figure id="lifecycle-image">
+ <title>Lifecycle of a Bugzilla Bug</title>
+ <mediaobject>
+ <imageobject>
+ <imagedata fileref="../images/bzLifecycle.png" scale="66" />
+ </imageobject>
+ </mediaobject>
+ </figure>
+ </section>
+
+ <section id="query">
+ <title>Searching for Bugs</title>
+
+ <para>The Bugzilla Search page is the interface where you can find
+ any bug report, comment, or patch currently in the Bugzilla system. You
+ can play with it here:
+ <ulink url="&landfillbase;query.cgi"/>.</para>
+
+ <para>The Search page has controls for selecting different possible
+ values for all of the fields in a bug, as described above. For some
+ fields, multiple values can be selected. In those cases, Bugzilla
+ returns bugs where the content of the field matches any one of the selected
+ values. If none is selected, then the field can take any value.</para>
+
+ <para>
+ After a search is run, you can save it as a Saved Search, which
+ will appear in the page footer. If you are in the group defined
+ by the "querysharegroup" parameter, you may share your queries
+ with other users, see <xref linkend="savedsearches"/> for more details.
+ </para>
+
+ <section id="boolean">
+ <title>Boolean Charts</title>
+ <para>
+ Highly advanced querying is done using Boolean Charts.
+ </para>
+ <para>
+ The boolean charts further restrict the set of results
+ returned by a query. It is possible to search for bugs
+ based on elaborate combinations of criteria.
+ </para>
+ <para>
+ The simplest boolean searches have only one term. These searches
+ permit the selected left <emphasis>field</emphasis>
+ to be compared using a
+ selectable <emphasis>operator</emphasis> to a
+ specified <emphasis>value.</emphasis>
+ Using the "And," "Or," and "Add Another Boolean Chart" buttons,
+ additional terms can be included in the query, further
+ altering the list of bugs returned by the query.
+ </para>
+ <para>
+ There are three fields in each row of a boolean search.
+ </para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <emphasis>Field:</emphasis>
+ the items being searched
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <emphasis>Operator:</emphasis>
+ the comparison operator
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <emphasis>Value:</emphasis>
+ the value to which the field is being compared
+ </para>
+ </listitem>
+ </itemizedlist>
+ <section id="pronouns">
+ <title>Pronoun Substitution</title>
+ <para>
+ Sometimes, a query needs to compare a user-related field
+ (such as ReportedBy) with a role-specific user (such as the
+ user running the query or the user to whom each bug is assigned).
+ When the operator is either "equals" or "notequals", the value
+ can be "%reporter%", "%assignee%", "%qacontact%", or "%user%".
+ The user pronoun
+ refers to the user who is executing the query or, in the case
+ of whining reports, the user who will be the recipient
+ of the report. The reporter, assignee, and qacontact
+ pronouns refer to the corresponding fields in the bug.
+ </para>
+ <para>
+ Boolean charts also let you type a group name in any user-related
+ field if the operator is either "equals", "notequals" or "anyexact".
+ This will let you query for any member belonging (or not) to the
+ specified group. The group name must be entered following the
+ "%group.foo%" syntax, where "foo" is the group name.
+ So if you are looking for bugs reported by any user being in the
+ "editbugs" group, then you can type "%group.editbugs%".
+ </para>
+ </section>
+ <section id="negation">
+ <title>Negation</title>
+ <para>
+ At first glance, negation seems redundant. Rather than
+ searching for
+ <blockquote>
+ <para>
+ NOT("summary" "contains the string" "foo"),
+ </para>
+ </blockquote>
+ one could search for
+ <blockquote>
+ <para>
+ ("summary" "does not contain the string" "foo").
+ </para>
+ </blockquote>
+ However, the search
+ <blockquote>
+ <para>
+ ("CC" "does not contain the string" "@mozilla.org")
+ </para>
+ </blockquote>
+ would find every bug where anyone on the CC list did not contain
+ "@mozilla.org" while
+ <blockquote>
+ <para>
+ NOT("CC" "contains the string" "@mozilla.org")
+ </para>
+ </blockquote>
+ would find every bug where there was nobody on the CC list who
+ did contain the string. Similarly, the use of negation also permits
+ complex expressions to be built using terms OR'd together and then
+ negated. Negation permits queries such as
+ <blockquote>
+ <para>
+ NOT(("product" "equals" "update") OR
+ ("component" "equals" "Documentation"))
+ </para>
+ </blockquote>
+ to find bugs that are neither
+ in the update product or in the documentation component or
+ <blockquote>
+ <para>
+ NOT(("commenter" "equals" "%assignee%") OR
+ ("component" "equals" "Documentation"))
+ </para>
+ </blockquote>
+ to find non-documentation
+ bugs on which the assignee has never commented.
+ </para>
+ </section>
+ <section id="multiplecharts">
+ <title>Multiple Charts</title>
+ <para>
+ The terms within a single row of a boolean chart are all
+ constraints on a single piece of data. If you are looking for
+ a bug that has two different people cc'd on it, then you need
+ to use two boolean charts. A search for
+ <blockquote>
+ <para>
+ ("cc" "contains the string" "foo@") AND
+ ("cc" "contains the string" "@mozilla.org")
+ </para>
+ </blockquote>
+ would return only bugs with "foo@mozilla.org" on the cc list.
+ If you wanted bugs where there is someone on the cc list
+ containing "foo@" and someone else containing "@mozilla.org",
+ then you would need two boolean charts.
+ <blockquote>
+ <para>
+ First chart: ("cc" "contains the string" "foo@")
+ </para>
+ <para>
+ Second chart: ("cc" "contains the string" "@mozilla.org")
+ </para>
+ </blockquote>
+ The bugs listed will be only the bugs where ALL the charts are true.
+ </para>
+ </section>
+ </section>
+
+ <section id="quicksearch">
+ <title>Quicksearch</title>
+
+ <para>
+ Quicksearch is a single-text-box query tool which uses
+ metacharacters to indicate what is to be searched. For example, typing
+ "<literal>foo|bar</literal>"
+ into Quicksearch would search for "foo" or "bar" in the
+ summary and status whiteboard of a bug; adding
+ "<literal>:BazProduct</literal>" would
+ search only in that product.
+ You can use it to find a bug by its number or its alias, too.
+ </para>
+
+ <para>
+ You'll find the Quicksearch box in Bugzilla's footer area.
+ On Bugzilla's front page, there is an additional
+ <ulink url="../../page.cgi?id=quicksearch.html">Help</ulink>
+ link which details how to use it.
+ </para>
+ </section>
+ <section id="casesensitivity">
+ <title>Case Sensitivity in Searches</title>
+ <para>
+ Bugzilla queries are case-insensitive and accent-insensitive, when
+ used with either MySQL or Oracle databases. When using Bugzilla with
+ PostgreSQL, however, some queries are case-sensitive. This is due to
+ the way PostgreSQL handles case and accent sensitivity.
+ </para>
+ </section>
+ <section id="list">
+ <title>Bug Lists</title>
+
+ <para>If you run a search, a list of matching bugs will be returned.
+ </para>
+
+ <para>The format of the list is configurable. For example, it can be
+ sorted by clicking the column headings. Other useful features can be
+ accessed using the links at the bottom of the list:
+ <simplelist>
+ <member>
+ <emphasis>Long Format:</emphasis>
+
+ this gives you a large page with a non-editable summary of the fields
+ of each bug.</member>
+
+ <member>
+ <emphasis>XML:</emphasis>
+
+ get the buglist in the XML format.</member>
+
+ <member>
+ <emphasis>CSV:</emphasis>
+
+ get the buglist as comma-separated values, for import into e.g.
+ a spreadsheet.</member>
+
+ <member>
+ <emphasis>Feed:</emphasis>
+
+ get the buglist as an Atom feed. Copy this link into your
+ favorite feed reader. If you are using Firefox, you can also
+ save the list as a live bookmark by clicking the live bookmark
+ icon in the status bar. To limit the number of bugs in the feed,
+ add a limit=n parameter to the URL.</member>
+
+ <member>
+ <emphasis>iCalendar:</emphasis>
+
+ Get the buglist as an iCalendar file. Each bug is represented as a
+ to-do item in the imported calendar.</member>
+
+ <member>
+ <emphasis>Change Columns:</emphasis>
+
+ change the bug attributes which appear in the list.</member>
+
+ <member>
+ <emphasis>Change several bugs at once:</emphasis>
+
+ If your account is sufficiently empowered, and more than one bug
+ appear in the bug list, this link is displayed which lets you make
+ the same change to all the bugs in the list - for example, changing
+ their assignee.</member>
+
+ <member>
+ <emphasis>Send mail to bug assignees:</emphasis>
+
+ If more than one bug appear in the bug list and there are at least
+ two distinct bug assignees, this links is displayed which lets you
+ easily send a mail to the assignees of all bugs on the list.</member>
+
+ <member>
+ <emphasis>Edit Search:</emphasis>
+
+ If you didn't get exactly the results you were looking for, you can
+ return to the Query page through this link and make small revisions
+ to the query you just made so you get more accurate results.</member>
+
+ <member>
+ <emphasis>Remember Search As:</emphasis>
+
+ You can give a search a name and remember it; a link will appear
+ in your page footer giving you quick access to run it again later.
+ </member>
+ </simplelist>
+ </para>
+
+ <para>
+ If you would like to access the bug list from another program
+ it is often useful to have the list returned in something other
+ than HTML. By adding the ctype=type parameter into the bug list URL
+ you can specify several alternate formats. Besides the types described
+ above, the following formats are also supported: ECMAScript, also known
+ as JavaScript (ctype=js), and Resource Description Framework RDF/XML
+ (ctype=rdf).
+ </para>
+ </section>
+
+ <section id="individual-buglists">
+ <title>Adding/removing tags to/from bugs</title>
+ <para>
+ You can add and remove tags from individual bugs, which let you find and
+ manage them more easily. Creating a new tag automatically generates a saved
+ search - whose name is the name of the tag - which lists bugs with this tag.
+ This saved search will be displayed in the footer of pages by default, as
+ all other saved searches. The main difference between tags and normal saved
+ searches is that saved searches, as described in the previous section, are
+ stored in the form of a list of matching criteria, while the saved search
+ generated by tags is a list of bug numbers. Consequently, you can easily
+ edit this list by either adding or removing tags from bugs. To enable this
+ feature, you have to turn on the <quote>Enable tags for bugs</quote> user
+ preference, see <xref linkend="userpreferences" />. This feature is disabled
+ by default.
+ </para>
+
+ <para>
+ This feature is useful when you want to keep track of several bugs, but
+ for different reasons. Instead of adding yourself to the CC list of all
+ these bugs and mixing all these reasons, you can now store these bugs in
+ separate lists, e.g. <quote>Keep in mind</quote>, <quote>Interesting bugs</quote>,
+ or <quote>Triage</quote>. One big advantage of this way to manage bugs
+ is that you can easily add or remove bugs one by one, which is not
+ possible to do with saved searches without having to edit the search
+ criteria again.
+ </para>
+ </section>
+ </section>
+
+ <section id="bugreports">
+ <title>Filing Bugs</title>
+
+ <section id="fillingbugs">
+ <title>Reporting a New Bug</title>
+
+ <para>Years of bug writing experience has been distilled for your
+ reading pleasure into the
+ <ulink
+ url="&landfillbase;page.cgi?id=bug-writing.html">
+ Bug Writing Guidelines</ulink>.
+ While some of the advice is Mozilla-specific, the basic principles of
+ reporting Reproducible, Specific bugs, isolating the Product you are
+ using, the Version of the Product, the Component which failed, the
+ Hardware Platform, and Operating System you were using at the time of
+ the failure go a long way toward ensuring accurate, responsible fixes
+ for the bug that bit you.</para>
+
+ <para>The procedure for filing a bug is as follows:</para>
+
+ <orderedlist>
+ <listitem>
+ <para>
+ Click the <quote>New</quote> link available in the footer
+ of pages, or the <quote>Enter a new bug report</quote> link
+ displayed on the home page of the Bugzilla installation.
+ </para>
+
+ <note>
+ <para>
+ If you want to file a test bug to see how Bugzilla works,
+ you can do it on one of our test installations on
+ <ulink url="&landfillbase;">Landfill</ulink>.
+ </para>
+ </note>
+ </listitem>
+
+ <listitem>
+ <para>
+ You first have to select the product in which you found a bug.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ You now see a form where you can specify the component (part of
+ the product which is affected by the bug you discovered; if you have
+ no idea, just select <quote>General</quote> if such a component exists),
+ the version of the program you were using, the Operating System and
+ platform your program is running on and the severity of the bug (if the
+ bug you found crashes the program, it's probably a major or a critical
+ bug; if it's a typo somewhere, that's something pretty minor; if it's
+ something you would like to see implemented, then that's an enhancement).
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ You now have to give a short but descriptive summary of the bug you found.
+ <quote>My program is crashing all the time</quote> is a very poor summary
+ and doesn't help developers at all. Try something more meaningful or
+ your bug will probably be ignored due to a lack of precision.
+ The next step is to give a very detailed list of steps to reproduce
+ the problem you encountered. Try to limit these steps to a minimum set
+ required to reproduce the problem. This will make the life of
+ developers easier, and the probability that they consider your bug in
+ a reasonable timeframe will be much higher.
+ </para>
+
+ <note>
+ <para>
+ Try to make sure that everything in the summary is also in the first
+ comment. Summaries are often updated and this will ensure your original
+ information is easily accessible.
+ </para>
+ </note>
+ </listitem>
+
+ <listitem>
+ <para>
+ As you file the bug, you can also attach a document (testcase, patch,
+ or screenshot of the problem).
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Depending on the Bugzilla installation you are using and the product in
+ which you are filing the bug, you can also request developers to consider
+ your bug in different ways (such as requesting review for the patch you
+ just attached, requesting your bug to block the next release of the
+ product, and many other product specific requests).
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Now is a good time to read your bug report again. Remove all misspellings,
+ otherwise your bug may not be found by developers running queries for some
+ specific words, and so your bug would not get any attention.
+ Also make sure you didn't forget any important information developers
+ should know in order to reproduce the problem, and make sure your
+ description of the problem is explicit and clear enough.
+ When you think your bug report is ready to go, the last step is to
+ click the <quote>Commit</quote> button to add your report into the database.
+ </para>
+ </listitem>
+ </orderedlist>
+
+ <para>
+ You do not need to put "any" or similar strings in the URL field.
+ If there is no specific URL associated with the bug, leave this
+ field blank.
+ </para>
+
+ <para>If you feel a bug you filed was incorrectly marked as a
+ DUPLICATE of another, please question it in your bug, not
+ the bug it was duped to. Feel free to CC the person who duped it
+ if they are not already CCed.
+ </para>
+ </section>
+
+ <section id="cloningbugs">
+ <title>Clone an Existing Bug</title>
+
+ <para>
+ Starting with version 2.20, Bugzilla has a feature that allows you
+ to clone an existing bug. The newly created bug will inherit
+ most settings from the old bug. This allows you to track more
+ easily similar concerns in a new bug. To use this, go to the bug
+ that you want to clone, then click the <quote>Clone This Bug</quote>
+ link on the bug page. This will take you to the <quote>Enter Bug</quote>
+ page that is filled with the values that the old bug has.
+ You can change those values and/or texts if needed.
+ </para>
+ </section>
+
+ </section>
+
+ <section id="attachments">
+ <title>Attachments</title>
+
+ <para>
+ You should use attachments, rather than comments, for large chunks of ASCII
+ data, such as trace, debugging output files, or log files. That way, it
+ doesn't bloat the bug for everyone who wants to read it, and cause people to
+ receive fat, useless mails.
+ </para>
+
+ <para>You should make sure to trim screenshots. There's no need to show the
+ whole screen if you are pointing out a single-pixel problem.
+ </para>
+
+ <para>Bugzilla stores and uses a Content-Type for each attachment
+ (e.g. text/html). To download an attachment as a different
+ Content-Type (e.g. application/xhtml+xml), you can override this
+ using a 'content_type' parameter on the URL, e.g.
+ <filename>&amp;content_type=text/plain</filename>.
+ </para>
+
+ <para>
+ If you have a really large attachment, something that does not need to
+ be recorded forever (as most attachments are), or something that is too
+ big for your database, you can mark your attachment as a
+ <quote>Big File</quote>, assuming the administrator of the installation
+ has enabled this feature. Big Files are stored directly on disk instead
+ of in the database. The maximum size of a <quote>Big File</quote> is
+ normally larger than the maximum size of a regular attachment. Independently
+ of the storage system used, an administrator can delete these attachments
+ at any time. Nevertheless, if these files are stored in the database, the
+ <quote>allow_attachment_deletion</quote> parameter (which is turned off
+ by default) must be enabled in order to delete them.
+ </para>
+
+ <para>
+ Also, if the administrator turned on the <quote>allow_attach_url</quote>
+ parameter, you can enter the URL pointing to the attachment instead of
+ uploading the attachment itself. For example, this is useful if you want to
+ point to an external application, a website or a very large file. Note that
+ there is no guarantee that the source file will always be available, nor
+ that its content will remain unchanged.
+ </para>
+
+ <section id="patchviewer">
+ <title>Patch Viewer</title>
+
+ <para>Viewing and reviewing patches in Bugzilla is often difficult due to
+ lack of context, improper format and the inherent readability issues that
+ raw patches present. Patch Viewer is an enhancement to Bugzilla designed
+ to fix that by offering increased context, linking to sections, and
+ integrating with Bonsai, LXR and CVS.</para>
+
+ <para>Patch viewer allows you to:</para>
+
+ <simplelist>
+ <member>View patches in color, with side-by-side view rather than trying
+ to interpret the contents of the patch.</member>
+ <member>See the difference between two patches.</member>
+ <member>Get more context in a patch.</member>
+ <member>Collapse and expand sections of a patch for easy
+ reading.</member>
+ <member>Link to a particular section of a patch for discussion or
+ review</member>
+ <member>Go to Bonsai or LXR to see more context, blame, and
+ cross-references for the part of the patch you are looking at</member>
+ <member>Create a rawtext unified format diff out of any patch, no
+ matter what format it came from</member>
+ </simplelist>
+
+ <section id="patchviewer_view">
+ <title>Viewing Patches in Patch Viewer</title>
+ <para>The main way to view a patch in patch viewer is to click on the
+ "Diff" link next to a patch in the Attachments list on a bug. You may
+ also do this within the edit window by clicking the "View Attachment As
+ Diff" button in the Edit Attachment screen.</para>
+ </section>
+
+ <section id="patchviewer_diff">
+ <title>Seeing the Difference Between Two Patches</title>
+ <para>To see the difference between two patches, you must first view the
+ newer patch in Patch Viewer. Then select the older patch from the
+ dropdown at the top of the page ("Differences between [dropdown] and
+ this patch") and click the "Diff" button. This will show you what
+ is new or changed in the newer patch.</para>
+ </section>
+
+ <section id="patchviewer_context">
+ <title>Getting More Context in a Patch</title>
+ <para>To get more context in a patch, you put a number in the textbox at
+ the top of Patch Viewer ("Patch / File / [textbox]") and hit enter.
+ This will give you that many lines of context before and after each
+ change. Alternatively, you can click on the "File" link there and it
+ will show each change in the full context of the file. This feature only
+ works against files that were diffed using "cvs diff".</para>
+ </section>
+
+ <section id="patchviewer_collapse">
+ <title>Collapsing and Expanding Sections of a Patch</title>
+ <para>To view only a certain set of files in a patch (for example, if a
+ patch is absolutely huge and you want to only review part of it at a
+ time), you can click the "(+)" and "(-)" links next to each file (to
+ expand it or collapse it). If you want to collapse all files or expand
+ all files, you can click the "Collapse All" and "Expand All" links at the
+ top of the page.</para>
+ </section>
+
+ <section id="patchviewer_link">
+ <title>Linking to a Section of a Patch</title>
+ <para>To link to a section of a patch (for example, if you want to be
+ able to give someone a URL to show them which part you are talking
+ about) you simply click the "Link Here" link on the section header. The
+ resulting URL can be copied and used in discussion.</para>
+ </section>
+
+ <section id="patchviewer_bonsai_lxr">
+ <title>Going to Bonsai and LXR</title>
+ <para>To go to Bonsai to get blame for the lines you are interested in,
+ you can click the "Lines XX-YY" link on the section header you are
+ interested in. This works even if the patch is against an old
+ version of the file, since Bonsai stores all versions of the file.</para>
+
+ <para>To go to LXR, you click on the filename on the file header
+ (unfortunately, since LXR only does the most recent version, line
+ numbers are likely to rot).</para>
+ </section>
+
+ <section id="patchviewer_unified_diff">
+ <title>Creating a Unified Diff</title>
+ <para>If the patch is not in a format that you like, you can turn it
+ into a unified diff format by clicking the "Raw Unified" link at the top
+ of the page.</para>
+ </section>
+ </section>
+ </section>
+
+ <section id="hintsandtips">
+ <title>Hints and Tips</title>
+
+ <para>This section distills some Bugzilla tips and best practices
+ that have been developed.</para>
+
+ <section>
+ <title>Autolinkification</title>
+ <para>Bugzilla comments are plain text - so typing &lt;U&gt; will
+ produce less-than, U, greater-than rather than underlined text.
+ However, Bugzilla will automatically make hyperlinks out of certain
+ sorts of text in comments. For example, the text
+ "http://www.bugzilla.org" will be turned into a link:
+ <ulink url="http://www.bugzilla.org"/>.
+ Other strings which get linkified in the obvious manner are:
+ <simplelist>
+ <member>bug 12345</member>
+ <member>comment 7</member>
+ <member>bug 23456, comment 53</member>
+ <member>attachment 4321</member>
+ <member>mailto:george@example.com</member>
+ <member>george@example.com</member>
+ <member>ftp://ftp.mozilla.org</member>
+ <member>Most other sorts of URL</member>
+ </simplelist>
+ </para>
+
+ <para>A corollary here is that if you type a bug number in a comment,
+ you should put the word "bug" before it, so it gets autolinkified
+ for the convenience of others.
+ </para>
+ </section>
+
+ <section id="commenting">
+ <title>Comments</title>
+
+ <para>If you are changing the fields on a bug, only comment if
+ either you have something pertinent to say, or Bugzilla requires it.
+ Otherwise, you may spam people unnecessarily with bug mail.
+ To take an example: a user can set up their account to filter out messages
+ where someone just adds themselves to the CC field of a bug
+ (which happens a lot.) If you come along, add yourself to the CC field,
+ and add a comment saying "Adding self to CC", then that person
+ gets a pointless piece of mail they would otherwise have avoided.
+ </para>
+
+ <para>
+ Don't use sigs in comments. Signing your name ("Bill") is acceptable,
+ if you do it out of habit, but full mail/news-style
+ four line ASCII art creations are not.
+ </para>
+ </section>
+
+ <section id="comment-wrapping">
+ <title>Server-Side Comment Wrapping</title>
+ <para>
+ Bugzilla stores comments unwrapped and wraps them at display time. This
+ ensures proper wrapping in all browsers. Lines beginning with the ">"
+ character are assumed to be quotes, and are not wrapped.
+ </para>
+ </section>
+
+ <section id="dependencytree">
+ <title>Dependency Tree</title>
+
+ <para>
+ On the <quote>Dependency tree</quote> page linked from each bug
+ page, you can see the dependency relationship from the bug as a
+ tree structure.
+ </para>
+
+ <para>
+ You can change how much depth to show, and you can hide resolved bugs
+ from this page. You can also collaps/expand dependencies for
+ each bug on the tree view, using the [-]/[+] buttons that appear
+ before its summary. This option is not available for terminal
+ bugs in the tree (that don't have further dependencies).
+ </para>
+ </section>
+ </section>
+
+ <section id="timetracking">
+ <title>Time Tracking Information</title>
+
+ <para>
+ Users who belong to the group specified by the <quote>timetrackinggroup</quote>
+ parameter have access to time-related fields. Developers can see
+ deadlines and estimated times to fix bugs, and can provide time spent
+ on these bugs.
+ </para>
+
+ <para>
+ At any time, a summary of the time spent by developers on bugs is
+ accessible either from bug lists when clicking the <quote>Time Summary</quote>
+ button or from individual bugs when clicking the <quote>Summarize time</quote>
+ link in the time tracking table. The <filename>summarize_time.cgi</filename>
+ page lets you view this information either per developer or per bug,
+ and can be split on a month basis to have greater details on how time
+ is spent by developers.
+ </para>
+
+ <para>
+ As soon as a bug is marked as RESOLVED, the remaining time expected
+ to fix the bug is set to zero. This lets QA people set it again for
+ their own usage, and it will be set to zero again when the bug will
+ be marked as CLOSED.
+ </para>
+ </section>
+
+ <section id="userpreferences">
+ <title>User Preferences</title>
+
+ <para>
+ Once logged in, you can customize various aspects of
+ Bugzilla via the "Preferences" link in the page footer.
+ The preferences are split into five tabs:</para>
+
+ <section id="generalpreferences" xreflabel="General Preferences">
+ <title>General Preferences</title>
+
+ <para>
+ This tab allows you to change several default settings of Bugzilla.
+ </para>
+
+ <itemizedlist spacing="compact">
+ <listitem>
+ <para>
+ Bugzilla's general appearance (skin) - select which skin to use.
+ Bugzilla supports adding custom skins.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Quote the associated comment when you click on its reply link - sets
+ the behavior of the comment "Reply" link. Options include quoting the
+ full comment, just reference the comment number, or turn the link off.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Language used in email - select which language email will be sent in,
+ from the list of available languages.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ After changing a bug - This controls what page is displayed after
+ changes to a bug are submitted. The options include to show the bug
+ just modified, to show the next bug in your list, or to do nothing.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Enable tags for bugs - turn bug tagging on or off.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Zoom textareas large when in use (requires JavaScript) - enable or
+ disable the automatic expanding of text areas when text is being
+ entered into them.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Field separator character for CSV files -
+ Select between a comma and semi-colon for exported CSV bug lists.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Automatically add me to the CC list of bugs I change - set default
+ behavior of CC list. Options include "Always", "Never", and "Only
+ if I have no role on them".
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ When viewing a bug, show comments in this order -
+ controls the order of comments. Options include "Oldest
+ to Newest", "Newest to Oldest" and "Newest to Oldest, but keep the
+ bug description at the top".
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Show a quip at the top of each bug list - controls
+ whether a quip will be shown on the Bug list page.
+ </para>
+ </listitem>
+ </itemizedlist>
+ </section>
+
+ <section id="emailpreferences">
+ <title>Email Preferences</title>
+
+ <para>
+ This tab allows you to enable or disable email notification on
+ specific events.
+ </para>
+
+ <para>
+ In general, users have almost complete control over how much (or
+ how little) email Bugzilla sends them. If you want to receive the
+ maximum amount of email possible, click the <quote>Enable All
+ Mail</quote> button. If you don't want to receive any email from
+ Bugzilla at all, click the <quote>Disable All Mail</quote> button.
+ </para>
+
+ <note>
+ <para>
+ A Bugzilla administrator can stop a user from receiving
+ bugmail by clicking the <quote>Bugmail Disabled</quote> checkbox
+ when editing the user account. This is a drastic step
+ best taken only for disabled accounts, as it overrides
+ the user's individual mail preferences.
+ </para>
+ </note>
+
+ <para>
+ There are two global options -- <quote>Email me when someone
+ asks me to set a flag</quote> and <quote>Email me when someone
+ sets a flag I asked for</quote>. These define how you want to
+ receive bugmail with regards to flags. Their use is quite
+ straightforward; enable the checkboxes if you want Bugzilla to
+ send you mail under either of the above conditions.
+ </para>
+
+ <para>
+ If you'd like to set your bugmail to something besides
+ 'Completely ON' and 'Completely OFF', the
+ <quote>Field/recipient specific options</quote> table
+ allows you to do just that. The rows of the table
+ define events that can happen to a bug -- things like
+ attachments being added, new comments being made, the
+ priority changing, etc. The columns in the table define
+ your relationship with the bug:
+ </para>
+
+ <itemizedlist spacing="compact">
+ <listitem>
+ <para>
+ Reporter - Where you are the person who initially
+ reported the bug. Your name/account appears in the
+ <quote>Reporter:</quote> field.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Assignee - Where you are the person who has been
+ designated as the one responsible for the bug. Your
+ name/account appears in the <quote>Assigned To:</quote>
+ field of the bug.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ QA Contact - You are one of the designated
+ QA Contacts for the bug. Your account appears in the
+ <quote>QA Contact:</quote> text-box of the bug.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ CC - You are on the list CC List for the bug.
+ Your account appears in the <quote>CC:</quote> text box
+ of the bug.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Voter - You have placed one or more votes for the bug.
+ Your account appears only if someone clicks on the
+ <quote>Show votes for this bug</quote> link on the bug.
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ <note>
+ <para>
+ Some columns may not be visible for your installation, depending
+ on your site's configuration.
+ </para>
+ </note>
+
+ <para>
+ To fine-tune your bugmail, decide the events for which you want
+ to receive bugmail; then decide if you want to receive it all
+ the time (enable the checkbox for every column), or only when
+ you have a certain relationship with a bug (enable the checkbox
+ only for those columns). For example: if you didn't want to
+ receive mail when someone added themselves to the CC list, you
+ could uncheck all the boxes in the <quote>CC Field Changes</quote>
+ line. As another example, if you never wanted to receive email
+ on bugs you reported unless the bug was resolved, you would
+ un-check all boxes in the <quote>Reporter</quote> column
+ except for the one on the <quote>The bug is resolved or
+ verified</quote> row.
+ </para>
+
+ <note>
+ <para>
+ Bugzilla adds the <quote>X-Bugzilla-Reason</quote> header to
+ all bugmail it sends, describing the recipient's relationship
+ (AssignedTo, Reporter, QAContact, CC, or Voter) to the bug.
+ This header can be used to do further client-side filtering.
+ </para>
+ </note>
+
+ <para>
+ Bugzilla has a feature called <quote>Users Watching</quote>.
+ When you enter one or more comma-delineated user accounts (usually email
+ addresses) into the text entry box, you will receive a copy of all the
+ bugmail those users are sent (security settings permitting).
+ This powerful functionality enables seamless transitions as developers
+ change projects or users go on holiday.
+ </para>
+
+ <note>
+ <para>
+ The ability to watch other users may not be available in all
+ Bugzilla installations. If you don't see this feature, and feel
+ that you need it, speak to your administrator.
+ </para>
+ </note>
+
+ <para>
+ Each user listed in the <quote>Users watching you</quote> field
+ has you listed in their <quote>Users to watch</quote> list
+ and can get bugmail according to your relationship to the bug and
+ their <quote>Field/recipient specific options</quote> setting.
+ </para>
+
+ </section>
+
+ <section id="savedsearches" xreflabel="Saved Searches">
+ <title>Saved Searches</title>
+ <para>
+ On this tab you can view and run any Saved Searches that you have
+ created, and also any Saved Searches that other members of the group
+ defined in the "querysharegroup" parameter have shared.
+ Saved Searches can be added to the page footer from this screen.
+ If somebody is sharing a Search with a group she or he is allowed to
+ <link linkend="groups">assign users to</link>, the sharer may opt to have
+ the Search show up in the footer of the group's direct members by default.
+ </para>
+ </section>
+
+ <section id="accountpreferences" xreflabel="Name and Password">
+ <title>Name and Password</title>
+
+ <para>On this tab, you can change your basic account information,
+ including your password, email address and real name. For security
+ reasons, in order to change anything on this page you must type your
+ <emphasis>current</emphasis> password into the <quote>Password</quote>
+ field at the top of the page.
+ If you attempt to change your email address, a confirmation
+ email is sent to both the old and new addresses, with a link to use to
+ confirm the change. This helps to prevent account hijacking.</para>
+ </section>
+
+ <section id="permissionsettings">
+ <title>Permissions</title>
+
+ <para>
+ This is a purely informative page which outlines your current
+ permissions on this installation of Bugzilla.
+ </para>
+
+ <para>
+ A complete list of permissions is below. Only users with
+ <emphasis>editusers</emphasis> privileges can change the permissions
+ of other users.
+ </para>
+
+ <variablelist>
+ <varlistentry>
+ <term>
+ admin
+ </term>
+ <listitem>
+ <para>
+ Indicates user is an Administrator.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ bz_canusewhineatothers
+ </term>
+ <listitem>
+ <para>
+ Indicates user can configure whine reports for other users.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ bz_canusewhines
+ </term>
+ <listitem>
+ <para>
+ Indicates user can configure whine reports for self.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ bz_sudoers
+ </term>
+ <listitem>
+ <para>
+ Indicates user can perform actions as other users.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ bz_sudo_protect
+ </term>
+ <listitem>
+ <para>
+ Indicates user can not be impersonated by other users.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ canconfirm
+ </term>
+ <listitem>
+ <para>
+ Indicates user can confirm a bug or mark it a duplicate.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ creategroups
+ </term>
+ <listitem>
+ <para>
+ Indicates user can create and destroy groups.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ editbugs
+ </term>
+ <listitem>
+ <para>
+ Indicates user can edit all bug fields.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ editclassifications
+ </term>
+ <listitem>
+ <para>
+ Indicates user can create, destroy, and edit classifications.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ editcomponents
+ </term>
+ <listitem>
+ <para>
+ Indicates user can create, destroy, and edit components.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ editkeywords
+ </term>
+ <listitem>
+ <para>
+ Indicates user can create, destroy, and edit keywords.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ editusers
+ </term>
+ <listitem>
+ <para>
+ Indicates user can edit or disable users.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ tweakparams
+ </term>
+ <listitem>
+ <para>
+ Indicates user can change Parameters.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+
+ <note>
+ <para>
+ For more information on how permissions work in Bugzilla (i.e. who can
+ change what), see <xref linkend="cust-change-permissions"/>.
+ </para>
+ </note>
+
+ </section>
+ </section>
+
+
+ <section id="reporting">
+ <title>Reports and Charts</title>
+
+ <para>As well as the standard buglist, Bugzilla has two more ways of
+ viewing sets of bugs. These are the reports (which give different
+ views of the current state of the database) and charts (which plot
+ the changes in particular sets of bugs over time.)</para>
+
+ <section id="reports">
+ <title>Reports</title>
+
+ <para>
+ A report is a view of the current state of the bug database.
+ </para>
+
+ <para>
+ You can run either an HTML-table-based report, or a graphical
+ line/pie/bar-chart-based one. The two have different pages to
+ define them, but are close cousins - once you've defined and
+ viewed a report, you can switch between any of the different
+ views of the data at will.
+ </para>
+
+ <para>
+ Both report types are based on the idea of defining a set of bugs
+ using the standard search interface, and then choosing some
+ aspect of that set to plot on the horizontal and/or vertical axes.
+ You can also get a form of 3-dimensional report by choosing to have
+ multiple images or tables.
+ </para>
+
+ <para>
+ So, for example, you could use the search form to choose "all
+ bugs in the WorldControl product", and then plot their severity
+ against their component to see which component had had the largest
+ number of bad bugs reported against it.
+ </para>
+
+ <para>
+ Once you've defined your parameters and hit "Generate Report",
+ you can switch between HTML, CSV, Bar, Line and Pie. (Note: Pie
+ is only available if you didn't define a vertical axis, as pie
+ charts don't have one.) The other controls are fairly self-explanatory;
+ you can change the size of the image if you find text is overwriting
+ other text, or the bars are too thin to see.
+ </para>
+
+ </section>
+
+ <section id="charts">
+ <title>Charts</title>
+
+ <para>
+ A chart is a view of the state of the bug database over time.
+ </para>
+
+ <para>
+ Bugzilla currently has two charting systems - Old Charts and New
+ Charts. Old Charts have been part of Bugzilla for a long time; they
+ chart each status and resolution for each product, and that's all.
+ They are deprecated, and going away soon - we won't say any more
+ about them.
+ New Charts are the future - they allow you to chart anything you
+ can define as a search.
+ </para>
+
+ <note>
+ <para>
+ Both charting forms require the administrator to set up the
+ data-gathering script. If you can't see any charts, ask them whether
+ they have done so.
+ </para>
+ </note>
+
+ <para>
+ An individual line on a chart is called a data set.
+ All data sets are organised into categories and subcategories. The
+ data sets that Bugzilla defines automatically use the Product name
+ as a Category and Component names as Subcategories, but there is no
+ need for you to follow that naming scheme with your own charts if
+ you don't want to.
+ </para>
+
+ <para>
+ Data sets may be public or private. Everyone sees public data sets in
+ the list, but only their creator sees private data sets. Only
+ administrators can make data sets public.
+ No two data sets, even two private ones, can have the same set of
+ category, subcategory and name. So if you are creating private data
+ sets, one idea is to have the Category be your username.
+ </para>
+
+ <section>
+ <title>Creating Charts</title>
+
+ <para>
+ You create a chart by selecting a number of data sets from the
+ list, and pressing Add To List for each. In the List Of Data Sets
+ To Plot, you can define the label that data set will have in the
+ chart's legend, and also ask Bugzilla to Sum a number of data sets
+ (e.g. you could Sum data sets representing RESOLVED, VERIFIED and
+ CLOSED in a particular product to get a data set representing all
+ the resolved bugs in that product.)
+ </para>
+
+ <para>
+ If you've erroneously added a data set to the list, select it
+ using the checkbox and click Remove. Once you add more than one
+ data set, a "Grand Total" line
+ automatically appears at the bottom of the list. If you don't want
+ this, simply remove it as you would remove any other line.
+ </para>
+
+ <para>
+ You may also choose to plot only over a certain date range, and
+ to cumulate the results - that is, to plot each one using the
+ previous one as a baseline, so the top line gives a sum of all
+ the data sets. It's easier to try than to explain :-)
+ </para>
+
+ <para>
+ Once a data set is in the list, one can also perform certain
+ actions on it. For example, one can edit the
+ data set's parameters (name, frequency etc.) if it's one you
+ created or if you are an administrator.
+ </para>
+
+ <para>
+ Once you are happy, click Chart This List to see the chart.
+ </para>
+
+ </section>
+
+ <section id="charts-new-series">
+ <title>Creating New Data Sets</title>
+
+ <para>
+ You may also create new data sets of your own. To do this,
+ click the "create a new data set" link on the Create Chart page.
+ This takes you to a search-like interface where you can define
+ the search that Bugzilla will plot. At the bottom of the page,
+ you choose the category, sub-category and name of your new
+ data set.
+ </para>
+
+ <para>
+ If you have sufficient permissions, you can make the data set public,
+ and reduce the frequency of data collection to less than the default
+ seven days.
+ </para>
+ </section>
+
+ </section>
+
+ </section>
+
+ <section id="flags">
+ <title>Flags</title>
+
+ <para>
+ A flag is a kind of status that can be set on bugs or attachments
+ to indicate that the bugs/attachments are in a certain state.
+ Each installation can define its own set of flags that can be set
+ on bugs or attachments.
+ </para>
+
+ <para>
+ If your installation has defined a flag, you can set or unset that flag,
+ and if your administrator has enabled requesting of flags, you can submit
+ a request for another user to set the flag.
+ </para>
+
+ <para>
+ To set a flag, select either "+" or "-" from the drop-down menu next to
+ the name of the flag in the "Flags" list. The meaning of these values are
+ flag-specific and thus cannot be described in this documentation,
+ but by way of example, setting a flag named "review" to "+" may indicate
+ that the bug/attachment has passed review, while setting it to "-"
+ may indicate that the bug/attachment has failed review.
+ </para>
+
+ <para>
+ To unset a flag, click its drop-down menu and select the blank value.
+ Note that marking an attachment as obsolete automatically cancels all
+ pending requests for the attachment.
+ </para>
+
+ <para>
+ If your administrator has enabled requests for a flag, request a flag
+ by selecting "?" from the drop-down menu and then entering the username
+ of the user you want to set the flag in the text field next to the menu.
+ </para>
+
+ <para>
+ A set flag appears in bug reports and on "edit attachment" pages with the
+ abbreviated username of the user who set the flag prepended to the
+ flag name. For example, if Jack sets a "review" flag to "+", it appears
+ as Jack: review [ + ]
+ </para>
+
+ <para>
+ A requested flag appears with the user who requested the flag prepended
+ to the flag name and the user who has been requested to set the flag
+ appended to the flag name within parentheses. For example, if Jack
+ asks Jill for review, it appears as Jack: review [ ? ] (Jill).
+ </para>
+
+ <para>
+ You can browse through open requests made of you and by you by selecting
+ 'My Requests' from the footer. You can also look at open requests limited
+ by other requesters, requestees, products, components, and flag names from
+ this page. Note that you can use '-' for requestee to specify flags with
+ 'no requestee' set.
+ </para>
+ </section>
+
+ <section id="whining">
+ <title>Whining</title>
+
+ <para>
+ Whining is a feature in Bugzilla that can regularly annoy users at
+ specified times. Using this feature, users can execute saved searches
+ at specific times (i.e. the 15th of the month at midnight) or at
+ regular intervals (i.e. every 15 minutes on Sundays). The results of the
+ searches are sent to the user, either as a single email or as one email
+ per bug, along with some descriptive text.
+ </para>
+
+ <warning>
+ <para>
+ Throughout this section it will be assumed that all users are members
+ of the bz_canusewhines group, membership in which is required in order
+ to use the Whining system. You can easily make all users members of
+ the bz_canusewhines group by setting the User RegExp to ".*" (without
+ the quotes).
+ </para>
+
+ <para>
+ Also worth noting is the bz_canusewhineatothers group. Members of this
+ group can create whines for any user or group in Bugzilla using a
+ extended form of the whining interface. Features only available to
+ members of the bz_canusewhineatothers group will be noted in the
+ appropriate places.
+ </para>
+ </warning>
+
+ <note>
+ <para>
+ For whining to work, a special Perl script must be executed at regular
+ intervals. More information on this is available in
+ <xref linkend="installation-whining"/>.
+ </para>
+ </note>
+
+ <note>
+ <para>
+ This section does not cover the whineatnews.pl script. See
+ <xref linkend="installation-whining-cron"/> for more information on
+ The Whining Cron.
+ </para>
+ </note>
+
+ <section id="whining-overview">
+ <title>The Event</title>
+
+ <para>
+ The whining system defines an "Event" as one or more queries being
+ executed at regular intervals, with the results of said queries (if
+ there are any) being emailed to the user. Events are created by
+ clicking on the "Add new event" button.
+ </para>
+
+ <para>
+ Once a new event is created, the first thing to set is the "Email
+ subject line". The contents of this field will be used in the subject
+ line of every email generated by this event. In addition to setting a
+ subject, space is provided to enter some descriptive text that will be
+ included at the top of each message (to help you in understanding why
+ you received the email in the first place).
+ </para>
+
+ <para>
+ The next step is to specify when the Event is to be run (the Schedule)
+ and what searches are to be performed (the Searches).
+ </para>
+
+ </section>
+
+ <section id="whining-schedule">
+ <title>Whining Schedule</title>
+
+ <para>
+ Each whining event is associated with zero or more schedules. A
+ schedule is used to specify when the query (specified below) is to be
+ run. A new event starts out with no schedules (which means it will
+ never run, as it is not scheduled to run). To add a schedule, press
+ the "Add a new schedule" button.
+ </para>
+
+ <para>
+ Each schedule includes an interval, which you use to tell Bugzilla
+ when the event should be run. An event can be run on certain days of
+ the week, certain days of the month, during weekdays (defined as
+ Monday through Friday), or every day.
+ </para>
+
+ <warning>
+ <para>
+ Be careful if you set your event to run on the 29th, 30th, or 31st of
+ the month, as your event may not run exactly when expected. If you
+ want your event to run on the last day of the month, select "Last day
+ of the month" as the interval.
+ </para>
+ </warning>
+
+ <para>
+ Once you have specified the day(s) on which the event is to be run, you
+ should now specify the time at which the event is to be run. You can
+ have the event run at a certain hour on the specified day(s), or
+ every hour, half-hour, or quarter-hour on the specified day(s).
+ </para>
+
+ <para>
+ If a single schedule does not execute an event as many times as you
+ would want, you can create another schedule for the same event. For
+ example, if you want to run an event on days whose numbers are
+ divisible by seven, you would need to add four schedules to the event,
+ setting the schedules to run on the 7th, 14th, 21st, and 28th (one day
+ per schedule) at whatever time (or times) you choose.
+ </para>
+
+ <note>
+ <para>
+ If you are a member of the bz_canusewhineatothers group, then you
+ will be presented with another option: "Mail to". Using this you
+ can control who will receive the emails generated by this event. You
+ can choose to send the emails to a single user (identified by email
+ address) or a single group (identified by group name). To send to
+ multiple users or groups, create a new schedule for each additional
+ user/group.
+ </para>
+ </note>
+ </section>
+
+ <section id="whining-query">
+ <title>Whining Searches</title>
+
+ <para>
+ Each whining event is associated with zero or more searches. A search
+ is any saved search to be run as part of the specified schedule (see
+ above). You start out without any searches associated with the event
+ (which means that the event will not run, as there will never be any
+ results to return). To add a search, press the "Include search" button.
+ </para>
+
+ <para>
+ The first field to examine in your newly added search is the Sort field.
+ Searches are run, and results included, in the order specified by the
+ Sort field. Searches with smaller Sort values will run before searches
+ with bigger Sort values.
+ </para>
+
+ <para>
+ The next field to examine is the Search field. This is where you
+ choose the actual search that is to be run. Instead of defining search
+ parameters here, you are asked to choose from the list of saved
+ searches (the same list that appears at the bottom of every Bugzilla
+ page). You are only allowed to choose from searches that you have
+ saved yourself (the default saved search, "My Bugs", is not a valid
+ choice). If you do not have any saved searches, you can take this
+ opportunity to create one (see <xref linkend="list"/>).
+ </para>
+
+ <note>
+ <para>
+ When running queries, the whining system acts as if you are the user
+ executing the query. This means that the whining system will ignore
+ bugs that match your query, but that you can not access.
+ </para>
+ </note>
+
+ <para>
+ Once you have chosen the saved search to be executed, give the query a
+ descriptive title. This title will appear in the email, above the
+ results of the query. If you choose "One message per bug", the query
+ title will appear at the top of each email that contains a bug matching
+ your query.
+ </para>
+
+ <para>
+ Finally, decide if the results of the query should be sent in a single
+ email, or if each bug should appear in its own email.
+ </para>
+
+ <warning>
+ <para>
+ Think carefully before checking the "One message per bug" box. If
+ you create a query that matches thousands of bugs, you will receive
+ thousands of emails!
+ </para>
+ </warning>
+ </section>
+
+ <section>
+ <title>Saving Your Changes</title>
+
+ <para>
+ Once you have defined at least one schedule, and created at least one
+ query, go ahead and "Update/Commit". This will save your Event and make
+ it available for immediate execution.
+ </para>
+
+ <note>
+ <para>
+ If you ever feel like deleting your event, you may do so using the
+ "Remove Event" button in the upper-right corner of each Event. You
+ can also modify an existing event, so long as you "Update/Commit"
+ after completing your modifications.
+ </para>
+ </note>
+ </section>
+
+ </section>
+
+</chapter>
+
+<!-- Keep this comment at the end of the file
+Local variables:
+mode: sgml
+sgml-always-quote-attributes:t
+sgml-auto-insert-required-elements:t
+sgml-balanced-tag-edit:t
+sgml-exposed-tags:nil
+sgml-general-insert-case:lower
+sgml-indent-data:t
+sgml-indent-step:2
+sgml-local-catalogs:nil
+sgml-local-ecat-files:nil
+sgml-minimize-attributes:nil
+sgml-namecase-general:t
+sgml-omittag:t
+sgml-parent-document:("Bugzilla-Guide.xml" "book" "chapter")
+sgml-shorttag:t
+sgml-tag-region-if-active:t
+End:
+-->
+
diff --git a/docs/lib/Pod/Simple/HTML/Bugzilla.pm b/docs/lib/Pod/Simple/HTML/Bugzilla.pm
new file mode 100644
index 000000000..f82ab9266
--- /dev/null
+++ b/docs/lib/Pod/Simple/HTML/Bugzilla.pm
@@ -0,0 +1,78 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved.
+# Portions created by Everything Solved are Copyright (C) 2006
+# Everything Solved. All Rights Reserved.
+#
+# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Pod::Simple::HTML::Bugzilla;
+
+use strict;
+use base 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">';
+
+sub new {
+ my $self = shift->SUPER::new(@_);
+
+ my $doctype = $self->DOCTYPE;
+ my $content_type = $self->META_CT;
+
+ my $html_pre_title = <<END_HTML;
+$doctype
+<html>
+ <head>
+ <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);
+
+ # 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'};
+
+ 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;
+}
+
+1;
diff --git a/docs/lib/Pod/Simple/HTMLBatch/Bugzilla.pm b/docs/lib/Pod/Simple/HTMLBatch/Bugzilla.pm
new file mode 100644
index 000000000..95ab3f435
--- /dev/null
+++ b/docs/lib/Pod/Simple/HTMLBatch/Bugzilla.pm
@@ -0,0 +1,119 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved.
+# Portions created by Everything Solved are Copyright (C) 2006
+# Everything Solved. All Rights Reserved.
+#
+# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Pod::Simple::HTMLBatch::Bugzilla;
+
+use strict;
+use base qw(Pod::Simple::HTMLBatch);
+
+# This is the same hack that HTMLBatch does to "import" this subroutine.
+BEGIN { *esc = \&Pod::Simple::HTML::esc }
+
+# Describes how top-level modules should be sorted and named. This
+# is a translation from HTMLBatch's names to our categories.
+# 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'],
+ 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;
+<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";
+ }
+
+ 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;
+}
+
+# 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;
+}
+
+1;
diff --git a/docs/makedocs.pl b/docs/makedocs.pl
new file mode 100755
index 000000000..506fbe61b
--- /dev/null
+++ b/docs/makedocs.pl
@@ -0,0 +1,228 @@
+#!/usr/bin/perl -w
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Matthew Tuck <matty@chariot.net.au>
+# Jacob Steenhagen <jake@bugzilla.org>
+# Colin Ogilvie <colin.ogilvie@gmail.com>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+# This script compiles all the documentation.
+
+use strict;
+use Cwd;
+
+# We need to be in this directory to use our libraries.
+BEGIN {
+ require File::Basename;
+ import File::Basename qw(dirname);
+ chdir dirname($0);
+}
+
+use lib qw(.. ../lib lib);
+
+# We only compile our POD if Pod::Simple is installed. We do the checks
+# this way so that if there's a compile error in Pod::Simple::HTML::Bugzilla,
+# makedocs doesn't just silently fail, but instead actually tells us there's
+# a compile error.
+my $pod_simple;
+if (eval { require Pod::Simple }) {
+ require Pod::Simple::HTMLBatch::Bugzilla;
+ require Pod::Simple::HTML::Bugzilla;
+ $pod_simple = 1;
+};
+
+use Bugzilla::Install::Requirements
+ qw(REQUIRED_MODULES OPTIONAL_MODULES);
+use Bugzilla::Constants qw(DB_MODULE BUGZILLA_VERSION);
+
+###############################################################################
+# Generate minimum version list
+###############################################################################
+
+my $modules = REQUIRED_MODULES;
+my $opt_modules = OPTIONAL_MODULES;
+
+open(ENTITIES, '>', 'bugzilla.ent') or die('Could not open bugzilla.ent: ' . $!);
+print ENTITIES '<?xml version="1.0"?>' ."\n\n";
+print ENTITIES '<!-- Module Versions -->' . "\n";
+foreach my $module (@$modules, @$opt_modules)
+{
+ my $name = $module->{'module'};
+ $name =~ s/::/-/g;
+ $name = lc($name);
+ #This needs to be a string comparison, due to the modules having
+ #version numbers like 0.9.4
+ my $version = $module->{'version'} eq 0 ? 'any' : $module->{'version'};
+ print ENTITIES '<!ENTITY min-' . $name . '-ver "'.$version.'">' . "\n";
+}
+
+print ENTITIES "\n <!-- Database Versions --> \n";
+
+my $db_modules = DB_MODULE;
+foreach my $db (keys %$db_modules) {
+ my $dbd = $db_modules->{$db}->{dbd};
+ my $name = $dbd->{module};
+ $name =~ s/::/-/g;
+ $name = lc($name);
+ my $version = $dbd->{version} || 'any';
+ my $db_version = $db_modules->{$db}->{'db_version'};
+ print ENTITIES '<!ENTITY min-' . $name . '-ver "'.$version.'">' . "\n";
+ print ENTITIES '<!ENTITY min-' . lc($db) . '-ver "'.$db_version.'">' . "\n";
+}
+close(ENTITIES);
+
+###############################################################################
+# Environment Variable Checking
+###############################################################################
+
+my ($JADE_PUB, $LDP_HOME, $build_docs);
+$build_docs = 1;
+if (defined $ENV{JADE_PUB} && $ENV{JADE_PUB} ne '') {
+ $JADE_PUB = $ENV{JADE_PUB};
+}
+else {
+ print "To build 'The Bugzilla Guide', you need to set the ";
+ print "JADE_PUB environment variable first.\n";
+ $build_docs = 0;
+}
+
+if (defined $ENV{LDP_HOME} && $ENV{LDP_HOME} ne '') {
+ $LDP_HOME = $ENV{LDP_HOME};
+}
+else {
+ print "To build 'The Bugzilla Guide', you need to set the ";
+ print "LDP_HOME environment variable first.\n";
+ $build_docs = 0;
+}
+
+###############################################################################
+# Subs
+###############################################################################
+
+sub MakeDocs {
+
+ my ($name, $cmdline) = @_;
+
+ print "Creating $name documentation ...\n" if defined $name;
+ print "$cmdline\n\n";
+ system $cmdline;
+ print "\n";
+
+}
+
+sub make_pod {
+
+ print "Creating API documentation...\n";
+
+ my $converter = Pod::Simple::HTMLBatch::Bugzilla->new;
+ # 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 $contents_start = <<END_HTML;
+$doctype
+<html>
+ <head>
+ $content_type
+ <title>Bugzilla $bz_version API Documentation</title>
+ </head>
+ <body class="contentspage">
+ <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);
+ $converter->batch_convert(['../../'], 'html/api/');
+
+ print "\n";
+}
+
+###############################################################################
+# Make the docs ...
+###############################################################################
+
+my @langs;
+# search for sub directories which have a 'xml' sub-directory
+opendir(LANGS, './');
+foreach my $dir (readdir(LANGS)) {
+ next if (($dir eq '.') || ($dir eq '..') || (! -d $dir));
+ if (-d "$dir/xml") {
+ push(@langs, $dir);
+ }
+}
+closedir(LANGS);
+
+my $docparent = getcwd();
+foreach my $lang (@langs) {
+ chdir "$docparent/$lang";
+ MakeDocs(undef, 'cp ../bugzilla.ent ./xml/');
+
+ if (!-d 'txt') {
+ unlink 'txt';
+ mkdir 'txt', 0755;
+ }
+ if (!-d 'pdf') {
+ unlink 'pdf';
+ mkdir 'pdf', 0755;
+ }
+ if (!-d 'html') {
+ unlink 'html';
+ mkdir 'html', 0755;
+ }
+ if (!-d 'html/api') {
+ unlink 'html/api';
+ mkdir 'html/api', 0755;
+ }
+
+ make_pod() if $pod_simple;
+ next unless $build_docs;
+
+ chdir 'html';
+
+ MakeDocs('separate HTML', "jade -t sgml -i html -d $LDP_HOME/ldp.dsl\#html " .
+ "$JADE_PUB/xml.dcl ../xml/Bugzilla-Guide.xml");
+ MakeDocs('big HTML', "jade -V nochunks -t sgml -i html -d " .
+ "$LDP_HOME/ldp.dsl\#html $JADE_PUB/xml.dcl " .
+ "../xml/Bugzilla-Guide.xml > Bugzilla-Guide.html");
+ MakeDocs('big text', "lynx -dump -justify=off -nolist Bugzilla-Guide.html " .
+ "> ../txt/Bugzilla-Guide.txt");
+
+ if (! grep($_ eq "--with-pdf", @ARGV)) {
+ next;
+ }
+
+ MakeDocs('PDF', "jade -t tex -d $LDP_HOME/ldp.dsl\#print $JADE_PUB/xml.dcl " .
+ '../xml/Bugzilla-Guide.xml');
+ chdir '../pdf';
+ MakeDocs(undef, 'mv ../xml/Bugzilla-Guide.tex .');
+ MakeDocs(undef, 'pdfjadetex Bugzilla-Guide.tex');
+ MakeDocs(undef, 'pdfjadetex Bugzilla-Guide.tex');
+ MakeDocs(undef, 'pdfjadetex Bugzilla-Guide.tex');
+ MakeDocs(undef, 'rm Bugzilla-Guide.tex Bugzilla-Guide.log Bugzilla-Guide.aux Bugzilla-Guide.out');
+
+}
+
diff --git a/docs/style.css b/docs/style.css
new file mode 100644
index 000000000..1c9a6bcb1
--- /dev/null
+++ b/docs/style.css
@@ -0,0 +1,107 @@
+/* The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is the Bugzilla Bug Tracking System.
+ *
+ * The Initial Developer of the Original Code is Everything Solved.
+ * Portions created by Everything Solved are Copyright (C) 2006
+ * Everything Solved. All Rights Reserved.
+ *
+ * Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+ */
+
+body {
+ background: white;
+ color: #111;
+ padding: 0 1em;
+ margin: 0;
+ font-family: Verdana, Arial, sans-serif;
+ font-size: small;
+}
+
+a:link, a:active { color: #36415c; }
+a:visited { color: #666; }
+a:hover { color: #888; }
+
+h1 {
+ font-size: 150%;
+ font-weight: bold;
+ border-bottom: 2px solid #ccc;
+}
+h2 {
+ font-size: 125%;
+ font-weight: bold;
+ border-bottom: 1px solid #ccc;
+ margin-bottom: 8px;
+}
+h3 {
+ font-size: 115%;
+ font-weight: bold;
+ margin-bottom: 0;
+ padding-bottom: 0;
+}
+
+/* This makes Description/Params/Returns look nice. */
+dd { margin-top: .2em; }
+dd p { margin-top: 0; }
+dl { margin-bottom: 1em; }
+
+/* This makes the names of functions slightly larger, in Gecko. */
+body > dl > dt code { font-size: 1.35em; }
+
+#pod h1 a, #pod h2 a, #pod h3 a {
+ color: #36415c;
+ text-decoration: none;
+}
+
+pre, code, tt, kbd, samp {
+ /* Unfortunately, the default monospace fonts on most browsers
+ look odd with relative sizing. */
+ font-size: 12px;
+}
+
+.code {
+ background: #eed;
+ border: 1px solid #ccc;
+}
+
+pre.code {
+ margin-left: 10px;
+ width: 90%;
+ padding: 10px;
+}
+
+/* Special styles for the Contents page */
+
+.contentspage dt {
+ font-size: large;
+ font-weight: bold;
+}
+
+.pod_desc_table {
+ border-collapse: collapse;
+ table-layout: auto;
+ border: 1px solid #ccc;
+}
+
+.pod_desc_table th {
+ text-align: left;
+}
+
+.pod_desc_table td, .pod_desc_table th {
+ padding: .25em;
+ border-top: 1px solid #ccc;
+}
+
+.pod_desc_table .odd th, .pod_desc_table .odd td {
+ background-color: #eee;
+}
+
+.pod_desc_table
diff --git a/duplicates.cgi b/duplicates.cgi
new file mode 100755
index 000000000..2a52742c6
--- /dev/null
+++ b/duplicates.cgi
@@ -0,0 +1,263 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s):
+# Gervase Markham <gerv@gerv.net>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+use strict;
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Util;
+use Bugzilla::Error;
+use Bugzilla::Search;
+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.
+ fully_exclude_status => ['CLOSED'],
+ partly_exclude_status => ['VERIFIED'],
+ except_resolution => ['INVALID', 'WONTFIX'],
+ changedsince => 7,
+ maxrows => 20,
+ sortby => 'count',
+};
+
+###############
+# Subroutines #
+###############
+
+# $counts is a count of exactly how many direct duplicates there are for
+# each bug we're considering. $dups is a map of duplicates, from one
+# bug_id to another. We go through the duplicates map ($dups) and if one bug
+# 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) = @_;
+
+ 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;
+ }
+ # 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;
+}
+
+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;
+
+}
+
+###############
+# Main Script #
+###############
+
+my $cgi = Bugzilla->cgi;
+my $template = Bugzilla->template;
+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");
+if (!grep(lc($_) eq lc($sortby), qw(count delta id))) {
+ 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;
+ }
+}
+my @query_products = $cgi->param('product');
+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) };
+}
+
+# Make sure all products are valid.
+@query_products = map { Bugzilla::Product->check($_) } @query_products;
+
+# Small backwards-compatibility hack, dated 2002-04-10.
+$sortby = "count" if $sortby eq "dup_count";
+
+my $origmaxrows = $maxrows;
+detaint_natural($maxrows)
+ || ThrowUserError("invalid_maxrows", { maxrows => $origmaxrows});
+
+my $origchangedsince = $changedsince;
+detaint_natural($changedsince)
+ || ThrowUserError("invalid_changedsince",
+ { changedsince => $origchangedsince });
+
+my %total_dups = @{$dbh->selectcol_arrayref(
+ "SELECT dupe_of, COUNT(dupe)
+ FROM duplicates
+ GROUP BY dupe_of", {Columns => [1,2]})};
+
+my %dupe_relation = @{$dbh->selectcol_arrayref(
+ "SELECT dupe, dupe_of FROM duplicates
+ 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(
+ "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 >= LOCALTIMESTAMP(0) - "
+ . $dbh->sql_interval('?', 'DAY') .
+ " GROUP BY dupe_of", {Columns=>[1,2]},
+ $reso_field_id, $changedsince)};
+add_indirect_dups(\%since_dups, \%dupe_relation);
+
+# Enforce the mostfreqthreshold parameter and the "bug_id" cgi param.
+my $mostfreq = Bugzilla->params->{'mostfreqthreshold'};
+foreach my $id (keys %total_dups) {
+ if ($total_dups{$id} < $mostfreq) {
+ 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) };
+}
+
+my @fully_exclude_status = formvalue('fully_exclude_status');
+my @partly_exclude_status = formvalue('partly_exclude_status');
+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);
+ }
+
+ # 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 = @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,
+);
+
+my $format = $template->get_format("reports/duplicates", $vars{'format'});
+print $cgi->header;
+
+# Generate and return the UI (HTML page) from the appropriate template.
+$template->process($format->{'template'}, \%vars)
+ || ThrowTemplateError($template->error());
diff --git a/editclassifications.cgi b/editclassifications.cgi
new file mode 100755
index 000000000..db9dd7f0a
--- /dev/null
+++ b/editclassifications.cgi
@@ -0,0 +1,236 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil; cperl-indent-level: 4 -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Albert Ting
+#
+# Contributor(s): Albert Ting <alt@sonic.net>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+# Frédéric Buclin <LpSolit@gmail.com>
+
+
+use strict;
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Util;
+use Bugzilla::Error;
+use Bugzilla::Classification;
+use Bugzilla::Token;
+
+my $dbh = Bugzilla->dbh;
+my $cgi = Bugzilla->cgi;
+my $template = Bugzilla->template;
+local our $vars = {};
+
+sub LoadTemplate {
+ my $action = shift;
+ my $cgi = Bugzilla->cgi;
+ 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'} = 'classifications.html';
+
+ $action =~ /(\w+)/;
+ $action = $1;
+ print $cgi->header();
+ $template->process("admin/classifications/$action.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
+}
+
+#
+# Preliminary checks:
+#
+
+Bugzilla->login(LOGIN_REQUIRED);
+
+print $cgi->header();
+
+Bugzilla->user->in_group('editclassifications')
+ || ThrowUserError("auth_failure", {group => "editclassifications",
+ action => "edit",
+ object => "classifications"});
+
+ThrowUserError("auth_classification_not_enabled")
+ unless Bugzilla->params->{"useclassification"};
+
+#
+# often used variables
+#
+my $action = trim($cgi->param('action') || '');
+my $class_name = trim($cgi->param('classification') || '');
+my $token = $cgi->param('token');
+
+#
+# action='' -> Show nice list of classifications
+#
+LoadTemplate('select') unless $action;
+
+#
+# action='add' -> present form for parameters for new classification
+#
+# (next action will be 'new')
+#
+
+if ($action eq 'add') {
+ $vars->{'token'} = issue_session_token('add_classification');
+ LoadTemplate($action);
+}
+
+#
+# action='new' -> add classification entered in the 'action=add' screen
+#
+
+if ($action eq 'new') {
+ check_token_data($token, 'add_classification');
+
+ my $classification =
+ Bugzilla::Classification->create({name => $class_name,
+ description => scalar $cgi->param('description'),
+ sortkey => scalar $cgi->param('sortkey')});
+
+ 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');
+}
+
+#
+# action='del' -> ask if user really wants to delete
+#
+# (next action would be 'delete')
+#
+
+if ($action eq 'del') {
+
+ my $classification = Bugzilla::Classification->check($class_name);
+
+ if ($classification->id == 1) {
+ ThrowUserError("classification_not_deletable");
+ }
+
+ if ($classification->product_count()) {
+ ThrowUserError("classification_has_products");
+ }
+
+ $vars->{'classification'} = $classification;
+ $vars->{'token'} = issue_session_token('delete_classification');
+
+ LoadTemplate($action);
+}
+
+#
+# action='delete' -> really delete the classification
+#
+
+if ($action eq 'delete') {
+ check_token_data($token, 'delete_classification');
+
+ my $classification = Bugzilla::Classification->check($class_name);
+ $classification->remove_from_db;
+ delete_token($token);
+
+ $vars->{'message'} = 'classification_deleted';
+ $vars->{'classification'} = $classification;
+ LoadTemplate('select');
+}
+
+#
+# action='edit' -> present the edit classifications from
+#
+# (next action would be 'update')
+#
+
+if ($action eq 'edit') {
+ my $classification = Bugzilla::Classification->check($class_name);
+
+ $vars->{'classification'} = $classification;
+ $vars->{'token'} = issue_session_token('edit_classification');
+
+ LoadTemplate($action);
+}
+
+#
+# action='update' -> update the classification
+#
+
+if ($action eq 'update') {
+ check_token_data($token, 'edit_classification');
+
+ my $class_old_name = trim($cgi->param('classificationold') || '');
+ my $classification = Bugzilla::Classification->check($class_old_name);
+
+ $classification->set_name($class_name);
+ $classification->set_description(scalar $cgi->param('description'));
+ $classification->set_sortkey(scalar $cgi->param('sortkey'));
+
+ my $changes = $classification->update;
+ delete_token($token);
+
+ $vars->{'message'} = 'classification_updated';
+ $vars->{'classification'} = $classification;
+ $vars->{'changes'} = $changes;
+ LoadTemplate('select');
+}
+
+#
+# action='reclassify' -> reclassify products for the classification
+#
+
+if ($action eq 'reclassify') {
+ my $classification = Bugzilla::Classification->check($class_name);
+
+ my $sth = $dbh->prepare("UPDATE products SET classification_id = ?
+ WHERE name = ?");
+
+ 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);
+ }
+ }
+ 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);
+ }
+ }
+ delete_token($token);
+ }
+
+ $vars->{'classifications'} = [Bugzilla::Classification->get_all];
+ $vars->{'classification'} = $classification;
+ $vars->{'token'} = issue_session_token('reclassify_classifications');
+
+ LoadTemplate($action);
+}
+
+#
+# No valid action found
+#
+
+ThrowUserError('unknown_action', {action => $action});
diff --git a/editcomponents.cgi b/editcomponents.cgi
new file mode 100755
index 000000000..fd30daed4
--- /dev/null
+++ b/editcomponents.cgi
@@ -0,0 +1,256 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is mozilla.org code.
+#
+# The Initial Developer of the Original Code is Holger
+# Schurig. Portions created by Holger Schurig are
+# Copyright (C) 1999 Holger Schurig. All
+# Rights Reserved.
+#
+# Contributor(s): Holger Schurig <holgerschurig@nikocity.de>
+# Terry Weissman <terry@mozilla.org>
+# Frédéric Buclin <LpSolit@gmail.com>
+# Akamai Technologies <bugzilla-dev@akamai.com>
+
+use strict;
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Util;
+use Bugzilla::Error;
+use Bugzilla::User;
+use Bugzilla::Component;
+use Bugzilla::Token;
+
+my $cgi = Bugzilla->cgi;
+my $template = Bugzilla->template;
+my $vars = {};
+# There is only one section about components in the documentation,
+# so all actions point to the same page.
+$vars->{'doc_section'} = 'components.html';
+
+#
+# Preliminary checks:
+#
+
+my $user = Bugzilla->login(LOGIN_REQUIRED);
+
+print $cgi->header();
+
+$user->in_group('editcomponents')
+ || scalar(@{$user->get_products_by_permission('editcomponents')})
+ || 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 $showbugcounts = (defined $cgi->param('showbugcounts'));
+my $token = $cgi->param('token');
+
+#
+# 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/components/select-product.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
+}
+
+my $product = $user->check_can_admin_product($product_name);
+
+#
+# action='' -> Show nice list of components
+#
+
+unless ($action) {
+ $vars->{'showbugcounts'} = $showbugcounts;
+ $vars->{'product'} = $product;
+ $template->process("admin/components/list.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
+}
+
+#
+# action='add' -> present form for parameters for new component
+#
+# (next action will be 'new')
+#
+
+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;
+}
+
+#
+# action='new' -> add component entered in the 'action=add' screen
+#
+
+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 $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;
+}
+
+#
+# action='del' -> ask if user really wants to delete
+#
+# (next action would be 'delete')
+#
+
+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;
+}
+
+#
+# action='delete' -> really delete the component
+#
+
+if ($action eq 'delete') {
+ check_token_data($token, 'delete_component');
+ my $component =
+ Bugzilla::Component->check({ product => $product, name => $comp_name });
+
+ $component->remove_from_db;
+
+ $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;
+}
+
+#
+# action='edit' -> present the edit component form
+#
+# (next action would be 'update')
+#
+
+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->{'initial_cc_names'} =
+ join(', ', map($_->login, @{$component->initial_cc}));
+
+ $vars->{'product'} = $product;
+
+ $template->process("admin/components/edit.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
+}
+
+#
+# action='update' -> update the component
+#
+
+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 $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);
+ 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
+ThrowUserError('unknown_action', {action => $action});
diff --git a/editfields.cgi b/editfields.cgi
new file mode 100755
index 000000000..e207a2ee4
--- /dev/null
+++ b/editfields.cgi
@@ -0,0 +1,175 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
+
+use strict;
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Util;
+use Bugzilla::Field;
+use Bugzilla::Token;
+
+my $cgi = Bugzilla->cgi;
+my $template = Bugzilla->template;
+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'});
+
+my $action = trim($cgi->param('action') || '');
+my $token = $cgi->param('token');
+
+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());
+}
+# Interface to add a new custom field.
+elsif ($action eq 'add') {
+ $vars->{'token'} = issue_session_token('add_field');
+
+ $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'),
+ 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_value_id => scalar $cgi->param('visibility_value_id'),
+ 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());
+}
+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_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_value($cgi->param('visibility_value_id'));
+ $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());
+}
+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());
+}
+else {
+ ThrowUserError('unknown_action', {action => $action});
+}
diff --git a/editflagtypes.cgi b/editflagtypes.cgi
new file mode 100755
index 000000000..c09d0edb0
--- /dev/null
+++ b/editflagtypes.cgi
@@ -0,0 +1,710 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Myk Melez <myk@mozilla.org>
+# Frédéric Buclin <LpSolit@gmail.com>
+
+################################################################################
+# Script Initialization
+################################################################################
+
+# Make it harder for us to do dangerous things in Perl.
+use strict;
+use lib qw(. lib);
+
+# Use Bugzilla's flag modules for handling flag types.
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Flag;
+use Bugzilla::FlagType;
+use Bugzilla::Group;
+use Bugzilla::Util;
+use Bugzilla::Error;
+use Bugzilla::Product;
+use Bugzilla::Component;
+use Bugzilla::Bug;
+use Bugzilla::Attachment;
+use Bugzilla::Token;
+
+local our $cgi = Bugzilla->cgi;
+local our $template = Bugzilla->template;
+local our $vars = {};
+
+# We need this everywhere.
+$vars = get_products_and_components($vars);
+
+# Make sure the user is logged in and is an administrator.
+my $user = Bugzilla->login(LOGIN_REQUIRED);
+$user->in_group('editcomponents')
+ || ThrowUserError("auth_failure", {group => "editcomponents",
+ action => "edit",
+ object => "flagtypes"});
+
+################################################################################
+# Main Body Execution
+################################################################################
+
+# All calls to this script should contain an "action" variable whose value
+# determines what the user wants to do. The code below checks the value of
+# that variable and runs the appropriate code.
+
+# Determine whether to use the action specified by the user or the default.
+my $action = $cgi->param('action') || 'list';
+my $token = $cgi->param('token');
+my @categoryActions;
+
+if (@categoryActions = grep(/^categoryAction-.+/, $cgi->param())) {
+ $categoryActions[0] =~ s/^categoryAction-//;
+ processCategoryChange($categoryActions[0], $token);
+ exit;
+}
+
+if ($action eq 'list') { list(); }
+elsif ($action eq 'enter') { edit($action); }
+elsif ($action eq 'copy') { edit($action); }
+elsif ($action eq 'edit') { edit($action); }
+elsif ($action eq 'insert') { insert($token); }
+elsif ($action eq 'update') { update($token); }
+elsif ($action eq 'confirmdelete') { confirmDelete(); }
+elsif ($action eq 'delete') { deleteType($token); }
+elsif ($action eq 'deactivate') { deactivate($token); }
+else {
+ ThrowUserError('unknown_action', {action => $action});
+}
+
+exit;
+
+################################################################################
+# Functions
+################################################################################
+
+sub list {
+ my $product = validateProduct(scalar $cgi->param('product'));
+ my $component = validateComponent($product, scalar $cgi->param('component'));
+ my $product_id = $product ? $product->id : 0;
+ my $component_id = $component ? $component->id : 0;
+ my $show_flag_counts = (defined $cgi->param('show_flag_counts')) ? 1 : 0;
+
+ # Define the variables and functions that will be passed to the UI template.
+ $vars->{'selected_product'} = $cgi->param('product');
+ $vars->{'selected_component'} = $cgi->param('component');
+
+ 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'};
+
+ # Filter flag types if a group ID is given.
+ $bug_flagtypes = filter_group($bug_flagtypes);
+ $attach_flagtypes = filter_group($attach_flagtypes);
+
+ }
+ # 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);
+ $attach_flagtypes = filter_group($attach_flagtypes);
+ }
+ # If no product is given, then show all flag types available.
+ else {
+ $bug_flagtypes =
+ Bugzilla::FlagType::match({'target_type' => 'bug',
+ 'group' => scalar $cgi->param('group')});
+
+ $attach_flagtypes =
+ Bugzilla::FlagType::match({'target_type' => 'attachment',
+ 'group' => scalar $cgi->param('group')});
+ }
+
+ 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.
+ map { push(@{$bug_lists{$flagtype->id}->{$map{$_->status}}}, $_->bug_id) } @$flags;
+ }
+ $vars->{'bug_lists'} = \%bug_lists;
+ $vars->{'show_flag_counts'} = 1;
+ }
+
+ $vars->{'bug_types'} = $bug_flagtypes;
+ $vars->{'attachment_types'} = $attach_flagtypes;
+
+ # Return the appropriate HTTP response headers.
+ print $cgi->header();
+
+ # Generate and return the UI (HTML page) from the appropriate template.
+ $template->process("admin/flag-type/list.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+}
+
+
+sub edit {
+ my ($action) = @_;
+
+ my $flag_type;
+ if ($action eq 'enter') {
+ validateTargetType();
+ }
+ else {
+ $flag_type = validateID();
+ }
+
+ $vars->{'last_action'} = $cgi->param('action');
+ if ($cgi->param('action') eq 'enter' || $cgi->param('action') eq 'copy') {
+ $vars->{'action'} = "insert";
+ $vars->{'token'} = issue_session_token('add_flagtype');
+ }
+ else {
+ $vars->{'action'} = "update";
+ $vars->{'token'} = issue_session_token('edit_flagtype');
+ }
+
+ # If copying or editing an existing flag type, retrieve it.
+ if ($cgi->param('action') eq 'copy' || $cgi->param('action') eq 'edit') {
+ $vars->{'type'} = $flag_type;
+ }
+ # Otherwise set the target type (the minimal information about the type
+ # that the template needs to know) from the URL parameter and default
+ # the list of inclusions to all categories.
+ else {
+ my %inclusions;
+ $inclusions{"__Any__:__Any__"} = "0:0";
+ $vars->{'type'} = { 'target_type' => scalar $cgi->param('target_type'),
+ 'inclusions' => \%inclusions };
+ }
+ # Get a list of groups available to restrict this flag type against.
+ my @groups = Bugzilla::Group->get_all;
+ $vars->{'groups'} = \@groups;
+ # Return the appropriate HTTP response headers.
+ print $cgi->header();
+
+ # Generate and return the UI (HTML page) from the appropriate template.
+ $template->process("admin/flag-type/edit.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+}
+
+sub processCategoryChange {
+ my ($categoryAction, $token) = @_;
+ validateIsActive();
+ validateIsRequestable();
+ validateIsRequesteeble();
+ validateAllowMultiple();
+
+ my @inclusions = $cgi->param('inclusions');
+ my @exclusions = $cgi->param('exclusions');
+ if ($categoryAction eq 'include') {
+ my $product = validateProduct(scalar $cgi->param('product'));
+ my $component = validateComponent($product, scalar $cgi->param('component'));
+ my $category = ($product ? $product->id : 0) . ":" .
+ ($component ? $component->id : 0);
+ push(@inclusions, $category) unless grep($_ eq $category, @inclusions);
+ }
+ elsif ($categoryAction eq 'exclude') {
+ my $product = validateProduct(scalar $cgi->param('product'));
+ my $component = validateComponent($product, scalar $cgi->param('component'));
+ my $category = ($product ? $product->id : 0) . ":" .
+ ($component ? $component->id : 0);
+ push(@exclusions, $category) unless grep($_ eq $category, @exclusions);
+ }
+ elsif ($categoryAction eq 'removeInclusion') {
+ my @inclusion_to_remove = $cgi->param('inclusion_to_remove');
+ foreach my $remove (@inclusion_to_remove) {
+ @inclusions = grep { $_ ne $remove } @inclusions;
+ }
+ }
+ elsif ($categoryAction eq 'removeExclusion') {
+ my @exclusion_to_remove = $cgi->param('exclusion_to_remove');
+ foreach my $remove (@exclusion_to_remove) {
+ @exclusions = grep { $_ ne $remove } @exclusions;
+ }
+ }
+
+ # Convert the array @clusions('prod_ID:comp_ID') back to a hash of
+ # the form %clusions{'prod_name:comp_name'} = 'prod_ID:comp_ID'
+ my %inclusions = clusion_array_to_hash(\@inclusions);
+ my %exclusions = clusion_array_to_hash(\@exclusions);
+
+ my @groups = Bugzilla::Group->get_all;
+ $vars->{'groups'} = \@groups;
+ $vars->{'action'} = $cgi->param('action');
+
+ my $type = {};
+ foreach my $key ($cgi->param()) { $type->{$key} = $cgi->param($key) }
+ # That's what I call a big hack. The template expects to see a group object.
+ # This script needs some rewrite anyway.
+ $type->{'grant_group'} = {};
+ $type->{'grant_group'}->{'name'} = $cgi->param('grant_group');
+ $type->{'request_group'} = {};
+ $type->{'request_group'}->{'name'} = $cgi->param('request_group');
+
+ $type->{'inclusions'} = \%inclusions;
+ $type->{'exclusions'} = \%exclusions;
+ $vars->{'type'} = $type;
+ $vars->{'token'} = $token;
+
+ # Return the appropriate HTTP response headers.
+ print $cgi->header();
+
+ # Generate and return the UI (HTML page) from the appropriate template.
+ $template->process("admin/flag-type/edit.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+}
+
+# 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 = shift;
+ my %hash;
+ my %products;
+ my %components;
+ foreach my $ids (@$array) {
+ trick_taint($ids);
+ my ($product_id, $component_id) = split(":", $ids);
+ my $product_name = "__Any__";
+ if ($product_id) {
+ $products{$product_id} ||= new Bugzilla::Product($product_id);
+ $product_name = $products{$product_id}->name if $products{$product_id};
+ }
+ my $component_name = "__Any__";
+ if ($component_id) {
+ $components{$component_id} ||= new Bugzilla::Component($component_id);
+ $component_name = $components{$component_id}->name if $components{$component_id};
+ }
+ $hash{"$product_name:$component_name"} = $ids;
+ }
+ return %hash;
+}
+
+sub insert {
+ my $token = shift;
+ check_token_data($token, 'add_flagtype');
+ my $name = validateName();
+ my $description = validateDescription();
+ my $cc_list = validateCCList();
+ validateTargetType();
+ validateSortKey();
+ validateIsActive();
+ validateIsRequestable();
+ validateIsRequesteeble();
+ validateAllowMultiple();
+ validateGroups();
+
+ my $dbh = Bugzilla->dbh;
+
+ my $target_type = $cgi->param('target_type') eq "bug" ? "b" : "a";
+
+ $dbh->bz_start_transaction();
+
+ # Insert a record for the new flag type into the database.
+ $dbh->do('INSERT INTO flagtypes
+ (name, description, cc_list, target_type,
+ sortkey, is_active, is_requestable,
+ is_requesteeble, is_multiplicable,
+ grant_group_id, request_group_id)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
+ undef, ($name, $description, $cc_list, $target_type,
+ $cgi->param('sortkey'), $cgi->param('is_active'),
+ $cgi->param('is_requestable'), $cgi->param('is_requesteeble'),
+ $cgi->param('is_multiplicable'), scalar($cgi->param('grant_gid')),
+ scalar($cgi->param('request_gid'))));
+
+ # Get the ID of the new flag type.
+ my $id = $dbh->bz_last_key('flagtypes', 'id');
+
+ # Populate the list of inclusions/exclusions for this flag type.
+ validateAndSubmit($id);
+
+ $dbh->bz_commit_transaction();
+
+ $vars->{'name'} = $name;
+ $vars->{'message'} = "flag_type_created";
+ delete_token($token);
+
+ $vars->{'bug_types'} = Bugzilla::FlagType::match({'target_type' => 'bug'});
+ $vars->{'attachment_types'} = Bugzilla::FlagType::match({'target_type' => 'attachment'});
+
+ # Return the appropriate HTTP response headers.
+ print $cgi->header();
+
+ $template->process("admin/flag-type/list.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+}
+
+
+sub update {
+ my $token = shift;
+ check_token_data($token, 'edit_flagtype');
+ my $flag_type = validateID();
+ my $id = $flag_type->id;
+ my $name = validateName();
+ my $description = validateDescription();
+ my $cc_list = validateCCList();
+ validateTargetType();
+ validateSortKey();
+ validateIsActive();
+ validateIsRequestable();
+ validateIsRequesteeble();
+ validateAllowMultiple();
+ validateGroups();
+
+ my $dbh = Bugzilla->dbh;
+ my $user = Bugzilla->user;
+ $dbh->bz_start_transaction();
+ $dbh->do('UPDATE flagtypes
+ SET name = ?, description = ?, cc_list = ?,
+ sortkey = ?, is_active = ?, is_requestable = ?,
+ is_requesteeble = ?, is_multiplicable = ?,
+ grant_group_id = ?, request_group_id = ?
+ WHERE id = ?',
+ undef, ($name, $description, $cc_list, $cgi->param('sortkey'),
+ $cgi->param('is_active'), $cgi->param('is_requestable'),
+ $cgi->param('is_requesteeble'), $cgi->param('is_multiplicable'),
+ scalar($cgi->param('grant_gid')), scalar($cgi->param('request_gid')),
+ $id));
+
+ # Update the list of inclusions/exclusions for this flag type.
+ validateAndSubmit($id);
+
+ $dbh->bz_commit_transaction();
+
+ # 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
+ LEFT OUTER JOIN flaginclusions AS i
+ 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 flags.type_id = ?
+ AND i.type_id IS NULL',
+ undef, $id);
+ Bugzilla::Flag->force_retarget($flag_ids);
+
+ $flag_ids = $dbh->selectcol_arrayref('SELECT DISTINCT flags.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 flags.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, $id);
+ Bugzilla::Flag->force_retarget($flag_ids);
+
+ # Now silently remove requestees from flags which are no longer
+ # specifically requestable.
+ if (!$cgi->param('is_requesteeble')) {
+ $dbh->do('UPDATE flags SET requestee_id = NULL WHERE type_id = ?',
+ undef, $id);
+ }
+
+ $vars->{'name'} = $name;
+ $vars->{'message'} = "flag_type_changes_saved";
+ delete_token($token);
+
+ $vars->{'bug_types'} = Bugzilla::FlagType::match({'target_type' => 'bug'});
+ $vars->{'attachment_types'} = Bugzilla::FlagType::match({'target_type' => 'attachment'});
+
+ # Return the appropriate HTTP response headers.
+ print $cgi->header();
+
+ $template->process("admin/flag-type/list.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+}
+
+
+sub confirmDelete {
+ my $flag_type = validateID();
+
+ $vars->{'flag_type'} = $flag_type;
+ $vars->{'token'} = issue_session_token('delete_flagtype');
+ # Return the appropriate HTTP response headers.
+ print $cgi->header();
+
+ # Generate and return the UI (HTML page) from the appropriate template.
+ $template->process("admin/flag-type/confirm-delete.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+}
+
+
+sub deleteType {
+ my $token = shift;
+ check_token_data($token, 'delete_flagtype');
+ my $flag_type = validateID();
+ my $id = $flag_type->id;
+ my $dbh = Bugzilla->dbh;
+
+ $dbh->bz_start_transaction();
+
+ # Get the name of the flag type so we can tell users
+ # what was deleted.
+ $vars->{'name'} = $flag_type->name;
+
+ $dbh->do('DELETE FROM flags WHERE type_id = ?', undef, $id);
+ $dbh->do('DELETE FROM flaginclusions WHERE type_id = ?', undef, $id);
+ $dbh->do('DELETE FROM flagexclusions WHERE type_id = ?', undef, $id);
+ $dbh->do('DELETE FROM flagtypes WHERE id = ?', undef, $id);
+ $dbh->bz_commit_transaction();
+
+ $vars->{'message'} = "flag_type_deleted";
+ delete_token($token);
+
+ $vars->{'bug_types'} = Bugzilla::FlagType::match({'target_type' => 'bug'});
+ $vars->{'attachment_types'} = Bugzilla::FlagType::match({'target_type' => 'attachment'});
+
+ # Return the appropriate HTTP response headers.
+ print $cgi->header();
+
+ $template->process("admin/flag-type/list.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+}
+
+
+sub deactivate {
+ my $token = shift;
+ check_token_data($token, 'delete_flagtype');
+ my $flag_type = validateID();
+ validateIsActive();
+
+ my $dbh = Bugzilla->dbh;
+
+ $dbh->bz_start_transaction();
+ $dbh->do('UPDATE flagtypes SET is_active = 0 WHERE id = ?', undef, $flag_type->id);
+ $dbh->bz_commit_transaction();
+
+ $vars->{'message'} = "flag_type_deactivated";
+ $vars->{'flag_type'} = $flag_type;
+ delete_token($token);
+
+ $vars->{'bug_types'} = Bugzilla::FlagType::match({'target_type' => 'bug'});
+ $vars->{'attachment_types'} = Bugzilla::FlagType::match({'target_type' => 'attachment'});
+
+ # Return the appropriate HTTP response headers.
+ print $cgi->header();
+
+ # Generate and return the UI (HTML page) from the appropriate template.
+ $template->process("admin/flag-type/list.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+}
+
+sub get_products_and_components {
+ my $vars = shift;
+
+ my @products = Bugzilla::Product->get_all;
+ # We require all unique component names.
+ my %components;
+ foreach my $product (@products) {
+ foreach my $component (@{$product->components}) {
+ $components{$component->name} = 1;
+ }
+ }
+ $vars->{'products'} = \@products;
+ $vars->{'components'} = [sort(keys %components)];
+ return $vars;
+}
+
+################################################################################
+# Data Validation / Security Authorization
+################################################################################
+
+sub validateID {
+ my $id = $cgi->param('id');
+ my $flag_type = new Bugzilla::FlagType($id)
+ || ThrowCodeError('flag_type_nonexistent', { id => $id });
+
+ return $flag_type;
+}
+
+sub validateName {
+ my $name = $cgi->param('name');
+ ($name && $name !~ /[ ,]/ && length($name) <= 50)
+ || ThrowUserError("flag_type_name_invalid",
+ { name => $name });
+ trick_taint($name);
+ return $name;
+}
+
+sub validateDescription {
+ my $description = $cgi->param('description');
+ length($description) < 2**16-1
+ || ThrowUserError("flag_type_description_invalid");
+ trick_taint($description);
+ return $description;
+}
+
+sub validateCCList {
+ my $cc_list = $cgi->param('cc_list');
+ length($cc_list) <= 200
+ || ThrowUserError("flag_type_cc_list_invalid",
+ { cc_list => $cc_list });
+
+ my @addresses = split(/[, ]+/, $cc_list);
+ # We do not call Util::validate_email_syntax because these
+ # addresses do not require to match 'emailregexp' and do not
+ # depend on 'emailsuffix'. So we limit ourselves to a simple
+ # sanity check:
+ # - match the syntax of a fully qualified email address;
+ # - do not contain any illegal character.
+ foreach my $address (@addresses) {
+ ($address =~ /^[\w\.\+\-=]+@[\w\.\-]+\.[\w\-]+$/
+ && $address !~ /[\\\(\)<>&,;:"\[\] \t\r\n]/)
+ || ThrowUserError('illegal_email_address',
+ {addr => $address, default => 1});
+ }
+ trick_taint($cc_list);
+ return $cc_list;
+}
+
+sub validateProduct {
+ my $product_name = shift;
+ return unless $product_name;
+
+ my $product = Bugzilla::Product->check({ name => $product_name,
+ allow_inaccessible => 1 });
+ return $product;
+}
+
+sub validateComponent {
+ my ($product, $component_name) = @_;
+ return unless $component_name;
+
+ ($product && $product->id)
+ || ThrowUserError("flag_type_component_without_product");
+
+ my $component = Bugzilla::Component->check({ product => $product,
+ name => $component_name });
+ return $component;
+}
+
+sub validateSortKey {
+ # $sortkey is destroyed if detaint_natural fails.
+ my $sortkey = $cgi->param('sortkey');
+ detaint_natural($sortkey)
+ && $sortkey < 32768
+ || ThrowUserError("flag_type_sortkey_invalid",
+ { sortkey => scalar $cgi->param('sortkey') });
+ $cgi->param('sortkey', $sortkey);
+}
+
+sub validateTargetType {
+ grep($cgi->param('target_type') eq $_, ("bug", "attachment"))
+ || ThrowCodeError("flag_type_target_type_invalid",
+ { target_type => scalar $cgi->param('target_type') });
+}
+
+sub validateIsActive {
+ $cgi->param('is_active', $cgi->param('is_active') ? 1 : 0);
+}
+
+sub validateIsRequestable {
+ $cgi->param('is_requestable', $cgi->param('is_requestable') ? 1 : 0);
+}
+
+sub validateIsRequesteeble {
+ $cgi->param('is_requesteeble', $cgi->param('is_requesteeble') ? 1 : 0);
+}
+
+sub validateAllowMultiple {
+ $cgi->param('is_multiplicable', $cgi->param('is_multiplicable') ? 1 : 0);
+}
+
+sub validateGroups {
+ my $dbh = Bugzilla->dbh;
+ # Convert group names to group IDs
+ foreach my $col ('grant', 'request') {
+ my $name = $cgi->param($col . '_group');
+ if ($name) {
+ trick_taint($name);
+ my $gid = $dbh->selectrow_array('SELECT id FROM groups
+ WHERE name = ?', undef, $name);
+ $gid || ThrowUserError("group_unknown", { name => $name });
+ $cgi->param($col . '_gid', $gid);
+ }
+ }
+}
+
+# At this point, values either come the DB itself or have been recently
+# added by the user and have passed all validation tests.
+# The only way to have invalid product/component combinations is to
+# hack the URL. So we silently ignore them, if any.
+sub validateAndSubmit {
+ my ($id) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ # Cache product objects.
+ my %products;
+ foreach my $category_type ("inclusions", "exclusions") {
+ # Will be used several times below.
+ my $sth = $dbh->prepare("INSERT INTO flag$category_type " .
+ "(type_id, product_id, component_id) " .
+ "VALUES (?, ?, ?)");
+
+ $dbh->do("DELETE FROM flag$category_type WHERE type_id = ?", undef, $id);
+ foreach my $category ($cgi->param($category_type)) {
+ trick_taint($category);
+ my ($product_id, $component_id) = split(":", $category);
+ # Does the product exist?
+ if ($product_id) {
+ $products{$product_id} ||= new Bugzilla::Product($product_id);
+ next unless defined $products{$product_id};
+ }
+ # A component was selected without a product being selected.
+ next if (!$product_id && $component_id);
+ # Does the component belong to this product?
+ if ($component_id) {
+ my @match = grep {$_->id == $component_id} @{$products{$product_id}->components};
+ next unless scalar(@match);
+ }
+ $product_id ||= undef;
+ $component_id ||= undef;
+ $sth->execute($id, $product_id, $component_id);
+ }
+ }
+}
+
+sub filter_group {
+ my $flag_types = shift;
+ return $flag_types unless Bugzilla->cgi->param('group');
+
+ my $gid = scalar $cgi->param('group');
+ my @flag_types = grep {($_->grant_group && $_->grant_group->id == $gid)
+ || ($_->request_group && $_->request_group->id == $gid)} @$flag_types;
+
+ return \@flag_types;
+}
diff --git a/editgroups.cgi b/editgroups.cgi
new file mode 100755
index 000000000..a879aa770
--- /dev/null
+++ b/editgroups.cgi
@@ -0,0 +1,472 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Dave Miller <justdave@syndicomm.com>
+# Joel Peshkin <bugreport@peshkin.net>
+# Jacob Steenhagen <jake@bugzilla.org>
+# Vlad Dascalu <jocuri@softhome.net>
+# Frédéric Buclin <LpSolit@gmail.com>
+
+use strict;
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Config qw(:admin);
+use Bugzilla::Util;
+use Bugzilla::Error;
+use Bugzilla::Group;
+use Bugzilla::Product;
+use Bugzilla::User;
+use Bugzilla::Token;
+
+use constant SPECIAL_GROUPS => ('chartgroup', 'insidergroup',
+ 'timetrackinggroup', 'querysharegroup');
+
+my $cgi = Bugzilla->cgi;
+my $dbh = Bugzilla->dbh;
+my $template = Bugzilla->template;
+my $vars = {};
+
+my $user = Bugzilla->login(LOGIN_REQUIRED);
+
+print $cgi->header();
+
+$user->in_group('creategroups')
+ || ThrowUserError("auth_failure", {group => "creategroups",
+ action => "edit",
+ object => "groups"});
+
+my $action = trim($cgi->param('action') || '');
+my $token = $cgi->param('token');
+
+# CheckGroupID checks that a positive integer is given and is
+# actually a valid group ID. If all tests are successful, the
+# 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;
+}
+
+# CheckGroupRegexp checks that the regular expression is valid
+# (the regular expression being optional, the test is successful
+# if none is given, as expected). The trimmed regular expression
+# is returned.
+
+sub CheckGroupRegexp {
+ 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);
+ 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'}) {
+ 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;
+ }
+}
+
+# If no action is specified, get a list of all groups available.
+
+unless ($action) {
+ my @groups = Bugzilla::Group->get_all;
+ $vars->{'groups'} = \@groups;
+
+ print $cgi->header();
+ $template->process("admin/groups/list.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
+}
+
+#
+# action='changeform' -> present form for altering an existing group
+#
+# (next action will be 'postchanges')
+#
+
+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');
+
+ print $cgi->header();
+ $template->process("admin/groups/edit.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+
+ exit;
+}
+
+#
+# action='add' -> present form for parameters for new group
+#
+# (next action will be 'new')
+#
+
+if ($action eq 'add') {
+ $vars->{'token'} = issue_session_token('add_group');
+ print $cgi->header();
+ $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,
+ });
+
+ # Permit all existing products to use the new group if makeproductgroups.
+ if ($cgi->param('insertnew')) {
+ $dbh->do('INSERT INTO group_control_map
+ (group_id, product_id, membercontrol, othercontrol)
+ SELECT ?, products.id, ?, ? FROM products',
+ undef, ($group->id, CONTROLMAPSHOWN, CONTROLMAPNA));
+ }
+ delete_token($token);
+
+ $vars->{'message'} = 'group_created';
+ $vars->{'group'} = $group;
+ get_current_and_available($group, $vars);
+ $vars->{'token'} = issue_session_token('edit_group');
+
+ print $cgi->header();
+ $template->process("admin/groups/edit.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
+}
+
+#
+# action='del' -> ask if user really wants to delete
+#
+# (next action would be 'delete')
+#
+
+if ($action eq 'del') {
+ # Check that an existing group ID is given
+ my $group = Bugzilla::Group->check({ id => $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);
+
+ $vars->{'group'} = $group;
+ $vars->{'token'} = issue_session_token('delete_group');
+
+ print $cgi->header();
+ $template->process("admin/groups/delete.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+
+ exit;
+}
+
+#
+# action='delete' -> really delete the group
+#
+
+if ($action eq 'delete') {
+ check_token_data($token, 'delete_group');
+ # Check that an existing group ID is given
+ my $group = Bugzilla::Group->check({ id => $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];
+
+ print $cgi->header();
+ $template->process("admin/groups/list.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
+}
+
+#
+# action='postchanges' -> update the groups
+#
+
+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');
+
+ print $cgi->header();
+ $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;
+}
+
+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);
+ }
+ }
+ $dbh->bz_commit_transaction();
+
+ $vars->{'users'} = \@deleted;
+ $vars->{'regexp'} = $regexp;
+ delete_token($token);
+
+ $vars->{'message'} = 'group_membership_removed';
+ $vars->{'group'} = $group->name;
+ $vars->{'groups'} = [Bugzilla::Group->get_all];
+
+ print $cgi->header();
+ $template->process("admin/groups/list.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+
+ exit;
+}
+
+# No valid action found
+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;
+
+ $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')));
+
+ 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 (defined $cgi->param('icon_url')) {
+ $group->set_icon_url($cgi->param('icon_url'));
+ }
+
+ my $changes = $group->update();
+
+ my $sth_insert = $dbh->prepare('INSERT INTO group_group_map
+ (member_id, grantor_id, grant_type)
+ VALUES (?, ?, ?)');
+
+ 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;
+}
+
+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 $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);
+ }
+}
diff --git a/editkeywords.cgi b/editkeywords.cgi
new file mode 100755
index 000000000..fd7eaa3e5
--- /dev/null
+++ b/editkeywords.cgi
@@ -0,0 +1,187 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Terry Weissman.
+# Portions created by Terry Weissman are
+# Copyright (C) 2000 Terry Weissman. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+
+use strict;
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Util;
+use Bugzilla::Error;
+use Bugzilla::Keyword;
+use Bugzilla::Token;
+
+my $cgi = Bugzilla->cgi;
+my $dbh = Bugzilla->dbh;
+my $template = Bugzilla->template;
+my $vars = {};
+
+#
+# Preliminary checks:
+#
+
+my $user = Bugzilla->login(LOGIN_REQUIRED);
+
+print $cgi->header();
+
+$user->in_group('editkeywords')
+ || ThrowUserError("auth_failure", {group => "editkeywords",
+ action => "edit",
+ object => "keywords"});
+
+my $action = trim($cgi->param('action') || '');
+my $key_id = $cgi->param('id');
+my $token = $cgi->param('token');
+
+$vars->{'action'} = $action;
+
+
+if ($action eq "") {
+ $vars->{'keywords'} = Bugzilla::Keyword->get_all_with_bug_count();
+
+ print $cgi->header();
+ $template->process("admin/keywords/list.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+
+ exit;
+}
+
+
+if ($action eq 'add') {
+ $vars->{'token'} = issue_session_token('add_keyword');
+
+ print $cgi->header();
+
+ $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') || '';
+
+ my $keyword = Bugzilla::Keyword->create(
+ { name => $name, description => $desc });
+
+ delete_token($token);
+
+ print $cgi->header();
+
+ $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;
+}
+
+
+#
+# action='edit' -> present the edit keywords from
+#
+# (next action would be 'update')
+#
+
+if ($action eq 'edit') {
+ my $keyword = new Bugzilla::Keyword($key_id)
+ || ThrowCodeError('invalid_keyword_id', { id => $key_id });
+
+ $vars->{'keyword'} = $keyword;
+ $vars->{'token'} = issue_session_token('edit_keyword');
+
+ print $cgi->header();
+ $template->process("admin/keywords/edit.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
+}
+
+
+#
+# action='update' -> update the keyword
+#
+
+if ($action eq 'update') {
+ check_token_data($token, 'edit_keyword');
+ my $keyword = new Bugzilla::Keyword($key_id)
+ || ThrowCodeError('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);
+
+ print $cgi->header();
+
+ $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)
+ || ThrowCodeError('invalid_keyword_id', { id => $key_id });
+
+ $vars->{'keyword'} = $keyword;
+ $vars->{'token'} = issue_session_token('delete_keyword');
+
+ print $cgi->header();
+ $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)
+ || ThrowCodeError('invalid_keyword_id', { id => $key_id });
+
+ $keyword->remove_from_db();
+
+ delete_token($token);
+
+ print $cgi->header();
+
+ $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;
+}
+
+ThrowUserError('unknown_action', {action => $action});
diff --git a/editmilestones.cgi b/editmilestones.cgi
new file mode 100755
index 000000000..ff5076bee
--- /dev/null
+++ b/editmilestones.cgi
@@ -0,0 +1,225 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Initial Developer of the Original Code is Matt Masson.
+# Portions created by Matt Masson are Copyright (C) 2000 Matt Masson.
+# All Rights Reserved.
+#
+# Contributors : Matt Masson <matthew@zeroknowledge.com>
+# Gavin Shelley <bugzilla@chimpychompy.org>
+# Frédéric Buclin <LpSolit@gmail.com>
+
+use strict;
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Util;
+use Bugzilla::Error;
+use Bugzilla::Milestone;
+use Bugzilla::Token;
+
+my $cgi = Bugzilla->cgi;
+my $dbh = Bugzilla->dbh;
+my $template = Bugzilla->template;
+my $vars = {};
+# There is only one section about milestones in the documentation,
+# so all actions point to the same page.
+$vars->{'doc_section'} = 'milestones.html';
+
+#
+# Preliminary checks:
+#
+
+my $user = Bugzilla->login(LOGIN_REQUIRED);
+
+print $cgi->header();
+
+$user->in_group('editcomponents')
+ || scalar(@{$user->get_products_by_permission('editcomponents')})
+ || 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 $token = $cgi->param('token');
+
+#
+# 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/milestones/select-product.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
+}
+
+my $product = $user->check_can_admin_product($product_name);
+
+#
+# action='' -> Show nice list of milestones
+#
+
+unless ($action) {
+
+ $vars->{'showbugcounts'} = $showbugcounts;
+ $vars->{'product'} = $product;
+ $template->process("admin/milestones/list.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
+}
+
+#
+# action='add' -> present form for parameters for new milestone
+#
+# (next action will be 'new')
+#
+
+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;
+}
+
+#
+# action='new' -> add milestone entered in the 'action=add' screen
+#
+
+if ($action eq 'new') {
+ check_token_data($token, 'add_milestone');
+ my $milestone = Bugzilla::Milestone->create({ value => $milestone_name,
+ product => $product,
+ sortkey => $sortkey });
+ 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;
+}
+
+#
+# action='del' -> ask if user really wants to delete
+#
+# (next action would be 'delete')
+#
+
+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;
+}
+
+#
+# action='delete' -> really delete the milestone
+#
+
+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;
+}
+
+#
+# action='edit' -> present the edit milestone form
+#
+# (next action would be 'update')
+#
+
+if ($action eq 'edit') {
+
+ my $milestone = Bugzilla::Milestone->check({ product => $product,
+ name => $milestone_name });
+
+ $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;
+}
+
+#
+# action='update' -> update the milestone
+#
+
+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);
+ 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
+ThrowUserError('unknown_action', {action => $action});
diff --git a/editparams.cgi b/editparams.cgi
new file mode 100755
index 000000000..a8dc0daf9
--- /dev/null
+++ b/editparams.cgi
@@ -0,0 +1,161 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# J. Paul Reed <preed@sigkill.com>
+# Frédéric Buclin <LpSolit@gmail.com>
+
+use strict;
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Config qw(:admin);
+use Bugzilla::Config::Common;
+use Bugzilla::Hook;
+use Bugzilla::Util;
+use Bugzilla::Error;
+use Bugzilla::Token;
+use Bugzilla::User;
+use Bugzilla::User::Setting;
+use Bugzilla::Status;
+
+my $user = Bugzilla->login(LOGIN_REQUIRED);
+my $cgi = Bugzilla->cgi;
+my $template = Bugzilla->template;
+my $vars = {};
+
+print $cgi->header();
+
+$user->in_group('tweakparams')
+ || ThrowUserError("auth_failure", {group => "tweakparams",
+ action => "access",
+ object => "parameters"});
+
+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 $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;"
+ };
+ push(@panels, $item);
+ $current_module = $panel if ($current_panel eq lc($panel));
+}
+
+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 });
+
+$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$//;
+ }
+ }
+
+ 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
+ # can not 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;
+
+ write_params();
+ delete_token($token);
+}
+
+$vars->{'token'} = issue_session_token('edit_parameters');
+
+$template->process("admin/params/editparams.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
diff --git a/editproducts.cgi b/editproducts.cgi
new file mode 100755
index 000000000..6d5c5e593
--- /dev/null
+++ b/editproducts.cgi
@@ -0,0 +1,426 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is mozilla.org code.
+#
+# The Initial Developer of the Original Code is Holger
+# Schurig. Portions created by Holger Schurig are
+# Copyright (C) 1999 Holger Schurig. All
+# Rights Reserved.
+#
+# Contributor(s): Holger Schurig <holgerschurig@nikocity.de>
+# Terry Weissman <terry@mozilla.org>
+# Dawn Endico <endico@mozilla.org>
+# Joe Robins <jmrobins@tgix.com>
+# Gavin Shelley <bugzilla@chimpychompy.org>
+# Frédéric Buclin <LpSolit@gmail.com>
+# Greg Hendricks <ghendricks@novell.com>
+# Lance Larsh <lance.larsh@oracle.com>
+# Elliotte Martin <elliotte.martin@yahoo.com>
+
+use strict;
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Util;
+use Bugzilla::Error;
+use Bugzilla::Group;
+use Bugzilla::Product;
+use Bugzilla::Classification;
+use Bugzilla::Token;
+
+#
+# Preliminary checks:
+#
+
+my $user = Bugzilla->login(LOGIN_REQUIRED);
+my $whoid = $user->id;
+
+my $dbh = Bugzilla->dbh;
+my $cgi = Bugzilla->cgi;
+my $template = Bugzilla->template;
+my $vars = {};
+# Remove this as soon as the documentation about products has been
+# improved and each action has its own section.
+$vars->{'doc_section'} = 'products.html';
+
+print $cgi->header();
+
+$user->in_group('editcomponents')
+ || scalar(@{$user->get_products_by_permission('editcomponents')})
+ || 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');
+
+#
+# product = '' -> Show nice list of classifications (if
+# classifications enabled)
+#
+
+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;
+}
+
+
+#
+# action = '' -> Show a nice list of products, unless a product
+# is already specified (then edit it)
+#
+
+if (!$action && !$product_name) {
+ 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'}) {
+ @$products = grep {$_->classification_id == $classification->id} @$products;
+ }
+ }
+ $vars->{'products'} = $products;
+ $vars->{'showbugcounts'} = $cgi->param('showbugcounts') ? 1 : 0;
+
+ $template->process("admin/products/list.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
+}
+
+
+
+
+#
+# action='add' -> present form for parameters for new product
+#
+# (next action will be 'new')
+#
+
+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');
+
+ $template->process("admin/products/create.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+
+ exit;
+}
+
+
+#
+# action='new' -> add product entered in the 'action=add' screen
+#
+
+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');
+
+ my %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'),
+ );
+ my $product = Bugzilla::Product->create(\%create_params);
+
+ 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;
+}
+
+#
+# action='del' -> ask if user really wants to delete
+#
+# (next action would be 'delete')
+#
+
+if ($action eq 'del') {
+ 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;
+}
+
+#
+# action='delete' -> really delete the product
+#
+
+if ($action eq 'delete') {
+ 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);
+
+ $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;
+
+ $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());
+ }
+ exit;
+}
+
+#
+# action='edit' -> present the 'edit product' form
+# If a product is given with no action associated with it, then edit it.
+#
+# (next action would be 'update')
+#
+
+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;
+}
+
+#
+# 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;
+}
+
+#
+# action='editgroupcontrols' -> display product group controls
+#
+
+if ($action eq 'editgroupcontrols') {
+ my $product = $user->check_can_admin_product($product_name);
+
+ $vars->{'product'} = $product;
+ $vars->{'token'} = issue_session_token('edit_group_controls');
+
+ $template->process("admin/products/groupcontrol/edit.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
+}
+
+#
+# action='updategroupcontrols' -> update product group controls
+#
+
+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;
+ }
+ }
+ }
+ 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,
+ (SELECT COUNT(bugs.bug_id)
+ FROM bugs
+ WHERE bugs.product_id = ?
+ AND bugs.bug_id NOT IN
+ (SELECT bug_group_map.bug_id FROM bug_group_map
+ WHERE bug_group_map.group_id = groups.id))
+ 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;
+ }
+ }
+
+ 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);
+
+ $vars->{'product'} = $product;
+ $vars->{'changes'} = $changes;
+
+ $template->process("admin/products/groupcontrol/updated.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
+}
+
+# No valid action found
+ThrowUserError('unknown_action', {action => $action});
diff --git a/editsettings.cgi b/editsettings.cgi
new file mode 100755
index 000000000..d375a3d5d
--- /dev/null
+++ b/editsettings.cgi
@@ -0,0 +1,73 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s): Shane H. W. Travis <travis@sedsystems.ca>
+# Frédéric Buclin <LpSolit@gmail.com>
+
+use strict;
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Util;
+use Bugzilla::Error;
+use Bugzilla::User::Setting;
+use Bugzilla::Token;
+
+my $template = Bugzilla->template;
+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"});
+
+my $action = trim($cgi->param('action') || '');
+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;
+
+ 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);
+
+ 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;
+ 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');
+
+$template->process("admin/settings/edit.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
diff --git a/editusers.cgi b/editusers.cgi
new file mode 100755
index 000000000..f53fde985
--- /dev/null
+++ b/editusers.cgi
@@ -0,0 +1,793 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s): Marc Schumann <wurblzap@gmail.com>
+# Lance Larsh <lance.larsh@oracle.com>
+# Frédéric Buclin <LpSolit@gmail.com>
+# David Lawrence <dkl@redhat.com>
+# Vlad Dascalu <jocuri@softhome.net>
+# Gavin Shelley <bugzilla@chimpychompy.org>
+
+use strict;
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Util;
+use Bugzilla::Error;
+use Bugzilla::User;
+use Bugzilla::Bug;
+use Bugzilla::BugMail;
+use Bugzilla::Flag;
+use Bugzilla::Field;
+use Bugzilla::Group;
+use Bugzilla::Token;
+
+my $user = Bugzilla->login(LOGIN_REQUIRED);
+
+my $cgi = Bugzilla->cgi;
+my $template = Bugzilla->template;
+my $dbh = Bugzilla->dbh;
+my $userid = $user->id;
+my $editusers = $user->in_group('editusers');
+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"});
+
+print $cgi->header();
+
+# Common CGI params
+my $action = $cgi->param('action') || 'search';
+my $otherUserID = $cgi->param('userid');
+my $otherUserLogin = $cgi->param('user');
+my $token = $cgi->param('token');
+
+# Prefill template vars with data used in all or nearly all templates
+$vars->{'editusers'} = $editusers;
+mirrorListSelectionValues();
+
+###########################################################################
+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 = $cgi->param('matchstr');
+ my $matchtype = $cgi->param('matchtype');
+ my $grouprestrict = $cgi->param('grouprestrict') || '0';
+ my $query = 'SELECT DISTINCT userid, login_name, realname, disabledtext ' .
+ '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
+ WHERE ugm.user_id = profiles.userid
+ AND ugm.isbless = 0
+ };
+ $nextCondition = 'AND';
+ }
+ else {
+ $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 ($matchstr =~ /^(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_istrcmp($expr, '?', 'LIKE');
+ $matchstr = "%$matchstr%";
+ }
+ $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) ";
+ }
+ $query .= ' ORDER BY profiles.login_name';
+
+ $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());
+ }
+
+###########################################################################
+} elsif ($action eq 'add') {
+ $editusers || ThrowUserError("auth_failure", {group => "editusers",
+ action => "add",
+ object => "users"});
+
+ $vars->{'token'} = issue_session_token('add_user');
+
+ $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')});
+
+ userDataToVars($new_user->id);
+
+ delete_token($token);
+
+ # 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 '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 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'));
+ $changes = $otherUser->update();
+ }
+
+ # 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
+ WHERE user_id = ?
+ AND group_id = ?
+ AND isbless = ?
+ AND grant_type = ?
+ });
+
+ my @groupsAddedTo;
+ my @groupsRemovedFrom;
+ my @groupsGrantedRightsToBless;
+ my @groupsDeniedRightsToBless;
+
+ # Regard only groups the user is allowed to bless and skip all others
+ # silently.
+ # XXX: checking for existence of each user_group_map entry
+ # would allow to display a friendlier error message on page reloads.
+ userDataToVars($otherUserID);
+ my $permissions = $vars->{'permissions'};
+ foreach my $blessable (@{$user->bless_groups()}) {
+ my $id = $blessable->id;
+ my $name = $blessable->name;
+
+ # Change memberships.
+ my $groupid = $cgi->param("group_$id") || 0;
+ if ($groupid != $permissions->{$id}->{'directmember'}) {
+ if (!$groupid) {
+ $sth_remove_mapping->execute(
+ $otherUserID, $id, 0, GRANT_DIRECT);
+ push(@groupsRemovedFrom, $name);
+ } else {
+ $sth_add_mapping->execute(
+ $otherUserID, $id, 0, GRANT_DIRECT);
+ push(@groupsAddedTo, $name);
+ }
+ }
+
+ # Only members of the editusers group may change bless grants.
+ # Skip silently if this is not the case.
+ if ($editusers) {
+ my $groupid = $cgi->param("bless_$id") || 0;
+ if ($groupid != $permissions->{$id}->{'directbless'}) {
+ if (!$groupid) {
+ $sth_remove_mapping->execute(
+ $otherUserID, $id, 1, GRANT_DIRECT);
+ push(@groupsDeniedRightsToBless, $name);
+ } else {
+ $sth_add_mapping->execute(
+ $otherUserID, $id, 1, GRANT_DIRECT);
+ push(@groupsGrantedRightsToBless, $name);
+ }
+ }
+ }
+ }
+ if (@groupsAddedTo || @groupsRemovedFrom) {
+ $dbh->do(qq{INSERT INTO profiles_activity (
+ userid, who,
+ profiles_when, fieldid,
+ oldvalue, newvalue
+ ) VALUES (
+ ?, ?, now(), ?, ?, ?
+ )
+ },
+ undef,
+ ($otherUserID, $userid,
+ get_field_id('bug_group'),
+ join(', ', @groupsRemovedFrom), join(', ', @groupsAddedTo)));
+ }
+ # XXX: should create profiles_activity entries for blesser changes.
+
+ $dbh->bz_commit_transaction();
+
+ # XXX: userDataToVars may be off when editing ourselves.
+ userDataToVars($otherUserID);
+ delete_token($token);
+
+ $vars->{'message'} = 'account_updated';
+ $vars->{'changed_fields'} = [keys %$changes];
+ $vars->{'groups_added_to'} = \@groupsAddedTo;
+ $vars->{'groups_removed_from'} = \@groupsRemovedFrom;
+ $vars->{'groups_granted_rights_to_bless'} = \@groupsGrantedRightsToBless;
+ $vars->{'groups_denied_rights_to_bless'} = \@groupsDeniedRightsToBless;
+ # 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(*)
+ 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)
+ FROM whine_schedules
+ WHERE mailto = ?
+ AND mailto_type = ?
+ },
+ undef, ($otherUserID, MAILTO_USER));
+ $vars->{'token'} = issue_session_token('delete_user');
+
+ $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 = ?');
+
+ my $sth_updateFlag = $dbh->prepare('INSERT INTO bugs_activity
+ (bug_id, attach_id, who, bug_when, fieldid, removed, added)
+ VALUES (?, ?, ?, ?, ?, ?, ?)');
+
+ # 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;
+ my $fieldid = get_field_id('flagtypes.name');
+ 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);
+ $sth_updateFlag->execute($bug_id, $attach_id || undef, $userid,
+ $timestamp, $fieldid, $removed, $added);
+ }
+ $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
+ 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);
+
+ $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);
+
+ $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
+ FROM profiles_activity
+ 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);
+
+ $vars->{'otheruser'} = $otherUser;
+
+ $template->process("account/profile-activity.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+
+###########################################################################
+} else {
+ ThrowUserError('unknown_action', {action => $action});
+}
+
+exit;
+
+###########################################################################
+# Helpers
+###########################################################################
+
+# 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) || ThrowCodeError('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') {
+ $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 $grouplist = $otheruser->groups_as_string;
+
+ $vars->{'otheruser'} = $otheruser;
+ $vars->{'groups'} = $user->bless_groups();
+
+ $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)
+ AND COUNT(directmember.group_id) = 0
+ AND COUNT(regexpmember.group_id) = 0
+ ) THEN 1 ELSE 0 END)
+ AS derivedmember,
+ COUNT(directbless.group_id) AS directbless
+ FROM groups
+ LEFT JOIN user_group_map AS directmember
+ ON directmember.group_id = id
+ AND directmember.user_id = ?
+ AND directmember.isbless = 0
+ AND directmember.grant_type = ?
+ LEFT JOIN user_group_map AS regexpmember
+ ON regexpmember.group_id = id
+ AND regexpmember.user_id = ?
+ AND regexpmember.isbless = 0
+ AND regexpmember.grant_type = ?
+ LEFT JOIN user_group_map AS directbless
+ ON directbless.group_id = id
+ AND directbless.user_id = ?
+ 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
+ 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;
+ }
+}
+
+sub edit_processing {
+ 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"});
+
+ userDataToVars($otherUser->id);
+ $vars->{'token'} = issue_session_token('edit_user');
+
+ $template->process('admin/users/edit.html.tmpl', $vars)
+ || ThrowTemplateError($template->error());
+}
diff --git a/editvalues.cgi b/editvalues.cgi
new file mode 100755
index 000000000..3f08a1671
--- /dev/null
+++ b/editvalues.cgi
@@ -0,0 +1,197 @@
+#!/usr/bin/perl -wT
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+# Frédéric Buclin <LpSolit@gmail.com>
+
+# This is a script to edit the values of fields that have drop-down
+# or select boxes. It is largely a copy of editmilestones.cgi, but
+# with some cleanup.
+
+use strict;
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Util;
+use Bugzilla::Error;
+use Bugzilla::Constants;
+use Bugzilla::Token;
+use Bugzilla::Field;
+use Bugzilla::Field::Choice;
+
+###############
+# Subroutines #
+###############
+
+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;
+}
+
+######################################################################
+# Main Body Execution
+######################################################################
+
+# require the user to have logged in
+Bugzilla->login(LOGIN_REQUIRED);
+
+my $dbh = Bugzilla->dbh;
+my $cgi = Bugzilla->cgi;
+my $template = Bugzilla->template;
+my $vars = {};
+
+# Replace this entry by separate entries in templates when
+# the documentation about legal values becomes bigger.
+$vars->{'doc_section'} = 'edit-values.html';
+
+print $cgi->header();
+
+Bugzilla->user->in_group('admin') ||
+ ThrowUserError('auth_failure', {group => "admin",
+ action => "edit",
+ object => "field_values"});
+
+#
+# often-used variables
+#
+my $action = trim($cgi->param('action') || '');
+my $token = $cgi->param('token');
+
+#
+# field = '' -> Show nice list of fields
+#
+if (!$cgi->param('field')) {
+ my @field_list = grep { !$_->is_abnormal }
+ Bugzilla->get_fields({ is_select => 1 });
+
+ $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 });
+}
+$vars->{'field'} = $field;
+
+#
+# action='' -> Show nice list of values.
+#
+display_field_values($vars) unless $action;
+
+#
+# action='add' -> show form for adding new field value.
+# (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;
+}
+
+#
+# action='new' -> add field value entered in the 'action=add' screen
+#
+if ($action eq 'new') {
+ 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'),
+ });
+
+ delete_token($token);
+
+ $vars->{'message'} = 'field_value_created';
+ $vars->{'value'} = $created_value;
+ display_field_values($vars);
+}
+
+# After this, we always have a value
+my $value = Bugzilla::Field::Choice->type($field)->check($cgi->param('value'));
+$vars->{'value'} = $value;
+
+#
+# action='del' -> ask if user really wants to delete
+# (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());
+
+ exit;
+}
+
+
+#
+# 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);
+}
+
+
+#
+# action='edit' -> present the edit-value form
+# (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());
+
+ exit;
+}
+
+
+#
+# action='update' -> update the field value
+#
+if ($action eq 'update') {
+ check_token_data($token, 'edit_field_value');
+ $vars->{'value_old'} = $value->name;
+ if ($cgi->should_set('is_active')) {
+ $value->set_is_active($cgi->param('is_active'));
+ }
+ $value->set_name($cgi->param('value_new'));
+ $value->set_sortkey($cgi->param('sortkey'));
+ $value->set_visibility_value($cgi->param('visibility_value_id'));
+ $vars->{'changes'} = $value->update();
+ delete_token($token);
+ $vars->{'message'} = 'field_value_updated';
+ display_field_values($vars);
+}
+
+# No valid action found
+ThrowUserError('unknown_action', {action => $action});
diff --git a/editversions.cgi b/editversions.cgi
new file mode 100755
index 000000000..0888ef0c6
--- /dev/null
+++ b/editversions.cgi
@@ -0,0 +1,223 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is mozilla.org code.
+#
+# The Initial Developer of the Original Code is Holger
+# Schurig. Portions created by Holger Schurig are
+# Copyright (C) 1999 Holger Schurig. All
+# Rights Reserved.
+#
+# Contributor(s): Holger Schurig <holgerschurig@nikocity.de>
+# Terry Weissman <terry@mozilla.org>
+# Gavin Shelley <bugzilla@chimpychompy.org>
+# Frédéric Buclin <LpSolit@gmail.com>
+
+use strict;
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Util;
+use Bugzilla::Error;
+use Bugzilla::Version;
+use Bugzilla::Token;
+
+my $cgi = Bugzilla->cgi;
+my $dbh = Bugzilla->dbh;
+my $template = Bugzilla->template;
+my $vars = {};
+# There is only one section about versions in the documentation,
+# so all actions point to the same page.
+$vars->{'doc_section'} = 'versions.html';
+
+#
+# Preliminary checks:
+#
+
+my $user = Bugzilla->login(LOGIN_REQUIRED);
+
+print $cgi->header();
+
+$user->in_group('editcomponents')
+ || scalar(@{$user->get_products_by_permission('editcomponents')})
+ || ThrowUserError("auth_failure", {group => "editcomponents",
+ action => "edit",
+ object => "versions"});
+
+#
+# often used variables
+#
+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');
+
+#
+# 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 $product = $user->check_can_admin_product($product_name);
+
+#
+# action='' -> Show nice list of versions
+#
+
+unless ($action) {
+ $vars->{'showbugcounts'} = $showbugcounts;
+ $vars->{'product'} = $product;
+ $template->process("admin/versions/list.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+
+ exit;
+}
+
+#
+# action='add' -> present form for parameters for new version
+#
+# (next action will be 'new')
+#
+
+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());
+
+ exit;
+}
+
+#
+# action='new' -> add version entered in the 'action=add' screen
+#
+
+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;
+}
+
+#
+# action='del' -> ask if user really wants to delete
+#
+# (next action would be 'delete')
+#
+
+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;
+}
+
+#
+# action='delete' -> really delete the version
+#
+
+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);
+
+ $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());
+
+ exit;
+}
+
+#
+# action='edit' -> present the edit version form
+#
+# (next action would be 'update')
+#
+
+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');
+
+ $template->process("admin/versions/edit.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+
+ exit;
+}
+
+#
+# action='update' -> update the version
+#
+
+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_name($version_name);
+ 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
+ThrowUserError('unknown_action', {action => $action});
diff --git a/editwhines.cgi b/editwhines.cgi
new file mode 100755
index 000000000..04e2b548f
--- /dev/null
+++ b/editwhines.cgi
@@ -0,0 +1,415 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Erik Stambaugh <erik@dasbistro.com>
+#
+
+################################################################################
+# Script Initialization
+################################################################################
+
+use strict;
+
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Util;
+use Bugzilla::Error;
+use Bugzilla::User;
+use Bugzilla::Group;
+use Bugzilla::Token;
+use Bugzilla::Whine::Schedule;
+use Bugzilla::Whine::Query;
+use Bugzilla::Whine;
+
+# require the user to have logged in
+my $user = Bugzilla->login(LOGIN_REQUIRED);
+
+###############################################################################
+# Main Body Execution
+###############################################################################
+
+my $cgi = Bugzilla->cgi;
+my $template = Bugzilla->template;
+my $vars = {};
+my $dbh = Bugzilla->dbh;
+
+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.
+#
+# Eventually, it winds up with:
+# 'queries' - array ref containing hashes of:
+# 'name' - the name of the saved query
+# 'title' - The title line for the search results table
+# 'sort' - Numeric sort ID
+# 'id' - row ID for the query entry
+# 'onemailperbug' - whether a single message must be sent for each
+# result.
+# 'schedule' - array ref containing hashes of:
+# 'day' - Day or range of days this schedule will be run
+# 'time' - time or interval to run
+# 'mailto_type' - MAILTO_USER or MAILTO_GROUP
+# 'mailto' - person/group who will receive the results
+# 'id' - row ID for the schedule
+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"});
+
+# May this user send mail to other users?
+my $can_mail_others = Bugzilla->user->in_group('bz_canusewhineatothers');
+
+# If the form was submitted, we need to look for what needs to be added or
+# 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);
+ }
+ }
+
+ # 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) {
+ # 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);
+}
+
+$vars->{'mail_others'} = $can_mail_others;
+
+# Return the appropriate HTTP response headers.
+print $cgi->header();
+
+# Get events again, to cover any updates that were made
+$events = get_events($userid);
+
+# Here is the data layout as sent to the template:
+#
+# events
+# event_id #
+# schedule
+# day
+# time
+# mailto
+# queries
+# name
+# title
+# sort
+#
+# 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,
+ };
+ }
+
+ # 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;
+
+# get the available queries
+$sth = $dbh->prepare("SELECT name FROM namedqueries WHERE userid=?");
+$sth->execute($userid);
+
+$vars->{'available_queries'} = [];
+while (my ($query) = $sth->fetchrow_array) {
+ push @{$vars->{'available_queries'}}, $query;
+}
+$vars->{'token'} = issue_session_token('edit_whine');
+$vars->{'local_timezone'} = Bugzilla->local_timezone->short_name_for_datetime(DateTime->now());
+
+$template->process("whine/schedule.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+
+# 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;
+
+ return \%events;
+}
+
diff --git a/editworkflow.cgi b/editworkflow.cgi
new file mode 100755
index 000000000..321f077fe
--- /dev/null
+++ b/editworkflow.cgi
@@ -0,0 +1,151 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Frédéric Buclin.
+# Portions created by Frédéric Buclin are Copyright (C) 2007
+# Frédéric Buclin. All Rights Reserved.
+#
+# Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
+
+use strict;
+
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Token;
+use Bugzilla::Status;
+
+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'});
+
+my $action = $cgi->param('action') || 'edit';
+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;
+}
+
+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;
+}
+
+if ($action eq '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 ($new->is_open && $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);
+ }
+ }
+ }
+ delete_token($token);
+ load_template('edit', 'workflow_updated');
+}
+elsif ($action eq 'edit_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);
+ }
+ }
+ }
+ delete_token($token);
+ load_template('comment', 'workflow_updated');
+}
+else {
+ ThrowUserError('unknown_action', {action => $action});
+}
diff --git a/email_in.pl b/email_in.pl
new file mode 100755
index 000000000..393061cd5
--- /dev/null
+++ b/email_in.pl
@@ -0,0 +1,574 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Inbound Email System.
+#
+# The Initial Developer of the Original Code is Akamai Technologies, Inc.
+# Portions created by Akamai are Copyright (C) 2006 Akamai Technologies,
+# Inc. All Rights Reserved.
+#
+# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+
+use strict;
+use warnings;
+
+# MTAs may call this script from any directory, but it should always
+# 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);
+}
+
+use lib qw(. lib);
+
+use Data::Dumper;
+use Email::Address;
+use Email::Reply qw(reply);
+use Email::MIME;
+use Email::MIME::Attachment::Stripper;
+use Getopt::Long qw(:config bundling);
+use Pod::Usage;
+use Encode;
+use Scalar::Util qw(blessed);
+
+use Bugzilla;
+use Bugzilla::Attachment;
+use Bugzilla::Bug;
+use Bugzilla::BugMail;
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Mailer;
+use Bugzilla::Token;
+use Bugzilla::User;
+use Bugzilla::Util;
+use Bugzilla::Hook;
+
+#############
+# Constants #
+#############
+
+# This is the USENET standard line for beginning a signature block
+# in a message. RFC-compliant mailers use this.
+use constant SIGNATURE_DELIMITER => '-- ';
+
+# $input_email is a global so that it can be used in die_handler.
+our ($input_email, %switch);
+
+####################
+# Main Subroutines #
+####################
+
+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 ($body, $attachments) = get_body_and_attachments($input_email);
+ if (@$attachments) {
+ $fields{'attachments'} = $attachments;
+ }
+
+ 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";
+ }
+ }
+ }
+
+ %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;
+ }
+
+ 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;
+
+ my %override = %{ $switch{'override'} || {} };
+ foreach my $key (keys %override) {
+ $fields{$key} = $override{$key};
+ }
+
+ debug_print("Parsed Fields:\n" . Dumper(\%fields), 2);
+
+ return \%fields;
+}
+
+sub post_bug {
+ my ($fields) = @_;
+ debug_print('Posting a new bug...');
+
+ my $user = Bugzilla->user;
+
+ my ($retval, $non_conclusive_fields) =
+ Bugzilla::User::match_field({
+ 'assigned_to' => { 'type' => 'single' },
+ 'qa_contact' => { 'type' => 'single' },
+ 'cc' => { 'type' => 'multi' }
+ }, $fields, MATCH_SKIP_CONFIRM);
+
+ if ($retval != USER_MATCH_SUCCESS) {
+ ThrowUserError('user_match_too_many', {fields => $non_conclusive_fields});
+ }
+
+ 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;
+ }
+
+ my $cgi = Bugzilla->cgi;
+ foreach my $field (keys %fields) {
+ $cgi->param(-name => $field, -value => $fields{$field});
+ }
+ $cgi->param('longdesclength', scalar @{ $bug->comments });
+ $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'})) {
+ $added_comment = $bug->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) {
+ my $data = delete $attachment->{payload};
+ debug_print("Inserting Attachment: " . Dumper($attachment), 2);
+ $attachment->{content_type} ||= 'application/octet-stream';
+ my $obj = Bugzilla::Attachment->create({
+ bug => $bug,
+ description => $attachment->{filename},
+ filename => $attachment->{filename},
+ mimetype => $attachment->{content_type},
+ data => $data,
+ });
+ # 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();
+}
+
+######################
+# Helper Subroutines #
+######################
+
+sub debug_print {
+ 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]...");
+
+ my $body;
+ my $attachments = [];
+ if ($ct =~ /^multipart\/(alternative|signed)/i) {
+ $body = get_text_alternative($email);
+ }
+ else {
+ my $stripper = new Email::MIME::Attachment::Stripper(
+ $email, force_filename => 1);
+ my $message = $stripper->message;
+ $body = get_text_alternative($message);
+ $attachments = [$stripper->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("Part Content-Type: $ct", 2);
+ debug_print("Part Character Encoding: $charset", 2);
+ if (!$ct || $ct =~ /^text\/plain/i) {
+ $body = $part->body;
+ if (Bugzilla->params->{'utf8'} && !utf8::is_utf8($body)) {
+ $body = Encode::decode($charset, $body);
+ }
+ 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_text_plain');
+ }
+
+ return $body;
+}
+
+sub remove_leading_blank_lines {
+ 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;
+}
+
+
+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;
+
+ # 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;
+}
+
+###############
+# Main Script #
+###############
+
+$SIG{__DIE__} = \&die_handler;
+
+GetOptions(\%switch, 'help|h', 'verbose|v+', 'default=s%', 'override=s%');
+$switch{'verbose'} ||= 0;
+
+# Print the help message if that switch was selected.
+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_fields = parse_mail($mail_text);
+
+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;
+}
+
+my $user = Bugzilla::User->check($username);
+Bugzilla->set_user($user);
+
+my ($bug, $comment);
+if ($mail_fields->{'bug_id'}) {
+ ($bug, $comment) = process_bug($mail_fields);
+}
+else {
+ ($bug, $comment) = post_bug($mail_fields);
+}
+
+handle_attachments($bug, $attachments, $comment);
+
+# This is here for post_bug and handle_attachments, so that when posting a bug
+# with an attachment, any comment goes out as an attachment comment.
+#
+# Eventually this should be sending the mail for process_bug, too, but we have
+# 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 });
+debug_print("Sent bugmail");
+
+
+__END__
+
+=head1 NAME
+
+email_in.pl - The Bugzilla Inbound Email Interface
+
+=head1 SYNOPSIS
+
+./email_in.pl [-vvv] [--default name=value] [--override name=value] < email.txt
+
+Reads an email on STDIN (the standard input).
+
+Options:
+
+ --verbose (-v) - Make the script print more to STDERR.
+ Specify multiple times to print even more.
+
+ --default name=value - Specify defaults for field values, like
+ product=TestProduct. Can be specified multiple
+ times to specify defaults for multiple fields.
+
+ --override name=value - Override field values specified in the email,
+ like product=TestProduct. Can be specified
+ multiple times to override multiple fields.
+
+=head1 DESCRIPTION
+
+This script processes inbound email and creates a bug, or appends data
+to an existing bug.
+
+=head2 Creating a New Bug
+
+The script expects to read an email with the following format:
+
+ From: account@domain.com
+ Subject: Bug Summary
+
+ @product ProductName
+ @component ComponentName
+ @version 1.0
+
+ This is a bug description. It will be entered into the bug exactly as
+ written here.
+
+ It can be multiple paragraphs.
+
+ --
+ This is a signature line, and will be removed automatically, It will not
+ be included in the bug description.
+
+For the list of valid field names for the C<@> fields, including
+a list of which ones are required, see L<Bugzilla::WebService::Bug/create>.
+(Note, however, that you cannot specify C<@description> as a field--
+you just add a comment by adding text after the C<@> fields.)
+
+The values for the fields can be split across multiple lines, but
+note that a newline will be parsed as a single space, for the value.
+So, for example:
+
+ @summary This is a very long
+ description
+
+Will be parsed as "This is a very long description".
+
+If you specify C<@summary>, it will override the summary you specify
+in the Subject header.
+
+C<account@domain.com> (the value of the C<From> header) must be a valid
+Bugzilla account.
+
+Note that signatures must start with '-- ', the standard signature
+border.
+
+=head2 Modifying an Existing Bug
+
+Bugzilla determines what bug you want to modify in one of two ways:
+
+=over
+
+=item *
+
+Your subject starts with [Bug 123456] -- then it modifies bug 123456.
+
+=item *
+
+You include C<@id 123456> in the first lines of the email.
+
+=back
+
+If you do both, C<@id> takes precedence.
+
+You send your email in the same format as for creating a bug, except
+that you only specify the fields you want to change. If the very
+first non-blank line of the email doesn't begin with C<@>, then it
+will be assumed that you are only adding a comment to the bug.
+
+Note that when updating a bug, the C<Subject> header is ignored,
+except for getting the bug ID. If you want to change the bug's summary,
+you have to specify C<@summary> as one of the fields to change.
+
+Please remember not to include any extra text in your emails, as that
+text will also be added as a comment. This includes any text that your
+email client automatically quoted and included, if this is a reply to
+another email.
+
+=head3 Adding/Removing CCs
+
+To add CCs, you can specify them in a comma-separated list in C<@cc>.
+
+To remove CCs, specify them as a comma-separated list in C<@removecc>.
+
+=head2 Errors
+
+If your request cannot be completed for any reason, Bugzilla will
+send an email back to you. If your request succeeds, Bugzilla will
+not send you anything.
+
+If any part of your request fails, all of it will fail. No partial
+changes will happen.
+
+=head1 CAUTION
+
+The script does not do any validation that the user is who they say
+they are. That is, it accepts I<any> 'From' address, as long as it's
+a valid Bugzilla account. So make sure that your MTA validates that
+the message is actually coming from who it says it's coming from,
+and only allow access to the inbound email system from people you trust.
+
+=head1 LIMITATIONS
+
+The email interface only accepts emails that are correctly formatted
+per RFC2822. If you send it an incorrectly formatted message, it
+may behave in an unpredictable fashion.
+
+You cannot send an HTML mail along with attachments. If you do, Bugzilla
+will reject your email, saying that it doesn't contain any text. This
+is a bug in L<Email::MIME::Attachment::Stripper> that we can't work
+around.
+
+You cannot modify Flags through the email interface.
diff --git a/enter_bug.cgi b/enter_bug.cgi
new file mode 100755
index 000000000..61fc06162
--- /dev/null
+++ b/enter_bug.cgi
@@ -0,0 +1,591 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are Copyright (C) 1998
+# Netscape Communications Corporation. All Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Dave Miller <justdave@syndicomm.com>
+# Joe Robins <jmrobins@tgix.com>
+# Gervase Markham <gerv@gerv.net>
+# Shane H. W. Travis <travis@sedsystems.ca>
+# Nitish Bezzala <nbezzala@yahoo.com>
+
+##############################################################################
+#
+# 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
+# passed in as parameters to the cgi.
+#
+##############################################################################
+
+use strict;
+
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Util;
+use Bugzilla::Error;
+use Bugzilla::Bug;
+use Bugzilla::User;
+use Bugzilla::Hook;
+use Bugzilla::Product;
+use Bugzilla::Classification;
+use Bugzilla::Keyword;
+use Bugzilla::Token;
+use Bugzilla::Field;
+use Bugzilla::Status;
+
+my $user = Bugzilla->login(LOGIN_REQUIRED);
+
+my $cloned_bug;
+my $cloned_bug_id;
+
+my $cgi = Bugzilla->cgi;
+my $dbh = Bugzilla->dbh;
+my $template = Bugzilla->template;
+my $vars = {};
+
+# All pages point to the same part of the documentation.
+$vars->{'doc_section'} = 'bugreports.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';
+
+ # Unless a real classification name is given, we sort products
+ # by classification.
+ my @classifications;
+
+ unless ($classification && $classification ne '__all') {
+ if (Bugzilla->params->{'useclassification'}) {
+ my $class;
+ # Get all classifications with at least one enterable product.
+ foreach my $product (@enterable_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);
+ }
+ @classifications = sort {$a->{'object'}->sortkey <=> $b->{'object'}->sortkey
+ || lc($a->{'object'}->name) cmp lc($b->{'object'}->name)}
+ (values %$class);
+ }
+ else {
+ @classifications = ({object => undef, products => \@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";
+ $vars->{'format'} = $cgi->param('format');
+ $vars->{'cloned_bug_id'} = $cgi->param('cloned_bug_id');
+
+ 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;
+ }
+
+ # 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 = ();
+ }
+ }
+
+ if (scalar(@enterable_products) == 0) {
+ ThrowUserError('no_products');
+ }
+ elsif (scalar(@enterable_products) > 1) {
+ $vars->{'classifications'} = \@classifications;
+ $vars->{'target'} = "enter_bug.cgi";
+ $vars->{'format'} = $cgi->param('format');
+ $vars->{'cloned_bug_id'} = $cgi->param('cloned_bug_id');
+
+ 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
+# to enter a bug against this product.
+$product = $user->can_enter_product($product || $product_name, THROW_ERROR);
+
+##############################################################################
+# Useful Subroutines
+##############################################################################
+sub formvalue {
+ my ($name, $default) = (@_);
+ return Bugzilla->cgi->param($name) || $default || "";
+}
+
+# Takes the name of a field and a list of possible values for that
+# field. Returns the first value in the list that is actually a
+# valid value for that field.
+# The field should be named after its DB table.
+# Returns undef if none of the platforms match.
+sub pick_valid_field_value (@) {
+ my ($field, @values) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ foreach my $value (@values) {
+ return $value if $dbh->selectrow_array(
+ "SELECT 1 FROM $field WHERE value = ?", undef, $value);
+ }
+ return undef;
+}
+
+sub pickplatform {
+ return formvalue("rep_platform") if formvalue("rep_platform");
+
+ my @platform;
+
+ if (Bugzilla->params->{'defaultplatform'}) {
+ @platform = Bugzilla->params->{'defaultplatform'};
+ } else {
+ # If @platform is a list, this function will return the first
+ # item in the list that is a valid platform choice. If
+ # no choice is valid, we return "Other".
+ for ($ENV{'HTTP_USER_AGENT'}) {
+ #PowerPC
+ /\(.*PowerPC.*\)/i && do {push @platform, ("PowerPC", "Macintosh");};
+ #AMD64, Intel x86_64
+ /\(.*amd64.*\)/ && do {push @platform, ("AMD64", "x86_64", "PC");};
+ /\(.*x86_64.*\)/ && do {push @platform, ("AMD64", "x86_64", "PC");};
+ #Intel Itanium
+ /\(.*IA64.*\)/ && do {push @platform, "IA64";};
+ #Intel x86
+ /\(.*Intel.*\)/ && do {push @platform, ("IA32", "x86", "PC");};
+ /\(.*[ix0-9]86.*\)/ && do {push @platform, ("IA32", "x86", "PC");};
+ #Versions of Windows that only run on Intel x86
+ /\(.*Win(?:dows |)[39M].*\)/ && do {push @platform, ("IA32", "x86", "PC");};
+ /\(.*Win(?:dows |)16.*\)/ && do {push @platform, ("IA32", "x86", "PC");};
+ #Sparc
+ /\(.*sparc.*\)/ && do {push @platform, ("Sparc", "Sun");};
+ /\(.*sun4.*\)/ && do {push @platform, ("Sparc", "Sun");};
+ #Alpha
+ /\(.*AXP.*\)/i && do {push @platform, ("Alpha", "DEC");};
+ /\(.*[ _]Alpha.\D/i && do {push @platform, ("Alpha", "DEC");};
+ /\(.*[ _]Alpha\)/i && do {push @platform, ("Alpha", "DEC");};
+ #MIPS
+ /\(.*IRIX.*\)/i && do {push @platform, ("MIPS", "SGI");};
+ /\(.*MIPS.*\)/i && do {push @platform, ("MIPS", "SGI");};
+ #68k
+ /\(.*68K.*\)/ && do {push @platform, ("68k", "Macintosh");};
+ /\(.*680[x0]0.*\)/ && do {push @platform, ("68k", "Macintosh");};
+ #HP
+ /\(.*9000.*\)/ && do {push @platform, ("PA-RISC", "HP");};
+ #ARM
+ /\(.*ARM.*\)/ && do {push @platform, ("ARM", "PocketPC");};
+ #PocketPC intentionally before PowerPC
+ /\(.*Windows CE.*PPC.*\)/ && do {push @platform, ("ARM", "PocketPC");};
+ #PowerPC
+ /\(.*PPC.*\)/ && do {push @platform, ("PowerPC", "Macintosh");};
+ /\(.*AIX.*\)/ && do {push @platform, ("PowerPC", "Macintosh");};
+ #Stereotypical and broken
+ /\(.*Windows CE.*\)/ && do {push @platform, ("ARM", "PocketPC");};
+ /\(.*Macintosh.*\)/ && do {push @platform, ("68k", "Macintosh");};
+ /\(.*Mac OS [89].*\)/ && do {push @platform, ("68k", "Macintosh");};
+ /\(.*Win64.*\)/ && do {push @platform, "IA64";};
+ /\(Win.*\)/ && do {push @platform, ("IA32", "x86", "PC");};
+ /\(.*Win(?:dows[ -])NT.*\)/ && do {push @platform, ("IA32", "x86", "PC");};
+ /\(.*OSF.*\)/ && do {push @platform, ("Alpha", "DEC");};
+ /\(.*HP-?UX.*\)/i && do {push @platform, ("PA-RISC", "HP");};
+ /\(.*IRIX.*\)/i && do {push @platform, ("MIPS", "SGI");};
+ /\(.*(SunOS|Solaris).*\)/ && do {push @platform, ("Sparc", "Sun");};
+ #Braindead old browsers who didn't follow convention:
+ /Amiga/ && do {push @platform, ("68k", "Macintosh");};
+ /WinMosaic/ && do {push @platform, ("IA32", "x86", "PC");};
+ }
+ }
+
+ return pick_valid_field_value('rep_platform', @platform) || "Other";
+}
+
+sub pickos {
+ if (formvalue('op_sys') ne "") {
+ return formvalue('op_sys');
+ }
+
+ my @os = ();
+
+ if (Bugzilla->params->{'defaultopsys'}) {
+ @os = Bugzilla->params->{'defaultopsys'};
+ } else {
+ # This function will return the first
+ # item in @os that is a valid platform choice. If
+ # no choice is valid, we return "Other".
+ for ($ENV{'HTTP_USER_AGENT'}) {
+ /\(.*IRIX.*\)/ && do {push @os, "IRIX";};
+ /\(.*OSF.*\)/ && do {push @os, "OSF/1";};
+ /\(.*Linux.*\)/ && do {push @os, "Linux";};
+ /\(.*Solaris.*\)/ && do {push @os, "Solaris";};
+ /\(.*SunOS.*\)/ && do {
+ /\(.*SunOS 5.11.*\)/ && do {push @os, ("OpenSolaris", "Opensolaris", "Solaris 11");};
+ /\(.*SunOS 5.10.*\)/ && do {push @os, "Solaris 10";};
+ /\(.*SunOS 5.9.*\)/ && do {push @os, "Solaris 9";};
+ /\(.*SunOS 5.8.*\)/ && do {push @os, "Solaris 8";};
+ /\(.*SunOS 5.7.*\)/ && do {push @os, "Solaris 7";};
+ /\(.*SunOS 5.6.*\)/ && do {push @os, "Solaris 6";};
+ /\(.*SunOS 5.5.*\)/ && do {push @os, "Solaris 5";};
+ /\(.*SunOS 5.*\)/ && do {push @os, "Solaris";};
+ /\(.*SunOS.*sun4u.*\)/ && do {push @os, "Solaris";};
+ /\(.*SunOS.*i86pc.*\)/ && do {push @os, "Solaris";};
+ /\(.*SunOS.*\)/ && do {push @os, "SunOS";};
+ };
+ /\(.*HP-?UX.*\)/ && do {push @os, "HP-UX";};
+ /\(.*BSD.*\)/ && do {
+ /\(.*BSD\/(?:OS|386).*\)/ && do {push @os, "BSDI";};
+ /\(.*FreeBSD.*\)/ && do {push @os, "FreeBSD";};
+ /\(.*OpenBSD.*\)/ && do {push @os, "OpenBSD";};
+ /\(.*NetBSD.*\)/ && do {push @os, "NetBSD";};
+ };
+ /\(.*BeOS.*\)/ && do {push @os, "BeOS";};
+ /\(.*AIX.*\)/ && do {push @os, "AIX";};
+ /\(.*OS\/2.*\)/ && do {push @os, "OS/2";};
+ /\(.*QNX.*\)/ && do {push @os, "Neutrino";};
+ /\(.*VMS.*\)/ && do {push @os, "OpenVMS";};
+ /\(.*Win.*\)/ && do {
+ /\(.*Windows XP.*\)/ && do {push @os, "Windows XP";};
+ /\(.*Windows NT 6\.1.*\)/ && do {push @os, "Windows 7";};
+ /\(.*Windows NT 6\.0.*\)/ && do {push @os, "Windows Vista";};
+ /\(.*Windows NT 5\.2.*\)/ && do {push @os, "Windows Server 2003";};
+ /\(.*Windows NT 5\.1.*\)/ && do {push @os, "Windows XP";};
+ /\(.*Windows 2000.*\)/ && do {push @os, "Windows 2000";};
+ /\(.*Windows NT 5.*\)/ && do {push @os, "Windows 2000";};
+ /\(.*Win.*9[8x].*4\.9.*\)/ && do {push @os, "Windows ME";};
+ /\(.*Win(?:dows |)M[Ee].*\)/ && do {push @os, "Windows ME";};
+ /\(.*Win(?:dows |)98.*\)/ && do {push @os, "Windows 98";};
+ /\(.*Win(?:dows |)95.*\)/ && do {push @os, "Windows 95";};
+ /\(.*Win(?:dows |)16.*\)/ && do {push @os, "Windows 3.1";};
+ /\(.*Win(?:dows[ -]|)NT.*\)/ && do {push @os, "Windows NT";};
+ /\(.*Windows.*NT.*\)/ && do {push @os, "Windows NT";};
+ };
+ /\(.*Mac OS X.*\)/ && do {
+ /\(.*Mac OS X (?:|Mach-O |\()10.6.*\)/ && do {push @os, "Mac OS X 10.6";};
+ /\(.*Mac OS X (?:|Mach-O |\()10.5.*\)/ && do {push @os, "Mac OS X 10.5";};
+ /\(.*Mac OS X (?:|Mach-O |\()10.4.*\)/ && do {push @os, "Mac OS X 10.4";};
+ /\(.*Mac OS X (?:|Mach-O |\()10.3.*\)/ && do {push @os, "Mac OS X 10.3";};
+ /\(.*Mac OS X (?:|Mach-O |\()10.2.*\)/ && do {push @os, "Mac OS X 10.2";};
+ /\(.*Mac OS X (?:|Mach-O |\()10.1.*\)/ && do {push @os, "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.
+ /\(.*Intel.*Mac OS X.*\)/ && do {push @os, "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
+ /\(.*Mac OS X.*\)/ && do {push @os, ("Mac OS X 10.3", "Mac OS X 10.0", "Mac OS X");};
+ };
+ /\(.*32bit.*\)/ && do {push @os, "Windows 95";};
+ /\(.*16bit.*\)/ && do {push @os, "Windows 3.1";};
+ /\(.*Mac OS \d.*\)/ && do {
+ /\(.*Mac OS 9.*\)/ && do {push @os, ("Mac System 9.x", "Mac System 9.0");};
+ /\(.*Mac OS 8\.6.*\)/ && do {push @os, ("Mac System 8.6", "Mac System 8.5");};
+ /\(.*Mac OS 8\.5.*\)/ && do {push @os, "Mac System 8.5";};
+ /\(.*Mac OS 8\.1.*\)/ && do {push @os, ("Mac System 8.1", "Mac System 8.0");};
+ /\(.*Mac OS 8\.0.*\)/ && do {push @os, "Mac System 8.0";};
+ /\(.*Mac OS 8[^.].*\)/ && do {push @os, "Mac System 8.0";};
+ /\(.*Mac OS 8.*\)/ && do {push @os, "Mac System 8.6";};
+ };
+ /\(.*Darwin.*\)/ && do {push @os, ("Mac OS X 10.0", "Mac OS X");};
+ # Silly
+ /\(.*Mac.*\)/ && do {
+ /\(.*Mac.*PowerPC.*\)/ && do {push @os, "Mac System 9.x";};
+ /\(.*Mac.*PPC.*\)/ && do {push @os, "Mac System 9.x";};
+ /\(.*Mac.*68k.*\)/ && do {push @os, "Mac System 8.0";};
+ };
+ # Evil
+ /Amiga/i && do {push @os, "Other";};
+ /WinMosaic/ && do {push @os, "Windows 95";};
+ /\(.*PowerPC.*\)/ && do {push @os, "Mac System 9.x";};
+ /\(.*PPC.*\)/ && do {push @os, "Mac System 9.x";};
+ /\(.*68K.*\)/ && do {push @os, "Mac System 8.0";};
+ }
+ }
+
+ push(@os, "Windows") if grep(/^Windows /, @os);
+ push(@os, "Mac OS") if grep(/^Mac /, @os);
+
+ return pick_valid_field_value('op_sys', @os) || "Other";
+}
+##############################################################################
+# End of subroutines
+##############################################################################
+
+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
+# Check that the user has authorization to view the parent bug
+# Create an instance of Bug that holds the info from the parent
+$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;
+}
+
+if (scalar(@{$product->components}) == 1) {
+ # Only one component; just pick it.
+ $cgi->param('component', $product->components->[0]->name);
+}
+
+my %default;
+
+$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->{'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->{'cloned_bug_id'} = $cloned_bug_id;
+
+$vars->{'token'} = issue_session_token('createbug:');
+
+
+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;
+ }
+}
+
+# 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;
+
+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_id, @{$cloned_bug->dependson});
+ $vars->{'blocked'} = join (", ", @{$cloned_bug->blocked});
+ $vars->{'deadline'} = $cloned_bug->deadline;
+ $vars->{'estimated_time'} = $cloned_bug->estimated_time;
+
+ if (defined $cloned_bug->cc) {
+ $vars->{'cc'} = join (", ", @{$cloned_bug->cc});
+ } else {
+ $vars->{'cc'} = formvalue('cc');
+ }
+
+ if ($cloned_bug->reporter->id != $user->id) {
+ $vars->{'cc'} = join (", ", $cloned_bug->reporter->login, $vars->{'cc'});
+ }
+
+ 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;
+
+ $vars->{'comment'} = "";
+ $vars->{'comment_is_private'} = 0;
+
+ if (!$isprivate || Bugzilla->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;
+ }
+
+} # 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'} = pickplatform();
+ $default{'op_sys'} = pickos();
+
+ $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->{'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,
+# AND the clone's product is the same as the parent's
+# THEN use the version from the parent bug
+# ELSE IF a version is supplied in the URL
+# THEN use it
+# ELSE IF there is a version in the cookie
+# THEN use it (Posting a bug sets a cookie for the current version.)
+# ELSE
+# The default version is the last one in the list (which, it is
+# hoped, will be the most recent one).
+#
+# Eventually maybe each product should have a "current version"
+# parameter.
+$vars->{'version'} = [map($_->name, @{$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 { $_ eq $version_cookie } @{ $vars->{'version'} })
+{
+ $default{'version'} = $version_cookie;
+} else {
+ $default{'version'} = $vars->{'version'}->[$#{$vars->{'version'}}];
+}
+
+# Get list of milestones.
+if ( Bugzilla->params->{'usetargetmilestone'} ) {
+ $vars->{'target_milestone'} = [map($_->name, @{$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::Status->can_change_to() };
+# Exclude closed states from the UI, even if the workflow allows them.
+# The back-end code will still accept them, though.
+@statuses = grep { $_->is_open } @statuses;
+
+# UNCONFIRMED is illegal if allows_unconfirmed is false.
+if (!$product->allows_unconfirmed) {
+ @statuses = grep { $_->name ne 'UNCONFIRMED' } @statuses;
+}
+scalar(@statuses) || ThrowUserError('no_initial_bug_status');
+
+# If the user has no privs...
+unless ($has_editbugs || $has_canconfirm) {
+ # ... 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]);
+}
+
+$vars->{'bug_status'} = \@statuses;
+
+# Get the default from a template value if it is legitimate.
+# Otherwise, and only if the user has privs, set the default
+# to the first confirmed bug status on the list, if available.
+
+my $picked_status = formvalue('bug_status');
+if ($picked_status and grep($_->name eq $picked_status, @statuses)) {
+ $default{'bug_status'} = formvalue('bug_status');
+} elsif (scalar @statuses == 1) {
+ $default{'bug_status'} = $statuses[0]->name;
+}
+else {
+ $default{'bug_status'} = ($statuses[0]->name ne 'UNCONFIRMED')
+ ? $statuses[0]->name : $statuses[1]->name;
+}
+
+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);
+}
+$default{'groups'} = \@groups;
+
+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'));
+
+print $cgi->header($format->{'ctype'});
+$template->process($format->{'template'}, $vars)
+ || ThrowTemplateError($template->error());
+
diff --git a/extensions/BmpConvert/Config.pm b/extensions/BmpConvert/Config.pm
new file mode 100644
index 000000000..1b314917a
--- /dev/null
+++ b/extensions/BmpConvert/Config.pm
@@ -0,0 +1,33 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by the Initial Developer are Copyright (C) 2009
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::Extension::BmpConvert;
+use strict;
+use constant NAME => 'BmpConvert';
+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
new file mode 100644
index 000000000..ee08e04f0
--- /dev/null
+++ b/extensions/BmpConvert/Extension.pm
@@ -0,0 +1,57 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Frédéric Buclin.
+# Portions created by Frédéric Buclin are Copyright (C) 2009
+# Frédéric Buclin. All Rights Reserved.
+#
+# Contributor(s):
+# Frédéric Buclin <LpSolit@gmail.com>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::Extension::BmpConvert;
+use strict;
+use base qw(Bugzilla::Extension);
+
+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;
+}
+
+ __PACKAGE__->NAME;
diff --git a/extensions/BmpConvert/disabled b/extensions/BmpConvert/disabled
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/extensions/BmpConvert/disabled
diff --git a/extensions/Example/Config.pm b/extensions/Example/Config.pm
new file mode 100644
index 000000000..378db359d
--- /dev/null
+++ b/extensions/Example/Config.pm
@@ -0,0 +1,42 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by the Initial Developers are Copyright (C) 2009 the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::Extension::Example;
+use strict;
+use constant NAME => 'Example';
+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__->NAME;
diff --git a/extensions/Example/Extension.pm b/extensions/Example/Extension.pm
new file mode 100644
index 000000000..9162ccd23
--- /dev/null
+++ b/extensions/Example/Extension.pm
@@ -0,0 +1,777 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by the Initial Developers are Copyright (C) 2009 the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+# Frédéric Buclin <LpSolit@gmail.com>
+
+package Bugzilla::Extension::Example;
+use strict;
+use base qw(Bugzilla::Extension);
+
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Group;
+use Bugzilla::User;
+use Bugzilla::User::Setting;
+use Bugzilla::Util qw(diff_arrays html_quote);
+use Bugzilla::Status qw(is_open_state);
+
+# This is extensions/Example/lib/Util.pm. I can load this here in my
+# Extension.pm only because I have a Config.pm.
+use Bugzilla::Extension::Example::Util;
+
+use Data::Dumper;
+
+# See bugmail_relationships.
+use constant REL_EXAMPLE => -127;
+
+our $VERSION = '1.0';
+
+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";
+ }
+ }
+}
+
+sub auth_login_methods {
+ 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';
+ }
+}
+
+sub bug_columns {
+ my ($self, $args) = @_;
+ my $columns = $args->{'columns'};
+ push (@$columns, "delta_ts AS example")
+}
+
+sub bug_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 $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!";
+}
+
+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} = [];
+}
+
+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 $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.";
+ # 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 $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 });
+}
+
+# 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>};
+};
+
+sub buglist_columns {
+ my ($self, $args) = @_;
+
+ my $columns = $args->{'columns'};
+ $columns->{'example'} = { 'name' => 'bugs.delta_ts' , 'title' => 'Example' };
+}
+
+sub search_operator_field_override {
+ my ($self, $args) = @_;
+
+ 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 = shift;
+
+ my %func_args = @_;
+ $invocant->$original(%func_args);
+
+ # Actually, it does not change anything in the result,
+ # just an example.
+ my ($term) = @func_args{qw(term)};
+ $$term = $$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 he 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';
+}
+
+sub config {
+ my ($self, $args) = @_;
+
+ my $config = $args->{config};
+ $config->{Example} = "Bugzilla::Extension::Example::Config";
+}
+
+sub config_add_panels {
+ 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);
+
+ push(@{ $info_class->{choices} }, 'CGI,Example');
+ push(@{ $verify_class->{choices} }, 'Example');
+}
+
+sub email_in_before_parse {
+ 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;
+ }
+}
+
+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 him,
+ # but he 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 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;
+}
+
+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 $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!";
+}
+
+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 $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 ($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;
+}
+
+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 .
+}
+
+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');
+}
+
+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));
+ }
+}
+
+sub object_before_delete {
+ my ($self, $args) = @_;
+
+ 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!";
+ }
+}
+
+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!";
+ }
+}
+
+sub object_columns {
+ my ($self, $args) = @_;
+ my ($class, $columns) = @$args{qw(class columns)};
+
+ if ($class->isa('Bugzilla::ExampleObject')) {
+ push(@$columns, 'example');
+ }
+}
+
+sub object_end_of_create {
+ my ($self, $args) = @_;
+
+ my $class = $args->{'class'};
+ my $object = $args->{'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;
+ }
+
+}
+
+sub object_end_of_set {
+ my ($self, $args) = @_;
+
+ 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};
+ }
+}
+
+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;
+ }
+ }
+
+}
+
+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!";
+ }
+ }
+}
+
+sub object_update_columns {
+ my ($self, $args) = @_;
+ my ($object, $columns) = @$args{qw(object columns)};
+
+ 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, @_) };
+ }
+}
+
+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";
+
+ # 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) {
+ # Uncomment 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.
+ #ThrowUserError('example_short_desc_invalid');
+ }
+ 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 };
+ }
+}
+
+sub product_confirm_delete {
+ my ($self, $args) = @_;
+
+ my $vars = $args->{vars};
+ $vars->{'example'} = 1;
+}
+
+
+sub product_end_of_create {
+ my ($self, $args) = @_;
+
+ my $product = $args->{product};
+
+ # 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.
+
+ my $example_group = new Bugzilla::Group({ name => 'example_group' });
+
+ 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'.
+
+ my $default_assignee = new Bugzilla::User(
+ { name => Bugzilla->params->{maintainer} });
+
+ if ($default_assignee) {
+# Bugzilla::Component->create(
+# { name => 'No Component',
+# product => $product,
+# 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'};
+
+ # 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
+ 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;
+}
+
+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');
+ }
+}
+
+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' };
+}
+
+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 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;
+ }
+ }
+
+ # 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 user_preferences {
+ my ($self, $args) = @_;
+ my $tab = $args->{current_tab};
+ my $save = $args->{save_changes};
+ my $handled = $args->{handled};
+
+ 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;
+
+ # 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 $dispatch = $args->{dispatch};
+ $dispatch->{Example} = "Bugzilla::Extension::Example::WebService";
+}
+
+sub webservice_error_codes {
+ my ($self, $args) = @_;
+
+ my $error_map = $args->{error_map};
+ $error_map->{'example_my_error'} = 10001;
+}
+
+# This must be the last line of your extension.
+__PACKAGE__->NAME;
diff --git a/extensions/Example/disabled b/extensions/Example/disabled
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/extensions/Example/disabled
diff --git a/extensions/Example/lib/Auth/Login.pm b/extensions/Example/lib/Auth/Login.pm
new file mode 100644
index 000000000..9f4f37dc3
--- /dev/null
+++ b/extensions/Example/lib/Auth/Login.pm
@@ -0,0 +1,32 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Example Plugin.
+#
+# The Initial Developer of the Original Code is Canonical Ltd.
+# Portions created by Canonical are Copyright (C) 2008 Canonical Ltd.
+# All Rights Reserved.
+#
+# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::Extension::Example::Auth::Login;
+use strict;
+use base qw(Bugzilla::Auth::Login);
+use constant user_can_create_account => 0;
+use Bugzilla::Constants;
+
+# Always returns no data.
+sub get_login_info {
+ return { failure => AUTH_NODATA };
+}
+
+1;
diff --git a/extensions/Example/lib/Auth/Verify.pm b/extensions/Example/lib/Auth/Verify.pm
new file mode 100644
index 000000000..0141a0d6a
--- /dev/null
+++ b/extensions/Example/lib/Auth/Verify.pm
@@ -0,0 +1,31 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Example Plugin.
+#
+# The Initial Developer of the Original Code is Canonical Ltd.
+# Portions created by Canonical are Copyright (C) 2008 Canonical Ltd.
+# All Rights Reserved.
+#
+# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::Extension::Example::Auth::Verify;
+use strict;
+use base qw(Bugzilla::Auth::Verify);
+use Bugzilla::Constants;
+
+# A verifier that always fails.
+sub check_credentials {
+ return { failure => AUTH_NO_SUCH_USER };
+}
+
+1;
diff --git a/extensions/Example/lib/Config.pm b/extensions/Example/lib/Config.pm
new file mode 100644
index 000000000..a126e82df
--- /dev/null
+++ b/extensions/Example/lib/Config.pm
@@ -0,0 +1,41 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Example Plugin.
+#
+# The Initial Developer of the Original Code is Canonical Ltd.
+# Portions created by Canonical Ltd. are Copyright (C) 2008
+# Canonical Ltd. All Rights Reserved.
+#
+# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+# Bradley Baetz <bbaetz@acm.org>
+
+package Bugzilla::Extension::Example::Config;
+use strict;
+use warnings;
+
+use Bugzilla::Config::Common;
+
+sub get_param_list {
+ my ($class) = @_;
+
+ my @param_list = (
+ {
+ name => 'example_string',
+ type => 't',
+ default => 'EXAMPLE',
+ },
+ );
+ return @param_list;
+}
+
+1;
diff --git a/extensions/Example/lib/Util.pm b/extensions/Example/lib/Util.pm
new file mode 100644
index 000000000..596f048e9
--- /dev/null
+++ b/extensions/Example/lib/Util.pm
@@ -0,0 +1,28 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by Everything Solved, Inc. are Copyright (C) 2009
+# Everything Solved, Inc. All Rights Reserved.
+#
+# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::Extension::Example::Util;
+use strict;
+use warnings;
+
+# This file exists only to demonstrate how to use and name your
+# modules in an extension.
+
+1;
diff --git a/extensions/Example/lib/WebService.pm b/extensions/Example/lib/WebService.pm
new file mode 100644
index 000000000..8563ec7f0
--- /dev/null
+++ b/extensions/Example/lib/WebService.pm
@@ -0,0 +1,32 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by Everything Solved, Inc. are Copyright (C) 2007
+# Everything Solved, Inc. All Rights Reserved.
+#
+# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::Extension::Example::WebService;
+use strict;
+use warnings;
+use base qw(Bugzilla::WebService);
+use Bugzilla::Error;
+
+# This can be called as Example.hello() from the WebService.
+sub hello { return 'Hello!'; }
+
+sub throw_an_error { ThrowUserError('example_my_error') }
+
+1;
diff --git a/extensions/Example/template/en/default/account/prefs/my_tab.html.tmpl b/extensions/Example/template/en/default/account/prefs/my_tab.html.tmpl
new file mode 100644
index 000000000..ca7a3bd13
--- /dev/null
+++ b/extensions/Example/template/en/default/account/prefs/my_tab.html.tmpl
@@ -0,0 +1,30 @@
+[%#
+ # The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Example Plugin.
+ #
+ # The Initial Developer of the Original Code is Frédéric Buclin.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+
+<p>
+ Type some short text in the field below. Whitespaces will be replaced
+ by colons.
+</p>
+
+<p>
+ <label for="example_pref">Short text:</label>
+ <input type="text" id="example_pref" name="example_pref" size="30"
+ maxlength="50" value="[% example_pref FILTER html %]">
+</p>
diff --git a/extensions/Example/template/en/default/admin/params/example.html.tmpl b/extensions/Example/template/en/default/admin/params/example.html.tmpl
new file mode 100644
index 000000000..e2bb5f35c
--- /dev/null
+++ b/extensions/Example/template/en/default/admin/params/example.html.tmpl
@@ -0,0 +1,28 @@
+[%#
+ # The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Example Plugin.
+ #
+ # The Initial Developer of the Original Code is Canonical Ltd.
+ # Portions created by Canonical Ltd. are Copyright (C) 2008
+ # Canonical Ltd. All Rights Reserved.
+ #
+ # Contributor(s): Bradley Baetz <bbaetz@acm.org>
+ #%]
+[%
+ title = "Example Extension"
+ desc = "Configure example extension"
+%]
+
+[% param_descs = {
+ example_string => "Example string",
+}
+%]
diff --git a/extensions/Example/template/en/default/hook/account/prefs/prefs-tabs.html.tmpl b/extensions/Example/template/en/default/hook/account/prefs/prefs-tabs.html.tmpl
new file mode 100644
index 000000000..83801540a
--- /dev/null
+++ b/extensions/Example/template/en/default/hook/account/prefs/prefs-tabs.html.tmpl
@@ -0,0 +1,23 @@
+[%#
+ # The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Example Plugin.
+ #
+ # The Initial Developer of the Original Code is Frédéric Buclin.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+
+[% tabs = tabs.import([{ name => "my_tab", label => "Example Custom Preferences",
+ link => "userprefs.cgi?tab=my_tab", saveable => 1 }
+ ]) %]
diff --git a/extensions/Example/template/en/default/hook/admin/sanitycheck/messages-statuses.html.tmpl b/extensions/Example/template/en/default/hook/admin/sanitycheck/messages-statuses.html.tmpl
new file mode 100644
index 000000000..0ad1f0be5
--- /dev/null
+++ b/extensions/Example/template/en/default/hook/admin/sanitycheck/messages-statuses.html.tmpl
@@ -0,0 +1,36 @@
+[%# -*- Mode: perl; indent-tabs-mode: nil -*-
+ #
+ # The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Example Plugin.
+ #
+ # The Initial Developer of the Original Code is ITA Software
+ # Portions created by the Initial Developer are Copyright (C) 2009
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s): Bradley Baetz <bbaetz@everythingsolved.com>
+ #%]
+
+[% IF san_tag == "example_check_au_user" %]
+ <em>EXAMPLE PLUGIN</em> - Checking for non-Australian users.
+[% ELSIF san_tag == "example_check_au_user_alert" %]
+ User &lt;[% login FILTER html %]&gt; isn't Australian.
+ [% IF user.in_group('editusers') %]
+ <a href="editusers.cgi?id=[% userid FILTER none %]">Edit this user</a>.
+ [% END %]
+[% ELSIF san_tag == "example_check_au_user_prompt" %]
+ <a href="sanitycheck.cgi?example_repair_au_user=1&amp;token=
+ [%- issue_hash_token(['sanitycheck']) FILTER url_quote %]">Fix these users</a>.
+[% ELSIF san_tag == "example_repair_au_user_start" %]
+ <em>EXAMPLE PLUGIN</em> - OK, would now make users Australian.
+[% ELSIF san_tag == "example_repair_au_user_end" %]
+ <em>EXAMPLE PLUGIN</em> - Users would now be Australian.
+[% END %]
diff --git a/extensions/Example/template/en/default/hook/global/setting-descs-settings.none.tmpl b/extensions/Example/template/en/default/hook/global/setting-descs-settings.none.tmpl
new file mode 100644
index 000000000..9dc5fc767
--- /dev/null
+++ b/extensions/Example/template/en/default/hook/global/setting-descs-settings.none.tmpl
@@ -0,0 +1,6 @@
+[%
+ setting_descs.product_chooser = "Product chooser to use when entering $terms.bugs",
+ setting_descs.pretty = "Pretty chooser with common products and icons",
+ setting_descs.full = "Full chooser with all products",
+ setting_descs.small = "Product chooser for mobile devices",
+%] \ No newline at end of file
diff --git a/extensions/Example/template/en/default/hook/global/user-error-errors.html.tmpl b/extensions/Example/template/en/default/hook/global/user-error-errors.html.tmpl
new file mode 100644
index 000000000..50d20a9f2
--- /dev/null
+++ b/extensions/Example/template/en/default/hook/global/user-error-errors.html.tmpl
@@ -0,0 +1,15 @@
+[%# Note that error messages should generally be indented four spaces, like
+ # below, because when Bugzilla translates an error message into plain
+ # text, it takes four spaces off the beginning of the lines.
+ #
+ # Note also that I prefixed my error name with "example", the name of my
+ # extension, so that I wouldn't conflict with other error names in
+ # Bugzilla or other extensions.
+ #%]
+[% IF error == "example_my_error" %]
+ [% title = "Example Error Title" %]
+ This is the error message! It contains <em>some html</em>.
+[% ELSIF error == "example_short_desc_invalid" %]
+ [% title = "Bad Summary" %]
+ The Summary must contain the word "example".
+[% END %]
diff --git a/extensions/Example/template/en/default/pages/example.html.tmpl b/extensions/Example/template/en/default/pages/example.html.tmpl
new file mode 100644
index 000000000..d53f78fde
--- /dev/null
+++ b/extensions/Example/template/en/default/pages/example.html.tmpl
@@ -0,0 +1,32 @@
+[%#
+ # The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Example Plugin.
+ #
+ # The Initial Developer of the Original Code is Canonical Ltd.
+ # Portions created by Canonical Ltd. are Copyright (C) 2009
+ # Canonical Ltd. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% PROCESS global/header.html.tmpl
+ title = "Example Page"
+%]
+
+<p>Here's what you passed me:</p>
+[% USE Dumper %]
+<pre>
+ [% Dumper.dump_html(cgi_variables) %]
+</pre>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/extensions/Example/template/en/default/setup/strings.txt.pl b/extensions/Example/template/en/default/setup/strings.txt.pl
new file mode 100644
index 000000000..8da19c0aa
--- /dev/null
+++ b/extensions/Example/template/en/default/setup/strings.txt.pl
@@ -0,0 +1,24 @@
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by the Initial Developer are Copyright (C) 2009 the
+# Initial Developer. All Rights Reserved.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+%strings = (
+ feature_example_acme => 'Example Extension: Acme Feature',
+);
+
+1;
diff --git a/extensions/OldBugMove/Config.pm b/extensions/OldBugMove/Config.pm
new file mode 100644
index 000000000..e40126046
--- /dev/null
+++ b/extensions/OldBugMove/Config.pm
@@ -0,0 +1,25 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the OldBugMove Bugzilla Extension.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by the Initial Developer is Copyright (C) 2010 the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::Extension::OldBugMove;
+use strict;
+use constant NAME => 'OldBugMove';
+__PACKAGE__->NAME;
diff --git a/extensions/OldBugMove/Extension.pm b/extensions/OldBugMove/Extension.pm
new file mode 100644
index 000000000..b12d36a9a
--- /dev/null
+++ b/extensions/OldBugMove/Extension.pm
@@ -0,0 +1,208 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the OldBugMove Bugzilla Extension.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by the Initial Developer are Copyright (C) 2010 the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::Extension::OldBugMove;
+use strict;
+use base qw(Bugzilla::Extension);
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Field::Choice;
+use Bugzilla::Mailer;
+use Bugzilla::User;
+use Bugzilla::Util qw(trim);
+
+use Scalar::Util qw(blessed);
+use Storable qw(dclone);
+
+use constant VERSION => BUGZILLA_VERSION;
+
+# This is 4 because that's what it originally was when this code was
+# a part of Bugzilla.
+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 });
+ }
+}
+
+sub config_add_panels {
+ 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 $constants = $config->{CONSTANTS};
+ $constants->{CMT_MOVED_TO} = CMT_MOVED_TO;
+
+ 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');
+ }
+ }
+}
+
+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;
+ }
+}
+
+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 });
+ }
+ }
+}
+
+sub object_end_of_set_all {
+ 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' });
+ }
+}
+
+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, @_) };
+ }
+}
+
+sub _check_bug_resolution {
+ my $original_validator = shift;
+ my ($invocant, $resolution) = @_;
+
+ if ($resolution eq 'MOVED' and !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->(@_);
+}
+
+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);
+ }
+}
+
+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);
+}
+
+sub _user_is_mover {
+ my $user = shift;
+
+ 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/disabled b/extensions/OldBugMove/disabled
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/extensions/OldBugMove/disabled
diff --git a/extensions/OldBugMove/lib/Params.pm b/extensions/OldBugMove/lib/Params.pm
new file mode 100644
index 000000000..a8617e347
--- /dev/null
+++ b/extensions/OldBugMove/lib/Params.pm
@@ -0,0 +1,60 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Dawn Endico <endico@mozilla.org>
+# Dan Mosedale <dmose@mozilla.org>
+# Joe Robins <jmrobins@tgix.com>
+# Jacob Steenhagen <jake@bugzilla.org>
+# J. Paul Reed <preed@sigkill.com>
+# Bradley Baetz <bbaetz@student.usyd.edu.au>
+# Joseph Heenan <joseph@heenan.me.uk>
+# Erik Stambaugh <erik@dasbistro.com>
+# Frédéric Buclin <LpSolit@gmail.com>
+#
+
+package Bugzilla::Extension::OldBugMove::Params;
+
+use strict;
+
+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 => ''
+ },
+);
+
+1;
diff --git a/extensions/OldBugMove/template/en/default/admin/params/oldbugmove.html.tmpl b/extensions/OldBugMove/template/en/default/admin/params/oldbugmove.html.tmpl
new file mode 100644
index 000000000..ce588b168
--- /dev/null
+++ b/extensions/OldBugMove/template/en/default/admin/params/oldbugmove.html.tmpl
@@ -0,0 +1,40 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Dave Miller <justdave@bugzilla.org>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+[%
+ title = "$terms.Bug Moving"
+ desc = "Set up parameters to move $terms.bugs to/from another installation"
+%]
+
+[% param_descs = {
+
+ "move-to-url" =>
+ "The URL of the database we allow some of our $terms.bugs to"
+ _ " be moved to.",
+
+ "move-to-address" =>
+ "To move ${terms.bugs}, an email is sent to the target database."
+ _ " This is the email address that that database uses to listen"
+ _ " for incoming ${terms.bugs}.",
+
+ movers =>
+ "A list of people with permission to move $terms.bugs ",
+
+} %]
diff --git a/extensions/OldBugMove/template/en/default/hook/bug/edit-after_comment_textarea.html.tmpl b/extensions/OldBugMove/template/en/default/hook/bug/edit-after_comment_textarea.html.tmpl
new file mode 100644
index 000000000..0a7a4fa27
--- /dev/null
+++ b/extensions/OldBugMove/template/en/default/hook/bug/edit-after_comment_textarea.html.tmpl
@@ -0,0 +1,27 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% IF oldbugmove_user_is_mover(user) AND bug.resolution != 'MOVED' %]
+ <p>
+ <input type="submit" id="oldbugmove" name="oldbugmove"
+ value="Move [% terms.Bug FILTER html %] to
+ [%= Param('move-to-url') FILTER html %]">
+ </p>
+[% END %]
diff --git a/extensions/OldBugMove/template/en/default/hook/bug/format_comment-type.txt.tmpl b/extensions/OldBugMove/template/en/default/hook/bug/format_comment-type.txt.tmpl
new file mode 100644
index 000000000..1ce8e369d
--- /dev/null
+++ b/extensions/OldBugMove/template/en/default/hook/bug/format_comment-type.txt.tmpl
@@ -0,0 +1,29 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% IF comment.type == constants.CMT_MOVED_TO %]
+[% comment.body %]
+
+[%+ terms.Bug %] moved to [% Param("move-to-url") %].
+If the move succeeded, [% comment.extra_data FILTER email %] will receive a mail
+containing the number of the new [% terms.bug %] in the other database.
+If all went well, please paste in a link to the new [% terms.bug %].
+Otherwise, reopen this [% terms.bug %].
+[% END %]
diff --git a/extensions/OldBugMove/template/en/default/hook/global/user-error-auth_failure_action.html.tmpl b/extensions/OldBugMove/template/en/default/hook/global/user-error-auth_failure_action.html.tmpl
new file mode 100644
index 000000000..58500976b
--- /dev/null
+++ b/extensions/OldBugMove/template/en/default/hook/global/user-error-auth_failure_action.html.tmpl
@@ -0,0 +1,23 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% IF action == "move" %]
+ move
+[% END %]
diff --git a/extensions/OldBugMove/template/en/default/hook/global/user-error-errors.html.tmpl b/extensions/OldBugMove/template/en/default/hook/global/user-error-errors.html.tmpl
new file mode 100644
index 000000000..935117780
--- /dev/null
+++ b/extensions/OldBugMove/template/en/default/hook/global/user-error-errors.html.tmpl
@@ -0,0 +1,29 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% IF error == "oldbugmove_no_delete_moved" %]
+ As long as the OldBugMove extension is active, you cannot
+ delete the [%+ display_value("resolution", "MOVED") FILTER html %]
+ resolution.
+[% ELSIF error == "oldbugmove_no_manual_move" %]
+ You cannot set the resolution of [% terms.abug %] to
+ [%+ display_value("resolution", "MOVED") FILTER html %] without
+ moving the [% terms.bug %].
+[% END %]
diff --git a/extensions/OldBugMove/template/en/default/hook/list/edit-multiple-after_groups.html.tmpl b/extensions/OldBugMove/template/en/default/hook/list/edit-multiple-after_groups.html.tmpl
new file mode 100644
index 000000000..10e6f73b3
--- /dev/null
+++ b/extensions/OldBugMove/template/en/default/hook/list/edit-multiple-after_groups.html.tmpl
@@ -0,0 +1,28 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+
+[% IF oldbugmove_user_is_mover(user) %]
+ <p>
+ <input type="submit" id="oldbugmove" name="oldbugmove"
+ value="Move [% terms.Bugs FILTER html %] to
+ [%= Param('move-to-url') FILTER html %]">
+ </p>
+[% END %]
diff --git a/extensions/Voting/Extension.pm b/extensions/Voting/Extension.pm
new file mode 100644
index 000000000..8417e0ec3
--- /dev/null
+++ b/extensions/Voting/Extension.pm
@@ -0,0 +1,870 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Stephan Niemz <st.n@gmx.net>
+# Christopher Aillon <christopher@aillon.com>
+# Gervase Markham <gerv@gerv.net>
+# Frédéric Buclin <LpSolit@gmail.com>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::Extension::Voting;
+use strict;
+use base qw(Bugzilla::Extension);
+
+use Bugzilla::Bug;
+use Bugzilla::BugMail;
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Field;
+use Bugzilla::Mailer;
+use Bugzilla::User;
+use Bugzilla::Util qw(detaint_natural);
+use Bugzilla::Token;
+
+use List::Util qw(min);
+
+use constant NAME => 'Voting';
+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;
+
+################
+# Installation #
+################
+
+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'],
+ ],
+ };
+}
+
+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});
+ }
+}
+
+###########
+# Objects #
+###########
+
+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));
+ }
+}
+
+sub bug_fields {
+ 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));
+ }
+}
+
+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;
+ }
+}
+
+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'};
+ }
+}
+
+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'});
+ }
+}
+
+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);
+ }
+}
+
+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->id);
+
+ foreach my $msg (@msgs) {
+ MessageToMTA($msg);
+ }
+ }
+}
+
+#############
+# Templates #
+#############
+
+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;
+}
+
+
+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;
+ }
+ }
+}
+
+###########
+# Bugmail #
+###########
+
+sub bugmail_recipients {
+ 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);
+}
+
+sub bugmail_relationships {
+ my ($self, $args) = @_;
+ my $relationships = $args->{relationships};
+ $relationships->{+REL_VOTER} = 'Voter';
+}
+
+###############
+# Sanitycheck #
+###############
+
+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;
+}
+
+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');
+}
+
+
+##############
+# Validators #
+##############
+
+sub _check_votesperuser {
+ return _check_votes(0, @_);
+}
+
+sub _check_maxvotesperbug {
+ return _check_votes(DEFAULT_VOTES_PER_BUG, @_);
+}
+
+sub _check_votestoconfirm {
+ 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;
+ }
+ }
+ return $votes;
+}
+
+#########
+# Pages #
+#########
+
+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);
+ }
+}
+
+sub _page_bug {
+ my ($vars) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $input = Bugzilla->input_params;
+
+ 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,
+ profiles.userid AS id,
+ votes.vote_count
+ FROM votes
+ INNER JOIN profiles
+ ON profiles.userid = votes.who
+ 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 $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($bug_id) if $bug_id;
+ my $who_id = $input->{user_id} || $user->id;
+
+ # Logged-out users must specify a user_id.
+ Bugzilla->login(LOGIN_REQUIRED) if !$who_id;
+
+ my $who = Bugzilla::User->check({ id => $who_id });
+
+ my $canedit = $user->id == $who->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 @bugs;
+ my @bug_ids;
+ my $total = 0;
+ my $onevoteonly = 0;
+
+ my $vote_list =
+ $dbh->selectall_arrayref('SELECT votes.bug_id, votes.vote_count,
+ bugs.short_desc
+ FROM votes
+ INNER JOIN bugs
+ ON votes.bug_id = bugs.bug_id
+ WHERE votes.who = ?
+ AND bugs.product_id = ?
+ ORDER BY votes.bug_id',
+ undef, ($who->id, $product->id));
+
+ foreach (@$vote_list) {
+ my ($id, $count, $summary) = @$_;
+ $total += $count;
+
+ # Next if user can't see this bug. So, the totals will be correct
+ # and they can see there are votes 'missing', but not on what bug
+ # they are. This seems a reasonable compromise; the alternative is
+ # to lie in the totals.
+ next if !$user->can_see_bug($id);
+
+ push (@bugs, { id => $id,
+ summary => $summary,
+ count => $count });
+ push (@bug_ids, $id);
+ push (@all_bug_ids, $id);
+ }
+
+ $onevoteonly = 1 if (min($product->{votesperuser},
+ $product->{maxvotesperbug}) == 1);
+
+ # Only add the product for display if there are any bugs in it.
+ if ($#bugs > -1) {
+ push (@products, { name => $product->name,
+ bugs => \@bugs,
+ bug_ids => \@bug_ids,
+ onevoteonly => $onevoteonly,
+ total => $total,
+ maxvotes => $product->{votesperuser},
+ maxperbug => $product->{maxvotesperbug} });
+ }
+ }
+
+ $dbh->do('DELETE FROM votes WHERE vote_count <= 0');
+ $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;
+
+ # 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;
+ }
+ }
+
+ # Call check() on each bug ID to make sure it is a positive
+ # integer representing an existing bug that the user is authorized
+ # to access, and make sure the number of votes submitted is also
+ # a non-negative integer (a series of digits not preceded by a
+ # minus sign).
+ my (%votes, @bugs);
+ foreach my $id (@buglist) {
+ my $bug = Bugzilla::Bug->check($id);
+ push(@bugs, $bug);
+ $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 @bugs) {
+ my (%prodcount, %products);
+ foreach my $bug (@bugs) {
+ my $bug_id = $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}});
+ }
+ }
+
+ # Update the user's votes in the database. If the user did not submit
+ # any votes, they may be using a form with checkboxes to remove all their
+ # votes (checkboxes are not submitted along with other form data when
+ # they are not checked, and Bugzilla uses them to represent single votes
+ # for products that only allow one vote per bug). In that case, we still
+ # need to clear the user's votes from the database.
+ my %affected;
+ $dbh->bz_start_transaction();
+
+ # Take note of, and delete the user's old votes from the database.
+ my $bug_list = $dbh->selectcol_arrayref('SELECT bug_id FROM votes
+ WHERE who = ?', undef, $who);
+
+ foreach my $id (@$bug_list) {
+ $affected{$id} = 1;
+ }
+ $dbh->do('DELETE FROM votes WHERE who = ?', undef, $who);
+
+ my $sth_insertVotes = $dbh->prepare('INSERT INTO votes (who, bug_id, vote_count)
+ VALUES (?, ?, ?)');
+
+ # Insert the new values in their place
+ foreach my $id (@buglist) {
+ if ($votes{$id} > 0) {
+ $sth_insertVotes->execute($who, $id, $votes{$id});
+ }
+ $affected{$id} = 1;
+ }
+
+ # Update the cached values in the bugs table
+ print $cgi->header();
+ my @updated_bugs = ();
+
+ my $sth_getVotes = $dbh->prepare("SELECT SUM(vote_count) FROM votes
+ WHERE bug_id = ?");
+
+ my $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);
+
+ my $confirmed = _confirm_if_vote_confirmed($id);
+ push (@updated_bugs, $id) if $confirmed;
+ }
+
+ $dbh->bz_commit_transaction();
+
+ $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->{'votes_recorded'} = 1;
+}
+
+######################
+# Helper Subroutines #
+######################
+
+sub _modify_bug_votes {
+ 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
+ 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 = user_id_to_login($who);
+
+ push(@toomanyvotes_list, {id => $id, name => $name});
+ }
+ }
+
+ $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().
+
+ 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);
+
+ 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
+ FROM votes
+ INNER JOIN bugs ON bugs.bug_id = votes.bug_id
+ WHERE bugs.product_id = ?
+ AND votes.who = ?',
+ undef, $product->id, $who);
+
+ 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'));
+ my $name = user_id_to_login($who);
+
+ 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->settings->{'lang'}->{'value'});
+
+ 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 = new Bugzilla::Bug($id);
+
+ 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;
+ }
+ $bug->update();
+
+ $ret = 1;
+ }
+ return $ret;
+}
+
+
+__PACKAGE__->NAME;
diff --git a/extensions/Voting/disabled b/extensions/Voting/disabled
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/extensions/Voting/disabled
diff --git a/extensions/Voting/template/en/default/hook/account/prefs/email-relationships.html.tmpl b/extensions/Voting/template/en/default/hook/account/prefs/email-relationships.html.tmpl
new file mode 100644
index 000000000..0bd81eae1
--- /dev/null
+++ b/extensions/Voting/template/en/default/hook/account/prefs/email-relationships.html.tmpl
@@ -0,0 +1,22 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% relationships.push({ id = constants.REL_VOTER, description = "Voter" }) %]
+[% no_added_removed.push(constants.REL_VOTER) %]
diff --git a/extensions/Voting/template/en/default/hook/admin/products/edit-common-rows.html.tmpl b/extensions/Voting/template/en/default/hook/admin/products/edit-common-rows.html.tmpl
new file mode 100644
index 000000000..fbbda3ea0
--- /dev/null
+++ b/extensions/Voting/template/en/default/hook/admin/products/edit-common-rows.html.tmpl
@@ -0,0 +1,60 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% DEFAULT
+ product.maxvotesperbug = constants.DEFAULT_VOTES_PER_BUG
+ product.votesperuser = 0
+ product.votestoconfirm = 0
+%]
+
+<tr>
+ <th align="right">Maximum votes per person:</th>
+ <td><input size="5" maxlength="5" name="votesperuser" id="votesperuser"
+ value="[% product.votesperuser FILTER html %]">
+ </td>
+</tr>
+
+<tr>
+ <th align="right">
+ Maximum votes a person can put on a single [% terms.bug %]:
+ </th>
+ <td><input size="5" maxlength="5" name="maxvotesperbug" id="maxvotesperbug"
+ value="[% product.maxvotesperbug FILTER html %]">
+ </td>
+</tr>
+
+<tr id="votes_to_confirm_container"
+ [%- ' class="bz_default_hidden"' IF !product.allows_unconfirmed %]>
+ <th align="right">
+ Confirm [% terms.abug %] if it gets this many votes:
+ </th>
+ <td>
+ <input size="3" maxlength="5" name="votestoconfirm" id="votestoconfirm"
+ value="[% product.votestoconfirm FILTER html %]">
+ <br>(Setting this to 0 disables auto-confirming [% terms.bugs %]
+ by vote.)
+ <script type="text/javascript">
+ YAHOO.util.Event.addListener('allows_unconfirmed', 'change',
+ function() { bz_toggleClass('votes_to_confirm_container',
+ 'bz_default_hidden'); });
+ </script>
+ </td>
+</tr>
+
diff --git a/extensions/Voting/template/en/default/hook/admin/products/updated-changes.html.tmpl b/extensions/Voting/template/en/default/hook/admin/products/updated-changes.html.tmpl
new file mode 100644
index 000000000..139fc641b
--- /dev/null
+++ b/extensions/Voting/template/en/default/hook/admin/products/updated-changes.html.tmpl
@@ -0,0 +1,102 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% SET checkvotes = 0 %]
+
+[% IF changes.votesperuser.defined %]
+ <p>
+ Updated votes per user from
+ [%+ changes.votesperuser.0 FILTER html %] to
+ [%+ product.votesperuser FILTER html %].
+ </p>
+ [% checkvotes = 1 %]
+[% END %]
+
+[% IF changes.maxvotesperbug.defined %]
+ <p>
+ Updated maximum votes per [% terms.bug %] from
+ [%+ changes.maxvotesperbug.0 FILTER html %] to
+ [%+ product.maxvotesperbug FILTER html %].
+ </p>
+ [% checkvotes = 1 %]
+[% END %]
+
+[% IF changes.votestoconfirm.defined %]
+ <p>
+ Updated number of votes needed to confirm a [% terms.bug %] from
+ [%+ changes.votestoconfirm.0 FILTER html %] to
+ [%+ product.votestoconfirm FILTER html %].
+ </p>
+ [% checkvotes = 1 %]
+[% END %]
+
+[%# Note that this display of changed votes and/or confirmed bugs is
+ not very scalable. We could have a _lot_, and we just list them all.
+ One day we should limit this perhaps, or have a more scalable display %]
+
+[% IF checkvotes %]
+ <hr>
+
+ <p>Checking existing votes in this product for anybody who now
+ has too many votes for [% terms.abug %]...<br>
+ [% IF changes.too_many_votes.size %]
+ [% FOREACH detail = changes.too_many_votes %]
+ &rarr;removed votes for [% terms.bug %] <a href="show_bug.cgi?id=
+ [%- detail.id FILTER url_quote %]">
+ [%- detail.id FILTER html %]</a> from [% detail.name FILTER html %]<br>
+ [% END %]
+ [% ELSE %]
+ &rarr;there were none.
+ [% END %]
+ </p>
+
+ <p>Checking existing votes in this product for anybody
+ who now has too many total votes...<br>
+ [% IF changes.too_many_total_votes.size %]
+ [% FOREACH detail = changes.too_many_total_votes %]
+ &rarr;removed votes for [% terms.bug %] <a href="show_bug.cgi?id=
+ [%- detail.id FILTER url_quote %]">
+ [%- detail.id FILTER html %]</a> from [% detail.name FILTER html %]<br>
+ [% END %]
+ [% ELSE %]
+ &rarr;there were none.
+ [% END %]
+ </p>
+
+ <p>Checking unconfirmed [% terms.bugs %] in this product for any which now have
+ sufficient votes...<br>
+ [% IF changes.confirmed_bugs.size %]
+ [% FOREACH id = changes.confirmed_bugs %]
+
+ [%# This is INCLUDED instead of PROCESSED to avoid variables getting
+ overwritten, which happens otherwise %]
+ [% INCLUDE bug/process/results.html.tmpl
+ type = 'votes'
+ header_done = 1
+ sent_bugmail = changes.confirmed_bugs_sent_bugmail.$id
+ id = id
+ %]
+ [% END %]
+ [% ELSE %]
+ &rarr;there were none.
+ [% END %]
+ </p>
+
+[% END %]
diff --git a/extensions/Voting/template/en/default/hook/admin/sanitycheck/messages-statuses.html.tmpl b/extensions/Voting/template/en/default/hook/admin/sanitycheck/messages-statuses.html.tmpl
new file mode 100644
index 000000000..f030922b2
--- /dev/null
+++ b/extensions/Voting/template/en/default/hook/admin/sanitycheck/messages-statuses.html.tmpl
@@ -0,0 +1,41 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% IF san_tag == "voting_cache_rebuild_fix" %]
+ <a href="sanitycheck.cgi?rebuild_vote_cache=1&amp;token=
+ [%- issue_hash_token(['sanitycheck']) FILTER url_quote %]">Click here to
+ rebuild the vote cache</a>
+
+[% ELSIF san_tag == "voting_cache_alert" %]
+ Bad vote cache for [% PROCESS bug_link bug_id = id %]
+
+[% ELSIF san_tag == "voting_count_start" %]
+ Checking cached vote counts.
+
+[% ELSIF san_tag == "voting_count_alert" %]
+ Bad vote sum for [% terms.bug %] [%+ id FILTER html %].
+
+[% ELSIF san_tag == "voting_cache_rebuild_start" %]
+ OK, now rebuilding vote cache.
+
+[% ELSIF san_tag == "voting_cache_rebuild_end" %]
+ Vote cache has been rebuilt
+
+[% END %]
diff --git a/extensions/Voting/template/en/default/hook/admin/users/confirm-delete-warn_safe.html.tmpl b/extensions/Voting/template/en/default/hook/admin/users/confirm-delete-warn_safe.html.tmpl
new file mode 100644
index 000000000..f799f1254
--- /dev/null
+++ b/extensions/Voting/template/en/default/hook/admin/users/confirm-delete-warn_safe.html.tmpl
@@ -0,0 +1,38 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% IF votes %]
+ <li>
+ [% otheruser.login FILTER html %] has voted on
+ [% IF votes == 1 %]
+ [%+ terms.abug %]
+ [% ELSE %]
+ [%+ votes %] [%+ terms.bugs %]
+ [% END %].
+
+ If you delete the user account,
+ [% IF votes == 1 %]
+ this vote
+ [% ELSE %]
+ these votes
+ [% END %]
+ will be deleted along with the user account.
+ </li>
+[% END %]
diff --git a/extensions/Voting/template/en/default/hook/bug/edit-after_importance.html.tmpl b/extensions/Voting/template/en/default/hook/bug/edit-after_importance.html.tmpl
new file mode 100644
index 000000000..606f981ee
--- /dev/null
+++ b/extensions/Voting/template/en/default/hook/bug/edit-after_importance.html.tmpl
@@ -0,0 +1,41 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+[% IF bug.product_obj.votesperuser %]
+ <style type="text/css">
+ #votes_container { white-space: nowrap; }
+ </style>
+
+ <span id="votes_container">
+ [% IF bug.votes %]
+ with
+ <a href="page.cgi?id=voting/bug.html&amp;bug_id=
+ [%- bug.id FILTER url_quote %]">
+ [%- bug.votes %]
+ [% IF bug.votes == 1 %]
+ vote
+ [% ELSE %]
+ votes
+ [% END %]</a>
+ [% END %]
+ (<a href="page.cgi?id=voting/user.html&amp;bug_id=
+ [%- bug.id FILTER url_quote %]#vote_
+ [%- bug.id FILTER url_quote %]">vote</a>)
+ </span>
+[% END %]
diff --git a/extensions/Voting/template/en/default/hook/bug/format_comment-type.txt.tmpl b/extensions/Voting/template/en/default/hook/bug/format_comment-type.txt.tmpl
new file mode 100644
index 000000000..ebba6fcab
--- /dev/null
+++ b/extensions/Voting/template/en/default/hook/bug/format_comment-type.txt.tmpl
@@ -0,0 +1,23 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% IF comment.type == constants.CMT_POPULAR_VOTES %]
+*** This [% terms.bug %] has been confirmed by popular vote. ***
+[% END %]
diff --git a/extensions/Voting/template/en/default/hook/bug/process/header-title.html.tmpl b/extensions/Voting/template/en/default/hook/bug/process/header-title.html.tmpl
new file mode 100644
index 000000000..a4530653b
--- /dev/null
+++ b/extensions/Voting/template/en/default/hook/bug/process/header-title.html.tmpl
@@ -0,0 +1,24 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% IF title_tag == "change_votes" %]
+ [% title = "Change Votes" %]
+[% END %]
+
diff --git a/extensions/Voting/template/en/default/hook/bug/process/results-title.html.tmpl b/extensions/Voting/template/en/default/hook/bug/process/results-title.html.tmpl
new file mode 100644
index 000000000..ae0d465dc
--- /dev/null
+++ b/extensions/Voting/template/en/default/hook/bug/process/results-title.html.tmpl
@@ -0,0 +1,21 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% title.votes = "$Link confirmed by number of votes" %]
diff --git a/extensions/Voting/template/en/default/hook/global/code-error-errors.html.tmpl b/extensions/Voting/template/en/default/hook/global/code-error-errors.html.tmpl
new file mode 100644
index 000000000..50e915941
--- /dev/null
+++ b/extensions/Voting/template/en/default/hook/global/code-error-errors.html.tmpl
@@ -0,0 +1,25 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% IF error == "voting_no_open_bug_status" %]
+ [% title = "$terms.Bug Cannot Be Confirmed" %]
+ There is no valid transition from
+ [%+ display_value("bug_status", "UNCONFIRMED") FILTER html %] to an open state
+[% END %]
diff --git a/extensions/Voting/template/en/default/hook/global/field-descs-end.none.tmpl b/extensions/Voting/template/en/default/hook/global/field-descs-end.none.tmpl
new file mode 100644
index 000000000..1becab4da
--- /dev/null
+++ b/extensions/Voting/template/en/default/hook/global/field-descs-end.none.tmpl
@@ -0,0 +1,23 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% IF in_template_var %]
+ [% vars.field_descs.votes = "Votes" %]
+[% END %]
diff --git a/extensions/Voting/template/en/default/hook/global/reason-descs-end.none.tmpl b/extensions/Voting/template/en/default/hook/global/reason-descs-end.none.tmpl
new file mode 100644
index 000000000..3a1f5a189
--- /dev/null
+++ b/extensions/Voting/template/en/default/hook/global/reason-descs-end.none.tmpl
@@ -0,0 +1,23 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% reason_descs.${constants.REL_VOTER} = "You voted for the ${terms.bug}." %]
+[% watch_reason_descs.${constants.REL_VOTER} =
+ "You are watching a voter for the ${terms.bug}." %]
diff --git a/extensions/Voting/template/en/default/hook/global/user-error-errors.html.tmpl b/extensions/Voting/template/en/default/hook/global/user-error-errors.html.tmpl
new file mode 100644
index 000000000..c2ff70728
--- /dev/null
+++ b/extensions/Voting/template/en/default/hook/global/user-error-errors.html.tmpl
@@ -0,0 +1,55 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% IF error == "voting_must_be_nonnegative" %]
+ [% title = "Votes Must Be Non-negative" %]
+ [% admindocslinks = {'voting.html' => 'Setting up the voting feature'} %]
+ Only use non-negative numbers for your [% terms.bug %] votes.
+
+[% ELSIF error == "voting_product_illegal_votes" %]
+ [% title = "Votes Must Be Non-negative" %]
+ [% admindocslinks = {'voting.html' => 'Setting up the voting feature'} %]
+ '[% votes FILTER html %]' is an invalid value for the
+ <em>
+ [% IF field == "votesperuser" %]
+ Votes Per User
+ [% ELSIF field == "maxvotesperbug" %]
+ Maximum Votes Per [% terms.Bug %]
+ [% ELSIF field == "votestoconfirm" %]
+ Votes To Confirm
+ [% END %]
+ </em> field, which should contain a non-negative number.
+
+[% ELSIF error == "voting_too_many_votes_for_bug" %]
+ [% title = "Illegal Vote" %]
+ [% admindocslinks = {'voting.html' => 'Setting up the voting feature'} %]
+ You may only use at most [% max FILTER html %] votes for a single
+ [%+ terms.bug %] in the
+ <tt>[% product FILTER html %]</tt> product, but you are trying to
+ use [% votes FILTER html %].
+
+[% ELSIF error == "voting_too_many_votes_for_product" %]
+ [% title = "Illegal Vote" %]
+ [% admindocslinks = {'voting.html' => 'Setting up the voting feature'} %]
+ You tried to use [% votes FILTER html %] votes in the
+ <tt>[% product FILTER html %]</tt> product, which exceeds the maximum of
+ [%+ max FILTER html %] votes for this product.
+
+[% END %]
diff --git a/extensions/Voting/template/en/default/hook/search/form-after_freetext_fields.html.tmpl b/extensions/Voting/template/en/default/hook/search/form-after_freetext_fields.html.tmpl
new file mode 100644
index 000000000..dca1dba00
--- /dev/null
+++ b/extensions/Voting/template/en/default/hook/search/form-after_freetext_fields.html.tmpl
@@ -0,0 +1,28 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+<div class="search_field_row">
+ <span class="field_label ">
+ <label for="votes">Only [% terms.bugs %] with at least</label>:
+ </span>
+ <input name="votes" id="votes" size="3"
+ value="[% default.votes.0 FILTER html %]"> votes
+ <input type="hidden" name="votes_type" value="greaterthaneq">
+</div>
diff --git a/extensions/Voting/template/en/default/hook/search/search-report-select-rep_fields.html.tmpl b/extensions/Voting/template/en/default/hook/search/search-report-select-rep_fields.html.tmpl
new file mode 100644
index 000000000..ca74f6d2d
--- /dev/null
+++ b/extensions/Voting/template/en/default/hook/search/search-report-select-rep_fields.html.tmpl
@@ -0,0 +1,21 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% rep_fields.push('votes') %]
diff --git a/extensions/Voting/template/en/default/pages/voting.html.tmpl b/extensions/Voting/template/en/default/pages/voting.html.tmpl
new file mode 100644
index 000000000..99026c0d5
--- /dev/null
+++ b/extensions/Voting/template/en/default/pages/voting.html.tmpl
@@ -0,0 +1,69 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Terry Weissman <terry@mozilla.org>
+ # Gervase Markham <gerv@gerv.net>
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+[% INCLUDE global/header.html.tmpl title = "Voting" %]
+
+<p>[% terms.Bugzilla %] has a "voting" feature. Each product allows users to
+have a certain number of votes. (Some products may not allow any, which means
+you can't vote on things in those products at all.) With your vote, you
+indicate which [% terms.bugs %] you think are the most important and
+would like to see fixed. Note that voting is nowhere near as effective
+as providing a fix yourself.</p>
+
+<p>Depending on how the administrator has configured the relevant product,
+you may be able to vote for the same [% terms.bug %] more than once.
+Remember that you have a limited number of votes. When weighted voting
+is allowed and a limited number of votes are available to you, you will
+have to decide whether you want to distribute your votes among a large
+number of [% terms.bugs %] indicating your minimal interest or focus on
+a few [% terms.bugs %] indicating your strong support for them.
+</p>
+
+<p>To look at votes:</p>
+
+<ul>
+ <li>Go to the query page. Do a normal query, but enter 1 in the "At least
+ ___ votes" field. This will show you items that match your query that
+ have at least one vote.</li>
+</ul>
+
+<p>To vote for [% terms.abug %]:</p>
+
+<ul>
+ <li>Bring up the [% terms.bug %] in question.</li>
+
+ <li>Click on the "(vote)" link that appears on the right of the "Importance"
+ fields. (If no such link appears, then voting may not be allowed in
+ this [% terms.bug %]'s product.)</li>
+
+ <li>Indicate how many votes you want to give this [% terms.bug %]. This page
+ also displays how many votes you've given to other [% terms.bugs %], so you
+ may rebalance your votes as necessary.</li>
+</ul>
+
+<p>You will automatically get email notifying you of any changes that occur
+on [% terms.bugs %] you vote for.</p>
+
+<p>You may review your votes at any time by clicking on the "<a href=
+"page.cgi?id=voting/user.html">My Votes</a>" link in the page footer.</p>
+
+[% INCLUDE global/footer.html.tmpl %]
diff --git a/extensions/Voting/template/en/default/pages/voting/bug.html.tmpl b/extensions/Voting/template/en/default/pages/voting/bug.html.tmpl
new file mode 100644
index 000000000..03434a505
--- /dev/null
+++ b/extensions/Voting/template/en/default/pages/voting/bug.html.tmpl
@@ -0,0 +1,61 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[%# INTERFACE:
+ # bug: Bugzilla::Bug that we are listing the votes for.
+ # users: list of hashes. May be empty. Each hash has two members:
+ # login_name: string. The login name of the user whose vote is attached
+ # vote_count: integer. The number of times that user has votes for this bug.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% PROCESS global/header.html.tmpl
+ title = "Show Votes"
+ subheader = "$terms.Bug $bug.id" FILTER bug_link(bug)
+ %]
+
+[% total = 0 %]
+<table cellspacing="4">
+ <tr>
+ <th>Who</th>
+ <th>Number of votes</th>
+ </tr>
+
+ [% FOREACH voter = users %]
+ [% total = total + voter.vote_count %]
+ <tr>
+ <td>
+ <a href="page.cgi?id=voting/user.html&amp;user_id=
+ [%- voter.id FILTER url_quote %]">
+ [% voter.login_name FILTER email FILTER html %]
+ </a>
+ </td>
+ <td align="right">
+ [% voter.vote_count FILTER html %]
+ </td>
+ </tr>
+ [% END %]
+</table>
+
+<p>Total votes: [% total FILTER html %]</p>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/extensions/Voting/template/en/default/pages/voting/user.html.tmpl b/extensions/Voting/template/en/default/pages/voting/user.html.tmpl
new file mode 100644
index 000000000..c2ee2ae7a
--- /dev/null
+++ b/extensions/Voting/template/en/default/pages/voting/user.html.tmpl
@@ -0,0 +1,186 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ #%]
+
+[%# INTERFACE:
+ # voting_user: hash containing a 'login' field
+ #
+ # products: list of hashes containing details of products relating to
+ # voting:
+ # name: name of product
+ # bugs: list of bugs the user has voted for
+ # bug_ids: list of bug ids the user has voted for
+ # onevoteonly: one or more votes allowed per bug?
+ # total: users current vote count for the product
+ # maxvotes: max votes allowed for a user in this product
+ # maxperbug: max votes per bug allowed for a user in this product
+ #
+ # this_bug: Bugzilla::Bug; if the user is voting for a bug, this is the bug
+ #
+ # canedit: boolean; Should the votes be presented in a form, or readonly?
+ #
+ # all_bug_ids: List of all bug ids the user has voted for, across all products
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% IF !header_done %]
+ [% subheader = voting_user.login FILTER html %]
+ [% IF canedit %]
+ [% title = "Change Votes" %]
+ [% IF this_bug %]
+ [%# We .select and .focus the input so it works for textbox and
+ checkbox %]
+ [% onload = "document.forms['voting_form'].bug_" _ this_bug.id _
+ ".select();document.forms['voting_form'].bug_" _ this_bug.id _
+ ".focus()" %]
+ [% END %]
+ [% ELSE %]
+ [% title = "Show Votes" %]
+ [% END %]
+ [% PROCESS global/header.html.tmpl
+ style_urls = [ "extensions/Voting/web/style.css" ]
+ %]
+[% ELSE %]
+ <hr>
+[% END %]
+
+[% IF votes_recorded %]
+ <p>
+ <font color="red">
+ The changes to your votes have been saved.
+ </font>
+ </p>
+[% ELSE %]
+ <br>
+[% END %]
+
+[% IF products.size %]
+ <form name="voting_form" method="post" action="page.cgi?id=voting/user.html">
+ <input type="hidden" name="action" value="vote">
+ <input type="hidden" name="token" value="[% issue_hash_token(['vote']) FILTER html %]">
+ <table cellspacing="4">
+ <tr>
+ <td></td>
+ <th>Votes</th>
+ <th>[% terms.Bug %] #</th>
+ <th>Summary</th>
+ </tr>
+
+ [% onevoteproduct = 0 %]
+ [% multivoteproduct = 0 %]
+ [% FOREACH product = products %]
+ [% IF product.onevoteonly %]
+ [% onevoteproduct = 1 %]
+ [% ELSE %]
+ [% multivoteproduct = 1 %]
+ [% END %]
+ <tr>
+ <th>[% product.name FILTER html %]</th>
+ <td colspan="2" ><a href="buglist.cgi?bug_id=
+ [%- product.bug_ids.join(",") FILTER url_quote %]">([% terms.bug %] list)</a>
+ </td>
+ <td>
+ [% IF product.maxperbug < product.maxvotes AND
+ product.maxperbug > 1 %]
+ <font size="-1">
+ (Note: only [% product.maxperbug %] vote
+ [% "s" IF product.maxperbug != 1 %] allowed per [% terms.bug %] in
+ this product.)
+ </font>
+ [% END %]
+ </td>
+ </tr>
+
+ [% FOREACH bug = product.bugs %]
+ <tr [% IF bug.id == this_bug.id && canedit %]
+ class="bz_bug_being_voted_on" [% END %]>
+ <td>[% IF bug.id == this_bug.id && canedit %]Enter New Vote here &rarr;
+ [%- END %]</td>
+ <td align="right"><a name="vote_[% bug.id %]">
+ [% IF canedit %]
+ [% IF product.onevoteonly %]
+ <input type="checkbox" name="[% bug.id %]" value="1"
+ [% " checked" IF bug.count %] id="bug_[% bug.id %]">
+ [% ELSE %]
+ <input name="[% bug.id %]" value="[% bug.count %]"
+ size="2" id="bug_[% bug.id %]">
+ [% END %]
+ [% ELSE %]
+ [% bug.count %]
+ [% END %]
+ </a></td>
+ <td align="center">
+ [% bug.id FILTER bug_link(bug) FILTER none %]
+ </td>
+ <td>
+ [% bug.summary FILTER html %]
+ (<a href="page.cgi?id=voting/bug.html&amp;bug_id=[% bug.id %]">Show Votes</a>)
+ </td>
+ </tr>
+ [% END %]
+
+ <tr>
+ <td></td>
+ <td colspan="3">[% product.total %] vote
+ [% "s" IF product.total != 1 %] used out of [% product.maxvotes %]
+ allowed.
+ <br>
+ <br>
+ </td>
+ </tr>
+ [% END %]
+ </table>
+
+ [% IF canedit %]
+ <input type="submit" value="Change My Votes" id="change"> or
+ <a href="buglist.cgi?bug_id=[% all_bug_ids.join(",") FILTER url_quote %]">view all
+ as [% terms.bug %] list</a>
+ <br>
+ <br>
+ To change your votes,
+ [% IF multivoteproduct %]
+ type in new numbers (using zero to mean no votes)
+ [% " or " IF onevoteproduct %]
+ [% END %]
+ [% IF onevoteproduct %]
+ change the checkbox
+ [% END %]
+ and then click <b>Change My Votes</b>.
+ [% ELSE %]
+ <a href="buglist.cgi?bug_id=[% all_bug_ids.join(",") FILTER url_quote %]">View all
+ as [% terms.bug %] list</a>
+ [% END %]
+ </form>
+[% ELSE %]
+ <p>
+ [% IF canedit %]
+ You are
+ [% ELSE %]
+ This user is
+ [% END %]
+ currently not voting on any [% terms.bugs %].
+ </p>
+[% END %]
+
+<p>
+ <a href="page.cgi?id=voting.html">Help with voting</a>.
+</p>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/extensions/Voting/template/en/default/voting/delete-all.html.tmpl b/extensions/Voting/template/en/default/voting/delete-all.html.tmpl
new file mode 100644
index 000000000..f0d3b7e13
--- /dev/null
+++ b/extensions/Voting/template/en/default/voting/delete-all.html.tmpl
@@ -0,0 +1,52 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ #%]
+
+[%# INTERFACE:
+ # This template has no interface.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% PROCESS global/header.html.tmpl
+ title = "Remove your votes?"
+ %]
+
+<p>
+ You are about to remove all of your [% terms.bug %] votes. Are you sure you wish to
+ remove your vote from every [% terms.bug %] you've voted on?
+</p>
+
+<form action="page.cgi?id=voting/user.html" method="post">
+ <input type="hidden" name="action" value="vote">
+ <input type="hidden" name="token" value="[% issue_hash_token(['vote']) FILTER html %]">
+ <p>
+ <input type="radio" name="delete_all_votes" value="1">
+ Yes, delete all my votes
+ </p>
+ <p>
+ <input type="radio" name="delete_all_votes" value="0" checked="checked">
+ No, go back and review my votes
+ </p>
+ <p>
+ <input type="submit" id="vote" value="Submit">
+ </p>
+</form>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/extensions/Voting/template/en/default/voting/votes-removed.txt.tmpl b/extensions/Voting/template/en/default/voting/votes-removed.txt.tmpl
new file mode 100644
index 000000000..bfb37c90d
--- /dev/null
+++ b/extensions/Voting/template/en/default/voting/votes-removed.txt.tmpl
@@ -0,0 +1,55 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Emmanuel Seyman <eseyman@linagora.com>
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+From: [% Param('mailfrom') %]
+To: [% to %]
+Subject: [% terms.Bug %] [%+ bugid %] Some or all of your votes have been removed.
+X-Bugzilla-Type: voteremoved
+
+Some or all of your votes have been removed from [% terms.bug %] [%+ bugid %].
+
+You had [% votesold FILTER html %] [%+ IF votesold == 1 %]vote[% ELSE %]votes[% END
+%] on this [% terms.bug %], but [% votesremoved FILTER html %] have been removed.
+
+[% IF votesnew %]
+You still have [% votesnew FILTER html %] [%+ IF votesnew == 1 %]vote[% ELSE %]votes[% END %] on this [% terms.bug %].
+[% ELSE %]
+You have no more votes remaining on this [% terms.bug %].
+[% END %]
+
+Reason:
+[% IF reason == "votes_bug_moved" %]
+ This [% terms.bug %] has been moved to a different product.
+
+[% ELSIF reason == "votes_too_many_per_bug" %]
+ The rules for voting on this product has changed;
+ you had too many votes for a single [% terms.bug %].
+
+[% ELSIF reason == "votes_too_many_per_user" %]
+ The rules for voting on this product has changed; you had
+ too many total votes, so all votes have been removed.
+[% END %]
+
+
+
+[% urlbase %]show_bug.cgi?id=[% bugid %]
+
diff --git a/extensions/Voting/web/style.css b/extensions/Voting/web/style.css
new file mode 100644
index 000000000..5d9c9afe6
--- /dev/null
+++ b/extensions/Voting/web/style.css
@@ -0,0 +1,24 @@
+/* The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is the Bugzilla Bug Tracking System.
+ *
+ * Contributor(s): Gavin Shelley <bugzilla@chimpychompy.org>
+ */
+
+/* Highlight the row for the bug being voted on */
+tr.bz_bug_being_voted_on {
+ background-color: #e2e2e2;
+}
+
+tr.bz_bug_being_voted_on td {
+ border-style: solid none solid none;
+ border-width: thin;
+}
diff --git a/extensions/create.pl b/extensions/create.pl
new file mode 100755
index 000000000..1b54e6b33
--- /dev/null
+++ b/extensions/create.pl
@@ -0,0 +1,86 @@
+#!/usr/bin/perl -w
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by the Initial Developer are Copyright (C) 2009 the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+use strict;
+use lib qw(. lib);
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Util qw(get_text);
+
+use File::Path qw(mkpath);
+use DateTime;
+
+my $base_dir = bz_locations()->{'extensionsdir'};
+
+my $name = $ARGV[0] or ThrowUserError('extension_create_no_name');
+if ($name !~ /^[A-Z]/) {
+ ThrowUserError('extension_first_letter_caps', { name => $name });
+}
+
+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 web template/en/default/hook),
+ "template/en/default/$lcname")
+{
+ mkpath("$extension_dir/$path") || die "$extension_dir/$path: $!";
+}
+
+my $year = DateTime->now()->year;
+
+my $template = Bugzilla->template;
+my $vars = { year => $year, 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",
+);
+
+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");
+ print $fh $output;
+ close($fh);
+}
+
+print get_text('extension_created', $vars), "\n";
+
+__END__
+
+=head1 NAME
+
+extensions/create.pl - Create a framework for a new Bugzilla Extension.
+
+=head1 SYNOPSIS
+
+ extensions/create.pl NAME
+
+ Creates a framework for an extension called NAME in the F<extensions/>
+ directory.
diff --git a/images/favicon.ico b/images/favicon.ico
new file mode 100644
index 000000000..c8ade39ad
--- /dev/null
+++ b/images/favicon.ico
Binary files differ
diff --git a/images/padlock.png b/images/padlock.png
new file mode 100644
index 000000000..a8be3681c
--- /dev/null
+++ b/images/padlock.png
Binary files differ
diff --git a/importxml.pl b/importxml.pl
new file mode 100755
index 000000000..350bf80c0
--- /dev/null
+++ b/importxml.pl
@@ -0,0 +1,1383 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Dawn Endico <endico@mozilla.org>
+# Gregary Hendricks <ghendricks@novell.com>
+# Vance Baarda <vrb@novell.com>
+# Guzman Braso <gbn@hqso.net>
+# Erik Purins <epurins@day1studios.com>
+# Frédéric Buclin <LpSolit@gmail.com>
+
+# This script reads in xml bug data from standard input and inserts
+# a new bug into bugzilla. Everything before the beginning <?xml line
+# is removed so you can pipe in email messages.
+
+use strict;
+
+#####################################################################
+#
+# This script is used to import bugs from another installation of bugzilla.
+# It can be used in two ways.
+# First using the move function of bugzilla
+# on another system will send mail to an alias provided by
+# the administrator of the target installation (you). Set up an alias
+# similar to the one given below so this mail will be automatically
+# run by this script and imported into your database. Run 'newaliases'
+# after adding this alias to your aliases file. Make sure your sendmail
+# installation is configured to allow mail aliases to execute code.
+#
+# bugzilla-import: "|/usr/bin/perl /opt/bugzilla/importxml.pl"
+#
+# Second it can be run from the command line with any xml file from
+# STDIN that conforms to the bugzilla DTD. In this case you can pass
+# an argument to set whether you want to send the
+# mail that will be sent to the exporter and maintainer normally.
+#
+# importxml.pl bugsfile.xml
+#
+#####################################################################
+
+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));
+}
+
+use lib qw(. lib);
+# Data dumber is used for debugging, I got tired of copying it back in
+# and then removing it.
+#use Data::Dumper;
+
+
+use Bugzilla;
+use Bugzilla::Bug;
+use Bugzilla::Product;
+use Bugzilla::Version;
+use Bugzilla::Component;
+use Bugzilla::Milestone;
+use Bugzilla::FlagType;
+use Bugzilla::BugMail;
+use Bugzilla::Mailer;
+use Bugzilla::User;
+use Bugzilla::Util;
+use Bugzilla::Constants;
+use Bugzilla::Keyword;
+use Bugzilla::Field;
+use Bugzilla::Status;
+
+use MIME::Base64;
+use MIME::Parser;
+use Getopt::Long;
+use Pod::Usage;
+use XML::Twig;
+
+my $debug = 0;
+my $mail = '';
+my $attach_path = '';
+my $help = 0;
+my ($default_product_name, $default_component_name);
+
+my $result = GetOptions(
+ "verbose|debug+" => \$debug,
+ "mail|sendmail!" => \$mail,
+ "attach_path=s" => \$attach_path,
+ "help|?" => \$help,
+ "product=s" => \$default_product_name,
+ "component=s" => \$default_component_name,
+);
+
+pod2usage(0) if $help;
+
+use constant OK_LEVEL => 3;
+use constant DEBUG_LEVEL => 2;
+use constant ERR_LEVEL => 1;
+
+our @logs;
+our @attachments;
+our $bugtotal;
+my $xml;
+my $dbh = Bugzilla->dbh;
+my $params = Bugzilla->params;
+my ($timestamp) = $dbh->selectrow_array("SELECT NOW()");
+
+$default_product_name = '' if !defined $default_product_name;
+$default_component_name = '' if !defined $default_component_name;
+
+###############################################################################
+# Helper sub routines #
+###############################################################################
+
+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);
+ }
+
+}
+
+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 ) );
+}
+
+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;
+}
+
+# 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,
+ $productid, $componentid, $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
+ } );
+ }
+ 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;
+ }
+
+ # 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
+ (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;
+ }
+ return $err;
+}
+
+# Converts and returns the input data as an array.
+sub _to_array {
+ my $value = shift;
+
+ $value = [$value] if !ref($value);
+ return @$value;
+}
+
+###############################################################################
+# XML Handlers #
+###############################################################################
+
+# This subroutine gets called only once - as soon as the <bugzilla> opening
+# tag is parsed. It simply checks to see that the all important exporter
+# maintainer and URL base are set.
+#
+# exporter: email address of the person moving the bugs
+# maintainer: the maintainer of the bugzilla installation
+# as set in the parameters file
+# urlbase: The urlbase parameter of the installation
+# 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);
+}
+
+
+# Parse attachments.
+#
+# This subroutine is called once for each attachment in the xml file.
+# It is called as soon as the closing </attachment> tag is parsed.
+# Since attachments have the potential to be very large, and
+# since each attachment will be inside <bug>..</bug> tags we shove
+# the attachment onto an array which will be processed by process_bug
+# and then disposed of. The attachment array will then contain only
+# one bugs' attachments at a time.
+# The cycle will then repeat for the next <bug>
+#
+# The attach_id is ignored since mysql generates a new one for us.
+# 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');
+ }
+
+ # 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.
+# It is called as soon as the closing </bug> tag is parsed.
+# If this bug had any <attachment> tags, they will have been processed
+# before we get to this point and their data will be in the @attachments
+# array.
+# As each bug is processed, it is inserted into the database and then
+# 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'};
+
+ # 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;
+ }
+ $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];
+ }
+ }
+ 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";
+ }
+ }
+
+ my @long_descs;
+ my $private = 0;
+
+ # Parse long descriptions
+ foreach my $comment ( $bug->children('long_desc') ) {
+ Debug( "Parsing Long Description", DEBUG_LEVEL );
+ my %long_desc;
+ $long_desc{'who'} = $comment->field('who');
+ $long_desc{'bug_when'} = $comment->field('bug_when');
+ $long_desc{'isprivate'} = $comment->{'att'}->{'isprivate'} || 0;
+
+ # if one of the comments is private we need to set this flag
+ if ( $long_desc{'isprivate'} && $exporter->is_insider) {
+ $private = 1;
+ }
+ 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.
+ my $url = $urlbase . "show_bug.cgi?id=";
+ $data =~ s/([Bb]ugs?\s*\#?\s*(\d+))/$url$2/g;
+
+ $long_desc{'thetext'} = $data;
+ push @long_descs, \%long_desc;
+ }
+
+ # instead of giving each comment its own item in the longdescs
+ # table like it should have, lets cat them all into one big
+ # comment otherwise we would have to lie often about who
+ # authored the comment since commenters in one bugzilla probably
+ # don't have accounts in the other one.
+ # If one of the comments is private the whole comment will be
+ # private since we don't want to expose these unnecessarily
+ sub by_date { my @a; my @b; $a->{'bug_when'} cmp $b->{'bug_when'}; }
+ my @sorted_descs = sort by_date @long_descs;
+ my $long_description = "";
+ for ( my $z = 0 ; $z <= $#sorted_descs ; $z++ ) {
+ if ( $z == 0 ) {
+ $long_description .= "\n\n\n---- Reported by ";
+ }
+ else {
+ $long_description .= "\n\n\n---- Additional Comments From ";
+ }
+ $long_description .= "$sorted_descs[$z]->{'who'} ";
+ $long_description .= "$sorted_descs[$z]->{'bug_when'}";
+ $long_description .= " ----";
+ $long_description .= "\n\n";
+ $long_description .= "THIS COMMENT IS PRIVATE \n"
+ if ( $sorted_descs[$z]->{'isprivate'} );
+ $long_description .= $sorted_descs[$z]->{'thetext'};
+ $long_description .= "\n";
+ }
+
+ my $comments;
+
+ $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 .= $urlbase . "show_bug.cgi?id=" . $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
+ 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;
+ }
+ }
+
+ # Timestamps
+ push( @query, "creation_ts" );
+ push( @values,
+ format_time( $bug_fields{'creation_ts'}, "%Y-%m-%d %X" )
+ || $timestamp );
+
+ push( @query, "delta_ts" );
+ push( @values,
+ format_time( $bug_fields{'delta_ts'}, "%Y-%m-%d %X" )
+ || $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) {
+ 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 "") {
+
+ $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" );
+ }
+
+ # 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::Bug::ValidateTime($bug_fields{'estimated_time'}, "e");
+ };
+ if (!$@){
+ push( @values, $bug_fields{'estimated_time'} );
+ push( @query, "estimated_time" );
+ }
+ }
+ if ( defined $bug_fields{'remaining_time'} ) {
+ eval {
+ Bugzilla::Bug::ValidateTime($bug_fields{'remaining_time'}, "r");
+ };
+ if (!$@){
+ push( @values, $bug_fields{'remaining_time'} );
+ push( @query, "remaining_time" );
+ }
+ }
+ if ( defined $bug_fields{'actual_time'} ) {
+ eval {
+ Bugzilla::Bug::ValidateTime($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";
+ }
+ }
+
+ # 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 {
+ $err .= " Previous reporter is unknown.\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";
+ }
+ else {
+ $err .= " Previous assignee is unknown.\n";
+ }
+ }
+
+ 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 {
+ push( @values, $component->default_qa_contact->id || undef );
+ if ($component->default_qa_contact->id){
+ $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 $has_res = defined($bug_fields{'resolution'});
+ my $has_status = defined($bug_fields{'bug_status'});
+ 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 $is_open = is_open_state($bug_fields{'bug_status'});
+ 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 {
+ # There is NO other open bug statuses outside UNCO???
+ Error("no open bug statuses available.");
+ }
+ }
+
+ if($has_status){
+ if($valid_status){
+ if($is_open){
+ if($has_res){
+ $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{ # $is_open is false
+ if(!$has_res){
+ $err .= "Missing Resolution. Setting status to ";
+ if($everconfirmed){
+ $status = $initial_status;
+ $err .= "$initial_status\n";
+ }
+ else{
+ $status = "UNCONFIRMED";
+ $err .= "UNCONFIRMED\n";
+ }
+ }
+ if(!$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;
+ }
+
+ }
+ else{ #has_status is false
+ 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;
+ }
+
+ if (defined $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));
+ } 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);
+ }
+ } 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' );
+
+ # 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";
+ }
+ }
+ }
+
+ # 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;
+ }
+ }
+ my ($keywordarray) = $dbh->selectcol_arrayref(
+ "SELECT d.name FROM keyworddefs d
+ INNER JOIN keywords k
+ ON d.id = k.keywordid
+ WHERE k.bug_id = ?
+ ORDER BY d.name", undef, $id);
+ my $keywordstring = join( ", ", @{$keywordarray} );
+ $dbh->do( "UPDATE bugs SET keywords = ? WHERE bug_id = ?",
+ undef, $keywordstring, $id )
+ }
+
+ # 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);
+ }
+ }
+
+ # 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;
+ }
+ if (!$exporter->is_insider && $att->{'isprivate'}) {
+ $err .= "Exporter not in insidergroup and attachment marked private.\n";
+ $err .= " Marking attachment public\n";
+ $att->{'isprivate'} = 0;
+ }
+
+ my $attacher_id = $att->{'attacher'} ? login_to_id($att->{'attacher'}) : undef;
+
+ $dbh->do("INSERT INTO attachments
+ (bug_id, creation_ts, modification_time, filename, description,
+ mimetype, ispatch, isprivate, isobsolete, submitter_id)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
+ undef, $id, $att->{'date'}, $att->{'date'}, $att->{'filename'},
+ $att->{'desc'}, $att->{'ctype'}, $att->{'ispatch'},
+ $att->{'isprivate'}, $att->{'isobsolete'}, $attacher_id || $exporterid);
+ my $att_id = $dbh->bz_last_key( 'attachments', 'attach_id' );
+ my $att_data = $att->{'data'};
+ my $sth = $dbh->prepare("INSERT INTO attach_data (id, thedata)
+ VALUES ($att_id, ?)" );
+ trick_taint($att_data);
+ $sth->bind_param( 1, $att_data, $dbh->BLOB_TYPE );
+ $sth->execute();
+
+ $comments .= "Imported an attachment (id=$att_id)\n";
+ if (!$attacher_id) {
+ if ($att->{'attacher'}) {
+ $err .= "The original submitter of attachment $att_id was\n ";
+ $err .= $att->{'attacher'} . ", but he doesn'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
+ );
+ }
+ }
+
+ # Clear the attachments array for the next bug
+ @attachments = ();
+
+ # Insert longdesc and append any errors
+ my $worktime = $bug_fields{'actual_time'} || 0.0;
+ $worktime = 0.0 if (!$exporter->is_timetracker);
+ $long_description .= "\n" . $comments;
+ if ($err) {
+ $long_description .= "\n$err\n";
+ }
+ trick_taint($long_description);
+ $dbh->do("INSERT INTO longdescs
+ (bug_id, who, bug_when, work_time, isprivate, thetext)
+ VALUES (?,?,?,?,?,?)", undef,
+ $id, $exporterid, $timestamp, $worktime, $private, $long_description
+ );
+ Bugzilla::Bug->new($id)->_sync_fulltext('new_bug');
+
+ # 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 ${urlbase}show_bug.cgi?id=$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 );
+
+# Read STDIN in slurp mode. VERY dangerous, but we live on the wild side ;-)
+local ($/);
+$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 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 );
+my $twig = XML::Twig->new(
+ twig_handlers => {
+ bug => \&process_bug,
+ attachment => \&process_attachment
+ },
+ start_tag_handlers => { bugzilla => \&init }
+);
+$twig->parse($xml);
+my $root = $twig->root;
+my $maintainer = $root->{'att'}->{'maintainer'};
+my $exporter = $root->{'att'}->{'exporter'};
+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"};
+my @to = ($exporter, $maintainer);
+MailMessage( $subject, $log, @to );
+
+__END__
+
+=head1 NAME
+
+importxml - Import bugzilla bug data from xml.
+
+=head1 SYNOPSIS
+
+ importxml.pl [options] [file ...]
+
+=head1 OPTIONS
+
+=over
+
+=item B<-?>
+
+Print a brief help message and exit.
+
+=item B<-v>
+
+Print error and debug information. Mulltiple -v increases verbosity
+
+=item B<-m> B<--sendmail>
+
+Send mail to exporter with a log of bugs imported and any errors.
+
+=item B<--attach_path>
+
+The path to the attachment files. (Required if encoding="filename"
+is used for attachments.)
+
+=item B<--product=name>
+
+The product to put the bug in if the product specified in the
+XML doesn't exist.
+
+=item B<--component=name>
+
+The component to put the bug in if the component specified in the
+XML doesn't exist.
+
+=back
+
+=head1 DESCRIPTION
+
+ This script is used to import bugs from another installation of bugzilla.
+ It can be used in two ways.
+ First using the move function of bugzilla
+ on another system will send mail to an alias provided by
+ the administrator of the target installation (you). Set up an alias
+ similar to the one given below so this mail will be automatically
+ run by this script and imported into your database. Run 'newaliases'
+ after adding this alias to your aliases file. Make sure your sendmail
+ installation is configured to allow mail aliases to execute code.
+
+ bugzilla-import: "|/usr/bin/perl /opt/bugzilla/importxml.pl --mail"
+
+ Second it can be run from the command line with any xml file from
+ STDIN that conforms to the bugzilla DTD. In this case you can pass
+ an argument to set whether you want to send the
+ mail that will be sent to the exporter and maintainer normally.
+
+ importxml.pl [options] bugsfile.xml
+
+=cut
+
diff --git a/index.cgi b/index.cgi
new file mode 100755
index 000000000..fac26434a
--- /dev/null
+++ b/index.cgi
@@ -0,0 +1,75 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Jacob Steenhagen <jake@bugzilla.org>
+# Frédéric Buclin <LpSolit@gmail.com>
+
+###############################################################################
+# Script Initialization
+###############################################################################
+
+# Make it harder for us to do dangerous things in Perl.
+use strict;
+
+# Include the Bugzilla CGI and general utility library.
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Constants;
+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 $template = Bugzilla->template;
+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');
+}
+
+###############################################################################
+# Main Body Execution
+###############################################################################
+
+# 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();
+}
+
+# Generate and return the UI (HTML page) from the appropriate template.
+$template->process("index.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
diff --git a/install-module.pl b/install-module.pl
new file mode 100755
index 000000000..a78d7ceff
--- /dev/null
+++ b/install-module.pl
@@ -0,0 +1,174 @@
+#!/usr/bin/perl -w
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by Everything Solved are Copyright (C) 2007
+# Everything Solved, Inc. All Rights Reserved.
+#
+# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+
+use strict;
+use warnings;
+
+# Have to abs_path('.') or calls to Bugzilla modules won't work once
+# CPAN has chdir'ed around. We do all of this in this funny order to
+# make sure that we use the lib/ modules instead of the base Perl modules,
+# in case the lib/ modules are newer.
+use Cwd qw(abs_path cwd);
+use lib abs_path('.');
+use Bugzilla::Constants;
+use lib abs_path(bz_locations()->{ext_libpath});
+
+use Bugzilla::Install::CPAN;
+
+use Bugzilla::Constants;
+use Bugzilla::Install::Requirements;
+use Bugzilla::Install::Util qw(bin_loc init_console vers_cmp);
+
+use Data::Dumper;
+use Getopt::Long;
+use Pod::Usage;
+
+init_console();
+
+my @original_args = @ARGV;
+my $original_dir = cwd();
+our %switch;
+GetOptions(\%switch, 'all|a', 'upgrade-all|u', 'show-config|s', 'global|g',
+ 'shell', 'help|h');
+
+pod2usage({ -verbose => 1 }) if $switch{'help'};
+
+if (ON_ACTIVESTATE) {
+ 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;
+}
+
+pod2usage({ -verbose => 0 }) if (!%switch && !@ARGV);
+
+set_cpan_config($switch{'global'});
+
+if ($switch{'show-config'}) {
+ print Dumper($CPAN::Config);
+ exit;
+}
+
+check_cpan_requirements($original_dir, \@original_args);
+
+if ($switch{'shell'}) {
+ 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);
+ }
+}
+
+foreach my $module (@ARGV) {
+ install_module($module);
+}
+
+__END__
+
+=head1 NAME
+
+install-module.pl - Installs or upgrades modules from CPAN.
+This script does not run on Windows.
+
+=head1 SYNOPSIS
+
+ ./install-module.pl Module::Name [--global]
+ ./install-module.pl --all [--global]
+ ./install-module.pl --upgrade-all [--global]
+ ./install-module.pl --show-config
+ ./install-module.pl --shell
+
+ Do "./install-module.pl --help" for more information.
+
+=head1 OPTIONS
+
+=over
+
+=item B<Module::Name>
+
+The name of a module that you want to install from CPAN. This is the
+same thing that you'd give to the C<install> command in the CPAN shell.
+
+You can specify multiple module names separated by a space to install
+multiple modules.
+
+=item B<--global>
+
+This makes install-module install modules globally for all applications,
+instead of just for Bugzilla.
+
+On most systems, you have to be root for C<--global> to work.
+
+=item B<--all>
+
+This will make install-module do its best to install every required
+and optional module that is not installed that Bugzilla can use.
+
+Some modules may fail to install. You can run checksetup.pl to see
+which installed properly.
+
+=item B<--upgrade-all>
+
+This is like C<--all>, except it forcibly installs the very latest
+version of every Bugzilla prerequisite, whether or not you already
+have them installed.
+
+=item B<--show-config>
+
+Prints out the CPAN configuration in raw Perl format. Useful for debugging.
+
+=item B<--shell>
+
+Starts a CPAN shell using the configuration of F<install-module.pl>.
+
+=item B<--help>
+
+Shows this help.
+
+=back
diff --git a/jobqueue.pl b/jobqueue.pl
new file mode 100755
index 000000000..78490ddf0
--- /dev/null
+++ b/jobqueue.pl
@@ -0,0 +1,79 @@
+#!/usr/bin/perl -w
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Mozilla Corporation.
+# Portions created by the Initial Developer are Copyright (C) 2008
+# Mozilla Corporation. All Rights Reserved.
+#
+# Contributor(s):
+# Mark Smith <mark@mozilla.com>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+use strict;
+use File::Basename;
+BEGIN { chdir dirname($0); }
+
+use lib qw(. lib);
+use Bugzilla;
+use Bugzilla::JobQueue::Runner;
+
+Bugzilla::JobQueue::Runner->new();
+
+=head1 NAME
+
+jobqueue.pl - Runs jobs in the background for Bugzilla.
+
+=head1 SYNOPSIS
+
+ ./jobqueue.pl [OPTIONS] COMMAND
+
+ OPTIONS:
+ -f Run in the foreground (don't detach)
+ -d Output a lot of debugging information
+ -p file Specify the file where jobqueue.pl should store its current
+ process id. Defaults to F<data/jobqueue.pl.pid>.
+ -n name What should this process call itself in the system log?
+ Defaults to the full path you used to invoke the script.
+
+ COMMANDS:
+ start Starts a new jobqueue daemon if there isn't one running already
+ stop Stops a running jobqueue daemon
+ restart Stops a running jobqueue if one is running, and then
+ starts a new one.
+ check Report the current status of the daemon.
+ install On some *nix systems, this automatically installs and
+ configures jobqueue.pl as a system service so that it will
+ start every time the machine boots.
+ uninstall Removes the system service for jobqueue.pl.
+ help Display this usage info
+ version Display the version of jobqueue.pl
+
+=head1 DESCRIPTION
+
+See L<Bugzilla::JobQueue> and L<Bugzilla::JobQueue::Runner>.
+
+=head1 Running jobqueue.pl as a System Service
+
+For systems that use Upstart or SysV Init, there is a SysV/Upstart init
+script included with Bugzilla for jobqueue.pl: F<contrib/bugzilla-queue>.
+It should work out-of-the-box on RHEL, Fedora, CentOS etc.
+
+You can install it by doing C<./jobqueue.pl install> as root, after
+already having run L<checksetup> at least once to completion
+on this Bugzilla installation.
+
+If you are using a system that isn't RHEL, Fedora, CentOS, etc., then you
+may have to modify F<contrib/bugzilla-queue> and install it yourself
+manually in order to get C<jobqueue.pl> running as a system service.
diff --git a/js/TUI.js b/js/TUI.js
new file mode 100644
index 000000000..34a79dc16
--- /dev/null
+++ b/js/TUI.js
@@ -0,0 +1,109 @@
+/* The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is the Bugzilla Bug Tracking System.
+ *
+ * The Initial Developer of the Original Code is Netscape Communications
+ * Corporation. Portions created by Netscape are
+ * Copyright (C) 1998 Netscape Communications Corporation. All
+ * Rights Reserved.
+ *
+ * Contributor(s): Dennis Melentyev <dennis.melentyev@infopulse.com.ua>
+ * Max Kanat-Alexander <mkanat@bugzilla.org>
+ */
+
+/* This file provides JavaScript functions to be included when one wishes
+ * to show/hide certain UI elements, and have the state of them being
+ * shown/hidden stored in a cookie.
+ *
+ * TUI stands for Tweak UI.
+ *
+ * Requires js/util.js and the YUI Dom and Cookie libraries.
+ *
+ * See template/en/default/bug/create/create.html.tmpl for a usage example.
+ */
+
+var TUI_HIDDEN_CLASS = 'bz_tui_hidden';
+var TUI_COOKIE_NAME = 'TUI';
+
+var TUI_alternates = new Array();
+
+/**
+ * Hides a particular class of elements if they are shown,
+ * or shows them if they are hidden. Then it stores whether that
+ * class is now hidden or shown.
+ *
+ * @param className The name of the CSS class to hide.
+ */
+function TUI_toggle_class(className) {
+ var elements = YAHOO.util.Dom.getElementsByClassName(className);
+ for (var i = 0; i < elements.length; i++) {
+ bz_toggleClass(elements[i], TUI_HIDDEN_CLASS);
+ }
+ _TUI_save_class_state(elements, className);
+ _TUI_toggle_control_link(className);
+}
+
+
+/**
+ * Specifies that a certain class of items should be hidden by default,
+ * if the user doesn't have a TUI cookie.
+ *
+ * @param className The class to hide by default.
+ */
+function TUI_hide_default(className) {
+ YAHOO.util.Event.onDOMReady(function () {
+ if (!YAHOO.util.Cookie.getSub('TUI', className)) {
+ TUI_toggle_class(className);
+ }
+ });
+}
+
+function _TUI_toggle_control_link(className) {
+ var link = document.getElementById(className + "_controller");
+ if (!link) return;
+ var original_text = link.innerHTML;
+ link.innerHTML = TUI_alternates[className];
+ TUI_alternates[className] = original_text;
+}
+
+function _TUI_save_class_state(elements, aClass) {
+ // We just check the first element to see if it's hidden or not, and
+ // consider that all elements are the same.
+ if (YAHOO.util.Dom.hasClass(elements[0], TUI_HIDDEN_CLASS)) {
+ _TUI_store(aClass, 0);
+ }
+ else {
+ _TUI_store(aClass, 1);
+ }
+}
+
+function _TUI_store(aClass, state) {
+ YAHOO.util.Cookie.setSub(TUI_COOKIE_NAME, aClass, state,
+ {
+ expires: new Date('January 1, 2038'),
+ path: BUGZILLA.param.cookie_path
+ });
+}
+
+function _TUI_restore() {
+ var yui_classes = YAHOO.util.Cookie.getSubs(TUI_COOKIE_NAME);
+ for (yui_item in yui_classes) {
+ if (yui_classes[yui_item] == 0) {
+ var elements = YAHOO.util.Dom.getElementsByClassName(yui_item);
+ for (var i = 0; i < elements.length; i++) {
+ YAHOO.util.Dom.addClass(elements[i], 'bz_tui_hidden');
+ }
+ _TUI_toggle_control_link(yui_item);
+ }
+ }
+}
+
+YAHOO.util.Event.onDOMReady(_TUI_restore);
diff --git a/js/attachment.js b/js/attachment.js
new file mode 100644
index 000000000..d759248cd
--- /dev/null
+++ b/js/attachment.js
@@ -0,0 +1,350 @@
+/* The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is the Bugzilla Bug Tracking System.
+ *
+ * The Initial Developer of the Original Code is Netscape Communications
+ * Corporation. Portions created by Netscape are
+ * Copyright (C) 1998 Netscape Communications Corporation. All
+ * Rights Reserved.
+ *
+ * Contributor(s): Myk Melez <myk@mozilla.org>
+ * Joel Peshkin <bugreport@peshkin.net>
+ * Erik Stambaugh <erik@dasbistro.com>
+ * Marc Schumann <wurblzap@gmail.com>
+ * Guy Pyrzak <guy.pyrzak@gmail.com>
+ */
+
+function validateAttachmentForm(theform) {
+ var desc_value = YAHOO.lang.trim(theform.description.value);
+ if (desc_value == '') {
+ alert(BUGZILLA.string.attach_desc_required);
+ return false;
+ }
+ return true;
+}
+
+function updateCommentPrivacy(checkbox) {
+ var text_elem = document.getElementById('comment');
+ if (checkbox.checked) {
+ text_elem.className='bz_private';
+ } else {
+ text_elem.className='';
+ }
+}
+
+function setContentTypeDisabledState(form)
+{
+ var isdisabled = false;
+ if (form.ispatch.checked)
+ isdisabled = true;
+
+ for (var i=0 ; i<form.contenttypemethod.length ; i++)
+ form.contenttypemethod[i].disabled = isdisabled;
+
+ form.contenttypeselection.disabled = isdisabled;
+ form.contenttypeentry.disabled = isdisabled;
+}
+
+function URLFieldHandler() {
+ var field_attachurl = document.getElementById("attachurl");
+ var greyfields = new Array("data", "ispatch", "autodetect",
+ "list", "manual", "bigfile",
+ "contenttypeselection",
+ "contenttypeentry");
+ var i, thisfield;
+ if (field_attachurl.value.match(/^\s*$/)) {
+ for (i = 0; i < greyfields.length; i++) {
+ thisfield = document.getElementById(greyfields[i]);
+ if (thisfield) {
+ thisfield.removeAttribute("disabled");
+ }
+ }
+ } else {
+ for (i = 0; i < greyfields.length; i++) {
+ thisfield = document.getElementById(greyfields[i]);
+ if (thisfield) {
+ thisfield.setAttribute("disabled", "disabled");
+ }
+ }
+ }
+}
+
+function DataFieldHandler() {
+ var field_data = document.getElementById("data");
+ var greyfields = new Array("attachurl");
+ var i, thisfield;
+ if (field_data.value.match(/^\s*$/)) {
+ for (i = 0; i < greyfields.length; i++) {
+ thisfield = document.getElementById(greyfields[i]);
+ if (thisfield) {
+ thisfield.removeAttribute("disabled");
+ }
+ }
+ } else {
+ for (i = 0; i < greyfields.length; i++) {
+ thisfield = document.getElementById(greyfields[i]);
+ if (thisfield) {
+ thisfield.setAttribute("disabled", "disabled");
+ }
+ }
+ }
+}
+
+function clearAttachmentFields() {
+ var element;
+
+ document.getElementById('data').value = '';
+ DataFieldHandler();
+ if ((element = document.getElementById('bigfile')))
+ element.checked = '';
+ if ((element = document.getElementById('attachurl'))) {
+ element.value = '';
+ URLFieldHandler();
+ }
+ document.getElementById('description').value = '';
+ /* Fire onchange so that the disabled state of the content-type
+ * radio buttons are also reset
+ */
+ element = document.getElementById('ispatch');
+ element.checked = '';
+ bz_fireEvent(element, 'change');
+ if ((element = document.getElementById('isprivate')))
+ element.checked = '';
+}
+
+/* Functions used when viewing patches in Diff mode. */
+
+function collapse_all() {
+ var elem = document.checkboxform.firstChild;
+ while (elem != null) {
+ if (elem.firstChild != null) {
+ var tbody = elem.firstChild.nextSibling;
+ if (tbody.className == 'file') {
+ tbody.className = 'file_collapse';
+ twisty = get_twisty_from_tbody(tbody);
+ twisty.firstChild.nodeValue = '(+)';
+ twisty.nextSibling.checked = false;
+ }
+ }
+ elem = elem.nextSibling;
+ }
+ return false;
+}
+
+function expand_all() {
+ var elem = document.checkboxform.firstChild;
+ while (elem != null) {
+ if (elem.firstChild != null) {
+ var tbody = elem.firstChild.nextSibling;
+ if (tbody.className == 'file_collapse') {
+ tbody.className = 'file';
+ twisty = get_twisty_from_tbody(tbody);
+ twisty.firstChild.nodeValue = '(-)';
+ twisty.nextSibling.checked = true;
+ }
+ }
+ elem = elem.nextSibling;
+ }
+ return false;
+}
+
+var current_restore_elem;
+
+function restore_all() {
+ current_restore_elem = null;
+ incremental_restore();
+}
+
+function incremental_restore() {
+ if (!document.checkboxform.restore_indicator.checked) {
+ return;
+ }
+ var next_restore_elem;
+ if (current_restore_elem) {
+ next_restore_elem = current_restore_elem.nextSibling;
+ } else {
+ next_restore_elem = document.checkboxform.firstChild;
+ }
+ while (next_restore_elem != null) {
+ current_restore_elem = next_restore_elem;
+ if (current_restore_elem.firstChild != null) {
+ restore_elem(current_restore_elem.firstChild.nextSibling);
+ }
+ next_restore_elem = current_restore_elem.nextSibling;
+ }
+}
+
+function restore_elem(elem, alertme) {
+ if (elem.className == 'file_collapse') {
+ twisty = get_twisty_from_tbody(elem);
+ if (twisty.nextSibling.checked) {
+ elem.className = 'file';
+ twisty.firstChild.nodeValue = '(-)';
+ }
+ } else if (elem.className == 'file') {
+ twisty = get_twisty_from_tbody(elem);
+ if (!twisty.nextSibling.checked) {
+ elem.className = 'file_collapse';
+ twisty.firstChild.nodeValue = '(+)';
+ }
+ }
+}
+
+function twisty_click(twisty) {
+ tbody = get_tbody_from_twisty(twisty);
+ if (tbody.className == 'file') {
+ tbody.className = 'file_collapse';
+ twisty.firstChild.nodeValue = '(+)';
+ twisty.nextSibling.checked = false;
+ } else {
+ tbody.className = 'file';
+ twisty.firstChild.nodeValue = '(-)';
+ twisty.nextSibling.checked = true;
+ }
+ return false;
+}
+
+function get_tbody_from_twisty(twisty) {
+ return twisty.parentNode.parentNode.parentNode.nextSibling;
+}
+function get_twisty_from_tbody(tbody) {
+ return tbody.previousSibling.firstChild.nextSibling.firstChild.firstChild;
+}
+
+var prev_mode = 'raw';
+var current_mode = 'raw';
+var has_edited = 0;
+var has_viewed_as_diff = 0;
+function editAsComment(patchviewerinstalled)
+{
+ switchToMode('edit', patchviewerinstalled);
+ has_edited = 1;
+}
+function undoEditAsComment(patchviewerinstalled)
+{
+ switchToMode(prev_mode, patchviewerinstalled);
+}
+function redoEditAsComment(patchviewerinstalled)
+{
+ switchToMode('edit', patchviewerinstalled);
+}
+
+function viewDiff(attachment_id, patchviewerinstalled)
+{
+ switchToMode('diff', patchviewerinstalled);
+
+ // If we have not viewed as diff before, set the view diff frame URL
+ if (!has_viewed_as_diff) {
+ var viewDiffFrame = document.getElementById('viewDiffFrame');
+ viewDiffFrame.src =
+ 'attachment.cgi?id=' + attachment_id + '&action=diff&headers=0';
+ has_viewed_as_diff = 1;
+ }
+}
+
+function viewRaw(patchviewerinstalled)
+{
+ switchToMode('raw', patchviewerinstalled);
+}
+
+function switchToMode(mode, patchviewerinstalled)
+{
+ if (mode == current_mode) {
+ alert('switched to same mode! This should not happen.');
+ return;
+ }
+
+ // Switch out of current mode
+ if (current_mode == 'edit') {
+ hideElementById('editFrame');
+ hideElementById('undoEditButton');
+ } else if (current_mode == 'raw') {
+ hideElementById('viewFrame');
+ if (patchviewerinstalled)
+ hideElementById('viewDiffButton');
+ hideElementById(has_edited ? 'redoEditButton' : 'editButton');
+ hideElementById('smallCommentFrame');
+ } else if (current_mode == 'diff') {
+ if (patchviewerinstalled)
+ hideElementById('viewDiffFrame');
+ hideElementById('viewRawButton');
+ hideElementById(has_edited ? 'redoEditButton' : 'editButton');
+ hideElementById('smallCommentFrame');
+ }
+
+ // Switch into new mode
+ if (mode == 'edit') {
+ showElementById('editFrame');
+ showElementById('undoEditButton');
+ } else if (mode == 'raw') {
+ showElementById('viewFrame');
+ if (patchviewerinstalled)
+ showElementById('viewDiffButton');
+
+ showElementById(has_edited ? 'redoEditButton' : 'editButton');
+ showElementById('smallCommentFrame');
+ } else if (mode == 'diff') {
+ if (patchviewerinstalled)
+ showElementById('viewDiffFrame');
+
+ showElementById('viewRawButton');
+ showElementById(has_edited ? 'redoEditButton' : 'editButton');
+ showElementById('smallCommentFrame');
+ }
+
+ prev_mode = current_mode;
+ current_mode = mode;
+}
+
+function hideElementById(id)
+{
+ var elm = document.getElementById(id);
+ if (elm) {
+ YAHOO.util.Dom.addClass(elm, 'bz_default_hidden');
+ }
+}
+
+function showElementById(id, val)
+{
+ var elm = document.getElementById(id);
+ if (elm) {
+ YAHOO.util.Dom.removeClass(elm, 'bz_default_hidden');
+ }
+}
+
+function normalizeComments()
+{
+ // Remove the unused comment field from the document so its contents
+ // do not get transmitted back to the server.
+
+ var small = document.getElementById('smallCommentFrame');
+ var big = document.getElementById('editFrame');
+ if ( (small) && YAHOO.util.Dom.hasClass(small, 'bz_default_hidden') )
+ {
+ small.parentNode.removeChild(small);
+ }
+ if ( (big) && YAHOO.util.Dom.hasClass(big, 'bz_default_hidden') )
+ {
+ big.parentNode.removeChild(big);
+ }
+}
+
+function toggle_attachment_details_visibility ( )
+{
+ // show hide classes
+ var container = document.getElementById('attachment_info');
+ if( YAHOO.util.Dom.hasClass(container, 'read') ){
+ YAHOO.util.Dom.replaceClass(container, 'read', 'edit');
+ }else{
+ YAHOO.util.Dom.replaceClass(container, 'edit', 'read');
+ }
+}
+
diff --git a/js/bug.js b/js/bug.js
new file mode 100644
index 000000000..ab3322ccd
--- /dev/null
+++ b/js/bug.js
@@ -0,0 +1,131 @@
+/* The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is the Bugzilla Bug Tracking System.
+ *
+ * The Initial Developer of the Original Code is Everything Solved, Inc.
+ * Portions created by Everything Solved are Copyright (C) 2010 Everything
+ * Solved, Inc. All Rights Reserved.
+ *
+ * Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+ */
+
+/* This library assumes that the needed YUI libraries have been loaded
+ already. */
+
+YAHOO.bugzilla.dupTable = {
+ counter: 0,
+ dataSource: null,
+ updateTable: function(dataTable, product_name, summary_field) {
+ if (summary_field.value.length < 4) return;
+
+ YAHOO.bugzilla.dupTable.counter = YAHOO.bugzilla.dupTable.counter + 1;
+ YAHOO.util.Connect.setDefaultPostHeader('application/json', true);
+ var json_object = {
+ version : "1.1",
+ method : "Bug.possible_duplicates",
+ id : YAHOO.bugzilla.dupTable.counter,
+ params : {
+ product : product_name,
+ summary : summary_field.value,
+ limit : 7,
+ include_fields : [ "id", "summary", "status", "resolution",
+ "update_token" ]
+ }
+ };
+ var post_data = YAHOO.lang.JSON.stringify(json_object);
+
+ var callback = {
+ success: dataTable.onDataReturnInitializeTable,
+ failure: dataTable.onDataReturnInitializeTable,
+ scope: dataTable,
+ argument: dataTable.getState()
+ };
+ dataTable.showTableMessage(dataTable.get("MSG_LOADING"),
+ YAHOO.widget.DataTable.CLASS_LOADING);
+ YAHOO.util.Dom.removeClass('possible_duplicates_container',
+ 'bz_default_hidden');
+ dataTable.getDataSource().sendRequest(post_data, callback);
+ },
+ // This is the keyup event handler. It calls updateTable with a relatively
+ // long delay, to allow additional input. However, the delay is short
+ // enough that nobody could get from the summary field to the Submit
+ // Bug button before the table is shown (which is important, because
+ // the showing of the table causes the Submit Bug button to move, and
+ // if the table shows at the exact same time as the button is clicked,
+ // the click on the button won't register.)
+ doUpdateTable: function(e, args) {
+ var dt = args[0];
+ var product_name = args[1];
+ var summary = YAHOO.util.Event.getTarget(e);
+ clearTimeout(YAHOO.bugzilla.dupTable.lastTimeout);
+ YAHOO.bugzilla.dupTable.lastTimeout = setTimeout(function() {
+ YAHOO.bugzilla.dupTable.updateTable(dt, product_name, summary) },
+ 600);
+ },
+ formatBugLink: function(el, oRecord, oColumn, oData) {
+ el.innerHTML = '<a href="show_bug.cgi?id=' + oData + '">'
+ + oData + '</a>';
+ },
+ formatStatus: function(el, oRecord, oColumn, oData) {
+ var resolution = oRecord.getData('resolution');
+ var bug_status = display_value('bug_status', oData);
+ if (resolution) {
+ el.innerHTML = bug_status + ' '
+ + display_value('resolution', resolution);
+ }
+ else {
+ el.innerHTML = bug_status;
+ }
+ },
+ formatCcButton: function(el, oRecord, oColumn, oData) {
+ var url = 'process_bug.cgi?id=' + oRecord.getData('id')
+ + '&addselfcc=1&token=' + escape(oData);
+ var button = document.createElement('button');
+ button.setAttribute('type', 'button');
+ button.innerHTML = YAHOO.bugzilla.dupTable.addCcMessage;
+ button.onclick = function() { window.location = url; return false; };
+ el.appendChild(button);
+ },
+ init_ds: function() {
+ var new_ds = new YAHOO.util.XHRDataSource("jsonrpc.cgi");
+ new_ds.connTimeout = 30000;
+ new_ds.connMethodPost = true;
+ new_ds.connXhrMode = "cancelStaleRequests";
+ new_ds.maxCacheEntries = 3;
+ new_ds.responseSchema = {
+ resultsList : "result.bugs",
+ metaFields : { error: "error", jsonRpcId: "id" }
+ };
+ // DataSource can't understand a JSON-RPC error response, so
+ // we have to modify the result data if we get one.
+ new_ds.doBeforeParseData =
+ function(oRequest, oFullResponse, oCallback) {
+ if (oFullResponse.error) {
+ oFullResponse.result = {};
+ oFullResponse.result.bugs = [];
+ if (console) {
+ console.log("JSON-RPC error:", oFullResponse.error);
+ }
+ }
+ return oFullResponse;
+ }
+
+ this.dataSource = new_ds;
+ },
+ init: function(data) {
+ if (this.dataSource == null) this.init_ds();
+ data.options.initialLoad = false;
+ var dt = new YAHOO.widget.DataTable(data.container, data.columns,
+ this.dataSource, data.options);
+ YAHOO.util.Event.on(data.summary_field, 'keyup', this.doUpdateTable,
+ [dt, data.product_name]);
+ }
+};
diff --git a/js/change-columns.js b/js/change-columns.js
new file mode 100644
index 000000000..e70cfd560
--- /dev/null
+++ b/js/change-columns.js
@@ -0,0 +1,142 @@
+/*# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Pascal Held.
+ #
+ # Contributor(s): Pascal Held <paheld@gmail.com>
+ #
+*/
+
+function initChangeColumns() {
+ window.onunload = unload;
+ var av_select = document.getElementById("available_columns");
+ var sel_select = document.getElementById("selected_columns");
+ YAHOO.util.Dom.removeClass(
+ ['avail_header', av_select, 'select_button',
+ 'deselect_button', 'up_button', 'down_button'], 'bz_default_hidden');
+ switch_options(sel_select, av_select, false);
+ sel_select.selectedIndex = -1;
+ updateView();
+}
+
+function switch_options(from_box, to_box, selected) {
+ for (var i = 0; i<from_box.options.length; i++) {
+ var opt = from_box.options[i];
+ if (opt.selected == selected) {
+ var newopt = new Option(opt.text, opt.value, opt.defaultselected, opt.selected);
+ to_box.options[to_box.options.length] = newopt;
+ from_box.options[i] = null;
+ i = i - 1;
+ }
+
+ }
+}
+
+function move_select() {
+ var av_select = document.getElementById("available_columns");
+ var sel_select = document.getElementById("selected_columns");
+ switch_options(av_select, sel_select, true);
+ updateView();
+}
+
+function move_deselect() {
+ var av_select = document.getElementById("available_columns");
+ var sel_select = document.getElementById("selected_columns");
+ switch_options(sel_select, av_select, true);
+ updateView();
+}
+
+function move_up() {
+ var sel_select = document.getElementById("selected_columns");
+ var last = sel_select.options[0];
+ var dummy = new Option("", "", false, false);
+ for (var i = 1; i<sel_select.options.length; i++) {
+ var opt = sel_select.options[i];
+ if (opt.selected) {
+ sel_select.options[i] = dummy;
+ sel_select.options[i-1] = opt;
+ sel_select.options[i] = last;
+ }
+ else{
+ last = opt;
+ }
+ }
+ updateView();
+}
+
+function move_down() {
+ var sel_select = document.getElementById("selected_columns");
+ var last = sel_select.options[sel_select.options.length-1];
+ var dummy = new Option("", "", false, false);
+ for (var i = sel_select.options.length-2; i >= 0; i--) {
+ var opt = sel_select.options[i];
+ if (opt.selected) {
+ sel_select.options[i] = dummy;
+ sel_select.options[i + 1] = opt;
+ sel_select.options[i] = last;
+ }
+ else{
+ last = opt;
+ }
+ }
+ updateView();
+}
+
+function updateView() {
+ var select_button = document.getElementById("select_button");
+ var deselect_button = document.getElementById("deselect_button");
+ var up_button = document.getElementById("up_button");
+ var down_button = document.getElementById("down_button");
+ select_button.disabled = true;
+ deselect_button.disabled = true;
+ up_button.disabled = true;
+ down_button.disabled = true;
+ var av_select = document.getElementById("available_columns");
+ var sel_select = document.getElementById("selected_columns");
+ for (var i = 0; i < av_select.options.length; i++) {
+ if (av_select.options[i].selected) {
+ select_button.disabled = false;
+ break;
+ }
+ }
+ for (var i = 0; i < sel_select.options.length; i++) {
+ if (sel_select.options[i].selected) {
+ deselect_button.disabled = false;
+ up_button.disabled = false;
+ down_button.disabled = false;
+ break;
+ }
+ }
+ if (sel_select.options.length > 0) {
+ if (sel_select.options[0].selected) {
+ up_button.disabled = true;
+ }
+ if (sel_select.options[sel_select.options.length - 1].selected) {
+ down_button.disabled = true;
+ }
+ }
+}
+
+function change_submit() {
+ var sel_select = document.getElementById("selected_columns");
+ for (var i = 0; i < sel_select.options.length; i++) {
+ sel_select.options[i].selected = true;
+ }
+ return false;
+}
+
+function unload() {
+ var sel_select = document.getElementById("selected_columns");
+ for (var i = 0; i < sel_select.options.length; i++) {
+ sel_select.options[i].selected = true;
+ }
+}
diff --git a/js/comments.js b/js/comments.js
new file mode 100644
index 000000000..2f1a14406
--- /dev/null
+++ b/js/comments.js
@@ -0,0 +1,99 @@
+/* The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is the Bugzilla Bug Tracking System.
+ *
+ * The Initial Developer of the Original Code is Netscape Communications
+ * Corporation. Portions created by Netscape are
+ * Copyright (C) 1998 Netscape Communications Corporation. All
+ * Rights Reserved.
+ *
+ * Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
+ * Max Kanat-Alexander <mkanat@bugzilla.org>
+ * Edmund Wong <ewong@pw-wspx.org>
+ */
+
+function updateCommentPrivacy(checkbox, id) {
+ var comment_elem = document.getElementById('comment_text_'+id).parentNode;
+ if (checkbox.checked) {
+ if (!comment_elem.className.match('bz_private')) {
+ comment_elem.className = comment_elem.className.concat(' bz_private');
+ }
+ }
+ else {
+ comment_elem.className =
+ comment_elem.className.replace(/(\s*|^)bz_private(\s*|$)/, '$2');
+ }
+}
+
+/* The functions below expand and collapse comments */
+
+function toggle_comment_display(link, comment_id) {
+ var comment = document.getElementById('comment_text_' + comment_id);
+ var re = new RegExp(/\bcollapsed\b/);
+ if (comment.className.match(re))
+ expand_comment(link, comment);
+ else
+ collapse_comment(link, comment);
+}
+
+function toggle_all_comments(action) {
+ // If for some given ID the comment doesn't exist, this doesn't mean
+ // there are no more comments, but that the comment is private and
+ // the user is not allowed to view it.
+
+ var comments = YAHOO.util.Dom.getElementsByClassName('bz_comment_text');
+ for (var i = 0; i < comments.length; i++) {
+ var comment = comments[i];
+ if (!comment)
+ continue;
+
+ var id = comments[i].id.match(/\d*$/);
+ var link = document.getElementById('comment_link_' + id);
+ if (action == 'collapse')
+ collapse_comment(link, comment);
+ else
+ expand_comment(link, comment);
+ }
+}
+
+function collapse_comment(link, comment) {
+ link.innerHTML = "[+]";
+ link.title = "Expand the comment.";
+ YAHOO.util.Dom.addClass(comment, 'collapsed');
+}
+
+function expand_comment(link, comment) {
+ link.innerHTML = "[-]";
+ link.title = "Collapse the comment";
+ YAHOO.util.Dom.removeClass(comment, 'collapsed');
+}
+
+/* This way, we are sure that browsers which do not support JS
+ * won't display this link */
+
+function addCollapseLink(count) {
+ document.write(' <a href="#" class="bz_collapse_comment"' +
+ ' id="comment_link_' + count +
+ '" onclick="toggle_comment_display(this, ' + count +
+ '); return false;" title="Collapse the comment.">[-]<\/a> ');
+}
+
+function goto_add_comments( anchor ){
+ anchor = (anchor || "add_comment");
+ // we need this line to expand the comment box
+ document.getElementById('comment').focus();
+ setTimeout(function(){
+ document.location.hash = anchor;
+ // firefox doesn't seem to keep focus through the anchor change
+ document.getElementById('comment').focus();
+ },10);
+ return false;
+}
diff --git a/js/expanding-tree.js b/js/expanding-tree.js
new file mode 100644
index 000000000..d210a629c
--- /dev/null
+++ b/js/expanding-tree.js
@@ -0,0 +1,157 @@
+/* The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is the Bugzilla Bug Tracking System.
+ *
+ * The Initial Developer of the Original Code is Netscape Communications
+ * Corporation. Portions created by Netscape are
+ * Copyright (C) 1998 Netscape Communications Corporation. All
+ * Rights Reserved.
+ *
+ * Contributor(s): Mike Shaver <shaver@mozilla.org>
+ * Christian Reis <kiko@async.com.br>
+ * Andr Batosti <batosti@async.com.br>
+ */
+
+if (!Node) {
+ // MSIE doesn't define Node, so provide a compatibility object
+ var Node = { TEXT_NODE: 3 }
+}
+
+if (!highlighted) {
+ var highlighted = 0;
+ var highlightedclass = "";
+ var highlightedover = 0;
+}
+
+function doToggle(node, event) {
+ var deep = event.altKey || event.ctrlKey;
+
+ if (node.nodeType == Node.TEXT_NODE)
+ node = node.parentNode;
+
+ var toggle = node.nextSibling;
+ while (toggle && toggle.tagName != "UL")
+ toggle = toggle.nextSibling;
+
+ if (toggle) {
+ if (deep) {
+ var direction = toggleDisplay(toggle, node);
+ changeChildren(toggle, direction);
+ } else {
+ toggleDisplay(toggle, node);
+ }
+ }
+ /* avoid problems with default actions on links (mozilla's
+ * ctrl/shift-click defaults, for instance */
+ event.preventBubble();
+ event.preventDefault();
+ return false;
+}
+
+function changeChildren(node, direction) {
+ var item = node.firstChild;
+ while (item) {
+ /* find the LI inside the UL I got */
+ while (item && item.tagName != "LI")
+ item = item.nextSibling;
+ if (!item)
+ return;
+
+ /* got it, now find the first A */
+ var child = item.firstChild;
+ while (child && child.tagName != "A")
+ child = child.nextSibling;
+ if (!child) {
+ return
+ }
+ var bullet = child;
+
+ /* and check if it has its own sublist */
+ var sublist = item.firstChild;
+ while (sublist && sublist.tagName != "UL")
+ sublist = sublist.nextSibling;
+ if (sublist) {
+ if (direction && isClosed(sublist)) {
+ openNode(sublist, bullet);
+ } else if (!direction && !isClosed(sublist)) {
+ closeNode(sublist, bullet);
+ }
+ changeChildren(sublist, direction)
+ }
+ item = item.nextSibling;
+ }
+}
+
+function openNode(node, bullet) {
+ node.style.display = "block";
+ bullet.className = "b b_open";
+}
+
+function closeNode(node, bullet) {
+ node.style.display = "none";
+ bullet.className = "b b_closed";
+}
+
+function isClosed(node) {
+ /* XXX we should in fact check our *computed* style, not the display
+ * attribute of the current node, which may be inherited and not
+ * set. However, this really only matters when changing the default
+ * appearance of the tree through a parent style. */
+ return node.style.display == "none";
+}
+
+function toggleDisplay(node, bullet) {
+ if (isClosed(node)) {
+ openNode(node, bullet);
+ return true;
+ }
+
+ closeNode(node, bullet);
+ return false;
+}
+
+function duplicated(element) {
+ var allsumm= document.getElementsByTagName("span");
+ if (highlighted) {
+ for (i = 0;i < allsumm.length; i++) {
+ if (allsumm.item(i).id == highlighted) {
+ allsumm.item(i).className = highlightedclass;
+ }
+ }
+ if (highlighted == element) {
+ highlighted = 0;
+ return;
+ }
+ }
+ highlighted = element;
+ var elem = document.getElementById(element);
+ highlightedclass = elem.className;
+ for (var i = 0;i < allsumm.length; i++) {
+ if (allsumm.item(i).id == element) {
+ allsumm.item(i).className = "summ_h";
+ }
+ }
+}
+
+function duplicatedover(element) {
+ if (!highlighted) {
+ highlightedover = 1;
+ duplicated(element);
+ }
+}
+
+function duplicatedout(element) {
+ if (highlighted == element && highlightedover) {
+ highlightedover = 0;
+ duplicated(element);
+ }
+}
+
diff --git a/js/field.js b/js/field.js
new file mode 100644
index 000000000..56084e7e1
--- /dev/null
+++ b/js/field.js
@@ -0,0 +1,754 @@
+/* The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is the Bugzilla Bug Tracking System.
+ *
+ * The Initial Developer of the Original Code is Everything Solved, Inc.
+ * Portions created by Everything Solved are Copyright (C) 2007 Everything
+ * Solved, Inc. All Rights Reserved.
+ *
+ * Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+ * Guy Pyrzak <guy.pyrzak@gmail.com>
+ * Reed Loden <reed@reedloden.com>
+ */
+
+/* This library assumes that the needed YUI libraries have been loaded
+ already. */
+
+function validateEnterBug(theform) {
+ var component = theform.component;
+ var short_desc = theform.short_desc;
+ var version = theform.version;
+ var bug_status = theform.bug_status;
+ var description = theform.comment;
+ var attach_data = theform.data;
+ var attach_desc = theform.description;
+
+ var current_errors = YAHOO.util.Dom.getElementsByClassName(
+ 'validation_error_text', null, theform);
+ for (var i = 0; i < current_errors.length; i++) {
+ current_errors[i].parentNode.removeChild(current_errors[i]);
+ }
+ var current_error_fields = YAHOO.util.Dom.getElementsByClassName(
+ 'validation_error_field', null, theform);
+ for (var i = 0; i < current_error_fields.length; i++) {
+ var field = current_error_fields[i];
+ YAHOO.util.Dom.removeClass(field, 'validation_error_field');
+ }
+
+ var focus_me;
+
+ // These are checked in the reverse order that they appear on the page,
+ // so that the one closest to the top of the form will be focused.
+ if (attach_data.value && YAHOO.lang.trim(attach_desc.value) == '') {
+ _errorFor(attach_desc, 'attach_desc');
+ focus_me = attach_desc;
+ }
+ var check_description = status_comment_required[bug_status.value];
+ if (check_description && YAHOO.lang.trim(description.value) == '') {
+ _errorFor(description, 'description');
+ focus_me = description;
+ }
+ if (YAHOO.lang.trim(short_desc.value) == '') {
+ _errorFor(short_desc);
+ focus_me = short_desc;
+ }
+ if (version.selectedIndex < 0) {
+ _errorFor(version);
+ focus_me = version;
+ }
+ if (component.selectedIndex < 0) {
+ _errorFor(component);
+ focus_me = component;
+ }
+
+ if (focus_me) {
+ focus_me.focus();
+ return false;
+ }
+
+ return true;
+}
+
+function _errorFor(field, name) {
+ if (!name) name = field.id;
+ var string_name = name + '_required';
+ var error_text = BUGZILLA.string[string_name];
+ var new_node = document.createElement('div');
+ YAHOO.util.Dom.addClass(new_node, 'validation_error_text');
+ new_node.innerHTML = error_text;
+ YAHOO.util.Dom.insertAfter(new_node, field);
+ YAHOO.util.Dom.addClass(field, 'validation_error_field');
+}
+
+function createCalendar(name) {
+ var cal = new YAHOO.widget.Calendar('calendar_' + name,
+ 'con_calendar_' + name);
+ YAHOO.bugzilla['calendar_' + name] = cal;
+ var field = document.getElementById(name);
+ cal.selectEvent.subscribe(setFieldFromCalendar, field, false);
+ updateCalendarFromField(field);
+ cal.render();
+}
+
+/* The onclick handlers for the button that shows the calendar. */
+function showCalendar(field_name) {
+ var calendar = YAHOO.bugzilla["calendar_" + field_name];
+ var field = document.getElementById(field_name);
+ var button = document.getElementById('button_calendar_' + field_name);
+
+ bz_overlayBelow(calendar.oDomContainer, field);
+ calendar.show();
+ button.onclick = function() { hideCalendar(field_name); };
+
+ // Because of the way removeListener works, this has to be a function
+ // attached directly to this calendar.
+ calendar.bz_myBodyCloser = function(event) {
+ var container = this.oDomContainer;
+ var target = YAHOO.util.Event.getTarget(event);
+ if (target != container && target != button
+ && !YAHOO.util.Dom.isAncestor(container, target))
+ {
+ hideCalendar(field_name);
+ }
+ };
+
+ // If somebody clicks outside the calendar, hide it.
+ YAHOO.util.Event.addListener(document.body, 'click',
+ calendar.bz_myBodyCloser, calendar, true);
+
+ // Make Esc close the calendar.
+ calendar.bz_escCal = function (event) {
+ var key = YAHOO.util.Event.getCharCode(event);
+ if (key == 27) {
+ hideCalendar(field_name);
+ }
+ };
+ YAHOO.util.Event.addListener(document.body, 'keydown', calendar.bz_escCal);
+}
+
+function hideCalendar(field_name) {
+ var cal = YAHOO.bugzilla["calendar_" + field_name];
+ cal.hide();
+ var button = document.getElementById('button_calendar_' + field_name);
+ button.onclick = function() { showCalendar(field_name); };
+ YAHOO.util.Event.removeListener(document.body, 'click',
+ cal.bz_myBodyCloser);
+ YAHOO.util.Event.removeListener(document.body, 'keydown', cal.bz_escCal);
+}
+
+/* This is the selectEvent for our Calendar objects on our custom
+ * DateTime fields.
+ */
+function setFieldFromCalendar(type, args, date_field) {
+ var dates = args[0];
+ var setDate = dates[0];
+
+ // We can't just write the date straight into the field, because there
+ // might already be a time there.
+ var timeRe = /\b(\d{1,2}):(\d\d)(?::(\d\d))?/;
+ var currentTime = timeRe.exec(date_field.value);
+ var d = new Date(setDate[0], setDate[1] - 1, setDate[2]);
+ if (currentTime) {
+ d.setHours(currentTime[1], currentTime[2]);
+ if (currentTime[3]) {
+ d.setSeconds(currentTime[3]);
+ }
+ }
+
+ var year = d.getFullYear();
+ // JavaScript's "Date" represents January as 0 and December as 11.
+ var month = d.getMonth() + 1;
+ if (month < 10) month = '0' + String(month);
+ var day = d.getDate();
+ if (day < 10) day = '0' + String(day);
+ var dateStr = year + '-' + month + '-' + day;
+
+ if (currentTime) {
+ var minutes = d.getMinutes();
+ if (minutes < 10) minutes = '0' + String(minutes);
+ var seconds = d.getSeconds();
+ if (seconds > 0 && seconds < 10) {
+ seconds = '0' + String(seconds);
+ }
+
+ dateStr = dateStr + ' ' + d.getHours() + ':' + minutes;
+ if (seconds) dateStr = dateStr + ':' + seconds;
+ }
+
+ date_field.value = dateStr;
+ hideCalendar(date_field.id);
+}
+
+/* Sets the calendar based on the current field value.
+ */
+function updateCalendarFromField(date_field) {
+ var dateRe = /(\d\d\d\d)-(\d\d?)-(\d\d?)/;
+ var pieces = dateRe.exec(date_field.value);
+ if (pieces) {
+ var cal = YAHOO.bugzilla["calendar_" + date_field.id];
+ cal.select(new Date(pieces[1], pieces[2] - 1, pieces[3]));
+ var selectedArray = cal.getSelectedDates();
+ var selected = selectedArray[0];
+ cal.cfg.setProperty("pagedate", (selected.getMonth() + 1) + '/'
+ + selected.getFullYear());
+ cal.render();
+ }
+}
+
+
+/* Hide input fields and show the text with (edit) next to it */
+function hideEditableField( container, input, action, field_id, original_value ) {
+ YAHOO.util.Dom.removeClass(container, 'bz_default_hidden');
+ YAHOO.util.Dom.addClass(input, 'bz_default_hidden');
+ YAHOO.util.Event.addListener(action, 'click', showEditableField,
+ new Array(container, input));
+ if(field_id != ""){
+ YAHOO.util.Event.addListener(window, 'load', checkForChangedFieldValues,
+ new Array(container, input, field_id, original_value));
+ }
+}
+
+/* showEditableField (e, ContainerInputArray)
+ * Function hides the (edit) link and the text and displays the input
+ *
+ * var e: the event
+ * var ContainerInputArray: An array containing the (edit) and text area and the input being displayed
+ * var ContainerInputArray[0]: the conainer that will be hidden usually shows the (edit) text
+ * var ContainerInputArray[1]: the input area and label that will be displayed
+ *
+ */
+function showEditableField (e, ContainerInputArray) {
+ var inputs = new Array();
+ var inputArea = YAHOO.util.Dom.get(ContainerInputArray[1]);
+ if ( ! inputArea ){
+ YAHOO.util.Event.preventDefault(e);
+ return;
+ }
+ YAHOO.util.Dom.addClass(ContainerInputArray[0], 'bz_default_hidden');
+ YAHOO.util.Dom.removeClass(inputArea, 'bz_default_hidden');
+ if ( inputArea.tagName.toLowerCase() == "input" ) {
+ inputs.push(inputArea);
+ } else {
+ inputs = inputArea.getElementsByTagName('input');
+ }
+ if ( inputs.length > 0 ) {
+ // focus on the first field, this makes it easier to edit
+ inputs[0].focus();
+ inputs[0].select();
+ }
+ YAHOO.util.Event.preventDefault(e);
+}
+
+
+/* checkForChangedFieldValues(e, array )
+ * Function checks if after the autocomplete by the browser if the values match the originals.
+ * If they don't match then hide the text and show the input so users don't get confused.
+ *
+ * var e: the event
+ * var ContainerInputArray: An array containing the (edit) and text area and the input being displayed
+ * var ContainerInputArray[0]: the conainer that will be hidden usually shows the (edit) text
+ * var ContainerInputArray[1]: the input area and label that will be displayed
+ * var ContainerInputArray[2]: the field that is on the page, might get changed by browser autocomplete
+ * var ContainerInputArray[3]: the original value from the page loading.
+ *
+ */
+function checkForChangedFieldValues(e, ContainerInputArray ) {
+ var el = document.getElementById(ContainerInputArray[2]);
+ var unhide = false;
+ if ( el ) {
+ if ( el.value != ContainerInputArray[3] ||
+ ( el.value == "" && el.id != "alias") ) {
+ unhide = true;
+ }
+ else {
+ var set_default = document.getElementById("set_default_" +
+ ContainerInputArray[2]);
+ if ( set_default ) {
+ if(set_default.checked){
+ unhide = true;
+ }
+ }
+ }
+ }
+ if(unhide){
+ YAHOO.util.Dom.addClass(ContainerInputArray[0], 'bz_default_hidden');
+ YAHOO.util.Dom.removeClass(ContainerInputArray[1], 'bz_default_hidden');
+ }
+
+}
+
+function hideAliasAndSummary(short_desc_value, alias_value) {
+ // check the short desc field
+ hideEditableField( 'summary_alias_container','summary_alias_input',
+ 'editme_action','short_desc', short_desc_value);
+ // check that the alias hasn't changed
+ var bz_alias_check_array = new Array('summary_alias_container',
+ 'summary_alias_input', 'alias', alias_value);
+ YAHOO.util.Event.addListener( window, 'load', checkForChangedFieldValues,
+ bz_alias_check_array);
+}
+
+function showPeopleOnChange( field_id_list ) {
+ for(var i = 0; i < field_id_list.length; i++) {
+ YAHOO.util.Event.addListener( field_id_list[i],'change', showEditableField,
+ new Array('bz_qa_contact_edit_container',
+ 'bz_qa_contact_input'));
+ YAHOO.util.Event.addListener( field_id_list[i],'change',showEditableField,
+ new Array('bz_assignee_edit_container',
+ 'bz_assignee_input'));
+ }
+}
+
+function assignToDefaultOnChange(field_id_list) {
+ showPeopleOnChange( field_id_list );
+ for(var i = 0; i < field_id_list.length; i++) {
+ YAHOO.util.Event.addListener( field_id_list[i],'change', setDefaultCheckbox,
+ 'set_default_assignee');
+ YAHOO.util.Event.addListener( field_id_list[i],'change',setDefaultCheckbox,
+ 'set_default_qa_contact');
+ }
+}
+
+function initDefaultCheckbox(field_id){
+ YAHOO.util.Event.addListener( 'set_default_' + field_id,'change', boldOnChange,
+ 'set_default_' + field_id);
+ YAHOO.util.Event.addListener( window,'load', checkForChangedFieldValues,
+ new Array( 'bz_' + field_id + '_edit_container',
+ 'bz_' + field_id + '_input',
+ 'set_default_' + field_id ,'1'));
+
+ YAHOO.util.Event.addListener( window, 'load', boldOnChange,
+ 'set_default_' + field_id );
+}
+
+function showHideStatusItems(e, dupArrayInfo) {
+ var el = document.getElementById('bug_status');
+ // finish doing stuff based on the selection.
+ if ( el ) {
+ showDuplicateItem(el);
+
+ // Make sure that fields whose visibility or values are controlled
+ // by "resolution" behave properly when resolution is hidden.
+ var resolution = document.getElementById('resolution');
+ if (resolution && resolution.options[0].value != '') {
+ resolution.bz_lastSelected = resolution.selectedIndex;
+ var emptyOption = new Option('', '');
+ resolution.insertBefore(emptyOption, resolution.options[0]);
+ emptyOption.selected = true;
+ }
+ YAHOO.util.Dom.addClass('resolution_settings', 'bz_default_hidden');
+ if (document.getElementById('resolution_settings_warning')) {
+ YAHOO.util.Dom.addClass('resolution_settings_warning',
+ 'bz_default_hidden');
+ }
+ YAHOO.util.Dom.addClass('duplicate_display', 'bz_default_hidden');
+
+
+ if ( (el.value == dupArrayInfo[1] && dupArrayInfo[0] == "is_duplicate")
+ || bz_isValueInArray(close_status_array, el.value) )
+ {
+ YAHOO.util.Dom.removeClass('resolution_settings',
+ 'bz_default_hidden');
+ YAHOO.util.Dom.removeClass('resolution_settings_warning',
+ 'bz_default_hidden');
+
+ // Remove the blank option we inserted.
+ if (resolution && resolution.options[0].value == '') {
+ resolution.removeChild(resolution.options[0]);
+ resolution.selectedIndex = resolution.bz_lastSelected;
+ }
+ }
+
+ if (resolution) {
+ bz_fireEvent(resolution, 'change');
+ }
+ }
+}
+
+function showDuplicateItem(e) {
+ var resolution = document.getElementById('resolution');
+ var bug_status = document.getElementById('bug_status');
+ var dup_id = document.getElementById('dup_id');
+ if (resolution) {
+ if (resolution.value == 'DUPLICATE' && bz_isValueInArray( close_status_array, bug_status.value) ) {
+ // hide resolution show duplicate
+ YAHOO.util.Dom.removeClass('duplicate_settings',
+ 'bz_default_hidden');
+ YAHOO.util.Dom.addClass('dup_id_discoverable', 'bz_default_hidden');
+ // check to make sure the field is visible or IE throws errors
+ if( ! YAHOO.util.Dom.hasClass( dup_id, 'bz_default_hidden' ) ){
+ dup_id.focus();
+ dup_id.select();
+ }
+ }
+ else {
+ YAHOO.util.Dom.addClass('duplicate_settings', 'bz_default_hidden');
+ YAHOO.util.Dom.removeClass('dup_id_discoverable',
+ 'bz_default_hidden');
+ dup_id.blur();
+ }
+ }
+ YAHOO.util.Event.preventDefault(e); //prevents the hyperlink from going to the url in the href.
+}
+
+function setResolutionToDuplicate(e, duplicate_or_move_bug_status) {
+ var status = document.getElementById('bug_status');
+ var resolution = document.getElementById('resolution');
+ YAHOO.util.Dom.addClass('dup_id_discoverable', 'bz_default_hidden');
+ status.value = duplicate_or_move_bug_status;
+ bz_fireEvent(status, 'change');
+ resolution.value = "DUPLICATE";
+ bz_fireEvent(resolution, 'change');
+ YAHOO.util.Event.preventDefault(e);
+}
+
+function setDefaultCheckbox(e, field_id ) {
+ var el = document.getElementById(field_id);
+ var elLabel = document.getElementById(field_id + "_label");
+ if( el && elLabel ) {
+ el.checked = "true";
+ YAHOO.util.Dom.setStyle(elLabel, 'font-weight', 'bold');
+ }
+}
+
+function boldOnChange(e, field_id){
+ var el = document.getElementById(field_id);
+ var elLabel = document.getElementById(field_id + "_label");
+ if( el && elLabel ) {
+ if( el.checked ){
+ YAHOO.util.Dom.setStyle(elLabel, 'font-weight', 'bold');
+ }
+ else{
+ YAHOO.util.Dom.setStyle(elLabel, 'font-weight', 'normal');
+ }
+ }
+}
+
+function updateCommentTagControl(checkbox, field) {
+ if (checkbox.checked) {
+ YAHOO.util.Dom.addClass(field, 'bz_private');
+ } else {
+ YAHOO.util.Dom.removeClass(field, 'bz_private');
+ }
+}
+
+/**
+ * Reset the value of the classification field and fire an event change
+ * on it. Called when the product changes, in case the classification
+ * field (which is hidden) controls the visibility of any other fields.
+ */
+function setClassification() {
+ var classification = document.getElementById('classification');
+ var product = document.getElementById('product');
+ var selected_product = product.value;
+ var select_classification = all_classifications[selected_product];
+ classification.value = select_classification;
+ bz_fireEvent(classification, 'change');
+}
+
+/**
+ * Says that a field should only be displayed when another field has
+ * a certain value. May only be called after the controller has already
+ * been added to the DOM.
+ */
+function showFieldWhen(controlled_id, controller_id, value) {
+ var controller = document.getElementById(controller_id);
+ // Note that we don't get an object for "controlled" here, because it
+ // might not yet exist in the DOM. We just pass along its id.
+ YAHOO.util.Event.addListener(controller, 'change',
+ handleVisControllerValueChange, [controlled_id, controller, value]);
+}
+
+/**
+ * Called by showFieldWhen when a field's visibility controller
+ * changes values.
+ */
+function handleVisControllerValueChange(e, args) {
+ var controlled_id = args[0];
+ var controller = args[1];
+ var value = args[2];
+
+ var label_container =
+ document.getElementById('field_label_' + controlled_id);
+ var field_container =
+ document.getElementById('field_container_' + controlled_id);
+ if (bz_valueSelected(controller, value)) {
+ YAHOO.util.Dom.removeClass(label_container, 'bz_hidden_field');
+ YAHOO.util.Dom.removeClass(field_container, 'bz_hidden_field');
+ }
+ else {
+ YAHOO.util.Dom.addClass(label_container, 'bz_hidden_field');
+ YAHOO.util.Dom.addClass(field_container, 'bz_hidden_field');
+ }
+}
+
+function showValueWhen(controlled_field_id, controlled_value_ids,
+ controller_field_id, controller_value_id)
+{
+ var controller_field = document.getElementById(controller_field_id);
+ // Note that we don't get an object for the controlled field here,
+ // because it might not yet exist in the DOM. We just pass along its id.
+ YAHOO.util.Event.addListener(controller_field, 'change',
+ handleValControllerChange, [controlled_field_id, controlled_value_ids,
+ controller_field, controller_value_id]);
+}
+
+function handleValControllerChange(e, args) {
+ var controlled_field = document.getElementById(args[0]);
+ var controlled_value_ids = args[1];
+ var controller_field = args[2];
+ var controller_value_id = args[3];
+
+ var controller_item = document.getElementById(
+ _value_id(controller_field.id, controller_value_id));
+
+ for (var i = 0; i < controlled_value_ids.length; i++) {
+ var item = getPossiblyHiddenOption(controlled_field,
+ controlled_value_ids[i]);
+ if (item.disabled && controller_item && controller_item.selected) {
+ item = showOptionInIE(item, controlled_field);
+ YAHOO.util.Dom.removeClass(item, 'bz_hidden_option');
+ item.disabled = false;
+ }
+ else if (!item.disabled) {
+ YAHOO.util.Dom.addClass(item, 'bz_hidden_option');
+ if (item.selected) {
+ item.selected = false;
+ bz_fireEvent(controlled_field, 'change');
+ }
+ item.disabled = true;
+ hideOptionInIE(item, controlled_field);
+ }
+ }
+}
+
+// A convenience function to generate the "id" tag of an <option>
+// based on the numeric id that Bugzilla uses for that value.
+function _value_id(field_name, id) {
+ return 'v' + id + '_' + field_name;
+}
+
+/*********************************/
+/* Code for Hiding Options in IE */
+/*********************************/
+
+/* IE 7 and below (and some other browsers) don't respond to "display: none"
+ * on <option> tags. However, you *can* insert a Comment Node as a
+ * child of a <select> tag. So we just insert a Comment where the <option>
+ * used to be. */
+var ie_hidden_options = new Array();
+function hideOptionInIE(anOption, aSelect) {
+ if (browserCanHideOptions(aSelect)) return;
+
+ var commentNode = document.createComment(anOption.value);
+ commentNode.id = anOption.id;
+ // This keeps the interface of Comments and Options the same for
+ // our other functions.
+ commentNode.disabled = true;
+ // replaceChild is very slow on IE in a <select> that has a lot of
+ // options, so we use replaceNode when we can.
+ if (anOption.replaceNode) {
+ anOption.replaceNode(commentNode);
+ }
+ else {
+ aSelect.replaceChild(commentNode, anOption);
+ }
+
+ // Store the comment node for quick access for getPossiblyHiddenOption
+ if (!ie_hidden_options[aSelect.id]) {
+ ie_hidden_options[aSelect.id] = new Array();
+ }
+ ie_hidden_options[aSelect.id][anOption.id] = commentNode;
+}
+
+function showOptionInIE(aNode, aSelect) {
+ if (browserCanHideOptions(aSelect)) return aNode;
+
+ // We do this crazy thing with innerHTML and createElement because
+ // this is the ONLY WAY that this works properly in IE.
+ var optionNode = document.createElement('option');
+ optionNode.innerHTML = aNode.data;
+ optionNode.value = aNode.data;
+ optionNode.id = aNode.id;
+ // replaceChild is very slow on IE in a <select> that has a lot of
+ // options, so we use replaceNode when we can.
+ if (aNode.replaceNode) {
+ aNode.replaceNode(optionNode);
+ }
+ else {
+ aSelect.replaceChild(optionNode, aNode);
+ }
+ delete ie_hidden_options[aSelect.id][optionNode.id];
+ return optionNode;
+}
+
+function initHidingOptionsForIE(select_name) {
+ var aSelect = document.getElementById(select_name);
+ if (browserCanHideOptions(aSelect)) return;
+
+ for (var i = 0; ;i++) {
+ var item = aSelect.options[i];
+ if (!item) break;
+ if (item.disabled) {
+ hideOptionInIE(item, aSelect);
+ i--; // Hiding an option means that the options array has changed.
+ }
+ }
+}
+
+function getPossiblyHiddenOption(aSelect, optionId) {
+ // Works always for <option> tags, and works for commentNodes
+ // in IE (but not in Webkit).
+ var id = _value_id(aSelect.id, optionId);
+ var val = document.getElementById(id);
+
+ // This is for WebKit and other browsers that can't "display: none"
+ // an <option> and also can't getElementById for a commentNode.
+ if (!val && ie_hidden_options[aSelect.id]) {
+ val = ie_hidden_options[aSelect.id][id];
+ }
+
+ return val;
+}
+
+var browser_can_hide_options;
+function browserCanHideOptions(aSelect) {
+ /* As far as I can tell, browsers that don't hide <option> tags
+ * also never have a X position for <option> tags, even if
+ * they're visible. This is the only reliable way I found to
+ * differentiate browsers. So we create a visible option, see
+ * if it has a position, and then remove it. */
+ if (typeof(browser_can_hide_options) == "undefined") {
+ var new_opt = bz_createOptionInSelect(aSelect, '', '');
+ var opt_pos = YAHOO.util.Dom.getX(new_opt);
+ aSelect.removeChild(new_opt);
+ if (opt_pos) {
+ browser_can_hide_options = true;
+ }
+ else {
+ browser_can_hide_options = false;
+ }
+ }
+ return browser_can_hide_options;
+}
+
+/* (end) option hiding code */
+
+// A convenience function to sanitize raw text for harmful HTML before outputting
+function _escapeHTML(text) {
+ return text.replace(/&/g, '&amp;').
+ replace(/</g, '&lt;').
+ replace(/>/g, '&gt;');
+}
+
+/**
+ * The Autoselect
+ */
+YAHOO.bugzilla.userAutocomplete = {
+ counter : 0,
+ dataSource : null,
+ generateRequest : function ( enteredText ){
+ YAHOO.bugzilla.userAutocomplete.counter =
+ YAHOO.bugzilla.userAutocomplete.counter + 1;
+ YAHOO.util.Connect.setDefaultPostHeader('application/json', true);
+ var json_object = {
+ method : "User.get",
+ id : YAHOO.bugzilla.userAutocomplete.counter,
+ params : [ {
+ match : [ decodeURIComponent(enteredText) ],
+ include_fields : [ "email", "real_name" ]
+ } ]
+ };
+ var stringified = YAHOO.lang.JSON.stringify(json_object);
+ var debug = { msg: "json-rpc obj debug info", "json obj": json_object,
+ "param" : stringified}
+ YAHOO.bugzilla.userAutocomplete.debug_helper( debug );
+ return stringified;
+ },
+ resultListFormat : function(oResultData, enteredText, sResultMatch) {
+ return ( _escapeHTML(oResultData.real_name) + " (" + _escapeHTML(oResultData.email) + ")");
+ },
+ debug_helper : function ( ){
+ /* used to help debug any errors that might happen */
+ if( typeof(console) !== 'undefined' && console != null && arguments.length > 0 ){
+ console.log("debug helper info:", arguments);
+ }
+ return true;
+ },
+ init_ds : function(){
+ this.dataSource = new YAHOO.util.XHRDataSource("jsonrpc.cgi");
+ this.dataSource.connTimeout = 30000;
+ this.dataSource.connMethodPost = true;
+ this.dataSource.connXhrMode = "cancelStaleRequests";
+ this.dataSource.maxCacheEntries = 5;
+ this.dataSource.responseSchema = {
+ resultsList : "result.users",
+ metaFields : { error: "error", jsonRpcId: "id"},
+ fields : [
+ { key : "email" },
+ { key : "real_name"}
+ ]
+ };
+ },
+ init : function( field, container, multiple ) {
+ if( this.dataSource == null ){
+ this.init_ds();
+ }
+ var userAutoComp = new YAHOO.widget.AutoComplete( field, container,
+ this.dataSource );
+ // other stuff we might want to do with the autocomplete goes here
+ userAutoComp.maxResultsDisplayed = BUGZILLA.param.maxusermatches;
+ userAutoComp.generateRequest = this.generateRequest;
+ userAutoComp.formatResult = this.resultListFormat;
+ userAutoComp.doBeforeLoadData = this.debug_helper;
+ userAutoComp.minQueryLength = 3;
+ userAutoComp.autoHighlight = false;
+ // this is a throttle to determine the delay of the query from typing
+ // set this higher to cause fewer calls to the server
+ userAutoComp.queryDelay = 0.05;
+ userAutoComp.useIFrame = true;
+ userAutoComp.resultTypeList = false;
+ if( multiple == true ){
+ userAutoComp.delimChar = [","];
+ }
+
+ }
+};
+
+YAHOO.bugzilla.keywordAutocomplete = {
+ dataSource : null,
+ init_ds : function(){
+ this.dataSource = new YAHOO.util.LocalDataSource( YAHOO.bugzilla.keyword_array );
+ },
+ init : function( field, container ) {
+ if( this.dataSource == null ){
+ this.init_ds();
+ }
+ var keywordAutoComp = new YAHOO.widget.AutoComplete(field, container, this.dataSource);
+ keywordAutoComp.maxResultsDisplayed = YAHOO.bugzilla.keyword_array.length;
+ keywordAutoComp.minQueryLength = 0;
+ keywordAutoComp.useIFrame = true;
+ keywordAutoComp.delimChar = [","," "];
+ keywordAutoComp.resultTypeList = false;
+ keywordAutoComp.queryDelay = 0;
+ /* Causes all the possibilities in the keyword to appear when a user
+ * focuses on the textbox
+ */
+ keywordAutoComp.textboxFocusEvent.subscribe( function(){
+ var sInputValue = YAHOO.util.Dom.get('keywords').value;
+ if( sInputValue.length === 0 ){
+ this.sendQuery(sInputValue);
+ this.collapseContainer();
+ this.expandContainer();
+ }
+ });
+ }
+};
diff --git a/js/flag.js b/js/flag.js
new file mode 100644
index 000000000..64b4ad8a2
--- /dev/null
+++ b/js/flag.js
@@ -0,0 +1,75 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Bugzilla Bug Tracking System.
+ *
+ * The Initial Developer of the Original Code is Netscape Communications
+ * Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Myk Melez <myk@mozilla.org>
+ * Frédéric Buclin <LpSolit@gmail.com>
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// Shows or hides a requestee field depending on whether or not
+// the user is requesting the corresponding flag.
+function toggleRequesteeField(flagField, no_focus)
+{
+ // Convert the ID of the flag field into the ID of its corresponding
+ // requestee field and then use the ID to get the field.
+ var id = flagField.name.replace(/flag(_type)?-(\d+)/, "requestee$1-$2");
+ var requesteeField = document.getElementById(id);
+ if (!requesteeField) return;
+
+ // Show or hide the requestee field based on the value
+ // of the flag field.
+ if (flagField.value == "?") {
+ YAHOO.util.Dom.removeClass(requesteeField.parentNode, 'bz_default_hidden');
+ if (!no_focus) requesteeField.focus();
+ } else
+ YAHOO.util.Dom.addClass(requesteeField.parentNode, 'bz_default_hidden');
+}
+
+// Hides requestee fields when the window is loaded since they shouldn't
+// be enabled until the user requests that flag type.
+function hideRequesteeFields()
+{
+ var inputElements = document.getElementsByTagName("input");
+ var selectElements = document.getElementsByTagName("select");
+ //You cannot update Node lists, so you must create an array to combine the NodeLists
+ var allElements = [];
+ for( var i=0; i < inputElements.length; i++ ) {
+ allElements[allElements.length] = inputElements.item(i);
+ }
+ for( var i=0; i < selectElements.length; i++ ) { //Combine inputs with selects
+ allElements[allElements.length] = selectElements.item(i);
+ }
+ var inputElement, id, flagField;
+ for ( var i=0 ; i<allElements.length ; i++ )
+ {
+ inputElement = allElements[i];
+ if (inputElement.name.search(/^requestee(_type)?-(\d+)$/) != -1)
+ {
+ // Convert the ID of the requestee field into the ID of its corresponding
+ // flag field and then use the ID to get the field.
+ id = inputElement.name.replace(/requestee(_type)?-(\d+)/, "flag$1-$2");
+ flagField = document.getElementById(id);
+ if (flagField && flagField.value != "?")
+ YAHOO.util.Dom.addClass(inputElement.parentNode, 'bz_default_hidden');
+ }
+ }
+}
+YAHOO.util.Event.onDOMReady(hideRequesteeFields);
diff --git a/js/global.js b/js/global.js
new file mode 100644
index 000000000..b62d7b9a7
--- /dev/null
+++ b/js/global.js
@@ -0,0 +1,131 @@
+/* The contents of this file are subject to the Mozilla Public
+* License Version 1.1 (the "License"); you may not use this file
+* except in compliance with the License. You may obtain a copy of
+* the License at http://www.mozilla.org/MPL/
+*
+* Software distributed under the License is distributed on an "AS
+* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+* implied. See the License for the specific language governing
+* rights and limitations under the License.
+*
+* The Original Code is the Bugzilla Bug Tracking System.
+*
+* Contributor(s):
+* Guy Pyrzak <guy.pyrzak@gmail.com>
+* Max Kanat-Alexander <mkanat@bugzilla.org>
+*
+*/
+
+var mini_login_constants;
+
+function show_mini_login_form( suffix ) {
+ var login_link = document.getElementById('login_link' + suffix);
+ var login_form = document.getElementById('mini_login' + suffix);
+ var account_container = document.getElementById('new_account_container'
+ + suffix);
+
+ YAHOO.util.Dom.addClass(login_link, 'bz_default_hidden');
+ YAHOO.util.Dom.removeClass(login_form, 'bz_default_hidden');
+ YAHOO.util.Dom.addClass(account_container, 'bz_default_hidden');
+ return false;
+}
+
+function hide_mini_login_form( suffix ) {
+ var login_link = document.getElementById('login_link' + suffix);
+ var login_form = document.getElementById('mini_login' + suffix);
+ var account_container = document.getElementById('new_account_container'
+ + suffix);
+
+ YAHOO.util.Dom.removeClass(login_link, 'bz_default_hidden');
+ YAHOO.util.Dom.addClass(login_form, 'bz_default_hidden');
+ YAHOO.util.Dom.removeClass(account_container, 'bz_default_hidden');
+ return false;
+}
+
+function show_forgot_form( suffix ) {
+ var forgot_link = document.getElementById('forgot_link' + suffix);
+ var forgot_form = document.getElementById('forgot_form' + suffix);
+ var login_container = document.getElementById('mini_login_container'
+ + suffix);
+ YAHOO.util.Dom.addClass(forgot_link, 'bz_default_hidden');
+ YAHOO.util.Dom.removeClass(forgot_form, 'bz_default_hidden');
+ YAHOO.util.Dom.addClass(login_container, 'bz_default_hidden');
+ return false;
+}
+
+function hide_forgot_form( suffix ) {
+ var forgot_link = document.getElementById('forgot_link' + suffix);
+ var forgot_form = document.getElementById('forgot_form' + suffix);
+ var login_container = document.getElementById('mini_login_container'
+ + suffix);
+ YAHOO.util.Dom.removeClass(forgot_link, 'bz_default_hidden');
+ YAHOO.util.Dom.addClass(forgot_form, 'bz_default_hidden');
+ YAHOO.util.Dom.removeClass(login_container, 'bz_default_hidden');
+ return false;
+}
+
+function init_mini_login_form( suffix ) {
+ var mini_login = document.getElementById('Bugzilla_login' + suffix );
+ var mini_password = document.getElementById('Bugzilla_password' + suffix );
+ var mini_dummy = document.getElementById(
+ 'Bugzilla_password_dummy' + suffix);
+ // If the login and password are blank when the page loads, we display
+ // "login" and "password" in the boxes by default.
+ if (mini_login.value == "" && mini_password.value == "") {
+ mini_login.value = mini_login_constants.login;
+ YAHOO.util.Dom.addClass(mini_login, "bz_mini_login_help");
+ YAHOO.util.Dom.addClass(mini_password, 'bz_default_hidden');
+ YAHOO.util.Dom.removeClass(mini_dummy, 'bz_default_hidden');
+ }
+ else {
+ show_mini_login_form(suffix);
+ }
+}
+
+// Clear the words "login" and "password" from the form when you click
+// in one of the boxes. We clear them both when you click in either box
+// so that the browser's password-autocomplete can work.
+function mini_login_on_focus( suffix ) {
+ var mini_login = document.getElementById('Bugzilla_login' + suffix );
+ var mini_password = document.getElementById('Bugzilla_password' + suffix );
+ var mini_dummy = document.getElementById(
+ 'Bugzilla_password_dummy' + suffix);
+
+ YAHOO.util.Dom.removeClass(mini_login, "bz_mini_login_help");
+ if (mini_login.value == mini_login_constants.login) {
+ mini_login.value = '';
+ }
+ YAHOO.util.Dom.removeClass(mini_password, 'bz_default_hidden');
+ YAHOO.util.Dom.addClass(mini_dummy, 'bz_default_hidden');
+}
+
+function check_mini_login_fields( suffix ) {
+ var mini_login = document.getElementById('Bugzilla_login' + suffix );
+ var mini_password = document.getElementById('Bugzilla_password' + suffix );
+ if( (mini_login.value != "" && mini_password.value != "")
+ && mini_login.value != mini_login_constants.login )
+ {
+ return true;
+ }
+ window.alert( mini_login_constants.warning );
+ return false;
+}
+
+function set_language( value ) {
+ YAHOO.util.Cookie.set('LANG', value,
+ {
+ expires: new Date('January 1, 2038'),
+ path: BUGZILLA.param.cookie_path
+ });
+ window.location.reload()
+}
+
+// This basically duplicates Bugzilla::Util::display_value for code that
+// can't go through the template and has to be in JS.
+function display_value(field, value) {
+ var field_trans = BUGZILLA.value_descs[field];
+ if (!field_trans) return value;
+ var translated = field_trans[value];
+ if (translated) return translated;
+ return value;
+}
diff --git a/js/params.js b/js/params.js
new file mode 100644
index 000000000..453740799
--- /dev/null
+++ b/js/params.js
@@ -0,0 +1,61 @@
+/* The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is the Bugzilla Bug Tracking System.
+ *
+ * The Initial Developer of the Original Code is Marc Schumann.
+ * Portions created by Marc Schumann are Copyright (c) 2007 Marc Schumann.
+ * All rights reserved.
+ *
+ * Contributor(s): Marc Schumann <wurblzap@gmail.com>
+ */
+
+function sortedList_moveItem(paramName, direction, separator) {
+ var select = document.getElementById('select_' + paramName);
+ var inputField = document.getElementById('input_' + paramName);
+ var currentIndex = select.selectedIndex;
+ var newIndex = currentIndex + direction;
+ var optionCurrentIndex;
+ var optionNewIndex;
+
+ /* Return if no selection */
+ if (currentIndex < 0) return;
+ /* Return if trying to move upward out of list */
+ if (newIndex < 0) return;
+ /* Return if trying to move downward out of list */
+ if (newIndex >= select.length) return;
+
+ /* Move selection */
+ optionNewIndex = select.options[newIndex];
+ optionCurrentIndex = select.options[currentIndex];
+ /* Because some browsers don't accept the same option object twice in a
+ * selection list, we need to put a blank option here first */
+ select.options[newIndex] = new Option();
+ select.options[currentIndex] = optionNewIndex;
+ select.options[newIndex] = optionCurrentIndex;
+ select.selectedIndex = newIndex;
+ populateInputField(select, inputField, separator);
+}
+
+function populateInputField(select, inputField, separator) {
+ var i;
+ var stringRepresentation = '';
+
+ for (i = 0; i < select.length; i++) {
+ if (select.options[i].value == separator) {
+ break;
+ }
+ if (stringRepresentation != '') {
+ stringRepresentation += ',';
+ }
+ stringRepresentation += select.options[i].value;
+ }
+ inputField.value = stringRepresentation;
+}
diff --git a/js/productform.js b/js/productform.js
new file mode 100644
index 000000000..f9b420c31
--- /dev/null
+++ b/js/productform.js
@@ -0,0 +1,408 @@
+/* The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is the Bugzilla Bug Tracking System.
+ *
+ * The Initial Developer of the Original Code is Netscape Communications
+ * Corporation. Portions created by Netscape are
+ * Copyright (C) 1998 Netscape Communications Corporation. All
+ * Rights Reserved.
+ *
+ * Contributor(s): Christian Reis <kiko@async.com.br>
+ */
+
+// Functions to update form select elements based on a
+// collection of javascript arrays containing strings.
+
+/**
+ * Reads the selected classifications and updates product, component,
+ * version and milestone lists accordingly.
+ *
+ * @param classfield Select element that contains classifications.
+ * @param product Select element that contains products.
+ * @param component Select element that contains components. Can be null if
+ * there is no such element to update.
+ * @param version Select element that contains versions. Can be null if
+ * there is no such element to update.
+ * @param milestone Select element that contains milestones. Can be null if
+ * there is no such element to update.
+ *
+ * @global prods Array of products indexed by classification name.
+ * @global first_load Boolean; true if this is the first time this page loads
+ * or false if not.
+ * @global last_sel Array that contains last list of products so we know what
+ * has changed, and optimize for additions.
+ */
+function selectClassification(classfield, product, component, version, milestone) {
+ // This is to avoid handling events that occur before the form
+ // itself is ready, which could happen in buggy browsers.
+ if (!classfield)
+ return;
+
+ // If this is the first load and nothing is selected, no need to
+ // merge and sort all lists; they are created sorted.
+ if ((first_load) && (classfield.selectedIndex == -1)) {
+ first_load = false;
+ return;
+ }
+
+ // Don't reset first_load as done in selectProduct. That's because we
+ // want selectProduct to handle the first_load attribute.
+
+ // Stores classifications that are selected.
+ var sel = Array();
+
+ // True if sel array has a full list or false if sel contains only
+ // new classifications that are to be merged to the current list.
+ var merging = false;
+
+ // If nothing selected, pick all.
+ var findall = classfield.selectedIndex == -1;
+ sel = get_selection(classfield, findall, false);
+ if (!findall) {
+ // Save sel for the next invocation of selectClassification().
+ var tmp = sel;
+
+ // This is an optimization: if we have just added classifications to an
+ // existing selection, no need to clear the form elements and add
+ // everything again; just merge the new ones with the existing
+ // options.
+ if ((last_sel.length > 0) && (last_sel.length < sel.length)) {
+ sel = fake_diff_array(sel, last_sel);
+ merging = true;
+ }
+ last_sel = tmp;
+ }
+
+ // Save original options selected.
+ var saved_prods = get_selection(product, false, true, null);
+
+ // Do the actual fill/update, reselect originally selected options.
+ updateSelect(prods, sel, product, merging, null);
+ restoreSelection(product, saved_prods);
+ selectProduct(product, component, version, milestone, null);
+}
+
+/**
+ * Reads the selected products and updates component, version and milestone
+ * lists accordingly.
+ *
+ * @param product Select element that contains products.
+ * @param component Select element that contains components. Can be null if
+ * there is no such element to update.
+ * @param version Select element that contains versions. Can be null if
+ * there is no such element to update.
+ * @param milestone Select element that contains milestones. Can be null if
+ * there is no such element to update.
+ * @param anyval Value to use for a special "Any" list item. Can be null
+ * to not use any. If used must and will be first item in
+ * the select element.
+ *
+ * @global cpts Array of arrays, indexed by product name. The subarrays
+ * contain a list of components to be fed to the respective
+ * select element.
+ * @global vers Array of arrays, indexed by product name. The subarrays
+ * contain a list of versions to be fed to the respective
+ * select element.
+ * @global tms Array of arrays, indexed by product name. The subarrays
+ * contain a list of milestones to be fed to the respective
+ * select element.
+ * @global first_load Boolean; true if this is the first time this page loads
+ * or false if not.
+ * @global last_sel Array that contains last list of products so we know what
+ * has changed, and optimize for additions.
+ */
+function selectProduct(product, component, version, milestone, anyval) {
+ // This is to avoid handling events that occur before the form
+ // itself is ready, which could happen in buggy browsers.
+ if (!product)
+ return;
+
+ // Do nothing if no products are defined. This is to avoid the
+ // "a has no properties" error from merge_arrays function.
+ if (product.length == (anyval != null ? 1 : 0))
+ return;
+
+ // If this is the first load and nothing is selected, no need to
+ // merge and sort all lists; they are created sorted.
+ if ((first_load) && (product.selectedIndex == -1)) {
+ first_load = false;
+ return;
+ }
+
+ // Turn first_load off. This is tricky, since it seems to be
+ // redundant with the above clause. It's not: if when we first load
+ // the page there is _one_ element selected, it won't fall into that
+ // clause, and first_load will remain 1. Then, if we unselect that
+ // item, selectProduct will be called but the clause will be valid
+ // (since selectedIndex == -1), and we will return - incorrectly -
+ // without merge/sorting.
+ first_load = false;
+
+ // Stores products that are selected.
+ var sel = Array();
+
+ // True if sel array has a full list or false if sel contains only
+ // new products that are to be merged to the current list.
+ var merging = false;
+
+ // If nothing is selected, or the special "Any" option is selected
+ // which represents all products, then pick all products so we show
+ // all components.
+ var findall = (product.selectedIndex == -1
+ || (anyval != null && product.options[0].selected));
+
+ if (useclassification) {
+ // Update index based on the complete product array.
+ sel = get_selection(product, findall, true, anyval);
+ for (var i=0; i<sel.length; i++)
+ sel[i] = prods[sel[i]];
+ }
+ else {
+ sel = get_selection(product, findall, false, anyval);
+ }
+ if (!findall) {
+ // Save sel for the next invocation of selectProduct().
+ var tmp = sel;
+
+ // This is an optimization: if we have just added products to an
+ // existing selection, no need to clear the form controls and add
+ // everybody again; just merge the new ones with the existing
+ // options.
+ if ((last_sel.length > 0) && (last_sel.length < sel.length)) {
+ sel = fake_diff_array(sel, last_sel);
+ merging = true;
+ }
+ last_sel = tmp;
+ }
+
+ // Do the actual fill/update.
+ if (component) {
+ var saved_cpts = get_selection(component, false, true, null);
+ updateSelect(cpts, sel, component, merging, anyval);
+ restoreSelection(component, saved_cpts);
+ }
+
+ if (version) {
+ var saved_vers = get_selection(version, false, true, null);
+ updateSelect(vers, sel, version, merging, anyval);
+ restoreSelection(version, saved_vers);
+ }
+
+ if (milestone) {
+ var saved_tms = get_selection(milestone, false, true, null);
+ updateSelect(tms, sel, milestone, merging, anyval);
+ restoreSelection(milestone, saved_tms);
+ }
+}
+
+/**
+ * Adds to the target select element all elements from array that
+ * correspond to the selected items.
+ *
+ * @param array An array of arrays, indexed by number. The array should
+ * contain elements for each selection.
+ * @param sel A list of selected items, either whole or a diff depending
+ * on merging parameter.
+ * @param target Select element that is to be updated.
+ * @param merging Boolean that determines if we are merging in a diff or
+ * substituting the whole selection. A diff is used to optimize
+ * adding selections.
+ * @param anyval Name of special "Any" value to add. Can be null if not used.
+ * @return Boolean; true if target contains options or false if target
+ * is empty.
+ *
+ * Example (compsel is a select form element):
+ *
+ * var components = Array();
+ * components[1] = [ 'ComponentA', 'ComponentB' ];
+ * components[2] = [ 'ComponentC', 'ComponentD' ];
+ * source = [ 2 ];
+ * updateSelect(components, source, compsel, false, null);
+ *
+ * This would clear compsel and add 'ComponentC' and 'ComponentD' to it.
+ */
+function updateSelect(array, sel, target, merging, anyval) {
+ var i, item;
+
+ // If we have no versions/components/milestones.
+ if (array.length < 1) {
+ target.options.length = 0;
+ return false;
+ }
+
+ if (merging) {
+ // Array merging/sorting in the case of multiple selections
+ // merge in the current options with the first selection.
+ item = merge_arrays(array[sel[0]], target.options, 1);
+
+ // Merge the rest of the selection with the results.
+ for (i = 1 ; i < sel.length ; i++)
+ item = merge_arrays(array[sel[i]], item, 0);
+ }
+ else if (sel.length > 1) {
+ // Here we micro-optimize for two arrays to avoid merging with a
+ // null array.
+ item = merge_arrays(array[sel[0]],array[sel[1]], 0);
+
+ // Merge the arrays. Not very good for multiple selections.
+ for (i = 2; i < sel.length; i++)
+ item = merge_arrays(item, array[sel[i]], 0);
+ }
+ else {
+ // Single item in selection, just get me the list.
+ item = array[sel[0]];
+ }
+
+ // Clear current selection.
+ target.options.length = 0;
+
+ // Add special "Any" value back to the list.
+ if (anyval != null)
+ target.options[0] = new Option(anyval, "");
+
+ // Load elements of list into select element.
+ for (i = 0; i < item.length; i++)
+ target.options[target.options.length] = new Option(item[i], item[i]);
+
+ return true;
+}
+
+/**
+ * Selects items in select element that are defined to be selected.
+ *
+ * @param control Select element of which selected options are to be restored.
+ * @param selnames Array of option names to select.
+ */
+function restoreSelection(control, selnames) {
+ // Right. This sucks but I see no way to avoid going through the
+ // list and comparing to the contents of the control.
+ for (var j = 0; j < selnames.length; j++)
+ for (var i = 0; i < control.options.length; i++)
+ if (control.options[i].value == selnames[j])
+ control.options[i].selected = true;
+}
+
+/**
+ * Returns elements in a that are not in b.
+ * NOT A REAL DIFF: does not check the reverse.
+ *
+ * @param a First array to compare.
+ * @param b Second array to compare.
+ * @return Array of elements in a but not in b.
+ */
+function fake_diff_array(a, b) {
+ var newsel = new Array();
+ var found = false;
+
+ // Do a boring array diff to see who's new.
+ for (var ia in a) {
+ for (var ib in b)
+ if (a[ia] == b[ib])
+ found = true;
+
+ if (!found)
+ newsel[newsel.length] = a[ia];
+
+ found = false;
+ }
+
+ return newsel;
+}
+
+/**
+ * Takes two arrays and sorts them by string, returning a new, sorted
+ * array. The merge removes dupes, too.
+ *
+ * @param a First array to merge.
+ * @param b Second array or an optionitem element to merge.
+ * @param b_is_select Boolean; true if b is an optionitem element (need to
+ * access its value by item.value) or false if b is a
+ * an array.
+ * @return Merged and sorted array.
+ */
+function merge_arrays(a, b, b_is_select) {
+ var pos_a = 0;
+ var pos_b = 0;
+ var ret = new Array();
+ var bitem, aitem;
+
+ // Iterate through both arrays and add the larger item to the return
+ // list. Remove dupes, too. Use toLowerCase to provide
+ // case-insensitivity.
+ while ((pos_a < a.length) && (pos_b < b.length)) {
+ aitem = a[pos_a];
+ if (b_is_select)
+ bitem = b[pos_b].value;
+ else
+ bitem = b[pos_b];
+
+ // Smaller item in list a.
+ if (aitem.toLowerCase() < bitem.toLowerCase()) {
+ ret[ret.length] = aitem;
+ pos_a++;
+ }
+ else {
+ // Smaller item in list b.
+ if (aitem.toLowerCase() > bitem.toLowerCase()) {
+ ret[ret.length] = bitem;
+ pos_b++;
+ }
+ else {
+ // List contents are equal, include both counters.
+ ret[ret.length] = aitem;
+ pos_a++;
+ pos_b++;
+ }
+ }
+ }
+
+ // Catch leftovers here. These sections are ugly code-copying.
+ if (pos_a < a.length)
+ for (; pos_a < a.length ; pos_a++)
+ ret[ret.length] = a[pos_a];
+
+ if (pos_b < b.length) {
+ for (; pos_b < b.length; pos_b++) {
+ if (b_is_select)
+ bitem = b[pos_b].value;
+ else
+ bitem = b[pos_b];
+ ret[ret.length] = bitem;
+ }
+ }
+
+ return ret;
+}
+
+/**
+ * Returns an array of indexes or values of options in a select form element.
+ *
+ * @param control Select form element from which to find selections.
+ * @param findall Boolean; true to return all options or false to return
+ * only selected options.
+ * @param want_values Boolean; true to return values and false to return
+ * indexes.
+ * @param anyval Name of a special "Any" value that should be skipped. Can
+ * be null if not used.
+ * @return Array of all or selected indexes or values.
+ */
+function get_selection(control, findall, want_values, anyval) {
+ var ret = new Array();
+
+ if ((!findall) && (control.selectedIndex == -1))
+ return ret;
+
+ for (var i = (anyval != null ? 1 : 0); i < control.length; i++)
+ if (findall || control.options[i].selected)
+ ret[ret.length] = want_values ? control.options[i].value : i;
+
+ return ret;
+}
diff --git a/js/util.js b/js/util.js
new file mode 100644
index 000000000..666f2666b
--- /dev/null
+++ b/js/util.js
@@ -0,0 +1,273 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Cross Platform JavaScript Utility Library.
+ *
+ * The Initial Developer of the Original Code is
+ * Everything Solved.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Max Kanat-Alexander <mkanat@bugzilla.org>
+ * Christopher A. Aillon <christopher@aillon.com>
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/**
+ * Locate where an element is on the page, x-wise.
+ *
+ * @param obj Element of which location to return.
+ * @return Current position of the element relative to the left of the
+ * page window. Measured in pixels.
+ */
+function bz_findPosX(obj)
+{
+ var curleft = 0;
+
+ if (obj.offsetParent) {
+ while (obj) {
+ curleft += obj.offsetLeft;
+ obj = obj.offsetParent;
+ }
+ }
+ else if (obj.x) {
+ curleft += obj.x;
+ }
+
+ return curleft;
+}
+
+/**
+ * Locate where an element is on the page, y-wise.
+ *
+ * @param obj Element of which location to return.
+ * @return Current position of the element relative to the top of the
+ * page window. Measured in pixels.
+ */
+function bz_findPosY(obj)
+{
+ var curtop = 0;
+
+ if (obj.offsetParent) {
+ while (obj) {
+ curtop += obj.offsetTop;
+ obj = obj.offsetParent;
+ }
+ }
+ else if (obj.y) {
+ curtop += obj.y;
+ }
+
+ return curtop;
+}
+
+/**
+ * Get the full height of an element, even if it's larger than the browser
+ * window.
+ *
+ * @param fromObj Element of which height to return.
+ * @return Current height of the element. Measured in pixels.
+ */
+function bz_getFullHeight(fromObj)
+{
+ var scrollY;
+
+ // All but Mac IE
+ if (fromObj.scrollHeight > fromObj.offsetHeight) {
+ scrollY = fromObj.scrollHeight;
+ // Mac IE
+ } else {
+ scrollY = fromObj.offsetHeight;
+ }
+
+ return scrollY;
+}
+
+/**
+ * Get the full width of an element, even if it's larger than the browser
+ * window.
+ *
+ * @param fromObj Element of which width to return.
+ * @return Current width of the element. Measured in pixels.
+ */
+function bz_getFullWidth(fromObj)
+{
+ var scrollX;
+
+ // All but Mac IE
+ if (fromObj.scrollWidth > fromObj.offsetWidth) {
+ scrollX = fromObj.scrollWidth;
+ // Mac IE
+ } else {
+ scrollX = fromObj.offsetWidth;
+ }
+
+ return scrollX;
+}
+
+/**
+ * Causes a block to appear directly underneath another block,
+ * overlaying anything below it.
+ *
+ * @param item The block that you want to move.
+ * @param parent The block that it goes on top of.
+ * @return nothing
+ */
+function bz_overlayBelow(item, parent) {
+ var elemY = bz_findPosY(parent);
+ var elemX = bz_findPosX(parent);
+ var elemH = parent.offsetHeight;
+
+ item.style.position = 'absolute';
+ item.style.left = elemX + "px";
+ item.style.top = elemY + elemH + 1 + "px";
+}
+
+/**
+ * Checks if a specified value is in the specified array.
+ *
+ * @param aArray Array to search for the value.
+ * @param aValue Value to search from the array.
+ * @return Boolean; true if value is found in the array and false if not.
+ */
+function bz_isValueInArray(aArray, aValue)
+{
+ var run = 0;
+ var len = aArray.length;
+
+ for ( ; run < len; run++) {
+ if (aArray[run] == aValue) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Create wanted options in a select form control.
+ *
+ * @param aSelect Select form control to manipulate.
+ * @param aValue Value attribute of the new option element.
+ * @param aTextValue Value of a text node appended to the new option
+ * element.
+ * @return Created option element.
+ */
+function bz_createOptionInSelect(aSelect, aTextValue, aValue) {
+ var myOption = new Option(aTextValue, aValue);
+ aSelect.options[aSelect.length] = myOption;
+ return myOption;
+}
+
+/**
+ * Clears all options from a select form control.
+ *
+ * @param aSelect Select form control of which options to clear.
+ */
+function bz_clearOptions(aSelect) {
+
+ var length = aSelect.options.length;
+
+ for (var i = 0; i < length; i++) {
+ aSelect.removeChild(aSelect.options[0]);
+ }
+}
+
+/**
+ * Takes an array and moves all the values to an select.
+ *
+ * @param aSelect Select form control to populate. Will be cleared
+ * before array values are created in it.
+ * @param aArray Array with values to populate select with.
+ */
+function bz_populateSelectFromArray(aSelect, aArray) {
+ // Clear the field
+ bz_clearOptions(aSelect);
+
+ for (var i = 0; i < aArray.length; i++) {
+ var item = aArray[i];
+ bz_createOptionInSelect(aSelect, item[1], item[0]);
+ }
+}
+
+/**
+ * Tells you whether or not a particular value is selected in a select,
+ * whether it's a multi-select or a single-select. The check is
+ * case-sensitive.
+ *
+ * @param aSelect The select you're checking.
+ * @param aValue The value that you want to know about.
+ */
+function bz_valueSelected(aSelect, aValue) {
+ var options = aSelect.options;
+ for (var i = 0; i < options.length; i++) {
+ if (options[i].selected && options[i].value == aValue) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Tells you where (what index) in a <select> a particular option is.
+ * Returns -1 if the value is not in the <select>
+ *
+ * @param aSelect The select you're checking.
+ * @param aValue The value you want to know the index of.
+ */
+function bz_optionIndex(aSelect, aValue) {
+ for (var i = 0; i < aSelect.options.length; i++) {
+ if (aSelect.options[i].value == aValue) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+/**
+ * Used to fire an event programmatically.
+ *
+ * @param anElement The element you want to fire the event of.
+ * @param anEvent The name of the event you want to fire,
+ * without the word "on" in front of it.
+ */
+function bz_fireEvent(anElement, anEvent) {
+ // IE
+ if (document.createEventObject) {
+ var evt = document.createEventObject();
+ return anElement.fireEvent('on' + anEvent, evt);
+ }
+ // Firefox, etc.
+ var evt = document.createEvent("HTMLEvents");
+ evt.initEvent(anEvent, true, true); // event type, bubbling, cancelable
+ return !anElement.dispatchEvent(evt);
+}
+
+/**
+ * Adds a CSS class to an element if it doesn't have it. Removes the
+ * CSS class from the element if the element does have the class.
+ *
+ * Requires YUI's Dom library.
+ *
+ * @param anElement The element to toggle the class on
+ * @param aClass The name of the CSS class to toggle.
+ */
+function bz_toggleClass(anElement, aClass) {
+ if (YAHOO.util.Dom.hasClass(anElement, aClass)) {
+ YAHOO.util.Dom.removeClass(anElement, aClass);
+ }
+ else {
+ YAHOO.util.Dom.addClass(anElement, aClass);
+ }
+}
diff --git a/js/yui/animation/animation-min.js b/js/yui/animation/animation-min.js
new file mode 100644
index 000000000..08b471846
--- /dev/null
+++ b/js/yui/animation/animation-min.js
@@ -0,0 +1,23 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+(function(){var B=YAHOO.util;var A=function(D,C,E,F){if(!D){}this.init(D,C,E,F);};A.NAME="Anim";A.prototype={toString:function(){var C=this.getEl()||{};var D=C.id||C.tagName;return(this.constructor.NAME+": "+D);},patterns:{noNegatives:/width|height|opacity|padding/i,offsetAttribute:/^((width|height)|(top|left))$/,defaultUnit:/width|height|top$|bottom$|left$|right$/i,offsetUnit:/\d+(em|%|en|ex|pt|in|cm|mm|pc)$/i},doMethod:function(C,E,D){return this.method(this.currentFrame,E,D-E,this.totalFrames);},setAttribute:function(C,F,E){var D=this.getEl();if(this.patterns.noNegatives.test(C)){F=(F>0)?F:0;}if(C in D&&!("style" in D&&C in D.style)){D[C]=F;}else{B.Dom.setStyle(D,C,F+E);}},getAttribute:function(C){var E=this.getEl();var G=B.Dom.getStyle(E,C);if(G!=="auto"&&!this.patterns.offsetUnit.test(G)){return parseFloat(G);}var D=this.patterns.offsetAttribute.exec(C)||[];var H=!!(D[3]);var F=!!(D[2]);if("style" in E){if(F||(B.Dom.getStyle(E,"position")=="absolute"&&H)){G=E["offset"+D[0].charAt(0).toUpperCase()+D[0].substr(1)];}else{G=0;}}else{if(C in E){G=E[C];}}return G;},getDefaultUnit:function(C){if(this.patterns.defaultUnit.test(C)){return"px";}return"";},setRuntimeAttribute:function(D){var I;var E;var F=this.attributes;this.runtimeAttributes[D]={};var H=function(J){return(typeof J!=="undefined");};if(!H(F[D]["to"])&&!H(F[D]["by"])){return false;}I=(H(F[D]["from"]))?F[D]["from"]:this.getAttribute(D);if(H(F[D]["to"])){E=F[D]["to"];}else{if(H(F[D]["by"])){if(I.constructor==Array){E=[];for(var G=0,C=I.length;G<C;++G){E[G]=I[G]+F[D]["by"][G]*1;}}else{E=I+F[D]["by"]*1;}}}this.runtimeAttributes[D].start=I;this.runtimeAttributes[D].end=E;this.runtimeAttributes[D].unit=(H(F[D].unit))?F[D]["unit"]:this.getDefaultUnit(D);return true;},init:function(E,J,I,C){var D=false;var F=null;var H=0;E=B.Dom.get(E);this.attributes=J||{};this.duration=!YAHOO.lang.isUndefined(I)?I:1;this.method=C||B.Easing.easeNone;this.useSeconds=true;this.currentFrame=0;this.totalFrames=B.AnimMgr.fps;this.setEl=function(M){E=B.Dom.get(M);};this.getEl=function(){return E;};this.isAnimated=function(){return D;};this.getStartTime=function(){return F;};this.runtimeAttributes={};this.animate=function(){if(this.isAnimated()){return false;}this.currentFrame=0;this.totalFrames=(this.useSeconds)?Math.ceil(B.AnimMgr.fps*this.duration):this.duration;if(this.duration===0&&this.useSeconds){this.totalFrames=1;}B.AnimMgr.registerElement(this);return true;};this.stop=function(M){if(!this.isAnimated()){return false;}if(M){this.currentFrame=this.totalFrames;this._onTween.fire();}B.AnimMgr.stop(this);};var L=function(){this.onStart.fire();this.runtimeAttributes={};for(var M in this.attributes){this.setRuntimeAttribute(M);}D=true;H=0;F=new Date();};var K=function(){var O={duration:new Date()-this.getStartTime(),currentFrame:this.currentFrame};O.toString=function(){return("duration: "+O.duration+", currentFrame: "+O.currentFrame);};this.onTween.fire(O);var N=this.runtimeAttributes;for(var M in N){this.setAttribute(M,this.doMethod(M,N[M].start,N[M].end),N[M].unit);}H+=1;};var G=function(){var M=(new Date()-F)/1000;var N={duration:M,frames:H,fps:H/M};N.toString=function(){return("duration: "+N.duration+", frames: "+N.frames+", fps: "+N.fps);};D=false;H=0;this.onComplete.fire(N);};this._onStart=new B.CustomEvent("_start",this,true);this.onStart=new B.CustomEvent("start",this);this.onTween=new B.CustomEvent("tween",this);this._onTween=new B.CustomEvent("_tween",this,true);this.onComplete=new B.CustomEvent("complete",this);this._onComplete=new B.CustomEvent("_complete",this,true);this._onStart.subscribe(L);this._onTween.subscribe(K);this._onComplete.subscribe(G);}};B.Anim=A;})();YAHOO.util.AnimMgr=new function(){var C=null;var B=[];var A=0;this.fps=1000;this.delay=1;this.registerElement=function(F){B[B.length]=F;A+=1;F._onStart.fire();this.start();};this.unRegister=function(G,F){F=F||E(G);if(!G.isAnimated()||F===-1){return false;}G._onComplete.fire();B.splice(F,1);A-=1;if(A<=0){this.stop();}return true;};this.start=function(){if(C===null){C=setInterval(this.run,this.delay);}};this.stop=function(H){if(!H){clearInterval(C);for(var G=0,F=B.length;G<F;++G){this.unRegister(B[0],0);}B=[];C=null;A=0;}else{this.unRegister(H);}};this.run=function(){for(var H=0,F=B.length;H<F;++H){var G=B[H];if(!G||!G.isAnimated()){continue;}if(G.currentFrame<G.totalFrames||G.totalFrames===null){G.currentFrame+=1;if(G.useSeconds){D(G);}G._onTween.fire();}else{YAHOO.util.AnimMgr.stop(G,H);}}};var E=function(H){for(var G=0,F=B.length;G<F;++G){if(B[G]===H){return G;}}return -1;};var D=function(G){var J=G.totalFrames;var I=G.currentFrame;var H=(G.currentFrame*G.duration*1000/G.totalFrames);var F=(new Date()-G.getStartTime());var K=0;if(F<G.duration*1000){K=Math.round((F/H-1)*G.currentFrame);}else{K=J-(I+1);}if(K>0&&isFinite(K)){if(G.currentFrame+K>=J){K=J-(I+1);}G.currentFrame+=K;}};this._queue=B;this._getIndex=E;};YAHOO.util.Bezier=new function(){this.getPosition=function(E,D){var F=E.length;var C=[];for(var B=0;B<F;++B){C[B]=[E[B][0],E[B][1]];}for(var A=1;A<F;++A){for(B=0;B<F-A;++B){C[B][0]=(1-D)*C[B][0]+D*C[parseInt(B+1,10)][0];C[B][1]=(1-D)*C[B][1]+D*C[parseInt(B+1,10)][1];}}return[C[0][0],C[0][1]];};};(function(){var A=function(F,E,G,H){A.superclass.constructor.call(this,F,E,G,H);};A.NAME="ColorAnim";A.DEFAULT_BGCOLOR="#fff";var C=YAHOO.util;YAHOO.extend(A,C.Anim);var D=A.superclass;var B=A.prototype;B.patterns.color=/color$/i;B.patterns.rgb=/^rgb\(([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\)$/i;B.patterns.hex=/^#?([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/i;B.patterns.hex3=/^#?([0-9A-F]{1})([0-9A-F]{1})([0-9A-F]{1})$/i;B.patterns.transparent=/^transparent|rgba\(0, 0, 0, 0\)$/;B.parseColor=function(E){if(E.length==3){return E;}var F=this.patterns.hex.exec(E);if(F&&F.length==4){return[parseInt(F[1],16),parseInt(F[2],16),parseInt(F[3],16)];}F=this.patterns.rgb.exec(E);if(F&&F.length==4){return[parseInt(F[1],10),parseInt(F[2],10),parseInt(F[3],10)];}F=this.patterns.hex3.exec(E);if(F&&F.length==4){return[parseInt(F[1]+F[1],16),parseInt(F[2]+F[2],16),parseInt(F[3]+F[3],16)];
+}return null;};B.getAttribute=function(E){var G=this.getEl();if(this.patterns.color.test(E)){var I=YAHOO.util.Dom.getStyle(G,E);var H=this;if(this.patterns.transparent.test(I)){var F=YAHOO.util.Dom.getAncestorBy(G,function(J){return !H.patterns.transparent.test(I);});if(F){I=C.Dom.getStyle(F,E);}else{I=A.DEFAULT_BGCOLOR;}}}else{I=D.getAttribute.call(this,E);}return I;};B.doMethod=function(F,J,G){var I;if(this.patterns.color.test(F)){I=[];for(var H=0,E=J.length;H<E;++H){I[H]=D.doMethod.call(this,F,J[H],G[H]);}I="rgb("+Math.floor(I[0])+","+Math.floor(I[1])+","+Math.floor(I[2])+")";}else{I=D.doMethod.call(this,F,J,G);}return I;};B.setRuntimeAttribute=function(F){D.setRuntimeAttribute.call(this,F);if(this.patterns.color.test(F)){var H=this.attributes;var J=this.parseColor(this.runtimeAttributes[F].start);var G=this.parseColor(this.runtimeAttributes[F].end);if(typeof H[F]["to"]==="undefined"&&typeof H[F]["by"]!=="undefined"){G=this.parseColor(H[F].by);for(var I=0,E=J.length;I<E;++I){G[I]=J[I]+G[I];}}this.runtimeAttributes[F].start=J;this.runtimeAttributes[F].end=G;}};C.ColorAnim=A;})();
+/*
+TERMS OF USE - EASING EQUATIONS
+Open source under the BSD License.
+Copyright 2001 Robert Penner All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+ * Neither the name of the author nor the names of contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE 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 PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+YAHOO.util.Easing={easeNone:function(B,A,D,C){return D*B/C+A;},easeIn:function(B,A,D,C){return D*(B/=C)*B+A;},easeOut:function(B,A,D,C){return -D*(B/=C)*(B-2)+A;},easeBoth:function(B,A,D,C){if((B/=C/2)<1){return D/2*B*B+A;}return -D/2*((--B)*(B-2)-1)+A;},easeInStrong:function(B,A,D,C){return D*(B/=C)*B*B*B+A;},easeOutStrong:function(B,A,D,C){return -D*((B=B/C-1)*B*B*B-1)+A;},easeBothStrong:function(B,A,D,C){if((B/=C/2)<1){return D/2*B*B*B*B+A;}return -D/2*((B-=2)*B*B*B-2)+A;},elasticIn:function(C,A,G,F,B,E){if(C==0){return A;}if((C/=F)==1){return A+G;}if(!E){E=F*0.3;}if(!B||B<Math.abs(G)){B=G;var D=E/4;}else{var D=E/(2*Math.PI)*Math.asin(G/B);}return -(B*Math.pow(2,10*(C-=1))*Math.sin((C*F-D)*(2*Math.PI)/E))+A;},elasticOut:function(C,A,G,F,B,E){if(C==0){return A;}if((C/=F)==1){return A+G;}if(!E){E=F*0.3;}if(!B||B<Math.abs(G)){B=G;var D=E/4;}else{var D=E/(2*Math.PI)*Math.asin(G/B);}return B*Math.pow(2,-10*C)*Math.sin((C*F-D)*(2*Math.PI)/E)+G+A;},elasticBoth:function(C,A,G,F,B,E){if(C==0){return A;}if((C/=F/2)==2){return A+G;}if(!E){E=F*(0.3*1.5);}if(!B||B<Math.abs(G)){B=G;var D=E/4;}else{var D=E/(2*Math.PI)*Math.asin(G/B);}if(C<1){return -0.5*(B*Math.pow(2,10*(C-=1))*Math.sin((C*F-D)*(2*Math.PI)/E))+A;}return B*Math.pow(2,-10*(C-=1))*Math.sin((C*F-D)*(2*Math.PI)/E)*0.5+G+A;},backIn:function(B,A,E,D,C){if(typeof C=="undefined"){C=1.70158;}return E*(B/=D)*B*((C+1)*B-C)+A;},backOut:function(B,A,E,D,C){if(typeof C=="undefined"){C=1.70158;}return E*((B=B/D-1)*B*((C+1)*B+C)+1)+A;},backBoth:function(B,A,E,D,C){if(typeof C=="undefined"){C=1.70158;}if((B/=D/2)<1){return E/2*(B*B*(((C*=(1.525))+1)*B-C))+A;}return E/2*((B-=2)*B*(((C*=(1.525))+1)*B+C)+2)+A;},bounceIn:function(B,A,D,C){return D-YAHOO.util.Easing.bounceOut(C-B,0,D,C)+A;},bounceOut:function(B,A,D,C){if((B/=C)<(1/2.75)){return D*(7.5625*B*B)+A;}else{if(B<(2/2.75)){return D*(7.5625*(B-=(1.5/2.75))*B+0.75)+A;}else{if(B<(2.5/2.75)){return D*(7.5625*(B-=(2.25/2.75))*B+0.9375)+A;}}}return D*(7.5625*(B-=(2.625/2.75))*B+0.984375)+A;},bounceBoth:function(B,A,D,C){if(B<C/2){return YAHOO.util.Easing.bounceIn(B*2,0,D,C)*0.5+A;}return YAHOO.util.Easing.bounceOut(B*2-C,0,D,C)*0.5+D*0.5+A;}};(function(){var A=function(H,G,I,J){if(H){A.superclass.constructor.call(this,H,G,I,J);}};A.NAME="Motion";var E=YAHOO.util;YAHOO.extend(A,E.ColorAnim);var F=A.superclass;var C=A.prototype;C.patterns.points=/^points$/i;C.setAttribute=function(G,I,H){if(this.patterns.points.test(G)){H=H||"px";F.setAttribute.call(this,"left",I[0],H);F.setAttribute.call(this,"top",I[1],H);}else{F.setAttribute.call(this,G,I,H);}};C.getAttribute=function(G){if(this.patterns.points.test(G)){var H=[F.getAttribute.call(this,"left"),F.getAttribute.call(this,"top")];}else{H=F.getAttribute.call(this,G);}return H;};C.doMethod=function(G,K,H){var J=null;if(this.patterns.points.test(G)){var I=this.method(this.currentFrame,0,100,this.totalFrames)/100;J=E.Bezier.getPosition(this.runtimeAttributes[G],I);}else{J=F.doMethod.call(this,G,K,H);}return J;};C.setRuntimeAttribute=function(P){if(this.patterns.points.test(P)){var H=this.getEl();var J=this.attributes;var G;var L=J["points"]["control"]||[];var I;var M,O;if(L.length>0&&!(L[0] instanceof Array)){L=[L];}else{var K=[];for(M=0,O=L.length;M<O;++M){K[M]=L[M];}L=K;}if(E.Dom.getStyle(H,"position")=="static"){E.Dom.setStyle(H,"position","relative");}if(D(J["points"]["from"])){E.Dom.setXY(H,J["points"]["from"]);
+}else{E.Dom.setXY(H,E.Dom.getXY(H));}G=this.getAttribute("points");if(D(J["points"]["to"])){I=B.call(this,J["points"]["to"],G);var N=E.Dom.getXY(this.getEl());for(M=0,O=L.length;M<O;++M){L[M]=B.call(this,L[M],G);}}else{if(D(J["points"]["by"])){I=[G[0]+J["points"]["by"][0],G[1]+J["points"]["by"][1]];for(M=0,O=L.length;M<O;++M){L[M]=[G[0]+L[M][0],G[1]+L[M][1]];}}}this.runtimeAttributes[P]=[G];if(L.length>0){this.runtimeAttributes[P]=this.runtimeAttributes[P].concat(L);}this.runtimeAttributes[P][this.runtimeAttributes[P].length]=I;}else{F.setRuntimeAttribute.call(this,P);}};var B=function(G,I){var H=E.Dom.getXY(this.getEl());G=[G[0]-H[0]+I[0],G[1]-H[1]+I[1]];return G;};var D=function(G){return(typeof G!=="undefined");};E.Motion=A;})();(function(){var D=function(F,E,G,H){if(F){D.superclass.constructor.call(this,F,E,G,H);}};D.NAME="Scroll";var B=YAHOO.util;YAHOO.extend(D,B.ColorAnim);var C=D.superclass;var A=D.prototype;A.doMethod=function(E,H,F){var G=null;if(E=="scroll"){G=[this.method(this.currentFrame,H[0],F[0]-H[0],this.totalFrames),this.method(this.currentFrame,H[1],F[1]-H[1],this.totalFrames)];}else{G=C.doMethod.call(this,E,H,F);}return G;};A.getAttribute=function(E){var G=null;var F=this.getEl();if(E=="scroll"){G=[F.scrollLeft,F.scrollTop];}else{G=C.getAttribute.call(this,E);}return G;};A.setAttribute=function(E,H,G){var F=this.getEl();if(E=="scroll"){F.scrollLeft=H[0];F.scrollTop=H[1];}else{C.setAttribute.call(this,E,H,G);}};B.Scroll=D;})();YAHOO.register("animation",YAHOO.util.Anim,{version:"2.8.2r1",build:"7"}); \ No newline at end of file
diff --git a/js/yui/assets/skins/sam/ajax-loader.gif b/js/yui/assets/skins/sam/ajax-loader.gif
new file mode 100644
index 000000000..fe2cd23b3
--- /dev/null
+++ b/js/yui/assets/skins/sam/ajax-loader.gif
Binary files differ
diff --git a/js/yui/assets/skins/sam/asc.gif b/js/yui/assets/skins/sam/asc.gif
new file mode 100644
index 000000000..a1fe7385d
--- /dev/null
+++ b/js/yui/assets/skins/sam/asc.gif
Binary files differ
diff --git a/js/yui/assets/skins/sam/autocomplete.css b/js/yui/assets/skins/sam/autocomplete.css
new file mode 100644
index 000000000..aa3435d32
--- /dev/null
+++ b/js/yui/assets/skins/sam/autocomplete.css
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+.yui-skin-sam .yui-ac{position:relative;font-family:arial;font-size:100%;}.yui-skin-sam .yui-ac-input{position:absolute;width:100%;}.yui-skin-sam .yui-ac-container{position:absolute;top:1.6em;width:100%;}.yui-skin-sam .yui-ac-content{position:absolute;width:100%;border:1px solid #808080;background:#fff;overflow:hidden;z-index:9050;}.yui-skin-sam .yui-ac-shadow{position:absolute;margin:.3em;width:100%;background:#000;-moz-opacity:.10;opacity:.10;filter:alpha(opacity=10);z-index:9049;}.yui-skin-sam .yui-ac iframe{opacity:0;filter:alpha(opacity=0);padding-right:.3em;padding-bottom:.3em;}.yui-skin-sam .yui-ac-content ul{margin:0;padding:0;width:100%;}.yui-skin-sam .yui-ac-content li{margin:0;padding:2px 5px;cursor:default;white-space:nowrap;list-style:none;zoom:1;}.yui-skin-sam .yui-ac-content li.yui-ac-prehighlight{background:#B3D4FF;}.yui-skin-sam .yui-ac-content li.yui-ac-highlight{background:#426FD9;color:#FFF;}
diff --git a/js/yui/assets/skins/sam/back-h.png b/js/yui/assets/skins/sam/back-h.png
new file mode 100644
index 000000000..5f69f4e25
--- /dev/null
+++ b/js/yui/assets/skins/sam/back-h.png
Binary files differ
diff --git a/js/yui/assets/skins/sam/back-v.png b/js/yui/assets/skins/sam/back-v.png
new file mode 100644
index 000000000..658574a9d
--- /dev/null
+++ b/js/yui/assets/skins/sam/back-v.png
Binary files differ
diff --git a/js/yui/assets/skins/sam/bar-h.png b/js/yui/assets/skins/sam/bar-h.png
new file mode 100644
index 000000000..fea13b15d
--- /dev/null
+++ b/js/yui/assets/skins/sam/bar-h.png
Binary files differ
diff --git a/js/yui/assets/skins/sam/bar-v.png b/js/yui/assets/skins/sam/bar-v.png
new file mode 100644
index 000000000..2efd664d9
--- /dev/null
+++ b/js/yui/assets/skins/sam/bar-v.png
Binary files differ
diff --git a/js/yui/assets/skins/sam/bg-h.gif b/js/yui/assets/skins/sam/bg-h.gif
new file mode 100644
index 000000000..996288916
--- /dev/null
+++ b/js/yui/assets/skins/sam/bg-h.gif
Binary files differ
diff --git a/js/yui/assets/skins/sam/bg-v.gif b/js/yui/assets/skins/sam/bg-v.gif
new file mode 100644
index 000000000..8e287cd52
--- /dev/null
+++ b/js/yui/assets/skins/sam/bg-v.gif
Binary files differ
diff --git a/js/yui/assets/skins/sam/blankimage.png b/js/yui/assets/skins/sam/blankimage.png
new file mode 100644
index 000000000..b87bb2485
--- /dev/null
+++ b/js/yui/assets/skins/sam/blankimage.png
Binary files differ
diff --git a/js/yui/assets/skins/sam/button.css b/js/yui/assets/skins/sam/button.css
new file mode 100644
index 000000000..40cc1dd9e
--- /dev/null
+++ b/js/yui/assets/skins/sam/button.css
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+.yui-button{display:-moz-inline-box;display:inline-block;vertical-align:text-bottom;}.yui-button .first-child{display:block;*display:inline-block;}.yui-button button,.yui-button a{display:block;*display:inline-block;border:none;margin:0;}.yui-button button{background-color:transparent;*overflow:visible;cursor:pointer;}.yui-button a{text-decoration:none;}.yui-skin-sam .yui-button{border-width:1px 0;border-style:solid;border-color:#808080;background:url(sprite.png) repeat-x 0 0;margin:auto .25em;}.yui-skin-sam .yui-button .first-child{border-width:0 1px;border-style:solid;border-color:#808080;margin:0 -1px;_margin:0;}.yui-skin-sam .yui-button button,.yui-skin-sam .yui-button a,.yui-skin-sam .yui-button a:visited{padding:0 10px;font-size:93%;line-height:2;*line-height:1.7;min-height:2em;*min-height:auto;color:#000;}.yui-skin-sam .yui-button a{*line-height:1.875;*padding-bottom:1px;}.yui-skin-sam .yui-split-button button,.yui-skin-sam .yui-menu-button button{padding-right:20px;background-position:right center;background-repeat:no-repeat;}.yui-skin-sam .yui-menu-button button{background-image:url(menu-button-arrow.png);}.yui-skin-sam .yui-split-button button{background-image:url(split-button-arrow.png);}.yui-skin-sam .yui-button-focus{border-color:#7D98B8;background-position:0 -1300px;}.yui-skin-sam .yui-button-focus .first-child{border-color:#7D98B8;}.yui-skin-sam .yui-split-button-focus button{background-image:url(split-button-arrow-focus.png);}.yui-skin-sam .yui-button-hover{border-color:#7D98B8;background-position:0 -1300px;}.yui-skin-sam .yui-button-hover .first-child{border-color:#7D98B8;}.yui-skin-sam .yui-split-button-hover button{background-image:url(split-button-arrow-hover.png);}.yui-skin-sam .yui-button-active{border-color:#7D98B8;background-position:0 -1700px;}.yui-skin-sam .yui-button-active .first-child{border-color:#7D98B8;}.yui-skin-sam .yui-split-button-activeoption{border-color:#808080;background-position:0 0;}.yui-skin-sam .yui-split-button-activeoption .first-child{border-color:#808080;}.yui-skin-sam .yui-split-button-activeoption button{background-image:url(split-button-arrow-active.png);}.yui-skin-sam .yui-radio-button-checked,.yui-skin-sam .yui-checkbox-button-checked{border-color:#304369;background-position:0 -1400px;}.yui-skin-sam .yui-radio-button-checked .first-child,.yui-skin-sam .yui-checkbox-button-checked .first-child{border-color:#304369;}.yui-skin-sam .yui-radio-button-checked button,.yui-skin-sam .yui-checkbox-button-checked button{color:#fff;}.yui-skin-sam .yui-button-disabled{border-color:#ccc;background-position:0 -1500px;}.yui-skin-sam .yui-button-disabled .first-child{border-color:#ccc;}.yui-skin-sam .yui-button-disabled button,.yui-skin-sam .yui-button-disabled a,.yui-skin-sam .yui-button-disabled a:visited{color:#A6A6A6;cursor:default;}.yui-skin-sam .yui-menu-button-disabled button{background-image:url(menu-button-arrow-disabled.png);}.yui-skin-sam .yui-split-button-disabled button{background-image:url(split-button-arrow-disabled.png);}
diff --git a/js/yui/assets/skins/sam/calendar.css b/js/yui/assets/skins/sam/calendar.css
new file mode 100644
index 000000000..206ceffd9
--- /dev/null
+++ b/js/yui/assets/skins/sam/calendar.css
@@ -0,0 +1,8 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+.yui-calcontainer{position:relative;float:left;_overflow:hidden;}.yui-calcontainer iframe{position:absolute;border:none;margin:0;padding:0;z-index:0;width:100%;height:100%;left:0;top:0;}.yui-calcontainer iframe.fixedsize{width:50em;height:50em;top:-1px;left:-1px;}.yui-calcontainer.multi .groupcal{z-index:1;float:left;position:relative;}.yui-calcontainer .title{position:relative;z-index:1;}.yui-calcontainer .close-icon{position:absolute;z-index:1;text-indent:-10000em;overflow:hidden;}.yui-calendar{position:relative;}.yui-calendar .calnavleft{position:absolute;z-index:1;text-indent:-10000em;overflow:hidden;}.yui-calendar .calnavright{position:absolute;z-index:1;text-indent:-10000em;overflow:hidden;}.yui-calendar .calheader{position:relative;width:100%;text-align:center;}.yui-calcontainer .yui-cal-nav-mask{position:absolute;z-index:2;margin:0;padding:0;width:100%;height:100%;_width:0;_height:0;left:0;top:0;display:none;}.yui-calcontainer .yui-cal-nav{position:absolute;z-index:3;top:0;display:none;}.yui-calcontainer .yui-cal-nav .yui-cal-nav-btn{display:-moz-inline-box;display:inline-block;}.yui-calcontainer .yui-cal-nav .yui-cal-nav-btn button{display:block;*display:inline-block;*overflow:visible;border:none;background-color:transparent;cursor:pointer;}.yui-calendar .calbody a:hover{background:inherit;}p#clear{clear:left;padding-top:10px;}.yui-skin-sam .yui-calcontainer{background-color:#f2f2f2;border:1px solid #808080;padding:10px;}.yui-skin-sam .yui-calcontainer.multi{padding:0 5px 0 5px;}.yui-skin-sam .yui-calcontainer.multi .groupcal{background-color:transparent;border:none;padding:10px 5px 10px 5px;margin:0;}.yui-skin-sam .yui-calcontainer .title{background:url(sprite.png) repeat-x 0 0;border-bottom:1px solid #ccc;font:100% sans-serif;color:#000;font-weight:bold;height:auto;padding:.4em;margin:0 -10px 10px -10px;top:0;left:0;text-align:left;}.yui-skin-sam .yui-calcontainer.multi .title{margin:0 -5px 0 -5px;}.yui-skin-sam .yui-calcontainer.withtitle{padding-top:0;}.yui-skin-sam .yui-calcontainer .calclose{background:url(sprite.png) no-repeat 0 -300px;width:25px;height:15px;top:.4em;right:.4em;cursor:pointer;}.yui-skin-sam .yui-calendar{border-spacing:0;border-collapse:collapse;font:100% sans-serif;text-align:center;margin:0;}.yui-skin-sam .yui-calendar .calhead{background:transparent;border:none;vertical-align:middle;padding:0;}.yui-skin-sam .yui-calendar .calheader{background:transparent;font-weight:bold;padding:0 0 .6em 0;text-align:center;}.yui-skin-sam .yui-calendar .calheader img{border:none;}.yui-skin-sam .yui-calendar .calnavleft{background:url(sprite.png) no-repeat 0 -450px;width:25px;height:15px;top:0;bottom:0;left:-10px;margin-left:.4em;cursor:pointer;}.yui-skin-sam .yui-calendar .calnavright{background:url(sprite.png) no-repeat 0 -500px;width:25px;height:15px;top:0;bottom:0;right:-10px;margin-right:.4em;cursor:pointer;}.yui-skin-sam .yui-calendar .calweekdayrow{height:2em;}.yui-skin-sam .yui-calendar .calweekdayrow th{padding:0;border:none;}.yui-skin-sam .yui-calendar .calweekdaycell{color:#000;font-weight:bold;text-align:center;width:2em;}.yui-skin-sam .yui-calendar .calfoot{background-color:#f2f2f2;}.yui-skin-sam .yui-calendar .calrowhead,.yui-skin-sam .yui-calendar .calrowfoot{color:#a6a6a6;font-size:85%;font-style:normal;font-weight:normal;border:none;}.yui-skin-sam .yui-calendar .calrowhead{text-align:right;padding:0 2px 0 0;}.yui-skin-sam .yui-calendar .calrowfoot{text-align:left;padding:0 0 0 2px;}.yui-skin-sam .yui-calendar td.calcell{border:1px solid #ccc;background:#fff;padding:1px;height:1.6em;line-height:1.6em;text-align:center;white-space:nowrap;}.yui-skin-sam .yui-calendar td.calcell a{color:#06c;display:block;height:100%;text-decoration:none;}.yui-skin-sam .yui-calendar td.calcell.today{background-color:#000;}.yui-skin-sam .yui-calendar td.calcell.today a{background-color:#fff;}.yui-skin-sam .yui-calendar td.calcell.oom{background-color:#ccc;color:#a6a6a6;cursor:default;}.yui-skin-sam .yui-calendar td.calcell.selected{background-color:#fff;color:#000;}.yui-skin-sam .yui-calendar td.calcell.selected a{background-color:#b3d4ff;color:#000;}.yui-skin-sam .yui-calendar td.calcell.calcellhover{background-color:#426fd9;color:#fff;cursor:pointer;}.yui-skin-sam .yui-calendar td.calcell.calcellhover a{background-color:#426fd9;color:#fff;}.yui-skin-sam .yui-calendar td.calcell.previous{color:#e0e0e0;}.yui-skin-sam .yui-calendar td.calcell.restricted{text-decoration:line-through;}.yui-skin-sam .yui-calendar td.calcell.highlight1{background-color:#cf9;}.yui-skin-sam .yui-calendar td.calcell.highlight2{background-color:#9cf;}.yui-skin-sam .yui-calendar td.calcell.highlight3{background-color:#fcc;}.yui-skin-sam .yui-calendar td.calcell.highlight4{background-color:#cf9;}.yui-skin-sam .yui-calendar a.calnav{border:1px solid #f2f2f2;padding:0 4px;text-decoration:none;color:#000;zoom:1;}.yui-skin-sam .yui-calendar a.calnav:hover{background:url(sprite.png) repeat-x 0 0;border-color:#A0A0A0;cursor:pointer;}.yui-skin-sam .yui-calcontainer .yui-cal-nav-mask{background-color:#000;opacity:.25;filter:alpha(opacity=25);}.yui-skin-sam .yui-calcontainer .yui-cal-nav{font-family:arial,helvetica,clean,sans-serif;font-size:93%;border:1px solid #808080;left:50%;margin-left:-7em;width:14em;padding:0;top:2.5em;background-color:#f2f2f2;}.yui-skin-sam .yui-calcontainer.withtitle .yui-cal-nav{top:4.5em;}.yui-skin-sam .yui-calcontainer.multi .yui-cal-nav{width:16em;margin-left:-8em;}.yui-skin-sam .yui-calcontainer .yui-cal-nav-y,.yui-skin-sam .yui-calcontainer .yui-cal-nav-m,.yui-skin-sam .yui-calcontainer .yui-cal-nav-b{padding:5px 10px 5px 10px;}.yui-skin-sam .yui-calcontainer .yui-cal-nav-b{text-align:center;}.yui-skin-sam .yui-calcontainer .yui-cal-nav-e{margin-top:5px;padding:5px;background-color:#EDF5FF;border-top:1px solid black;display:none;}.yui-skin-sam .yui-calcontainer .yui-cal-nav label{display:block;font-weight:bold;}
+.yui-skin-sam .yui-calcontainer .yui-cal-nav-mc{width:100%;_width:auto;}.yui-skin-sam .yui-calcontainer .yui-cal-nav-y input.yui-invalid{background-color:#FFEE69;border:1px solid #000;}.yui-skin-sam .yui-calcontainer .yui-cal-nav-yc{width:4em;}.yui-skin-sam .yui-calcontainer .yui-cal-nav .yui-cal-nav-btn{border:1px solid #808080;background:url(sprite.png) repeat-x 0 0;background-color:#ccc;margin:auto .15em;}.yui-skin-sam .yui-calcontainer .yui-cal-nav .yui-cal-nav-btn button{padding:0 8px;font-size:93%;line-height:2;*line-height:1.7;min-height:2em;*min-height:auto;color:#000;}.yui-skin-sam .yui-calcontainer .yui-cal-nav .yui-cal-nav-btn.yui-default{border:1px solid #304369;background-color:#426fd9;background:url(sprite.png) repeat-x 0 -1400px;}.yui-skin-sam .yui-calcontainer .yui-cal-nav .yui-cal-nav-btn.yui-default button{color:#fff;}
diff --git a/js/yui/assets/skins/sam/carousel.css b/js/yui/assets/skins/sam/carousel.css
new file mode 100644
index 000000000..2db4ed16d
--- /dev/null
+++ b/js/yui/assets/skins/sam/carousel.css
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+.yui-carousel{visibility:hidden;overflow:hidden;position:relative;text-align:left;zoom:1;}.yui-carousel.yui-carousel-visible{visibility:visible;}.yui-carousel-content{overflow:hidden;position:relative;text-align:center;}.yui-carousel-element li{border:1px solid #ccc;list-style:none;margin:1px;overflow:hidden;padding:0;position:absolute;text-align:center;}.yui-carousel-vertical .yui-carousel-element li{display:block;float:none;}.yui-log .carousel{background:#f2e886;}.yui-carousel-nav{zoom:1;}.yui-carousel-nav:after{content:".";display:block;height:0;clear:both;visibility:hidden;}.yui-carousel-button-focus{outline:1px dotted #000;}.yui-carousel-min-width{min-width:115px;}.yui-carousel-element{overflow:hidden;position:relative;margin:0 auto;padding:0;text-align:left;*margin:0;}.yui-carousel-horizontal .yui-carousel-element{width:320000px;}.yui-carousel-vertical .yui-carousel-element{height:320000px;}.yui-skin-sam .yui-carousel-nav select{position:static;}.yui-carousel .yui-carousel-item-selected{border:1px dashed #000;margin:1px;}.yui-skin-sam .yui-carousel,.yui-skin-sam .yui-carousel-vertical{border:1px solid #808080;}.yui-skin-sam .yui-carousel-nav{background:url(sprite.png) repeat-x 0 0;padding:3px;text-align:right;}.yui-skin-sam .yui-carousel-button{background:url(sprite.png) no-repeat 0 -600px;float:right;height:19px;margin:5px;overflow:hidden;width:40px;}.yui-skin-sam .yui-carousel-vertical .yui-carousel-button{background-position:0 -800px;}.yui-skin-sam .yui-carousel-button-disabled{background-position:0 -2000px;}.yui-skin-sam .yui-carousel-vertical .yui-carousel-button-disabled{background-position:0 -2100px;}.yui-skin-sam .yui-carousel-button input,.yui-skin-sam .yui-carousel-button button{background-color:transparent;border:0;cursor:pointer;display:block;height:44px;margin:-2px 0 0 -2px;padding:0 0 0 50px;}.yui-skin-sam span.yui-carousel-first-button{background-position:0 -550px;margin-left:-100px;margin-right:50px;*margin:5px 5px 5px -90px;}.yui-skin-sam .yui-carousel-vertical span.yui-carousel-first-button{background-position:0 -750px;}.yui-skin-sam span.yui-carousel-first-button-disabled{background-position:0 -1950px;}.yui-skin-sam .yui-carousel-vertical span.yui-carousel-first-button-disabled{background-position:0 -2050px;}.yui-skin-sam .yui-carousel-nav ul{float:right;height:19px;margin:0;margin-left:-220px;margin-right:100px;*margin-left:-160px;*margin-right:0;padding:0;}.yui-skin-sam .yui-carousel-min-width .yui-carousel-nav ul{*margin-left:-170px;}.yui-skin-sam .yui-carousel-nav select{position:relative;*right:50px;top:4px;}.yui-skin-sam .yui-carousel-vertical .yui-carousel-nav select{position:static;}.yui-skin-sam .yui-carousel-vertical .yui-carousel-nav ul,.yui-skin-sam .yui-carousel-vertical .yui-carousel-nav select{float:none;margin:0;*zoom:1;}.yui-skin-sam .yui-carousel-nav ul li{background:url(sprite.png) no-repeat 0 -650px;cursor:pointer;float:left;height:9px;list-style:none;margin:10px 0 0 5px;overflow:hidden;padding:0;width:9px;}.yui-skin-sam .yui-carousel-nav ul:after{content:".";display:block;height:0;clear:both;visibility:hidden;}.yui-skin-sam .yui-carousel-nav ul li a{display:block;width:100%;height:100%;text-indent:-10000px;text-align:left;overflow:hidden;}.yui-skin-sam .yui-carousel-nav ul li.yui-carousel-nav-page-focus{outline:1px dotted #000;}.yui-skin-sam .yui-carousel-nav ul li.yui-carousel-nav-page-selected{background-position:0 -700px;}.yui-skin-sam .yui-carousel-item-loading{background:url(ajax-loader.gif) no-repeat 50% 50%;position:absolute;text-indent:-150px;}
diff --git a/js/yui/assets/skins/sam/check0.gif b/js/yui/assets/skins/sam/check0.gif
new file mode 100644
index 000000000..193028b99
--- /dev/null
+++ b/js/yui/assets/skins/sam/check0.gif
Binary files differ
diff --git a/js/yui/assets/skins/sam/check1.gif b/js/yui/assets/skins/sam/check1.gif
new file mode 100644
index 000000000..7d9ceba38
--- /dev/null
+++ b/js/yui/assets/skins/sam/check1.gif
Binary files differ
diff --git a/js/yui/assets/skins/sam/check2.gif b/js/yui/assets/skins/sam/check2.gif
new file mode 100644
index 000000000..181317599
--- /dev/null
+++ b/js/yui/assets/skins/sam/check2.gif
Binary files differ
diff --git a/js/yui/assets/skins/sam/colorpicker.css b/js/yui/assets/skins/sam/colorpicker.css
new file mode 100644
index 000000000..3a8815115
--- /dev/null
+++ b/js/yui/assets/skins/sam/colorpicker.css
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+.yui-picker-panel{background:#e3e3e3;border-color:#888;}.yui-picker-panel .hd{background-color:#ccc;font-size:100%;line-height:100%;border:1px solid #e3e3e3;font-weight:bold;overflow:hidden;padding:6px;color:#000;}.yui-picker-panel .bd{background:#e8e8e8;margin:1px;height:200px;}.yui-picker-panel .ft{background:#e8e8e8;margin:1px;padding:1px;}.yui-picker{position:relative;}.yui-picker-hue-thumb{cursor:default;width:18px;height:18px;top:-8px;left:-2px;z-index:9;position:absolute;}.yui-picker-hue-bg{-moz-outline:none;outline:0 none;position:absolute;left:200px;height:183px;width:14px;background:url(hue_bg.png) no-repeat;top:4px;}.yui-picker-bg{-moz-outline:none;outline:0 none;position:absolute;top:4px;left:4px;height:182px;width:182px;background-color:#F00;background-image:url(picker_mask.png);}*html .yui-picker-bg{background-image:none;filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../../build/colorpicker/assets/picker_mask.png',sizingMethod='scale');}.yui-picker-mask{position:absolute;z-index:1;top:0;left:0;}.yui-picker-thumb{cursor:default;width:11px;height:11px;z-index:9;position:absolute;top:-4px;left:-4px;}.yui-picker-swatch{position:absolute;left:240px;top:4px;height:60px;width:55px;border:1px solid #888;}.yui-picker-websafe-swatch{position:absolute;left:304px;top:4px;height:24px;width:24px;border:1px solid #888;}.yui-picker-controls{position:absolute;top:72px;left:226px;font:1em monospace;}.yui-picker-controls .hd{background:transparent;border-width:0!important;}.yui-picker-controls .bd{height:100px;border-width:0!important;}.yui-picker-controls ul{float:left;padding:0 2px 0 0;margin:0;}.yui-picker-controls li{padding:2px;list-style:none;margin:0;}.yui-picker-controls input{font-size:.85em;width:2.4em;}.yui-picker-hex-controls{clear:both;padding:2px;}.yui-picker-hex-controls input{width:4.6em;}.yui-picker-controls a{font:1em arial,helvetica,clean,sans-serif;display:block;*display:inline-block;padding:0;color:#000;}
diff --git a/js/yui/assets/skins/sam/container.css b/js/yui/assets/skins/sam/container.css
new file mode 100644
index 000000000..f40833d48
--- /dev/null
+++ b/js/yui/assets/skins/sam/container.css
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+.yui-overlay,.yui-panel-container{visibility:hidden;position:absolute;z-index:2;}.yui-panel{position:relative;}.yui-panel-container form{margin:0;}.mask{z-index:1;display:none;position:absolute;top:0;left:0;right:0;bottom:0;}.mask.block-scrollbars{overflow:auto;}.masked select,.drag select,.hide-select select{_visibility:hidden;}.yui-panel-container select{_visibility:inherit;}.hide-scrollbars,.hide-scrollbars *{overflow:hidden;}.hide-scrollbars select{display:none;}.show-scrollbars{overflow:auto;}.yui-panel-container.show-scrollbars,.yui-tt.show-scrollbars{overflow:visible;}.yui-panel-container.show-scrollbars .underlay,.yui-tt.show-scrollbars .yui-tt-shadow{overflow:auto;}.yui-panel-container.shadow .underlay.yui-force-redraw{padding-bottom:1px;}.yui-effect-fade .underlay,.yui-effect-fade .yui-tt-shadow{display:none;}.yui-tt-shadow{position:absolute;}.yui-override-padding{padding:0!important;}.yui-panel-container .container-close{overflow:hidden;text-indent:-10000em;text-decoration:none;}.yui-overlay.yui-force-redraw,.yui-panel-container.yui-force-redraw{margin-bottom:1px;}.yui-skin-sam .mask{background-color:#000;opacity:.25;filter:alpha(opacity=25);}.yui-skin-sam .yui-panel-container{padding:0 1px;*padding:2px;}.yui-skin-sam .yui-panel{position:relative;left:0;top:0;border-style:solid;border-width:1px 0;border-color:#808080;z-index:1;*border-width:1px;*zoom:1;_zoom:normal;}.yui-skin-sam .yui-panel .hd,.yui-skin-sam .yui-panel .bd,.yui-skin-sam .yui-panel .ft{border-style:solid;border-width:0 1px;border-color:#808080;margin:0 -1px;*margin:0;*border:0;}.yui-skin-sam .yui-panel .hd{border-bottom:solid 1px #ccc;}.yui-skin-sam .yui-panel .bd,.yui-skin-sam .yui-panel .ft{background-color:#F2F2F2;}.yui-skin-sam .yui-panel .hd{padding:0 10px;font-size:93%;line-height:2;*line-height:1.9;font-weight:bold;color:#000;background:url(sprite.png) repeat-x 0 -200px;}.yui-skin-sam .yui-panel .bd{padding:10px;}.yui-skin-sam .yui-panel .ft{border-top:solid 1px #808080;padding:5px 10px;font-size:77%;}.yui-skin-sam .container-close{position:absolute;top:5px;right:6px;width:25px;height:15px;background:url(sprite.png) no-repeat 0 -300px;cursor:pointer;}.yui-skin-sam .yui-panel-container .underlay{right:-1px;left:-1px;}.yui-skin-sam .yui-panel-container.matte{padding:9px 10px;background-color:#fff;}.yui-skin-sam .yui-panel-container.shadow{_padding:2px 4px 0 2px;}.yui-skin-sam .yui-panel-container.shadow .underlay{position:absolute;top:2px;left:-3px;right:-3px;bottom:-3px;*top:4px;*left:-1px;*right:-1px;*bottom:-1px;_top:0;_left:0;_right:0;_bottom:0;_margin-top:3px;_margin-left:-1px;background-color:#000;opacity:.12;filter:alpha(opacity=12);}.yui-skin-sam .yui-dialog .ft{border-top:none;padding:0 10px 10px 10px;font-size:100%;}.yui-skin-sam .yui-dialog .ft .button-group{display:block;text-align:right;}.yui-skin-sam .yui-dialog .ft button.default{font-weight:bold;}.yui-skin-sam .yui-dialog .ft span.default{border-color:#304369;background-position:0 -1400px;}.yui-skin-sam .yui-dialog .ft span.default .first-child{border-color:#304369;}.yui-skin-sam .yui-dialog .ft span.default button{color:#fff;}.yui-skin-sam .yui-dialog .ft span.yui-button-disabled{background-position:0 -1500px;border-color:#ccc;}.yui-skin-sam .yui-dialog .ft span.yui-button-disabled .first-child{border-color:#ccc;}.yui-skin-sam .yui-dialog .ft span.yui-button-disabled button{color:#a6a6a6;}.yui-skin-sam .yui-simple-dialog .bd .yui-icon{background:url(sprite.png) no-repeat 0 0;width:16px;height:16px;margin-right:10px;float:left;}.yui-skin-sam .yui-simple-dialog .bd span.blckicon{background-position:0 -1100px;}.yui-skin-sam .yui-simple-dialog .bd span.alrticon{background-position:0 -1050px;}.yui-skin-sam .yui-simple-dialog .bd span.hlpicon{background-position:0 -1150px;}.yui-skin-sam .yui-simple-dialog .bd span.infoicon{background-position:0 -1200px;}.yui-skin-sam .yui-simple-dialog .bd span.warnicon{background-position:0 -1900px;}.yui-skin-sam .yui-simple-dialog .bd span.tipicon{background-position:0 -1250px;}.yui-skin-sam .yui-tt .bd{position:relative;top:0;left:0;z-index:1;color:#000;padding:2px 5px;border-color:#D4C237 #A6982B #A6982B #A6982B;border-width:1px;border-style:solid;background-color:#FFEE69;}.yui-skin-sam .yui-tt.show-scrollbars .bd{overflow:auto;}.yui-skin-sam .yui-tt-shadow{top:2px;right:-3px;left:-3px;bottom:-3px;background-color:#000;}.yui-skin-sam .yui-tt-shadow-visible{opacity:.12;filter:alpha(opacity=12);}
diff --git a/js/yui/assets/skins/sam/datatable.css b/js/yui/assets/skins/sam/datatable.css
new file mode 100644
index 000000000..5163388cc
--- /dev/null
+++ b/js/yui/assets/skins/sam/datatable.css
@@ -0,0 +1,8 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+.yui-skin-sam .yui-dt-mask{position:absolute;z-index:9500;}.yui-dt-tmp{position:absolute;left:-9000px;}.yui-dt-scrollable .yui-dt-bd{overflow:auto;}.yui-dt-scrollable .yui-dt-hd{overflow:hidden;position:relative;}.yui-dt-scrollable .yui-dt-bd thead tr,.yui-dt-scrollable .yui-dt-bd thead th{position:absolute;left:-1500px;}.yui-dt-scrollable tbody{-moz-outline:none;}.yui-skin-sam thead .yui-dt-sortable{cursor:pointer;}.yui-skin-sam thead .yui-dt-draggable{cursor:move;}.yui-dt-coltarget{position:absolute;z-index:999;}.yui-dt-hd{zoom:1;}th.yui-dt-resizeable .yui-dt-resizerliner{position:relative;}.yui-dt-resizer{position:absolute;right:0;bottom:0;height:100%;cursor:e-resize;cursor:col-resize;background-color:#CCC;opacity:0;filter:alpha(opacity=0);}.yui-dt-resizerproxy{visibility:hidden;position:absolute;z-index:9000;background-color:#CCC;opacity:0;filter:alpha(opacity=0);}th.yui-dt-hidden .yui-dt-liner,td.yui-dt-hidden .yui-dt-liner,th.yui-dt-hidden .yui-dt-resizer{display:none;}.yui-dt-editor{position:absolute;z-index:9000;}.yui-skin-sam .yui-dt table{margin:0;padding:0;font-family:arial;font-size:inherit;border-collapse:separate;*border-collapse:collapse;border-spacing:0;border:1px solid #7F7F7F;}.yui-skin-sam .yui-dt thead{border-spacing:0;}.yui-skin-sam .yui-dt caption{color:#000;font-size:85%;font-weight:normal;font-style:italic;line-height:1;padding:1em 0;text-align:center;}.yui-skin-sam .yui-dt th{background:#D8D8DA url(sprite.png) repeat-x 0 0;}.yui-skin-sam .yui-dt th,.yui-skin-sam .yui-dt th a{font-weight:normal;text-decoration:none;color:#000;vertical-align:bottom;}.yui-skin-sam .yui-dt th{margin:0;padding:0;border:none;border-right:1px solid #CBCBCB;}.yui-skin-sam .yui-dt tr.yui-dt-first td{border-top:1px solid #7F7F7F;}.yui-skin-sam .yui-dt th .yui-dt-liner{white-space:nowrap;}.yui-skin-sam .yui-dt-liner{margin:0;padding:0;padding:4px 10px 4px 10px;}.yui-skin-sam .yui-dt-coltarget{width:5px;background-color:red;}.yui-skin-sam .yui-dt td{margin:0;padding:0;border:none;border-right:1px solid #CBCBCB;text-align:left;}.yui-skin-sam .yui-dt-list td{border-right:none;}.yui-skin-sam .yui-dt-resizer{width:6px;}.yui-skin-sam .yui-dt-mask{background-color:#000;opacity:.25;filter:alpha(opacity=25);}.yui-skin-sam .yui-dt-message{background-color:#FFF;}.yui-skin-sam .yui-dt-scrollable table{border:none;}.yui-skin-sam .yui-dt-scrollable .yui-dt-hd{border-left:1px solid #7F7F7F;border-top:1px solid #7F7F7F;border-right:1px solid #7F7F7F;}.yui-skin-sam .yui-dt-scrollable .yui-dt-bd{border-left:1px solid #7F7F7F;border-bottom:1px solid #7F7F7F;border-right:1px solid #7F7F7F;background-color:#FFF;}.yui-skin-sam .yui-dt-scrollable .yui-dt-data tr.yui-dt-last td{border-bottom:1px solid #7F7F7F;}.yui-skin-sam th.yui-dt-asc,.yui-skin-sam th.yui-dt-desc{background:url(sprite.png) repeat-x 0 -100px;}.yui-skin-sam th.yui-dt-sortable .yui-dt-label{margin-right:10px;}.yui-skin-sam th.yui-dt-asc .yui-dt-liner{background:url(dt-arrow-up.png) no-repeat right;}.yui-skin-sam th.yui-dt-desc .yui-dt-liner{background:url(dt-arrow-dn.png) no-repeat right;}tbody .yui-dt-editable{cursor:pointer;}.yui-dt-editor{text-align:left;background-color:#F2F2F2;border:1px solid #808080;padding:6px;}.yui-dt-editor label{padding-left:4px;padding-right:6px;}.yui-dt-editor .yui-dt-button{padding-top:6px;text-align:right;}.yui-dt-editor .yui-dt-button button{background:url(sprite.png) repeat-x 0 0;border:1px solid #999;width:4em;height:1.8em;margin-left:6px;}.yui-dt-editor .yui-dt-button button.yui-dt-default{background:url(sprite.png) repeat-x 0 -1400px;background-color:#5584E0;border:1px solid #304369;color:#FFF;}.yui-dt-editor .yui-dt-button button:hover{background:url(sprite.png) repeat-x 0 -1300px;color:#000;}.yui-dt-editor .yui-dt-button button:active{background:url(sprite.png) repeat-x 0 -1700px;color:#000;}.yui-skin-sam tr.yui-dt-even{background-color:#FFF;}.yui-skin-sam tr.yui-dt-odd{background-color:#EDF5FF;}.yui-skin-sam tr.yui-dt-even td.yui-dt-asc,.yui-skin-sam tr.yui-dt-even td.yui-dt-desc{background-color:#EDF5FF;}.yui-skin-sam tr.yui-dt-odd td.yui-dt-asc,.yui-skin-sam tr.yui-dt-odd td.yui-dt-desc{background-color:#DBEAFF;}.yui-skin-sam .yui-dt-list tr.yui-dt-even{background-color:#FFF;}.yui-skin-sam .yui-dt-list tr.yui-dt-odd{background-color:#FFF;}.yui-skin-sam .yui-dt-list tr.yui-dt-even td.yui-dt-asc,.yui-skin-sam .yui-dt-list tr.yui-dt-even td.yui-dt-desc{background-color:#EDF5FF;}.yui-skin-sam .yui-dt-list tr.yui-dt-odd td.yui-dt-asc,.yui-skin-sam .yui-dt-list tr.yui-dt-odd td.yui-dt-desc{background-color:#EDF5FF;}.yui-skin-sam th.yui-dt-highlighted,.yui-skin-sam th.yui-dt-highlighted a{background-color:#B2D2FF;}.yui-skin-sam tr.yui-dt-highlighted,.yui-skin-sam tr.yui-dt-highlighted td.yui-dt-asc,.yui-skin-sam tr.yui-dt-highlighted td.yui-dt-desc,.yui-skin-sam tr.yui-dt-even td.yui-dt-highlighted,.yui-skin-sam tr.yui-dt-odd td.yui-dt-highlighted{cursor:pointer;background-color:#B2D2FF;}.yui-skin-sam .yui-dt-list th.yui-dt-highlighted,.yui-skin-sam .yui-dt-list th.yui-dt-highlighted a{background-color:#B2D2FF;}.yui-skin-sam .yui-dt-list tr.yui-dt-highlighted,.yui-skin-sam .yui-dt-list tr.yui-dt-highlighted td.yui-dt-asc,.yui-skin-sam .yui-dt-list tr.yui-dt-highlighted td.yui-dt-desc,.yui-skin-sam .yui-dt-list tr.yui-dt-even td.yui-dt-highlighted,.yui-skin-sam .yui-dt-list tr.yui-dt-odd td.yui-dt-highlighted{cursor:pointer;background-color:#B2D2FF;}.yui-skin-sam th.yui-dt-selected,.yui-skin-sam th.yui-dt-selected a{background-color:#446CD7;}.yui-skin-sam tr.yui-dt-selected td,.yui-skin-sam tr.yui-dt-selected td.yui-dt-asc,.yui-skin-sam tr.yui-dt-selected td.yui-dt-desc{background-color:#426FD9;color:#FFF;}.yui-skin-sam tr.yui-dt-even td.yui-dt-selected,.yui-skin-sam tr.yui-dt-odd td.yui-dt-selected{background-color:#446CD7;color:#FFF;}.yui-skin-sam .yui-dt-list th.yui-dt-selected,.yui-skin-sam .yui-dt-list th.yui-dt-selected a{background-color:#446CD7;}
+.yui-skin-sam .yui-dt-list tr.yui-dt-selected td,.yui-skin-sam .yui-dt-list tr.yui-dt-selected td.yui-dt-asc,.yui-skin-sam .yui-dt-list tr.yui-dt-selected td.yui-dt-desc{background-color:#426FD9;color:#FFF;}.yui-skin-sam .yui-dt-list tr.yui-dt-even td.yui-dt-selected,.yui-skin-sam .yui-dt-list tr.yui-dt-odd td.yui-dt-selected{background-color:#446CD7;color:#FFF;}.yui-skin-sam .yui-dt-paginator{display:block;margin:6px 0;white-space:nowrap;}.yui-skin-sam .yui-dt-paginator .yui-dt-first,.yui-skin-sam .yui-dt-paginator .yui-dt-last,.yui-skin-sam .yui-dt-paginator .yui-dt-selected{padding:2px 6px;}.yui-skin-sam .yui-dt-paginator a.yui-dt-first,.yui-skin-sam .yui-dt-paginator a.yui-dt-last{text-decoration:none;}.yui-skin-sam .yui-dt-paginator .yui-dt-previous,.yui-skin-sam .yui-dt-paginator .yui-dt-next{display:none;}.yui-skin-sam a.yui-dt-page{border:1px solid #CBCBCB;padding:2px 6px;text-decoration:none;background-color:#fff;}.yui-skin-sam .yui-dt-selected{border:1px solid #fff;background-color:#fff;}
diff --git a/js/yui/assets/skins/sam/desc.gif b/js/yui/assets/skins/sam/desc.gif
new file mode 100644
index 000000000..c114f290c
--- /dev/null
+++ b/js/yui/assets/skins/sam/desc.gif
Binary files differ
diff --git a/js/yui/assets/skins/sam/dt-arrow-dn.png b/js/yui/assets/skins/sam/dt-arrow-dn.png
new file mode 100644
index 000000000..85fda0bbc
--- /dev/null
+++ b/js/yui/assets/skins/sam/dt-arrow-dn.png
Binary files differ
diff --git a/js/yui/assets/skins/sam/dt-arrow-up.png b/js/yui/assets/skins/sam/dt-arrow-up.png
new file mode 100644
index 000000000..1c674316a
--- /dev/null
+++ b/js/yui/assets/skins/sam/dt-arrow-up.png
Binary files differ
diff --git a/js/yui/assets/skins/sam/editor-knob.gif b/js/yui/assets/skins/sam/editor-knob.gif
new file mode 100644
index 000000000..03feab3b0
--- /dev/null
+++ b/js/yui/assets/skins/sam/editor-knob.gif
Binary files differ
diff --git a/js/yui/assets/skins/sam/editor-sprite-active.gif b/js/yui/assets/skins/sam/editor-sprite-active.gif
new file mode 100644
index 000000000..3e9d4200b
--- /dev/null
+++ b/js/yui/assets/skins/sam/editor-sprite-active.gif
Binary files differ
diff --git a/js/yui/assets/skins/sam/editor-sprite.gif b/js/yui/assets/skins/sam/editor-sprite.gif
new file mode 100644
index 000000000..02042fa14
--- /dev/null
+++ b/js/yui/assets/skins/sam/editor-sprite.gif
Binary files differ
diff --git a/js/yui/assets/skins/sam/editor.css b/js/yui/assets/skins/sam/editor.css
new file mode 100644
index 000000000..1515ce383
--- /dev/null
+++ b/js/yui/assets/skins/sam/editor.css
@@ -0,0 +1,10 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+.yui-busy{cursor:wait!important;}.yui-toolbar-container fieldset,.yui-editor-container fieldset{padding:0;margin:0;border:0;}.yui-toolbar-container legend{display:none;}.yui-skin-sam .yui-toolbar-container .yui-button button,.yui-skin-sam .yui-toolbar-container .yui-button a,.yui-skin-sam .yui-toolbar-container .yui-button a:visited{font-size:0;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-select button,.yui-skin-sam .yui-toolbar-container .yui-toolbar-select a,.yui-skin-sam .yui-toolbar-container .yui-toolbar-select a:visited,.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton button,.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton a,.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton a:visited{font-size:12px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton a.up,.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton a.down{font-size:0;line-height:0;padding:0;}.yui-toolbar-container .yui-toolbar-subcont{padding:.25em 0;zoom:1;}.yui-toolbar-container-collapsed .yui-toolbar-subcont{display:none;}.yui-toolbar-container .yui-toolbar-subcont:after{display:block;clear:both;visibility:hidden;content:'.';height:0;}.yui-toolbar-container span.yui-toolbar-draghandle{cursor:move;border-left:1px solid #999;border-right:1px solid #999;overflow:hidden;text-indent:77777px;width:2px;height:20px;display:block;clear:none;float:left;margin:0 0 0 .2em;}.yui-toolbar-container .yui-toolbar-titlebar.draggable{cursor:move;}.yui-toolbar-container .yui-toolbar-titlebar{position:relative;}.yui-toolbar-container .yui-toolbar-titlebar h2{font-weight:bold;letter-spacing:0;border:none;color:#000;margin:0;padding:.2em;}.yui-toolbar-container .yui-toolbar-titlebar h2 a{text-decoration:none;color:#000;cursor:default;}.yui-toolbar-container.yui-toolbar-grouped span.yui-toolbar-draghandle{height:40px;}.yui-toolbar-container .yui-toolbar-group{float:left;margin-right:.5em;zoom:1;}.yui-toolbar-container .yui-toolbar-group:after{display:block;clear:both;visibility:hidden;content:'.';height:0;}.yui-toolbar-container .yui-toolbar-group h3{font-size:75%;padding:0 0 0 .25em;margin:0;}.yui-toolbar-container span.yui-toolbar-separator{width:2px;padding:0;height:18px;margin:.2em 0 .2em .1em;display:none;float:left;}.yui-toolbar-container.yui-toolbar-grouped span.yui-toolbar-separator{height:45px;*height:50px;}.yui-toolbar-container.yui-toolbar-grouped .yui-toolbar-group span.yui-toolbar-separator{height:18px;display:block;}.yui-toolbar-container ul li{margin:0;padding:0;list-style-type:none;}.yui-toolbar-container .yui-toolbar-nogrouplabels h3{display:none;}.yui-toolbar-container .yui-push-button,.yui-toolbar-container .yui-color-button,.yui-toolbar-container .yui-menu-button{position:relative;cursor:pointer;}.yui-toolbar-container .yui-button .first-child,.yui-toolbar-container .yui-button .first-child a{height:100%;width:100%;overflow:hidden;font-size:0;}.yui-toolbar-container .yui-button-disabled{cursor:default;}.yui-toolbar-container .yui-button-disabled .yui-toolbar-icon{opacity:.5;filter:alpha(opacity=50);}.yui-toolbar-container .yui-button-disabled .up,.yui-toolbar-container .yui-button-disabled .down{opacity:.5;filter:alpha(opacity=50);}.yui-toolbar-container .yui-button a{overflow:hidden;}.yui-toolbar-container .yui-toolbar-select .first-child a{cursor:pointer;}.yui-toolbar-fontname-arial{font-family:Arial;}.yui-toolbar-fontname-arial-black{font-family:Arial Black;}.yui-toolbar-fontname-comic-sans-ms{font-family:Comic Sans MS;}.yui-toolbar-fontname-courier-new{font-family:Courier New;}.yui-toolbar-fontname-times-new-roman{font-family:Times New Roman;}.yui-toolbar-fontname-verdana{font-family:Verdana;}.yui-toolbar-fontname-impact{font-family:Impact;}.yui-toolbar-fontname-lucida-console{font-family:Lucida Console;}.yui-toolbar-fontname-tahoma{font-family:Tahoma;}.yui-toolbar-fontname-trebuchet-ms{font-family:Trebuchet MS;}.yui-toolbar-container .yui-toolbar-spinbutton{position:relative;}.yui-toolbar-container .yui-toolbar-spinbutton .first-child a{z-index:0;opacity:1;}.yui-toolbar-container .yui-toolbar-spinbutton a.up,.yui-toolbar-container .yui-toolbar-spinbutton a.down{position:absolute;display:block;right:0;cursor:pointer;z-index:1;padding:0;margin:0;}.yui-toolbar-container .yui-overlay{position:absolute;}.yui-toolbar-container .yui-overlay ul li{margin:0;list-style-type:none;}.yui-toolbar-container{z-index:1;}.yui-editor-container .yui-editor-editable-container{position:relative;z-index:0;width:100%;}.yui-editor-container .yui-editor-masked{background-color:#CCC;height:100%;width:100%;position:absolute;top:0;left:0;opacity:.5;filter:alpha(opacity=50);}.yui-editor-container iframe{border:0;padding:0;margin:0;zoom:1;display:block;}.yui-editor-container .yui-editor-editable{padding:0;margin:0;}.yui-editor-container .dompath{font-size:85%;}.yui-editor-panel .hd{text-align:left;position:relative;}.yui-editor-panel .hd h3{font-weight:bold;padding:.25em 0 .25em .25em;margin:0;}.yui-editor-panel .bd{width:100%;zoom:1;position:relative;}.yui-editor-panel .bd div.yui-editor-body-cont{padding:.25em .1em;zoom:1;}.yui-editor-panel .bd .gecko form{overflow:auto;}.yui-editor-panel .bd div.yui-editor-body-cont:after{display:block;clear:both;visibility:hidden;content:'.';height:0;}.yui-editor-panel .ft{text-align:right;width:99%;float:left;clear:both;}.yui-editor-panel .ft span.tip{display:block;position:relative;padding:.5em .5em .5em 23px;text-align:left;zoom:1;}.yui-editor-panel label{clear:both;float:left;padding:0;width:100%;text-align:left;zoom:1;}.yui-editor-panel .gecko label{overflow:auto;}.yui-editor-panel label strong{float:left;width:6em;}.yui-editor-panel .removeLink{width:80%;text-align:right;}.yui-editor-panel label input{margin-left:.25em;float:left;}.yui-editor-panel .yui-toolbar-group{margin-bottom:.75em;}.yui-editor-panel .height-width{float:left;}.yui-editor-panel .height-width span{font-style:italic;display:block;float:left;overflow:visible;}.yui-editor-panel .height-width span.info{font-size:70%;margin-top:3px;float:none;}
+.yui-editor-panel .yui-toolbar-bordersize,.yui-editor-panel .yui-toolbar-bordertype{font-size:75%;}.yui-editor-panel .yui-toolbar-container span.yui-toolbar-separator{border:none;}.yui-editor-panel .yui-toolbar-bordersize span a span,.yui-editor-panel .yui-toolbar-bordertype span a span{display:block;height:8px;left:4px;position:absolute;top:3px;_top:-5px;width:24px;text-indent:52px;font-size:0;}.yui-editor-panel .yui-toolbar-bordertype span a span.yui-toolbar-bordertype-solid{border-bottom:1px solid black;}.yui-editor-panel .yui-toolbar-bordertype span a span.yui-toolbar-bordertype-dotted{border-bottom:1px dotted black;}.yui-editor-panel .yui-toolbar-bordertype span a span.yui-toolbar-bordertype-dashed{border-bottom:1px dashed black;}.yui-editor-panel .yui-toolbar-bordersize span a span.yui-toolbar-bordersize-0{*top:0;text-indent:0;font-size:75%;}.yui-editor-panel .yui-toolbar-bordersize span a span.yui-toolbar-bordersize-1{border-bottom:1px solid black;}.yui-editor-panel .yui-toolbar-bordersize span a span.yui-toolbar-bordersize-2{border-bottom:2px solid black;}.yui-editor-panel .yui-toolbar-bordersize span a span.yui-toolbar-bordersize-3{top:2px;*top:-5px;border-bottom:3px solid black;}.yui-editor-panel .yui-toolbar-bordersize span a span.yui-toolbar-bordersize-4{top:1px;*top:-5px;border-bottom:4px solid black;}.yui-editor-panel .yui-toolbar-bordersize span a span.yui-toolbar-bordersize-5{top:1px;*top:-5px;border-bottom:5px solid black;}.yui-toolbar-container .yui-toolbar-bordersize-menu,.yui-toolbar-container .yui-toolbar-bordertype-menu{width:95px!important;}.yui-toolbar-bordersize-menu .yuimenuitemlabel,.yui-toolbar-bordertype-menu .yuimenuitemlabel,.yui-toolbar-bordersize-menu .yuimenuitemlabel,.yui-toolbar-bordertype-menu .yuimenuitemlabel:hover{margin:0 3px 7px 17px;}.yui-toolbar-bordersize-menu .yuimenuitemlabel .checkedindicator,.yui-toolbar-bordertype-menu .yuimenuitemlabel .checkedindicator{position:absolute;left:-12px;*top:14px;*left:0;}.yui-toolbar-bordersize-menu li.yui-toolbar-bordersize-1 a{border-bottom:1px solid black;height:14px;}.yui-toolbar-bordersize-menu li.yui-toolbar-bordersize-2 a{border-bottom:2px solid black;height:14px;}.yui-toolbar-bordersize-menu li.yui-toolbar-bordersize-3 a{border-bottom:3px solid black;height:14px;}.yui-toolbar-bordersize-menu li.yui-toolbar-bordersize-4 a{border-bottom:4px solid black;height:14px;}.yui-toolbar-bordersize-menu li.yui-toolbar-bordersize-5 a{border-bottom:5px solid black;height:14px;}.yui-toolbar-bordertype-menu li.yui-toolbar-bordertype-solid a{border-bottom:1px solid black;height:14px;}.yui-toolbar-bordertype-menu li.yui-toolbar-bordertype-dashed a{border-bottom:1px dashed black;height:14px;}.yui-toolbar-bordertype-menu li.yui-toolbar-bordertype-dotted a{border-bottom:1px dotted black;height:14px;}h2.yui-editor-skipheader,h3.yui-editor-skipheader{height:0;margin:0;padding:0;border:none;width:0;overflow:hidden;position:absolute;}.yui-toolbar-colors{width:133px;zoom:1;display:none;z-index:100;overflow:hidden;}.yui-toolbar-colors:after{display:block;clear:both;visibility:hidden;content:'.';height:0;}.yui-toolbar-colors a{height:9px;width:9px;float:left;display:block;overflow:hidden;text-indent:999px;margin:0;cursor:pointer;border:1px solid #F6F7EE;}.yui-toolbar-colors a:hover{border:1px solid black;}.yui-color-button-menu{overflow:visible;background-color:transparent;}.yui-toolbar-colors span{position:relative;display:block;padding:3px;overflow:hidden;float:left;width:100%;zoom:1;}.yui-toolbar-colors span:after{display:block;clear:both;visibility:hidden;content:'.';height:0;}.yui-toolbar-colors span em{height:35px;width:30px;float:left;display:block;overflow:hidden;text-indent:999px;margin:.75px;border:1px solid black;}.yui-toolbar-colors span strong{font-weight:normal;padding-left:3px;display:block;font-size:85%;float:left;width:65%;}.yui-toolbar-group-undoredo h3,.yui-toolbar-group-insertitem h3,.yui-toolbar-group-indentlist h3{width:68px;}.yui-toolbar-group-indentlist2 h3{width:122px;}.yui-toolbar-group-alignment h3{width:130px;}.yui-skin-sam .yui-editor-container{border:1px solid #808080;}.yui-skin-sam .yui-toolbar-container{zoom:1;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-titlebar{background:url(sprite.png) repeat-x 0 -200px;position:relative;}.yui-skin-sam .yui-editor-container .draggable .yui-toolbar-titlebar{cursor:move;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-titlebar h2{color:#000;font-weight:bold;margin:0;padding:.3em 1em;font-size:100%;text-align:left;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-group h3{color:#808080;font-size:75%;margin:1em 0 0;padding-bottom:0;padding-left:.25em;text-align:left;}.yui-toolbar-container span.yui-toolbar-separator{border:none;text-indent:33px;overflow:hidden;margin:0 .25em;}.yui-skin-sam .yui-toolbar-container{background-color:#F2F2F2;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-subcont{padding:0 1em .35em;border-bottom:1px solid #808080;}.yui-skin-sam .yui-toolbar-container-collapsed .yui-toolbar-titlebar{border-bottom:1px solid #808080;}.yui-skin-sam .yui-editor-container .visible .yui-menu-shadow,.yui-skin-sam .yui-editor-panel .visible .yui-menu-shadow{display:none;}.yui-skin-sam .yui-editor-container ul{list-style-type:none;margin:0;padding:0;}.yui-skin-sam .yui-editor-container ul li{list-style-type:none;margin:0;padding:0;}.yui-skin-sam .yui-toolbar-group ul li.yui-toolbar-groupitem{float:left;}.yui-skin-sam .yui-editor-container .dompath{background-color:#F2F2F2;border-top:1px solid #808080;color:#999;text-align:left;padding:.25em;}.yui-skin-sam .yui-toolbar-container .collapse{background:url(sprite.png) no-repeat 0 -400px;}.yui-skin-sam .yui-toolbar-container .collapsed{background:url(sprite.png) no-repeat 0 -350px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-titlebar span.collapse{cursor:pointer;position:absolute;top:4px;right:2px;display:block;overflow:hidden;height:15px;width:15px;text-indent:9999px;}
+.yui-skin-sam .yui-toolbar-container .yui-push-button,.yui-skin-sam .yui-toolbar-container .yui-color-button,.yui-skin-sam .yui-toolbar-container .yui-menu-button{background:url(sprite.png) repeat-x 0 0;position:relative;display:block;height:22px;width:30px;_font-size:0;margin:0;border-color:#808080;color:#f2f2f2;border-style:solid;border-width:1px 0;zoom:1;}.yui-skin-sam .yui-toolbar-container .yui-push-button a,.yui-skin-sam .yui-toolbar-container .yui-color-button a,.yui-skin-sam .yui-toolbar-container .yui-menu-button a{padding-left:35px;height:20px;text-decoration:none;font-size:0;line-height:2;display:block;color:#000;overflow:hidden;white-space:nowrap;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton a,.yui-skin-sam .yui-toolbar-container .yui-toolbar-select a{font-size:12px;}.yui-skin-sam .yui-toolbar-container .yui-push-button .first-child,.yui-skin-sam .yui-toolbar-container .yui-color-button .first-child,.yui-skin-sam .yui-toolbar-container .yui-menu-button .first-child{border-color:#808080;border-style:solid;border-width:0 1px;margin:0 -1px;display:block;position:relative;}.yui-skin-sam .yui-toolbar-container .yui-push-button-disabled .first-child,.yui-skin-sam .yui-toolbar-container .yui-color-button-disabled .first-child,.yui-skin-sam .yui-toolbar-container .yui-menu-button-disabled .first-child{border-color:#ccc;}.yui-skin-sam .yui-toolbar-container .yui-push-button-disabled a,.yui-skin-sam .yui-toolbar-container .yui-color-button-disabled a,.yui-skin-sam .yui-toolbar-container .yui-menu-button-disabled a{color:#A6A6A6;cursor:default;}.yui-skin-sam .yui-toolbar-container .yui-push-button-disabled,.yui-skin-sam .yui-toolbar-container .yui-color-button-disabled,.yui-skin-sam .yui-toolbar-container .yui-menu-button-disabled{border-color:#ccc;}.yui-skin-sam .yui-toolbar-container .yui-button .first-child{*left:0;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-fontname{width:135px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-heading{width:92px;}.yui-skin-sam .yui-toolbar-container .yui-button-hover{background:url(sprite.png) repeat-x 0 -1300px;border-color:#808080;}.yui-skin-sam .yui-toolbar-container .yui-button-selected{background:url(sprite.png) repeat-x 0 -1700px;border-color:#808080;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-nogrouplabels h3{display:none;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-nogrouplabels .yui-toolbar-group{margin-top:.75em;}.yui-skin-sam .yui-toolbar-container .yui-push-button span.yui-toolbar-icon,.yui-skin-sam .yui-toolbar-container .yui-color-button span.yui-toolbar-icon,.yui-skin-sam .yui-toolbar-container .yui-menu-button span.yui-toolbar-icon{display:block;position:absolute;top:2px;height:18px;width:18px;overflow:hidden;background:url(editor-sprite.gif) no-repeat 30px 30px;}.yui-skin-sam .yui-toolbar-container .yui-button-selected span.yui-toolbar-icon,.yui-skin-sam .yui-toolbar-container .yui-button-hover span.yui-toolbar-icon{background-image:url(editor-sprite-active.gif);}.yui-skin-sam .yui-toolbar-container .visible .yuimenuitemlabel{cursor:pointer;color:#000;*position:relative;}.yui-skin-sam .yui-toolbar-container .yui-button-menu{background-color:#fff;}.yui-skin-sam .yui-toolbar-container .yui-button-menu .yui-menu-body-scrolled{position:relative;}.yui-skin-sam div.yuimenu li.selected{background-color:#B3D4FF;}.yui-skin-sam div.yuimenu li.selected a.selected{color:#000;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-bold span.yui-toolbar-icon{background-position:0 0;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-strikethrough span.yui-toolbar-icon{background-position:0 -108px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-italic span.yui-toolbar-icon{background-position:0 -36px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-undo span.yui-toolbar-icon{background-position:0 -1326px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-redo span.yui-toolbar-icon{background-position:0 -1355px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-underline span.yui-toolbar-icon{background-position:0 -72px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-subscript span.yui-toolbar-icon{background-position:0 -180px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-superscript span.yui-toolbar-icon{background-position:0 -144px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-forecolor span.yui-toolbar-icon{background-position:0 -216px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-backcolor span.yui-toolbar-icon{background-position:0 -288px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-justifyleft span.yui-toolbar-icon{background-position:0 -324px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-justifycenter span.yui-toolbar-icon{background-position:0 -360px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-justifyright span.yui-toolbar-icon{background-position:0 -396px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-justifyfull span.yui-toolbar-icon{background-position:0 -432px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-indent span.yui-toolbar-icon{background-position:0 -720px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-outdent span.yui-toolbar-icon{background-position:0 -684px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-createlink span.yui-toolbar-icon{background-position:0 -792px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-insertimage span.yui-toolbar-icon{background-position:1px -756px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-left span.yui-toolbar-icon{background-position:0 -972px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-right span.yui-toolbar-icon{background-position:0 -936px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-inline span.yui-toolbar-icon{background-position:0 -900px;left:5px;}
+.yui-skin-sam .yui-toolbar-container .yui-toolbar-block span.yui-toolbar-icon{background-position:0 -864px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-bordercolor span.yui-toolbar-icon{background-position:0 -252px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-removeformat span.yui-toolbar-icon{background-position:0 -1080px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-hiddenelements span.yui-toolbar-icon{background-position:0 -1044px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-insertunorderedlist span.yui-toolbar-icon{background-position:0 -468px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-insertorderedlist span.yui-toolbar-icon{background-position:0 -504px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton,.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton .first-child{width:35px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton .first-child a{padding-left:2px;text-align:left;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton span.yui-toolbar-icon{display:none;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton a.up,.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton a.down{right:2px;background:url(editor-sprite.gif) no-repeat 0 -1222px;overflow:hidden;height:6px;width:7px;min-height:0;padding:0;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton a.up{top:2px;background-position:0 -1222px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton a.down{bottom:2px;background-position:0 -1187px;}.yui-skin-sam .yui-toolbar-container select{height:22px;border:1px solid #808080;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-select .first-child a{padding-left:5px;text-align:left;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-select span.yui-toolbar-icon{background:url(editor-sprite.gif) no-repeat 0 -1144px;overflow:hidden;right:-2px;top:0;height:20px;}.yui-skin-sam .yui-editor-panel .yui-color-button-menu .bd{background-color:transparent;border:none;width:135px;}.yui-skin-sam .yui-color-button-menu .yui-toolbar-colors{border:1px solid #808080;}.yui-skin-sam .yui-editor-panel{padding:0;margin:0;border:none;background-color:transparent;overflow:visible;position:absolute;}.yui-skin-sam .yui-editor-panel .hd{margin:10px 0 0;padding:0;border:none;}.yui-skin-sam .yui-editor-panel .hd h3{color:#000;border:1px solid #808080;background:url(sprite.png) repeat-x 0 -200px;width:99%;position:relative;margin:0;padding:3px 0 0 0;font-size:93%;text-indent:5px;height:20px;}.yui-skin-sam .yui-editor-panel .bd{background-color:#F2F2F2;border-left:1px solid #808080;border-right:1px solid #808080;width:99%;margin:0;padding:0;overflow:visible;}.yui-skin-sam .yui-editor-panel ul{list-style-type:none;margin:0;padding:0;}.yui-skin-sam .yui-editor-panel ul li{margin:0;padding:0;}.yui-skin-sam .yui-editor-panel .yui-toolbar-container .yui-toolbar-subcont{padding:0;border:none;margin-top:.35em;}.yui-skin-sam .yui-editor-panel .yui-toolbar-bordersize,.yui-skin-sam .yui-editor-panel .yui-toolbar-bordertype{width:50px;}.yui-skin-sam .yui-editor-panel label{display:block;float:none;padding:4px 0;margin-bottom:7px;}.yui-skin-sam .yui-editor-panel label strong{font-weight:normal;font-size:93%;text-align:right;padding-top:2px;}.yui-skin-sam .yui-editor-panel label input{width:75%;}.yui-skin-sam .yui-editor-panel .createlink_target,.yui-skin-sam .yui-editor-panel .insertimage_target{width:auto;margin-right:5px;}.yui-skin-sam .yui-editor-panel .removeLink{width:98%;}.yui-skin-sam .yui-editor-panel label input.warning{background-color:#FFEE69;}.yui-skin-sam .yui-editor-panel .yui-toolbar-group h3{color:#000;float:left;font-weight:normal;font-size:93%;margin:5px 0 0 0;padding:0 3px 0 0;text-align:right;}.yui-skin-sam .yui-editor-panel .height-width h3{margin:3px 0 0 10px;}.yui-skin-sam .yui-editor-panel .height-width{margin:3px 0 0 35px;*margin-left:14px;width:42%;*width:44%;}.yui-skin-sam .yui-editor-panel .yui-toolbar-group-border{width:190px;}.yui-skin-sam .yui-editor-panel .no-button .yui-toolbar-group-border{width:210px;}.yui-skin-sam .yui-editor-panel .yui-toolbar-group-padding{width:203px;_width:198px;}.yui-skin-sam .yui-editor-panel .no-button .yui-toolbar-group-padding{width:172px;}.yui-skin-sam .yui-editor-panel .yui-toolbar-group-padding h3{margin-left:25px;*margin-left:12px;}.yui-skin-sam .yui-editor-panel .yui-toolbar-group-textflow{width:182px;}.yui-skin-sam .yui-editor-panel .hd{background:none;}.yui-skin-sam .yui-editor-panel .ft{background-color:#F2F2F2;border:1px solid #808080;border-top:none;padding:0;margin:0 0 2px 0;}.yui-skin-sam .yui-editor-panel .hd span.close{background:url(sprite.png) no-repeat 0 -300px;cursor:pointer;display:block;height:16px;overflow:hidden;position:absolute;right:5px;text-indent:500px;top:2px;width:26px;}.yui-skin-sam .yui-editor-panel .ft span.tip{background-color:#EDF5FF;border-top:1px solid #808080;font-size:85%;}.yui-skin-sam .yui-editor-panel .ft span.tip strong{display:block;float:left;margin:0 2px 8px 0;}.yui-skin-sam .yui-editor-panel .ft span.tip span.icon{background:url(editor-sprite.gif) no-repeat 0 -1260px;display:block;height:20px;left:2px;position:absolute;top:8px;width:20px;}.yui-skin-sam .yui-editor-panel .ft span.tip span.icon-info{background-position:2px -1260px;}.yui-skin-sam .yui-editor-panel .ft span.tip span.icon-warn{background-position:2px -1296px;}.yui-skin-sam .yui-editor-panel .hd span.knob{position:absolute;height:10px;width:28px;top:-10px;left:25px;text-indent:9999px;overflow:hidden;background:url(editor-knob.gif) no-repeat 0 0;}.yui-skin-sam .yui-editor-panel .yui-toolbar-container{float:left;width:100%;background-image:none;border:none;}.yui-skin-sam .yui-editor-panel .yui-toolbar-container .bd{background-color:#fff;}.yui-editor-blankimage{background-image:url(blankimage.png);}.yui-skin-sam .yui-editor-container .yui-resize-handle-br{height:11px;width:11px;background-position:-20px -60px;background-color:transparent;}
diff --git a/js/yui/assets/skins/sam/header_background.png b/js/yui/assets/skins/sam/header_background.png
new file mode 100644
index 000000000..3ef7909d3
--- /dev/null
+++ b/js/yui/assets/skins/sam/header_background.png
Binary files differ
diff --git a/js/yui/assets/skins/sam/hue_bg.png b/js/yui/assets/skins/sam/hue_bg.png
new file mode 100644
index 000000000..d9bcdeb5c
--- /dev/null
+++ b/js/yui/assets/skins/sam/hue_bg.png
Binary files differ
diff --git a/js/yui/assets/skins/sam/imagecropper.css b/js/yui/assets/skins/sam/imagecropper.css
new file mode 100644
index 000000000..dad5f8230
--- /dev/null
+++ b/js/yui/assets/skins/sam/imagecropper.css
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+.yui-crop{position:relative;}.yui-crop .yui-crop-mask{position:absolute;top:0;left:0;height:100%;width:100%;}.yui-crop .yui-resize{position:absolute;top:10px;left:10px;border:0;}.yui-crop .yui-crop-resize-mask{position:absolute;top:0;left:0;height:100%;width:100%;background-position:-10px -10px;overflow:hidden;}.yui-skin-sam .yui-crop .yui-crop-mask{background-color:#000;opacity:.5;filter:alpha(opacity=50);}.yui-skin-sam .yui-crop .yui-resize{border:1px dashed #fff;}
diff --git a/js/yui/assets/skins/sam/layout.css b/js/yui/assets/skins/sam/layout.css
new file mode 100644
index 000000000..190f2708a
--- /dev/null
+++ b/js/yui/assets/skins/sam/layout.css
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+.yui-layout-loading{visibility:hidden;}body.yui-layout{overflow:hidden;position:relative;padding:0;margin:0;}.yui-layout-doc{position:relative;overflow:hidden;padding:0;margin:0;}.yui-layout-unit{height:50px;width:50px;padding:0;margin:0;float:none;z-index:0;}.yui-layout-unit-top{position:absolute;top:0;left:0;width:100%;}.yui-layout-unit-left{position:absolute;top:0;left:0;}.yui-layout-unit-right{position:absolute;top:0;right:0;}.yui-layout-unit-bottom{position:absolute;bottom:0;left:0;width:100%;}.yui-layout-unit-center{position:absolute;top:0;left:0;width:100%;}.yui-layout div.yui-layout-hd{position:absolute;top:0;left:0;zoom:1;width:100%;}.yui-layout div.yui-layout-bd{position:absolute;top:0;left:0;zoom:1;width:100%;}.yui-layout .yui-layout-noscroll div.yui-layout-bd{overflow:hidden;}.yui-layout .yui-layout-scroll div.yui-layout-bd{overflow:auto;}.yui-layout div.yui-layout-ft{position:absolute;bottom:0;left:0;width:100%;zoom:1;}.yui-layout .yui-layout-unit div.yui-layout-hd h2{text-align:left;}.yui-layout .yui-layout-unit div.yui-layout-hd .collapse{cursor:pointer;height:13px;position:absolute;right:2px;top:2px;width:17px;font-size:0;}.yui-layout .yui-layout-unit div.yui-layout-hd .close{cursor:pointer;height:13px;position:absolute;right:2px;top:2px;width:17px;font-size:0;}.yui-layout .yui-layout-unit div.yui-layout-hd .collapse-close{right:25px;}.yui-layout .yui-layout-clip{position:absolute;height:20px;background-color:#c0c0c0;display:none;}.yui-layout .yui-layout-clip .collapse{cursor:pointer;height:13px;position:absolute;right:2px;top:2px;width:17px;font-size:0;}.yui-layout .yui-layout-wrap{height:100%;width:100%;position:absolute;left:0;}.yui-skin-sam .yui-layout .yui-resize-proxy{border:none;font-size:0;margin:0;padding:0;}.yui-skin-sam .yui-layout .yui-resize-resizing .yui-resize-handle{display:none;zoom:1;}.yui-skin-sam .yui-layout .yui-resize-proxy div{position:absolute;border:1px solid #808080;background-color:#EDF5FF;}.yui-skin-sam .yui-layout .yui-resize .yui-resize-handle-active{zoom:1;}.yui-skin-sam .yui-layout .yui-resize-proxy .yui-layout-handle-l{width:5px;height:100%;top:0;left:0;zoom:1;}.yui-skin-sam .yui-layout .yui-resize-proxy .yui-layout-handle-r{width:5px;top:0;right:0;height:100%;position:absolute;zoom:1;}.yui-skin-sam .yui-layout .yui-resize-proxy .yui-layout-handle-b{width:100%;bottom:0;left:0;height:5px;}.yui-skin-sam .yui-layout .yui-resize-proxy .yui-layout-handle-t{width:100%;top:0;left:0;height:5px;}.yui-skin-sam .yui-layout .yui-layout-unit-left div.yui-layout-hd .collapse{background:transparent url(layout_sprite.png) no-repeat -20px -160px;border:1px solid #808080;}.yui-skin-sam .yui-layout .yui-layout-clip-left .collapse{background:transparent url(layout_sprite.png) no-repeat -20px -140px;border:1px solid #808080;}.yui-skin-sam .yui-layout .yui-layout-unit-right div.yui-layout-hd .collapse{background:transparent url(layout_sprite.png) no-repeat -20px -200px;border:1px solid #808080;}.yui-skin-sam .yui-layout .yui-layout-clip-right .collapse{background:transparent url(layout_sprite.png) no-repeat -20px -120px;border:1px solid #808080;}.yui-skin-sam .yui-layout .yui-layout-unit-top div.yui-layout-hd .collapse{background:transparent url(layout_sprite.png) no-repeat -20px -220px;border:1px solid #808080;}.yui-skin-sam .yui-layout .yui-layout-clip-top .collapse{background:transparent url(layout_sprite.png) no-repeat -20px -240px;border:1px solid #808080;}.yui-skin-sam .yui-layout .yui-layout-unit-bottom div.yui-layout-hd .collapse{background:transparent url(layout_sprite.png) no-repeat -20px -260px;border:1px solid #808080;}.yui-skin-sam .yui-layout .yui-layout-clip-bottom .collapse{background:transparent url(layout_sprite.png) no-repeat -20px -180px;border:1px solid #808080;}.yui-skin-sam .yui-layout .yui-layout-unit div.yui-layout-hd .close{background:transparent url(layout_sprite.png) no-repeat -20px -100px;border:1px solid #808080;}.yui-skin-sam .yui-layout .yui-layout-hd{background:url(sprite.png) repeat-x 0 -1400px;border:1px solid #808080;}.yui-skin-sam .yui-layout{background-color:#EDF5FF;}.yui-skin-sam .yui-layout .yui-layout-unit div.yui-layout-hd h2{font-weight:bold;color:#fff;padding:3px;margin:0;}.yui-skin-sam .yui-layout .yui-layout-unit div.yui-layout-bd{border:1px solid #808080;border-bottom:none;border-top:none;*border-bottom-width:0;*border-top-width:0;background-color:#f2f2f2;text-align:left;}.yui-skin-sam .yui-layout .yui-layout-unit div.yui-layout-bd-noft{border-bottom:1px solid #808080;}.yui-skin-sam .yui-layout .yui-layout-unit div.yui-layout-bd-nohd{border-top:1px solid #808080;}.yui-skin-sam .yui-layout .yui-layout-clip{position:absolute;height:20px;background-color:#EDF5FF;display:none;border:1px solid #808080;}.yui-skin-sam .yui-layout div.yui-layout-ft{border:1px solid #808080;border-top:none;*border-top-width:0;background-color:#f2f2f2;}.yui-skin-sam .yui-layout-unit .yui-resize-handle{background-color:transparent;zoom:1;}.yui-skin-sam .yui-layout-unit .yui-resize-handle-r{right:0;top:0;background-image:none;zoom:1;}.yui-skin-sam .yui-layout-unit .yui-resize-handle-l{left:0;top:0;background-image:none;zoom:1;}.yui-skin-sam .yui-layout-unit .yui-resize-handle-b{right:0;bottom:0;background-image:none;}.yui-skin-sam .yui-layout-unit .yui-resize-handle-t{right:0;top:0;background-image:none;}.yui-skin-sam .yui-layout-unit .yui-resize-handle-r .yui-layout-resize-knob,.yui-skin-sam .yui-layout-unit .yui-resize-handle-l .yui-layout-resize-knob{position:absolute;height:16px;width:6px;top:45%;left:0;display:block;background:transparent url(layout_sprite.png) no-repeat 0 -5px;}.yui-skin-sam .yui-layout-unit .yui-resize-handle-t .yui-layout-resize-knob,.yui-skin-sam .yui-layout-unit .yui-resize-handle-b .yui-layout-resize-knob{position:absolute;height:6px;width:16px;left:45%;background:transparent url(layout_sprite.png) no-repeat -20px 0;zoom:1;}
diff --git a/js/yui/assets/skins/sam/layout_sprite.png b/js/yui/assets/skins/sam/layout_sprite.png
new file mode 100644
index 000000000..d6fce3c7a
--- /dev/null
+++ b/js/yui/assets/skins/sam/layout_sprite.png
Binary files differ
diff --git a/js/yui/assets/skins/sam/loading.gif b/js/yui/assets/skins/sam/loading.gif
new file mode 100644
index 000000000..0bbf3bc0c
--- /dev/null
+++ b/js/yui/assets/skins/sam/loading.gif
Binary files differ
diff --git a/js/yui/assets/skins/sam/logger.css b/js/yui/assets/skins/sam/logger.css
new file mode 100644
index 000000000..e6ae8456f
--- /dev/null
+++ b/js/yui/assets/skins/sam/logger.css
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+.yui-skin-sam .yui-log{padding:1em;width:31em;background-color:#AAA;color:#000;border:1px solid black;font-family:monospace;font-size:77%;text-align:left;z-index:9000;}.yui-skin-sam .yui-log-container{position:absolute;top:1em;right:1em;}.yui-skin-sam .yui-log input{margin:0;padding:0;font-family:arial;font-size:100%;font-weight:normal;}.yui-skin-sam .yui-log .yui-log-btns{position:relative;float:right;bottom:.25em;}.yui-skin-sam .yui-log .yui-log-hd{margin-top:1em;padding:.5em;background-color:#575757;}.yui-skin-sam .yui-log .yui-log-hd h4{margin:0;padding:0;font-size:108%;font-weight:bold;color:#FFF;}.yui-skin-sam .yui-log .yui-log-bd{width:100%;height:20em;background-color:#FFF;border:1px solid gray;overflow:auto;}.yui-skin-sam .yui-log p{margin:1px;padding:.1em;}.yui-skin-sam .yui-log pre{margin:0;padding:0;}.yui-skin-sam .yui-log pre.yui-log-verbose{white-space:pre-wrap;white-space:-moz-pre-wrap!important;white-space:-pre-wrap;white-space:-o-pre-wrap;word-wrap:break-word;}.yui-skin-sam .yui-log .yui-log-ft{margin-top:.5em;}.yui-skin-sam .yui-log .yui-log-ft .yui-log-sourcefilters{width:100%;border-top:1px solid #575757;margin-top:.75em;padding-top:.75em;}.yui-skin-sam .yui-log .yui-log-filtergrp{margin-right:.5em;}.yui-skin-sam .yui-log .info{background-color:#A7CC25;}.yui-skin-sam .yui-log .warn{background-color:#F58516;}.yui-skin-sam .yui-log .error{background-color:#E32F0B;}.yui-skin-sam .yui-log .time{background-color:#A6C9D7;}.yui-skin-sam .yui-log .window{background-color:#F2E886;}
diff --git a/js/yui/assets/skins/sam/menu-button-arrow-disabled.png b/js/yui/assets/skins/sam/menu-button-arrow-disabled.png
new file mode 100644
index 000000000..8cef2abb3
--- /dev/null
+++ b/js/yui/assets/skins/sam/menu-button-arrow-disabled.png
Binary files differ
diff --git a/js/yui/assets/skins/sam/menu-button-arrow.png b/js/yui/assets/skins/sam/menu-button-arrow.png
new file mode 100644
index 000000000..f03dfee4e
--- /dev/null
+++ b/js/yui/assets/skins/sam/menu-button-arrow.png
Binary files differ
diff --git a/js/yui/assets/skins/sam/menu.css b/js/yui/assets/skins/sam/menu.css
new file mode 100644
index 000000000..e4b18ca07
--- /dev/null
+++ b/js/yui/assets/skins/sam/menu.css
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+.yuimenu{top:-999em;left:-999em;}.yuimenubar{position:static;}.yuimenu .yuimenu,.yuimenubar .yuimenu{position:absolute;}.yuimenubar li,.yuimenu li{list-style-type:none;}.yuimenubar ul,.yuimenu ul,.yuimenubar li,.yuimenu li,.yuimenu h6,.yuimenubar h6{margin:0;padding:0;}.yuimenuitemlabel,.yuimenubaritemlabel{text-align:left;white-space:nowrap;}.yuimenubar ul{*zoom:1;}.yuimenubar .yuimenu ul{*zoom:normal;}.yuimenubar>.bd>ul:after{content:".";display:block;clear:both;visibility:hidden;height:0;line-height:0;}.yuimenubaritem{float:left;}.yuimenubaritemlabel,.yuimenuitemlabel{display:block;}.yuimenuitemlabel .helptext{font-style:normal;display:block;margin:-1em 0 0 10em;}.yui-menu-shadow{position:absolute;visibility:hidden;z-index:-1;}.yui-menu-shadow-visible{top:2px;right:-3px;left:-3px;bottom:-3px;visibility:visible;}.hide-scrollbars *{overflow:hidden;}.hide-scrollbars select{display:none;}.yuimenu.show-scrollbars,.yuimenubar.show-scrollbars{overflow:visible;}.yuimenu.hide-scrollbars .yui-menu-shadow,.yuimenubar.hide-scrollbars .yui-menu-shadow{overflow:hidden;}.yuimenu.show-scrollbars .yui-menu-shadow,.yuimenubar.show-scrollbars .yui-menu-shadow{overflow:auto;}.yui-overlay.yui-force-redraw{margin-bottom:1px;}.yui-skin-sam .yuimenubar{font-size:93%;line-height:2;*line-height:1.9;border:solid 1px #808080;background:url(sprite.png) repeat-x 0 0;}.yui-skin-sam .yuimenubarnav .yuimenubaritem{border-right:solid 1px #ccc;}.yui-skin-sam .yuimenubaritemlabel{padding:0 10px;color:#000;text-decoration:none;cursor:default;border-style:solid;border-color:#808080;border-width:1px 0;*position:relative;margin:-1px 0;}.yui-skin-sam .yuimenubaritemlabel:visited{color:#000;}.yui-skin-sam .yuimenubarnav .yuimenubaritemlabel{padding-right:20px;*display:inline-block;}.yui-skin-sam .yuimenubarnav .yuimenubaritemlabel-hassubmenu{background:url(menubaritem_submenuindicator.png) right center no-repeat;}.yui-skin-sam .yuimenubaritem-selected{background:url(sprite.png) repeat-x 0 -1700px;}.yui-skin-sam .yuimenubaritemlabel-selected{border-color:#7D98B8;}.yui-skin-sam .yuimenubarnav .yuimenubaritemlabel-selected{border-left-width:1px;margin-left:-1px;*left:-1px;}.yui-skin-sam .yuimenubaritemlabel-disabled,.yui-skin-sam .yuimenubaritemlabel-disabled:visited{cursor:default;color:#A6A6A6;}.yui-skin-sam .yuimenubarnav .yuimenubaritemlabel-hassubmenu-disabled{background-image:url(menubaritem_submenuindicator_disabled.png);}.yui-skin-sam .yuimenu{font-size:93%;line-height:1.5;*line-height:1.45;}.yui-skin-sam .yuimenubar .yuimenu,.yui-skin-sam .yuimenu .yuimenu{font-size:100%;}.yui-skin-sam .yuimenu .bd{*zoom:1;_zoom:normal;border:solid 1px #808080;background-color:#fff;}.yui-skin-sam .yuimenu .yuimenu .bd{*zoom:normal;}.yui-skin-sam .yuimenu ul{padding:3px 0;border-width:1px 0 0 0;border-color:#ccc;border-style:solid;}.yui-skin-sam .yuimenu ul.first-of-type{border-width:0;}.yui-skin-sam .yuimenu h6{font-weight:bold;border-style:solid;border-color:#ccc;border-width:1px 0 0 0;color:#a4a4a4;padding:3px 10px 0 10px;}.yui-skin-sam .yuimenu ul.hastitle,.yui-skin-sam .yuimenu h6.first-of-type{border-width:0;}.yui-skin-sam .yuimenu .yui-menu-body-scrolled{border-color:#ccc #808080;overflow:hidden;}.yui-skin-sam .yuimenu .topscrollbar,.yui-skin-sam .yuimenu .bottomscrollbar{height:16px;border:solid 1px #808080;background:#fff url(sprite.png) no-repeat 0 0;}.yui-skin-sam .yuimenu .topscrollbar{border-bottom-width:0;background-position:center -950px;}.yui-skin-sam .yuimenu .topscrollbar_disabled{background-position:center -975px;}.yui-skin-sam .yuimenu .bottomscrollbar{border-top-width:0;background-position:center -850px;}.yui-skin-sam .yuimenu .bottomscrollbar_disabled{background-position:center -875px;}.yui-skin-sam .yuimenuitem{_border-bottom:solid 1px #fff;}.yui-skin-sam .yuimenuitemlabel{padding:0 20px;color:#000;text-decoration:none;cursor:default;}.yui-skin-sam .yuimenuitemlabel:visited{color:#000;}.yui-skin-sam .yuimenuitemlabel .helptext{margin-top:-1.5em;*margin-top:-1.45em;}.yui-skin-sam .yuimenuitem-hassubmenu{background-image:url(menuitem_submenuindicator.png);background-position:right center;background-repeat:no-repeat;}.yui-skin-sam .yuimenuitem-checked{background-image:url(menuitem_checkbox.png);background-position:left center;background-repeat:no-repeat;}.yui-skin-sam .yui-menu-shadow-visible{background-color:#000;opacity:.12;filter:alpha(opacity=12);}.yui-skin-sam .yuimenuitem-selected{background-color:#B3D4FF;}.yui-skin-sam .yuimenuitemlabel-disabled,.yui-skin-sam .yuimenuitemlabel-disabled:visited{cursor:default;color:#A6A6A6;}.yui-skin-sam .yuimenuitem-hassubmenu-disabled{background-image:url(menuitem_submenuindicator_disabled.png);}.yui-skin-sam .yuimenuitem-checked-disabled{background-image:url(menuitem_checkbox_disabled.png);}
diff --git a/js/yui/assets/skins/sam/menubaritem_submenuindicator.png b/js/yui/assets/skins/sam/menubaritem_submenuindicator.png
new file mode 100644
index 000000000..030941c9c
--- /dev/null
+++ b/js/yui/assets/skins/sam/menubaritem_submenuindicator.png
Binary files differ
diff --git a/js/yui/assets/skins/sam/menubaritem_submenuindicator_disabled.png b/js/yui/assets/skins/sam/menubaritem_submenuindicator_disabled.png
new file mode 100644
index 000000000..6c1612230
--- /dev/null
+++ b/js/yui/assets/skins/sam/menubaritem_submenuindicator_disabled.png
Binary files differ
diff --git a/js/yui/assets/skins/sam/menuitem_checkbox.png b/js/yui/assets/skins/sam/menuitem_checkbox.png
new file mode 100644
index 000000000..1437a4f4b
--- /dev/null
+++ b/js/yui/assets/skins/sam/menuitem_checkbox.png
Binary files differ
diff --git a/js/yui/assets/skins/sam/menuitem_checkbox_disabled.png b/js/yui/assets/skins/sam/menuitem_checkbox_disabled.png
new file mode 100644
index 000000000..5d5b9985e
--- /dev/null
+++ b/js/yui/assets/skins/sam/menuitem_checkbox_disabled.png
Binary files differ
diff --git a/js/yui/assets/skins/sam/menuitem_submenuindicator.png b/js/yui/assets/skins/sam/menuitem_submenuindicator.png
new file mode 100644
index 000000000..ea4f66029
--- /dev/null
+++ b/js/yui/assets/skins/sam/menuitem_submenuindicator.png
Binary files differ
diff --git a/js/yui/assets/skins/sam/menuitem_submenuindicator_disabled.png b/js/yui/assets/skins/sam/menuitem_submenuindicator_disabled.png
new file mode 100644
index 000000000..427d60a38
--- /dev/null
+++ b/js/yui/assets/skins/sam/menuitem_submenuindicator_disabled.png
Binary files differ
diff --git a/js/yui/assets/skins/sam/paginator.css b/js/yui/assets/skins/sam/paginator.css
new file mode 100644
index 000000000..ca1d99ec5
--- /dev/null
+++ b/js/yui/assets/skins/sam/paginator.css
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+.yui-skin-sam .yui-pg-container{display:block;margin:6px 0;white-space:nowrap;}.yui-skin-sam .yui-pg-first,.yui-skin-sam .yui-pg-previous,.yui-skin-sam .yui-pg-next,.yui-skin-sam .yui-pg-last,.yui-skin-sam .yui-pg-current,.yui-skin-sam .yui-pg-pages,.yui-skin-sam .yui-pg-page{display:inline-block;font-family:arial,helvetica,clean,sans-serif;padding:3px 6px;zoom:1;}.yui-skin-sam .yui-pg-pages{padding:0;}.yui-skin-sam .yui-pg-current{padding:3px 0;}.yui-skin-sam a.yui-pg-first:link,.yui-skin-sam a.yui-pg-first:visited,.yui-skin-sam a.yui-pg-first:active,.yui-skin-sam a.yui-pg-first:hover,.yui-skin-sam a.yui-pg-previous:link,.yui-skin-sam a.yui-pg-previous:visited,.yui-skin-sam a.yui-pg-previous:active,.yui-skin-sam a.yui-pg-previous:hover,.yui-skin-sam a.yui-pg-next:link,.yui-skin-sam a.yui-pg-next:visited,.yui-skin-sam a.yui-pg-next:active,.yui-skin-sam a.yui-pg-next:hover,.yui-skin-sam a.yui-pg-last:link,.yui-skin-sam a.yui-pg-last:visited,.yui-skin-sam a.yui-pg-last:active,.yui-skin-sam a.yui-pg-last:hover,.yui-skin-sam a.yui-pg-page:link,.yui-skin-sam a.yui-pg-page:visited,.yui-skin-sam a.yui-pg-page:active,.yui-skin-sam a.yui-pg-page:hover{color:#06c;text-decoration:underline;outline:0;}.yui-skin-sam span.yui-pg-first,.yui-skin-sam span.yui-pg-previous,.yui-skin-sam span.yui-pg-next,.yui-skin-sam span.yui-pg-last{color:#a6a6a6;}.yui-skin-sam .yui-pg-page{background-color:#fff;border:1px solid #CBCBCB;padding:2px 6px;text-decoration:none;}.yui-skin-sam .yui-pg-current-page{background-color:transparent;border:none;font-weight:bold;padding:3px 6px;}.yui-skin-sam .yui-pg-page{margin-left:1px;margin-right:1px;}.yui-skin-sam .yui-pg-first,.yui-skin-sam .yui-pg-previous{padding-left:0;}.yui-skin-sam .yui-pg-next,.yui-skin-sam .yui-pg-last{padding-right:0;}.yui-skin-sam .yui-pg-current,.yui-skin-sam .yui-pg-rpp-options{margin-left:1em;margin-right:1em;}
diff --git a/js/yui/assets/skins/sam/picker_mask.png b/js/yui/assets/skins/sam/picker_mask.png
new file mode 100644
index 000000000..f8d91932b
--- /dev/null
+++ b/js/yui/assets/skins/sam/picker_mask.png
Binary files differ
diff --git a/js/yui/assets/skins/sam/profilerviewer.css b/js/yui/assets/skins/sam/profilerviewer.css
new file mode 100644
index 000000000..36eb86c9c
--- /dev/null
+++ b/js/yui/assets/skins/sam/profilerviewer.css
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+.yui-skin-sam .yui-pv{background-color:#4a4a4a;font:arial;position:relative;width:99%;z-index:1000;margin-bottom:1em;overflow:hidden;}.yui-skin-sam .yui-pv .hd{background:url(header_background.png) repeat-x;min-height:30px;overflow:hidden;zoom:1;padding:2px 0;}.yui-skin-sam .yui-pv .hd h4{padding:8px 10px;margin:0;font:bold 14px arial;color:#fff;}.yui-skin-sam .yui-pv .hd a{background:#3f6bc3;font:bold 11px arial;color:#fff;padding:4px;margin:3px 10px 0 0;border:1px solid #3f567d;cursor:pointer;display:block;float:right;}.yui-skin-sam .yui-pv .hd span{display:none;}.yui-skin-sam .yui-pv .hd span.yui-pv-busy{height:18px;width:18px;background:url(wait.gif) no-repeat;overflow:hidden;display:block;float:right;margin:4px 10px 0 0;}.yui-skin-sam .yui-pv .hd:after,.yui-pv .bd:after,.yui-skin-sam .yui-pv-chartlegend dl:after{content:'.';visibility:hidden;clear:left;height:0;display:block;}.yui-skin-sam .yui-pv .bd{position:relative;zoom:1;overflow-x:auto;overflow-y:hidden;}.yui-skin-sam .yui-pv .yui-pv-table{padding:0 10px;margin:5px 0 10px 0;}.yui-skin-sam .yui-pv .yui-pv-table .yui-dt-bd td{color:#eeee5c;font:12px arial;}.yui-skin-sam .yui-pv .yui-pv-table tr.yui-dt-odd{background:#929292;}.yui-skin-sam .yui-pv .yui-pv-table tr.yui-dt-even{background:#58637a;}.yui-skin-sam .yui-pv .yui-pv-table tr.yui-dt-even td.yui-dt-asc,.yui-skin-sam .yui-pv .yui-pv-table tr.yui-dt-even td.yui-dt-desc{background:#384970;}.yui-skin-sam .yui-pv .yui-pv-table tr.yui-dt-odd td.yui-dt-asc,.yui-skin-sam .yui-pv .yui-pv-table tr.yui-dt-odd td.yui-dt-desc{background:#6F6E6E;}.yui-skin-sam .yui-pv .yui-pv-table .yui-dt-hd th{background-image:none;background:#2E2D2D;}.yui-skin-sam .yui-pv th.yui-dt-asc .yui-dt-liner{background:transparent url(asc.gif) no-repeat scroll right center;}.yui-skin-sam .yui-pv th.yui-dt-desc .yui-dt-liner{background:transparent url(desc.gif) no-repeat scroll right center;}.yui-skin-sam .yui-pv .yui-pv-table .yui-dt-hd th a{color:#fff;font:bold 12px arial;}.yui-skin-sam .yui-pv .yui-pv-table .yui-dt-hd th.yui-dt-asc,.yui-skin-sam .yui-pv .yui-pv-table .yui-dt-hd th.yui-dt-desc{background:#333;}.yui-skin-sam .yui-pv-chartcontainer{padding:0 10px;}.yui-skin-sam .yui-pv-chart{height:250px;clear:right;margin:5px 0 0 0;color:#fff;}.yui-skin-sam .yui-pv-chartlegend div{float:right;margin:0 0 0 10px;_width:250px;}.yui-skin-sam .yui-pv-chartlegend dl{border:1px solid #999;padding:.2em 0 .2em .5em;zoom:1;margin:5px 0;}.yui-skin-sam .yui-pv-chartlegend dt{float:left;display:block;height:.7em;width:.7em;padding:0;}.yui-skin-sam .yui-pv-chartlegend dd{float:left;display:block;color:#fff;margin:0 1em 0 .5em;padding:0;font:11px arial;}.yui-skin-sam .yui-pv-minimized{height:35px;}.yui-skin-sam .yui-pv-minimized .bd{top:-3000px;}.yui-skin-sam .yui-pv-minimized .hd a.yui-pv-refresh{display:none;}
diff --git a/js/yui/assets/skins/sam/progressbar.css b/js/yui/assets/skins/sam/progressbar.css
new file mode 100644
index 000000000..427d6cbe0
--- /dev/null
+++ b/js/yui/assets/skins/sam/progressbar.css
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+.yui-pb-bar,.yui-pb-mask{width:100%;height:100%;}.yui-pb{position:relative;top:0;left:0;width:200px;height:20px;padding:0;border:none;margin:0;text-align:left;}.yui-pb-mask{position:absolute;top:0;left:0;z-index:2;}.yui-pb-mask div{width:50%;height:50%;background-repeat:no-repeat;padding:0;position:absolute;}.yui-pb-tl{background-position:top left;}.yui-pb-tr{background-position:top right;left:50%;}.yui-pb-bl{background-position:bottom left;top:50%;}.yui-pb-br{background-position:bottom right;left:50%;top:50%;}.yui-pb-bar{margin:0;position:absolute;left:0;top:0;z-index:1;}.yui-pb-ltr .yui-pb-bar{_position:static;}.yui-pb-rtl .yui-pb-bar{background-position:right;}.yui-pb-btt .yui-pb-bar{background-position:left bottom;}.yui-pb-bar{background-color:blue;}.yui-pb{border:thin solid #808080;}.yui-skin-sam .yui-pb{background-color:transparent;border:solid #808080;border-width:1px 0;}.yui-skin-sam .yui-pb-rtl,.yui-skin-sam .yui-pb-ltr{background-image:url(back-h.png);background-repeat:repeat-x;}.yui-skin-sam .yui-pb-ttb,.yui-skin-sam .yui-pb-btt{background-image:url(back-v.png);background-repeat:repeat-y;}.yui-skin-sam .yui-pb-bar{background-color:transparent;}.yui-skin-sam .yui-pb-ltr .yui-pb-bar,.yui-skin-sam .yui-pb-rtl .yui-pb-bar{background-image:url(bar-h.png);background-repeat:repeat-x;}.yui-skin-sam .yui-pb-ttb .yui-pb-bar,.yui-skin-sam .yui-pb-btt .yui-pb-bar{background-image:url(bar-v.png);background-repeat:repeat-y;}.yui-skin-sam .yui-pb-mask{border:solid #808080;border-width:0 1px;margin:0 -1px;}.yui-skin-sam .yui-pb-caption{color:#000;text-align:center;margin:0 auto;}.yui-skin-sam .yui-pb-range{color:#a6a6a6;}
diff --git a/js/yui/assets/skins/sam/resize.css b/js/yui/assets/skins/sam/resize.css
new file mode 100644
index 000000000..c6f8bbcf1
--- /dev/null
+++ b/js/yui/assets/skins/sam/resize.css
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+.yui-resize{position:relative;zoom:1;z-index:0;}.yui-resize-wrap{zoom:1;}.yui-draggable{cursor:move;}.yui-resize .yui-resize-handle{position:absolute;z-index:1;font-size:0;margin:0;padding:0;zoom:1;height:1px;width:1px;}.yui-resize .yui-resize-handle-br{height:5px;width:5px;bottom:0;right:0;cursor:se-resize;z-index:2;zoom:1;}.yui-resize .yui-resize-handle-bl{height:5px;width:5px;bottom:0;left:0;cursor:sw-resize;z-index:2;zoom:1;}.yui-resize .yui-resize-handle-tl{height:5px;width:5px;top:0;left:0;cursor:nw-resize;z-index:2;zoom:1;}.yui-resize .yui-resize-handle-tr{height:5px;width:5px;top:0;right:0;cursor:ne-resize;z-index:2;zoom:1;}.yui-resize .yui-resize-handle-r{width:5px;height:100%;top:0;right:0;cursor:e-resize;zoom:1;}.yui-resize .yui-resize-handle-l{height:100%;width:5px;top:0;left:0;cursor:w-resize;zoom:1;}.yui-resize .yui-resize-handle-b{width:100%;height:5px;bottom:0;right:0;cursor:s-resize;zoom:1;}.yui-resize .yui-resize-handle-t{width:100%;height:5px;top:0;right:0;cursor:n-resize;zoom:1;}.yui-resize-proxy{position:absolute;border:1px dashed #000;visibility:hidden;z-index:1000;}.yui-resize-hover .yui-resize-handle,.yui-resize-hidden .yui-resize-handle{opacity:0;filter:alpha(opacity=0);}.yui-resize-ghost{opacity:.5;filter:alpha(opacity=50);}.yui-resize-knob .yui-resize-handle{height:6px;width:6px;}.yui-resize-knob .yui-resize-handle-tr{right:-3px;top:-3px;}.yui-resize-knob .yui-resize-handle-tl{left:-3px;top:-3px;}.yui-resize-knob .yui-resize-handle-bl{left:-3px;bottom:-3px;}.yui-resize-knob .yui-resize-handle-br{right:-3px;bottom:-3px;}.yui-resize-knob .yui-resize-handle-t{left:45%;top:-3px;}.yui-resize-knob .yui-resize-handle-r{right:-3px;top:45%;}.yui-resize-knob .yui-resize-handle-l{left:-3px;top:45%;}.yui-resize-knob .yui-resize-handle-b{left:45%;bottom:-3px;}.yui-resize-status{position:absolute;top:-999px;left:-999px;padding:2px;font-size:80%;display:none;zoom:1;z-index:9999;}.yui-resize-status strong,.yui-resize-status em{font-weight:normal;font-style:normal;padding:1px;zoom:1;}.yui-skin-sam .yui-resize .yui-resize-handle{background-color:#F2F2F2;zoom:1;}.yui-skin-sam .yui-resize .yui-resize-handle-active{background-color:#7D98B8;zoom:1;}.yui-skin-sam .yui-resize .yui-resize-handle-l,.yui-skin-sam .yui-resize .yui-resize-handle-r,.yui-skin-sam .yui-resize .yui-resize-handle-l-active,.yui-skin-sam .yui-resize .yui-resize-handle-r-active{height:100%;zoom:1;}.yui-skin-sam .yui-resize-knob .yui-resize-handle{border:1px solid #808080;}.yui-skin-sam .yui-resize-hover .yui-resize-handle-active{opacity:1;filter:alpha(opacity=100);}.yui-skin-sam .yui-resize-proxy{border:1px dashed #426FD9;}.yui-skin-sam .yui-resize-status{border:1px solid #A6982B;border-top:1px solid #D4C237;background-color:#FFEE69;color:#000;}.yui-skin-sam .yui-resize-status strong,.yui-skin-sam .yui-resize-status em{float:left;display:block;clear:both;padding:1px;text-align:center;}.yui-skin-sam .yui-resize .yui-resize-handle-inner-r,.yui-skin-sam .yui-resize .yui-resize-handle-inner-l{background:transparent url(layout_sprite.png) no-repeat 0 -5px;height:16px;width:5px;position:absolute;top:45%;}.yui-skin-sam .yui-resize .yui-resize-handle-inner-t,.yui-skin-sam .yui-resize .yui-resize-handle-inner-b{background:transparent url(layout_sprite.png) no-repeat -20px 0;height:5px;width:16px;position:absolute;left:50%;}.yui-skin-sam .yui-resize .yui-resize-handle-br{background-image:url(layout_sprite.png);background-repeat:no-repeat;background-position:-22px -62px;}.yui-skin-sam .yui-resize .yui-resize-handle-tr{background-image:url(layout_sprite.png);background-repeat:no-repeat;background-position:-22px -42px;}.yui-skin-sam .yui-resize .yui-resize-handle-tl{background-image:url(layout_sprite.png);background-repeat:no-repeat;background-position:-22px -82px;}.yui-skin-sam .yui-resize .yui-resize-handle-bl{background-image:url(layout_sprite.png);background-repeat:no-repeat;background-position:-22px -23px;}.yui-skin-sam .yui-resize-knob .yui-resize-handle-t,.yui-skin-sam .yui-resize-knob .yui-resize-handle-r,.yui-skin-sam .yui-resize-knob .yui-resize-handle-b,.yui-skin-sam .yui-resize-knob .yui-resize-handle-l,.yui-skin-sam .yui-resize-knob .yui-resize-handle-tl,.yui-skin-sam .yui-resize-knob .yui-resize-handle-tr,.yui-skin-sam .yui-resize-knob .yui-resize-handle-bl,.yui-skin-sam .yui-resize-knob .yui-resize-handle-br,.yui-skin-sam .yui-resize-knob .yui-resize-handle-inner-t,.yui-skin-sam .yui-resize-knob .yui-resize-handle-inner-r,.yui-skin-sam .yui-resize-knob .yui-resize-handle-inner-b,.yui-skin-sam .yui-resize-knob .yui-resize-handle-inner-l,.yui-skin-sam .yui-resize-knob .yui-resize-handle-inner-tl,.yui-skin-sam .yui-resize-knob .yui-resize-handle-inner-tr,.yui-skin-sam .yui-resize-knob .yui-resize-handle-inner-bl,.yui-skin-sam .yui-resize-knob .yui-resize-handle-inner-br{background-image:none;}.yui-skin-sam .yui-resize-knob .yui-resize-handle-l,.yui-skin-sam .yui-resize-knob .yui-resize-handle-r,.yui-skin-sam .yui-resize-knob .yui-resize-handle-l-active,.yui-skin-sam .yui-resize-knob .yui-resize-handle-r-active{height:6px;width:6px;}.yui-skin-sam .yui-resize-textarea .yui-resize-handle-r{right:-8px;}.yui-skin-sam .yui-resize-textarea .yui-resize-handle-b{bottom:-8px;}.yui-skin-sam .yui-resize-textarea .yui-resize-handle-br{right:-8px;bottom:-8px;}
diff --git a/js/yui/assets/skins/sam/simpleeditor.css b/js/yui/assets/skins/sam/simpleeditor.css
new file mode 100644
index 000000000..1515ce383
--- /dev/null
+++ b/js/yui/assets/skins/sam/simpleeditor.css
@@ -0,0 +1,10 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+.yui-busy{cursor:wait!important;}.yui-toolbar-container fieldset,.yui-editor-container fieldset{padding:0;margin:0;border:0;}.yui-toolbar-container legend{display:none;}.yui-skin-sam .yui-toolbar-container .yui-button button,.yui-skin-sam .yui-toolbar-container .yui-button a,.yui-skin-sam .yui-toolbar-container .yui-button a:visited{font-size:0;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-select button,.yui-skin-sam .yui-toolbar-container .yui-toolbar-select a,.yui-skin-sam .yui-toolbar-container .yui-toolbar-select a:visited,.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton button,.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton a,.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton a:visited{font-size:12px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton a.up,.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton a.down{font-size:0;line-height:0;padding:0;}.yui-toolbar-container .yui-toolbar-subcont{padding:.25em 0;zoom:1;}.yui-toolbar-container-collapsed .yui-toolbar-subcont{display:none;}.yui-toolbar-container .yui-toolbar-subcont:after{display:block;clear:both;visibility:hidden;content:'.';height:0;}.yui-toolbar-container span.yui-toolbar-draghandle{cursor:move;border-left:1px solid #999;border-right:1px solid #999;overflow:hidden;text-indent:77777px;width:2px;height:20px;display:block;clear:none;float:left;margin:0 0 0 .2em;}.yui-toolbar-container .yui-toolbar-titlebar.draggable{cursor:move;}.yui-toolbar-container .yui-toolbar-titlebar{position:relative;}.yui-toolbar-container .yui-toolbar-titlebar h2{font-weight:bold;letter-spacing:0;border:none;color:#000;margin:0;padding:.2em;}.yui-toolbar-container .yui-toolbar-titlebar h2 a{text-decoration:none;color:#000;cursor:default;}.yui-toolbar-container.yui-toolbar-grouped span.yui-toolbar-draghandle{height:40px;}.yui-toolbar-container .yui-toolbar-group{float:left;margin-right:.5em;zoom:1;}.yui-toolbar-container .yui-toolbar-group:after{display:block;clear:both;visibility:hidden;content:'.';height:0;}.yui-toolbar-container .yui-toolbar-group h3{font-size:75%;padding:0 0 0 .25em;margin:0;}.yui-toolbar-container span.yui-toolbar-separator{width:2px;padding:0;height:18px;margin:.2em 0 .2em .1em;display:none;float:left;}.yui-toolbar-container.yui-toolbar-grouped span.yui-toolbar-separator{height:45px;*height:50px;}.yui-toolbar-container.yui-toolbar-grouped .yui-toolbar-group span.yui-toolbar-separator{height:18px;display:block;}.yui-toolbar-container ul li{margin:0;padding:0;list-style-type:none;}.yui-toolbar-container .yui-toolbar-nogrouplabels h3{display:none;}.yui-toolbar-container .yui-push-button,.yui-toolbar-container .yui-color-button,.yui-toolbar-container .yui-menu-button{position:relative;cursor:pointer;}.yui-toolbar-container .yui-button .first-child,.yui-toolbar-container .yui-button .first-child a{height:100%;width:100%;overflow:hidden;font-size:0;}.yui-toolbar-container .yui-button-disabled{cursor:default;}.yui-toolbar-container .yui-button-disabled .yui-toolbar-icon{opacity:.5;filter:alpha(opacity=50);}.yui-toolbar-container .yui-button-disabled .up,.yui-toolbar-container .yui-button-disabled .down{opacity:.5;filter:alpha(opacity=50);}.yui-toolbar-container .yui-button a{overflow:hidden;}.yui-toolbar-container .yui-toolbar-select .first-child a{cursor:pointer;}.yui-toolbar-fontname-arial{font-family:Arial;}.yui-toolbar-fontname-arial-black{font-family:Arial Black;}.yui-toolbar-fontname-comic-sans-ms{font-family:Comic Sans MS;}.yui-toolbar-fontname-courier-new{font-family:Courier New;}.yui-toolbar-fontname-times-new-roman{font-family:Times New Roman;}.yui-toolbar-fontname-verdana{font-family:Verdana;}.yui-toolbar-fontname-impact{font-family:Impact;}.yui-toolbar-fontname-lucida-console{font-family:Lucida Console;}.yui-toolbar-fontname-tahoma{font-family:Tahoma;}.yui-toolbar-fontname-trebuchet-ms{font-family:Trebuchet MS;}.yui-toolbar-container .yui-toolbar-spinbutton{position:relative;}.yui-toolbar-container .yui-toolbar-spinbutton .first-child a{z-index:0;opacity:1;}.yui-toolbar-container .yui-toolbar-spinbutton a.up,.yui-toolbar-container .yui-toolbar-spinbutton a.down{position:absolute;display:block;right:0;cursor:pointer;z-index:1;padding:0;margin:0;}.yui-toolbar-container .yui-overlay{position:absolute;}.yui-toolbar-container .yui-overlay ul li{margin:0;list-style-type:none;}.yui-toolbar-container{z-index:1;}.yui-editor-container .yui-editor-editable-container{position:relative;z-index:0;width:100%;}.yui-editor-container .yui-editor-masked{background-color:#CCC;height:100%;width:100%;position:absolute;top:0;left:0;opacity:.5;filter:alpha(opacity=50);}.yui-editor-container iframe{border:0;padding:0;margin:0;zoom:1;display:block;}.yui-editor-container .yui-editor-editable{padding:0;margin:0;}.yui-editor-container .dompath{font-size:85%;}.yui-editor-panel .hd{text-align:left;position:relative;}.yui-editor-panel .hd h3{font-weight:bold;padding:.25em 0 .25em .25em;margin:0;}.yui-editor-panel .bd{width:100%;zoom:1;position:relative;}.yui-editor-panel .bd div.yui-editor-body-cont{padding:.25em .1em;zoom:1;}.yui-editor-panel .bd .gecko form{overflow:auto;}.yui-editor-panel .bd div.yui-editor-body-cont:after{display:block;clear:both;visibility:hidden;content:'.';height:0;}.yui-editor-panel .ft{text-align:right;width:99%;float:left;clear:both;}.yui-editor-panel .ft span.tip{display:block;position:relative;padding:.5em .5em .5em 23px;text-align:left;zoom:1;}.yui-editor-panel label{clear:both;float:left;padding:0;width:100%;text-align:left;zoom:1;}.yui-editor-panel .gecko label{overflow:auto;}.yui-editor-panel label strong{float:left;width:6em;}.yui-editor-panel .removeLink{width:80%;text-align:right;}.yui-editor-panel label input{margin-left:.25em;float:left;}.yui-editor-panel .yui-toolbar-group{margin-bottom:.75em;}.yui-editor-panel .height-width{float:left;}.yui-editor-panel .height-width span{font-style:italic;display:block;float:left;overflow:visible;}.yui-editor-panel .height-width span.info{font-size:70%;margin-top:3px;float:none;}
+.yui-editor-panel .yui-toolbar-bordersize,.yui-editor-panel .yui-toolbar-bordertype{font-size:75%;}.yui-editor-panel .yui-toolbar-container span.yui-toolbar-separator{border:none;}.yui-editor-panel .yui-toolbar-bordersize span a span,.yui-editor-panel .yui-toolbar-bordertype span a span{display:block;height:8px;left:4px;position:absolute;top:3px;_top:-5px;width:24px;text-indent:52px;font-size:0;}.yui-editor-panel .yui-toolbar-bordertype span a span.yui-toolbar-bordertype-solid{border-bottom:1px solid black;}.yui-editor-panel .yui-toolbar-bordertype span a span.yui-toolbar-bordertype-dotted{border-bottom:1px dotted black;}.yui-editor-panel .yui-toolbar-bordertype span a span.yui-toolbar-bordertype-dashed{border-bottom:1px dashed black;}.yui-editor-panel .yui-toolbar-bordersize span a span.yui-toolbar-bordersize-0{*top:0;text-indent:0;font-size:75%;}.yui-editor-panel .yui-toolbar-bordersize span a span.yui-toolbar-bordersize-1{border-bottom:1px solid black;}.yui-editor-panel .yui-toolbar-bordersize span a span.yui-toolbar-bordersize-2{border-bottom:2px solid black;}.yui-editor-panel .yui-toolbar-bordersize span a span.yui-toolbar-bordersize-3{top:2px;*top:-5px;border-bottom:3px solid black;}.yui-editor-panel .yui-toolbar-bordersize span a span.yui-toolbar-bordersize-4{top:1px;*top:-5px;border-bottom:4px solid black;}.yui-editor-panel .yui-toolbar-bordersize span a span.yui-toolbar-bordersize-5{top:1px;*top:-5px;border-bottom:5px solid black;}.yui-toolbar-container .yui-toolbar-bordersize-menu,.yui-toolbar-container .yui-toolbar-bordertype-menu{width:95px!important;}.yui-toolbar-bordersize-menu .yuimenuitemlabel,.yui-toolbar-bordertype-menu .yuimenuitemlabel,.yui-toolbar-bordersize-menu .yuimenuitemlabel,.yui-toolbar-bordertype-menu .yuimenuitemlabel:hover{margin:0 3px 7px 17px;}.yui-toolbar-bordersize-menu .yuimenuitemlabel .checkedindicator,.yui-toolbar-bordertype-menu .yuimenuitemlabel .checkedindicator{position:absolute;left:-12px;*top:14px;*left:0;}.yui-toolbar-bordersize-menu li.yui-toolbar-bordersize-1 a{border-bottom:1px solid black;height:14px;}.yui-toolbar-bordersize-menu li.yui-toolbar-bordersize-2 a{border-bottom:2px solid black;height:14px;}.yui-toolbar-bordersize-menu li.yui-toolbar-bordersize-3 a{border-bottom:3px solid black;height:14px;}.yui-toolbar-bordersize-menu li.yui-toolbar-bordersize-4 a{border-bottom:4px solid black;height:14px;}.yui-toolbar-bordersize-menu li.yui-toolbar-bordersize-5 a{border-bottom:5px solid black;height:14px;}.yui-toolbar-bordertype-menu li.yui-toolbar-bordertype-solid a{border-bottom:1px solid black;height:14px;}.yui-toolbar-bordertype-menu li.yui-toolbar-bordertype-dashed a{border-bottom:1px dashed black;height:14px;}.yui-toolbar-bordertype-menu li.yui-toolbar-bordertype-dotted a{border-bottom:1px dotted black;height:14px;}h2.yui-editor-skipheader,h3.yui-editor-skipheader{height:0;margin:0;padding:0;border:none;width:0;overflow:hidden;position:absolute;}.yui-toolbar-colors{width:133px;zoom:1;display:none;z-index:100;overflow:hidden;}.yui-toolbar-colors:after{display:block;clear:both;visibility:hidden;content:'.';height:0;}.yui-toolbar-colors a{height:9px;width:9px;float:left;display:block;overflow:hidden;text-indent:999px;margin:0;cursor:pointer;border:1px solid #F6F7EE;}.yui-toolbar-colors a:hover{border:1px solid black;}.yui-color-button-menu{overflow:visible;background-color:transparent;}.yui-toolbar-colors span{position:relative;display:block;padding:3px;overflow:hidden;float:left;width:100%;zoom:1;}.yui-toolbar-colors span:after{display:block;clear:both;visibility:hidden;content:'.';height:0;}.yui-toolbar-colors span em{height:35px;width:30px;float:left;display:block;overflow:hidden;text-indent:999px;margin:.75px;border:1px solid black;}.yui-toolbar-colors span strong{font-weight:normal;padding-left:3px;display:block;font-size:85%;float:left;width:65%;}.yui-toolbar-group-undoredo h3,.yui-toolbar-group-insertitem h3,.yui-toolbar-group-indentlist h3{width:68px;}.yui-toolbar-group-indentlist2 h3{width:122px;}.yui-toolbar-group-alignment h3{width:130px;}.yui-skin-sam .yui-editor-container{border:1px solid #808080;}.yui-skin-sam .yui-toolbar-container{zoom:1;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-titlebar{background:url(sprite.png) repeat-x 0 -200px;position:relative;}.yui-skin-sam .yui-editor-container .draggable .yui-toolbar-titlebar{cursor:move;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-titlebar h2{color:#000;font-weight:bold;margin:0;padding:.3em 1em;font-size:100%;text-align:left;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-group h3{color:#808080;font-size:75%;margin:1em 0 0;padding-bottom:0;padding-left:.25em;text-align:left;}.yui-toolbar-container span.yui-toolbar-separator{border:none;text-indent:33px;overflow:hidden;margin:0 .25em;}.yui-skin-sam .yui-toolbar-container{background-color:#F2F2F2;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-subcont{padding:0 1em .35em;border-bottom:1px solid #808080;}.yui-skin-sam .yui-toolbar-container-collapsed .yui-toolbar-titlebar{border-bottom:1px solid #808080;}.yui-skin-sam .yui-editor-container .visible .yui-menu-shadow,.yui-skin-sam .yui-editor-panel .visible .yui-menu-shadow{display:none;}.yui-skin-sam .yui-editor-container ul{list-style-type:none;margin:0;padding:0;}.yui-skin-sam .yui-editor-container ul li{list-style-type:none;margin:0;padding:0;}.yui-skin-sam .yui-toolbar-group ul li.yui-toolbar-groupitem{float:left;}.yui-skin-sam .yui-editor-container .dompath{background-color:#F2F2F2;border-top:1px solid #808080;color:#999;text-align:left;padding:.25em;}.yui-skin-sam .yui-toolbar-container .collapse{background:url(sprite.png) no-repeat 0 -400px;}.yui-skin-sam .yui-toolbar-container .collapsed{background:url(sprite.png) no-repeat 0 -350px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-titlebar span.collapse{cursor:pointer;position:absolute;top:4px;right:2px;display:block;overflow:hidden;height:15px;width:15px;text-indent:9999px;}
+.yui-skin-sam .yui-toolbar-container .yui-push-button,.yui-skin-sam .yui-toolbar-container .yui-color-button,.yui-skin-sam .yui-toolbar-container .yui-menu-button{background:url(sprite.png) repeat-x 0 0;position:relative;display:block;height:22px;width:30px;_font-size:0;margin:0;border-color:#808080;color:#f2f2f2;border-style:solid;border-width:1px 0;zoom:1;}.yui-skin-sam .yui-toolbar-container .yui-push-button a,.yui-skin-sam .yui-toolbar-container .yui-color-button a,.yui-skin-sam .yui-toolbar-container .yui-menu-button a{padding-left:35px;height:20px;text-decoration:none;font-size:0;line-height:2;display:block;color:#000;overflow:hidden;white-space:nowrap;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton a,.yui-skin-sam .yui-toolbar-container .yui-toolbar-select a{font-size:12px;}.yui-skin-sam .yui-toolbar-container .yui-push-button .first-child,.yui-skin-sam .yui-toolbar-container .yui-color-button .first-child,.yui-skin-sam .yui-toolbar-container .yui-menu-button .first-child{border-color:#808080;border-style:solid;border-width:0 1px;margin:0 -1px;display:block;position:relative;}.yui-skin-sam .yui-toolbar-container .yui-push-button-disabled .first-child,.yui-skin-sam .yui-toolbar-container .yui-color-button-disabled .first-child,.yui-skin-sam .yui-toolbar-container .yui-menu-button-disabled .first-child{border-color:#ccc;}.yui-skin-sam .yui-toolbar-container .yui-push-button-disabled a,.yui-skin-sam .yui-toolbar-container .yui-color-button-disabled a,.yui-skin-sam .yui-toolbar-container .yui-menu-button-disabled a{color:#A6A6A6;cursor:default;}.yui-skin-sam .yui-toolbar-container .yui-push-button-disabled,.yui-skin-sam .yui-toolbar-container .yui-color-button-disabled,.yui-skin-sam .yui-toolbar-container .yui-menu-button-disabled{border-color:#ccc;}.yui-skin-sam .yui-toolbar-container .yui-button .first-child{*left:0;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-fontname{width:135px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-heading{width:92px;}.yui-skin-sam .yui-toolbar-container .yui-button-hover{background:url(sprite.png) repeat-x 0 -1300px;border-color:#808080;}.yui-skin-sam .yui-toolbar-container .yui-button-selected{background:url(sprite.png) repeat-x 0 -1700px;border-color:#808080;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-nogrouplabels h3{display:none;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-nogrouplabels .yui-toolbar-group{margin-top:.75em;}.yui-skin-sam .yui-toolbar-container .yui-push-button span.yui-toolbar-icon,.yui-skin-sam .yui-toolbar-container .yui-color-button span.yui-toolbar-icon,.yui-skin-sam .yui-toolbar-container .yui-menu-button span.yui-toolbar-icon{display:block;position:absolute;top:2px;height:18px;width:18px;overflow:hidden;background:url(editor-sprite.gif) no-repeat 30px 30px;}.yui-skin-sam .yui-toolbar-container .yui-button-selected span.yui-toolbar-icon,.yui-skin-sam .yui-toolbar-container .yui-button-hover span.yui-toolbar-icon{background-image:url(editor-sprite-active.gif);}.yui-skin-sam .yui-toolbar-container .visible .yuimenuitemlabel{cursor:pointer;color:#000;*position:relative;}.yui-skin-sam .yui-toolbar-container .yui-button-menu{background-color:#fff;}.yui-skin-sam .yui-toolbar-container .yui-button-menu .yui-menu-body-scrolled{position:relative;}.yui-skin-sam div.yuimenu li.selected{background-color:#B3D4FF;}.yui-skin-sam div.yuimenu li.selected a.selected{color:#000;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-bold span.yui-toolbar-icon{background-position:0 0;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-strikethrough span.yui-toolbar-icon{background-position:0 -108px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-italic span.yui-toolbar-icon{background-position:0 -36px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-undo span.yui-toolbar-icon{background-position:0 -1326px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-redo span.yui-toolbar-icon{background-position:0 -1355px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-underline span.yui-toolbar-icon{background-position:0 -72px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-subscript span.yui-toolbar-icon{background-position:0 -180px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-superscript span.yui-toolbar-icon{background-position:0 -144px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-forecolor span.yui-toolbar-icon{background-position:0 -216px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-backcolor span.yui-toolbar-icon{background-position:0 -288px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-justifyleft span.yui-toolbar-icon{background-position:0 -324px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-justifycenter span.yui-toolbar-icon{background-position:0 -360px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-justifyright span.yui-toolbar-icon{background-position:0 -396px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-justifyfull span.yui-toolbar-icon{background-position:0 -432px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-indent span.yui-toolbar-icon{background-position:0 -720px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-outdent span.yui-toolbar-icon{background-position:0 -684px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-createlink span.yui-toolbar-icon{background-position:0 -792px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-insertimage span.yui-toolbar-icon{background-position:1px -756px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-left span.yui-toolbar-icon{background-position:0 -972px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-right span.yui-toolbar-icon{background-position:0 -936px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-inline span.yui-toolbar-icon{background-position:0 -900px;left:5px;}
+.yui-skin-sam .yui-toolbar-container .yui-toolbar-block span.yui-toolbar-icon{background-position:0 -864px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-bordercolor span.yui-toolbar-icon{background-position:0 -252px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-removeformat span.yui-toolbar-icon{background-position:0 -1080px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-hiddenelements span.yui-toolbar-icon{background-position:0 -1044px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-insertunorderedlist span.yui-toolbar-icon{background-position:0 -468px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-insertorderedlist span.yui-toolbar-icon{background-position:0 -504px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton,.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton .first-child{width:35px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton .first-child a{padding-left:2px;text-align:left;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton span.yui-toolbar-icon{display:none;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton a.up,.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton a.down{right:2px;background:url(editor-sprite.gif) no-repeat 0 -1222px;overflow:hidden;height:6px;width:7px;min-height:0;padding:0;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton a.up{top:2px;background-position:0 -1222px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton a.down{bottom:2px;background-position:0 -1187px;}.yui-skin-sam .yui-toolbar-container select{height:22px;border:1px solid #808080;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-select .first-child a{padding-left:5px;text-align:left;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-select span.yui-toolbar-icon{background:url(editor-sprite.gif) no-repeat 0 -1144px;overflow:hidden;right:-2px;top:0;height:20px;}.yui-skin-sam .yui-editor-panel .yui-color-button-menu .bd{background-color:transparent;border:none;width:135px;}.yui-skin-sam .yui-color-button-menu .yui-toolbar-colors{border:1px solid #808080;}.yui-skin-sam .yui-editor-panel{padding:0;margin:0;border:none;background-color:transparent;overflow:visible;position:absolute;}.yui-skin-sam .yui-editor-panel .hd{margin:10px 0 0;padding:0;border:none;}.yui-skin-sam .yui-editor-panel .hd h3{color:#000;border:1px solid #808080;background:url(sprite.png) repeat-x 0 -200px;width:99%;position:relative;margin:0;padding:3px 0 0 0;font-size:93%;text-indent:5px;height:20px;}.yui-skin-sam .yui-editor-panel .bd{background-color:#F2F2F2;border-left:1px solid #808080;border-right:1px solid #808080;width:99%;margin:0;padding:0;overflow:visible;}.yui-skin-sam .yui-editor-panel ul{list-style-type:none;margin:0;padding:0;}.yui-skin-sam .yui-editor-panel ul li{margin:0;padding:0;}.yui-skin-sam .yui-editor-panel .yui-toolbar-container .yui-toolbar-subcont{padding:0;border:none;margin-top:.35em;}.yui-skin-sam .yui-editor-panel .yui-toolbar-bordersize,.yui-skin-sam .yui-editor-panel .yui-toolbar-bordertype{width:50px;}.yui-skin-sam .yui-editor-panel label{display:block;float:none;padding:4px 0;margin-bottom:7px;}.yui-skin-sam .yui-editor-panel label strong{font-weight:normal;font-size:93%;text-align:right;padding-top:2px;}.yui-skin-sam .yui-editor-panel label input{width:75%;}.yui-skin-sam .yui-editor-panel .createlink_target,.yui-skin-sam .yui-editor-panel .insertimage_target{width:auto;margin-right:5px;}.yui-skin-sam .yui-editor-panel .removeLink{width:98%;}.yui-skin-sam .yui-editor-panel label input.warning{background-color:#FFEE69;}.yui-skin-sam .yui-editor-panel .yui-toolbar-group h3{color:#000;float:left;font-weight:normal;font-size:93%;margin:5px 0 0 0;padding:0 3px 0 0;text-align:right;}.yui-skin-sam .yui-editor-panel .height-width h3{margin:3px 0 0 10px;}.yui-skin-sam .yui-editor-panel .height-width{margin:3px 0 0 35px;*margin-left:14px;width:42%;*width:44%;}.yui-skin-sam .yui-editor-panel .yui-toolbar-group-border{width:190px;}.yui-skin-sam .yui-editor-panel .no-button .yui-toolbar-group-border{width:210px;}.yui-skin-sam .yui-editor-panel .yui-toolbar-group-padding{width:203px;_width:198px;}.yui-skin-sam .yui-editor-panel .no-button .yui-toolbar-group-padding{width:172px;}.yui-skin-sam .yui-editor-panel .yui-toolbar-group-padding h3{margin-left:25px;*margin-left:12px;}.yui-skin-sam .yui-editor-panel .yui-toolbar-group-textflow{width:182px;}.yui-skin-sam .yui-editor-panel .hd{background:none;}.yui-skin-sam .yui-editor-panel .ft{background-color:#F2F2F2;border:1px solid #808080;border-top:none;padding:0;margin:0 0 2px 0;}.yui-skin-sam .yui-editor-panel .hd span.close{background:url(sprite.png) no-repeat 0 -300px;cursor:pointer;display:block;height:16px;overflow:hidden;position:absolute;right:5px;text-indent:500px;top:2px;width:26px;}.yui-skin-sam .yui-editor-panel .ft span.tip{background-color:#EDF5FF;border-top:1px solid #808080;font-size:85%;}.yui-skin-sam .yui-editor-panel .ft span.tip strong{display:block;float:left;margin:0 2px 8px 0;}.yui-skin-sam .yui-editor-panel .ft span.tip span.icon{background:url(editor-sprite.gif) no-repeat 0 -1260px;display:block;height:20px;left:2px;position:absolute;top:8px;width:20px;}.yui-skin-sam .yui-editor-panel .ft span.tip span.icon-info{background-position:2px -1260px;}.yui-skin-sam .yui-editor-panel .ft span.tip span.icon-warn{background-position:2px -1296px;}.yui-skin-sam .yui-editor-panel .hd span.knob{position:absolute;height:10px;width:28px;top:-10px;left:25px;text-indent:9999px;overflow:hidden;background:url(editor-knob.gif) no-repeat 0 0;}.yui-skin-sam .yui-editor-panel .yui-toolbar-container{float:left;width:100%;background-image:none;border:none;}.yui-skin-sam .yui-editor-panel .yui-toolbar-container .bd{background-color:#fff;}.yui-editor-blankimage{background-image:url(blankimage.png);}.yui-skin-sam .yui-editor-container .yui-resize-handle-br{height:11px;width:11px;background-position:-20px -60px;background-color:transparent;}
diff --git a/js/yui/assets/skins/sam/slider.css b/js/yui/assets/skins/sam/slider.css
new file mode 100644
index 000000000..895f1c693
--- /dev/null
+++ b/js/yui/assets/skins/sam/slider.css
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+.yui-h-slider,.yui-v-slider,.yui-region-slider{position:relative;}.yui-h-slider .yui-slider-thumb,.yui-v-slider .yui-slider-thumb,.yui-region-slider .yui-slider-thumb{position:absolute;cursor:default;}.yui-skin-sam .yui-h-slider{background:url(bg-h.gif) no-repeat 5px 0;height:28px;width:228px;}.yui-skin-sam .yui-h-slider .yui-slider-thumb{top:4px;}.yui-skin-sam .yui-v-slider{background:url(bg-v.gif) no-repeat 12px 0;height:228px;width:48px;}.yui-skin-sam .yui-region-slider{height:228px;width:228px;}
diff --git a/js/yui/assets/skins/sam/split-button-arrow-active.png b/js/yui/assets/skins/sam/split-button-arrow-active.png
new file mode 100644
index 000000000..fa58c5030
--- /dev/null
+++ b/js/yui/assets/skins/sam/split-button-arrow-active.png
Binary files differ
diff --git a/js/yui/assets/skins/sam/split-button-arrow-disabled.png b/js/yui/assets/skins/sam/split-button-arrow-disabled.png
new file mode 100644
index 000000000..0a6a82c64
--- /dev/null
+++ b/js/yui/assets/skins/sam/split-button-arrow-disabled.png
Binary files differ
diff --git a/js/yui/assets/skins/sam/split-button-arrow-focus.png b/js/yui/assets/skins/sam/split-button-arrow-focus.png
new file mode 100644
index 000000000..167d71eb7
--- /dev/null
+++ b/js/yui/assets/skins/sam/split-button-arrow-focus.png
Binary files differ
diff --git a/js/yui/assets/skins/sam/split-button-arrow-hover.png b/js/yui/assets/skins/sam/split-button-arrow-hover.png
new file mode 100644
index 000000000..167d71eb7
--- /dev/null
+++ b/js/yui/assets/skins/sam/split-button-arrow-hover.png
Binary files differ
diff --git a/js/yui/assets/skins/sam/split-button-arrow.png b/js/yui/assets/skins/sam/split-button-arrow.png
new file mode 100644
index 000000000..b33a93ff2
--- /dev/null
+++ b/js/yui/assets/skins/sam/split-button-arrow.png
Binary files differ
diff --git a/js/yui/assets/skins/sam/sprite.png b/js/yui/assets/skins/sam/sprite.png
new file mode 100644
index 000000000..73634d6a2
--- /dev/null
+++ b/js/yui/assets/skins/sam/sprite.png
Binary files differ
diff --git a/js/yui/assets/skins/sam/tabview.css b/js/yui/assets/skins/sam/tabview.css
new file mode 100644
index 000000000..b52b0295a
--- /dev/null
+++ b/js/yui/assets/skins/sam/tabview.css
@@ -0,0 +1,8 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+.yui-navset .yui-nav li,.yui-navset .yui-navset-top .yui-nav li,.yui-navset .yui-navset-bottom .yui-nav li{margin:0 .5em 0 0;}.yui-navset-left .yui-nav li,.yui-navset-right .yui-nav li{margin:0 0 .5em;}.yui-navset .yui-content .yui-hidden{border:0;height:0;width:0;padding:0;position:absolute;left:-999999px;overflow:hidden;visibility:hidden;}.yui-navset .yui-navset-left .yui-nav,.yui-navset .yui-navset-right .yui-nav,.yui-navset-left .yui-nav,.yui-navset-right .yui-nav{width:6em;}.yui-navset-top .yui-nav,.yui-navset-bottom .yui-nav{width:auto;}.yui-navset .yui-navset-left,.yui-navset-left{padding:0 0 0 6em;}.yui-navset-right{padding:0 6em 0 0;}.yui-navset-top,.yui-navset-bottom{padding:auto;}.yui-nav,.yui-nav li{margin:0;padding:0;list-style:none;}.yui-navset li em{font-style:normal;}.yui-navset{position:relative;zoom:1;}.yui-navset .yui-content,.yui-navset .yui-content div{zoom:1;}.yui-navset .yui-content:after{content:'';display:block;clear:both;}.yui-navset .yui-nav li,.yui-navset .yui-navset-top .yui-nav li,.yui-navset .yui-navset-bottom .yui-nav li{display:inline-block;display:-moz-inline-stack;*display:inline;vertical-align:bottom;cursor:pointer;zoom:1;}.yui-navset-left .yui-nav li,.yui-navset-right .yui-nav li{display:block;}.yui-navset .yui-nav a{position:relative;}.yui-navset .yui-nav li a,.yui-navset-top .yui-nav li a,.yui-navset-bottom .yui-nav li a{display:block;display:inline-block;vertical-align:bottom;zoom:1;}.yui-navset-left .yui-nav li a,.yui-navset-right .yui-nav li a{display:block;}.yui-navset-bottom .yui-nav li a{vertical-align:text-top;}.yui-navset .yui-nav li a em,.yui-navset-top .yui-nav li a em,.yui-navset-bottom .yui-nav li a em{display:block;}.yui-navset .yui-navset-left .yui-nav,.yui-navset .yui-navset-right .yui-nav,.yui-navset-left .yui-nav,.yui-navset-right .yui-nav{position:absolute;z-index:1;}.yui-navset-top .yui-nav,.yui-navset-bottom .yui-nav{position:static;}.yui-navset .yui-navset-left .yui-nav,.yui-navset-left .yui-nav{left:0;right:auto;}.yui-navset .yui-navset-right .yui-nav,.yui-navset-right .yui-nav{right:0;left:auto;}.yui-skin-sam .yui-navset .yui-nav,.yui-skin-sam .yui-navset .yui-navset-top .yui-nav{border:solid #2647a0;border-width:0 0 5px;zoom:1;}.yui-skin-sam .yui-navset .yui-nav li,.yui-skin-sam .yui-navset .yui-navset-top .yui-nav li{margin:0 .16em 0 0;padding:1px 0 0;zoom:1;}.yui-skin-sam .yui-navset .yui-nav .selected,.yui-skin-sam .yui-navset .yui-navset-top .yui-nav .selected{margin:0 .16em -1px 0;}.yui-skin-sam .yui-navset .yui-nav a,.yui-skin-sam .yui-navset .yui-navset-top .yui-nav a{background:#d8d8d8 url(sprite.png) repeat-x;border:solid #a3a3a3;border-width:0 1px;color:#000;position:relative;text-decoration:none;}.yui-skin-sam .yui-navset .yui-nav a em,.yui-skin-sam .yui-navset .yui-navset-top .yui-nav a em{border:solid #a3a3a3;border-width:1px 0 0;cursor:hand;padding:.25em .75em;left:0;right:0;bottom:0;top:-1px;position:relative;}.yui-skin-sam .yui-navset .yui-nav .selected a,.yui-skin-sam .yui-navset .yui-nav .selected a:focus,.yui-skin-sam .yui-navset .yui-nav .selected a:hover{background:#2647a0 url(sprite.png) repeat-x left -1400px;color:#fff;}.yui-skin-sam .yui-navset .yui-nav a:hover,.yui-skin-sam .yui-navset .yui-nav a:focus{background:#bfdaff url(sprite.png) repeat-x left -1300px;outline:0;}.yui-skin-sam .yui-navset .yui-nav .selected a em{padding:.35em .75em;}.yui-skin-sam .yui-navset .yui-nav .selected a,.yui-skin-sam .yui-navset .yui-nav .selected a em{border-color:#243356;}.yui-skin-sam .yui-navset .yui-content{background:#edf5ff;}.yui-skin-sam .yui-navset .yui-content,.yui-skin-sam .yui-navset .yui-navset-top .yui-content{border:1px solid #808080;border-top-color:#243356;padding:.25em .5em;}.yui-skin-sam .yui-navset-left .yui-nav,.yui-skin-sam .yui-navset .yui-navset-left .yui-nav,.yui-skin-sam .yui-navset .yui-navset-right .yui-nav,.yui-skin-sam .yui-navset-right .yui-nav{border-width:0 5px 0 0;Xposition:absolute;top:0;bottom:0;}.yui-skin-sam .yui-navset .yui-navset-right .yui-nav,.yui-skin-sam .yui-navset-right .yui-nav{border-width:0 0 0 5px;}.yui-skin-sam .yui-navset-left .yui-nav li,.yui-skin-sam .yui-navset .yui-navset-left .yui-nav li,.yui-skin-sam .yui-navset-right .yui-nav li{margin:0 0 .16em;padding:0 0 0 1px;}.yui-skin-sam .yui-navset-right .yui-nav li{padding:0 1px 0 0;}.yui-skin-sam .yui-navset-left .yui-nav .selected,.yui-skin-sam .yui-navset .yui-navset-left .yui-nav .selected{margin:0 -1px .16em 0;}.yui-skin-sam .yui-navset-right .yui-nav .selected{margin:0 0 .16em -1px;}.yui-skin-sam .yui-navset-left .yui-nav a,.yui-skin-sam .yui-navset-right .yui-nav a{border-width:1px 0;}.yui-skin-sam .yui-navset-left .yui-nav a em,.yui-skin-sam .yui-navset .yui-navset-left .yui-nav a em,.yui-skin-sam .yui-navset-right .yui-nav a em{border-width:0 0 0 1px;padding:.2em .75em;top:auto;left:-1px;}.yui-skin-sam .yui-navset-right .yui-nav a em{border-width:0 1px 0 0;left:auto;right:-1px;}.yui-skin-sam .yui-navset-left .yui-nav a,.yui-skin-sam .yui-navset-left .yui-nav .selected a,.yui-skin-sam .yui-navset-left .yui-nav a:hover,.yui-skin-sam .yui-navset-right .yui-nav a,.yui-skin-sam .yui-navset-right .yui-nav .selected a,.yui-skin-sam .yui-navset-right .yui-nav a:hover,.yui-skin-sam .yui-navset-bottom .yui-nav a,.yui-skin-sam .yui-navset-bottom .yui-nav .selected a,.yui-skin-sam .yui-navset-bottom .yui-nav a:hover{background-image:none;}.yui-skin-sam .yui-navset-left .yui-content{border:1px solid #808080;border-left-color:#243356;}.yui-skin-sam .yui-navset-bottom .yui-nav,.yui-skin-sam .yui-navset .yui-navset-bottom .yui-nav{border-width:5px 0 0;}.yui-skin-sam .yui-navset .yui-navset-bottom .yui-nav .selected,.yui-skin-sam .yui-navset-bottom .yui-nav .selected{margin:-1px .16em 0 0;}.yui-skin-sam .yui-navset .yui-navset-bottom .yui-nav li,.yui-skin-sam .yui-navset-bottom .yui-nav li{padding:0 0 1px 0;vertical-align:top;}.yui-skin-sam .yui-navset .yui-navset-bottom .yui-nav a em,.yui-skin-sam .yui-navset-bottom .yui-nav a em{border-width:0 0 1px;top:auto;bottom:-1px;}
+.yui-skin-sam .yui-navset-bottom .yui-content,.yui-skin-sam .yui-navset .yui-navset-bottom .yui-content{border:1px solid #808080;border-bottom-color:#243356;}
diff --git a/js/yui/assets/skins/sam/treeview-loading.gif b/js/yui/assets/skins/sam/treeview-loading.gif
new file mode 100644
index 000000000..0bbf3bc0c
--- /dev/null
+++ b/js/yui/assets/skins/sam/treeview-loading.gif
Binary files differ
diff --git a/js/yui/assets/skins/sam/treeview-sprite.gif b/js/yui/assets/skins/sam/treeview-sprite.gif
new file mode 100644
index 000000000..8fb3f0137
--- /dev/null
+++ b/js/yui/assets/skins/sam/treeview-sprite.gif
Binary files differ
diff --git a/js/yui/assets/skins/sam/treeview.css b/js/yui/assets/skins/sam/treeview.css
new file mode 100644
index 000000000..804afb5f5
--- /dev/null
+++ b/js/yui/assets/skins/sam/treeview.css
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+table.ygtvtable{margin-bottom:0;border:none;border-collapse:collapse;}td.ygtvcell{border:none;padding:0;}a.ygtvspacer{text-decoration:none;outline-style:none;display:block;}.ygtvtn{width:18px;height:22px;background:url(treeview-sprite.gif) 0 -5600px no-repeat;cursor:pointer;}.ygtvtm{width:18px;height:22px;cursor:pointer;background:url(treeview-sprite.gif) 0 -4000px no-repeat;}.ygtvtmh,.ygtvtmhh{width:18px;height:22px;cursor:pointer;background:url(treeview-sprite.gif) 0 -4800px no-repeat;}.ygtvtp{width:18px;height:22px;cursor:pointer;background:url(treeview-sprite.gif) 0 -6400px no-repeat;}.ygtvtph,.ygtvtphh{width:18px;height:22px;cursor:pointer;background:url(treeview-sprite.gif) 0 -7200px no-repeat;}.ygtvln{width:18px;height:22px;background:url(treeview-sprite.gif) 0 -1600px no-repeat;cursor:pointer;}.ygtvlm{width:18px;height:22px;cursor:pointer;background:url(treeview-sprite.gif) 0 0 no-repeat;}.ygtvlmh,.ygtvlmhh{width:18px;height:22px;cursor:pointer;background:url(treeview-sprite.gif) 0 -800px no-repeat;}.ygtvlp{width:18px;height:22px;cursor:pointer;background:url(treeview-sprite.gif) 0 -2400px no-repeat;}.ygtvlph,.ygtvlphh{width:18px;height:22px;cursor:pointer;background:url(treeview-sprite.gif) 0 -3200px no-repeat;cursor:pointer;}.ygtvloading{width:18px;height:22px;background:url(treeview-loading.gif) 0 0 no-repeat;}.ygtvdepthcell{width:18px;height:22px;background:url(treeview-sprite.gif) 0 -8000px no-repeat;}.ygtvblankdepthcell{width:18px;height:22px;}* html .ygtvchildren{height:2%;}.ygtvlabel,.ygtvlabel:link,.ygtvlabel:visited,.ygtvlabel:hover{margin-left:2px;text-decoration:none;background-color:white;cursor:pointer;}.ygtvcontent{cursor:default;}.ygtvspacer{height:22px;width:18px;}.ygtvfocus{background-color:#c0e0e0;border:none;}.ygtvfocus .ygtvlabel,.ygtvfocus .ygtvlabel:link,.ygtvfocus .ygtvlabel:visited,.ygtvfocus .ygtvlabel:hover{background-color:#c0e0e0;}.ygtvfocus a{outline-style:none;}.ygtvok{width:18px;height:22px;background:url(treeview-sprite.gif) 0 -8800px no-repeat;}.ygtvok:hover{background:url(treeview-sprite.gif) 0 -8844px no-repeat;}.ygtvcancel{width:18px;height:22px;background:url(treeview-sprite.gif) 0 -8822px no-repeat;}.ygtvcancel:hover{background:url(treeview-sprite.gif) 0 -8866px no-repeat;}.ygtv-label-editor{background-color:#f2f2f2;border:1px solid silver;position:absolute;display:none;overflow:hidden;margin:auto;z-index:9000;}.ygtv-edit-TextNode{width:190px;}.ygtv-edit-TextNode .ygtvcancel,.ygtv-edit-TextNode .ygtvok{border:none;}.ygtv-edit-TextNode .ygtv-button-container{float:right;}.ygtv-edit-TextNode .ygtv-input input{width:140px;}.ygtv-edit-DateNode .ygtvcancel{border:none;}.ygtv-edit-DateNode .ygtvok{display:none;}.ygtv-edit-DateNode .ygtv-button-container{text-align:right;margin:auto;}.ygtv-highlight .ygtv-highlight1,.ygtv-highlight .ygtv-highlight1 .ygtvlabel{background-color:blue;color:white;}.ygtv-highlight .ygtv-highlight2,.ygtv-highlight .ygtv-highlight2 .ygtvlabel{background-color:silver;}.ygtv-highlight .ygtv-highlight0 .ygtvfocus .ygtvlabel,.ygtv-highlight .ygtv-highlight1 .ygtvfocus .ygtvlabel,.ygtv-highlight .ygtv-highlight2 .ygtvfocus .ygtvlabel{background-color:#c0e0e0;}.ygtv-highlight .ygtvcontent{padding-right:1em;}.ygtv-checkbox .ygtv-highlight0 .ygtvcontent{padding-left:1em;background:url(check0.gif) no-repeat;}.ygtv-checkbox .ygtv-highlight0 .ygtvfocus.ygtvcontent,.ygtv-checkbox .ygtv-highlight1 .ygtvfocus.ygtvcontent,.ygtv-checkbox .ygtv-highlight2 .ygtvfocus.ygtvcontent{background-color:#c0e0e0;}.ygtv-checkbox .ygtv-highlight1 .ygtvcontent{padding-left:1em;background:url(check1.gif) no-repeat;}.ygtv-checkbox .ygtv-highlight2 .ygtvcontent{padding-left:1em;background:url(check2.gif) no-repeat;}
diff --git a/js/yui/assets/skins/sam/wait.gif b/js/yui/assets/skins/sam/wait.gif
new file mode 100644
index 000000000..471c1a4f9
--- /dev/null
+++ b/js/yui/assets/skins/sam/wait.gif
Binary files differ
diff --git a/js/yui/assets/skins/sam/yuitest.css b/js/yui/assets/skins/sam/yuitest.css
new file mode 100644
index 000000000..7e727bb7b
--- /dev/null
+++ b/js/yui/assets/skins/sam/yuitest.css
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+
diff --git a/js/yui/autocomplete/autocomplete-min.js b/js/yui/autocomplete/autocomplete-min.js
new file mode 100644
index 000000000..e9dfe5e94
--- /dev/null
+++ b/js/yui/autocomplete/autocomplete-min.js
@@ -0,0 +1,12 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+YAHOO.widget.DS_JSArray=YAHOO.util.LocalDataSource;YAHOO.widget.DS_JSFunction=YAHOO.util.FunctionDataSource;YAHOO.widget.DS_XHR=function(B,A,D){var C=new YAHOO.util.XHRDataSource(B,D);C._aDeprecatedSchema=A;return C;};YAHOO.widget.DS_ScriptNode=function(B,A,D){var C=new YAHOO.util.ScriptNodeDataSource(B,D);C._aDeprecatedSchema=A;return C;};YAHOO.widget.DS_XHR.TYPE_JSON=YAHOO.util.DataSourceBase.TYPE_JSON;YAHOO.widget.DS_XHR.TYPE_XML=YAHOO.util.DataSourceBase.TYPE_XML;YAHOO.widget.DS_XHR.TYPE_FLAT=YAHOO.util.DataSourceBase.TYPE_TEXT;YAHOO.widget.AutoComplete=function(G,B,J,C){if(G&&B&&J){if(J&&YAHOO.lang.isFunction(J.sendRequest)){this.dataSource=J;}else{return;}this.key=0;var D=J.responseSchema;if(J._aDeprecatedSchema){var K=J._aDeprecatedSchema;if(YAHOO.lang.isArray(K)){if((J.responseType===YAHOO.util.DataSourceBase.TYPE_JSON)||(J.responseType===YAHOO.util.DataSourceBase.TYPE_UNKNOWN)){D.resultsList=K[0];this.key=K[1];D.fields=(K.length<3)?null:K.slice(1);}else{if(J.responseType===YAHOO.util.DataSourceBase.TYPE_XML){D.resultNode=K[0];this.key=K[1];D.fields=K.slice(1);}else{if(J.responseType===YAHOO.util.DataSourceBase.TYPE_TEXT){D.recordDelim=K[0];D.fieldDelim=K[1];}}}J.responseSchema=D;}}if(YAHOO.util.Dom.inDocument(G)){if(YAHOO.lang.isString(G)){this._sName="instance"+YAHOO.widget.AutoComplete._nIndex+" "+G;this._elTextbox=document.getElementById(G);}else{this._sName=(G.id)?"instance"+YAHOO.widget.AutoComplete._nIndex+" "+G.id:"instance"+YAHOO.widget.AutoComplete._nIndex;this._elTextbox=G;}YAHOO.util.Dom.addClass(this._elTextbox,"yui-ac-input");}else{return;}if(YAHOO.util.Dom.inDocument(B)){if(YAHOO.lang.isString(B)){this._elContainer=document.getElementById(B);}else{this._elContainer=B;}if(this._elContainer.style.display=="none"){}var E=this._elContainer.parentNode;var A=E.tagName.toLowerCase();if(A=="div"){YAHOO.util.Dom.addClass(E,"yui-ac");}else{}}else{return;}if(this.dataSource.dataType===YAHOO.util.DataSourceBase.TYPE_LOCAL){this.applyLocalFilter=true;}if(C&&(C.constructor==Object)){for(var I in C){if(I){this[I]=C[I];}}}this._initContainerEl();this._initProps();this._initListEl();this._initContainerHelperEls();var H=this;var F=this._elTextbox;YAHOO.util.Event.addListener(F,"keyup",H._onTextboxKeyUp,H);YAHOO.util.Event.addListener(F,"keydown",H._onTextboxKeyDown,H);YAHOO.util.Event.addListener(F,"focus",H._onTextboxFocus,H);YAHOO.util.Event.addListener(F,"blur",H._onTextboxBlur,H);YAHOO.util.Event.addListener(B,"mouseover",H._onContainerMouseover,H);YAHOO.util.Event.addListener(B,"mouseout",H._onContainerMouseout,H);YAHOO.util.Event.addListener(B,"click",H._onContainerClick,H);YAHOO.util.Event.addListener(B,"scroll",H._onContainerScroll,H);YAHOO.util.Event.addListener(B,"resize",H._onContainerResize,H);YAHOO.util.Event.addListener(F,"keypress",H._onTextboxKeyPress,H);YAHOO.util.Event.addListener(window,"unload",H._onWindowUnload,H);this.textboxFocusEvent=new YAHOO.util.CustomEvent("textboxFocus",this);this.textboxKeyEvent=new YAHOO.util.CustomEvent("textboxKey",this);this.dataRequestEvent=new YAHOO.util.CustomEvent("dataRequest",this);this.dataReturnEvent=new YAHOO.util.CustomEvent("dataReturn",this);this.dataErrorEvent=new YAHOO.util.CustomEvent("dataError",this);this.containerPopulateEvent=new YAHOO.util.CustomEvent("containerPopulate",this);this.containerExpandEvent=new YAHOO.util.CustomEvent("containerExpand",this);this.typeAheadEvent=new YAHOO.util.CustomEvent("typeAhead",this);this.itemMouseOverEvent=new YAHOO.util.CustomEvent("itemMouseOver",this);this.itemMouseOutEvent=new YAHOO.util.CustomEvent("itemMouseOut",this);this.itemArrowToEvent=new YAHOO.util.CustomEvent("itemArrowTo",this);this.itemArrowFromEvent=new YAHOO.util.CustomEvent("itemArrowFrom",this);this.itemSelectEvent=new YAHOO.util.CustomEvent("itemSelect",this);this.unmatchedItemSelectEvent=new YAHOO.util.CustomEvent("unmatchedItemSelect",this);this.selectionEnforceEvent=new YAHOO.util.CustomEvent("selectionEnforce",this);this.containerCollapseEvent=new YAHOO.util.CustomEvent("containerCollapse",this);this.textboxBlurEvent=new YAHOO.util.CustomEvent("textboxBlur",this);this.textboxChangeEvent=new YAHOO.util.CustomEvent("textboxChange",this);F.setAttribute("autocomplete","off");YAHOO.widget.AutoComplete._nIndex++;}else{}};YAHOO.widget.AutoComplete.prototype.dataSource=null;YAHOO.widget.AutoComplete.prototype.applyLocalFilter=null;YAHOO.widget.AutoComplete.prototype.queryMatchCase=false;YAHOO.widget.AutoComplete.prototype.queryMatchContains=false;YAHOO.widget.AutoComplete.prototype.queryMatchSubset=false;YAHOO.widget.AutoComplete.prototype.minQueryLength=1;YAHOO.widget.AutoComplete.prototype.maxResultsDisplayed=10;YAHOO.widget.AutoComplete.prototype.queryDelay=0.2;YAHOO.widget.AutoComplete.prototype.typeAheadDelay=0.5;YAHOO.widget.AutoComplete.prototype.queryInterval=500;YAHOO.widget.AutoComplete.prototype.highlightClassName="yui-ac-highlight";YAHOO.widget.AutoComplete.prototype.prehighlightClassName=null;YAHOO.widget.AutoComplete.prototype.delimChar=null;YAHOO.widget.AutoComplete.prototype.autoHighlight=true;YAHOO.widget.AutoComplete.prototype.typeAhead=false;YAHOO.widget.AutoComplete.prototype.animHoriz=false;YAHOO.widget.AutoComplete.prototype.animVert=true;YAHOO.widget.AutoComplete.prototype.animSpeed=0.3;YAHOO.widget.AutoComplete.prototype.forceSelection=false;YAHOO.widget.AutoComplete.prototype.allowBrowserAutocomplete=true;YAHOO.widget.AutoComplete.prototype.alwaysShowContainer=false;YAHOO.widget.AutoComplete.prototype.useIFrame=false;YAHOO.widget.AutoComplete.prototype.useShadow=false;YAHOO.widget.AutoComplete.prototype.suppressInputUpdate=false;YAHOO.widget.AutoComplete.prototype.resultTypeList=true;YAHOO.widget.AutoComplete.prototype.queryQuestionMark=true;YAHOO.widget.AutoComplete.prototype.autoSnapContainer=true;YAHOO.widget.AutoComplete.prototype.toString=function(){return"AutoComplete "+this._sName;};YAHOO.widget.AutoComplete.prototype.getInputEl=function(){return this._elTextbox;};YAHOO.widget.AutoComplete.prototype.getContainerEl=function(){return this._elContainer;
+};YAHOO.widget.AutoComplete.prototype.isFocused=function(){return this._bFocused;};YAHOO.widget.AutoComplete.prototype.isContainerOpen=function(){return this._bContainerOpen;};YAHOO.widget.AutoComplete.prototype.getListEl=function(){return this._elList;};YAHOO.widget.AutoComplete.prototype.getListItemMatch=function(A){if(A._sResultMatch){return A._sResultMatch;}else{return null;}};YAHOO.widget.AutoComplete.prototype.getListItemData=function(A){if(A._oResultData){return A._oResultData;}else{return null;}};YAHOO.widget.AutoComplete.prototype.getListItemIndex=function(A){if(YAHOO.lang.isNumber(A._nItemIndex)){return A._nItemIndex;}else{return null;}};YAHOO.widget.AutoComplete.prototype.setHeader=function(B){if(this._elHeader){var A=this._elHeader;if(B){A.innerHTML=B;A.style.display="";}else{A.innerHTML="";A.style.display="none";}}};YAHOO.widget.AutoComplete.prototype.setFooter=function(B){if(this._elFooter){var A=this._elFooter;if(B){A.innerHTML=B;A.style.display="";}else{A.innerHTML="";A.style.display="none";}}};YAHOO.widget.AutoComplete.prototype.setBody=function(A){if(this._elBody){var B=this._elBody;YAHOO.util.Event.purgeElement(B,true);if(A){B.innerHTML=A;B.style.display="";}else{B.innerHTML="";B.style.display="none";}this._elList=null;}};YAHOO.widget.AutoComplete.prototype.generateRequest=function(B){var A=this.dataSource.dataType;if(A===YAHOO.util.DataSourceBase.TYPE_XHR){if(!this.dataSource.connMethodPost){B=(this.queryQuestionMark?"?":"")+(this.dataSource.scriptQueryParam||"query")+"="+B+(this.dataSource.scriptQueryAppend?("&"+this.dataSource.scriptQueryAppend):"");}else{B=(this.dataSource.scriptQueryParam||"query")+"="+B+(this.dataSource.scriptQueryAppend?("&"+this.dataSource.scriptQueryAppend):"");}}else{if(A===YAHOO.util.DataSourceBase.TYPE_SCRIPTNODE){B="&"+(this.dataSource.scriptQueryParam||"query")+"="+B+(this.dataSource.scriptQueryAppend?("&"+this.dataSource.scriptQueryAppend):"");}}return B;};YAHOO.widget.AutoComplete.prototype.sendQuery=function(B){this._bFocused=true;var A=(this.delimChar)?this._elTextbox.value+B:B;this._sendQuery(A);};YAHOO.widget.AutoComplete.prototype.snapContainer=function(){var A=this._elTextbox,B=YAHOO.util.Dom.getXY(A);B[1]+=YAHOO.util.Dom.get(A).offsetHeight+2;YAHOO.util.Dom.setXY(this._elContainer,B);};YAHOO.widget.AutoComplete.prototype.expandContainer=function(){this._toggleContainer(true);};YAHOO.widget.AutoComplete.prototype.collapseContainer=function(){this._toggleContainer(false);};YAHOO.widget.AutoComplete.prototype.clearList=function(){var B=this._elList.childNodes,A=B.length-1;for(;A>-1;A--){B[A].style.display="none";}};YAHOO.widget.AutoComplete.prototype.getSubsetMatches=function(E){var D,C,A;for(var B=E.length;B>=this.minQueryLength;B--){A=this.generateRequest(E.substr(0,B));this.dataRequestEvent.fire(this,D,A);C=this.dataSource.getCachedResponse(A);if(C){return this.filterResults.apply(this.dataSource,[E,C,C,{scope:this}]);}}return null;};YAHOO.widget.AutoComplete.prototype.preparseRawResponse=function(C,B,A){var D=((this.responseStripAfter!=="")&&(B.indexOf))?B.indexOf(this.responseStripAfter):-1;if(D!=-1){B=B.substring(0,D);}return B;};YAHOO.widget.AutoComplete.prototype.filterResults=function(K,M,Q,L){if(L&&L.argument&&L.argument.query){K=L.argument.query;}if(K&&K!==""){Q=YAHOO.widget.AutoComplete._cloneObject(Q);var I=L.scope,P=this,C=Q.results,N=[],B=I.maxResultsDisplayed,J=(P.queryMatchCase||I.queryMatchCase),A=(P.queryMatchContains||I.queryMatchContains);for(var D=0,H=C.length;D<H;D++){var F=C[D];var E=null;if(YAHOO.lang.isString(F)){E=F;}else{if(YAHOO.lang.isArray(F)){E=F[0];}else{if(this.responseSchema.fields){var O=this.responseSchema.fields[0].key||this.responseSchema.fields[0];E=F[O];}else{if(this.key){E=F[this.key];}}}}if(YAHOO.lang.isString(E)){var G=(J)?E.indexOf(decodeURIComponent(K)):E.toLowerCase().indexOf(decodeURIComponent(K).toLowerCase());if((!A&&(G===0))||(A&&(G>-1))){N.push(F);}}if(H>B&&N.length===B){break;}}Q.results=N;}else{}return Q;};YAHOO.widget.AutoComplete.prototype.handleResponse=function(C,A,B){if((this instanceof YAHOO.widget.AutoComplete)&&this._sName){this._populateList(C,A,B);}};YAHOO.widget.AutoComplete.prototype.doBeforeLoadData=function(C,A,B){return true;};YAHOO.widget.AutoComplete.prototype.formatResult=function(B,D,A){var C=(A)?A:"";return C;};YAHOO.widget.AutoComplete.prototype.doBeforeExpandContainer=function(D,A,C,B){return true;};YAHOO.widget.AutoComplete.prototype.destroy=function(){var B=this.toString();var A=this._elTextbox;var D=this._elContainer;this.textboxFocusEvent.unsubscribeAll();this.textboxKeyEvent.unsubscribeAll();this.dataRequestEvent.unsubscribeAll();this.dataReturnEvent.unsubscribeAll();this.dataErrorEvent.unsubscribeAll();this.containerPopulateEvent.unsubscribeAll();this.containerExpandEvent.unsubscribeAll();this.typeAheadEvent.unsubscribeAll();this.itemMouseOverEvent.unsubscribeAll();this.itemMouseOutEvent.unsubscribeAll();this.itemArrowToEvent.unsubscribeAll();this.itemArrowFromEvent.unsubscribeAll();this.itemSelectEvent.unsubscribeAll();this.unmatchedItemSelectEvent.unsubscribeAll();this.selectionEnforceEvent.unsubscribeAll();this.containerCollapseEvent.unsubscribeAll();this.textboxBlurEvent.unsubscribeAll();this.textboxChangeEvent.unsubscribeAll();YAHOO.util.Event.purgeElement(A,true);YAHOO.util.Event.purgeElement(D,true);D.innerHTML="";for(var C in this){if(YAHOO.lang.hasOwnProperty(this,C)){this[C]=null;}}};YAHOO.widget.AutoComplete.prototype.textboxFocusEvent=null;YAHOO.widget.AutoComplete.prototype.textboxKeyEvent=null;YAHOO.widget.AutoComplete.prototype.dataRequestEvent=null;YAHOO.widget.AutoComplete.prototype.dataReturnEvent=null;YAHOO.widget.AutoComplete.prototype.dataErrorEvent=null;YAHOO.widget.AutoComplete.prototype.containerPopulateEvent=null;YAHOO.widget.AutoComplete.prototype.containerExpandEvent=null;YAHOO.widget.AutoComplete.prototype.typeAheadEvent=null;YAHOO.widget.AutoComplete.prototype.itemMouseOverEvent=null;YAHOO.widget.AutoComplete.prototype.itemMouseOutEvent=null;
+YAHOO.widget.AutoComplete.prototype.itemArrowToEvent=null;YAHOO.widget.AutoComplete.prototype.itemArrowFromEvent=null;YAHOO.widget.AutoComplete.prototype.itemSelectEvent=null;YAHOO.widget.AutoComplete.prototype.unmatchedItemSelectEvent=null;YAHOO.widget.AutoComplete.prototype.selectionEnforceEvent=null;YAHOO.widget.AutoComplete.prototype.containerCollapseEvent=null;YAHOO.widget.AutoComplete.prototype.textboxBlurEvent=null;YAHOO.widget.AutoComplete.prototype.textboxChangeEvent=null;YAHOO.widget.AutoComplete._nIndex=0;YAHOO.widget.AutoComplete.prototype._sName=null;YAHOO.widget.AutoComplete.prototype._elTextbox=null;YAHOO.widget.AutoComplete.prototype._elContainer=null;YAHOO.widget.AutoComplete.prototype._elContent=null;YAHOO.widget.AutoComplete.prototype._elHeader=null;YAHOO.widget.AutoComplete.prototype._elBody=null;YAHOO.widget.AutoComplete.prototype._elFooter=null;YAHOO.widget.AutoComplete.prototype._elShadow=null;YAHOO.widget.AutoComplete.prototype._elIFrame=null;YAHOO.widget.AutoComplete.prototype._bFocused=false;YAHOO.widget.AutoComplete.prototype._oAnim=null;YAHOO.widget.AutoComplete.prototype._bContainerOpen=false;YAHOO.widget.AutoComplete.prototype._bOverContainer=false;YAHOO.widget.AutoComplete.prototype._elList=null;YAHOO.widget.AutoComplete.prototype._nDisplayedItems=0;YAHOO.widget.AutoComplete.prototype._sCurQuery=null;YAHOO.widget.AutoComplete.prototype._sPastSelections="";YAHOO.widget.AutoComplete.prototype._sInitInputValue=null;YAHOO.widget.AutoComplete.prototype._elCurListItem=null;YAHOO.widget.AutoComplete.prototype._elCurPrehighlightItem=null;YAHOO.widget.AutoComplete.prototype._bItemSelected=false;YAHOO.widget.AutoComplete.prototype._nKeyCode=null;YAHOO.widget.AutoComplete.prototype._nDelayID=-1;YAHOO.widget.AutoComplete.prototype._nTypeAheadDelayID=-1;YAHOO.widget.AutoComplete.prototype._iFrameSrc="javascript:false;";YAHOO.widget.AutoComplete.prototype._queryInterval=null;YAHOO.widget.AutoComplete.prototype._sLastTextboxValue=null;YAHOO.widget.AutoComplete.prototype._initProps=function(){var B=this.minQueryLength;if(!YAHOO.lang.isNumber(B)){this.minQueryLength=1;}var E=this.maxResultsDisplayed;if(!YAHOO.lang.isNumber(E)||(E<1)){this.maxResultsDisplayed=10;}var F=this.queryDelay;if(!YAHOO.lang.isNumber(F)||(F<0)){this.queryDelay=0.2;}var C=this.typeAheadDelay;if(!YAHOO.lang.isNumber(C)||(C<0)){this.typeAheadDelay=0.2;}var A=this.delimChar;if(YAHOO.lang.isString(A)&&(A.length>0)){this.delimChar=[A];}else{if(!YAHOO.lang.isArray(A)){this.delimChar=null;}}var D=this.animSpeed;if((this.animHoriz||this.animVert)&&YAHOO.util.Anim){if(!YAHOO.lang.isNumber(D)||(D<0)){this.animSpeed=0.3;}if(!this._oAnim){this._oAnim=new YAHOO.util.Anim(this._elContent,{},this.animSpeed);}else{this._oAnim.duration=this.animSpeed;}}if(this.forceSelection&&A){}};YAHOO.widget.AutoComplete.prototype._initContainerHelperEls=function(){if(this.useShadow&&!this._elShadow){var A=document.createElement("div");A.className="yui-ac-shadow";A.style.width=0;A.style.height=0;this._elShadow=this._elContainer.appendChild(A);}if(this.useIFrame&&!this._elIFrame){var B=document.createElement("iframe");B.src=this._iFrameSrc;B.frameBorder=0;B.scrolling="no";B.style.position="absolute";B.style.width=0;B.style.height=0;B.style.padding=0;B.tabIndex=-1;B.role="presentation";B.title="Presentational iframe shim";this._elIFrame=this._elContainer.appendChild(B);}};YAHOO.widget.AutoComplete.prototype._initContainerEl=function(){YAHOO.util.Dom.addClass(this._elContainer,"yui-ac-container");if(!this._elContent){var C=document.createElement("div");C.className="yui-ac-content";C.style.display="none";this._elContent=this._elContainer.appendChild(C);var B=document.createElement("div");B.className="yui-ac-hd";B.style.display="none";this._elHeader=this._elContent.appendChild(B);var D=document.createElement("div");D.className="yui-ac-bd";this._elBody=this._elContent.appendChild(D);var A=document.createElement("div");A.className="yui-ac-ft";A.style.display="none";this._elFooter=this._elContent.appendChild(A);}else{}};YAHOO.widget.AutoComplete.prototype._initListEl=function(){var C=this.maxResultsDisplayed,A=this._elList||document.createElement("ul"),B;while(A.childNodes.length<C){B=document.createElement("li");B.style.display="none";B._nItemIndex=A.childNodes.length;A.appendChild(B);}if(!this._elList){var D=this._elBody;YAHOO.util.Event.purgeElement(D,true);D.innerHTML="";this._elList=D.appendChild(A);}this._elBody.style.display="";};YAHOO.widget.AutoComplete.prototype._focus=function(){var A=this;setTimeout(function(){try{A._elTextbox.focus();}catch(B){}},0);};YAHOO.widget.AutoComplete.prototype._enableIntervalDetection=function(){var A=this;if(!A._queryInterval&&A.queryInterval){A._queryInterval=setInterval(function(){A._onInterval();},A.queryInterval);}};YAHOO.widget.AutoComplete.prototype.enableIntervalDetection=YAHOO.widget.AutoComplete.prototype._enableIntervalDetection;YAHOO.widget.AutoComplete.prototype._onInterval=function(){var A=this._elTextbox.value;var B=this._sLastTextboxValue;if(A!=B){this._sLastTextboxValue=A;this._sendQuery(A);}};YAHOO.widget.AutoComplete.prototype._clearInterval=function(){if(this._queryInterval){clearInterval(this._queryInterval);this._queryInterval=null;}};YAHOO.widget.AutoComplete.prototype._isIgnoreKey=function(A){if((A==9)||(A==13)||(A==16)||(A==17)||(A>=18&&A<=20)||(A==27)||(A>=33&&A<=35)||(A>=36&&A<=40)||(A>=44&&A<=45)||(A==229)){return true;}return false;};YAHOO.widget.AutoComplete.prototype._sendQuery=function(D){if(this.minQueryLength<0){this._toggleContainer(false);return;}if(this.delimChar){var A=this._extractQuery(D);D=A.query;this._sPastSelections=A.previous;}if((D&&(D.length<this.minQueryLength))||(!D&&this.minQueryLength>0)){if(this._nDelayID!=-1){clearTimeout(this._nDelayID);}this._toggleContainer(false);return;}D=encodeURIComponent(D);this._nDelayID=-1;if(this.dataSource.queryMatchSubset||this.queryMatchSubset){var C=this.getSubsetMatches(D);if(C){this.handleResponse(D,C,{query:D});return;
+}}if(this.dataSource.responseStripAfter){this.dataSource.doBeforeParseData=this.preparseRawResponse;}if(this.applyLocalFilter){this.dataSource.doBeforeCallback=this.filterResults;}var B=this.generateRequest(D);this.dataRequestEvent.fire(this,D,B);this.dataSource.sendRequest(B,{success:this.handleResponse,failure:this.handleResponse,scope:this,argument:{query:D}});};YAHOO.widget.AutoComplete.prototype._populateListItem=function(B,A,C){B.innerHTML=this.formatResult(A,C,B._sResultMatch);};YAHOO.widget.AutoComplete.prototype._populateList=function(K,F,C){if(this._nTypeAheadDelayID!=-1){clearTimeout(this._nTypeAheadDelayID);}K=(C&&C.query)?C.query:K;var H=this.doBeforeLoadData(K,F,C);if(H&&!F.error){this.dataReturnEvent.fire(this,K,F.results);if(this._bFocused){var M=decodeURIComponent(K);this._sCurQuery=M;this._bItemSelected=false;var R=F.results,A=Math.min(R.length,this.maxResultsDisplayed),J=(this.dataSource.responseSchema.fields)?(this.dataSource.responseSchema.fields[0].key||this.dataSource.responseSchema.fields[0]):0;if(A>0){if(!this._elList||(this._elList.childNodes.length<A)){this._initListEl();}this._initContainerHelperEls();var I=this._elList.childNodes;for(var Q=A-1;Q>=0;Q--){var P=I[Q],E=R[Q];if(this.resultTypeList){var B=[];B[0]=(YAHOO.lang.isString(E))?E:E[J]||E[this.key];var L=this.dataSource.responseSchema.fields;if(YAHOO.lang.isArray(L)&&(L.length>1)){for(var N=1,S=L.length;N<S;N++){B[B.length]=E[L[N].key||L[N]];}}else{if(YAHOO.lang.isArray(E)){B=E;}else{if(YAHOO.lang.isString(E)){B=[E];}else{B[1]=E;}}}E=B;}P._sResultMatch=(YAHOO.lang.isString(E))?E:(YAHOO.lang.isArray(E))?E[0]:(E[J]||"");P._oResultData=E;this._populateListItem(P,E,M);P.style.display="";}if(A<I.length){var G;for(var O=I.length-1;O>=A;O--){G=I[O];G.style.display="none";}}this._nDisplayedItems=A;this.containerPopulateEvent.fire(this,K,R);if(this.autoHighlight){var D=this._elList.firstChild;this._toggleHighlight(D,"to");this.itemArrowToEvent.fire(this,D);this._typeAhead(D,K);}else{this._toggleHighlight(this._elCurListItem,"from");}H=this._doBeforeExpandContainer(this._elTextbox,this._elContainer,K,R);this._toggleContainer(H);}else{this._toggleContainer(false);}return;}}else{this.dataErrorEvent.fire(this,K,F);}};YAHOO.widget.AutoComplete.prototype._doBeforeExpandContainer=function(D,A,C,B){if(this.autoSnapContainer){this.snapContainer();}return this.doBeforeExpandContainer(D,A,C,B);};YAHOO.widget.AutoComplete.prototype._clearSelection=function(){var A=(this.delimChar)?this._extractQuery(this._elTextbox.value):{previous:"",query:this._elTextbox.value};this._elTextbox.value=A.previous;this.selectionEnforceEvent.fire(this,A.query);};YAHOO.widget.AutoComplete.prototype._textMatchesOption=function(){var A=null;for(var B=0;B<this._nDisplayedItems;B++){var C=this._elList.childNodes[B];var D=(""+C._sResultMatch).toLowerCase();if(D==this._sCurQuery.toLowerCase()){A=C;break;}}return(A);};YAHOO.widget.AutoComplete.prototype._typeAhead=function(B,D){if(!this.typeAhead||(this._nKeyCode==8)){return;}var A=this,C=this._elTextbox;if(C.setSelectionRange||C.createTextRange){this._nTypeAheadDelayID=setTimeout(function(){var F=C.value.length;A._updateValue(B);var G=C.value.length;A._selectText(C,F,G);var E=C.value.substr(F,G);A.typeAheadEvent.fire(A,D,E);},(this.typeAheadDelay*1000));}};YAHOO.widget.AutoComplete.prototype._selectText=function(D,A,B){if(D.setSelectionRange){D.setSelectionRange(A,B);}else{if(D.createTextRange){var C=D.createTextRange();C.moveStart("character",A);C.moveEnd("character",B-D.value.length);C.select();}else{D.select();}}};YAHOO.widget.AutoComplete.prototype._extractQuery=function(H){var C=this.delimChar,F=-1,G,E,B=C.length-1,D;for(;B>=0;B--){G=H.lastIndexOf(C[B]);if(G>F){F=G;}}if(C[B]==" "){for(var A=C.length-1;A>=0;A--){if(H[F-1]==C[A]){F--;break;}}}if(F>-1){E=F+1;while(H.charAt(E)==" "){E+=1;}D=H.substring(0,E);H=H.substr(E);}else{D="";}return{previous:D,query:H};};YAHOO.widget.AutoComplete.prototype._toggleContainerHelpers=function(D){var E=this._elContent.offsetWidth+"px";var B=this._elContent.offsetHeight+"px";if(this.useIFrame&&this._elIFrame){var C=this._elIFrame;if(D){C.style.width=E;C.style.height=B;C.style.padding="";}else{C.style.width=0;C.style.height=0;C.style.padding=0;}}if(this.useShadow&&this._elShadow){var A=this._elShadow;if(D){A.style.width=E;A.style.height=B;}else{A.style.width=0;A.style.height=0;}}};YAHOO.widget.AutoComplete.prototype._toggleContainer=function(I){var D=this._elContainer;if(this.alwaysShowContainer&&this._bContainerOpen){return;}if(!I){this._toggleHighlight(this._elCurListItem,"from");this._nDisplayedItems=0;this._sCurQuery=null;if(this._elContent.style.display=="none"){return;}}var A=this._oAnim;if(A&&A.getEl()&&(this.animHoriz||this.animVert)){if(A.isAnimated()){A.stop(true);}var G=this._elContent.cloneNode(true);D.appendChild(G);G.style.top="-9000px";G.style.width="";G.style.height="";G.style.display="";var F=G.offsetWidth;var C=G.offsetHeight;var B=(this.animHoriz)?0:F;var E=(this.animVert)?0:C;A.attributes=(I)?{width:{to:F},height:{to:C}}:{width:{to:B},height:{to:E}};if(I&&!this._bContainerOpen){this._elContent.style.width=B+"px";this._elContent.style.height=E+"px";}else{this._elContent.style.width=F+"px";this._elContent.style.height=C+"px";}D.removeChild(G);G=null;var H=this;var J=function(){A.onComplete.unsubscribeAll();if(I){H._toggleContainerHelpers(true);H._bContainerOpen=I;H.containerExpandEvent.fire(H);}else{H._elContent.style.display="none";H._bContainerOpen=I;H.containerCollapseEvent.fire(H);}};this._toggleContainerHelpers(false);this._elContent.style.display="";A.onComplete.subscribe(J);A.animate();}else{if(I){this._elContent.style.display="";this._toggleContainerHelpers(true);this._bContainerOpen=I;this.containerExpandEvent.fire(this);}else{this._toggleContainerHelpers(false);this._elContent.style.display="none";this._bContainerOpen=I;this.containerCollapseEvent.fire(this);}}};YAHOO.widget.AutoComplete.prototype._toggleHighlight=function(A,C){if(A){var B=this.highlightClassName;
+if(this._elCurListItem){YAHOO.util.Dom.removeClass(this._elCurListItem,B);this._elCurListItem=null;}if((C=="to")&&B){YAHOO.util.Dom.addClass(A,B);this._elCurListItem=A;}}};YAHOO.widget.AutoComplete.prototype._togglePrehighlight=function(B,C){var A=this.prehighlightClassName;if(this._elCurPrehighlightItem){YAHOO.util.Dom.removeClass(this._elCurPrehighlightItem,A);}if(B==this._elCurListItem){return;}if((C=="mouseover")&&A){YAHOO.util.Dom.addClass(B,A);this._elCurPrehighlightItem=B;}else{YAHOO.util.Dom.removeClass(B,A);}};YAHOO.widget.AutoComplete.prototype._updateValue=function(C){if(!this.suppressInputUpdate){var F=this._elTextbox;var E=(this.delimChar)?(this.delimChar[0]||this.delimChar):null;var B=C._sResultMatch;var D="";if(E){D=this._sPastSelections;D+=B+E;if(E!=" "){D+=" ";}}else{D=B;}F.value=D;if(F.type=="textarea"){F.scrollTop=F.scrollHeight;}var A=F.value.length;this._selectText(F,A,A);this._elCurListItem=C;}};YAHOO.widget.AutoComplete.prototype._selectItem=function(A){this._bItemSelected=true;this._updateValue(A);this._sPastSelections=this._elTextbox.value;this._clearInterval();this.itemSelectEvent.fire(this,A,A._oResultData);this._toggleContainer(false);};YAHOO.widget.AutoComplete.prototype._jumpSelection=function(){if(this._elCurListItem){this._selectItem(this._elCurListItem);}else{this._toggleContainer(false);}};YAHOO.widget.AutoComplete.prototype._moveSelection=function(G){if(this._bContainerOpen){var H=this._elCurListItem,D=-1;if(H){D=H._nItemIndex;}var E=(G==40)?(D+1):(D-1);if(E<-2||E>=this._nDisplayedItems){return;}if(H){this._toggleHighlight(H,"from");this.itemArrowFromEvent.fire(this,H);}if(E==-1){if(this.delimChar){this._elTextbox.value=this._sPastSelections+this._sCurQuery;}else{this._elTextbox.value=this._sCurQuery;}return;}if(E==-2){this._toggleContainer(false);return;}var F=this._elList.childNodes[E],B=this._elContent,C=YAHOO.util.Dom.getStyle(B,"overflow"),I=YAHOO.util.Dom.getStyle(B,"overflowY"),A=((C=="auto")||(C=="scroll")||(I=="auto")||(I=="scroll"));if(A&&(E>-1)&&(E<this._nDisplayedItems)){if(G==40){if((F.offsetTop+F.offsetHeight)>(B.scrollTop+B.offsetHeight)){B.scrollTop=(F.offsetTop+F.offsetHeight)-B.offsetHeight;}else{if((F.offsetTop+F.offsetHeight)<B.scrollTop){B.scrollTop=F.offsetTop;}}}else{if(F.offsetTop<B.scrollTop){this._elContent.scrollTop=F.offsetTop;}else{if(F.offsetTop>(B.scrollTop+B.offsetHeight)){this._elContent.scrollTop=(F.offsetTop+F.offsetHeight)-B.offsetHeight;}}}}this._toggleHighlight(F,"to");this.itemArrowToEvent.fire(this,F);if(this.typeAhead){this._updateValue(F);}}};YAHOO.widget.AutoComplete.prototype._onContainerMouseover=function(A,C){var D=YAHOO.util.Event.getTarget(A);var B=D.nodeName.toLowerCase();while(D&&(B!="table")){switch(B){case"body":return;case"li":if(C.prehighlightClassName){C._togglePrehighlight(D,"mouseover");}else{C._toggleHighlight(D,"to");}C.itemMouseOverEvent.fire(C,D);break;case"div":if(YAHOO.util.Dom.hasClass(D,"yui-ac-container")){C._bOverContainer=true;return;}break;default:break;}D=D.parentNode;if(D){B=D.nodeName.toLowerCase();}}};YAHOO.widget.AutoComplete.prototype._onContainerMouseout=function(A,C){var D=YAHOO.util.Event.getTarget(A);var B=D.nodeName.toLowerCase();while(D&&(B!="table")){switch(B){case"body":return;case"li":if(C.prehighlightClassName){C._togglePrehighlight(D,"mouseout");}else{C._toggleHighlight(D,"from");}C.itemMouseOutEvent.fire(C,D);break;case"ul":C._toggleHighlight(C._elCurListItem,"to");break;case"div":if(YAHOO.util.Dom.hasClass(D,"yui-ac-container")){C._bOverContainer=false;return;}break;default:break;}D=D.parentNode;if(D){B=D.nodeName.toLowerCase();}}};YAHOO.widget.AutoComplete.prototype._onContainerClick=function(A,C){var D=YAHOO.util.Event.getTarget(A);var B=D.nodeName.toLowerCase();while(D&&(B!="table")){switch(B){case"body":return;case"li":C._toggleHighlight(D,"to");C._selectItem(D);return;default:break;}D=D.parentNode;if(D){B=D.nodeName.toLowerCase();}}};YAHOO.widget.AutoComplete.prototype._onContainerScroll=function(A,B){B._focus();};YAHOO.widget.AutoComplete.prototype._onContainerResize=function(A,B){B._toggleContainerHelpers(B._bContainerOpen);};YAHOO.widget.AutoComplete.prototype._onTextboxKeyDown=function(A,B){var C=A.keyCode;if(B._nTypeAheadDelayID!=-1){clearTimeout(B._nTypeAheadDelayID);}switch(C){case 9:if(!YAHOO.env.ua.opera&&(navigator.userAgent.toLowerCase().indexOf("mac")==-1)||(YAHOO.env.ua.webkit>420)){if(B._elCurListItem){if(B.delimChar&&(B._nKeyCode!=C)){if(B._bContainerOpen){YAHOO.util.Event.stopEvent(A);}}B._selectItem(B._elCurListItem);}else{B._toggleContainer(false);}}break;case 13:if(!YAHOO.env.ua.opera&&(navigator.userAgent.toLowerCase().indexOf("mac")==-1)||(YAHOO.env.ua.webkit>420)){if(B._elCurListItem){if(B._nKeyCode!=C){if(B._bContainerOpen){YAHOO.util.Event.stopEvent(A);}}B._selectItem(B._elCurListItem);}else{B._toggleContainer(false);}}break;case 27:B._toggleContainer(false);return;case 39:B._jumpSelection();break;case 38:if(B._bContainerOpen){YAHOO.util.Event.stopEvent(A);B._moveSelection(C);}break;case 40:if(B._bContainerOpen){YAHOO.util.Event.stopEvent(A);B._moveSelection(C);}break;default:B._bItemSelected=false;B._toggleHighlight(B._elCurListItem,"from");B.textboxKeyEvent.fire(B,C);break;}if(C===18){B._enableIntervalDetection();}B._nKeyCode=C;};YAHOO.widget.AutoComplete.prototype._onTextboxKeyPress=function(A,B){var C=A.keyCode;if(YAHOO.env.ua.opera||(navigator.userAgent.toLowerCase().indexOf("mac")!=-1)&&(YAHOO.env.ua.webkit<420)){switch(C){case 9:if(B._bContainerOpen){if(B.delimChar){YAHOO.util.Event.stopEvent(A);}if(B._elCurListItem){B._selectItem(B._elCurListItem);}else{B._toggleContainer(false);}}break;case 13:if(B._bContainerOpen){YAHOO.util.Event.stopEvent(A);if(B._elCurListItem){B._selectItem(B._elCurListItem);}else{B._toggleContainer(false);}}break;default:break;}}else{if(C==229){B._enableIntervalDetection();}}};YAHOO.widget.AutoComplete.prototype._onTextboxKeyUp=function(A,C){var B=this.value;C._initProps();var D=A.keyCode;if(C._isIgnoreKey(D)){return;
+}if(C._nDelayID!=-1){clearTimeout(C._nDelayID);}C._nDelayID=setTimeout(function(){C._sendQuery(B);},(C.queryDelay*1000));};YAHOO.widget.AutoComplete.prototype._onTextboxFocus=function(A,B){if(!B._bFocused){B._elTextbox.setAttribute("autocomplete","off");B._bFocused=true;B._sInitInputValue=B._elTextbox.value;B.textboxFocusEvent.fire(B);}};YAHOO.widget.AutoComplete.prototype._onTextboxBlur=function(A,C){if(!C._bOverContainer||(C._nKeyCode==9)){if(!C._bItemSelected){var B=C._textMatchesOption();if(!C._bContainerOpen||(C._bContainerOpen&&(B===null))){if(C.forceSelection){C._clearSelection();}else{C.unmatchedItemSelectEvent.fire(C,C._sCurQuery);}}else{if(C.forceSelection){C._selectItem(B);}}}C._clearInterval();C._bFocused=false;if(C._sInitInputValue!==C._elTextbox.value){C.textboxChangeEvent.fire(C);}C.textboxBlurEvent.fire(C);C._toggleContainer(false);}else{C._focus();}};YAHOO.widget.AutoComplete.prototype._onWindowUnload=function(A,B){if(B&&B._elTextbox&&B.allowBrowserAutocomplete){B._elTextbox.setAttribute("autocomplete","on");}};YAHOO.widget.AutoComplete.prototype.doBeforeSendQuery=function(A){return this.generateRequest(A);};YAHOO.widget.AutoComplete.prototype.getListItems=function(){var C=[],B=this._elList.childNodes;for(var A=B.length-1;A>=0;A--){C[A]=B[A];}return C;};YAHOO.widget.AutoComplete._cloneObject=function(D){if(!YAHOO.lang.isValue(D)){return D;}var F={};if(YAHOO.lang.isFunction(D)){F=D;}else{if(YAHOO.lang.isArray(D)){var E=[];for(var C=0,B=D.length;C<B;C++){E[C]=YAHOO.widget.AutoComplete._cloneObject(D[C]);}F=E;}else{if(YAHOO.lang.isObject(D)){for(var A in D){if(YAHOO.lang.hasOwnProperty(D,A)){if(YAHOO.lang.isValue(D[A])&&YAHOO.lang.isObject(D[A])||YAHOO.lang.isArray(D[A])){F[A]=YAHOO.widget.AutoComplete._cloneObject(D[A]);}else{F[A]=D[A];}}}}else{F=D;}}}return F;};YAHOO.register("autocomplete",YAHOO.widget.AutoComplete,{version:"2.8.2r1",build:"7"}); \ No newline at end of file
diff --git a/js/yui/base/base-min.css b/js/yui/base/base-min.css
new file mode 100644
index 000000000..c354440fe
--- /dev/null
+++ b/js/yui/base/base-min.css
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+body{margin:10px;}h1{font-size:138.5%;}h2{font-size:123.1%;}h3{font-size:108%;}h1,h2,h3{margin:1em 0;}h1,h2,h3,h4,h5,h6,strong,dt{font-weight:bold;}optgroup{font-weight:normal;}abbr,acronym{border-bottom:1px dotted #000;cursor:help;}em{font-style:italic;}del{text-decoration:line-through;}blockquote,ul,ol,dl{margin:1em;}ol,ul,dl{margin-left:2em;}ol li{list-style:decimal outside;}ul li{list-style:disc outside;}dl dd{margin-left:1em;}th,td{border:1px solid #000;padding:.5em;}th{font-weight:bold;text-align:center;}caption{margin-bottom:.5em;text-align:center;}sup{vertical-align:super;}sub{vertical-align:sub;}p,fieldset,table,pre{margin-bottom:1em;}button,input[type="checkbox"],input[type="radio"],input[type="reset"],input[type="submit"]{padding:1px;} \ No newline at end of file
diff --git a/js/yui/base/base.css b/js/yui/base/base.css
new file mode 100644
index 000000000..57f394363
--- /dev/null
+++ b/js/yui/base/base.css
@@ -0,0 +1,131 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+/**
+ * YUI Base
+ * @module base
+ * @namespace yui-
+ * @requires reset, fonts
+*/
+
+body {
+ /* For breathing room between content and viewport. */
+ margin:10px;
+}
+
+h1 {
+ /* 18px via YUI Fonts CSS foundation. */
+ font-size: 138.5%;
+}
+
+h2 {
+ /* 16px via YUI Fonts CSS foundation. */
+ font-size: 123.1%;
+}
+
+h3 {
+ /* 14px via YUI Fonts CSS foundation. */
+ font-size: 108%;
+}
+
+h1,h2,h3 {
+ /* Top & bottom margin based on font size. */
+ margin: 1em 0;
+}
+
+h1,h2,h3,h4,h5,h6,strong,dt {
+ /* Bringing boldness back to headers and the strong element. */
+ font-weight: bold;
+}
+optgroup {
+ font-weight:normal;
+}
+
+abbr,acronym {
+ /* Indicating to users that more info is available. */
+ border-bottom: 1px dotted #000;
+ cursor: help;
+}
+
+em {
+ /* Bringing italics back to the em element. */
+ font-style: italic;
+}
+
+del {
+ /* Striking deleted phrases. */
+ text-decoration: line-through;
+}
+
+blockquote,ul,ol,dl {
+ /* Giving blockquotes and lists room to breath. */
+ margin: 1em;
+}
+
+ol,ul,dl {
+ /* Bringing lists on to the page with breathing room. */
+ margin-left: 2em;
+}
+
+ol li {
+ /* Giving OL's LIs generated numbers. */
+ list-style: decimal outside;
+}
+
+ul li {
+ /* Giving UL's LIs generated disc markers. */
+ list-style: disc outside;
+}
+
+dl dd {
+ /* Giving UL's LIs generated numbers. */
+ margin-left: 1em;
+}
+
+th,td {
+ /* Borders and padding to make the table readable. */
+ border: 1px solid #000;
+ padding: .5em;
+}
+
+th {
+ /* Distinguishing table headers from data cells. */
+ font-weight: bold;
+ text-align: center;
+}
+
+caption {
+ /* Coordinated margin to match cell's padding. */
+ margin-bottom: .5em;
+ /* Centered so it doesn't blend in to other content. */
+ text-align: center;
+}
+
+sup {
+ /* to preserve line-height and selector appearance */
+ vertical-align: super;
+}
+
+sub {
+ /* to preserve line-height and selector appearance */
+ vertical-align: sub;
+}
+
+p,
+fieldset,
+table,
+pre {
+ /* So things don't run into each other. */
+ margin-bottom: 1em;
+}
+/* Opera requires 1px of passing to render with contemporary native chrome */
+button,
+input[type="checkbox"],
+input[type="radio"],
+input[type="reset"],
+input[type="submit"] {
+ padding:1px;
+}
diff --git a/js/yui/button/button-min.js b/js/yui/button/button-min.js
new file mode 100644
index 000000000..fd32af9d4
--- /dev/null
+++ b/js/yui/button/button-min.js
@@ -0,0 +1,11 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+(function(){var G=YAHOO.util.Dom,M=YAHOO.util.Event,I=YAHOO.lang,L=YAHOO.env.ua,B=YAHOO.widget.Overlay,J=YAHOO.widget.Menu,D={},K=null,E=null,C=null;function F(O,N,R,P){var S,Q;if(I.isString(O)&&I.isString(N)){if(L.ie){Q='<input type="'+O+'" name="'+N+'"';if(P){Q+=" checked";}Q+=">";S=document.createElement(Q);}else{S=document.createElement("input");S.name=N;S.type=O;if(P){S.checked=true;}}S.value=R;}return S;}function H(O,V){var N=O.nodeName.toUpperCase(),S=(this.CLASS_NAME_PREFIX+this.CSS_CLASS_NAME),T=this,U,P,Q;function W(X){if(!(X in V)){U=O.getAttributeNode(X);if(U&&("value" in U)){V[X]=U.value;}}}function R(){W("type");if(V.type=="button"){V.type="push";}if(!("disabled" in V)){V.disabled=O.disabled;}W("name");W("value");W("title");}switch(N){case"A":V.type="link";W("href");W("target");break;case"INPUT":R();if(!("checked" in V)){V.checked=O.checked;}break;case"BUTTON":R();P=O.parentNode.parentNode;if(G.hasClass(P,S+"-checked")){V.checked=true;}if(G.hasClass(P,S+"-disabled")){V.disabled=true;}O.removeAttribute("value");O.setAttribute("type","button");break;}O.removeAttribute("id");O.removeAttribute("name");if(!("tabindex" in V)){V.tabindex=O.tabIndex;}if(!("label" in V)){Q=N=="INPUT"?O.value:O.innerHTML;if(Q&&Q.length>0){V.label=Q;}}}function A(P){var O=P.attributes,N=O.srcelement,R=N.nodeName.toUpperCase(),Q=this;if(R==this.NODE_NAME){P.element=N;P.id=N.id;G.getElementsBy(function(S){switch(S.nodeName.toUpperCase()){case"BUTTON":case"A":case"INPUT":H.call(Q,S,O);break;}},"*",N);}else{switch(R){case"BUTTON":case"A":case"INPUT":H.call(this,N,O);break;}}}YAHOO.widget.Button=function(R,O){if(!B&&YAHOO.widget.Overlay){B=YAHOO.widget.Overlay;}if(!J&&YAHOO.widget.Menu){J=YAHOO.widget.Menu;}var Q=YAHOO.widget.Button.superclass.constructor,P,N;if(arguments.length==1&&!I.isString(R)&&!R.nodeName){if(!R.id){R.id=G.generateId();}Q.call(this,(this.createButtonElement(R.type)),R);}else{P={element:null,attributes:(O||{})};if(I.isString(R)){N=G.get(R);if(N){if(!P.attributes.id){P.attributes.id=R;}P.attributes.srcelement=N;A.call(this,P);if(!P.element){P.element=this.createButtonElement(P.attributes.type);}Q.call(this,P.element,P.attributes);}}else{if(R.nodeName){if(!P.attributes.id){if(R.id){P.attributes.id=R.id;}else{P.attributes.id=G.generateId();}}P.attributes.srcelement=R;A.call(this,P);if(!P.element){P.element=this.createButtonElement(P.attributes.type);}Q.call(this,P.element,P.attributes);}}}};YAHOO.extend(YAHOO.widget.Button,YAHOO.util.Element,{_button:null,_menu:null,_hiddenFields:null,_onclickAttributeValue:null,_activationKeyPressed:false,_activationButtonPressed:false,_hasKeyEventHandlers:false,_hasMouseEventHandlers:false,_nOptionRegionX:0,CLASS_NAME_PREFIX:"yui-",NODE_NAME:"SPAN",CHECK_ACTIVATION_KEYS:[32],ACTIVATION_KEYS:[13,32],OPTION_AREA_WIDTH:20,CSS_CLASS_NAME:"button",_setType:function(N){if(N=="split"){this.on("option",this._onOption);}},_setLabel:function(O){this._button.innerHTML=O;var P,N=L.gecko;if(N&&N<1.9&&G.inDocument(this.get("element"))){P=(this.CLASS_NAME_PREFIX+this.CSS_CLASS_NAME);this.removeClass(P);I.later(0,this,this.addClass,P);}},_setTabIndex:function(N){this._button.tabIndex=N;},_setTitle:function(N){if(this.get("type")!="link"){this._button.title=N;}},_setDisabled:function(N){if(this.get("type")!="link"){if(N){if(this._menu){this._menu.hide();}if(this.hasFocus()){this.blur();}this._button.setAttribute("disabled","disabled");this.addStateCSSClasses("disabled");this.removeStateCSSClasses("hover");this.removeStateCSSClasses("active");this.removeStateCSSClasses("focus");}else{this._button.removeAttribute("disabled");this.removeStateCSSClasses("disabled");}}},_setHref:function(N){if(this.get("type")=="link"){this._button.href=N;}},_setTarget:function(N){if(this.get("type")=="link"){this._button.setAttribute("target",N);}},_setChecked:function(N){var O=this.get("type");if(O=="checkbox"||O=="radio"){if(N){this.addStateCSSClasses("checked");}else{this.removeStateCSSClasses("checked");}}},_setMenu:function(U){var P=this.get("lazyloadmenu"),R=this.get("element"),N,W=false,X,O,Q;function V(){X.render(R.parentNode);this.removeListener("appendTo",V);}function T(){X.cfg.queueProperty("container",R.parentNode);this.removeListener("appendTo",T);}function S(){var Y;if(X){G.addClass(X.element,this.get("menuclassname"));G.addClass(X.element,this.CLASS_NAME_PREFIX+this.get("type")+"-button-menu");X.showEvent.subscribe(this._onMenuShow,null,this);X.hideEvent.subscribe(this._onMenuHide,null,this);X.renderEvent.subscribe(this._onMenuRender,null,this);if(J&&X instanceof J){if(P){Y=this.get("container");if(Y){X.cfg.queueProperty("container",Y);}else{this.on("appendTo",T);}}X.cfg.queueProperty("clicktohide",false);X.keyDownEvent.subscribe(this._onMenuKeyDown,this,true);X.subscribe("click",this._onMenuClick,this,true);this.on("selectedMenuItemChange",this._onSelectedMenuItemChange);Q=X.srcElement;if(Q&&Q.nodeName.toUpperCase()=="SELECT"){Q.style.display="none";Q.parentNode.removeChild(Q);}}else{if(B&&X instanceof B){if(!K){K=new YAHOO.widget.OverlayManager();}K.register(X);}}this._menu=X;if(!W&&!P){if(G.inDocument(R)){X.render(R.parentNode);}else{this.on("appendTo",V);}}}}if(B){if(J){N=J.prototype.CSS_CLASS_NAME;}if(U&&J&&(U instanceof J)){X=U;W=true;S.call(this);}else{if(B&&U&&(U instanceof B)){X=U;W=true;X.cfg.queueProperty("visible",false);S.call(this);}else{if(J&&I.isArray(U)){X=new J(G.generateId(),{lazyload:P,itemdata:U});this._menu=X;this.on("appendTo",S);}else{if(I.isString(U)){O=G.get(U);if(O){if(J&&G.hasClass(O,N)||O.nodeName.toUpperCase()=="SELECT"){X=new J(U,{lazyload:P});S.call(this);}else{if(B){X=new B(U,{visible:false});S.call(this);}}}}else{if(U&&U.nodeName){if(J&&G.hasClass(U,N)||U.nodeName.toUpperCase()=="SELECT"){X=new J(U,{lazyload:P});S.call(this);}else{if(B){if(!U.id){G.generateId(U);}X=new B(U,{visible:false});S.call(this);}}}}}}}}},_setOnClick:function(N){if(this._onclickAttributeValue&&(this._onclickAttributeValue!=N)){this.removeListener("click",this._onclickAttributeValue.fn);
+this._onclickAttributeValue=null;}if(!this._onclickAttributeValue&&I.isObject(N)&&I.isFunction(N.fn)){this.on("click",N.fn,N.obj,N.scope);this._onclickAttributeValue=N;}},_isActivationKey:function(N){var S=this.get("type"),O=(S=="checkbox"||S=="radio")?this.CHECK_ACTIVATION_KEYS:this.ACTIVATION_KEYS,Q=O.length,R=false,P;if(Q>0){P=Q-1;do{if(N==O[P]){R=true;break;}}while(P--);}return R;},_isSplitButtonOptionKey:function(P){var O=(M.getCharCode(P)==40);var N=function(Q){M.preventDefault(Q);this.removeListener("keypress",N);};if(O){if(L.opera){this.on("keypress",N);}M.preventDefault(P);}return O;},_addListenersToForm:function(){var T=this.getForm(),S=YAHOO.widget.Button.onFormKeyPress,R,N,Q,P,O;if(T){M.on(T,"reset",this._onFormReset,null,this);M.on(T,"submit",this._onFormSubmit,null,this);N=this.get("srcelement");if(this.get("type")=="submit"||(N&&N.type=="submit")){Q=M.getListeners(T,"keypress");R=false;if(Q){P=Q.length;if(P>0){O=P-1;do{if(Q[O].fn==S){R=true;break;}}while(O--);}}if(!R){M.on(T,"keypress",S);}}}},_showMenu:function(R){if(YAHOO.widget.MenuManager){YAHOO.widget.MenuManager.hideVisible();}if(K){K.hideAll();}var N=this._menu,Q=this.get("menualignment"),P=this.get("focusmenu"),O;if(this._renderedMenu){N.cfg.setProperty("context",[this.get("element"),Q[0],Q[1]]);N.cfg.setProperty("preventcontextoverlap",true);N.cfg.setProperty("constraintoviewport",true);}else{N.cfg.queueProperty("context",[this.get("element"),Q[0],Q[1]]);N.cfg.queueProperty("preventcontextoverlap",true);N.cfg.queueProperty("constraintoviewport",true);}this.focus();if(J&&N&&(N instanceof J)){O=N.focus;N.focus=function(){};if(this._renderedMenu){N.cfg.setProperty("minscrollheight",this.get("menuminscrollheight"));N.cfg.setProperty("maxheight",this.get("menumaxheight"));}else{N.cfg.queueProperty("minscrollheight",this.get("menuminscrollheight"));N.cfg.queueProperty("maxheight",this.get("menumaxheight"));}N.show();N.focus=O;N.align();if(R.type=="mousedown"){M.stopPropagation(R);}if(P){N.focus();}}else{if(B&&N&&(N instanceof B)){if(!this._renderedMenu){N.render(this.get("element").parentNode);}N.show();N.align();}}},_hideMenu:function(){var N=this._menu;if(N){N.hide();}},_onMouseOver:function(O){var Q=this.get("type"),N,P;if(Q==="split"){N=this.get("element");P=(G.getX(N)+(N.offsetWidth-this.OPTION_AREA_WIDTH));this._nOptionRegionX=P;}if(!this._hasMouseEventHandlers){if(Q==="split"){this.on("mousemove",this._onMouseMove);}this.on("mouseout",this._onMouseOut);this._hasMouseEventHandlers=true;}this.addStateCSSClasses("hover");if(Q==="split"&&(M.getPageX(O)>P)){this.addStateCSSClasses("hoveroption");}if(this._activationButtonPressed){this.addStateCSSClasses("active");}if(this._bOptionPressed){this.addStateCSSClasses("activeoption");}if(this._activationButtonPressed||this._bOptionPressed){M.removeListener(document,"mouseup",this._onDocumentMouseUp);}},_onMouseMove:function(N){var O=this._nOptionRegionX;if(O){if(M.getPageX(N)>O){this.addStateCSSClasses("hoveroption");}else{this.removeStateCSSClasses("hoveroption");}}},_onMouseOut:function(N){var O=this.get("type");this.removeStateCSSClasses("hover");if(O!="menu"){this.removeStateCSSClasses("active");}if(this._activationButtonPressed||this._bOptionPressed){M.on(document,"mouseup",this._onDocumentMouseUp,null,this);}if(O==="split"&&(M.getPageX(N)>this._nOptionRegionX)){this.removeStateCSSClasses("hoveroption");}},_onDocumentMouseUp:function(P){this._activationButtonPressed=false;this._bOptionPressed=false;var Q=this.get("type"),N,O;if(Q=="menu"||Q=="split"){N=M.getTarget(P);O=this._menu.element;if(N!=O&&!G.isAncestor(O,N)){this.removeStateCSSClasses((Q=="menu"?"active":"activeoption"));this._hideMenu();}}M.removeListener(document,"mouseup",this._onDocumentMouseUp);},_onMouseDown:function(P){var Q,O=true;function N(){this._hideMenu();this.removeListener("mouseup",N);}if((P.which||P.button)==1){if(!this.hasFocus()){this.focus();}Q=this.get("type");if(Q=="split"){if(M.getPageX(P)>this._nOptionRegionX){this.fireEvent("option",P);O=false;}else{this.addStateCSSClasses("active");this._activationButtonPressed=true;}}else{if(Q=="menu"){if(this.isActive()){this._hideMenu();this._activationButtonPressed=false;}else{this._showMenu(P);this._activationButtonPressed=true;}}else{this.addStateCSSClasses("active");this._activationButtonPressed=true;}}if(Q=="split"||Q=="menu"){this._hideMenuTimer=I.later(250,this,this.on,["mouseup",N]);}}return O;},_onMouseUp:function(P){var Q=this.get("type"),N=this._hideMenuTimer,O=true;if(N){N.cancel();}if(Q=="checkbox"||Q=="radio"){this.set("checked",!(this.get("checked")));}this._activationButtonPressed=false;if(Q!="menu"){this.removeStateCSSClasses("active");}if(Q=="split"&&M.getPageX(P)>this._nOptionRegionX){O=false;}return O;},_onFocus:function(O){var N;this.addStateCSSClasses("focus");if(this._activationKeyPressed){this.addStateCSSClasses("active");}C=this;if(!this._hasKeyEventHandlers){N=this._button;M.on(N,"blur",this._onBlur,null,this);M.on(N,"keydown",this._onKeyDown,null,this);M.on(N,"keyup",this._onKeyUp,null,this);this._hasKeyEventHandlers=true;}this.fireEvent("focus",O);},_onBlur:function(N){this.removeStateCSSClasses("focus");if(this.get("type")!="menu"){this.removeStateCSSClasses("active");}if(this._activationKeyPressed){M.on(document,"keyup",this._onDocumentKeyUp,null,this);}C=null;this.fireEvent("blur",N);},_onDocumentKeyUp:function(N){if(this._isActivationKey(M.getCharCode(N))){this._activationKeyPressed=false;M.removeListener(document,"keyup",this._onDocumentKeyUp);}},_onKeyDown:function(O){var N=this._menu;if(this.get("type")=="split"&&this._isSplitButtonOptionKey(O)){this.fireEvent("option",O);}else{if(this._isActivationKey(M.getCharCode(O))){if(this.get("type")=="menu"){this._showMenu(O);}else{this._activationKeyPressed=true;this.addStateCSSClasses("active");}}}if(N&&N.cfg.getProperty("visible")&&M.getCharCode(O)==27){N.hide();this.focus();}},_onKeyUp:function(N){var O;if(this._isActivationKey(M.getCharCode(N))){O=this.get("type");if(O=="checkbox"||O=="radio"){this.set("checked",!(this.get("checked")));
+}this._activationKeyPressed=false;if(this.get("type")!="menu"){this.removeStateCSSClasses("active");}}},_onClick:function(P){var R=this.get("type"),Q,N,O;switch(R){case"submit":if(P.returnValue!==false){this.submitForm();}break;case"reset":Q=this.getForm();if(Q){Q.reset();}break;case"split":if(this._nOptionRegionX>0&&(M.getPageX(P)>this._nOptionRegionX)){O=false;}else{this._hideMenu();N=this.get("srcelement");if(N&&N.type=="submit"&&P.returnValue!==false){this.submitForm();}}break;}return O;},_onDblClick:function(O){var N=true;if(this.get("type")=="split"&&M.getPageX(O)>this._nOptionRegionX){N=false;}return N;},_onAppendTo:function(N){I.later(0,this,this._addListenersToForm);},_onFormReset:function(O){var P=this.get("type"),N=this._menu;if(P=="checkbox"||P=="radio"){this.resetValue("checked");}if(J&&N&&(N instanceof J)){this.resetValue("selectedMenuItem");}},_onFormSubmit:function(N){this.createHiddenFields();},_onDocumentMouseDown:function(Q){var N=M.getTarget(Q),P=this.get("element"),O=this._menu.element;if(N!=P&&!G.isAncestor(P,N)&&N!=O&&!G.isAncestor(O,N)){this._hideMenu();if(L.ie&&N.focus){N.setActive();}M.removeListener(document,"mousedown",this._onDocumentMouseDown);}},_onOption:function(N){if(this.hasClass(this.CLASS_NAME_PREFIX+"split-button-activeoption")){this._hideMenu();this._bOptionPressed=false;}else{this._showMenu(N);this._bOptionPressed=true;}},_onMenuShow:function(N){M.on(document,"mousedown",this._onDocumentMouseDown,null,this);var O=(this.get("type")=="split")?"activeoption":"active";this.addStateCSSClasses(O);},_onMenuHide:function(N){var O=(this.get("type")=="split")?"activeoption":"active";this.removeStateCSSClasses(O);if(this.get("type")=="split"){this._bOptionPressed=false;}},_onMenuKeyDown:function(P,O){var N=O[0];if(M.getCharCode(N)==27){this.focus();if(this.get("type")=="split"){this._bOptionPressed=false;}}},_onMenuRender:function(P){var S=this.get("element"),O=S.parentNode,N=this._menu,R=N.element,Q=N.srcElement,T;if(O!=R.parentNode){O.appendChild(R);}this._renderedMenu=true;if(Q&&Q.nodeName.toLowerCase()==="select"&&Q.value){T=N.getItem(Q.selectedIndex);this.set("selectedMenuItem",T,true);this._onSelectedMenuItemChange({newValue:T});}},_onMenuClick:function(O,N){var Q=N[1],P;if(Q){this.set("selectedMenuItem",Q);P=this.get("srcelement");if(P&&P.type=="submit"){this.submitForm();}this._hideMenu();}},_onSelectedMenuItemChange:function(O){var P=O.prevValue,Q=O.newValue,N=this.CLASS_NAME_PREFIX;if(P){G.removeClass(P.element,(N+"button-selectedmenuitem"));}if(Q){G.addClass(Q.element,(N+"button-selectedmenuitem"));}},_onLabelClick:function(N){this.focus();var O=this.get("type");if(O=="radio"||O=="checkbox"){this.set("checked",(!this.get("checked")));}},createButtonElement:function(N){var P=this.NODE_NAME,O=document.createElement(P);O.innerHTML="<"+P+' class="first-child">'+(N=="link"?"<a></a>":'<button type="button"></button>')+"</"+P+">";return O;},addStateCSSClasses:function(O){var P=this.get("type"),N=this.CLASS_NAME_PREFIX;if(I.isString(O)){if(O!="activeoption"&&O!="hoveroption"){this.addClass(N+this.CSS_CLASS_NAME+("-"+O));}this.addClass(N+P+("-button-"+O));}},removeStateCSSClasses:function(O){var P=this.get("type"),N=this.CLASS_NAME_PREFIX;if(I.isString(O)){this.removeClass(N+this.CSS_CLASS_NAME+("-"+O));this.removeClass(N+P+("-button-"+O));}},createHiddenFields:function(){this.removeHiddenFields();var V=this.getForm(),Z,O,S,X,Y,T,U,N,R,W,P,Q=false;if(V&&!this.get("disabled")){O=this.get("type");S=(O=="checkbox"||O=="radio");if((S&&this.get("checked"))||(E==this)){Z=F((S?O:"hidden"),this.get("name"),this.get("value"),this.get("checked"));if(Z){if(S){Z.style.display="none";}V.appendChild(Z);}}X=this._menu;if(J&&X&&(X instanceof J)){Y=this.get("selectedMenuItem");P=X.srcElement;Q=(P&&P.nodeName.toUpperCase()=="SELECT");if(Y){U=(Y.value===null||Y.value==="")?Y.cfg.getProperty("text"):Y.value;T=this.get("name");if(Q){W=P.name;}else{if(T){W=(T+"_options");}}if(U&&W){N=F("hidden",W,U);V.appendChild(N);}}else{if(Q){N=V.appendChild(P);}}}if(Z&&N){this._hiddenFields=[Z,N];}else{if(!Z&&N){this._hiddenFields=N;}else{if(Z&&!N){this._hiddenFields=Z;}}}R=this._hiddenFields;}return R;},removeHiddenFields:function(){var Q=this._hiddenFields,O,P;function N(R){if(G.inDocument(R)){R.parentNode.removeChild(R);}}if(Q){if(I.isArray(Q)){O=Q.length;if(O>0){P=O-1;do{N(Q[P]);}while(P--);}}else{N(Q);}this._hiddenFields=null;}},submitForm:function(){var Q=this.getForm(),P=this.get("srcelement"),O=false,N;if(Q){if(this.get("type")=="submit"||(P&&P.type=="submit")){E=this;}if(L.ie){O=Q.fireEvent("onsubmit");}else{N=document.createEvent("HTMLEvents");N.initEvent("submit",true,true);O=Q.dispatchEvent(N);}if((L.ie||L.webkit)&&O){Q.submit();}}return O;},init:function(P,d){var V=d.type=="link"?"a":"button",a=d.srcelement,S=P.getElementsByTagName(V)[0],U;if(!S){U=P.getElementsByTagName("input")[0];if(U){S=document.createElement("button");S.setAttribute("type","button");U.parentNode.replaceChild(S,U);}}this._button=S;YAHOO.widget.Button.superclass.init.call(this,P,d);var T=this.get("id"),Z=T+"-button";S.id=Z;var X,Q;var e=function(f){return(f.htmlFor===T);};var c=function(){Q.setAttribute((L.ie?"htmlFor":"for"),Z);};if(a&&this.get("type")!="link"){X=G.getElementsBy(e,"label");if(I.isArray(X)&&X.length>0){Q=X[0];}}D[T]=this;var b=this.CLASS_NAME_PREFIX;this.addClass(b+this.CSS_CLASS_NAME);this.addClass(b+this.get("type")+"-button");M.on(this._button,"focus",this._onFocus,null,this);this.on("mouseover",this._onMouseOver);this.on("mousedown",this._onMouseDown);this.on("mouseup",this._onMouseUp);this.on("click",this._onClick);var R=this.get("onclick");this.set("onclick",null);this.set("onclick",R);this.on("dblclick",this._onDblClick);var O;if(Q){if(this.get("replaceLabel")){this.set("label",Q.innerHTML);O=Q.parentNode;O.removeChild(Q);}else{this.on("appendTo",c);M.on(Q,"click",this._onLabelClick,null,this);this._label=Q;}}this.on("appendTo",this._onAppendTo);var N=this.get("container"),Y=this.get("element"),W=G.inDocument(Y);
+if(N){if(a&&a!=Y){O=a.parentNode;if(O){O.removeChild(a);}}if(I.isString(N)){M.onContentReady(N,this.appendTo,N,this);}else{this.on("init",function(){I.later(0,this,this.appendTo,N);});}}else{if(!W&&a&&a!=Y){O=a.parentNode;if(O){this.fireEvent("beforeAppendTo",{type:"beforeAppendTo",target:O});O.replaceChild(Y,a);this.fireEvent("appendTo",{type:"appendTo",target:O});}}else{if(this.get("type")!="link"&&W&&a&&a==Y){this._addListenersToForm();}}}this.fireEvent("init",{type:"init",target:this});},initAttributes:function(O){var N=O||{};YAHOO.widget.Button.superclass.initAttributes.call(this,N);this.setAttributeConfig("type",{value:(N.type||"push"),validator:I.isString,writeOnce:true,method:this._setType});this.setAttributeConfig("label",{value:N.label,validator:I.isString,method:this._setLabel});this.setAttributeConfig("value",{value:N.value});this.setAttributeConfig("name",{value:N.name,validator:I.isString});this.setAttributeConfig("tabindex",{value:N.tabindex,validator:I.isNumber,method:this._setTabIndex});this.configureAttribute("title",{value:N.title,validator:I.isString,method:this._setTitle});this.setAttributeConfig("disabled",{value:(N.disabled||false),validator:I.isBoolean,method:this._setDisabled});this.setAttributeConfig("href",{value:N.href,validator:I.isString,method:this._setHref});this.setAttributeConfig("target",{value:N.target,validator:I.isString,method:this._setTarget});this.setAttributeConfig("checked",{value:(N.checked||false),validator:I.isBoolean,method:this._setChecked});this.setAttributeConfig("container",{value:N.container,writeOnce:true});this.setAttributeConfig("srcelement",{value:N.srcelement,writeOnce:true});this.setAttributeConfig("menu",{value:null,method:this._setMenu,writeOnce:true});this.setAttributeConfig("lazyloadmenu",{value:(N.lazyloadmenu===false?false:true),validator:I.isBoolean,writeOnce:true});this.setAttributeConfig("menuclassname",{value:(N.menuclassname||(this.CLASS_NAME_PREFIX+"button-menu")),validator:I.isString,method:this._setMenuClassName,writeOnce:true});this.setAttributeConfig("menuminscrollheight",{value:(N.menuminscrollheight||90),validator:I.isNumber});this.setAttributeConfig("menumaxheight",{value:(N.menumaxheight||0),validator:I.isNumber});this.setAttributeConfig("menualignment",{value:(N.menualignment||["tl","bl"]),validator:I.isArray});this.setAttributeConfig("selectedMenuItem",{value:null});this.setAttributeConfig("onclick",{value:N.onclick,method:this._setOnClick});this.setAttributeConfig("focusmenu",{value:(N.focusmenu===false?false:true),validator:I.isBoolean});this.setAttributeConfig("replaceLabel",{value:false,validator:I.isBoolean,writeOnce:true});},focus:function(){if(!this.get("disabled")){this._button.focus();}},blur:function(){if(!this.get("disabled")){this._button.blur();}},hasFocus:function(){return(C==this);},isActive:function(){return this.hasClass(this.CLASS_NAME_PREFIX+this.CSS_CLASS_NAME+"-active");},getMenu:function(){return this._menu;},getForm:function(){var N=this._button,O;if(N){O=N.form;}return O;},getHiddenFields:function(){return this._hiddenFields;},destroy:function(){var P=this.get("element"),N=this._menu,T=this._label,O,S;if(N){if(K&&K.find(N)){K.remove(N);}N.destroy();}M.purgeElement(P);M.purgeElement(this._button);M.removeListener(document,"mouseup",this._onDocumentMouseUp);M.removeListener(document,"keyup",this._onDocumentKeyUp);M.removeListener(document,"mousedown",this._onDocumentMouseDown);if(T){M.removeListener(T,"click",this._onLabelClick);O=T.parentNode;O.removeChild(T);}var Q=this.getForm();if(Q){M.removeListener(Q,"reset",this._onFormReset);M.removeListener(Q,"submit",this._onFormSubmit);}this.unsubscribeAll();O=P.parentNode;if(O){O.removeChild(P);}delete D[this.get("id")];var R=(this.CLASS_NAME_PREFIX+this.CSS_CLASS_NAME);S=G.getElementsByClassName(R,this.NODE_NAME,Q);if(I.isArray(S)&&S.length===0){M.removeListener(Q,"keypress",YAHOO.widget.Button.onFormKeyPress);}},fireEvent:function(O,N){var P=arguments[0];if(this.DOM_EVENTS[P]&&this.get("disabled")){return false;}return YAHOO.widget.Button.superclass.fireEvent.apply(this,arguments);},toString:function(){return("Button "+this.get("id"));}});YAHOO.widget.Button.onFormKeyPress=function(R){var P=M.getTarget(R),S=M.getCharCode(R),Q=P.nodeName&&P.nodeName.toUpperCase(),N=P.type,T=false,V,X,O,W;function U(a){var Z,Y;switch(a.nodeName.toUpperCase()){case"INPUT":case"BUTTON":if(a.type=="submit"&&!a.disabled){if(!T&&!O){O=a;}}break;default:Z=a.id;if(Z){V=D[Z];if(V){T=true;if(!V.get("disabled")){Y=V.get("srcelement");if(!X&&(V.get("type")=="submit"||(Y&&Y.type=="submit"))){X=V;}}}}break;}}if(S==13&&((Q=="INPUT"&&(N=="text"||N=="password"||N=="checkbox"||N=="radio"||N=="file"))||Q=="SELECT")){G.getElementsBy(U,"*",this);if(O){O.focus();}else{if(!O&&X){M.preventDefault(R);if(L.ie){X.get("element").fireEvent("onclick");}else{W=document.createEvent("HTMLEvents");W.initEvent("click",true,true);if(L.gecko<1.9){X.fireEvent("click",W);}else{X.get("element").dispatchEvent(W);}}}}}};YAHOO.widget.Button.addHiddenFieldsToForm=function(N){var R=YAHOO.widget.Button.prototype,T=G.getElementsByClassName((R.CLASS_NAME_PREFIX+R.CSS_CLASS_NAME),"*",N),Q=T.length,S,O,P;if(Q>0){for(P=0;P<Q;P++){O=T[P].id;if(O){S=D[O];if(S){S.createHiddenFields();}}}}};YAHOO.widget.Button.getButton=function(N){return D[N];};})();(function(){var C=YAHOO.util.Dom,B=YAHOO.util.Event,D=YAHOO.lang,A=YAHOO.widget.Button,E={};YAHOO.widget.ButtonGroup=function(J,H){var I=YAHOO.widget.ButtonGroup.superclass.constructor,K,G,F;if(arguments.length==1&&!D.isString(J)&&!J.nodeName){if(!J.id){F=C.generateId();J.id=F;}I.call(this,(this._createGroupElement()),J);}else{if(D.isString(J)){G=C.get(J);if(G){if(G.nodeName.toUpperCase()==this.NODE_NAME){I.call(this,G,H);}}}else{K=J.nodeName.toUpperCase();if(K&&K==this.NODE_NAME){if(!J.id){J.id=C.generateId();}I.call(this,J,H);}}}};YAHOO.extend(YAHOO.widget.ButtonGroup,YAHOO.util.Element,{_buttons:null,NODE_NAME:"DIV",CLASS_NAME_PREFIX:"yui-",CSS_CLASS_NAME:"buttongroup",_createGroupElement:function(){var F=document.createElement(this.NODE_NAME);
+return F;},_setDisabled:function(G){var H=this.getCount(),F;if(H>0){F=H-1;do{this._buttons[F].set("disabled",G);}while(F--);}},_onKeyDown:function(K){var G=B.getTarget(K),I=B.getCharCode(K),H=G.parentNode.parentNode.id,J=E[H],F=-1;if(I==37||I==38){F=(J.index===0)?(this._buttons.length-1):(J.index-1);}else{if(I==39||I==40){F=(J.index===(this._buttons.length-1))?0:(J.index+1);}}if(F>-1){this.check(F);this.getButton(F).focus();}},_onAppendTo:function(H){var I=this._buttons,G=I.length,F;for(F=0;F<G;F++){I[F].appendTo(this.get("element"));}},_onButtonCheckedChange:function(G,F){var I=G.newValue,H=this.get("checkedButton");if(I&&H!=F){if(H){H.set("checked",false,true);}this.set("checkedButton",F);this.set("value",F.get("value"));}else{if(H&&!H.set("checked")){H.set("checked",true,true);}}},init:function(I,H){this._buttons=[];YAHOO.widget.ButtonGroup.superclass.init.call(this,I,H);this.addClass(this.CLASS_NAME_PREFIX+this.CSS_CLASS_NAME);var K=(YAHOO.widget.Button.prototype.CLASS_NAME_PREFIX+"radio-button"),J=this.getElementsByClassName(K);if(J.length>0){this.addButtons(J);}function F(L){return(L.type=="radio");}J=C.getElementsBy(F,"input",this.get("element"));if(J.length>0){this.addButtons(J);}this.on("keydown",this._onKeyDown);this.on("appendTo",this._onAppendTo);var G=this.get("container");if(G){if(D.isString(G)){B.onContentReady(G,function(){this.appendTo(G);},null,this);}else{this.appendTo(G);}}},initAttributes:function(G){var F=G||{};YAHOO.widget.ButtonGroup.superclass.initAttributes.call(this,F);this.setAttributeConfig("name",{value:F.name,validator:D.isString});this.setAttributeConfig("disabled",{value:(F.disabled||false),validator:D.isBoolean,method:this._setDisabled});this.setAttributeConfig("value",{value:F.value});this.setAttributeConfig("container",{value:F.container,writeOnce:true});this.setAttributeConfig("checkedButton",{value:null});},addButton:function(J){var L,K,G,F,H,I;if(J instanceof A&&J.get("type")=="radio"){L=J;}else{if(!D.isString(J)&&!J.nodeName){J.type="radio";L=new A(J);}else{L=new A(J,{type:"radio"});}}if(L){F=this._buttons.length;H=L.get("name");I=this.get("name");L.index=F;this._buttons[F]=L;E[L.get("id")]=L;if(H!=I){L.set("name",I);}if(this.get("disabled")){L.set("disabled",true);}if(L.get("checked")){this.set("checkedButton",L);}K=L.get("element");G=this.get("element");if(K.parentNode!=G){G.appendChild(K);}L.on("checkedChange",this._onButtonCheckedChange,L,this);}return L;},addButtons:function(G){var H,I,J,F;if(D.isArray(G)){H=G.length;J=[];if(H>0){for(F=0;F<H;F++){I=this.addButton(G[F]);if(I){J[J.length]=I;}}}}return J;},removeButton:function(H){var I=this.getButton(H),G,F;if(I){this._buttons.splice(H,1);delete E[I.get("id")];I.removeListener("checkedChange",this._onButtonCheckedChange);I.destroy();G=this._buttons.length;if(G>0){F=this._buttons.length-1;do{this._buttons[F].index=F;}while(F--);}}},getButton:function(F){return this._buttons[F];},getButtons:function(){return this._buttons;},getCount:function(){return this._buttons.length;},focus:function(H){var I,G,F;if(D.isNumber(H)){I=this._buttons[H];if(I){I.focus();}}else{G=this.getCount();for(F=0;F<G;F++){I=this._buttons[F];if(!I.get("disabled")){I.focus();break;}}}},check:function(F){var G=this.getButton(F);if(G){G.set("checked",true);}},destroy:function(){var I=this._buttons.length,H=this.get("element"),F=H.parentNode,G;if(I>0){G=this._buttons.length-1;do{this._buttons[G].destroy();}while(G--);}B.purgeElement(H);F.removeChild(H);},toString:function(){return("ButtonGroup "+this.get("id"));}});})();YAHOO.register("button",YAHOO.widget.Button,{version:"2.8.2r1",build:"7"}); \ No newline at end of file
diff --git a/js/yui/calendar/calendar-min.js b/js/yui/calendar/calendar-min.js
new file mode 100644
index 000000000..fa13b91f7
--- /dev/null
+++ b/js/yui/calendar/calendar-min.js
@@ -0,0 +1,18 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+(function(){YAHOO.util.Config=function(D){if(D){this.init(D);}};var B=YAHOO.lang,C=YAHOO.util.CustomEvent,A=YAHOO.util.Config;A.CONFIG_CHANGED_EVENT="configChanged";A.BOOLEAN_TYPE="boolean";A.prototype={owner:null,queueInProgress:false,config:null,initialConfig:null,eventQueue:null,configChangedEvent:null,init:function(D){this.owner=D;this.configChangedEvent=this.createEvent(A.CONFIG_CHANGED_EVENT);this.configChangedEvent.signature=C.LIST;this.queueInProgress=false;this.config={};this.initialConfig={};this.eventQueue=[];},checkBoolean:function(D){return(typeof D==A.BOOLEAN_TYPE);},checkNumber:function(D){return(!isNaN(D));},fireEvent:function(D,F){var E=this.config[D];if(E&&E.event){E.event.fire(F);}},addProperty:function(E,D){E=E.toLowerCase();this.config[E]=D;D.event=this.createEvent(E,{scope:this.owner});D.event.signature=C.LIST;D.key=E;if(D.handler){D.event.subscribe(D.handler,this.owner);}this.setProperty(E,D.value,true);if(!D.suppressEvent){this.queueProperty(E,D.value);}},getConfig:function(){var D={},F=this.config,G,E;for(G in F){if(B.hasOwnProperty(F,G)){E=F[G];if(E&&E.event){D[G]=E.value;}}}return D;},getProperty:function(D){var E=this.config[D.toLowerCase()];if(E&&E.event){return E.value;}else{return undefined;}},resetProperty:function(D){D=D.toLowerCase();var E=this.config[D];if(E&&E.event){if(this.initialConfig[D]&&!B.isUndefined(this.initialConfig[D])){this.setProperty(D,this.initialConfig[D]);return true;}}else{return false;}},setProperty:function(E,G,D){var F;E=E.toLowerCase();if(this.queueInProgress&&!D){this.queueProperty(E,G);return true;}else{F=this.config[E];if(F&&F.event){if(F.validator&&!F.validator(G)){return false;}else{F.value=G;if(!D){this.fireEvent(E,G);this.configChangedEvent.fire([E,G]);}return true;}}else{return false;}}},queueProperty:function(S,P){S=S.toLowerCase();var R=this.config[S],K=false,J,G,H,I,O,Q,F,M,N,D,L,T,E;if(R&&R.event){if(!B.isUndefined(P)&&R.validator&&!R.validator(P)){return false;}else{if(!B.isUndefined(P)){R.value=P;}else{P=R.value;}K=false;J=this.eventQueue.length;for(L=0;L<J;L++){G=this.eventQueue[L];if(G){H=G[0];I=G[1];if(H==S){this.eventQueue[L]=null;this.eventQueue.push([S,(!B.isUndefined(P)?P:I)]);K=true;break;}}}if(!K&&!B.isUndefined(P)){this.eventQueue.push([S,P]);}}if(R.supercedes){O=R.supercedes.length;for(T=0;T<O;T++){Q=R.supercedes[T];F=this.eventQueue.length;for(E=0;E<F;E++){M=this.eventQueue[E];if(M){N=M[0];D=M[1];if(N==Q.toLowerCase()){this.eventQueue.push([N,D]);this.eventQueue[E]=null;break;}}}}}return true;}else{return false;}},refireEvent:function(D){D=D.toLowerCase();var E=this.config[D];if(E&&E.event&&!B.isUndefined(E.value)){if(this.queueInProgress){this.queueProperty(D);}else{this.fireEvent(D,E.value);}}},applyConfig:function(D,G){var F,E;if(G){E={};for(F in D){if(B.hasOwnProperty(D,F)){E[F.toLowerCase()]=D[F];}}this.initialConfig=E;}for(F in D){if(B.hasOwnProperty(D,F)){this.queueProperty(F,D[F]);}}},refresh:function(){var D;for(D in this.config){if(B.hasOwnProperty(this.config,D)){this.refireEvent(D);}}},fireQueue:function(){var E,H,D,G,F;this.queueInProgress=true;for(E=0;E<this.eventQueue.length;E++){H=this.eventQueue[E];if(H){D=H[0];G=H[1];F=this.config[D];F.value=G;this.eventQueue[E]=null;this.fireEvent(D,G);}}this.queueInProgress=false;this.eventQueue=[];},subscribeToConfigEvent:function(D,E,G,H){var F=this.config[D.toLowerCase()];if(F&&F.event){if(!A.alreadySubscribed(F.event,E,G)){F.event.subscribe(E,G,H);}return true;}else{return false;}},unsubscribeFromConfigEvent:function(D,E,G){var F=this.config[D.toLowerCase()];if(F&&F.event){return F.event.unsubscribe(E,G);}else{return false;}},toString:function(){var D="Config";if(this.owner){D+=" ["+this.owner.toString()+"]";}return D;},outputEventQueue:function(){var D="",G,E,F=this.eventQueue.length;for(E=0;E<F;E++){G=this.eventQueue[E];if(G){D+=G[0]+"="+G[1]+", ";}}return D;},destroy:function(){var E=this.config,D,F;for(D in E){if(B.hasOwnProperty(E,D)){F=E[D];F.event.unsubscribeAll();F.event=null;}}this.configChangedEvent.unsubscribeAll();this.configChangedEvent=null;this.owner=null;this.config=null;this.initialConfig=null;this.eventQueue=null;}};A.alreadySubscribed=function(E,H,I){var F=E.subscribers.length,D,G;if(F>0){G=F-1;do{D=E.subscribers[G];if(D&&D.obj==I&&D.fn==H){return true;}}while(G--);}return false;};YAHOO.lang.augmentProto(A,YAHOO.util.EventProvider);}());YAHOO.widget.DateMath={DAY:"D",WEEK:"W",YEAR:"Y",MONTH:"M",ONE_DAY_MS:1000*60*60*24,WEEK_ONE_JAN_DATE:1,add:function(A,D,C){var F=new Date(A.getTime());switch(D){case this.MONTH:var E=A.getMonth()+C;var B=0;if(E<0){while(E<0){E+=12;B-=1;}}else{if(E>11){while(E>11){E-=12;B+=1;}}}F.setMonth(E);F.setFullYear(A.getFullYear()+B);break;case this.DAY:this._addDays(F,C);break;case this.YEAR:F.setFullYear(A.getFullYear()+C);break;case this.WEEK:this._addDays(F,(C*7));break;}return F;},_addDays:function(D,C){if(YAHOO.env.ua.webkit&&YAHOO.env.ua.webkit<420){if(C<0){for(var B=-128;C<B;C-=B){D.setDate(D.getDate()+B);}}else{for(var A=96;C>A;C-=A){D.setDate(D.getDate()+A);}}}D.setDate(D.getDate()+C);},subtract:function(A,C,B){return this.add(A,C,(B*-1));},before:function(C,B){var A=B.getTime();if(C.getTime()<A){return true;}else{return false;}},after:function(C,B){var A=B.getTime();if(C.getTime()>A){return true;}else{return false;}},between:function(B,A,C){if(this.after(B,A)&&this.before(B,C)){return true;}else{return false;}},getJan1:function(A){return this.getDate(A,0,1);},getDayOffset:function(B,D){var C=this.getJan1(D);var A=Math.ceil((B.getTime()-C.getTime())/this.ONE_DAY_MS);return A;},getWeekNumber:function(D,B,G){B=B||0;G=G||this.WEEK_ONE_JAN_DATE;var H=this.clearTime(D),L,M;if(H.getDay()===B){L=H;}else{L=this.getFirstDayOfWeek(H,B);}var I=L.getFullYear();M=new Date(L.getTime()+6*this.ONE_DAY_MS);var F;if(I!==M.getFullYear()&&M.getDate()>=G){F=1;}else{var E=this.clearTime(this.getDate(I,0,G)),A=this.getFirstDayOfWeek(E,B);var J=Math.round((H.getTime()-A.getTime())/this.ONE_DAY_MS);var K=J%7;var C=(J-K)/7;
+F=C+1;}return F;},getFirstDayOfWeek:function(D,A){A=A||0;var B=D.getDay(),C=(B-A+7)%7;return this.subtract(D,this.DAY,C);},isYearOverlapWeek:function(A){var C=false;var B=this.add(A,this.DAY,6);if(B.getFullYear()!=A.getFullYear()){C=true;}return C;},isMonthOverlapWeek:function(A){var C=false;var B=this.add(A,this.DAY,6);if(B.getMonth()!=A.getMonth()){C=true;}return C;},findMonthStart:function(A){var B=this.getDate(A.getFullYear(),A.getMonth(),1);return B;},findMonthEnd:function(B){var D=this.findMonthStart(B);var C=this.add(D,this.MONTH,1);var A=this.subtract(C,this.DAY,1);return A;},clearTime:function(A){A.setHours(12,0,0,0);return A;},getDate:function(D,A,C){var B=null;if(YAHOO.lang.isUndefined(C)){C=1;}if(D>=100){B=new Date(D,A,C);}else{B=new Date();B.setFullYear(D);B.setMonth(A);B.setDate(C);B.setHours(0,0,0,0);}return B;}};(function(){var C=YAHOO.util.Dom,A=YAHOO.util.Event,E=YAHOO.lang,D=YAHOO.widget.DateMath;function F(I,G,H){this.init.apply(this,arguments);}F.IMG_ROOT=null;F.DATE="D";F.MONTH_DAY="MD";F.WEEKDAY="WD";F.RANGE="R";F.MONTH="M";F.DISPLAY_DAYS=42;F.STOP_RENDER="S";F.SHORT="short";F.LONG="long";F.MEDIUM="medium";F.ONE_CHAR="1char";F.DEFAULT_CONFIG={YEAR_OFFSET:{key:"year_offset",value:0,supercedes:["pagedate","selected","mindate","maxdate"]},TODAY:{key:"today",value:new Date(),supercedes:["pagedate"]},PAGEDATE:{key:"pagedate",value:null},SELECTED:{key:"selected",value:[]},TITLE:{key:"title",value:""},CLOSE:{key:"close",value:false},IFRAME:{key:"iframe",value:(YAHOO.env.ua.ie&&YAHOO.env.ua.ie<=6)?true:false},MINDATE:{key:"mindate",value:null},MAXDATE:{key:"maxdate",value:null},MULTI_SELECT:{key:"multi_select",value:false},START_WEEKDAY:{key:"start_weekday",value:0},SHOW_WEEKDAYS:{key:"show_weekdays",value:true},SHOW_WEEK_HEADER:{key:"show_week_header",value:false},SHOW_WEEK_FOOTER:{key:"show_week_footer",value:false},HIDE_BLANK_WEEKS:{key:"hide_blank_weeks",value:false},NAV_ARROW_LEFT:{key:"nav_arrow_left",value:null},NAV_ARROW_RIGHT:{key:"nav_arrow_right",value:null},MONTHS_SHORT:{key:"months_short",value:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]},MONTHS_LONG:{key:"months_long",value:["January","February","March","April","May","June","July","August","September","October","November","December"]},WEEKDAYS_1CHAR:{key:"weekdays_1char",value:["S","M","T","W","T","F","S"]},WEEKDAYS_SHORT:{key:"weekdays_short",value:["Su","Mo","Tu","We","Th","Fr","Sa"]},WEEKDAYS_MEDIUM:{key:"weekdays_medium",value:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"]},WEEKDAYS_LONG:{key:"weekdays_long",value:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"]},LOCALE_MONTHS:{key:"locale_months",value:"long"},LOCALE_WEEKDAYS:{key:"locale_weekdays",value:"short"},DATE_DELIMITER:{key:"date_delimiter",value:","},DATE_FIELD_DELIMITER:{key:"date_field_delimiter",value:"/"},DATE_RANGE_DELIMITER:{key:"date_range_delimiter",value:"-"},MY_MONTH_POSITION:{key:"my_month_position",value:1},MY_YEAR_POSITION:{key:"my_year_position",value:2},MD_MONTH_POSITION:{key:"md_month_position",value:1},MD_DAY_POSITION:{key:"md_day_position",value:2},MDY_MONTH_POSITION:{key:"mdy_month_position",value:1},MDY_DAY_POSITION:{key:"mdy_day_position",value:2},MDY_YEAR_POSITION:{key:"mdy_year_position",value:3},MY_LABEL_MONTH_POSITION:{key:"my_label_month_position",value:1},MY_LABEL_YEAR_POSITION:{key:"my_label_year_position",value:2},MY_LABEL_MONTH_SUFFIX:{key:"my_label_month_suffix",value:" "},MY_LABEL_YEAR_SUFFIX:{key:"my_label_year_suffix",value:""},NAV:{key:"navigator",value:null},STRINGS:{key:"strings",value:{previousMonth:"Previous Month",nextMonth:"Next Month",close:"Close"},supercedes:["close","title"]}};F._DEFAULT_CONFIG=F.DEFAULT_CONFIG;var B=F.DEFAULT_CONFIG;F._EVENT_TYPES={BEFORE_SELECT:"beforeSelect",SELECT:"select",BEFORE_DESELECT:"beforeDeselect",DESELECT:"deselect",CHANGE_PAGE:"changePage",BEFORE_RENDER:"beforeRender",RENDER:"render",BEFORE_DESTROY:"beforeDestroy",DESTROY:"destroy",RESET:"reset",CLEAR:"clear",BEFORE_HIDE:"beforeHide",HIDE:"hide",BEFORE_SHOW:"beforeShow",SHOW:"show",BEFORE_HIDE_NAV:"beforeHideNav",HIDE_NAV:"hideNav",BEFORE_SHOW_NAV:"beforeShowNav",SHOW_NAV:"showNav",BEFORE_RENDER_NAV:"beforeRenderNav",RENDER_NAV:"renderNav"};F.STYLES={CSS_ROW_HEADER:"calrowhead",CSS_ROW_FOOTER:"calrowfoot",CSS_CELL:"calcell",CSS_CELL_SELECTOR:"selector",CSS_CELL_SELECTED:"selected",CSS_CELL_SELECTABLE:"selectable",CSS_CELL_RESTRICTED:"restricted",CSS_CELL_TODAY:"today",CSS_CELL_OOM:"oom",CSS_CELL_OOB:"previous",CSS_HEADER:"calheader",CSS_HEADER_TEXT:"calhead",CSS_BODY:"calbody",CSS_WEEKDAY_CELL:"calweekdaycell",CSS_WEEKDAY_ROW:"calweekdayrow",CSS_FOOTER:"calfoot",CSS_CALENDAR:"yui-calendar",CSS_SINGLE:"single",CSS_CONTAINER:"yui-calcontainer",CSS_NAV_LEFT:"calnavleft",CSS_NAV_RIGHT:"calnavright",CSS_NAV:"calnav",CSS_CLOSE:"calclose",CSS_CELL_TOP:"calcelltop",CSS_CELL_LEFT:"calcellleft",CSS_CELL_RIGHT:"calcellright",CSS_CELL_BOTTOM:"calcellbottom",CSS_CELL_HOVER:"calcellhover",CSS_CELL_HIGHLIGHT1:"highlight1",CSS_CELL_HIGHLIGHT2:"highlight2",CSS_CELL_HIGHLIGHT3:"highlight3",CSS_CELL_HIGHLIGHT4:"highlight4",CSS_WITH_TITLE:"withtitle",CSS_FIXED_SIZE:"fixedsize",CSS_LINK_CLOSE:"link-close"};F._STYLES=F.STYLES;F.prototype={Config:null,parent:null,index:-1,cells:null,cellDates:null,id:null,containerId:null,oDomContainer:null,today:null,renderStack:null,_renderStack:null,oNavigator:null,_selectedDates:null,domEventMap:null,_parseArgs:function(H){var G={id:null,container:null,config:null};if(H&&H.length&&H.length>0){switch(H.length){case 1:G.id=null;G.container=H[0];G.config=null;break;case 2:if(E.isObject(H[1])&&!H[1].tagName&&!(H[1] instanceof String)){G.id=null;G.container=H[0];G.config=H[1];}else{G.id=H[0];G.container=H[1];G.config=null;}break;default:G.id=H[0];G.container=H[1];G.config=H[2];break;}}else{}return G;},init:function(J,H,I){var G=this._parseArgs(arguments);J=G.id;H=G.container;I=G.config;this.oDomContainer=C.get(H);if(!this.oDomContainer.id){this.oDomContainer.id=C.generateId();
+}if(!J){J=this.oDomContainer.id+"_t";}this.id=J;this.containerId=this.oDomContainer.id;this.initEvents();this.cfg=new YAHOO.util.Config(this);this.Options={};this.Locale={};this.initStyles();C.addClass(this.oDomContainer,this.Style.CSS_CONTAINER);C.addClass(this.oDomContainer,this.Style.CSS_SINGLE);this.cellDates=[];this.cells=[];this.renderStack=[];this._renderStack=[];this.setupConfig();if(I){this.cfg.applyConfig(I,true);}this.cfg.fireQueue();this.today=this.cfg.getProperty("today");},configIframe:function(I,H,J){var G=H[0];if(!this.parent){if(C.inDocument(this.oDomContainer)){if(G){var K=C.getStyle(this.oDomContainer,"position");if(K=="absolute"||K=="relative"){if(!C.inDocument(this.iframe)){this.iframe=document.createElement("iframe");this.iframe.src="javascript:false;";C.setStyle(this.iframe,"opacity","0");if(YAHOO.env.ua.ie&&YAHOO.env.ua.ie<=6){C.addClass(this.iframe,this.Style.CSS_FIXED_SIZE);}this.oDomContainer.insertBefore(this.iframe,this.oDomContainer.firstChild);}}}else{if(this.iframe){if(this.iframe.parentNode){this.iframe.parentNode.removeChild(this.iframe);}this.iframe=null;}}}}},configTitle:function(H,G,I){var K=G[0];if(K){this.createTitleBar(K);}else{var J=this.cfg.getProperty(B.CLOSE.key);if(!J){this.removeTitleBar();}else{this.createTitleBar("&#160;");}}},configClose:function(H,G,I){var K=G[0],J=this.cfg.getProperty(B.TITLE.key);if(K){if(!J){this.createTitleBar("&#160;");}this.createCloseButton();}else{this.removeCloseButton();if(!J){this.removeTitleBar();}}},initEvents:function(){var G=F._EVENT_TYPES,I=YAHOO.util.CustomEvent,H=this;H.beforeSelectEvent=new I(G.BEFORE_SELECT);H.selectEvent=new I(G.SELECT);H.beforeDeselectEvent=new I(G.BEFORE_DESELECT);H.deselectEvent=new I(G.DESELECT);H.changePageEvent=new I(G.CHANGE_PAGE);H.beforeRenderEvent=new I(G.BEFORE_RENDER);H.renderEvent=new I(G.RENDER);H.beforeDestroyEvent=new I(G.BEFORE_DESTROY);H.destroyEvent=new I(G.DESTROY);H.resetEvent=new I(G.RESET);H.clearEvent=new I(G.CLEAR);H.beforeShowEvent=new I(G.BEFORE_SHOW);H.showEvent=new I(G.SHOW);H.beforeHideEvent=new I(G.BEFORE_HIDE);H.hideEvent=new I(G.HIDE);H.beforeShowNavEvent=new I(G.BEFORE_SHOW_NAV);H.showNavEvent=new I(G.SHOW_NAV);H.beforeHideNavEvent=new I(G.BEFORE_HIDE_NAV);H.hideNavEvent=new I(G.HIDE_NAV);H.beforeRenderNavEvent=new I(G.BEFORE_RENDER_NAV);H.renderNavEvent=new I(G.RENDER_NAV);H.beforeSelectEvent.subscribe(H.onBeforeSelect,this,true);H.selectEvent.subscribe(H.onSelect,this,true);H.beforeDeselectEvent.subscribe(H.onBeforeDeselect,this,true);H.deselectEvent.subscribe(H.onDeselect,this,true);H.changePageEvent.subscribe(H.onChangePage,this,true);H.renderEvent.subscribe(H.onRender,this,true);H.resetEvent.subscribe(H.onReset,this,true);H.clearEvent.subscribe(H.onClear,this,true);},doPreviousMonthNav:function(H,G){A.preventDefault(H);setTimeout(function(){G.previousMonth();var J=C.getElementsByClassName(G.Style.CSS_NAV_LEFT,"a",G.oDomContainer);if(J&&J[0]){try{J[0].focus();}catch(I){}}},0);},doNextMonthNav:function(H,G){A.preventDefault(H);setTimeout(function(){G.nextMonth();var J=C.getElementsByClassName(G.Style.CSS_NAV_RIGHT,"a",G.oDomContainer);if(J&&J[0]){try{J[0].focus();}catch(I){}}},0);},doSelectCell:function(M,G){var R,O,I,L;var N=A.getTarget(M),H=N.tagName.toLowerCase(),K=false;while(H!="td"&&!C.hasClass(N,G.Style.CSS_CELL_SELECTABLE)){if(!K&&H=="a"&&C.hasClass(N,G.Style.CSS_CELL_SELECTOR)){K=true;}N=N.parentNode;H=N.tagName.toLowerCase();if(N==this.oDomContainer||H=="html"){return;}}if(K){A.preventDefault(M);}R=N;if(C.hasClass(R,G.Style.CSS_CELL_SELECTABLE)){L=G.getIndexFromId(R.id);if(L>-1){O=G.cellDates[L];if(O){I=D.getDate(O[0],O[1]-1,O[2]);var Q;if(G.Options.MULTI_SELECT){Q=R.getElementsByTagName("a")[0];if(Q){Q.blur();}var J=G.cellDates[L];var P=G._indexOfSelectedFieldArray(J);if(P>-1){G.deselectCell(L);}else{G.selectCell(L);}}else{Q=R.getElementsByTagName("a")[0];if(Q){Q.blur();}G.selectCell(L);}}}}},doCellMouseOver:function(I,H){var G;if(I){G=A.getTarget(I);}else{G=this;}while(G.tagName&&G.tagName.toLowerCase()!="td"){G=G.parentNode;if(!G.tagName||G.tagName.toLowerCase()=="html"){return;}}if(C.hasClass(G,H.Style.CSS_CELL_SELECTABLE)){C.addClass(G,H.Style.CSS_CELL_HOVER);}},doCellMouseOut:function(I,H){var G;if(I){G=A.getTarget(I);}else{G=this;}while(G.tagName&&G.tagName.toLowerCase()!="td"){G=G.parentNode;if(!G.tagName||G.tagName.toLowerCase()=="html"){return;}}if(C.hasClass(G,H.Style.CSS_CELL_SELECTABLE)){C.removeClass(G,H.Style.CSS_CELL_HOVER);}},setupConfig:function(){var G=this.cfg;G.addProperty(B.TODAY.key,{value:new Date(B.TODAY.value.getTime()),supercedes:B.TODAY.supercedes,handler:this.configToday,suppressEvent:true});G.addProperty(B.PAGEDATE.key,{value:B.PAGEDATE.value||new Date(B.TODAY.value.getTime()),handler:this.configPageDate});G.addProperty(B.SELECTED.key,{value:B.SELECTED.value.concat(),handler:this.configSelected});G.addProperty(B.TITLE.key,{value:B.TITLE.value,handler:this.configTitle});G.addProperty(B.CLOSE.key,{value:B.CLOSE.value,handler:this.configClose});G.addProperty(B.IFRAME.key,{value:B.IFRAME.value,handler:this.configIframe,validator:G.checkBoolean});G.addProperty(B.MINDATE.key,{value:B.MINDATE.value,handler:this.configMinDate});G.addProperty(B.MAXDATE.key,{value:B.MAXDATE.value,handler:this.configMaxDate});G.addProperty(B.MULTI_SELECT.key,{value:B.MULTI_SELECT.value,handler:this.configOptions,validator:G.checkBoolean});G.addProperty(B.START_WEEKDAY.key,{value:B.START_WEEKDAY.value,handler:this.configOptions,validator:G.checkNumber});G.addProperty(B.SHOW_WEEKDAYS.key,{value:B.SHOW_WEEKDAYS.value,handler:this.configOptions,validator:G.checkBoolean});G.addProperty(B.SHOW_WEEK_HEADER.key,{value:B.SHOW_WEEK_HEADER.value,handler:this.configOptions,validator:G.checkBoolean});G.addProperty(B.SHOW_WEEK_FOOTER.key,{value:B.SHOW_WEEK_FOOTER.value,handler:this.configOptions,validator:G.checkBoolean});G.addProperty(B.HIDE_BLANK_WEEKS.key,{value:B.HIDE_BLANK_WEEKS.value,handler:this.configOptions,validator:G.checkBoolean});G.addProperty(B.NAV_ARROW_LEFT.key,{value:B.NAV_ARROW_LEFT.value,handler:this.configOptions});
+G.addProperty(B.NAV_ARROW_RIGHT.key,{value:B.NAV_ARROW_RIGHT.value,handler:this.configOptions});G.addProperty(B.MONTHS_SHORT.key,{value:B.MONTHS_SHORT.value,handler:this.configLocale});G.addProperty(B.MONTHS_LONG.key,{value:B.MONTHS_LONG.value,handler:this.configLocale});G.addProperty(B.WEEKDAYS_1CHAR.key,{value:B.WEEKDAYS_1CHAR.value,handler:this.configLocale});G.addProperty(B.WEEKDAYS_SHORT.key,{value:B.WEEKDAYS_SHORT.value,handler:this.configLocale});G.addProperty(B.WEEKDAYS_MEDIUM.key,{value:B.WEEKDAYS_MEDIUM.value,handler:this.configLocale});G.addProperty(B.WEEKDAYS_LONG.key,{value:B.WEEKDAYS_LONG.value,handler:this.configLocale});var H=function(){G.refireEvent(B.LOCALE_MONTHS.key);G.refireEvent(B.LOCALE_WEEKDAYS.key);};G.subscribeToConfigEvent(B.START_WEEKDAY.key,H,this,true);G.subscribeToConfigEvent(B.MONTHS_SHORT.key,H,this,true);G.subscribeToConfigEvent(B.MONTHS_LONG.key,H,this,true);G.subscribeToConfigEvent(B.WEEKDAYS_1CHAR.key,H,this,true);G.subscribeToConfigEvent(B.WEEKDAYS_SHORT.key,H,this,true);G.subscribeToConfigEvent(B.WEEKDAYS_MEDIUM.key,H,this,true);G.subscribeToConfigEvent(B.WEEKDAYS_LONG.key,H,this,true);G.addProperty(B.LOCALE_MONTHS.key,{value:B.LOCALE_MONTHS.value,handler:this.configLocaleValues});G.addProperty(B.LOCALE_WEEKDAYS.key,{value:B.LOCALE_WEEKDAYS.value,handler:this.configLocaleValues});G.addProperty(B.YEAR_OFFSET.key,{value:B.YEAR_OFFSET.value,supercedes:B.YEAR_OFFSET.supercedes,handler:this.configLocale});G.addProperty(B.DATE_DELIMITER.key,{value:B.DATE_DELIMITER.value,handler:this.configLocale});G.addProperty(B.DATE_FIELD_DELIMITER.key,{value:B.DATE_FIELD_DELIMITER.value,handler:this.configLocale});G.addProperty(B.DATE_RANGE_DELIMITER.key,{value:B.DATE_RANGE_DELIMITER.value,handler:this.configLocale});G.addProperty(B.MY_MONTH_POSITION.key,{value:B.MY_MONTH_POSITION.value,handler:this.configLocale,validator:G.checkNumber});G.addProperty(B.MY_YEAR_POSITION.key,{value:B.MY_YEAR_POSITION.value,handler:this.configLocale,validator:G.checkNumber});G.addProperty(B.MD_MONTH_POSITION.key,{value:B.MD_MONTH_POSITION.value,handler:this.configLocale,validator:G.checkNumber});G.addProperty(B.MD_DAY_POSITION.key,{value:B.MD_DAY_POSITION.value,handler:this.configLocale,validator:G.checkNumber});G.addProperty(B.MDY_MONTH_POSITION.key,{value:B.MDY_MONTH_POSITION.value,handler:this.configLocale,validator:G.checkNumber});G.addProperty(B.MDY_DAY_POSITION.key,{value:B.MDY_DAY_POSITION.value,handler:this.configLocale,validator:G.checkNumber});G.addProperty(B.MDY_YEAR_POSITION.key,{value:B.MDY_YEAR_POSITION.value,handler:this.configLocale,validator:G.checkNumber});G.addProperty(B.MY_LABEL_MONTH_POSITION.key,{value:B.MY_LABEL_MONTH_POSITION.value,handler:this.configLocale,validator:G.checkNumber});G.addProperty(B.MY_LABEL_YEAR_POSITION.key,{value:B.MY_LABEL_YEAR_POSITION.value,handler:this.configLocale,validator:G.checkNumber});G.addProperty(B.MY_LABEL_MONTH_SUFFIX.key,{value:B.MY_LABEL_MONTH_SUFFIX.value,handler:this.configLocale});G.addProperty(B.MY_LABEL_YEAR_SUFFIX.key,{value:B.MY_LABEL_YEAR_SUFFIX.value,handler:this.configLocale});G.addProperty(B.NAV.key,{value:B.NAV.value,handler:this.configNavigator});G.addProperty(B.STRINGS.key,{value:B.STRINGS.value,handler:this.configStrings,validator:function(I){return E.isObject(I);},supercedes:B.STRINGS.supercedes});},configStrings:function(H,G,I){var J=E.merge(B.STRINGS.value,G[0]);this.cfg.setProperty(B.STRINGS.key,J,true);},configPageDate:function(H,G,I){this.cfg.setProperty(B.PAGEDATE.key,this._parsePageDate(G[0]),true);},configMinDate:function(H,G,I){var J=G[0];if(E.isString(J)){J=this._parseDate(J);this.cfg.setProperty(B.MINDATE.key,D.getDate(J[0],(J[1]-1),J[2]));}},configMaxDate:function(H,G,I){var J=G[0];if(E.isString(J)){J=this._parseDate(J);this.cfg.setProperty(B.MAXDATE.key,D.getDate(J[0],(J[1]-1),J[2]));}},configToday:function(I,H,J){var K=H[0];if(E.isString(K)){K=this._parseDate(K);}var G=D.clearTime(K);if(!this.cfg.initialConfig[B.PAGEDATE.key]){this.cfg.setProperty(B.PAGEDATE.key,G);}this.today=G;this.cfg.setProperty(B.TODAY.key,G,true);},configSelected:function(I,G,K){var H=G[0],J=B.SELECTED.key;if(H){if(E.isString(H)){this.cfg.setProperty(J,this._parseDates(H),true);}}if(!this._selectedDates){this._selectedDates=this.cfg.getProperty(J);}},configOptions:function(H,G,I){this.Options[H.toUpperCase()]=G[0];},configLocale:function(H,G,I){this.Locale[H.toUpperCase()]=G[0];this.cfg.refireEvent(B.LOCALE_MONTHS.key);this.cfg.refireEvent(B.LOCALE_WEEKDAYS.key);},configLocaleValues:function(J,I,K){J=J.toLowerCase();var M=I[0],H=this.cfg,N=this.Locale;switch(J){case B.LOCALE_MONTHS.key:switch(M){case F.SHORT:N.LOCALE_MONTHS=H.getProperty(B.MONTHS_SHORT.key).concat();break;case F.LONG:N.LOCALE_MONTHS=H.getProperty(B.MONTHS_LONG.key).concat();break;}break;case B.LOCALE_WEEKDAYS.key:switch(M){case F.ONE_CHAR:N.LOCALE_WEEKDAYS=H.getProperty(B.WEEKDAYS_1CHAR.key).concat();break;case F.SHORT:N.LOCALE_WEEKDAYS=H.getProperty(B.WEEKDAYS_SHORT.key).concat();break;case F.MEDIUM:N.LOCALE_WEEKDAYS=H.getProperty(B.WEEKDAYS_MEDIUM.key).concat();break;case F.LONG:N.LOCALE_WEEKDAYS=H.getProperty(B.WEEKDAYS_LONG.key).concat();break;}var L=H.getProperty(B.START_WEEKDAY.key);if(L>0){for(var G=0;G<L;++G){N.LOCALE_WEEKDAYS.push(N.LOCALE_WEEKDAYS.shift());}}break;}},configNavigator:function(H,G,I){var J=G[0];if(YAHOO.widget.CalendarNavigator&&(J===true||E.isObject(J))){if(!this.oNavigator){this.oNavigator=new YAHOO.widget.CalendarNavigator(this);this.beforeRenderEvent.subscribe(function(){if(!this.pages){this.oNavigator.erase();}},this,true);}}else{if(this.oNavigator){this.oNavigator.destroy();this.oNavigator=null;}}},initStyles:function(){var G=F.STYLES;this.Style={CSS_ROW_HEADER:G.CSS_ROW_HEADER,CSS_ROW_FOOTER:G.CSS_ROW_FOOTER,CSS_CELL:G.CSS_CELL,CSS_CELL_SELECTOR:G.CSS_CELL_SELECTOR,CSS_CELL_SELECTED:G.CSS_CELL_SELECTED,CSS_CELL_SELECTABLE:G.CSS_CELL_SELECTABLE,CSS_CELL_RESTRICTED:G.CSS_CELL_RESTRICTED,CSS_CELL_TODAY:G.CSS_CELL_TODAY,CSS_CELL_OOM:G.CSS_CELL_OOM,CSS_CELL_OOB:G.CSS_CELL_OOB,CSS_HEADER:G.CSS_HEADER,CSS_HEADER_TEXT:G.CSS_HEADER_TEXT,CSS_BODY:G.CSS_BODY,CSS_WEEKDAY_CELL:G.CSS_WEEKDAY_CELL,CSS_WEEKDAY_ROW:G.CSS_WEEKDAY_ROW,CSS_FOOTER:G.CSS_FOOTER,CSS_CALENDAR:G.CSS_CALENDAR,CSS_SINGLE:G.CSS_SINGLE,CSS_CONTAINER:G.CSS_CONTAINER,CSS_NAV_LEFT:G.CSS_NAV_LEFT,CSS_NAV_RIGHT:G.CSS_NAV_RIGHT,CSS_NAV:G.CSS_NAV,CSS_CLOSE:G.CSS_CLOSE,CSS_CELL_TOP:G.CSS_CELL_TOP,CSS_CELL_LEFT:G.CSS_CELL_LEFT,CSS_CELL_RIGHT:G.CSS_CELL_RIGHT,CSS_CELL_BOTTOM:G.CSS_CELL_BOTTOM,CSS_CELL_HOVER:G.CSS_CELL_HOVER,CSS_CELL_HIGHLIGHT1:G.CSS_CELL_HIGHLIGHT1,CSS_CELL_HIGHLIGHT2:G.CSS_CELL_HIGHLIGHT2,CSS_CELL_HIGHLIGHT3:G.CSS_CELL_HIGHLIGHT3,CSS_CELL_HIGHLIGHT4:G.CSS_CELL_HIGHLIGHT4,CSS_WITH_TITLE:G.CSS_WITH_TITLE,CSS_FIXED_SIZE:G.CSS_FIXED_SIZE,CSS_LINK_CLOSE:G.CSS_LINK_CLOSE};
+},buildMonthLabel:function(){return this._buildMonthLabel(this.cfg.getProperty(B.PAGEDATE.key));},_buildMonthLabel:function(G){var I=this.Locale.LOCALE_MONTHS[G.getMonth()]+this.Locale.MY_LABEL_MONTH_SUFFIX,H=(G.getFullYear()+this.Locale.YEAR_OFFSET)+this.Locale.MY_LABEL_YEAR_SUFFIX;if(this.Locale.MY_LABEL_MONTH_POSITION==2||this.Locale.MY_LABEL_YEAR_POSITION==1){return H+I;}else{return I+H;}},buildDayLabel:function(G){return G.getDate();},createTitleBar:function(G){var H=C.getElementsByClassName(YAHOO.widget.CalendarGroup.CSS_2UPTITLE,"div",this.oDomContainer)[0]||document.createElement("div");H.className=YAHOO.widget.CalendarGroup.CSS_2UPTITLE;H.innerHTML=G;this.oDomContainer.insertBefore(H,this.oDomContainer.firstChild);C.addClass(this.oDomContainer,this.Style.CSS_WITH_TITLE);return H;},removeTitleBar:function(){var G=C.getElementsByClassName(YAHOO.widget.CalendarGroup.CSS_2UPTITLE,"div",this.oDomContainer)[0]||null;if(G){A.purgeElement(G);this.oDomContainer.removeChild(G);}C.removeClass(this.oDomContainer,this.Style.CSS_WITH_TITLE);},createCloseButton:function(){var K=YAHOO.widget.CalendarGroup.CSS_2UPCLOSE,J=this.Style.CSS_LINK_CLOSE,M="us/my/bn/x_d.gif",L=C.getElementsByClassName(J,"a",this.oDomContainer)[0],G=this.cfg.getProperty(B.STRINGS.key),H=(G&&G.close)?G.close:"";if(!L){L=document.createElement("a");A.addListener(L,"click",function(O,N){N.hide();A.preventDefault(O);},this);}L.href="#";L.className=J;if(F.IMG_ROOT!==null){var I=C.getElementsByClassName(K,"img",L)[0]||document.createElement("img");I.src=F.IMG_ROOT+M;I.className=K;L.appendChild(I);}else{L.innerHTML='<span class="'+K+" "+this.Style.CSS_CLOSE+'">'+H+"</span>";}this.oDomContainer.appendChild(L);return L;},removeCloseButton:function(){var G=C.getElementsByClassName(this.Style.CSS_LINK_CLOSE,"a",this.oDomContainer)[0]||null;if(G){A.purgeElement(G);this.oDomContainer.removeChild(G);}},renderHeader:function(Q){var P=7,O="us/tr/callt.gif",G="us/tr/calrt.gif",N=this.cfg,K=N.getProperty(B.PAGEDATE.key),L=N.getProperty(B.STRINGS.key),V=(L&&L.previousMonth)?L.previousMonth:"",H=(L&&L.nextMonth)?L.nextMonth:"",M;if(N.getProperty(B.SHOW_WEEK_HEADER.key)){P+=1;}if(N.getProperty(B.SHOW_WEEK_FOOTER.key)){P+=1;}Q[Q.length]="<thead>";Q[Q.length]="<tr>";Q[Q.length]='<th colspan="'+P+'" class="'+this.Style.CSS_HEADER_TEXT+'">';Q[Q.length]='<div class="'+this.Style.CSS_HEADER+'">';var X,U=false;if(this.parent){if(this.index===0){X=true;}if(this.index==(this.parent.cfg.getProperty("pages")-1)){U=true;}}else{X=true;U=true;}if(X){M=this._buildMonthLabel(D.subtract(K,D.MONTH,1));var R=N.getProperty(B.NAV_ARROW_LEFT.key);if(R===null&&F.IMG_ROOT!==null){R=F.IMG_ROOT+O;}var I=(R===null)?"":' style="background-image:url('+R+')"';Q[Q.length]='<a class="'+this.Style.CSS_NAV_LEFT+'"'+I+' href="#">'+V+" ("+M+")"+"</a>";}var W=this.buildMonthLabel();var S=this.parent||this;if(S.cfg.getProperty("navigator")){W='<a class="'+this.Style.CSS_NAV+'" href="#">'+W+"</a>";}Q[Q.length]=W;if(U){M=this._buildMonthLabel(D.add(K,D.MONTH,1));var T=N.getProperty(B.NAV_ARROW_RIGHT.key);if(T===null&&F.IMG_ROOT!==null){T=F.IMG_ROOT+G;}var J=(T===null)?"":' style="background-image:url('+T+')"';Q[Q.length]='<a class="'+this.Style.CSS_NAV_RIGHT+'"'+J+' href="#">'+H+" ("+M+")"+"</a>";}Q[Q.length]="</div>\n</th>\n</tr>";if(N.getProperty(B.SHOW_WEEKDAYS.key)){Q=this.buildWeekdays(Q);}Q[Q.length]="</thead>";return Q;},buildWeekdays:function(H){H[H.length]='<tr class="'+this.Style.CSS_WEEKDAY_ROW+'">';if(this.cfg.getProperty(B.SHOW_WEEK_HEADER.key)){H[H.length]="<th>&#160;</th>";}for(var G=0;G<this.Locale.LOCALE_WEEKDAYS.length;++G){H[H.length]='<th class="'+this.Style.CSS_WEEKDAY_CELL+'">'+this.Locale.LOCALE_WEEKDAYS[G]+"</th>";}if(this.cfg.getProperty(B.SHOW_WEEK_FOOTER.key)){H[H.length]="<th>&#160;</th>";}H[H.length]="</tr>";return H;},renderBody:function(m,k){var AK=this.cfg.getProperty(B.START_WEEKDAY.key);this.preMonthDays=m.getDay();if(AK>0){this.preMonthDays-=AK;}if(this.preMonthDays<0){this.preMonthDays+=7;}this.monthDays=D.findMonthEnd(m).getDate();this.postMonthDays=F.DISPLAY_DAYS-this.preMonthDays-this.monthDays;m=D.subtract(m,D.DAY,this.preMonthDays);var Y,N,M="w",f="_cell",c="wd",w="d",P,u,AC=this.today,O=this.cfg,W=AC.getFullYear(),v=AC.getMonth(),J=AC.getDate(),AB=O.getProperty(B.PAGEDATE.key),I=O.getProperty(B.HIDE_BLANK_WEEKS.key),j=O.getProperty(B.SHOW_WEEK_FOOTER.key),b=O.getProperty(B.SHOW_WEEK_HEADER.key),U=O.getProperty(B.MINDATE.key),a=O.getProperty(B.MAXDATE.key),T=this.Locale.YEAR_OFFSET;if(U){U=D.clearTime(U);}if(a){a=D.clearTime(a);}k[k.length]='<tbody class="m'+(AB.getMonth()+1)+" "+this.Style.CSS_BODY+'">';var AI=0,Q=document.createElement("div"),l=document.createElement("td");Q.appendChild(l);var AA=this.parent||this;for(var AE=0;AE<6;AE++){Y=D.getWeekNumber(m,AK);N=M+Y;if(AE!==0&&I===true&&m.getMonth()!=AB.getMonth()){break;}else{k[k.length]='<tr class="'+N+'">';if(b){k=this.renderRowHeader(Y,k);}for(var AJ=0;AJ<7;AJ++){P=[];this.clearElement(l);l.className=this.Style.CSS_CELL;l.id=this.id+f+AI;if(m.getDate()==J&&m.getMonth()==v&&m.getFullYear()==W){P[P.length]=AA.renderCellStyleToday;}var Z=[m.getFullYear(),m.getMonth()+1,m.getDate()];this.cellDates[this.cellDates.length]=Z;if(m.getMonth()!=AB.getMonth()){P[P.length]=AA.renderCellNotThisMonth;}else{C.addClass(l,c+m.getDay());C.addClass(l,w+m.getDate());for(var AD=0;AD<this.renderStack.length;++AD){u=null;var y=this.renderStack[AD],AL=y[0],H,e,L;switch(AL){case F.DATE:H=y[1][1];e=y[1][2];L=y[1][0];if(m.getMonth()+1==H&&m.getDate()==e&&m.getFullYear()==L){u=y[2];this.renderStack.splice(AD,1);}break;case F.MONTH_DAY:H=y[1][0];e=y[1][1];if(m.getMonth()+1==H&&m.getDate()==e){u=y[2];this.renderStack.splice(AD,1);}break;case F.RANGE:var h=y[1][0],g=y[1][1],n=h[1],S=h[2],X=h[0],AH=D.getDate(X,n-1,S),K=g[1],q=g[2],G=g[0],AG=D.getDate(G,K-1,q);if(m.getTime()>=AH.getTime()&&m.getTime()<=AG.getTime()){u=y[2];if(m.getTime()==AG.getTime()){this.renderStack.splice(AD,1);}}break;case F.WEEKDAY:var R=y[1][0];
+if(m.getDay()+1==R){u=y[2];}break;case F.MONTH:H=y[1][0];if(m.getMonth()+1==H){u=y[2];}break;}if(u){P[P.length]=u;}}}if(this._indexOfSelectedFieldArray(Z)>-1){P[P.length]=AA.renderCellStyleSelected;}if((U&&(m.getTime()<U.getTime()))||(a&&(m.getTime()>a.getTime()))){P[P.length]=AA.renderOutOfBoundsDate;}else{P[P.length]=AA.styleCellDefault;P[P.length]=AA.renderCellDefault;}for(var z=0;z<P.length;++z){if(P[z].call(AA,m,l)==F.STOP_RENDER){break;}}m.setTime(m.getTime()+D.ONE_DAY_MS);m=D.clearTime(m);if(AI>=0&&AI<=6){C.addClass(l,this.Style.CSS_CELL_TOP);}if((AI%7)===0){C.addClass(l,this.Style.CSS_CELL_LEFT);}if(((AI+1)%7)===0){C.addClass(l,this.Style.CSS_CELL_RIGHT);}var o=this.postMonthDays;if(I&&o>=7){var V=Math.floor(o/7);for(var AF=0;AF<V;++AF){o-=7;}}if(AI>=((this.preMonthDays+o+this.monthDays)-7)){C.addClass(l,this.Style.CSS_CELL_BOTTOM);}k[k.length]=Q.innerHTML;AI++;}if(j){k=this.renderRowFooter(Y,k);}k[k.length]="</tr>";}}k[k.length]="</tbody>";return k;},renderFooter:function(G){return G;},render:function(){this.beforeRenderEvent.fire();var H=D.findMonthStart(this.cfg.getProperty(B.PAGEDATE.key));this.resetRenderers();this.cellDates.length=0;A.purgeElement(this.oDomContainer,true);var G=[];G[G.length]='<table cellSpacing="0" class="'+this.Style.CSS_CALENDAR+" y"+(H.getFullYear()+this.Locale.YEAR_OFFSET)+'" id="'+this.id+'">';G=this.renderHeader(G);G=this.renderBody(H,G);G=this.renderFooter(G);G[G.length]="</table>";this.oDomContainer.innerHTML=G.join("\n");this.applyListeners();this.cells=C.getElementsByClassName(this.Style.CSS_CELL,"td",this.id);this.cfg.refireEvent(B.TITLE.key);this.cfg.refireEvent(B.CLOSE.key);this.cfg.refireEvent(B.IFRAME.key);this.renderEvent.fire();},applyListeners:function(){var P=this.oDomContainer,H=this.parent||this,L="a",S="click";var M=C.getElementsByClassName(this.Style.CSS_NAV_LEFT,L,P),I=C.getElementsByClassName(this.Style.CSS_NAV_RIGHT,L,P);if(M&&M.length>0){this.linkLeft=M[0];A.addListener(this.linkLeft,S,this.doPreviousMonthNav,H,true);}if(I&&I.length>0){this.linkRight=I[0];A.addListener(this.linkRight,S,this.doNextMonthNav,H,true);}if(H.cfg.getProperty("navigator")!==null){this.applyNavListeners();}if(this.domEventMap){var J,G;for(var R in this.domEventMap){if(E.hasOwnProperty(this.domEventMap,R)){var N=this.domEventMap[R];if(!(N instanceof Array)){N=[N];}for(var K=0;K<N.length;K++){var Q=N[K];G=C.getElementsByClassName(R,Q.tag,this.oDomContainer);for(var O=0;O<G.length;O++){J=G[O];A.addListener(J,Q.event,Q.handler,Q.scope,Q.correct);}}}}}A.addListener(this.oDomContainer,"click",this.doSelectCell,this);A.addListener(this.oDomContainer,"mouseover",this.doCellMouseOver,this);A.addListener(this.oDomContainer,"mouseout",this.doCellMouseOut,this);},applyNavListeners:function(){var H=this.parent||this,I=this,G=C.getElementsByClassName(this.Style.CSS_NAV,"a",this.oDomContainer);if(G.length>0){A.addListener(G,"click",function(N,M){var L=A.getTarget(N);if(this===L||C.isAncestor(this,L)){A.preventDefault(N);}var J=H.oNavigator;if(J){var K=I.cfg.getProperty("pagedate");J.setYear(K.getFullYear()+I.Locale.YEAR_OFFSET);J.setMonth(K.getMonth());J.show();}});}},getDateByCellId:function(H){var G=this.getDateFieldsByCellId(H);return(G)?D.getDate(G[0],G[1]-1,G[2]):null;},getDateFieldsByCellId:function(G){G=this.getIndexFromId(G);return(G>-1)?this.cellDates[G]:null;},getCellIndex:function(I){var H=-1;if(I){var G=I.getMonth(),N=I.getFullYear(),M=I.getDate(),K=this.cellDates;for(var J=0;J<K.length;++J){var L=K[J];if(L[0]===N&&L[1]===G+1&&L[2]===M){H=J;break;}}}return H;},getIndexFromId:function(I){var H=-1,G=I.lastIndexOf("_cell");if(G>-1){H=parseInt(I.substring(G+5),10);}return H;},renderOutOfBoundsDate:function(H,G){C.addClass(G,this.Style.CSS_CELL_OOB);G.innerHTML=H.getDate();return F.STOP_RENDER;},renderRowHeader:function(H,G){G[G.length]='<th class="'+this.Style.CSS_ROW_HEADER+'">'+H+"</th>";return G;},renderRowFooter:function(H,G){G[G.length]='<th class="'+this.Style.CSS_ROW_FOOTER+'">'+H+"</th>";return G;},renderCellDefault:function(H,G){G.innerHTML='<a href="#" class="'+this.Style.CSS_CELL_SELECTOR+'">'+this.buildDayLabel(H)+"</a>";},styleCellDefault:function(H,G){C.addClass(G,this.Style.CSS_CELL_SELECTABLE);},renderCellStyleHighlight1:function(H,G){C.addClass(G,this.Style.CSS_CELL_HIGHLIGHT1);},renderCellStyleHighlight2:function(H,G){C.addClass(G,this.Style.CSS_CELL_HIGHLIGHT2);},renderCellStyleHighlight3:function(H,G){C.addClass(G,this.Style.CSS_CELL_HIGHLIGHT3);},renderCellStyleHighlight4:function(H,G){C.addClass(G,this.Style.CSS_CELL_HIGHLIGHT4);},renderCellStyleToday:function(H,G){C.addClass(G,this.Style.CSS_CELL_TODAY);},renderCellStyleSelected:function(H,G){C.addClass(G,this.Style.CSS_CELL_SELECTED);},renderCellNotThisMonth:function(H,G){C.addClass(G,this.Style.CSS_CELL_OOM);G.innerHTML=H.getDate();return F.STOP_RENDER;},renderBodyCellRestricted:function(H,G){C.addClass(G,this.Style.CSS_CELL);C.addClass(G,this.Style.CSS_CELL_RESTRICTED);G.innerHTML=H.getDate();return F.STOP_RENDER;},addMonths:function(I){var H=B.PAGEDATE.key,J=this.cfg.getProperty(H),G=D.add(J,D.MONTH,I);this.cfg.setProperty(H,G);this.resetRenderers();this.changePageEvent.fire(J,G);},subtractMonths:function(G){this.addMonths(-1*G);},addYears:function(I){var H=B.PAGEDATE.key,J=this.cfg.getProperty(H),G=D.add(J,D.YEAR,I);this.cfg.setProperty(H,G);this.resetRenderers();this.changePageEvent.fire(J,G);},subtractYears:function(G){this.addYears(-1*G);},nextMonth:function(){this.addMonths(1);},previousMonth:function(){this.addMonths(-1);},nextYear:function(){this.addYears(1);},previousYear:function(){this.addYears(-1);},reset:function(){this.cfg.resetProperty(B.SELECTED.key);this.cfg.resetProperty(B.PAGEDATE.key);this.resetEvent.fire();},clear:function(){this.cfg.setProperty(B.SELECTED.key,[]);this.cfg.setProperty(B.PAGEDATE.key,new Date(this.today.getTime()));this.clearEvent.fire();},select:function(I){var L=this._toFieldArray(I),H=[],K=[],M=B.SELECTED.key;for(var G=0;G<L.length;++G){var J=L[G];
+if(!this.isDateOOB(this._toDate(J))){if(H.length===0){this.beforeSelectEvent.fire();K=this.cfg.getProperty(M);}H.push(J);if(this._indexOfSelectedFieldArray(J)==-1){K[K.length]=J;}}}if(H.length>0){if(this.parent){this.parent.cfg.setProperty(M,K);}else{this.cfg.setProperty(M,K);}this.selectEvent.fire(H);}return this.getSelectedDates();},selectCell:function(J){var H=this.cells[J],N=this.cellDates[J],M=this._toDate(N),I=C.hasClass(H,this.Style.CSS_CELL_SELECTABLE);if(I){this.beforeSelectEvent.fire();var L=B.SELECTED.key;var K=this.cfg.getProperty(L);var G=N.concat();if(this._indexOfSelectedFieldArray(G)==-1){K[K.length]=G;}if(this.parent){this.parent.cfg.setProperty(L,K);}else{this.cfg.setProperty(L,K);}this.renderCellStyleSelected(M,H);this.selectEvent.fire([G]);this.doCellMouseOut.call(H,null,this);}return this.getSelectedDates();},deselect:function(K){var G=this._toFieldArray(K),J=[],M=[],N=B.SELECTED.key;for(var H=0;H<G.length;++H){var L=G[H];if(!this.isDateOOB(this._toDate(L))){if(J.length===0){this.beforeDeselectEvent.fire();M=this.cfg.getProperty(N);}J.push(L);var I=this._indexOfSelectedFieldArray(L);if(I!=-1){M.splice(I,1);}}}if(J.length>0){if(this.parent){this.parent.cfg.setProperty(N,M);}else{this.cfg.setProperty(N,M);}this.deselectEvent.fire(J);}return this.getSelectedDates();},deselectCell:function(K){var H=this.cells[K],N=this.cellDates[K],I=this._indexOfSelectedFieldArray(N);var J=C.hasClass(H,this.Style.CSS_CELL_SELECTABLE);if(J){this.beforeDeselectEvent.fire();var L=this.cfg.getProperty(B.SELECTED.key),M=this._toDate(N),G=N.concat();if(I>-1){if(this.cfg.getProperty(B.PAGEDATE.key).getMonth()==M.getMonth()&&this.cfg.getProperty(B.PAGEDATE.key).getFullYear()==M.getFullYear()){C.removeClass(H,this.Style.CSS_CELL_SELECTED);}L.splice(I,1);}if(this.parent){this.parent.cfg.setProperty(B.SELECTED.key,L);}else{this.cfg.setProperty(B.SELECTED.key,L);}this.deselectEvent.fire([G]);}return this.getSelectedDates();},deselectAll:function(){this.beforeDeselectEvent.fire();var J=B.SELECTED.key,G=this.cfg.getProperty(J),H=G.length,I=G.concat();if(this.parent){this.parent.cfg.setProperty(J,[]);}else{this.cfg.setProperty(J,[]);}if(H>0){this.deselectEvent.fire(I);}return this.getSelectedDates();},_toFieldArray:function(H){var G=[];if(H instanceof Date){G=[[H.getFullYear(),H.getMonth()+1,H.getDate()]];}else{if(E.isString(H)){G=this._parseDates(H);}else{if(E.isArray(H)){for(var I=0;I<H.length;++I){var J=H[I];G[G.length]=[J.getFullYear(),J.getMonth()+1,J.getDate()];}}}}return G;},toDate:function(G){return this._toDate(G);},_toDate:function(G){if(G instanceof Date){return G;}else{return D.getDate(G[0],G[1]-1,G[2]);}},_fieldArraysAreEqual:function(I,H){var G=false;if(I[0]==H[0]&&I[1]==H[1]&&I[2]==H[2]){G=true;}return G;},_indexOfSelectedFieldArray:function(K){var J=-1,G=this.cfg.getProperty(B.SELECTED.key);for(var I=0;I<G.length;++I){var H=G[I];if(K[0]==H[0]&&K[1]==H[1]&&K[2]==H[2]){J=I;break;}}return J;},isDateOOM:function(G){return(G.getMonth()!=this.cfg.getProperty(B.PAGEDATE.key).getMonth());},isDateOOB:function(I){var J=this.cfg.getProperty(B.MINDATE.key),K=this.cfg.getProperty(B.MAXDATE.key),H=D;if(J){J=H.clearTime(J);}if(K){K=H.clearTime(K);}var G=new Date(I.getTime());G=H.clearTime(G);return((J&&G.getTime()<J.getTime())||(K&&G.getTime()>K.getTime()));},_parsePageDate:function(G){var J;if(G){if(G instanceof Date){J=D.findMonthStart(G);}else{var K,I,H;H=G.split(this.cfg.getProperty(B.DATE_FIELD_DELIMITER.key));K=parseInt(H[this.cfg.getProperty(B.MY_MONTH_POSITION.key)-1],10)-1;I=parseInt(H[this.cfg.getProperty(B.MY_YEAR_POSITION.key)-1],10)-this.Locale.YEAR_OFFSET;J=D.getDate(I,K,1);}}else{J=D.getDate(this.today.getFullYear(),this.today.getMonth(),1);}return J;},onBeforeSelect:function(){if(this.cfg.getProperty(B.MULTI_SELECT.key)===false){if(this.parent){this.parent.callChildFunction("clearAllBodyCellStyles",this.Style.CSS_CELL_SELECTED);this.parent.deselectAll();}else{this.clearAllBodyCellStyles(this.Style.CSS_CELL_SELECTED);this.deselectAll();}}},onSelect:function(G){},onBeforeDeselect:function(){},onDeselect:function(G){},onChangePage:function(){this.render();},onRender:function(){},onReset:function(){this.render();},onClear:function(){this.render();},validate:function(){return true;},_parseDate:function(I){var J=I.split(this.Locale.DATE_FIELD_DELIMITER),G;if(J.length==2){G=[J[this.Locale.MD_MONTH_POSITION-1],J[this.Locale.MD_DAY_POSITION-1]];G.type=F.MONTH_DAY;}else{G=[J[this.Locale.MDY_YEAR_POSITION-1]-this.Locale.YEAR_OFFSET,J[this.Locale.MDY_MONTH_POSITION-1],J[this.Locale.MDY_DAY_POSITION-1]];G.type=F.DATE;}for(var H=0;H<G.length;H++){G[H]=parseInt(G[H],10);}return G;},_parseDates:function(H){var O=[],N=H.split(this.Locale.DATE_DELIMITER);for(var M=0;M<N.length;++M){var L=N[M];if(L.indexOf(this.Locale.DATE_RANGE_DELIMITER)!=-1){var G=L.split(this.Locale.DATE_RANGE_DELIMITER),K=this._parseDate(G[0]),P=this._parseDate(G[1]),J=this._parseRange(K,P);O=O.concat(J);}else{var I=this._parseDate(L);O.push(I);}}return O;},_parseRange:function(G,K){var H=D.add(D.getDate(G[0],G[1]-1,G[2]),D.DAY,1),J=D.getDate(K[0],K[1]-1,K[2]),I=[];I.push(G);while(H.getTime()<=J.getTime()){I.push([H.getFullYear(),H.getMonth()+1,H.getDate()]);H=D.add(H,D.DAY,1);}return I;},resetRenderers:function(){this.renderStack=this._renderStack.concat();},removeRenderers:function(){this._renderStack=[];this.renderStack=[];},clearElement:function(G){G.innerHTML="&#160;";G.className="";},addRenderer:function(G,H){var J=this._parseDates(G);for(var I=0;I<J.length;++I){var K=J[I];if(K.length==2){if(K[0] instanceof Array){this._addRenderer(F.RANGE,K,H);}else{this._addRenderer(F.MONTH_DAY,K,H);}}else{if(K.length==3){this._addRenderer(F.DATE,K,H);}}}},_addRenderer:function(H,I,G){var J=[H,I,G];this.renderStack.unshift(J);this._renderStack=this.renderStack.concat();},addMonthRenderer:function(H,G){this._addRenderer(F.MONTH,[H],G);},addWeekdayRenderer:function(H,G){this._addRenderer(F.WEEKDAY,[H],G);},clearAllBodyCellStyles:function(G){for(var H=0;
+H<this.cells.length;++H){C.removeClass(this.cells[H],G);}},setMonth:function(I){var G=B.PAGEDATE.key,H=this.cfg.getProperty(G);H.setMonth(parseInt(I,10));this.cfg.setProperty(G,H);},setYear:function(H){var G=B.PAGEDATE.key,I=this.cfg.getProperty(G);I.setFullYear(parseInt(H,10)-this.Locale.YEAR_OFFSET);this.cfg.setProperty(G,I);},getSelectedDates:function(){var I=[],H=this.cfg.getProperty(B.SELECTED.key);for(var K=0;K<H.length;++K){var J=H[K];var G=D.getDate(J[0],J[1]-1,J[2]);I.push(G);}I.sort(function(M,L){return M-L;});return I;},hide:function(){if(this.beforeHideEvent.fire()){this.oDomContainer.style.display="none";this.hideEvent.fire();}},show:function(){if(this.beforeShowEvent.fire()){this.oDomContainer.style.display="block";this.showEvent.fire();}},browser:(function(){var G=navigator.userAgent.toLowerCase();if(G.indexOf("opera")!=-1){return"opera";}else{if(G.indexOf("msie 7")!=-1){return"ie7";}else{if(G.indexOf("msie")!=-1){return"ie";}else{if(G.indexOf("safari")!=-1){return"safari";}else{if(G.indexOf("gecko")!=-1){return"gecko";}else{return false;}}}}}})(),toString:function(){return"Calendar "+this.id;},destroy:function(){if(this.beforeDestroyEvent.fire()){var G=this;if(G.navigator){G.navigator.destroy();}if(G.cfg){G.cfg.destroy();}A.purgeElement(G.oDomContainer,true);C.removeClass(G.oDomContainer,G.Style.CSS_WITH_TITLE);C.removeClass(G.oDomContainer,G.Style.CSS_CONTAINER);C.removeClass(G.oDomContainer,G.Style.CSS_SINGLE);G.oDomContainer.innerHTML="";G.oDomContainer=null;G.cells=null;this.destroyEvent.fire();}}};YAHOO.widget.Calendar=F;YAHOO.widget.Calendar_Core=YAHOO.widget.Calendar;YAHOO.widget.Cal_Core=YAHOO.widget.Calendar;})();(function(){var D=YAHOO.util.Dom,F=YAHOO.widget.DateMath,A=YAHOO.util.Event,E=YAHOO.lang,G=YAHOO.widget.Calendar;function B(J,H,I){if(arguments.length>0){this.init.apply(this,arguments);}}B.DEFAULT_CONFIG=B._DEFAULT_CONFIG=G.DEFAULT_CONFIG;B.DEFAULT_CONFIG.PAGES={key:"pages",value:2};var C=B.DEFAULT_CONFIG;B.prototype={init:function(K,I,J){var H=this._parseArgs(arguments);K=H.id;I=H.container;J=H.config;this.oDomContainer=D.get(I);if(!this.oDomContainer.id){this.oDomContainer.id=D.generateId();}if(!K){K=this.oDomContainer.id+"_t";}this.id=K;this.containerId=this.oDomContainer.id;this.initEvents();this.initStyles();this.pages=[];D.addClass(this.oDomContainer,B.CSS_CONTAINER);D.addClass(this.oDomContainer,B.CSS_MULTI_UP);this.cfg=new YAHOO.util.Config(this);this.Options={};this.Locale={};this.setupConfig();if(J){this.cfg.applyConfig(J,true);}this.cfg.fireQueue();if(YAHOO.env.ua.opera){this.renderEvent.subscribe(this._fixWidth,this,true);this.showEvent.subscribe(this._fixWidth,this,true);}},setupConfig:function(){var H=this.cfg;H.addProperty(C.PAGES.key,{value:C.PAGES.value,validator:H.checkNumber,handler:this.configPages});H.addProperty(C.YEAR_OFFSET.key,{value:C.YEAR_OFFSET.value,handler:this.delegateConfig,supercedes:C.YEAR_OFFSET.supercedes,suppressEvent:true});H.addProperty(C.TODAY.key,{value:new Date(C.TODAY.value.getTime()),supercedes:C.TODAY.supercedes,handler:this.configToday,suppressEvent:false});H.addProperty(C.PAGEDATE.key,{value:C.PAGEDATE.value||new Date(C.TODAY.value.getTime()),handler:this.configPageDate});H.addProperty(C.SELECTED.key,{value:[],handler:this.configSelected});H.addProperty(C.TITLE.key,{value:C.TITLE.value,handler:this.configTitle});H.addProperty(C.CLOSE.key,{value:C.CLOSE.value,handler:this.configClose});H.addProperty(C.IFRAME.key,{value:C.IFRAME.value,handler:this.configIframe,validator:H.checkBoolean});H.addProperty(C.MINDATE.key,{value:C.MINDATE.value,handler:this.delegateConfig});H.addProperty(C.MAXDATE.key,{value:C.MAXDATE.value,handler:this.delegateConfig});H.addProperty(C.MULTI_SELECT.key,{value:C.MULTI_SELECT.value,handler:this.delegateConfig,validator:H.checkBoolean});H.addProperty(C.START_WEEKDAY.key,{value:C.START_WEEKDAY.value,handler:this.delegateConfig,validator:H.checkNumber});H.addProperty(C.SHOW_WEEKDAYS.key,{value:C.SHOW_WEEKDAYS.value,handler:this.delegateConfig,validator:H.checkBoolean});H.addProperty(C.SHOW_WEEK_HEADER.key,{value:C.SHOW_WEEK_HEADER.value,handler:this.delegateConfig,validator:H.checkBoolean});H.addProperty(C.SHOW_WEEK_FOOTER.key,{value:C.SHOW_WEEK_FOOTER.value,handler:this.delegateConfig,validator:H.checkBoolean});H.addProperty(C.HIDE_BLANK_WEEKS.key,{value:C.HIDE_BLANK_WEEKS.value,handler:this.delegateConfig,validator:H.checkBoolean});H.addProperty(C.NAV_ARROW_LEFT.key,{value:C.NAV_ARROW_LEFT.value,handler:this.delegateConfig});H.addProperty(C.NAV_ARROW_RIGHT.key,{value:C.NAV_ARROW_RIGHT.value,handler:this.delegateConfig});H.addProperty(C.MONTHS_SHORT.key,{value:C.MONTHS_SHORT.value,handler:this.delegateConfig});H.addProperty(C.MONTHS_LONG.key,{value:C.MONTHS_LONG.value,handler:this.delegateConfig});H.addProperty(C.WEEKDAYS_1CHAR.key,{value:C.WEEKDAYS_1CHAR.value,handler:this.delegateConfig});H.addProperty(C.WEEKDAYS_SHORT.key,{value:C.WEEKDAYS_SHORT.value,handler:this.delegateConfig});H.addProperty(C.WEEKDAYS_MEDIUM.key,{value:C.WEEKDAYS_MEDIUM.value,handler:this.delegateConfig});H.addProperty(C.WEEKDAYS_LONG.key,{value:C.WEEKDAYS_LONG.value,handler:this.delegateConfig});H.addProperty(C.LOCALE_MONTHS.key,{value:C.LOCALE_MONTHS.value,handler:this.delegateConfig});H.addProperty(C.LOCALE_WEEKDAYS.key,{value:C.LOCALE_WEEKDAYS.value,handler:this.delegateConfig});H.addProperty(C.DATE_DELIMITER.key,{value:C.DATE_DELIMITER.value,handler:this.delegateConfig});H.addProperty(C.DATE_FIELD_DELIMITER.key,{value:C.DATE_FIELD_DELIMITER.value,handler:this.delegateConfig});H.addProperty(C.DATE_RANGE_DELIMITER.key,{value:C.DATE_RANGE_DELIMITER.value,handler:this.delegateConfig});H.addProperty(C.MY_MONTH_POSITION.key,{value:C.MY_MONTH_POSITION.value,handler:this.delegateConfig,validator:H.checkNumber});H.addProperty(C.MY_YEAR_POSITION.key,{value:C.MY_YEAR_POSITION.value,handler:this.delegateConfig,validator:H.checkNumber});H.addProperty(C.MD_MONTH_POSITION.key,{value:C.MD_MONTH_POSITION.value,handler:this.delegateConfig,validator:H.checkNumber});
+H.addProperty(C.MD_DAY_POSITION.key,{value:C.MD_DAY_POSITION.value,handler:this.delegateConfig,validator:H.checkNumber});H.addProperty(C.MDY_MONTH_POSITION.key,{value:C.MDY_MONTH_POSITION.value,handler:this.delegateConfig,validator:H.checkNumber});H.addProperty(C.MDY_DAY_POSITION.key,{value:C.MDY_DAY_POSITION.value,handler:this.delegateConfig,validator:H.checkNumber});H.addProperty(C.MDY_YEAR_POSITION.key,{value:C.MDY_YEAR_POSITION.value,handler:this.delegateConfig,validator:H.checkNumber});H.addProperty(C.MY_LABEL_MONTH_POSITION.key,{value:C.MY_LABEL_MONTH_POSITION.value,handler:this.delegateConfig,validator:H.checkNumber});H.addProperty(C.MY_LABEL_YEAR_POSITION.key,{value:C.MY_LABEL_YEAR_POSITION.value,handler:this.delegateConfig,validator:H.checkNumber});H.addProperty(C.MY_LABEL_MONTH_SUFFIX.key,{value:C.MY_LABEL_MONTH_SUFFIX.value,handler:this.delegateConfig});H.addProperty(C.MY_LABEL_YEAR_SUFFIX.key,{value:C.MY_LABEL_YEAR_SUFFIX.value,handler:this.delegateConfig});H.addProperty(C.NAV.key,{value:C.NAV.value,handler:this.configNavigator});H.addProperty(C.STRINGS.key,{value:C.STRINGS.value,handler:this.configStrings,validator:function(I){return E.isObject(I);},supercedes:C.STRINGS.supercedes});},initEvents:function(){var J=this,L="Event",M=YAHOO.util.CustomEvent;var I=function(O,R,N){for(var Q=0;Q<J.pages.length;++Q){var P=J.pages[Q];P[this.type+L].subscribe(O,R,N);}};var H=function(N,Q){for(var P=0;P<J.pages.length;++P){var O=J.pages[P];O[this.type+L].unsubscribe(N,Q);}};var K=G._EVENT_TYPES;J.beforeSelectEvent=new M(K.BEFORE_SELECT);J.beforeSelectEvent.subscribe=I;J.beforeSelectEvent.unsubscribe=H;J.selectEvent=new M(K.SELECT);J.selectEvent.subscribe=I;J.selectEvent.unsubscribe=H;J.beforeDeselectEvent=new M(K.BEFORE_DESELECT);J.beforeDeselectEvent.subscribe=I;J.beforeDeselectEvent.unsubscribe=H;J.deselectEvent=new M(K.DESELECT);J.deselectEvent.subscribe=I;J.deselectEvent.unsubscribe=H;J.changePageEvent=new M(K.CHANGE_PAGE);J.changePageEvent.subscribe=I;J.changePageEvent.unsubscribe=H;J.beforeRenderEvent=new M(K.BEFORE_RENDER);J.beforeRenderEvent.subscribe=I;J.beforeRenderEvent.unsubscribe=H;J.renderEvent=new M(K.RENDER);J.renderEvent.subscribe=I;J.renderEvent.unsubscribe=H;J.resetEvent=new M(K.RESET);J.resetEvent.subscribe=I;J.resetEvent.unsubscribe=H;J.clearEvent=new M(K.CLEAR);J.clearEvent.subscribe=I;J.clearEvent.unsubscribe=H;J.beforeShowEvent=new M(K.BEFORE_SHOW);J.showEvent=new M(K.SHOW);J.beforeHideEvent=new M(K.BEFORE_HIDE);J.hideEvent=new M(K.HIDE);J.beforeShowNavEvent=new M(K.BEFORE_SHOW_NAV);J.showNavEvent=new M(K.SHOW_NAV);J.beforeHideNavEvent=new M(K.BEFORE_HIDE_NAV);J.hideNavEvent=new M(K.HIDE_NAV);J.beforeRenderNavEvent=new M(K.BEFORE_RENDER_NAV);J.renderNavEvent=new M(K.RENDER_NAV);J.beforeDestroyEvent=new M(K.BEFORE_DESTROY);J.destroyEvent=new M(K.DESTROY);},configPages:function(T,R,N){var L=R[0],J=C.PAGEDATE.key,W="_",M,O=null,S="groupcal",V="first-of-type",K="last-of-type";for(var I=0;I<L;++I){var U=this.id+W+I,Q=this.containerId+W+I,P=this.cfg.getConfig();P.close=false;P.title=false;P.navigator=null;if(I>0){M=new Date(O);this._setMonthOnDate(M,M.getMonth()+I);P.pageDate=M;}var H=this.constructChild(U,Q,P);D.removeClass(H.oDomContainer,this.Style.CSS_SINGLE);D.addClass(H.oDomContainer,S);if(I===0){O=H.cfg.getProperty(J);D.addClass(H.oDomContainer,V);}if(I==(L-1)){D.addClass(H.oDomContainer,K);}H.parent=this;H.index=I;this.pages[this.pages.length]=H;}},configPageDate:function(O,N,L){var J=N[0],M;var K=C.PAGEDATE.key;for(var I=0;I<this.pages.length;++I){var H=this.pages[I];if(I===0){M=H._parsePageDate(J);H.cfg.setProperty(K,M);}else{var P=new Date(M);this._setMonthOnDate(P,P.getMonth()+I);H.cfg.setProperty(K,P);}}},configSelected:function(J,H,L){var K=C.SELECTED.key;this.delegateConfig(J,H,L);var I=(this.pages.length>0)?this.pages[0].cfg.getProperty(K):[];this.cfg.setProperty(K,I,true);},delegateConfig:function(I,H,L){var M=H[0];var K;for(var J=0;J<this.pages.length;J++){K=this.pages[J];K.cfg.setProperty(I,M);}},setChildFunction:function(K,I){var H=this.cfg.getProperty(C.PAGES.key);for(var J=0;J<H;++J){this.pages[J][K]=I;}},callChildFunction:function(M,I){var H=this.cfg.getProperty(C.PAGES.key);for(var L=0;L<H;++L){var K=this.pages[L];if(K[M]){var J=K[M];J.call(K,I);}}},constructChild:function(K,I,J){var H=document.getElementById(I);if(!H){H=document.createElement("div");H.id=I;this.oDomContainer.appendChild(H);}return new G(K,I,J);},setMonth:function(L){L=parseInt(L,10);var M;var I=C.PAGEDATE.key;for(var K=0;K<this.pages.length;++K){var J=this.pages[K];var H=J.cfg.getProperty(I);if(K===0){M=H.getFullYear();}else{H.setFullYear(M);}this._setMonthOnDate(H,L+K);J.cfg.setProperty(I,H);}},setYear:function(J){var I=C.PAGEDATE.key;J=parseInt(J,10);for(var L=0;L<this.pages.length;++L){var K=this.pages[L];var H=K.cfg.getProperty(I);if((H.getMonth()+1)==1&&L>0){J+=1;}K.setYear(J);}},render:function(){this.renderHeader();for(var I=0;I<this.pages.length;++I){var H=this.pages[I];H.render();}this.renderFooter();},select:function(H){for(var J=0;J<this.pages.length;++J){var I=this.pages[J];I.select(H);}return this.getSelectedDates();},selectCell:function(H){for(var J=0;J<this.pages.length;++J){var I=this.pages[J];I.selectCell(H);}return this.getSelectedDates();},deselect:function(H){for(var J=0;J<this.pages.length;++J){var I=this.pages[J];I.deselect(H);}return this.getSelectedDates();},deselectAll:function(){for(var I=0;I<this.pages.length;++I){var H=this.pages[I];H.deselectAll();}return this.getSelectedDates();},deselectCell:function(H){for(var J=0;J<this.pages.length;++J){var I=this.pages[J];I.deselectCell(H);}return this.getSelectedDates();},reset:function(){for(var I=0;I<this.pages.length;++I){var H=this.pages[I];H.reset();}},clear:function(){for(var I=0;I<this.pages.length;++I){var H=this.pages[I];H.clear();}this.cfg.setProperty(C.SELECTED.key,[]);this.cfg.setProperty(C.PAGEDATE.key,new Date(this.pages[0].today.getTime()));this.render();},nextMonth:function(){for(var I=0;I<this.pages.length;
+++I){var H=this.pages[I];H.nextMonth();}},previousMonth:function(){for(var I=this.pages.length-1;I>=0;--I){var H=this.pages[I];H.previousMonth();}},nextYear:function(){for(var I=0;I<this.pages.length;++I){var H=this.pages[I];H.nextYear();}},previousYear:function(){for(var I=0;I<this.pages.length;++I){var H=this.pages[I];H.previousYear();}},getSelectedDates:function(){var J=[];var I=this.cfg.getProperty(C.SELECTED.key);for(var L=0;L<I.length;++L){var K=I[L];var H=F.getDate(K[0],K[1]-1,K[2]);J.push(H);}J.sort(function(N,M){return N-M;});return J;},addRenderer:function(H,I){for(var K=0;K<this.pages.length;++K){var J=this.pages[K];J.addRenderer(H,I);}},addMonthRenderer:function(K,H){for(var J=0;J<this.pages.length;++J){var I=this.pages[J];I.addMonthRenderer(K,H);}},addWeekdayRenderer:function(I,H){for(var K=0;K<this.pages.length;++K){var J=this.pages[K];J.addWeekdayRenderer(I,H);}},removeRenderers:function(){this.callChildFunction("removeRenderers");},renderHeader:function(){},renderFooter:function(){},addMonths:function(H){this.callChildFunction("addMonths",H);},subtractMonths:function(H){this.callChildFunction("subtractMonths",H);},addYears:function(H){this.callChildFunction("addYears",H);},subtractYears:function(H){this.callChildFunction("subtractYears",H);},getCalendarPage:function(K){var M=null;if(K){var N=K.getFullYear(),J=K.getMonth();var I=this.pages;for(var L=0;L<I.length;++L){var H=I[L].cfg.getProperty("pagedate");if(H.getFullYear()===N&&H.getMonth()===J){M=I[L];break;}}}return M;},_setMonthOnDate:function(I,J){if(YAHOO.env.ua.webkit&&YAHOO.env.ua.webkit<420&&(J<0||J>11)){var H=F.add(I,F.MONTH,J-I.getMonth());I.setTime(H.getTime());}else{I.setMonth(J);}},_fixWidth:function(){var H=0;for(var J=0;J<this.pages.length;++J){var I=this.pages[J];H+=I.oDomContainer.offsetWidth;}if(H>0){this.oDomContainer.style.width=H+"px";}},toString:function(){return"CalendarGroup "+this.id;},destroy:function(){if(this.beforeDestroyEvent.fire()){var J=this;if(J.navigator){J.navigator.destroy();}if(J.cfg){J.cfg.destroy();}A.purgeElement(J.oDomContainer,true);D.removeClass(J.oDomContainer,B.CSS_CONTAINER);D.removeClass(J.oDomContainer,B.CSS_MULTI_UP);for(var I=0,H=J.pages.length;I<H;I++){J.pages[I].destroy();J.pages[I]=null;}J.oDomContainer.innerHTML="";J.oDomContainer=null;this.destroyEvent.fire();}}};B.CSS_CONTAINER="yui-calcontainer";B.CSS_MULTI_UP="multi";B.CSS_2UPTITLE="title";B.CSS_2UPCLOSE="close-icon";YAHOO.lang.augmentProto(B,G,"buildDayLabel","buildMonthLabel","renderOutOfBoundsDate","renderRowHeader","renderRowFooter","renderCellDefault","styleCellDefault","renderCellStyleHighlight1","renderCellStyleHighlight2","renderCellStyleHighlight3","renderCellStyleHighlight4","renderCellStyleToday","renderCellStyleSelected","renderCellNotThisMonth","renderBodyCellRestricted","initStyles","configTitle","configClose","configIframe","configStrings","configToday","configNavigator","createTitleBar","createCloseButton","removeTitleBar","removeCloseButton","hide","show","toDate","_toDate","_parseArgs","browser");YAHOO.widget.CalGrp=B;YAHOO.widget.CalendarGroup=B;YAHOO.widget.Calendar2up=function(J,H,I){this.init(J,H,I);};YAHOO.extend(YAHOO.widget.Calendar2up,B);YAHOO.widget.Cal2up=YAHOO.widget.Calendar2up;})();YAHOO.widget.CalendarNavigator=function(A){this.init(A);};(function(){var A=YAHOO.widget.CalendarNavigator;A.CLASSES={NAV:"yui-cal-nav",NAV_VISIBLE:"yui-cal-nav-visible",MASK:"yui-cal-nav-mask",YEAR:"yui-cal-nav-y",MONTH:"yui-cal-nav-m",BUTTONS:"yui-cal-nav-b",BUTTON:"yui-cal-nav-btn",ERROR:"yui-cal-nav-e",YEAR_CTRL:"yui-cal-nav-yc",MONTH_CTRL:"yui-cal-nav-mc",INVALID:"yui-invalid",DEFAULT:"yui-default"};A.DEFAULT_CONFIG={strings:{month:"Month",year:"Year",submit:"Okay",cancel:"Cancel",invalidYear:"Year needs to be a number"},monthFormat:YAHOO.widget.Calendar.LONG,initialFocus:"year"};A._DEFAULT_CFG=A.DEFAULT_CONFIG;A.ID_SUFFIX="_nav";A.MONTH_SUFFIX="_month";A.YEAR_SUFFIX="_year";A.ERROR_SUFFIX="_error";A.CANCEL_SUFFIX="_cancel";A.SUBMIT_SUFFIX="_submit";A.YR_MAX_DIGITS=4;A.YR_MINOR_INC=1;A.YR_MAJOR_INC=10;A.UPDATE_DELAY=50;A.YR_PATTERN=/^\d+$/;A.TRIM=/^\s*(.*?)\s*$/;})();YAHOO.widget.CalendarNavigator.prototype={id:null,cal:null,navEl:null,maskEl:null,yearEl:null,monthEl:null,errorEl:null,submitEl:null,cancelEl:null,firstCtrl:null,lastCtrl:null,_doc:null,_year:null,_month:0,__rendered:false,init:function(A){var C=A.oDomContainer;this.cal=A;this.id=C.id+YAHOO.widget.CalendarNavigator.ID_SUFFIX;this._doc=C.ownerDocument;var B=YAHOO.env.ua.ie;this.__isIEQuirks=(B&&((B<=6)||(this._doc.compatMode=="BackCompat")));},show:function(){var A=YAHOO.widget.CalendarNavigator.CLASSES;if(this.cal.beforeShowNavEvent.fire()){if(!this.__rendered){this.render();}this.clearErrors();this._updateMonthUI();this._updateYearUI();this._show(this.navEl,true);this.setInitialFocus();this.showMask();YAHOO.util.Dom.addClass(this.cal.oDomContainer,A.NAV_VISIBLE);this.cal.showNavEvent.fire();}},hide:function(){var A=YAHOO.widget.CalendarNavigator.CLASSES;if(this.cal.beforeHideNavEvent.fire()){this._show(this.navEl,false);this.hideMask();YAHOO.util.Dom.removeClass(this.cal.oDomContainer,A.NAV_VISIBLE);this.cal.hideNavEvent.fire();}},showMask:function(){this._show(this.maskEl,true);if(this.__isIEQuirks){this._syncMask();}},hideMask:function(){this._show(this.maskEl,false);},getMonth:function(){return this._month;},getYear:function(){return this._year;},setMonth:function(A){if(A>=0&&A<12){this._month=A;}this._updateMonthUI();},setYear:function(B){var A=YAHOO.widget.CalendarNavigator.YR_PATTERN;if(YAHOO.lang.isNumber(B)&&A.test(B+"")){this._year=B;}this._updateYearUI();},render:function(){this.cal.beforeRenderNavEvent.fire();if(!this.__rendered){this.createNav();this.createMask();this.applyListeners();this.__rendered=true;}this.cal.renderNavEvent.fire();},createNav:function(){var B=YAHOO.widget.CalendarNavigator;var C=this._doc;var D=C.createElement("div");D.className=B.CLASSES.NAV;var A=this.renderNavContents([]);D.innerHTML=A.join("");this.cal.oDomContainer.appendChild(D);
+this.navEl=D;this.yearEl=C.getElementById(this.id+B.YEAR_SUFFIX);this.monthEl=C.getElementById(this.id+B.MONTH_SUFFIX);this.errorEl=C.getElementById(this.id+B.ERROR_SUFFIX);this.submitEl=C.getElementById(this.id+B.SUBMIT_SUFFIX);this.cancelEl=C.getElementById(this.id+B.CANCEL_SUFFIX);if(YAHOO.env.ua.gecko&&this.yearEl&&this.yearEl.type=="text"){this.yearEl.setAttribute("autocomplete","off");}this._setFirstLastElements();},createMask:function(){var B=YAHOO.widget.CalendarNavigator.CLASSES;var A=this._doc.createElement("div");A.className=B.MASK;this.cal.oDomContainer.appendChild(A);this.maskEl=A;},_syncMask:function(){var B=this.cal.oDomContainer;if(B&&this.maskEl){var A=YAHOO.util.Dom.getRegion(B);YAHOO.util.Dom.setStyle(this.maskEl,"width",A.right-A.left+"px");YAHOO.util.Dom.setStyle(this.maskEl,"height",A.bottom-A.top+"px");}},renderNavContents:function(A){var D=YAHOO.widget.CalendarNavigator,E=D.CLASSES,B=A;B[B.length]='<div class="'+E.MONTH+'">';this.renderMonth(B);B[B.length]="</div>";B[B.length]='<div class="'+E.YEAR+'">';this.renderYear(B);B[B.length]="</div>";B[B.length]='<div class="'+E.BUTTONS+'">';this.renderButtons(B);B[B.length]="</div>";B[B.length]='<div class="'+E.ERROR+'" id="'+this.id+D.ERROR_SUFFIX+'"></div>';return B;},renderMonth:function(D){var G=YAHOO.widget.CalendarNavigator,H=G.CLASSES;var I=this.id+G.MONTH_SUFFIX,F=this.__getCfg("monthFormat"),A=this.cal.cfg.getProperty((F==YAHOO.widget.Calendar.SHORT)?"MONTHS_SHORT":"MONTHS_LONG"),E=D;if(A&&A.length>0){E[E.length]='<label for="'+I+'">';E[E.length]=this.__getCfg("month",true);E[E.length]="</label>";E[E.length]='<select name="'+I+'" id="'+I+'" class="'+H.MONTH_CTRL+'">';for(var B=0;B<A.length;B++){E[E.length]='<option value="'+B+'">';E[E.length]=A[B];E[E.length]="</option>";}E[E.length]="</select>";}return E;},renderYear:function(B){var E=YAHOO.widget.CalendarNavigator,F=E.CLASSES;var G=this.id+E.YEAR_SUFFIX,A=E.YR_MAX_DIGITS,D=B;D[D.length]='<label for="'+G+'">';D[D.length]=this.__getCfg("year",true);D[D.length]="</label>";D[D.length]='<input type="text" name="'+G+'" id="'+G+'" class="'+F.YEAR_CTRL+'" maxlength="'+A+'"/>';return D;},renderButtons:function(A){var D=YAHOO.widget.CalendarNavigator.CLASSES;var B=A;B[B.length]='<span class="'+D.BUTTON+" "+D.DEFAULT+'">';B[B.length]='<button type="button" id="'+this.id+"_submit"+'">';B[B.length]=this.__getCfg("submit",true);B[B.length]="</button>";B[B.length]="</span>";B[B.length]='<span class="'+D.BUTTON+'">';B[B.length]='<button type="button" id="'+this.id+"_cancel"+'">';B[B.length]=this.__getCfg("cancel",true);B[B.length]="</button>";B[B.length]="</span>";return B;},applyListeners:function(){var B=YAHOO.util.Event;function A(){if(this.validate()){this.setYear(this._getYearFromUI());}}function C(){this.setMonth(this._getMonthFromUI());}B.on(this.submitEl,"click",this.submit,this,true);B.on(this.cancelEl,"click",this.cancel,this,true);B.on(this.yearEl,"blur",A,this,true);B.on(this.monthEl,"change",C,this,true);if(this.__isIEQuirks){YAHOO.util.Event.on(this.cal.oDomContainer,"resize",this._syncMask,this,true);}this.applyKeyListeners();},purgeListeners:function(){var A=YAHOO.util.Event;A.removeListener(this.submitEl,"click",this.submit);A.removeListener(this.cancelEl,"click",this.cancel);A.removeListener(this.yearEl,"blur");A.removeListener(this.monthEl,"change");if(this.__isIEQuirks){A.removeListener(this.cal.oDomContainer,"resize",this._syncMask);}this.purgeKeyListeners();},applyKeyListeners:function(){var D=YAHOO.util.Event,A=YAHOO.env.ua;var C=(A.ie||A.webkit)?"keydown":"keypress";var B=(A.ie||A.opera||A.webkit)?"keydown":"keypress";D.on(this.yearEl,"keypress",this._handleEnterKey,this,true);D.on(this.yearEl,C,this._handleDirectionKeys,this,true);D.on(this.lastCtrl,B,this._handleTabKey,this,true);D.on(this.firstCtrl,B,this._handleShiftTabKey,this,true);},purgeKeyListeners:function(){var D=YAHOO.util.Event,A=YAHOO.env.ua;var C=(A.ie||A.webkit)?"keydown":"keypress";var B=(A.ie||A.opera||A.webkit)?"keydown":"keypress";D.removeListener(this.yearEl,"keypress",this._handleEnterKey);D.removeListener(this.yearEl,C,this._handleDirectionKeys);D.removeListener(this.lastCtrl,B,this._handleTabKey);D.removeListener(this.firstCtrl,B,this._handleShiftTabKey);},submit:function(){if(this.validate()){this.hide();this.setMonth(this._getMonthFromUI());this.setYear(this._getYearFromUI());var B=this.cal;var A=YAHOO.widget.CalendarNavigator.UPDATE_DELAY;if(A>0){var C=this;window.setTimeout(function(){C._update(B);},A);}else{this._update(B);}}},_update:function(B){var A=YAHOO.widget.DateMath.getDate(this.getYear()-B.cfg.getProperty("YEAR_OFFSET"),this.getMonth(),1);B.cfg.setProperty("pagedate",A);B.render();},cancel:function(){this.hide();},validate:function(){if(this._getYearFromUI()!==null){this.clearErrors();return true;}else{this.setYearError();this.setError(this.__getCfg("invalidYear",true));return false;}},setError:function(A){if(this.errorEl){this.errorEl.innerHTML=A;this._show(this.errorEl,true);}},clearError:function(){if(this.errorEl){this.errorEl.innerHTML="";this._show(this.errorEl,false);}},setYearError:function(){YAHOO.util.Dom.addClass(this.yearEl,YAHOO.widget.CalendarNavigator.CLASSES.INVALID);},clearYearError:function(){YAHOO.util.Dom.removeClass(this.yearEl,YAHOO.widget.CalendarNavigator.CLASSES.INVALID);},clearErrors:function(){this.clearError();this.clearYearError();},setInitialFocus:function(){var A=this.submitEl,C=this.__getCfg("initialFocus");if(C&&C.toLowerCase){C=C.toLowerCase();if(C=="year"){A=this.yearEl;try{this.yearEl.select();}catch(B){}}else{if(C=="month"){A=this.monthEl;}}}if(A&&YAHOO.lang.isFunction(A.focus)){try{A.focus();}catch(D){}}},erase:function(){if(this.__rendered){this.purgeListeners();this.yearEl=null;this.monthEl=null;this.errorEl=null;this.submitEl=null;this.cancelEl=null;this.firstCtrl=null;this.lastCtrl=null;if(this.navEl){this.navEl.innerHTML="";}var B=this.navEl.parentNode;if(B){B.removeChild(this.navEl);}this.navEl=null;var A=this.maskEl.parentNode;
+if(A){A.removeChild(this.maskEl);}this.maskEl=null;this.__rendered=false;}},destroy:function(){this.erase();this._doc=null;this.cal=null;this.id=null;},_show:function(B,A){if(B){YAHOO.util.Dom.setStyle(B,"display",(A)?"block":"none");}},_getMonthFromUI:function(){if(this.monthEl){return this.monthEl.selectedIndex;}else{return 0;}},_getYearFromUI:function(){var B=YAHOO.widget.CalendarNavigator;var A=null;if(this.yearEl){var C=this.yearEl.value;C=C.replace(B.TRIM,"$1");if(B.YR_PATTERN.test(C)){A=parseInt(C,10);}}return A;},_updateYearUI:function(){if(this.yearEl&&this._year!==null){this.yearEl.value=this._year;}},_updateMonthUI:function(){if(this.monthEl){this.monthEl.selectedIndex=this._month;}},_setFirstLastElements:function(){this.firstCtrl=this.monthEl;this.lastCtrl=this.cancelEl;if(this.__isMac){if(YAHOO.env.ua.webkit&&YAHOO.env.ua.webkit<420){this.firstCtrl=this.monthEl;this.lastCtrl=this.yearEl;}if(YAHOO.env.ua.gecko){this.firstCtrl=this.yearEl;this.lastCtrl=this.yearEl;}}},_handleEnterKey:function(B){var A=YAHOO.util.KeyListener.KEY;if(YAHOO.util.Event.getCharCode(B)==A.ENTER){YAHOO.util.Event.preventDefault(B);this.submit();}},_handleDirectionKeys:function(H){var G=YAHOO.util.Event,A=YAHOO.util.KeyListener.KEY,D=YAHOO.widget.CalendarNavigator;var F=(this.yearEl.value)?parseInt(this.yearEl.value,10):null;if(isFinite(F)){var B=false;switch(G.getCharCode(H)){case A.UP:this.yearEl.value=F+D.YR_MINOR_INC;B=true;break;case A.DOWN:this.yearEl.value=Math.max(F-D.YR_MINOR_INC,0);B=true;break;case A.PAGE_UP:this.yearEl.value=F+D.YR_MAJOR_INC;B=true;break;case A.PAGE_DOWN:this.yearEl.value=Math.max(F-D.YR_MAJOR_INC,0);B=true;break;default:break;}if(B){G.preventDefault(H);try{this.yearEl.select();}catch(C){}}}},_handleTabKey:function(D){var C=YAHOO.util.Event,A=YAHOO.util.KeyListener.KEY;if(C.getCharCode(D)==A.TAB&&!D.shiftKey){try{C.preventDefault(D);this.firstCtrl.focus();}catch(B){}}},_handleShiftTabKey:function(D){var C=YAHOO.util.Event,A=YAHOO.util.KeyListener.KEY;if(D.shiftKey&&C.getCharCode(D)==A.TAB){try{C.preventDefault(D);this.lastCtrl.focus();}catch(B){}}},__getCfg:function(D,B){var C=YAHOO.widget.CalendarNavigator.DEFAULT_CONFIG;var A=this.cal.cfg.getProperty("navigator");if(B){return(A!==true&&A.strings&&A.strings[D])?A.strings[D]:C.strings[D];}else{return(A!==true&&A[D])?A[D]:C[D];}},__isMac:(navigator.userAgent.toLowerCase().indexOf("macintosh")!=-1)};YAHOO.register("calendar",YAHOO.widget.Calendar,{version:"2.8.2r1",build:"7"}); \ No newline at end of file
diff --git a/js/yui/carousel/carousel-min.js b/js/yui/carousel/carousel-min.js
new file mode 100644
index 000000000..88235e2b7
--- /dev/null
+++ b/js/yui/carousel/carousel-min.js
@@ -0,0 +1,12 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+(function(){var P;YAHOO.widget.Carousel=function(s,r){YAHOO.widget.Carousel.superclass.constructor.call(this,s,r);};var U=YAHOO.widget.Carousel,e=YAHOO.util.Dom,c=YAHOO.util.Event,p=YAHOO.lang;P="Carousel";var T={},F="afterScroll",g="allItemsRemoved",b="beforeHide",J="beforePageChange",i="beforeScroll",Y="beforeShow",B="blur",X="focus",a="hide",S="itemAdded",o="itemRemoved",Q="itemReplaced",C="itemSelected",L="loadItems",I="navigationStateChange",h="pageChange",H="render",V="show",Z="startAutoPlay",q="stopAutoPlay",K="uiUpdate";function G(r,s){var t;for(t in s){if(s.hasOwnProperty(t)){e.setStyle(r,t,s[t]);}}}function W(s,r){var t=document.createElement(s);r=r||{};if(r.className){e.addClass(t,r.className);}if(r.styles){G(t,r.styles);}if(r.parent){r.parent.appendChild(t);}if(r.id){t.setAttribute("id",r.id);}if(r.content){if(r.content.nodeName){t.appendChild(r.content);}else{t.innerHTML=r.content;}}return t;}function d(t,s,r){var v;if(!t){return 0;}function u(y,x){var z;if(x=="marginRight"&&YAHOO.env.ua.webkit){z=parseInt(e.getStyle(y,"marginLeft"),10);}else{z=parseInt(e.getStyle(y,x),10);}return p.isNumber(z)?z:0;}function w(y,x){var z;if(x=="marginRight"&&YAHOO.env.ua.webkit){z=parseFloat(e.getStyle(y,"marginLeft"));}else{z=parseFloat(e.getStyle(y,x));}return p.isNumber(z)?z:0;}if(typeof r=="undefined"){r="int";}switch(s){case"height":v=t.offsetHeight;if(v>0){v+=u(t,"marginTop")+u(t,"marginBottom");}else{v=w(t,"height")+u(t,"marginTop")+u(t,"marginBottom")+u(t,"borderTopWidth")+u(t,"borderBottomWidth")+u(t,"paddingTop")+u(t,"paddingBottom");}break;case"width":v=t.offsetWidth;if(v>0){v+=u(t,"marginLeft")+u(t,"marginRight");}else{v=w(t,"width")+u(t,"marginLeft")+u(t,"marginRight")+u(t,"borderLeftWidth")+u(t,"borderRightWidth")+u(t,"paddingLeft")+u(t,"paddingRight");}break;default:if(r=="int"){v=u(t,s);}else{if(r=="float"){v=w(t,s);}else{v=e.getStyle(t,s);}}break;}return v;}function O(w){var u=this,x,t,s=0,v=u.get("firstVisible"),r=false;if(u._itemsTable.numItems===0){return 0;}t=u._itemsTable.items[v]||u._itemsTable.loading[v];if(p.isUndefined(t)){return 0;}x=e.get(t.id);if(typeof w=="undefined"){r=u.get("isVertical");}else{r=w=="height";}if(this._itemAttrCache[w]){return this._itemAttrCache[w];}if(r){s=d(x,"height");}else{s=d(x,"width");}this._itemAttrCache[w]=s;return s;}function N(){var s=this,t,r;t=s.get("isVertical");r=O.call(s,t?"height":"width");return(r*s.get("revealAmount")/100);}function m(w){var AH=this,z=AH._cols,v=AH._rows,u,AC,AB,t,x,AD,AJ=0,AE,s,AG,AA={},y=0,AI=AH._itemsTable,AF=AI.items,r=AI.loading;AB=AH.get("isVertical");AC=O.call(AH,AB?"height":"width");AG=N.call(AH);while(y<w){if(!AF[y]&&!r[y]){AJ++;}y++;}w-=AJ;if(v){u=this.getPageForItem(w);if(AB){x=Math.floor(w/z);AJ=x;AE=AJ*AC;AA.top=(AE+AG)+"px";AC=O.call(AH,"width");t=w%z;AJ=t;s=AJ*AC;AA.left=s+"px";}else{t=w%z;AD=(u-1)*z;AJ=t+AD;s=AJ*AC;AA.left=(s+AG)+"px";AC=O.call(AH,"height");x=Math.floor(w/z);AD=(u-1)*v;AJ=x-AD;AE=AJ*AC;AA.top=AE+"px";}}else{if(AB){AA.left=0;AA.top=((w*AC)+AG)+"px";}else{AA.top=0;AA.left=((w*AC)+AG)+"px";}}return AA;}function D(s){var r=this.get("numVisible");return Math.floor(s/r)*r;}function j(t){var s=0,r=0;s=O.call(this);r=s*t;return r;}function f(r,s){s.scrollPageBackward();c.preventDefault(r);}function k(r,s){s.scrollPageForward();c.preventDefault(r);}function n(w,s){var AA=this,AB=AA.CLASSES,r,y=AA._firstItem,t=AA.get("isCircular"),x=AA.get("numItems"),z=AA.get("numVisible"),v=s,u=y+z-1;if(v>=0&&v<x){if(!p.isUndefined(AA._itemsTable.items[v])){r=e.get(AA._itemsTable.items[v].id);if(r){e.removeClass(r,AB.SELECTED_ITEM);}}}if(p.isNumber(w)){w=parseInt(w,10);w=p.isNumber(w)?w:0;}else{w=y;}if(p.isUndefined(AA._itemsTable.items[w])){w=D.call(AA,w);AA.scrollTo(w);}if(!p.isUndefined(AA._itemsTable.items[w])){r=e.get(AA._itemsTable.items[w].id);if(r){e.addClass(r,AB.SELECTED_ITEM);}}if(w<y||w>u){w=D.call(AA,w);AA.scrollTo(w);}}function l(){var t=false,w=this,s=w.CLASSES,v,r,u;if(!w._hasRendered){return;}r=w.get("navigation");u=w._firstItem+w.get("numVisible");if(r.prev){if(w.get("numItems")===0||w._firstItem===0){if(w.get("numItems")===0||!w.get("isCircular")){c.removeListener(r.prev,"click",f);e.addClass(r.prev,s.FIRST_NAV_DISABLED);for(v=0;v<w._navBtns.prev.length;v++){w._navBtns.prev[v].setAttribute("disabled","true");}w._prevEnabled=false;}else{t=!w._prevEnabled;}}else{t=!w._prevEnabled;}if(t){c.on(r.prev,"click",f,w);e.removeClass(r.prev,s.FIRST_NAV_DISABLED);for(v=0;v<w._navBtns.prev.length;v++){w._navBtns.prev[v].removeAttribute("disabled");}w._prevEnabled=true;}}t=false;if(r.next){if(u>=w.get("numItems")){if(!w.get("isCircular")){c.removeListener(r.next,"click",k);e.addClass(r.next,s.DISABLED);for(v=0;v<w._navBtns.next.length;v++){w._navBtns.next[v].setAttribute("disabled","true");}w._nextEnabled=false;}else{t=!w._nextEnabled;}}else{t=!w._nextEnabled;}if(t){c.on(r.next,"click",k,w);e.removeClass(r.next,s.DISABLED);for(v=0;v<w._navBtns.next.length;v++){w._navBtns.next[v].removeAttribute("disabled");}w._nextEnabled=true;}}w.fireEvent(I,{next:w._nextEnabled,prev:w._prevEnabled});}function R(t){var u=this,r,s;if(!u._hasRendered){return;}s=u.get("numVisible");if(!p.isNumber(t)){t=Math.floor(u.get("selectedItem")/s);}r=Math.ceil(u.get("numItems")/s);u._pages.num=r;u._pages.cur=t;if(r>u.CONFIG.MAX_PAGER_BUTTONS){u._updatePagerMenu();}else{u._updatePagerButtons();}}function M(r,s){switch(s){case"height":return d(r,"marginTop")+d(r,"marginBottom")+d(r,"paddingTop")+d(r,"paddingBottom")+d(r,"borderTopWidth")+d(r,"borderBottomWidth");case"width":return d(r,"marginLeft")+d(r,"marginRight")+d(r,"paddingLeft")+d(r,"paddingRight")+d(r,"borderLeftWidth")+d(r,"borderRightWidth");default:break;}return d(r,s);}function A(s){var r=this;if(!p.isObject(s)){return;}switch(s.ev){case S:r._syncUiForItemAdd(s);break;case o:r._syncUiForItemRemove(s);break;case Q:r._syncUiForItemReplace(s);break;case L:r._syncUiForLazyLoading(s);break;}r.fireEvent(K);}function E(u,s){var w=this,v=w.get("currentPage"),t,r=w.get("numVisible");
+t=parseInt(w._firstItem/r,10);if(t!=v){w.setAttributeConfig("currentPage",{value:t});w.fireEvent(h,t);}if(w.get("selectOnScroll")){if(w.get("selectedItem")!=w._selectedItem){w.set("selectedItem",w._selectedItem);}}clearTimeout(w._autoPlayTimer);delete w._autoPlayTimer;if(w.isAutoPlayOn()){w.startAutoPlay();}w.fireEvent(F,{first:w._firstItem,last:s},w);}U.getById=function(r){return T[r]?T[r].object:false;};YAHOO.extend(U,YAHOO.util.Element,{_rows:null,_cols:null,_animObj:null,_carouselEl:null,_clipEl:null,_firstItem:0,_hasFocus:false,_hasRendered:false,_isAnimationInProgress:false,_isAutoPlayInProgress:false,_itemsTable:null,_navBtns:null,_navEl:null,_nextEnabled:true,_pages:null,_pagination:{},_prevEnabled:true,_recomputeSize:true,_itemAttrCache:{},CLASSES:{BUTTON:"yui-carousel-button",CAROUSEL:"yui-carousel",CAROUSEL_EL:"yui-carousel-element",CONTAINER:"yui-carousel-container",CONTENT:"yui-carousel-content",DISABLED:"yui-carousel-button-disabled",FIRST_NAV:" yui-carousel-first-button",FIRST_NAV_DISABLED:"yui-carousel-first-button-disabled",FIRST_PAGE:"yui-carousel-nav-first-page",FOCUSSED_BUTTON:"yui-carousel-button-focus",HORIZONTAL:"yui-carousel-horizontal",ITEM_LOADING:"yui-carousel-item-loading",MIN_WIDTH:"yui-carousel-min-width",NAVIGATION:"yui-carousel-nav",NEXT_NAV:" yui-carousel-next-button",NEXT_PAGE:"yui-carousel-next",NAV_CONTAINER:"yui-carousel-buttons",PAGER_ITEM:"yui-carousel-pager-item",PAGINATION:"yui-carousel-pagination",PAGE_FOCUS:"yui-carousel-nav-page-focus",PREV_PAGE:"yui-carousel-prev",SELECTED_ITEM:"yui-carousel-item-selected",SELECTED_NAV:"yui-carousel-nav-page-selected",VERTICAL:"yui-carousel-vertical",MULTI_ROW:"yui-carousel-multi-row",ROW:"yui-carousel-row",VERTICAL_CONTAINER:"yui-carousel-vertical-container",VISIBLE:"yui-carousel-visible"},CONFIG:{FIRST_VISIBLE:0,HORZ_MIN_WIDTH:180,MAX_PAGER_BUTTONS:5,VERT_MIN_WIDTH:115,NUM_VISIBLE:3},STRINGS:{ITEM_LOADING_CONTENT:"Loading",NEXT_BUTTON_TEXT:"Next Page",PAGER_PREFIX_TEXT:"Go to page ",PREVIOUS_BUTTON_TEXT:"Previous Page"},addItem:function(y,s){var x=this,u,t,r,z=0,w,v=x.get("numItems");if(!y){return false;}if(p.isString(y)||y.nodeName){t=y.nodeName?y.innerHTML:y;}else{if(p.isObject(y)){t=y.content;}else{return false;}}u=y.className||"";r=y.id?y.id:e.generateId();if(p.isUndefined(s)){x._itemsTable.items.push({item:t,className:u,id:r});w=x._itemsTable.items.length-1;}else{if(s<0||s>v){return false;}if(!x._itemsTable.items[s]){x._itemsTable.items[s]=undefined;z=1;}x._itemsTable.items.splice(s,z,{item:t,className:u,id:r});}x._itemsTable.numItems++;if(v<x._itemsTable.items.length){x.set("numItems",x._itemsTable.items.length);}x.fireEvent(S,{pos:s,ev:S,newPos:w});return true;},addItems:function(r){var s,u,t=true;if(!p.isArray(r)){return false;}for(s=0,u=r.length;s<u;s++){if(this.addItem(r[s][0],r[s][1])===false){t=false;}}return t;},blur:function(){this._carouselEl.blur();this.fireEvent(B);},clearItems:function(){var r=this,s=r.get("numItems");while(s>0){if(!r.removeItem(0)){}if(r._itemsTable.numItems===0){r.set("numItems",0);break;}s--;}r.fireEvent(g);},focus:function(){var AA=this,v,w,x,u,z,AB,s,t,r;if(!AA._hasRendered){return;}if(AA.isAnimating()){return;}r=AA.get("selectedItem");AB=AA.get("numVisible");s=AA.get("selectOnScroll");t=(r>=0)?AA.getItem(r):null;v=AA.get("firstVisible");z=v+AB-1;x=(r<v||r>z);w=(t&&t.id)?e.get(t.id):null;u=AA._itemsTable;if(!s&&x){w=(u&&u.items&&u.items[v])?e.get(u.items[v].id):null;}if(w){try{w.focus();}catch(y){}}AA.fireEvent(X);},hide:function(){var r=this;if(r.fireEvent(b)!==false){r.removeClass(r.CLASSES.VISIBLE);r.fireEvent(a);}},init:function(u,s){var v=this,r=u,w=false,t;if(!u){return;}v._hasRendered=false;v._navBtns={prev:[],next:[]};v._pages={el:null,num:0,cur:0};v._pagination={};v._itemAttrCache={};v._itemsTable={loading:{},numItems:0,items:[],size:0};if(p.isString(u)){u=e.get(u);}else{if(!u.nodeName){return;}}U.superclass.init.call(v,u,s);t=v.get("selectedItem");if(t>0){v.set("firstVisible",D.call(v,t));}if(u){if(!u.id){u.setAttribute("id",e.generateId());}w=v._parseCarousel(u);if(!w){v._createCarousel(r);}}else{u=v._createCarousel(r);}r=u.id;v.initEvents();if(w){v._parseCarouselItems();}if(t>0){n.call(v,t,0);}if(!s||typeof s.isVertical=="undefined"){v.set("isVertical",false);}v._parseCarouselNavigation(u);v._navEl=v._setupCarouselNavigation();T[r]={object:v};v._loadItems(Math.min(v.get("firstVisible")+v.get("numVisible"),v.get("numItems"))-1);},initAttributes:function(r){var s=this;r=r||{};U.superclass.initAttributes.call(s,r);s.setAttributeConfig("carouselEl",{validator:p.isString,value:r.carouselEl||"OL"});s.setAttributeConfig("carouselItemEl",{validator:p.isString,value:r.carouselItemEl||"LI"});s.setAttributeConfig("currentPage",{readOnly:true,value:0});s.setAttributeConfig("firstVisible",{method:s._setFirstVisible,validator:s._validateFirstVisible,value:r.firstVisible||s.CONFIG.FIRST_VISIBLE});s.setAttributeConfig("selectOnScroll",{validator:p.isBoolean,value:r.selectOnScroll||true});s.setAttributeConfig("numVisible",{setter:s._numVisibleSetter,method:s._setNumVisible,validator:s._validateNumVisible,value:r.numVisible||s.CONFIG.NUM_VISIBLE});s.setAttributeConfig("numItems",{method:s._setNumItems,validator:s._validateNumItems,value:s._itemsTable.numItems});s.setAttributeConfig("scrollIncrement",{validator:s._validateScrollIncrement,value:r.scrollIncrement||1});s.setAttributeConfig("selectedItem",{setter:s._selectedItemSetter,method:s._setSelectedItem,validator:p.isNumber,value:-1});s.setAttributeConfig("revealAmount",{method:s._setRevealAmount,validator:s._validateRevealAmount,value:r.revealAmount||0});s.setAttributeConfig("isCircular",{validator:p.isBoolean,value:r.isCircular||false});s.setAttributeConfig("isVertical",{method:s._setOrientation,validator:p.isBoolean,value:r.isVertical||false});s.setAttributeConfig("navigation",{method:s._setNavigation,validator:s._validateNavigation,value:r.navigation||{prev:null,next:null,page:null}});s.setAttributeConfig("animation",{validator:s._validateAnimation,value:r.animation||{speed:0,effect:null}});
+s.setAttributeConfig("autoPlay",{validator:p.isNumber,value:r.autoPlay||0});s.setAttributeConfig("autoPlayInterval",{validator:p.isNumber,value:r.autoPlayInterval||0});s.setAttributeConfig("numPages",{readOnly:true,getter:s._getNumPages});s.setAttributeConfig("lastVisible",{readOnly:true,getter:s._getLastVisible});},initEvents:function(){var t=this,s=t.CLASSES,r;t.on("keydown",t._keyboardEventHandler);t.on(F,l);t.on(S,A);t.on(o,A);t.on(Q,A);t.on(C,function(){if(t._hasFocus){t.focus();}});t.on(L,A);t.on(g,function(u){t.scrollTo(0);l.call(t);R.call(t);});t.on(h,R,t);t.on(H,function(u){if(t.get("selectedItem")===null||t.get("selectedItem")<=0){t.set("selectedItem",t.get("firstVisible"));}l.call(t,u);R.call(t,u);t._setClipContainerSize();t.show();});t.on("selectedItemChange",function(u){n.call(t,u.newValue,u.prevValue);if(u.newValue>=0){t._updateTabIndex(t.getElementForItem(u.newValue));}t.fireEvent(C,u.newValue);});t.on(K,function(u){l.call(t,u);R.call(t,u);});t.on("firstVisibleChange",function(u){if(!t.get("selectOnScroll")){if(u.newValue>=0){t._updateTabIndex(t.getElementForItem(u.newValue));}}});t.on("click",function(u){if(t.isAutoPlayOn()){t.stopAutoPlay();}t._itemClickHandler(u);t._pagerClickHandler(u);});c.onFocus(t.get("element"),function(u,w){var v=c.getTarget(u);if(v&&v.nodeName.toUpperCase()=="A"&&e.getAncestorByClassName(v,s.NAVIGATION)){if(r){e.removeClass(r,s.PAGE_FOCUS);}r=v.parentNode;e.addClass(r,s.PAGE_FOCUS);}else{if(r){e.removeClass(r,s.PAGE_FOCUS);}}w._hasFocus=true;w._updateNavButtons(c.getTarget(u),true);},t);c.onBlur(t.get("element"),function(u,v){v._hasFocus=false;v._updateNavButtons(c.getTarget(u),false);},t);},isAnimating:function(){return this._isAnimationInProgress;},isAutoPlayOn:function(){return this._isAutoPlayInProgress;},getElementForItem:function(r){var s=this;if(r<0||r>=s.get("numItems")){return null;}if(s._itemsTable.items[r]){return e.get(s._itemsTable.items[r].id);}return null;},getElementForItems:function(){var t=this,s=[],r;for(r=0;r<t._itemsTable.numItems;r++){s.push(t.getElementForItem(r));}return s;},getItem:function(r){var s=this;if(r<0||r>=s.get("numItems")){return null;}if(s._itemsTable.numItems>r){if(!p.isUndefined(s._itemsTable.items[r])){return s._itemsTable.items[r];}}return null;},getItems:function(){return this._itemsTable.items;},getLoadingItems:function(){return this._itemsTable.loading;},getRows:function(){return this._rows;},getCols:function(){return this._cols;},getItemPositionById:function(w){var u=this,v=u.get("numItems"),s=0,r=u._itemsTable.items,t;while(s<v){t=r[s]||{};if(t.id==w){return s;}s++;}return -1;},getVisibleItems:function(){var u=this,s=u.get("firstVisible"),v=s+u.get("numVisible"),t=[];while(s<v){t.push(u.getElementForItem(s));s++;}return t;},removeItem:function(s){var u=this,t,r=u.get("numItems");if(s<0||s>=r){return false;}t=u._itemsTable.items.splice(s,1);if(t&&t.length==1){u._itemsTable.numItems--;u.set("numItems",r-1);u.fireEvent(o,{item:t[0],pos:s,ev:o});return true;}return false;},replaceItem:function(z,u){var y=this,w,v,t,x=y.get("numItems"),s,r=z;if(!z){return false;}if(p.isString(z)||z.nodeName){v=z.nodeName?z.innerHTML:z;}else{if(p.isObject(z)){v=z.content;}else{return false;}}if(p.isUndefined(u)){return false;}else{if(u<0||u>=x){return false;}s=y._itemsTable.items[u];if(!s){s=y._itemsTable.loading[u];y._itemsTable.items[u]=undefined;}y._itemsTable.items.splice(u,1,{item:v,className:z.className||"",id:e.generateId()});r=y._itemsTable.items[u];}y.fireEvent(Q,{newItem:r,oldItem:s,pos:u,ev:Q});return true;},replaceItems:function(r){var s,u,t=true;if(!p.isArray(r)){return false;}for(s=0,u=r.length;s<u;s++){if(this.replaceItem(r[s][0],r[s][1])===false){t=false;}}return t;},render:function(s){var u=this,r=u.CLASSES,t=u._rows;u.addClass(r.CAROUSEL);if(!u._clipEl){u._clipEl=u._createCarouselClip();u._clipEl.appendChild(u._carouselEl);}if(s){u.appendChild(u._clipEl);u.appendTo(s);}else{if(!e.inDocument(u.get("element"))){return false;}u.appendChild(u._clipEl);}if(t){e.addClass(u._clipEl,r.MULTI_ROW);}if(u.get("isVertical")){u.addClass(r.VERTICAL);}else{u.addClass(r.HORIZONTAL);}if(u.get("numItems")<1){return false;}u._refreshUi();return true;},scrollBackward:function(){var r=this;r.scrollTo(r._firstItem-r.get("scrollIncrement"));},scrollForward:function(){var r=this;r.scrollTo(r._firstItem+r.get("scrollIncrement"));},scrollPageBackward:function(){var t=this,u=t.get("isVertical"),s=t._cols,r=t._firstItem-t.get("numVisible");if(r<0){if(s){r=t._firstItem-s;}}if(t.get("selectOnScroll")){t._selectedItem=t._getSelectedItem(r);}t.scrollTo(r);},scrollPageForward:function(){var s=this,r=s._firstItem+s.get("numVisible");if(r>s.get("numItems")){r=0;}if(s.get("selectOnScroll")){s._selectedItem=s._getSelectedItem(r);}s.scrollTo(r);},scrollTo:function(AL,AI){var AH=this,u,AJ,z,AB,AC,AM,AN,AO,AD,AA,v,AF,s,w,t,x,AE,y,AP,AK=AH._itemsTable,AG=AK.items,r=AK.loading;if(p.isUndefined(AL)||AL==AH._firstItem||AH.isAnimating()){return;}AJ=AH.get("animation");z=AH.get("isCircular");AB=AH.get("isVertical");AA=AH._cols;v=AH._rows;AO=AH._firstItem;AF=AH.get("numItems");s=AH.get("numVisible");t=AH.get("currentPage");AP=function(){if(AH.isAutoPlayOn()){AH.stopAutoPlay();}};if(AL<0){if(z){AL=AF+AL;}else{AP.call(AH);return;}}else{if(AF>0&&AL>AF-1){if(AH.get("isCircular")){AL=AF-AL;}else{AP.call(AH);return;}}}if(isNaN(AL)){return;}AN=(AH._firstItem>AL)?"backward":"forward";AE=AO+s;AE=(AE>AF-1)?AF-1:AE;x=AH.fireEvent(i,{dir:AN,first:AO,last:AE});if(x===false){return;}AH.fireEvent(J,{page:t});AD=AL+s-1;AH._loadItems(AD>AF-1?AF-1:AD);AM=0-AL;if(v){if(AB){AM=parseInt(AM/AA,10);}else{AM=parseInt(AM/v,10);}}y=0;while(AM<0&&y<AL+s-1&&y<AF){if(!AG[y]&&!r[y]){AM++;}y+=v?v:1;}AH._firstItem=AL;AH.set("firstVisible",AL);AE=AL+s;AE=(AE>AF-1)?AF-1:AE;w=j.call(AH,AM);u=AJ.speed>0;if(u){AH._animateAndSetCarouselOffset(w,AL,AE,AI);}else{AH._setCarouselOffset(w);E.call(AH,AL,AE);}},getPageForItem:function(r){return Math.ceil((r+1)/parseInt(this.get("numVisible"),10));},getFirstVisibleOnPage:function(r){return(r-1)*this.get("numVisible");
+},selectPreviousItem:function(){var t=this,s=0,r=t.get("selectedItem");if(r==this._firstItem){s=r-t.get("numVisible");t._selectedItem=t._getSelectedItem(r-1);t.scrollTo(s);}else{s=t.get("selectedItem")-t.get("scrollIncrement");t.set("selectedItem",t._getSelectedItem(s));}},selectNextItem:function(){var s=this,r=0;r=s.get("selectedItem")+s.get("scrollIncrement");s.set("selectedItem",s._getSelectedItem(r));},show:function(){var s=this,r=s.CLASSES;if(s.fireEvent(Y)!==false){s.addClass(r.VISIBLE);s.fireEvent(V);}},startAutoPlay:function(){var r=this,s;if(p.isUndefined(r._autoPlayTimer)){s=r.get("autoPlayInterval");if(s<=0){return;}r._isAutoPlayInProgress=true;r.fireEvent(Z);r._autoPlayTimer=setTimeout(function(){r._autoScroll();},s);}},stopAutoPlay:function(){var r=this;if(!p.isUndefined(r._autoPlayTimer)){clearTimeout(r._autoPlayTimer);delete r._autoPlayTimer;r._isAutoPlayInProgress=false;r.fireEvent(q);}},updatePagination:function(){var z=this,x=z._pagination;if(!x.el){return false;}var w=z.get("numItems"),AA=z.get("numVisible"),u=z.get("firstVisible")+1,v=z.get("currentPage")+1,r=z.get("numPages"),t={"numVisible":AA,"numPages":r,"numItems":w,"selectedItem":z.get("selectedItem")+1,"currentPage":v,"firstVisible":u,"lastVisible":z.get("lastVisible")+1},s=x.callback||{},y=s.scope&&s.obj?s.obj:z;x.el.innerHTML=p.isFunction(s.fn)?s.fn.apply(y,[x.template,t]):YAHOO.lang.substitute(x.template,t);},registerPagination:function(s,u,r){var t=this;t._pagination.template=s;t._pagination.callback=r||{};if(!t._pagination.el){t._pagination.el=W("DIV",{className:t.CLASSES.PAGINATION});if(u=="before"){t._navEl.insertBefore(t._pagination.el,t._navEl.firstChild);}else{t._navEl.appendChild(t._pagination.el);}t.on("itemSelected",t.updatePagination);t.on("pageChange",t.updatePagination);}t.updatePagination();},toString:function(){return P+(this.get?" (#"+this.get("id")+")":"");},_animateAndSetCarouselOffset:function(w,u,s){var v=this,t=v.get("animation"),r=null;if(v.get("isVertical")){r=new YAHOO.util.Motion(v._carouselEl,{top:{to:w}},t.speed,t.effect);}else{r=new YAHOO.util.Motion(v._carouselEl,{left:{to:w}},t.speed,t.effect);}v._isAnimationInProgress=true;r.onComplete.subscribe(v._animationCompleteHandler,{scope:v,item:u,last:s});r.animate();},_animationCompleteHandler:function(r,s,t){t.scope._isAnimationInProgress=false;E.call(t.scope,t.item,t.last);},_autoScroll:function(){var s=this,t=s._firstItem,r;if(t>=s.get("numItems")-1){if(s.get("isCircular")){r=0;}else{s.stopAutoPlay();}}else{r=t+s.get("numVisible");}s._selectedItem=s._getSelectedItem(r);s.scrollTo.call(s,r);},_createCarousel:function(s){var u=this,r=u.CLASSES,t=e.get(s);if(!t){t=W("DIV",{className:r.CAROUSEL,id:s});}if(!u._carouselEl){u._carouselEl=W(u.get("carouselEl"),{className:r.CAROUSEL_EL});}return t;},_createCarouselClip:function(){return W("DIV",{className:this.CLASSES.CONTENT});},_createCarouselItem:function(u){var r,t=this,s=m.call(t,u.pos);return W(t.get("carouselItemEl"),{className:u.className,styles:u.styles,content:u.content,id:u.id});},_getValidIndex:function(t){var w=this,r=w.get("isCircular"),u=w.get("numItems"),v=w.get("numVisible"),s=u-1;if(t<0){t=r?Math.ceil(u/v)*v+t:0;}else{if(t>s){t=r?0:s;}}return t;},_getSelectedItem:function(v){var u=this,r=u.get("isCircular"),t=u.get("numItems"),s=t-1;if(v<0){if(r){v=t+v;}else{v=u.get("selectedItem");}}else{if(v>s){if(r){v=v-t;}else{v=u.get("selectedItem");}}}return v;},_itemClickHandler:function(v){var y=this,w=y.get("carouselItemEl"),s=y.get("element"),t,u,x=c.getTarget(v),r=x.tagName.toUpperCase();if(r==="INPUT"||r==="SELECT"||r==="TEXTAREA"){return;}while(x&&x!=s&&x.id!=y._carouselEl){t=x.nodeName;if(t.toUpperCase()==w){break;}x=x.parentNode;}if((u=y.getItemPositionById(x.id))>=0){y.set("selectedItem",y._getSelectedItem(u));y.focus();}},_keyboardEventHandler:function(t){var v=this,s=c.getCharCode(t),u=c.getTarget(t),r=false;if(v.isAnimating()||u.tagName.toUpperCase()==="SELECT"){return;}switch(s){case 37:case 38:v.selectPreviousItem();r=true;break;case 39:case 40:v.selectNextItem();r=true;break;case 33:v.scrollPageBackward();r=true;break;case 34:v.scrollPageForward();r=true;break;}if(r){if(v.isAutoPlayOn()){v.stopAutoPlay();}c.preventDefault(t);}},_loadItems:function(t){var w=this,s=w.get("numItems"),u=w.get("numVisible"),v=w.get("revealAmount"),x=w._itemsTable.items.length,r=w.get("lastVisible");if(x>t&&t+1>=u){x=t%u||t==r?t-t%u:t-u+1;}if(v&&t<s-1){t++;}if(t>=x&&(!w.getItem(x)||!w.getItem(t))){w.fireEvent(L,{ev:L,first:x,last:t,num:t-x+1});}},_pagerChangeHandler:function(s){var v=this,u=c.getTarget(s),t=u.value,r;if(t){r=v.getFirstVisibleOnPage(t);v._selectedItem=r;v.scrollTo(r);v.focus();}},_pagerClickHandler:function(x){var z=this,t=z.CLASSES,u=c.getTarget(x),s=u.nodeName.toUpperCase(),r,w,v,y;if(e.hasClass(u,t.PAGER_ITEM)||e.hasClass(u.parentNode,t.PAGER_ITEM)){if(s=="EM"){u=u.parentNode;}r=u.href;w=r.lastIndexOf("#");v=parseInt(r.substring(w+1),10);if(v!=-1){y=z.getFirstVisibleOnPage(v);z._selectedItem=y;z.scrollTo(y);z.focus();}c.preventDefault(x);}},_parseCarousel:function(t){var w=this,x,r,s,v,u;r=w.CLASSES;s=w.get("carouselEl");v=false;for(x=t.firstChild;x;x=x.nextSibling){if(x.nodeType==1){u=x.nodeName;if(u.toUpperCase()==s){w._carouselEl=x;e.addClass(w._carouselEl,w.CLASSES.CAROUSEL_EL);v=true;}}}return v;},_parseCarouselItems:function(){var y=this,AA=y.CLASSES,v=0,z,r,t,u,s,w=y.get("firstVisible"),x=y._carouselEl;z=y._rows;t=y.get("carouselItemEl");for(r=x.firstChild;r;r=r.nextSibling){if(r.nodeType==1){s=r.nodeName;if(s.toUpperCase()==t){if(r.id){u=r.id;}else{u=e.generateId();r.setAttribute("id",u);}y.addItem(r,w);w++;}}}},_parseCarouselNavigation:function(x){var y=this,w,z=y.CLASSES,s,v,u,r,t=false;r=e.getElementsByClassName(z.PREV_PAGE,"*",x);if(r.length>0){for(v in r){if(r.hasOwnProperty(v)){s=r[v];if(s.nodeName=="INPUT"||s.nodeName=="BUTTON"||s.nodeName=="A"){y._navBtns.prev.push(s);}else{u=s.getElementsByTagName("INPUT");if(p.isArray(u)&&u.length>0){y._navBtns.prev.push(u[0]);
+}else{u=s.getElementsByTagName("BUTTON");if(p.isArray(u)&&u.length>0){y._navBtns.prev.push(u[0]);}}}}}w={prev:r};}r=e.getElementsByClassName(z.NEXT_PAGE,"*",x);if(r.length>0){for(v in r){if(r.hasOwnProperty(v)){s=r[v];if(s.nodeName=="INPUT"||s.nodeName=="BUTTON"||s.nodeName=="A"){y._navBtns.next.push(s);}else{u=s.getElementsByTagName("INPUT");if(p.isArray(u)&&u.length>0){y._navBtns.next.push(u[0]);}else{u=s.getElementsByTagName("BUTTON");if(p.isArray(u)&&u.length>0){y._navBtns.next.push(u[0]);}}}}}if(w){w.next=r;}else{w={next:r};}}if(w){y.set("navigation",w);t=true;}return t;},_refreshUi:function(){var v=this,s,w=v.get("isVertical"),y=v.get("firstVisible"),t,x,r,u;if(v._itemsTable.numItems<1){return;}u=O.call(v,w?"height":"width");t=v._itemsTable.items[y].id;u=w?d(t,"width"):d(t,"height");e.setStyle(v._carouselEl,w?"width":"height",u+"px");v._hasRendered=true;v.fireEvent(H);},_setCarouselOffset:function(t){var r=this,s;s=r.get("isVertical")?"top":"left";e.setStyle(r._carouselEl,s,t+"px");},_setupCarouselNavigation:function(){var w=this,u,s,r,y,v,x,t;r=w.CLASSES;v=e.getElementsByClassName(r.NAVIGATION,"DIV",w.get("element"));if(v.length===0){v=W("DIV",{className:r.NAVIGATION});w.insertBefore(v,e.getFirstChild(w.get("element")));}else{v=v[0];}w._pages.el=W("UL");v.appendChild(w._pages.el);y=w.get("navigation");if(p.isString(y.prev)||p.isArray(y.prev)){if(p.isString(y.prev)){y.prev=[y.prev];}for(u in y.prev){if(y.prev.hasOwnProperty(u)){w._navBtns.prev.push(e.get(y.prev[u]));}}}else{t=W("SPAN",{className:r.BUTTON+r.FIRST_NAV});e.setStyle(t,"visibility","visible");u=e.generateId();t.innerHTML='<button type="button" '+'id="'+u+'" name="'+w.STRINGS.PREVIOUS_BUTTON_TEXT+'">'+w.STRINGS.PREVIOUS_BUTTON_TEXT+"</button>";v.appendChild(t);u=e.get(u);w._navBtns.prev=[u];s={prev:[t]};}if(p.isString(y.next)||p.isArray(y.next)){if(p.isString(y.next)){y.next=[y.next];}for(u in y.next){if(y.next.hasOwnProperty(u)){w._navBtns.next.push(e.get(y.next[u]));}}}else{x=W("SPAN",{className:r.BUTTON+r.NEXT_NAV});e.setStyle(x,"visibility","visible");u=e.generateId();x.innerHTML='<button type="button" '+'id="'+u+'" name="'+w.STRINGS.NEXT_BUTTON_TEXT+'">'+w.STRINGS.NEXT_BUTTON_TEXT+"</button>";v.appendChild(x);u=e.get(u);w._navBtns.next=[u];if(s){s.next=[x];}else{s={next:[x]};}}if(s){w.set("navigation",s);}return v;},_setClipContainerSize:function(r,t){var z=this,x=z.get("isVertical"),AB=z._rows,v=z._cols,y=z.get("revealAmount"),s=O.call(z,"height"),u=O.call(z,"width"),AA,w;r=r||z._clipEl;if(AB){AA=s*AB;w=u*v;}else{t=t||z.get("numVisible");if(x){AA=s*t;}else{w=u*t;}}z._recomputeSize=(AA===0);if(z._recomputeSize){z._hasRendered=false;return;}y=N.call(z);if(x){AA+=(y*2);}else{w+=(y*2);}if(x){AA+=M(z._carouselEl,"height");e.setStyle(r,"height",AA+"px");if(v){w+=M(z._carouselEl,"width");e.setStyle(r,"width",w+(0)+"px");}}else{w+=M(z._carouselEl,"width");e.setStyle(r,"width",w+"px");if(AB){AA+=M(z._carouselEl,"height");e.setStyle(r,"height",AA+"px");}}z._setContainerSize(r);},_setContainerSize:function(s,t){var w=this,r=w.CONFIG,z=w.CLASSES,v,y,u,x;v=w.get("isVertical");y=w._rows;u=w._cols;s=s||w._clipEl;t=t||(v?"height":"width");x=parseFloat(e.getStyle(s,t),10);x=p.isNumber(x)?x:0;if(v){x+=M(w._carouselEl,"height")+d(w._navEl,"height");}else{x+=M(w._carouselEl,"width");}if(!v){if(x<r.HORZ_MIN_WIDTH){x=r.HORZ_MIN_WIDTH;w.addClass(z.MIN_WIDTH);}}w.setStyle(t,x+"px");if(v){x=O.call(w,"width");if(u){x=x*u;}e.setStyle(w._carouselEl,"width",x+"px");if(x<r.VERT_MIN_WIDTH){x=r.VERT_MIN_WIDTH;w.addClass(z.MIN_WIDTH);}w.setStyle("width",x+"px");}else{if(y){x=O.call(w,"height");x=x*y;e.setStyle(w._carouselEl,"height",x+"px");}}},_setFirstVisible:function(s){var r=this;if(s>=0&&s<r.get("numItems")){r.scrollTo(s);}else{s=r.get("firstVisible");}return s;},_setNavigation:function(r){var s=this;if(r.prev){c.on(r.prev,"click",f,s);}if(r.next){c.on(r.next,"click",k,s);}},_setNumVisible:function(s){var r=this;r._setClipContainerSize(r._clipEl,s);},_numVisibleSetter:function(t){var s=this,r=t;if(p.isArray(t)){s._cols=t[0];s._rows=t[1];r=t[0]*t[1];}return r;},_selectedItemSetter:function(s){var r=this;return(s<r.get("numItems"))?s:0;},_setNumItems:function(t){var s=this,r=s._itemsTable.numItems;if(p.isArray(s._itemsTable.items)){if(s._itemsTable.items.length!=r){r=s._itemsTable.items.length;s._itemsTable.numItems=r;}}if(t<r){while(r>t){s.removeItem(r-1);r--;}}return t;},_setOrientation:function(t){var s=this,r=s.CLASSES;if(t){s.replaceClass(r.HORIZONTAL,r.VERTICAL);}else{s.replaceClass(r.VERTICAL,r.HORIZONTAL);}this._itemAttrCache={};return t;},_setRevealAmount:function(s){var r=this;if(s>=0&&s<=100){s=parseInt(s,10);s=p.isNumber(s)?s:0;r._setClipContainerSize();}else{s=r.get("revealAmount");}return s;},_setSelectedItem:function(r){this._selectedItem=r;},_getNumPages:function(){return Math.ceil(parseInt(this.get("numItems"),10)/parseInt(this.get("numVisible"),10));},_getLastVisible:function(){var r=this;return r.get("currentPage")+1==r.get("numPages")?r.get("numItems")-1:r.get("firstVisible")+r.get("numVisible")-1;},_syncUiForItemAdd:function(u){var v,AA=this,x=AA._carouselEl,r,AB,t=AA._itemsTable,s,w,y,z;w=p.isUndefined(u.pos)?u.newPos||t.numItems-1:u.pos;if(!s){AB=t.items[w]||{};r=AA._createCarouselItem({className:AB.className,styles:AB.styles,content:AB.item,id:AB.id,pos:w});if(p.isUndefined(u.pos)){if(!p.isUndefined(t.loading[w])){s=t.loading[w];}if(s){x.replaceChild(r,s);delete t.loading[w];}else{x.appendChild(r);}}else{if(!p.isUndefined(t.items[u.pos+1])){y=e.get(t.items[u.pos+1].id);}if(y){x.insertBefore(r,y);}else{}}}else{if(p.isUndefined(u.pos)){if(!e.isAncestor(AA._carouselEl,s)){x.appendChild(s);}}else{if(!e.isAncestor(x,s)){if(!p.isUndefined(t.items[u.pos+1])){x.insertBefore(s,e.get(t.items[u.pos+1].id));}}}}if(!AA._hasRendered){AA._refreshUi();}if(AA.get("selectedItem")<0){AA.set("selectedItem",AA.get("firstVisible"));}AA._syncUiItems();},_syncUiForItemReplace:function(x){var w=this,t=w._carouselEl,r=w._itemsTable,y=x.pos,v=x.newItem,s=x.oldItem,u;
+u=w._createCarouselItem({className:v.className,styles:v.styles,content:v.item,id:v.id,pos:y});if(u&&s){c.purgeElement(s,true);t.replaceChild(u,e.get(s.id));if(!p.isUndefined(r.loading[y])){r.numItems++;delete r.loading[y];}}if(!w._hasRendered){w._refreshUi();}w._syncUiItems();},_syncUiForItemRemove:function(w){var v=this,r=v._carouselEl,t,u,s,x;s=v.get("numItems");u=w.item;x=w.pos;if(u&&(t=e.get(u.id))){if(t&&e.isAncestor(r,t)){c.purgeElement(t,true);r.removeChild(t);}if(v.get("selectedItem")==x){x=x>=s?s-1:x;}}else{}v._syncUiItems();},_syncUiForLazyLoading:function(v){var z=this,x=z._carouselEl,t=z._itemsTable,w=t.items.length,y=t.items[v.last+1],r,s;if(!y&&v.last<w){s=v.first;do{y=t.items[s];s++;}while(s<w&&!y);}for(var u=v.first;u<=v.last;u++){if(p.isUndefined(t.loading[u])&&p.isUndefined(t.items[u])){r=z._createCarouselItem({className:z.CLASSES.ITEM_LOADING,content:z.STRINGS.ITEM_LOADING_CONTENT,id:e.generateId(),pos:u});if(r){if(y){y=e.get(y.id);if(y){x.insertBefore(r,y);}else{}}else{x.appendChild(r);}}t.loading[u]=r;}}z._syncUiItems();},_syncUiItems:function(){var u,y=this,w=y.get("numItems"),t,s=y._itemsTable,v=s.items,r=s.loading,z,x;for(t=0;t<w;t++){z=v[t]||r[t];if(z&&z.id){x=m.call(y,t);z.styles=z.styles||{};for(u in x){if(x.hasOwnProperty(u)){z.styles[u]=x[u];}}G(e.get(z.id),x);}}},_updateNavButtons:function(v,s){var t,r=this.CLASSES,w,u=v.parentNode;if(!u){return;}w=u.parentNode;if(v.nodeName.toUpperCase()=="BUTTON"&&e.hasClass(u,r.BUTTON)){if(s){if(w){t=e.getChildren(w);if(t){e.removeClass(t,r.FOCUSSED_BUTTON);}}e.addClass(u,r.FOCUSSED_BUTTON);}else{e.removeClass(u,r.FOCUSSED_BUTTON);}}},_updatePagerButtons:function(){var z=this,x=z.CLASSES,y=z._pages.cur,r,w,u,AA,s=z.get("numVisible"),v=z._pages.num,t=z._pages.el;if(v===0||!t){return;}e.setStyle(t,"visibility","hidden");while(t.firstChild){t.removeChild(t.firstChild);}for(u=0;u<v;u++){r=document.createElement("LI");if(u===0){e.addClass(r,x.FIRST_PAGE);}if(u==y){e.addClass(r,x.SELECTED_NAV);}w="<a class="+x.PAGER_ITEM+' href="#'+(u+1)+'" tabindex="0"><em>'+z.STRINGS.PAGER_PREFIX_TEXT+" "+(u+1)+"</em></a>";r.innerHTML=w;t.appendChild(r);}e.setStyle(t,"visibility","visible");},_updatePagerMenu:function(){var z=this,x=z.CLASSES,y=z._pages.cur,s,v,AA,t=z.get("numVisible"),w=z._pages.num,u=z._pages.el,r;if(w===0){return;}r=document.createElement("SELECT");if(!r){return;}e.setStyle(u,"visibility","hidden");while(u.firstChild){u.removeChild(u.firstChild);}for(v=0;v<w;v++){s=document.createElement("OPTION");s.value=v+1;s.innerHTML=z.STRINGS.PAGER_PREFIX_TEXT+" "+(v+1);if(v==y){s.setAttribute("selected","selected");}r.appendChild(s);}s=document.createElement("FORM");if(!s){}else{s.appendChild(r);u.appendChild(s);}c.addListener(r,"change",z._pagerChangeHandler,this,true);e.setStyle(u,"visibility","visible");},_updateTabIndex:function(r){var s=this;if(r){if(s._focusableItemEl){s._focusableItemEl.tabIndex=-1;}s._focusableItemEl=r;r.tabIndex=0;}},_validateAnimation:function(r){var s=true;if(p.isObject(r)){if(r.speed){s=s&&p.isNumber(r.speed);}if(r.effect){s=s&&p.isFunction(r.effect);}else{if(!p.isUndefined(YAHOO.util.Easing)){r.effect=YAHOO.util.Easing.easeOut;}}}else{s=false;}return s;},_validateFirstVisible:function(t){var s=this,r=s.get("numItems");if(p.isNumber(t)){if(r===0&&t==r){return true;}else{return(t>=0&&t<r);}}return false;},_validateNavigation:function(r){var s;if(!p.isObject(r)){return false;}if(r.prev){if(!p.isArray(r.prev)){return false;}for(s in r.prev){if(r.prev.hasOwnProperty(s)){if(!p.isString(r.prev[s].nodeName)){return false;}}}}if(r.next){if(!p.isArray(r.next)){return false;}for(s in r.next){if(r.next.hasOwnProperty(s)){if(!p.isString(r.next[s].nodeName)){return false;}}}}return true;},_validateNumItems:function(r){return p.isNumber(r)&&(r>=0);},_validateNumVisible:function(r){var s=false;if(p.isNumber(r)){s=r>0&&r<=this.get("numItems");}else{if(p.isArray(r)){if(p.isNumber(r[0])&&p.isNumber(r[1])){s=r[0]*r[1]>0&&r.length==2;}}}return s;},_validateRevealAmount:function(r){var s=false;if(p.isNumber(r)){s=r>=0&&r<100;}return s;},_validateScrollIncrement:function(r){var s=false;if(p.isNumber(r)){s=(r>0&&r<this.get("numItems"));}return s;}});})();YAHOO.register("carousel",YAHOO.widget.Carousel,{version:"2.8.2r1",build:"7"}); \ No newline at end of file
diff --git a/js/yui/charts/charts-min.js b/js/yui/charts/charts-min.js
new file mode 100644
index 000000000..692d13096
--- /dev/null
+++ b/js/yui/charts/charts-min.js
@@ -0,0 +1,9 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+YAHOO.widget.Chart=function(I,F,A,J){this._type=I;this._dataSource=A;var B={align:"",allowNetworking:"",allowScriptAccess:"",base:"",bgcolor:"",menu:"",name:"",quality:"",salign:"",scale:"",tabindex:"",wmode:""};var D={fixedAttributes:{allowScriptAccess:"always"},flashVars:{allowedDomain:document.location.hostname},backgroundColor:"#ffffff",host:this,version:9.045};for(var E in J){if(B.hasOwnProperty(E)){D.fixedAttributes[E]=J[E];}else{D[E]=J[E];}}this._id=D.id=D.id||YAHOO.util.Dom.generateId(null,"yuigen");if(D.version&&D.version!=null&&D.version!=undefined&&D.version!="undefined"){var H=(/\w*.\w*/.exec(((D.version).toString()).replace(/.0./g,"."))).toString();var C=H.split(".");H=C[0]+".";switch((C[1].toString()).length){case 1:H+="00";break;case 2:H+="0";break;}H+=C[1];D.version=parseFloat(H);}this._swfURL=YAHOO.widget.Chart.SWFURL;this._containerID=F;this._attributes=D;this._swfEmbed=new YAHOO.widget.SWF(F,YAHOO.widget.Chart.SWFURL,D);this._swf=this._swfEmbed.swf;this._swfEmbed.subscribe("swfReady",this._eventHandler,this,true);try{this.createEvent("contentReady");}catch(G){}this.createEvent("itemMouseOverEvent");this.createEvent("itemMouseOutEvent");this.createEvent("itemClickEvent");this.createEvent("itemDoubleClickEvent");this.createEvent("itemDragStartEvent");this.createEvent("itemDragEvent");this.createEvent("itemDragEndEvent");};YAHOO.extend(YAHOO.widget.Chart,YAHOO.util.AttributeProvider,{_type:null,_pollingID:null,_pollingInterval:null,_dataTipFunction:null,_legendLabelFunction:null,_seriesFunctions:null,toString:function(){return"Chart "+this._id;},setStyle:function(A,B){B=YAHOO.lang.JSON.stringify(B);this._swf.setStyle(A,B);},setStyles:function(A){A=YAHOO.lang.JSON.stringify(A);this._swf.setStyles(A);},setSeriesStyles:function(B){for(var A=0;A<B.length;A++){B[A]=YAHOO.lang.JSON.stringify(B[A]);}this._swf.setSeriesStyles(B);},destroy:function(){if(this._dataSource!==null){if(this._pollingID!==null){this._dataSource.clearInterval(this._pollingID);this._pollingID=null;}}if(this._dataTipFunction){YAHOO.widget.Chart.removeProxyFunction(this._dataTipFunction);}if(this._legendLabelFunction){YAHOO.widget.Chart.removeProxyFunction(this._legendLabelFunction);}if(this._swf){var B=YAHOO.util.Dom.get(this._containerID);B.removeChild(this._swf);}var A=this._id;for(var C in this){if(YAHOO.lang.hasOwnProperty(this,C)){this[C]=null;}}},_initAttributes:function(A){this.setAttributeConfig("altText",{method:this._setAltText,getter:this._getAltText});this.setAttributeConfig("swfURL",{getter:this._getSWFURL});this.setAttributeConfig("request",{method:this._setRequest,getter:this._getRequest});this.setAttributeConfig("dataSource",{method:this._setDataSource,getter:this._getDataSource});this.setAttributeConfig("series",{method:this._setSeriesDefs,getter:this._getSeriesDefs});this.setAttributeConfig("categoryNames",{validator:YAHOO.lang.isArray,method:this._setCategoryNames,getter:this._getCategoryNames});this.setAttributeConfig("dataTipFunction",{method:this._setDataTipFunction,getter:this._getDataTipFunction});this.setAttributeConfig("legendLabelFunction",{method:this._setLegendLabelFunction,getter:this._legendLabelFunction});this.setAttributeConfig("polling",{method:this._setPolling,getter:this._getPolling});},_eventHandler:function(A){if(A.type=="swfReady"){this._swf=this._swfEmbed._swf;this._loadHandler();this.fireEvent("contentReady");}},_loadHandler:function(){if(!this._swf||!this._swf.setType){return;}this._swf.setType(this._type);if(this._attributes.style){var A=this._attributes.style;this.setStyles(A);}this._initialized=false;this._initAttributes(this._attributes);this.setAttributes(this._attributes,true);this._initialized=true;if(this._dataSource){this.set("dataSource",this._dataSource);}},refreshData:function(){if(!this._initialized){return;}if(this._dataSource!==null){if(this._pollingID!==null){this._dataSource.clearInterval(this._pollingID);this._pollingID=null;}if(this._pollingInterval>0){this._pollingID=this._dataSource.setInterval(this._pollingInterval,this._request,this._loadDataHandler,this);}this._dataSource.sendRequest(this._request,this._loadDataHandler,this);}},_loadDataHandler:function(D,C,K){if(this._swf){if(K){}else{var H;if(this._seriesFunctions){var I=this._seriesFunctions.length;for(H=0;H<I;H++){YAHOO.widget.Chart.removeProxyFunction(this._seriesFunctions[H]);}this._seriesFunctions=null;}this._seriesFunctions=[];var F=[];var E=0;var L=null;if(this._seriesDefs!==null){E=this._seriesDefs.length;for(H=0;H<E;H++){L=this._seriesDefs[H];var B={};for(var A in L){if(YAHOO.lang.hasOwnProperty(L,A)){if(A=="style"){if(L.style!==null){B.style=YAHOO.lang.JSON.stringify(L.style);}}else{if(A=="labelFunction"){if(L.labelFunction!==null){B.labelFunction=YAHOO.widget.Chart.getFunctionReference(L.labelFunction);this._seriesFunctions.push(B.labelFunction);}}else{if(A=="dataTipFunction"){if(L.dataTipFunction!==null){B.dataTipFunction=YAHOO.widget.Chart.getFunctionReference(L.dataTipFunction);this._seriesFunctions.push(B.dataTipFunction);}}else{if(A=="legendLabelFunction"){if(L.legendLabelFunction!==null){B.legendLabelFunction=YAHOO.widget.Chart.getFunctionReference(L.legendLabelFunction);this._seriesFunctions.push(B.legendLabelFunction);}}else{B[A]=L[A];}}}}}}F.push(B);}}if(E>0){for(H=0;H<E;H++){L=F[H];if(!L.type){L.type=this._type;}L.dataProvider=C.results;}}else{var G={type:this._type,dataProvider:C.results};F.push(G);}try{if(this._swf.setDataProvider){this._swf.setDataProvider(F);}}catch(J){this._swf.setDataProvider(F);}}}},_request:"",_getRequest:function(){return this._request;},_setRequest:function(A){this._request=A;this.refreshData();},_dataSource:null,_getDataSource:function(){return this._dataSource;},_setDataSource:function(A){this._dataSource=A;this.refreshData();},_seriesDefs:null,_getSeriesDefs:function(){return this._seriesDefs;},_setSeriesDefs:function(A){this._seriesDefs=A;this.refreshData();},_getCategoryNames:function(){return this._swf.getCategoryNames();},_setCategoryNames:function(A){this._swf.setCategoryNames(A);
+},_setDataTipFunction:function(A){if(this._dataTipFunction){YAHOO.widget.Chart.removeProxyFunction(this._dataTipFunction);}if(A){this._dataTipFunction=A=YAHOO.widget.Chart.getFunctionReference(A);}this._swf.setDataTipFunction(A);},_setLegendLabelFunction:function(A){if(this._legendLabelFunction){YAHOO.widget.Chart.removeProxyFunction(this._legendLabelFunction);}if(A){this._legendLabelFunction=A=YAHOO.widget.Chart.getFunctionReference(A);}this._swf.setLegendLabelFunction(A);},_getPolling:function(){return this._pollingInterval;},_setPolling:function(A){this._pollingInterval=A;this.refreshData();},_swfEmbed:null,_swfURL:null,_containerID:null,_swf:null,_id:null,_initialized:false,_attributes:null,set:function(A,B){this._attributes[A]=B;YAHOO.widget.Chart.superclass.set.call(this,A,B);},_getSWFURL:function(){return this._swfURL;},_getAltText:function(){return this._swf.getAltText();},_setAltText:function(A){this._swf.setAltText(A);}});YAHOO.widget.Chart.proxyFunctionCount=0;YAHOO.widget.Chart.createProxyFunction=function(C,B){var B=B||null;var A=YAHOO.widget.Chart.proxyFunctionCount;YAHOO.widget.Chart["proxyFunction"+A]=function(){return C.apply(B,arguments);};YAHOO.widget.Chart.proxyFunctionCount++;return"YAHOO.widget.Chart.proxyFunction"+A.toString();};YAHOO.widget.Chart.getFunctionReference=function(B){if(typeof B=="function"){B=YAHOO.widget.Chart.createProxyFunction(B);}else{if(B.func&&typeof B.func=="function"){var A=[B.func];if(B.scope&&typeof B.scope=="object"){A.push(B.scope);}B=YAHOO.widget.Chart.createProxyFunction.apply(this,A);}}return B;};YAHOO.widget.Chart.removeProxyFunction=function(A){if(!A||A.indexOf("YAHOO.widget.Chart.proxyFunction")<0){return;}A=A.substr(26);YAHOO.widget.Chart[A]=null;};YAHOO.widget.Chart.SWFURL="assets/charts.swf";YAHOO.widget.PieChart=function(A,C,B){YAHOO.widget.PieChart.superclass.constructor.call(this,"pie",A,C,B);};YAHOO.lang.extend(YAHOO.widget.PieChart,YAHOO.widget.Chart,{_initAttributes:function(A){YAHOO.widget.PieChart.superclass._initAttributes.call(this,A);this.setAttributeConfig("dataField",{validator:YAHOO.lang.isString,method:this._setDataField,getter:this._getDataField});this.setAttributeConfig("categoryField",{validator:YAHOO.lang.isString,method:this._setCategoryField,getter:this._getCategoryField});},_getDataField:function(){return this._swf.getDataField();},_setDataField:function(A){this._swf.setDataField(A);},_getCategoryField:function(){return this._swf.getCategoryField();},_setCategoryField:function(A){this._swf.setCategoryField(A);}});YAHOO.widget.CartesianChart=function(C,A,D,B){YAHOO.widget.CartesianChart.superclass.constructor.call(this,C,A,D,B);};YAHOO.lang.extend(YAHOO.widget.CartesianChart,YAHOO.widget.Chart,{_xAxisLabelFunctions:[],_yAxisLabelFunctions:[],destroy:function(){this._removeAxisFunctions(this._xAxisLabelFunctions);this._removeAxisFunctions(this._yAxisLabelFunctions);YAHOO.widget.CartesianChart.superclass.destroy.call(this);},_initAttributes:function(A){YAHOO.widget.CartesianChart.superclass._initAttributes.call(this,A);this.setAttributeConfig("xField",{validator:YAHOO.lang.isString,method:this._setXField,getter:this._getXField});this.setAttributeConfig("yField",{validator:YAHOO.lang.isString,method:this._setYField,getter:this._getYField});this.setAttributeConfig("xAxis",{method:this._setXAxis});this.setAttributeConfig("xAxes",{method:this._setXAxes});this.setAttributeConfig("yAxis",{method:this._setYAxis});this.setAttributeConfig("yAxes",{method:this._setYAxes});this.setAttributeConfig("constrainViewport",{method:this._setConstrainViewport});},_getXField:function(){return this._swf.getHorizontalField();},_setXField:function(A){this._swf.setHorizontalField(A);},_getYField:function(){return this._swf.getVerticalField();},_setYField:function(A){this._swf.setVerticalField(A);},_getClonedAxis:function(A){var B={};for(var C in A){if(C=="labelFunction"){if(A.labelFunction&&A.labelFunction!==null){B.labelFunction=YAHOO.widget.Chart.getFunctionReference(A.labelFunction);}}else{B[C]=A[C];}}return B;},_removeAxisFunctions:function(C){if(C&&C.length>0){var A=C.length;for(var B=0;B<A;B++){if(C[B]!==null){YAHOO.widget.Chart.removeProxyFunction(C[B]);}}C=[];}},_setXAxis:function(A){if(A.position!="bottom"&&A.position!="top"){A.position="bottom";}this._removeAxisFunctions(this._xAxisLabelFunctions);A=this._getClonedAxis(A);this._xAxisLabelFunctions.push(A.labelFunction);this._swf.setHorizontalAxis(A);},_setXAxes:function(C){this._removeAxisFunctions(this._xAxisLabelFunctions);var A=C.length;for(var B=0;B<A;B++){if(C[B].position=="left"){C[B].position="bottom";}C[B]=this._getClonedAxis(C[B]);if(C[B].labelFunction){this._xAxisLabelFunctions.push(C[B].labelFunction);}this._swf.setHorizontalAxis(C[B]);}},_setYAxis:function(A){this._removeAxisFunctions(this._yAxisLabelFunctions);A=this._getClonedAxis(A);this._yAxisLabelFunctions.push(A.labelFunction);this._swf.setVerticalAxis(A);},_setYAxes:function(C){this._removeAxisFunctions(this._yAxisLabelFunctions);var A=C.length;for(var B=0;B<A;B++){C[B]=this._getClonedAxis(C[B]);if(C[B].labelFunction){this._yAxisLabelFunctions.push(C[B].labelFunction);}this._swf.setVerticalAxis(C[B]);}},_setConstrainViewport:function(A){this._swf.setConstrainViewport(A);},setSeriesStylesByIndex:function(A,B){B=YAHOO.lang.JSON.stringify(B);if(this._swf&&this._swf.setSeriesStylesByIndex){this._swf.setSeriesStylesByIndex(A,B);}}});YAHOO.widget.LineChart=function(A,C,B){YAHOO.widget.LineChart.superclass.constructor.call(this,"line",A,C,B);};YAHOO.lang.extend(YAHOO.widget.LineChart,YAHOO.widget.CartesianChart);YAHOO.widget.ColumnChart=function(A,C,B){YAHOO.widget.ColumnChart.superclass.constructor.call(this,"column",A,C,B);};YAHOO.lang.extend(YAHOO.widget.ColumnChart,YAHOO.widget.CartesianChart);YAHOO.widget.BarChart=function(A,C,B){YAHOO.widget.BarChart.superclass.constructor.call(this,"bar",A,C,B);};YAHOO.lang.extend(YAHOO.widget.BarChart,YAHOO.widget.CartesianChart);YAHOO.widget.StackedColumnChart=function(A,C,B){YAHOO.widget.StackedColumnChart.superclass.constructor.call(this,"stackcolumn",A,C,B);
+};YAHOO.lang.extend(YAHOO.widget.StackedColumnChart,YAHOO.widget.CartesianChart);YAHOO.widget.StackedBarChart=function(A,C,B){YAHOO.widget.StackedBarChart.superclass.constructor.call(this,"stackbar",A,C,B);};YAHOO.lang.extend(YAHOO.widget.StackedBarChart,YAHOO.widget.CartesianChart);YAHOO.widget.Axis=function(){};YAHOO.widget.Axis.prototype={type:null,reverse:false,labelFunction:null,labelSpacing:2,title:null};YAHOO.widget.NumericAxis=function(){YAHOO.widget.NumericAxis.superclass.constructor.call(this);};YAHOO.lang.extend(YAHOO.widget.NumericAxis,YAHOO.widget.Axis,{type:"numeric",minimum:NaN,maximum:NaN,majorUnit:NaN,minorUnit:NaN,snapToUnits:true,stackingEnabled:false,alwaysShowZero:true,scale:"linear",roundMajorUnit:true,calculateByLabelSize:true,position:"left",adjustMaximumByMajorUnit:true,adjustMinimumByMajorUnit:true});YAHOO.widget.TimeAxis=function(){YAHOO.widget.TimeAxis.superclass.constructor.call(this);};YAHOO.lang.extend(YAHOO.widget.TimeAxis,YAHOO.widget.Axis,{type:"time",minimum:null,maximum:null,majorUnit:NaN,majorTimeUnit:null,minorUnit:NaN,minorTimeUnit:null,snapToUnits:true,stackingEnabled:false,calculateByLabelSize:true});YAHOO.widget.CategoryAxis=function(){YAHOO.widget.CategoryAxis.superclass.constructor.call(this);};YAHOO.lang.extend(YAHOO.widget.CategoryAxis,YAHOO.widget.Axis,{type:"category",categoryNames:null,calculateCategoryCount:false});YAHOO.widget.Series=function(){};YAHOO.widget.Series.prototype={type:null,displayName:null};YAHOO.widget.CartesianSeries=function(){YAHOO.widget.CartesianSeries.superclass.constructor.call(this);};YAHOO.lang.extend(YAHOO.widget.CartesianSeries,YAHOO.widget.Series,{xField:null,yField:null,axis:"primary",showInLegend:true});YAHOO.widget.ColumnSeries=function(){YAHOO.widget.ColumnSeries.superclass.constructor.call(this);};YAHOO.lang.extend(YAHOO.widget.ColumnSeries,YAHOO.widget.CartesianSeries,{type:"column"});YAHOO.widget.LineSeries=function(){YAHOO.widget.LineSeries.superclass.constructor.call(this);};YAHOO.lang.extend(YAHOO.widget.LineSeries,YAHOO.widget.CartesianSeries,{type:"line"});YAHOO.widget.BarSeries=function(){YAHOO.widget.BarSeries.superclass.constructor.call(this);};YAHOO.lang.extend(YAHOO.widget.BarSeries,YAHOO.widget.CartesianSeries,{type:"bar"});YAHOO.widget.PieSeries=function(){YAHOO.widget.PieSeries.superclass.constructor.call(this);};YAHOO.lang.extend(YAHOO.widget.PieSeries,YAHOO.widget.Series,{type:"pie",dataField:null,categoryField:null,labelFunction:null});YAHOO.widget.StackedBarSeries=function(){YAHOO.widget.StackedBarSeries.superclass.constructor.call(this);};YAHOO.lang.extend(YAHOO.widget.StackedBarSeries,YAHOO.widget.CartesianSeries,{type:"stackbar"});YAHOO.widget.StackedColumnSeries=function(){YAHOO.widget.StackedColumnSeries.superclass.constructor.call(this);};YAHOO.lang.extend(YAHOO.widget.StackedColumnSeries,YAHOO.widget.CartesianSeries,{type:"stackcolumn"});YAHOO.register("charts",YAHOO.widget.Chart,{version:"2.8.2r1",build:"7"}); \ No newline at end of file
diff --git a/js/yui/colorpicker/colorpicker-min.js b/js/yui/colorpicker/colorpicker-min.js
new file mode 100644
index 000000000..461e422bf
--- /dev/null
+++ b/js/yui/colorpicker/colorpicker-min.js
@@ -0,0 +1,9 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+YAHOO.util.Color=function(){var A="0",B=YAHOO.lang.isArray,C=YAHOO.lang.isNumber;return{real2dec:function(D){return Math.min(255,Math.round(D*256));},hsv2rgb:function(H,O,M){if(B(H)){return this.hsv2rgb.call(this,H[0],H[1],H[2]);}var D,I,L,G=Math.floor((H/60)%6),J=(H/60)-G,F=M*(1-O),E=M*(1-J*O),N=M*(1-(1-J)*O),K;switch(G){case 0:D=M;I=N;L=F;break;case 1:D=E;I=M;L=F;break;case 2:D=F;I=M;L=N;break;case 3:D=F;I=E;L=M;break;case 4:D=N;I=F;L=M;break;case 5:D=M;I=F;L=E;break;}K=this.real2dec;return[K(D),K(I),K(L)];},rgb2hsv:function(D,H,I){if(B(D)){return this.rgb2hsv.apply(this,D);}D/=255;H/=255;I/=255;var G,L,E=Math.min(Math.min(D,H),I),J=Math.max(Math.max(D,H),I),K=J-E,F;switch(J){case E:G=0;break;case D:G=60*(H-I)/K;if(H<I){G+=360;}break;case H:G=(60*(I-D)/K)+120;break;case I:G=(60*(D-H)/K)+240;break;}L=(J===0)?0:1-(E/J);F=[Math.round(G),L,J];return F;},rgb2hex:function(F,E,D){if(B(F)){return this.rgb2hex.apply(this,F);}var G=this.dec2hex;return G(F)+G(E)+G(D);},dec2hex:function(D){D=parseInt(D,10)|0;D=(D>255||D<0)?0:D;return(A+D.toString(16)).slice(-2).toUpperCase();},hex2dec:function(D){return parseInt(D,16);},hex2rgb:function(D){var E=this.hex2dec;return[E(D.slice(0,2)),E(D.slice(2,4)),E(D.slice(4,6))];},websafe:function(F,E,D){if(B(F)){return this.websafe.apply(this,F);}var G=function(H){if(C(H)){H=Math.min(Math.max(0,H),255);var I,J;for(I=0;I<256;I=I+51){J=I+51;if(H>=I&&H<=J){return(H-I>25)?J:I;}}}return H;};return[G(F),G(E),G(D)];}};}();(function(){var J=0,F=YAHOO.util,C=YAHOO.lang,D=YAHOO.widget.Slider,B=F.Color,E=F.Dom,I=F.Event,A=C.substitute,H="yui-picker";function G(L,K){J=J+1;K=K||{};if(arguments.length===1&&!YAHOO.lang.isString(L)&&!L.nodeName){K=L;L=K.element||null;}if(!L&&!K.element){L=this._createHostElement(K);}G.superclass.constructor.call(this,L,K);this.initPicker();}YAHOO.extend(G,YAHOO.util.Element,{ID:{R:H+"-r",R_HEX:H+"-rhex",G:H+"-g",G_HEX:H+"-ghex",B:H+"-b",B_HEX:H+"-bhex",H:H+"-h",S:H+"-s",V:H+"-v",PICKER_BG:H+"-bg",PICKER_THUMB:H+"-thumb",HUE_BG:H+"-hue-bg",HUE_THUMB:H+"-hue-thumb",HEX:H+"-hex",SWATCH:H+"-swatch",WEBSAFE_SWATCH:H+"-websafe-swatch",CONTROLS:H+"-controls",RGB_CONTROLS:H+"-rgb-controls",HSV_CONTROLS:H+"-hsv-controls",HEX_CONTROLS:H+"-hex-controls",HEX_SUMMARY:H+"-hex-summary",CONTROLS_LABEL:H+"-controls-label"},TXT:{ILLEGAL_HEX:"Illegal hex value entered",SHOW_CONTROLS:"Show color details",HIDE_CONTROLS:"Hide color details",CURRENT_COLOR:"Currently selected color: {rgb}",CLOSEST_WEBSAFE:"Closest websafe color: {rgb}. Click to select.",R:"R",G:"G",B:"B",H:"H",S:"S",V:"V",HEX:"#",DEG:"\u00B0",PERCENT:"%"},IMAGE:{PICKER_THUMB:"../../build/colorpicker/assets/picker_thumb.png",HUE_THUMB:"../../build/colorpicker/assets/hue_thumb.png"},DEFAULT:{PICKER_SIZE:180},OPT:{HUE:"hue",SATURATION:"saturation",VALUE:"value",RED:"red",GREEN:"green",BLUE:"blue",HSV:"hsv",RGB:"rgb",WEBSAFE:"websafe",HEX:"hex",PICKER_SIZE:"pickersize",SHOW_CONTROLS:"showcontrols",SHOW_RGB_CONTROLS:"showrgbcontrols",SHOW_HSV_CONTROLS:"showhsvcontrols",SHOW_HEX_CONTROLS:"showhexcontrols",SHOW_HEX_SUMMARY:"showhexsummary",SHOW_WEBSAFE:"showwebsafe",CONTAINER:"container",IDS:"ids",ELEMENTS:"elements",TXT:"txt",IMAGES:"images",ANIMATE:"animate"},skipAnim:true,_createHostElement:function(){var K=document.createElement("div");if(this.CSS.BASE){K.className=this.CSS.BASE;}return K;},_updateHueSlider:function(){var K=this.get(this.OPT.PICKER_SIZE),L=this.get(this.OPT.HUE);L=K-Math.round(L/360*K);if(L===K){L=0;}this.hueSlider.setValue(L,this.skipAnim);},_updatePickerSlider:function(){var L=this.get(this.OPT.PICKER_SIZE),M=this.get(this.OPT.SATURATION),K=this.get(this.OPT.VALUE);M=Math.round(M*L/100);K=Math.round(L-(K*L/100));this.pickerSlider.setRegionValue(M,K,this.skipAnim);},_updateSliders:function(){this._updateHueSlider();this._updatePickerSlider();},setValue:function(L,K){K=(K)||false;this.set(this.OPT.RGB,L,K);this._updateSliders();},hueSlider:null,pickerSlider:null,_getH:function(){var K=this.get(this.OPT.PICKER_SIZE),L=(K-this.hueSlider.getValue())/K;L=Math.round(L*360);return(L===360)?0:L;},_getS:function(){return this.pickerSlider.getXValue()/this.get(this.OPT.PICKER_SIZE);},_getV:function(){var K=this.get(this.OPT.PICKER_SIZE);return(K-this.pickerSlider.getYValue())/K;},_updateSwatch:function(){var M=this.get(this.OPT.RGB),O=this.get(this.OPT.WEBSAFE),N=this.getElement(this.ID.SWATCH),L=M.join(","),K=this.get(this.OPT.TXT);E.setStyle(N,"background-color","rgb("+L+")");N.title=A(K.CURRENT_COLOR,{"rgb":"#"+this.get(this.OPT.HEX)});N=this.getElement(this.ID.WEBSAFE_SWATCH);L=O.join(",");E.setStyle(N,"background-color","rgb("+L+")");N.title=A(K.CLOSEST_WEBSAFE,{"rgb":"#"+B.rgb2hex(O)});},_getValuesFromSliders:function(){this.set(this.OPT.RGB,B.hsv2rgb(this._getH(),this._getS(),this._getV()));},_updateFormFields:function(){this.getElement(this.ID.H).value=this.get(this.OPT.HUE);this.getElement(this.ID.S).value=this.get(this.OPT.SATURATION);this.getElement(this.ID.V).value=this.get(this.OPT.VALUE);this.getElement(this.ID.R).value=this.get(this.OPT.RED);this.getElement(this.ID.R_HEX).innerHTML=B.dec2hex(this.get(this.OPT.RED));this.getElement(this.ID.G).value=this.get(this.OPT.GREEN);this.getElement(this.ID.G_HEX).innerHTML=B.dec2hex(this.get(this.OPT.GREEN));this.getElement(this.ID.B).value=this.get(this.OPT.BLUE);this.getElement(this.ID.B_HEX).innerHTML=B.dec2hex(this.get(this.OPT.BLUE));this.getElement(this.ID.HEX).value=this.get(this.OPT.HEX);},_onHueSliderChange:function(N){var L=this._getH(),K=B.hsv2rgb(L,1,1),M="rgb("+K.join(",")+")";this.set(this.OPT.HUE,L,true);E.setStyle(this.getElement(this.ID.PICKER_BG),"background-color",M);if(this.hueSlider.valueChangeSource!==D.SOURCE_SET_VALUE){this._getValuesFromSliders();}this._updateFormFields();this._updateSwatch();},_onPickerSliderChange:function(M){var L=this._getS(),K=this._getV();this.set(this.OPT.SATURATION,Math.round(L*100),true);this.set(this.OPT.VALUE,Math.round(K*100),true);if(this.pickerSlider.valueChangeSource!==D.SOURCE_SET_VALUE){this._getValuesFromSliders();
+}this._updateFormFields();this._updateSwatch();},_getCommand:function(K){var L=I.getCharCode(K);if(L===38){return 3;}else{if(L===13){return 6;}else{if(L===40){return 4;}else{if(L>=48&&L<=57){return 1;}else{if(L>=97&&L<=102){return 2;}else{if(L>=65&&L<=70){return 2;}else{if("8, 9, 13, 27, 37, 39".indexOf(L)>-1||K.ctrlKey||K.metaKey){return 5;}else{return 0;}}}}}}}},_useFieldValue:function(L,K,N){var M=K.value;if(N!==this.OPT.HEX){M=parseInt(M,10);}if(M!==this.get(N)){this.set(N,M);}},_rgbFieldKeypress:function(M,K,O){var N=this._getCommand(M),L=(M.shiftKey)?10:1;switch(N){case 6:this._useFieldValue.apply(this,arguments);break;case 3:this.set(O,Math.min(this.get(O)+L,255));this._updateFormFields();break;case 4:this.set(O,Math.max(this.get(O)-L,0));this._updateFormFields();break;default:}},_hexFieldKeypress:function(L,K,N){var M=this._getCommand(L);if(M===6){this._useFieldValue.apply(this,arguments);}},_hexOnly:function(L,K){var M=this._getCommand(L);switch(M){case 6:case 5:case 1:break;case 2:if(K!==true){break;}default:I.stopEvent(L);return false;}},_numbersOnly:function(K){return this._hexOnly(K,true);},getElement:function(K){return this.get(this.OPT.ELEMENTS)[this.get(this.OPT.IDS)[K]];},_createElements:function(){var N,M,P,O,L,K=this.get(this.OPT.IDS),Q=this.get(this.OPT.TXT),S=this.get(this.OPT.IMAGES),R=function(U,V){var W=document.createElement(U);if(V){C.augmentObject(W,V,true);}return W;},T=function(U,V){var W=C.merge({autocomplete:"off",value:"0",size:3,maxlength:3},V);W.name=W.id;return new R(U,W);};L=this.get("element");N=new R("div",{id:K[this.ID.PICKER_BG],className:"yui-picker-bg",tabIndex:-1,hideFocus:true});M=new R("div",{id:K[this.ID.PICKER_THUMB],className:"yui-picker-thumb"});P=new R("img",{src:S.PICKER_THUMB});M.appendChild(P);N.appendChild(M);L.appendChild(N);N=new R("div",{id:K[this.ID.HUE_BG],className:"yui-picker-hue-bg",tabIndex:-1,hideFocus:true});M=new R("div",{id:K[this.ID.HUE_THUMB],className:"yui-picker-hue-thumb"});P=new R("img",{src:S.HUE_THUMB});M.appendChild(P);N.appendChild(M);L.appendChild(N);N=new R("div",{id:K[this.ID.CONTROLS],className:"yui-picker-controls"});L.appendChild(N);L=N;N=new R("div",{className:"hd"});M=new R("a",{id:K[this.ID.CONTROLS_LABEL],href:"#"});N.appendChild(M);L.appendChild(N);N=new R("div",{className:"bd"});L.appendChild(N);L=N;N=new R("ul",{id:K[this.ID.RGB_CONTROLS],className:"yui-picker-rgb-controls"});M=new R("li");M.appendChild(document.createTextNode(Q.R+" "));O=new T("input",{id:K[this.ID.R],className:"yui-picker-r"});M.appendChild(O);N.appendChild(M);M=new R("li");M.appendChild(document.createTextNode(Q.G+" "));O=new T("input",{id:K[this.ID.G],className:"yui-picker-g"});M.appendChild(O);N.appendChild(M);M=new R("li");M.appendChild(document.createTextNode(Q.B+" "));O=new T("input",{id:K[this.ID.B],className:"yui-picker-b"});M.appendChild(O);N.appendChild(M);L.appendChild(N);N=new R("ul",{id:K[this.ID.HSV_CONTROLS],className:"yui-picker-hsv-controls"});M=new R("li");M.appendChild(document.createTextNode(Q.H+" "));O=new T("input",{id:K[this.ID.H],className:"yui-picker-h"});M.appendChild(O);M.appendChild(document.createTextNode(" "+Q.DEG));N.appendChild(M);M=new R("li");M.appendChild(document.createTextNode(Q.S+" "));O=new T("input",{id:K[this.ID.S],className:"yui-picker-s"});M.appendChild(O);M.appendChild(document.createTextNode(" "+Q.PERCENT));N.appendChild(M);M=new R("li");M.appendChild(document.createTextNode(Q.V+" "));O=new T("input",{id:K[this.ID.V],className:"yui-picker-v"});M.appendChild(O);M.appendChild(document.createTextNode(" "+Q.PERCENT));N.appendChild(M);L.appendChild(N);N=new R("ul",{id:K[this.ID.HEX_SUMMARY],className:"yui-picker-hex_summary"});M=new R("li",{id:K[this.ID.R_HEX]});N.appendChild(M);M=new R("li",{id:K[this.ID.G_HEX]});N.appendChild(M);M=new R("li",{id:K[this.ID.B_HEX]});N.appendChild(M);L.appendChild(N);N=new R("div",{id:K[this.ID.HEX_CONTROLS],className:"yui-picker-hex-controls"});N.appendChild(document.createTextNode(Q.HEX+" "));M=new T("input",{id:K[this.ID.HEX],className:"yui-picker-hex",size:6,maxlength:6});N.appendChild(M);L.appendChild(N);L=this.get("element");N=new R("div",{id:K[this.ID.SWATCH],className:"yui-picker-swatch"});L.appendChild(N);N=new R("div",{id:K[this.ID.WEBSAFE_SWATCH],className:"yui-picker-websafe-swatch"});L.appendChild(N);},_attachRGBHSV:function(L,K){I.on(this.getElement(L),"keydown",function(N,M){M._rgbFieldKeypress(N,this,K);},this);I.on(this.getElement(L),"keypress",this._numbersOnly,this,true);I.on(this.getElement(L),"blur",function(N,M){M._useFieldValue(N,this,K);},this);},_updateRGB:function(){var K=[this.get(this.OPT.RED),this.get(this.OPT.GREEN),this.get(this.OPT.BLUE)];this.set(this.OPT.RGB,K);this._updateSliders();},_initElements:function(){var O=this.OPT,N=this.get(O.IDS),L=this.get(O.ELEMENTS),K,M,P;for(K in this.ID){if(C.hasOwnProperty(this.ID,K)){N[this.ID[K]]=N[K];}}M=E.get(N[this.ID.PICKER_BG]);if(!M){this._createElements();}else{}for(K in N){if(C.hasOwnProperty(N,K)){M=E.get(N[K]);P=E.generateId(M);N[K]=P;N[N[K]]=P;L[P]=M;}}},initPicker:function(){this._initSliders();this._bindUI();this.syncUI(true);},_initSliders:function(){var K=this.ID,L=this.get(this.OPT.PICKER_SIZE);this.hueSlider=D.getVertSlider(this.getElement(K.HUE_BG),this.getElement(K.HUE_THUMB),0,L);this.pickerSlider=D.getSliderRegion(this.getElement(K.PICKER_BG),this.getElement(K.PICKER_THUMB),0,L,0,L);this.set(this.OPT.ANIMATE,this.get(this.OPT.ANIMATE));},_bindUI:function(){var K=this.ID,L=this.OPT;this.hueSlider.subscribe("change",this._onHueSliderChange,this,true);this.pickerSlider.subscribe("change",this._onPickerSliderChange,this,true);I.on(this.getElement(K.WEBSAFE_SWATCH),"click",function(M){this.setValue(this.get(L.WEBSAFE));},this,true);I.on(this.getElement(K.CONTROLS_LABEL),"click",function(M){this.set(L.SHOW_CONTROLS,!this.get(L.SHOW_CONTROLS));I.preventDefault(M);},this,true);this._attachRGBHSV(K.R,L.RED);this._attachRGBHSV(K.G,L.GREEN);this._attachRGBHSV(K.B,L.BLUE);this._attachRGBHSV(K.H,L.HUE);
+this._attachRGBHSV(K.S,L.SATURATION);this._attachRGBHSV(K.V,L.VALUE);I.on(this.getElement(K.HEX),"keydown",function(N,M){M._hexFieldKeypress(N,this,L.HEX);},this);I.on(this.getElement(this.ID.HEX),"keypress",this._hexOnly,this,true);I.on(this.getElement(this.ID.HEX),"blur",function(N,M){M._useFieldValue(N,this,L.HEX);},this);},syncUI:function(K){this.skipAnim=K;this._updateRGB();this.skipAnim=false;},_updateRGBFromHSV:function(){var L=[this.get(this.OPT.HUE),this.get(this.OPT.SATURATION)/100,this.get(this.OPT.VALUE)/100],K=B.hsv2rgb(L);this.set(this.OPT.RGB,K);this._updateSliders();},_updateHex:function(){var N=this.get(this.OPT.HEX),K=N.length,O,M,L;if(K===3){O=N.split("");for(M=0;M<K;M=M+1){O[M]=O[M]+O[M];}N=O.join("");}if(N.length!==6){return false;}L=B.hex2rgb(N);this.setValue(L);},_hideShowEl:function(M,K){var L=(C.isString(M)?this.getElement(M):M);E.setStyle(L,"display",(K)?"":"none");},initAttributes:function(K){K=K||{};G.superclass.initAttributes.call(this,K);this.setAttributeConfig(this.OPT.PICKER_SIZE,{value:K.size||this.DEFAULT.PICKER_SIZE});this.setAttributeConfig(this.OPT.HUE,{value:K.hue||0,validator:C.isNumber});this.setAttributeConfig(this.OPT.SATURATION,{value:K.saturation||0,validator:C.isNumber});this.setAttributeConfig(this.OPT.VALUE,{value:C.isNumber(K.value)?K.value:100,validator:C.isNumber});this.setAttributeConfig(this.OPT.RED,{value:C.isNumber(K.red)?K.red:255,validator:C.isNumber});this.setAttributeConfig(this.OPT.GREEN,{value:C.isNumber(K.green)?K.green:255,validator:C.isNumber});this.setAttributeConfig(this.OPT.BLUE,{value:C.isNumber(K.blue)?K.blue:255,validator:C.isNumber});this.setAttributeConfig(this.OPT.HEX,{value:K.hex||"FFFFFF",validator:C.isString});this.setAttributeConfig(this.OPT.RGB,{value:K.rgb||[255,255,255],method:function(O){this.set(this.OPT.RED,O[0],true);this.set(this.OPT.GREEN,O[1],true);this.set(this.OPT.BLUE,O[2],true);var Q=B.websafe(O),P=B.rgb2hex(O),N=B.rgb2hsv(O);this.set(this.OPT.WEBSAFE,Q,true);this.set(this.OPT.HEX,P,true);if(N[1]){this.set(this.OPT.HUE,N[0],true);}this.set(this.OPT.SATURATION,Math.round(N[1]*100),true);this.set(this.OPT.VALUE,Math.round(N[2]*100),true);},readonly:true});this.setAttributeConfig(this.OPT.CONTAINER,{value:null,method:function(N){if(N){N.showEvent.subscribe(function(){this.pickerSlider.focus();},this,true);}}});this.setAttributeConfig(this.OPT.WEBSAFE,{value:K.websafe||[255,255,255]});var M=K.ids||C.merge({},this.ID),L;if(!K.ids&&J>1){for(L in M){if(C.hasOwnProperty(M,L)){M[L]=M[L]+J;}}}this.setAttributeConfig(this.OPT.IDS,{value:M,writeonce:true});this.setAttributeConfig(this.OPT.TXT,{value:K.txt||this.TXT,writeonce:true});this.setAttributeConfig(this.OPT.IMAGES,{value:K.images||this.IMAGE,writeonce:true});this.setAttributeConfig(this.OPT.ELEMENTS,{value:{},readonly:true});this.setAttributeConfig(this.OPT.SHOW_CONTROLS,{value:C.isBoolean(K.showcontrols)?K.showcontrols:true,method:function(N){var O=E.getElementsByClassName("bd","div",this.getElement(this.ID.CONTROLS))[0];this._hideShowEl(O,N);this.getElement(this.ID.CONTROLS_LABEL).innerHTML=(N)?this.get(this.OPT.TXT).HIDE_CONTROLS:this.get(this.OPT.TXT).SHOW_CONTROLS;}});this.setAttributeConfig(this.OPT.SHOW_RGB_CONTROLS,{value:C.isBoolean(K.showrgbcontrols)?K.showrgbcontrols:true,method:function(N){this._hideShowEl(this.ID.RGB_CONTROLS,N);}});this.setAttributeConfig(this.OPT.SHOW_HSV_CONTROLS,{value:C.isBoolean(K.showhsvcontrols)?K.showhsvcontrols:false,method:function(N){this._hideShowEl(this.ID.HSV_CONTROLS,N);if(N&&this.get(this.OPT.SHOW_HEX_SUMMARY)){this.set(this.OPT.SHOW_HEX_SUMMARY,false);}}});this.setAttributeConfig(this.OPT.SHOW_HEX_CONTROLS,{value:C.isBoolean(K.showhexcontrols)?K.showhexcontrols:false,method:function(N){this._hideShowEl(this.ID.HEX_CONTROLS,N);}});this.setAttributeConfig(this.OPT.SHOW_WEBSAFE,{value:C.isBoolean(K.showwebsafe)?K.showwebsafe:true,method:function(N){this._hideShowEl(this.ID.WEBSAFE_SWATCH,N);}});this.setAttributeConfig(this.OPT.SHOW_HEX_SUMMARY,{value:C.isBoolean(K.showhexsummary)?K.showhexsummary:true,method:function(N){this._hideShowEl(this.ID.HEX_SUMMARY,N);if(N&&this.get(this.OPT.SHOW_HSV_CONTROLS)){this.set(this.OPT.SHOW_HSV_CONTROLS,false);}}});this.setAttributeConfig(this.OPT.ANIMATE,{value:C.isBoolean(K.animate)?K.animate:true,method:function(N){if(this.pickerSlider){this.pickerSlider.animate=N;this.hueSlider.animate=N;}}});this.on(this.OPT.HUE+"Change",this._updateRGBFromHSV,this,true);this.on(this.OPT.SATURATION+"Change",this._updateRGBFromHSV,this,true);this.on(this.OPT.VALUE+"Change",this._updateRGBFromHSV,this,true);this.on(this.OPT.RED+"Change",this._updateRGB,this,true);this.on(this.OPT.GREEN+"Change",this._updateRGB,this,true);this.on(this.OPT.BLUE+"Change",this._updateRGB,this,true);this.on(this.OPT.HEX+"Change",this._updateHex,this,true);this._initElements();}});YAHOO.widget.ColorPicker=G;})();YAHOO.register("colorpicker",YAHOO.widget.ColorPicker,{version:"2.8.2r1",build:"7"}); \ No newline at end of file
diff --git a/js/yui/connection/connection-min.js b/js/yui/connection/connection-min.js
new file mode 100644
index 000000000..10ce51b74
--- /dev/null
+++ b/js/yui/connection/connection-min.js
@@ -0,0 +1,9 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+YAHOO.util.Connect={_msxml_progid:["Microsoft.XMLHTTP","MSXML2.XMLHTTP.3.0","MSXML2.XMLHTTP"],_http_headers:{},_has_http_headers:false,_use_default_post_header:true,_default_post_header:"application/x-www-form-urlencoded; charset=UTF-8",_default_form_header:"application/x-www-form-urlencoded",_use_default_xhr_header:true,_default_xhr_header:"XMLHttpRequest",_has_default_headers:true,_default_headers:{},_poll:{},_timeOut:{},_polling_interval:50,_transaction_id:0,startEvent:new YAHOO.util.CustomEvent("start"),completeEvent:new YAHOO.util.CustomEvent("complete"),successEvent:new YAHOO.util.CustomEvent("success"),failureEvent:new YAHOO.util.CustomEvent("failure"),abortEvent:new YAHOO.util.CustomEvent("abort"),_customEvents:{onStart:["startEvent","start"],onComplete:["completeEvent","complete"],onSuccess:["successEvent","success"],onFailure:["failureEvent","failure"],onUpload:["uploadEvent","upload"],onAbort:["abortEvent","abort"]},setProgId:function(A){this._msxml_progid.unshift(A);},setDefaultPostHeader:function(A){if(typeof A=="string"){this._default_post_header=A;}else{if(typeof A=="boolean"){this._use_default_post_header=A;}}},setDefaultXhrHeader:function(A){if(typeof A=="string"){this._default_xhr_header=A;}else{this._use_default_xhr_header=A;}},setPollingInterval:function(A){if(typeof A=="number"&&isFinite(A)){this._polling_interval=A;}},createXhrObject:function(F){var D,A,B;try{A=new XMLHttpRequest();D={conn:A,tId:F,xhr:true};}catch(C){for(B=0;B<this._msxml_progid.length;++B){try{A=new ActiveXObject(this._msxml_progid[B]);D={conn:A,tId:F,xhr:true};break;}catch(E){}}}finally{return D;}},getConnectionObject:function(A){var C,D=this._transaction_id;try{if(!A){C=this.createXhrObject(D);}else{C={tId:D};if(A==="xdr"){C.conn=this._transport;C.xdr=true;}else{if(A==="upload"){C.upload=true;}}}if(C){this._transaction_id++;}}catch(B){}return C;},asyncRequest:function(G,D,F,A){var E,C,B=(F&&F.argument)?F.argument:null;if(this._isFileUpload){C="upload";}else{if(F.xdr){C="xdr";}}E=this.getConnectionObject(C);if(!E){return null;}else{if(F&&F.customevents){this.initCustomEvents(E,F);}if(this._isFormSubmit){if(this._isFileUpload){this.uploadFile(E,F,D,A);return E;}if(G.toUpperCase()=="GET"){if(this._sFormData.length!==0){D+=((D.indexOf("?")==-1)?"?":"&")+this._sFormData;}}else{if(G.toUpperCase()=="POST"){A=A?this._sFormData+"&"+A:this._sFormData;}}}if(G.toUpperCase()=="GET"&&(F&&F.cache===false)){D+=((D.indexOf("?")==-1)?"?":"&")+"rnd="+new Date().valueOf().toString();}if(this._use_default_xhr_header){if(!this._default_headers["X-Requested-With"]){this.initHeader("X-Requested-With",this._default_xhr_header,true);}}if((G.toUpperCase()==="POST"&&this._use_default_post_header)&&this._isFormSubmit===false){this.initHeader("Content-Type",this._default_post_header);}if(E.xdr){this.xdr(E,G,D,F,A);return E;}E.conn.open(G,D,true);if(this._has_default_headers||this._has_http_headers){this.setHeader(E);}this.handleReadyState(E,F);E.conn.send(A||"");if(this._isFormSubmit===true){this.resetFormState();}this.startEvent.fire(E,B);if(E.startEvent){E.startEvent.fire(E,B);}return E;}},initCustomEvents:function(A,C){var B;for(B in C.customevents){if(this._customEvents[B][0]){A[this._customEvents[B][0]]=new YAHOO.util.CustomEvent(this._customEvents[B][1],(C.scope)?C.scope:null);A[this._customEvents[B][0]].subscribe(C.customevents[B]);}}},handleReadyState:function(C,D){var B=this,A=(D&&D.argument)?D.argument:null;if(D&&D.timeout){this._timeOut[C.tId]=window.setTimeout(function(){B.abort(C,D,true);},D.timeout);}this._poll[C.tId]=window.setInterval(function(){if(C.conn&&C.conn.readyState===4){window.clearInterval(B._poll[C.tId]);delete B._poll[C.tId];if(D&&D.timeout){window.clearTimeout(B._timeOut[C.tId]);delete B._timeOut[C.tId];}B.completeEvent.fire(C,A);if(C.completeEvent){C.completeEvent.fire(C,A);}B.handleTransactionResponse(C,D);}},this._polling_interval);},handleTransactionResponse:function(B,I,D){var E,A,G=(I&&I.argument)?I.argument:null,C=(B.r&&B.r.statusText==="xdr:success")?true:false,H=(B.r&&B.r.statusText==="xdr:failure")?true:false,J=D;try{if((B.conn.status!==undefined&&B.conn.status!==0)||C){E=B.conn.status;}else{if(H&&!J){E=0;}else{E=13030;}}}catch(F){E=13030;}if((E>=200&&E<300)||E===1223||C){A=B.xdr?B.r:this.createResponseObject(B,G);if(I&&I.success){if(!I.scope){I.success(A);}else{I.success.apply(I.scope,[A]);}}this.successEvent.fire(A);if(B.successEvent){B.successEvent.fire(A);}}else{switch(E){case 12002:case 12029:case 12030:case 12031:case 12152:case 13030:A=this.createExceptionObject(B.tId,G,(D?D:false));if(I&&I.failure){if(!I.scope){I.failure(A);}else{I.failure.apply(I.scope,[A]);}}break;default:A=(B.xdr)?B.response:this.createResponseObject(B,G);if(I&&I.failure){if(!I.scope){I.failure(A);}else{I.failure.apply(I.scope,[A]);}}}this.failureEvent.fire(A);if(B.failureEvent){B.failureEvent.fire(A);}}this.releaseObject(B);A=null;},createResponseObject:function(A,G){var D={},I={},E,C,F,B;try{C=A.conn.getAllResponseHeaders();F=C.split("\n");for(E=0;E<F.length;E++){B=F[E].indexOf(":");if(B!=-1){I[F[E].substring(0,B)]=YAHOO.lang.trim(F[E].substring(B+2));}}}catch(H){}D.tId=A.tId;D.status=(A.conn.status==1223)?204:A.conn.status;D.statusText=(A.conn.status==1223)?"No Content":A.conn.statusText;D.getResponseHeader=I;D.getAllResponseHeaders=C;D.responseText=A.conn.responseText;D.responseXML=A.conn.responseXML;if(G){D.argument=G;}return D;},createExceptionObject:function(H,D,A){var F=0,G="communication failure",C=-1,B="transaction aborted",E={};E.tId=H;if(A){E.status=C;E.statusText=B;}else{E.status=F;E.statusText=G;}if(D){E.argument=D;}return E;},initHeader:function(A,D,C){var B=(C)?this._default_headers:this._http_headers;B[A]=D;if(C){this._has_default_headers=true;}else{this._has_http_headers=true;}},setHeader:function(A){var B;if(this._has_default_headers){for(B in this._default_headers){if(YAHOO.lang.hasOwnProperty(this._default_headers,B)){A.conn.setRequestHeader(B,this._default_headers[B]);}}}if(this._has_http_headers){for(B in this._http_headers){if(YAHOO.lang.hasOwnProperty(this._http_headers,B)){A.conn.setRequestHeader(B,this._http_headers[B]);
+}}this._http_headers={};this._has_http_headers=false;}},resetDefaultHeaders:function(){this._default_headers={};this._has_default_headers=false;},abort:function(E,G,A){var D,B=(G&&G.argument)?G.argument:null;E=E||{};if(E.conn){if(E.xhr){if(this.isCallInProgress(E)){E.conn.abort();window.clearInterval(this._poll[E.tId]);delete this._poll[E.tId];if(A){window.clearTimeout(this._timeOut[E.tId]);delete this._timeOut[E.tId];}D=true;}}else{if(E.xdr){E.conn.abort(E.tId);D=true;}}}else{if(E.upload){var C="yuiIO"+E.tId;var F=document.getElementById(C);if(F){YAHOO.util.Event.removeListener(F,"load");document.body.removeChild(F);if(A){window.clearTimeout(this._timeOut[E.tId]);delete this._timeOut[E.tId];}D=true;}}else{D=false;}}if(D===true){this.abortEvent.fire(E,B);if(E.abortEvent){E.abortEvent.fire(E,B);}this.handleTransactionResponse(E,G,true);}return D;},isCallInProgress:function(A){A=A||{};if(A.xhr&&A.conn){return A.conn.readyState!==4&&A.conn.readyState!==0;}else{if(A.xdr&&A.conn){return A.conn.isCallInProgress(A.tId);}else{if(A.upload===true){return document.getElementById("yuiIO"+A.tId)?true:false;}else{return false;}}}},releaseObject:function(A){if(A&&A.conn){A.conn=null;A=null;}}};(function(){var G=YAHOO.util.Connect,H={};function D(I){var J='<object id="YUIConnectionSwf" type="application/x-shockwave-flash" data="'+I+'" width="0" height="0">'+'<param name="movie" value="'+I+'">'+'<param name="allowScriptAccess" value="always">'+"</object>",K=document.createElement("div");document.body.appendChild(K);K.innerHTML=J;}function B(L,I,J,M,K){H[parseInt(L.tId)]={"o":L,"c":M};if(K){M.method=I;M.data=K;}L.conn.send(J,M,L.tId);}function E(I){D(I);G._transport=document.getElementById("YUIConnectionSwf");}function C(){G.xdrReadyEvent.fire();}function A(J,I){if(J){G.startEvent.fire(J,I.argument);if(J.startEvent){J.startEvent.fire(J,I.argument);}}}function F(J){var K=H[J.tId].o,I=H[J.tId].c;if(J.statusText==="xdr:start"){A(K,I);return;}J.responseText=decodeURI(J.responseText);K.r=J;if(I.argument){K.r.argument=I.argument;}this.handleTransactionResponse(K,I,J.statusText==="xdr:abort"?true:false);delete H[J.tId];}G.xdr=B;G.swf=D;G.transport=E;G.xdrReadyEvent=new YAHOO.util.CustomEvent("xdrReady");G.xdrReady=C;G.handleXdrResponse=F;})();(function(){var D=YAHOO.util.Connect,F=YAHOO.util.Event;D._isFormSubmit=false;D._isFileUpload=false;D._formNode=null;D._sFormData=null;D._submitElementValue=null;D.uploadEvent=new YAHOO.util.CustomEvent("upload"),D._hasSubmitListener=function(){if(F){F.addListener(document,"click",function(J){var I=F.getTarget(J),H=I.nodeName.toLowerCase();if((H==="input"||H==="button")&&(I.type&&I.type.toLowerCase()=="submit")){D._submitElementValue=encodeURIComponent(I.name)+"="+encodeURIComponent(I.value);}});return true;}return false;}();function G(T,O,J){var S,I,R,P,W,Q=false,M=[],V=0,L,N,K,U,H;this.resetFormState();if(typeof T=="string"){S=(document.getElementById(T)||document.forms[T]);}else{if(typeof T=="object"){S=T;}else{return;}}if(O){this.createFrame(J?J:null);this._isFormSubmit=true;this._isFileUpload=true;this._formNode=S;return;}for(L=0,N=S.elements.length;L<N;++L){I=S.elements[L];W=I.disabled;R=I.name;if(!W&&R){R=encodeURIComponent(R)+"=";P=encodeURIComponent(I.value);switch(I.type){case"select-one":if(I.selectedIndex>-1){H=I.options[I.selectedIndex];M[V++]=R+encodeURIComponent((H.attributes.value&&H.attributes.value.specified)?H.value:H.text);}break;case"select-multiple":if(I.selectedIndex>-1){for(K=I.selectedIndex,U=I.options.length;K<U;++K){H=I.options[K];if(H.selected){M[V++]=R+encodeURIComponent((H.attributes.value&&H.attributes.value.specified)?H.value:H.text);}}}break;case"radio":case"checkbox":if(I.checked){M[V++]=R+P;}break;case"file":case undefined:case"reset":case"button":break;case"submit":if(Q===false){if(this._hasSubmitListener&&this._submitElementValue){M[V++]=this._submitElementValue;}Q=true;}break;default:M[V++]=R+P;}}}this._isFormSubmit=true;this._sFormData=M.join("&");this.initHeader("Content-Type",this._default_form_header);return this._sFormData;}function C(){this._isFormSubmit=false;this._isFileUpload=false;this._formNode=null;this._sFormData="";}function B(H){var I="yuiIO"+this._transaction_id,J;if(YAHOO.env.ua.ie){J=document.createElement('<iframe id="'+I+'" name="'+I+'" />');if(typeof H=="boolean"){J.src="javascript:false";}}else{J=document.createElement("iframe");J.id=I;J.name=I;}J.style.position="absolute";J.style.top="-1000px";J.style.left="-1000px";document.body.appendChild(J);}function E(H){var K=[],I=H.split("&"),J,L;for(J=0;J<I.length;J++){L=I[J].indexOf("=");if(L!=-1){K[J]=document.createElement("input");K[J].type="hidden";K[J].name=decodeURIComponent(I[J].substring(0,L));K[J].value=decodeURIComponent(I[J].substring(L+1));this._formNode.appendChild(K[J]);}}return K;}function A(K,V,L,J){var Q="yuiIO"+K.tId,R="multipart/form-data",T=document.getElementById(Q),M=(document.documentMode&&document.documentMode===8)?true:false,W=this,S=(V&&V.argument)?V.argument:null,U,P,I,O,H,N;H={action:this._formNode.getAttribute("action"),method:this._formNode.getAttribute("method"),target:this._formNode.getAttribute("target")};this._formNode.setAttribute("action",L);this._formNode.setAttribute("method","POST");this._formNode.setAttribute("target",Q);if(YAHOO.env.ua.ie&&!M){this._formNode.setAttribute("encoding",R);}else{this._formNode.setAttribute("enctype",R);}if(J){U=this.appendPostData(J);}this._formNode.submit();this.startEvent.fire(K,S);if(K.startEvent){K.startEvent.fire(K,S);}if(V&&V.timeout){this._timeOut[K.tId]=window.setTimeout(function(){W.abort(K,V,true);},V.timeout);}if(U&&U.length>0){for(P=0;P<U.length;P++){this._formNode.removeChild(U[P]);}}for(I in H){if(YAHOO.lang.hasOwnProperty(H,I)){if(H[I]){this._formNode.setAttribute(I,H[I]);}else{this._formNode.removeAttribute(I);}}}this.resetFormState();N=function(){if(V&&V.timeout){window.clearTimeout(W._timeOut[K.tId]);delete W._timeOut[K.tId];}W.completeEvent.fire(K,S);if(K.completeEvent){K.completeEvent.fire(K,S);
+}O={tId:K.tId,argument:V.argument};try{O.responseText=T.contentWindow.document.body?T.contentWindow.document.body.innerHTML:T.contentWindow.document.documentElement.textContent;O.responseXML=T.contentWindow.document.XMLDocument?T.contentWindow.document.XMLDocument:T.contentWindow.document;}catch(X){}if(V&&V.upload){if(!V.scope){V.upload(O);}else{V.upload.apply(V.scope,[O]);}}W.uploadEvent.fire(O);if(K.uploadEvent){K.uploadEvent.fire(O);}F.removeListener(T,"load",N);setTimeout(function(){document.body.removeChild(T);W.releaseObject(K);},100);};F.addListener(T,"load",N);}D.setForm=G;D.resetFormState=C;D.createFrame=B;D.appendPostData=E;D.uploadFile=A;})();YAHOO.register("connection",YAHOO.util.Connect,{version:"2.8.2r1",build:"7"}); \ No newline at end of file
diff --git a/js/yui/connection/connection.swf b/js/yui/connection/connection.swf
new file mode 100644
index 000000000..c33a7fe27
--- /dev/null
+++ b/js/yui/connection/connection.swf
Binary files differ
diff --git a/js/yui/connection/connection_core-min.js b/js/yui/connection/connection_core-min.js
new file mode 100644
index 000000000..fa8e53c2a
--- /dev/null
+++ b/js/yui/connection/connection_core-min.js
@@ -0,0 +1,8 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+YAHOO.util.Connect={_msxml_progid:["Microsoft.XMLHTTP","MSXML2.XMLHTTP.3.0","MSXML2.XMLHTTP"],_http_headers:{},_has_http_headers:false,_use_default_post_header:true,_default_post_header:"application/x-www-form-urlencoded; charset=UTF-8",_default_form_header:"application/x-www-form-urlencoded",_use_default_xhr_header:true,_default_xhr_header:"XMLHttpRequest",_has_default_headers:true,_default_headers:{},_poll:{},_timeOut:{},_polling_interval:50,_transaction_id:0,startEvent:new YAHOO.util.CustomEvent("start"),completeEvent:new YAHOO.util.CustomEvent("complete"),successEvent:new YAHOO.util.CustomEvent("success"),failureEvent:new YAHOO.util.CustomEvent("failure"),abortEvent:new YAHOO.util.CustomEvent("abort"),_customEvents:{onStart:["startEvent","start"],onComplete:["completeEvent","complete"],onSuccess:["successEvent","success"],onFailure:["failureEvent","failure"],onUpload:["uploadEvent","upload"],onAbort:["abortEvent","abort"]},setProgId:function(A){this._msxml_progid.unshift(A);},setDefaultPostHeader:function(A){if(typeof A=="string"){this._default_post_header=A;}else{if(typeof A=="boolean"){this._use_default_post_header=A;}}},setDefaultXhrHeader:function(A){if(typeof A=="string"){this._default_xhr_header=A;}else{this._use_default_xhr_header=A;}},setPollingInterval:function(A){if(typeof A=="number"&&isFinite(A)){this._polling_interval=A;}},createXhrObject:function(F){var D,A,B;try{A=new XMLHttpRequest();D={conn:A,tId:F,xhr:true};}catch(C){for(B=0;B<this._msxml_progid.length;++B){try{A=new ActiveXObject(this._msxml_progid[B]);D={conn:A,tId:F,xhr:true};break;}catch(E){}}}finally{return D;}},getConnectionObject:function(A){var C,D=this._transaction_id;try{if(!A){C=this.createXhrObject(D);}else{C={tId:D};if(A==="xdr"){C.conn=this._transport;C.xdr=true;}else{if(A==="upload"){C.upload=true;}}}if(C){this._transaction_id++;}}catch(B){}return C;},asyncRequest:function(G,D,F,A){var E,C,B=(F&&F.argument)?F.argument:null;if(this._isFileUpload){C="upload";}else{if(F.xdr){C="xdr";}}E=this.getConnectionObject(C);if(!E){return null;}else{if(F&&F.customevents){this.initCustomEvents(E,F);}if(this._isFormSubmit){if(this._isFileUpload){this.uploadFile(E,F,D,A);return E;}if(G.toUpperCase()=="GET"){if(this._sFormData.length!==0){D+=((D.indexOf("?")==-1)?"?":"&")+this._sFormData;}}else{if(G.toUpperCase()=="POST"){A=A?this._sFormData+"&"+A:this._sFormData;}}}if(G.toUpperCase()=="GET"&&(F&&F.cache===false)){D+=((D.indexOf("?")==-1)?"?":"&")+"rnd="+new Date().valueOf().toString();}if(this._use_default_xhr_header){if(!this._default_headers["X-Requested-With"]){this.initHeader("X-Requested-With",this._default_xhr_header,true);}}if((G.toUpperCase()==="POST"&&this._use_default_post_header)&&this._isFormSubmit===false){this.initHeader("Content-Type",this._default_post_header);}if(E.xdr){this.xdr(E,G,D,F,A);return E;}E.conn.open(G,D,true);if(this._has_default_headers||this._has_http_headers){this.setHeader(E);}this.handleReadyState(E,F);E.conn.send(A||"");if(this._isFormSubmit===true){this.resetFormState();}this.startEvent.fire(E,B);if(E.startEvent){E.startEvent.fire(E,B);}return E;}},initCustomEvents:function(A,C){var B;for(B in C.customevents){if(this._customEvents[B][0]){A[this._customEvents[B][0]]=new YAHOO.util.CustomEvent(this._customEvents[B][1],(C.scope)?C.scope:null);A[this._customEvents[B][0]].subscribe(C.customevents[B]);}}},handleReadyState:function(C,D){var B=this,A=(D&&D.argument)?D.argument:null;if(D&&D.timeout){this._timeOut[C.tId]=window.setTimeout(function(){B.abort(C,D,true);},D.timeout);}this._poll[C.tId]=window.setInterval(function(){if(C.conn&&C.conn.readyState===4){window.clearInterval(B._poll[C.tId]);delete B._poll[C.tId];if(D&&D.timeout){window.clearTimeout(B._timeOut[C.tId]);delete B._timeOut[C.tId];}B.completeEvent.fire(C,A);if(C.completeEvent){C.completeEvent.fire(C,A);}B.handleTransactionResponse(C,D);}},this._polling_interval);},handleTransactionResponse:function(B,I,D){var E,A,G=(I&&I.argument)?I.argument:null,C=(B.r&&B.r.statusText==="xdr:success")?true:false,H=(B.r&&B.r.statusText==="xdr:failure")?true:false,J=D;try{if((B.conn.status!==undefined&&B.conn.status!==0)||C){E=B.conn.status;}else{if(H&&!J){E=0;}else{E=13030;}}}catch(F){E=13030;}if((E>=200&&E<300)||E===1223||C){A=B.xdr?B.r:this.createResponseObject(B,G);if(I&&I.success){if(!I.scope){I.success(A);}else{I.success.apply(I.scope,[A]);}}this.successEvent.fire(A);if(B.successEvent){B.successEvent.fire(A);}}else{switch(E){case 12002:case 12029:case 12030:case 12031:case 12152:case 13030:A=this.createExceptionObject(B.tId,G,(D?D:false));if(I&&I.failure){if(!I.scope){I.failure(A);}else{I.failure.apply(I.scope,[A]);}}break;default:A=(B.xdr)?B.response:this.createResponseObject(B,G);if(I&&I.failure){if(!I.scope){I.failure(A);}else{I.failure.apply(I.scope,[A]);}}}this.failureEvent.fire(A);if(B.failureEvent){B.failureEvent.fire(A);}}this.releaseObject(B);A=null;},createResponseObject:function(A,G){var D={},I={},E,C,F,B;try{C=A.conn.getAllResponseHeaders();F=C.split("\n");for(E=0;E<F.length;E++){B=F[E].indexOf(":");if(B!=-1){I[F[E].substring(0,B)]=YAHOO.lang.trim(F[E].substring(B+2));}}}catch(H){}D.tId=A.tId;D.status=(A.conn.status==1223)?204:A.conn.status;D.statusText=(A.conn.status==1223)?"No Content":A.conn.statusText;D.getResponseHeader=I;D.getAllResponseHeaders=C;D.responseText=A.conn.responseText;D.responseXML=A.conn.responseXML;if(G){D.argument=G;}return D;},createExceptionObject:function(H,D,A){var F=0,G="communication failure",C=-1,B="transaction aborted",E={};E.tId=H;if(A){E.status=C;E.statusText=B;}else{E.status=F;E.statusText=G;}if(D){E.argument=D;}return E;},initHeader:function(A,D,C){var B=(C)?this._default_headers:this._http_headers;B[A]=D;if(C){this._has_default_headers=true;}else{this._has_http_headers=true;}},setHeader:function(A){var B;if(this._has_default_headers){for(B in this._default_headers){if(YAHOO.lang.hasOwnProperty(this._default_headers,B)){A.conn.setRequestHeader(B,this._default_headers[B]);}}}if(this._has_http_headers){for(B in this._http_headers){if(YAHOO.lang.hasOwnProperty(this._http_headers,B)){A.conn.setRequestHeader(B,this._http_headers[B]);
+}}this._http_headers={};this._has_http_headers=false;}},resetDefaultHeaders:function(){this._default_headers={};this._has_default_headers=false;},abort:function(E,G,A){var D,B=(G&&G.argument)?G.argument:null;E=E||{};if(E.conn){if(E.xhr){if(this.isCallInProgress(E)){E.conn.abort();window.clearInterval(this._poll[E.tId]);delete this._poll[E.tId];if(A){window.clearTimeout(this._timeOut[E.tId]);delete this._timeOut[E.tId];}D=true;}}else{if(E.xdr){E.conn.abort(E.tId);D=true;}}}else{if(E.upload){var C="yuiIO"+E.tId;var F=document.getElementById(C);if(F){YAHOO.util.Event.removeListener(F,"load");document.body.removeChild(F);if(A){window.clearTimeout(this._timeOut[E.tId]);delete this._timeOut[E.tId];}D=true;}}else{D=false;}}if(D===true){this.abortEvent.fire(E,B);if(E.abortEvent){E.abortEvent.fire(E,B);}this.handleTransactionResponse(E,G,true);}return D;},isCallInProgress:function(A){A=A||{};if(A.xhr&&A.conn){return A.conn.readyState!==4&&A.conn.readyState!==0;}else{if(A.xdr&&A.conn){return A.conn.isCallInProgress(A.tId);}else{if(A.upload===true){return document.getElementById("yuiIO"+A.tId)?true:false;}else{return false;}}}},releaseObject:function(A){if(A&&A.conn){A.conn=null;A=null;}}};YAHOO.register("connection_core",YAHOO.util.Connect,{version:"2.8.2r1",build:"7"}); \ No newline at end of file
diff --git a/js/yui/container/container-min.js b/js/yui/container/container-min.js
new file mode 100644
index 000000000..eadb7f25a
--- /dev/null
+++ b/js/yui/container/container-min.js
@@ -0,0 +1,19 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+(function(){YAHOO.util.Config=function(D){if(D){this.init(D);}};var B=YAHOO.lang,C=YAHOO.util.CustomEvent,A=YAHOO.util.Config;A.CONFIG_CHANGED_EVENT="configChanged";A.BOOLEAN_TYPE="boolean";A.prototype={owner:null,queueInProgress:false,config:null,initialConfig:null,eventQueue:null,configChangedEvent:null,init:function(D){this.owner=D;this.configChangedEvent=this.createEvent(A.CONFIG_CHANGED_EVENT);this.configChangedEvent.signature=C.LIST;this.queueInProgress=false;this.config={};this.initialConfig={};this.eventQueue=[];},checkBoolean:function(D){return(typeof D==A.BOOLEAN_TYPE);},checkNumber:function(D){return(!isNaN(D));},fireEvent:function(D,F){var E=this.config[D];if(E&&E.event){E.event.fire(F);}},addProperty:function(E,D){E=E.toLowerCase();this.config[E]=D;D.event=this.createEvent(E,{scope:this.owner});D.event.signature=C.LIST;D.key=E;if(D.handler){D.event.subscribe(D.handler,this.owner);}this.setProperty(E,D.value,true);if(!D.suppressEvent){this.queueProperty(E,D.value);}},getConfig:function(){var D={},F=this.config,G,E;for(G in F){if(B.hasOwnProperty(F,G)){E=F[G];if(E&&E.event){D[G]=E.value;}}}return D;},getProperty:function(D){var E=this.config[D.toLowerCase()];if(E&&E.event){return E.value;}else{return undefined;}},resetProperty:function(D){D=D.toLowerCase();var E=this.config[D];if(E&&E.event){if(this.initialConfig[D]&&!B.isUndefined(this.initialConfig[D])){this.setProperty(D,this.initialConfig[D]);return true;}}else{return false;}},setProperty:function(E,G,D){var F;E=E.toLowerCase();if(this.queueInProgress&&!D){this.queueProperty(E,G);return true;}else{F=this.config[E];if(F&&F.event){if(F.validator&&!F.validator(G)){return false;}else{F.value=G;if(!D){this.fireEvent(E,G);this.configChangedEvent.fire([E,G]);}return true;}}else{return false;}}},queueProperty:function(S,P){S=S.toLowerCase();var R=this.config[S],K=false,J,G,H,I,O,Q,F,M,N,D,L,T,E;if(R&&R.event){if(!B.isUndefined(P)&&R.validator&&!R.validator(P)){return false;}else{if(!B.isUndefined(P)){R.value=P;}else{P=R.value;}K=false;J=this.eventQueue.length;for(L=0;L<J;L++){G=this.eventQueue[L];if(G){H=G[0];I=G[1];if(H==S){this.eventQueue[L]=null;this.eventQueue.push([S,(!B.isUndefined(P)?P:I)]);K=true;break;}}}if(!K&&!B.isUndefined(P)){this.eventQueue.push([S,P]);}}if(R.supercedes){O=R.supercedes.length;for(T=0;T<O;T++){Q=R.supercedes[T];F=this.eventQueue.length;for(E=0;E<F;E++){M=this.eventQueue[E];if(M){N=M[0];D=M[1];if(N==Q.toLowerCase()){this.eventQueue.push([N,D]);this.eventQueue[E]=null;break;}}}}}return true;}else{return false;}},refireEvent:function(D){D=D.toLowerCase();var E=this.config[D];if(E&&E.event&&!B.isUndefined(E.value)){if(this.queueInProgress){this.queueProperty(D);}else{this.fireEvent(D,E.value);}}},applyConfig:function(D,G){var F,E;if(G){E={};for(F in D){if(B.hasOwnProperty(D,F)){E[F.toLowerCase()]=D[F];}}this.initialConfig=E;}for(F in D){if(B.hasOwnProperty(D,F)){this.queueProperty(F,D[F]);}}},refresh:function(){var D;for(D in this.config){if(B.hasOwnProperty(this.config,D)){this.refireEvent(D);}}},fireQueue:function(){var E,H,D,G,F;this.queueInProgress=true;for(E=0;E<this.eventQueue.length;E++){H=this.eventQueue[E];if(H){D=H[0];G=H[1];F=this.config[D];F.value=G;this.eventQueue[E]=null;this.fireEvent(D,G);}}this.queueInProgress=false;this.eventQueue=[];},subscribeToConfigEvent:function(D,E,G,H){var F=this.config[D.toLowerCase()];if(F&&F.event){if(!A.alreadySubscribed(F.event,E,G)){F.event.subscribe(E,G,H);}return true;}else{return false;}},unsubscribeFromConfigEvent:function(D,E,G){var F=this.config[D.toLowerCase()];if(F&&F.event){return F.event.unsubscribe(E,G);}else{return false;}},toString:function(){var D="Config";if(this.owner){D+=" ["+this.owner.toString()+"]";}return D;},outputEventQueue:function(){var D="",G,E,F=this.eventQueue.length;for(E=0;E<F;E++){G=this.eventQueue[E];if(G){D+=G[0]+"="+G[1]+", ";}}return D;},destroy:function(){var E=this.config,D,F;for(D in E){if(B.hasOwnProperty(E,D)){F=E[D];F.event.unsubscribeAll();F.event=null;}}this.configChangedEvent.unsubscribeAll();this.configChangedEvent=null;this.owner=null;this.config=null;this.initialConfig=null;this.eventQueue=null;}};A.alreadySubscribed=function(E,H,I){var F=E.subscribers.length,D,G;if(F>0){G=F-1;do{D=E.subscribers[G];if(D&&D.obj==I&&D.fn==H){return true;}}while(G--);}return false;};YAHOO.lang.augmentProto(A,YAHOO.util.EventProvider);}());(function(){YAHOO.widget.Module=function(R,Q){if(R){this.init(R,Q);}else{}};var F=YAHOO.util.Dom,D=YAHOO.util.Config,N=YAHOO.util.Event,M=YAHOO.util.CustomEvent,G=YAHOO.widget.Module,I=YAHOO.env.ua,H,P,O,E,A={"BEFORE_INIT":"beforeInit","INIT":"init","APPEND":"append","BEFORE_RENDER":"beforeRender","RENDER":"render","CHANGE_HEADER":"changeHeader","CHANGE_BODY":"changeBody","CHANGE_FOOTER":"changeFooter","CHANGE_CONTENT":"changeContent","DESTROY":"destroy","BEFORE_SHOW":"beforeShow","SHOW":"show","BEFORE_HIDE":"beforeHide","HIDE":"hide"},J={"VISIBLE":{key:"visible",value:true,validator:YAHOO.lang.isBoolean},"EFFECT":{key:"effect",suppressEvent:true,supercedes:["visible"]},"MONITOR_RESIZE":{key:"monitorresize",value:true},"APPEND_TO_DOCUMENT_BODY":{key:"appendtodocumentbody",value:false}};G.IMG_ROOT=null;G.IMG_ROOT_SSL=null;G.CSS_MODULE="yui-module";G.CSS_HEADER="hd";G.CSS_BODY="bd";G.CSS_FOOTER="ft";G.RESIZE_MONITOR_SECURE_URL="javascript:false;";G.RESIZE_MONITOR_BUFFER=1;G.textResizeEvent=new M("textResize");G.forceDocumentRedraw=function(){var Q=document.documentElement;if(Q){Q.className+=" ";Q.className=YAHOO.lang.trim(Q.className);}};function L(){if(!H){H=document.createElement("div");H.innerHTML=('<div class="'+G.CSS_HEADER+'"></div>'+'<div class="'+G.CSS_BODY+'"></div><div class="'+G.CSS_FOOTER+'"></div>');P=H.firstChild;O=P.nextSibling;E=O.nextSibling;}return H;}function K(){if(!P){L();}return(P.cloneNode(false));}function B(){if(!O){L();}return(O.cloneNode(false));}function C(){if(!E){L();}return(E.cloneNode(false));}G.prototype={constructor:G,element:null,header:null,body:null,footer:null,id:null,imageRoot:G.IMG_ROOT,initEvents:function(){var Q=M.LIST;
+this.beforeInitEvent=this.createEvent(A.BEFORE_INIT);this.beforeInitEvent.signature=Q;this.initEvent=this.createEvent(A.INIT);this.initEvent.signature=Q;this.appendEvent=this.createEvent(A.APPEND);this.appendEvent.signature=Q;this.beforeRenderEvent=this.createEvent(A.BEFORE_RENDER);this.beforeRenderEvent.signature=Q;this.renderEvent=this.createEvent(A.RENDER);this.renderEvent.signature=Q;this.changeHeaderEvent=this.createEvent(A.CHANGE_HEADER);this.changeHeaderEvent.signature=Q;this.changeBodyEvent=this.createEvent(A.CHANGE_BODY);this.changeBodyEvent.signature=Q;this.changeFooterEvent=this.createEvent(A.CHANGE_FOOTER);this.changeFooterEvent.signature=Q;this.changeContentEvent=this.createEvent(A.CHANGE_CONTENT);this.changeContentEvent.signature=Q;this.destroyEvent=this.createEvent(A.DESTROY);this.destroyEvent.signature=Q;this.beforeShowEvent=this.createEvent(A.BEFORE_SHOW);this.beforeShowEvent.signature=Q;this.showEvent=this.createEvent(A.SHOW);this.showEvent.signature=Q;this.beforeHideEvent=this.createEvent(A.BEFORE_HIDE);this.beforeHideEvent.signature=Q;this.hideEvent=this.createEvent(A.HIDE);this.hideEvent.signature=Q;},platform:function(){var Q=navigator.userAgent.toLowerCase();if(Q.indexOf("windows")!=-1||Q.indexOf("win32")!=-1){return"windows";}else{if(Q.indexOf("macintosh")!=-1){return"mac";}else{return false;}}}(),browser:function(){var Q=navigator.userAgent.toLowerCase();if(Q.indexOf("opera")!=-1){return"opera";}else{if(Q.indexOf("msie 7")!=-1){return"ie7";}else{if(Q.indexOf("msie")!=-1){return"ie";}else{if(Q.indexOf("safari")!=-1){return"safari";}else{if(Q.indexOf("gecko")!=-1){return"gecko";}else{return false;}}}}}}(),isSecure:function(){if(window.location.href.toLowerCase().indexOf("https")===0){return true;}else{return false;}}(),initDefaultConfig:function(){this.cfg.addProperty(J.VISIBLE.key,{handler:this.configVisible,value:J.VISIBLE.value,validator:J.VISIBLE.validator});this.cfg.addProperty(J.EFFECT.key,{suppressEvent:J.EFFECT.suppressEvent,supercedes:J.EFFECT.supercedes});this.cfg.addProperty(J.MONITOR_RESIZE.key,{handler:this.configMonitorResize,value:J.MONITOR_RESIZE.value});this.cfg.addProperty(J.APPEND_TO_DOCUMENT_BODY.key,{value:J.APPEND_TO_DOCUMENT_BODY.value});},init:function(V,U){var S,W;this.initEvents();this.beforeInitEvent.fire(G);this.cfg=new D(this);if(this.isSecure){this.imageRoot=G.IMG_ROOT_SSL;}if(typeof V=="string"){S=V;V=document.getElementById(V);if(!V){V=(L()).cloneNode(false);V.id=S;}}this.id=F.generateId(V);this.element=V;W=this.element.firstChild;if(W){var R=false,Q=false,T=false;do{if(1==W.nodeType){if(!R&&F.hasClass(W,G.CSS_HEADER)){this.header=W;R=true;}else{if(!Q&&F.hasClass(W,G.CSS_BODY)){this.body=W;Q=true;}else{if(!T&&F.hasClass(W,G.CSS_FOOTER)){this.footer=W;T=true;}}}}}while((W=W.nextSibling));}this.initDefaultConfig();F.addClass(this.element,G.CSS_MODULE);if(U){this.cfg.applyConfig(U,true);}if(!D.alreadySubscribed(this.renderEvent,this.cfg.fireQueue,this.cfg)){this.renderEvent.subscribe(this.cfg.fireQueue,this.cfg,true);}this.initEvent.fire(G);},initResizeMonitor:function(){var R=(I.gecko&&this.platform=="windows");if(R){var Q=this;setTimeout(function(){Q._initResizeMonitor();},0);}else{this._initResizeMonitor();}},_initResizeMonitor:function(){var Q,S,U;function W(){G.textResizeEvent.fire();}if(!I.opera){S=F.get("_yuiResizeMonitor");var V=this._supportsCWResize();if(!S){S=document.createElement("iframe");if(this.isSecure&&G.RESIZE_MONITOR_SECURE_URL&&I.ie){S.src=G.RESIZE_MONITOR_SECURE_URL;}if(!V){U=["<html><head><script ",'type="text/javascript">',"window.onresize=function(){window.parent.","YAHOO.widget.Module.textResizeEvent.","fire();};<","/script></head>","<body></body></html>"].join("");S.src="data:text/html;charset=utf-8,"+encodeURIComponent(U);}S.id="_yuiResizeMonitor";S.title="Text Resize Monitor";S.style.position="absolute";S.style.visibility="hidden";var R=document.body,T=R.firstChild;if(T){R.insertBefore(S,T);}else{R.appendChild(S);}S.style.backgroundColor="transparent";S.style.borderWidth="0";S.style.width="2em";S.style.height="2em";S.style.left="0";S.style.top=(-1*(S.offsetHeight+G.RESIZE_MONITOR_BUFFER))+"px";S.style.visibility="visible";if(I.webkit){Q=S.contentWindow.document;Q.open();Q.close();}}if(S&&S.contentWindow){G.textResizeEvent.subscribe(this.onDomResize,this,true);if(!G.textResizeInitialized){if(V){if(!N.on(S.contentWindow,"resize",W)){N.on(S,"resize",W);}}G.textResizeInitialized=true;}this.resizeMonitor=S;}}},_supportsCWResize:function(){var Q=true;if(I.gecko&&I.gecko<=1.8){Q=false;}return Q;},onDomResize:function(S,R){var Q=-1*(this.resizeMonitor.offsetHeight+G.RESIZE_MONITOR_BUFFER);this.resizeMonitor.style.top=Q+"px";this.resizeMonitor.style.left="0";},setHeader:function(R){var Q=this.header||(this.header=K());if(R.nodeName){Q.innerHTML="";Q.appendChild(R);}else{Q.innerHTML=R;}if(this._rendered){this._renderHeader();}this.changeHeaderEvent.fire(R);this.changeContentEvent.fire();},appendToHeader:function(R){var Q=this.header||(this.header=K());Q.appendChild(R);this.changeHeaderEvent.fire(R);this.changeContentEvent.fire();},setBody:function(R){var Q=this.body||(this.body=B());if(R.nodeName){Q.innerHTML="";Q.appendChild(R);}else{Q.innerHTML=R;}if(this._rendered){this._renderBody();}this.changeBodyEvent.fire(R);this.changeContentEvent.fire();},appendToBody:function(R){var Q=this.body||(this.body=B());Q.appendChild(R);this.changeBodyEvent.fire(R);this.changeContentEvent.fire();},setFooter:function(R){var Q=this.footer||(this.footer=C());if(R.nodeName){Q.innerHTML="";Q.appendChild(R);}else{Q.innerHTML=R;}if(this._rendered){this._renderFooter();}this.changeFooterEvent.fire(R);this.changeContentEvent.fire();},appendToFooter:function(R){var Q=this.footer||(this.footer=C());Q.appendChild(R);this.changeFooterEvent.fire(R);this.changeContentEvent.fire();},render:function(S,Q){var T=this;function R(U){if(typeof U=="string"){U=document.getElementById(U);}if(U){T._addToParent(U,T.element);T.appendEvent.fire();}}this.beforeRenderEvent.fire();
+if(!Q){Q=this.element;}if(S){R(S);}else{if(!F.inDocument(this.element)){return false;}}this._renderHeader(Q);this._renderBody(Q);this._renderFooter(Q);this._rendered=true;this.renderEvent.fire();return true;},_renderHeader:function(Q){Q=Q||this.element;if(this.header&&!F.inDocument(this.header)){var R=Q.firstChild;if(R){Q.insertBefore(this.header,R);}else{Q.appendChild(this.header);}}},_renderBody:function(Q){Q=Q||this.element;if(this.body&&!F.inDocument(this.body)){if(this.footer&&F.isAncestor(Q,this.footer)){Q.insertBefore(this.body,this.footer);}else{Q.appendChild(this.body);}}},_renderFooter:function(Q){Q=Q||this.element;if(this.footer&&!F.inDocument(this.footer)){Q.appendChild(this.footer);}},destroy:function(){var Q;if(this.element){N.purgeElement(this.element,true);Q=this.element.parentNode;}if(Q){Q.removeChild(this.element);}this.element=null;this.header=null;this.body=null;this.footer=null;G.textResizeEvent.unsubscribe(this.onDomResize,this);this.cfg.destroy();this.cfg=null;this.destroyEvent.fire();},show:function(){this.cfg.setProperty("visible",true);},hide:function(){this.cfg.setProperty("visible",false);},configVisible:function(R,Q,S){var T=Q[0];if(T){this.beforeShowEvent.fire();F.setStyle(this.element,"display","block");this.showEvent.fire();}else{this.beforeHideEvent.fire();F.setStyle(this.element,"display","none");this.hideEvent.fire();}},configMonitorResize:function(S,R,T){var Q=R[0];if(Q){this.initResizeMonitor();}else{G.textResizeEvent.unsubscribe(this.onDomResize,this,true);this.resizeMonitor=null;}},_addToParent:function(Q,R){if(!this.cfg.getProperty("appendtodocumentbody")&&Q===document.body&&Q.firstChild){Q.insertBefore(R,Q.firstChild);}else{Q.appendChild(R);}},toString:function(){return"Module "+this.id;}};YAHOO.lang.augmentProto(G,YAHOO.util.EventProvider);}());(function(){YAHOO.widget.Overlay=function(P,O){YAHOO.widget.Overlay.superclass.constructor.call(this,P,O);};var I=YAHOO.lang,M=YAHOO.util.CustomEvent,G=YAHOO.widget.Module,N=YAHOO.util.Event,F=YAHOO.util.Dom,D=YAHOO.util.Config,K=YAHOO.env.ua,B=YAHOO.widget.Overlay,H="subscribe",E="unsubscribe",C="contained",J,A={"BEFORE_MOVE":"beforeMove","MOVE":"move"},L={"X":{key:"x",validator:I.isNumber,suppressEvent:true,supercedes:["iframe"]},"Y":{key:"y",validator:I.isNumber,suppressEvent:true,supercedes:["iframe"]},"XY":{key:"xy",suppressEvent:true,supercedes:["iframe"]},"CONTEXT":{key:"context",suppressEvent:true,supercedes:["iframe"]},"FIXED_CENTER":{key:"fixedcenter",value:false,supercedes:["iframe","visible"]},"WIDTH":{key:"width",suppressEvent:true,supercedes:["context","fixedcenter","iframe"]},"HEIGHT":{key:"height",suppressEvent:true,supercedes:["context","fixedcenter","iframe"]},"AUTO_FILL_HEIGHT":{key:"autofillheight",supercedes:["height"],value:"body"},"ZINDEX":{key:"zindex",value:null},"CONSTRAIN_TO_VIEWPORT":{key:"constraintoviewport",value:false,validator:I.isBoolean,supercedes:["iframe","x","y","xy"]},"IFRAME":{key:"iframe",value:(K.ie==6?true:false),validator:I.isBoolean,supercedes:["zindex"]},"PREVENT_CONTEXT_OVERLAP":{key:"preventcontextoverlap",value:false,validator:I.isBoolean,supercedes:["constraintoviewport"]}};B.IFRAME_SRC="javascript:false;";B.IFRAME_OFFSET=3;B.VIEWPORT_OFFSET=10;B.TOP_LEFT="tl";B.TOP_RIGHT="tr";B.BOTTOM_LEFT="bl";B.BOTTOM_RIGHT="br";B.PREVENT_OVERLAP_X={"tltr":true,"blbr":true,"brbl":true,"trtl":true};B.PREVENT_OVERLAP_Y={"trbr":true,"tlbl":true,"bltl":true,"brtr":true};B.CSS_OVERLAY="yui-overlay";B.CSS_HIDDEN="yui-overlay-hidden";B.CSS_IFRAME="yui-overlay-iframe";B.STD_MOD_RE=/^\s*?(body|footer|header)\s*?$/i;B.windowScrollEvent=new M("windowScroll");B.windowResizeEvent=new M("windowResize");B.windowScrollHandler=function(P){var O=N.getTarget(P);if(!O||O===window||O===window.document){if(K.ie){if(!window.scrollEnd){window.scrollEnd=-1;}clearTimeout(window.scrollEnd);window.scrollEnd=setTimeout(function(){B.windowScrollEvent.fire();},1);}else{B.windowScrollEvent.fire();}}};B.windowResizeHandler=function(O){if(K.ie){if(!window.resizeEnd){window.resizeEnd=-1;}clearTimeout(window.resizeEnd);window.resizeEnd=setTimeout(function(){B.windowResizeEvent.fire();},100);}else{B.windowResizeEvent.fire();}};B._initialized=null;if(B._initialized===null){N.on(window,"scroll",B.windowScrollHandler);N.on(window,"resize",B.windowResizeHandler);B._initialized=true;}B._TRIGGER_MAP={"windowScroll":B.windowScrollEvent,"windowResize":B.windowResizeEvent,"textResize":G.textResizeEvent};YAHOO.extend(B,G,{CONTEXT_TRIGGERS:[],init:function(P,O){B.superclass.init.call(this,P);this.beforeInitEvent.fire(B);F.addClass(this.element,B.CSS_OVERLAY);if(O){this.cfg.applyConfig(O,true);}if(this.platform=="mac"&&K.gecko){if(!D.alreadySubscribed(this.showEvent,this.showMacGeckoScrollbars,this)){this.showEvent.subscribe(this.showMacGeckoScrollbars,this,true);}if(!D.alreadySubscribed(this.hideEvent,this.hideMacGeckoScrollbars,this)){this.hideEvent.subscribe(this.hideMacGeckoScrollbars,this,true);}}this.initEvent.fire(B);},initEvents:function(){B.superclass.initEvents.call(this);var O=M.LIST;this.beforeMoveEvent=this.createEvent(A.BEFORE_MOVE);this.beforeMoveEvent.signature=O;this.moveEvent=this.createEvent(A.MOVE);this.moveEvent.signature=O;},initDefaultConfig:function(){B.superclass.initDefaultConfig.call(this);var O=this.cfg;O.addProperty(L.X.key,{handler:this.configX,validator:L.X.validator,suppressEvent:L.X.suppressEvent,supercedes:L.X.supercedes});O.addProperty(L.Y.key,{handler:this.configY,validator:L.Y.validator,suppressEvent:L.Y.suppressEvent,supercedes:L.Y.supercedes});O.addProperty(L.XY.key,{handler:this.configXY,suppressEvent:L.XY.suppressEvent,supercedes:L.XY.supercedes});O.addProperty(L.CONTEXT.key,{handler:this.configContext,suppressEvent:L.CONTEXT.suppressEvent,supercedes:L.CONTEXT.supercedes});O.addProperty(L.FIXED_CENTER.key,{handler:this.configFixedCenter,value:L.FIXED_CENTER.value,validator:L.FIXED_CENTER.validator,supercedes:L.FIXED_CENTER.supercedes});O.addProperty(L.WIDTH.key,{handler:this.configWidth,suppressEvent:L.WIDTH.suppressEvent,supercedes:L.WIDTH.supercedes});
+O.addProperty(L.HEIGHT.key,{handler:this.configHeight,suppressEvent:L.HEIGHT.suppressEvent,supercedes:L.HEIGHT.supercedes});O.addProperty(L.AUTO_FILL_HEIGHT.key,{handler:this.configAutoFillHeight,value:L.AUTO_FILL_HEIGHT.value,validator:this._validateAutoFill,supercedes:L.AUTO_FILL_HEIGHT.supercedes});O.addProperty(L.ZINDEX.key,{handler:this.configzIndex,value:L.ZINDEX.value});O.addProperty(L.CONSTRAIN_TO_VIEWPORT.key,{handler:this.configConstrainToViewport,value:L.CONSTRAIN_TO_VIEWPORT.value,validator:L.CONSTRAIN_TO_VIEWPORT.validator,supercedes:L.CONSTRAIN_TO_VIEWPORT.supercedes});O.addProperty(L.IFRAME.key,{handler:this.configIframe,value:L.IFRAME.value,validator:L.IFRAME.validator,supercedes:L.IFRAME.supercedes});O.addProperty(L.PREVENT_CONTEXT_OVERLAP.key,{value:L.PREVENT_CONTEXT_OVERLAP.value,validator:L.PREVENT_CONTEXT_OVERLAP.validator,supercedes:L.PREVENT_CONTEXT_OVERLAP.supercedes});},moveTo:function(O,P){this.cfg.setProperty("xy",[O,P]);},hideMacGeckoScrollbars:function(){F.replaceClass(this.element,"show-scrollbars","hide-scrollbars");},showMacGeckoScrollbars:function(){F.replaceClass(this.element,"hide-scrollbars","show-scrollbars");},_setDomVisibility:function(O){F.setStyle(this.element,"visibility",(O)?"visible":"hidden");var P=B.CSS_HIDDEN;if(O){F.removeClass(this.element,P);}else{F.addClass(this.element,P);}},configVisible:function(R,O,X){var Q=O[0],S=F.getStyle(this.element,"visibility"),Y=this.cfg.getProperty("effect"),V=[],U=(this.platform=="mac"&&K.gecko),g=D.alreadySubscribed,W,P,f,c,b,a,d,Z,T;if(S=="inherit"){f=this.element.parentNode;while(f.nodeType!=9&&f.nodeType!=11){S=F.getStyle(f,"visibility");if(S!="inherit"){break;}f=f.parentNode;}if(S=="inherit"){S="visible";}}if(Y){if(Y instanceof Array){Z=Y.length;for(c=0;c<Z;c++){W=Y[c];V[V.length]=W.effect(this,W.duration);}}else{V[V.length]=Y.effect(this,Y.duration);}}if(Q){if(U){this.showMacGeckoScrollbars();}if(Y){if(Q){if(S!="visible"||S===""){this.beforeShowEvent.fire();T=V.length;for(b=0;b<T;b++){P=V[b];if(b===0&&!g(P.animateInCompleteEvent,this.showEvent.fire,this.showEvent)){P.animateInCompleteEvent.subscribe(this.showEvent.fire,this.showEvent,true);}P.animateIn();}}}}else{if(S!="visible"||S===""){this.beforeShowEvent.fire();this._setDomVisibility(true);this.cfg.refireEvent("iframe");this.showEvent.fire();}else{this._setDomVisibility(true);}}}else{if(U){this.hideMacGeckoScrollbars();}if(Y){if(S=="visible"){this.beforeHideEvent.fire();T=V.length;for(a=0;a<T;a++){d=V[a];if(a===0&&!g(d.animateOutCompleteEvent,this.hideEvent.fire,this.hideEvent)){d.animateOutCompleteEvent.subscribe(this.hideEvent.fire,this.hideEvent,true);}d.animateOut();}}else{if(S===""){this._setDomVisibility(false);}}}else{if(S=="visible"||S===""){this.beforeHideEvent.fire();this._setDomVisibility(false);this.hideEvent.fire();}else{this._setDomVisibility(false);}}}},doCenterOnDOMEvent:function(){var O=this.cfg,P=O.getProperty("fixedcenter");if(O.getProperty("visible")){if(P&&(P!==C||this.fitsInViewport())){this.center();}}},fitsInViewport:function(){var S=B.VIEWPORT_OFFSET,Q=this.element,T=Q.offsetWidth,R=Q.offsetHeight,O=F.getViewportWidth(),P=F.getViewportHeight();return((T+S<O)&&(R+S<P));},configFixedCenter:function(S,Q,T){var U=Q[0],P=D.alreadySubscribed,R=B.windowResizeEvent,O=B.windowScrollEvent;if(U){this.center();if(!P(this.beforeShowEvent,this.center)){this.beforeShowEvent.subscribe(this.center);}if(!P(R,this.doCenterOnDOMEvent,this)){R.subscribe(this.doCenterOnDOMEvent,this,true);}if(!P(O,this.doCenterOnDOMEvent,this)){O.subscribe(this.doCenterOnDOMEvent,this,true);}}else{this.beforeShowEvent.unsubscribe(this.center);R.unsubscribe(this.doCenterOnDOMEvent,this);O.unsubscribe(this.doCenterOnDOMEvent,this);}},configHeight:function(R,P,S){var O=P[0],Q=this.element;F.setStyle(Q,"height",O);this.cfg.refireEvent("iframe");},configAutoFillHeight:function(T,S,P){var V=S[0],Q=this.cfg,U="autofillheight",W="height",R=Q.getProperty(U),O=this._autoFillOnHeightChange;Q.unsubscribeFromConfigEvent(W,O);G.textResizeEvent.unsubscribe(O);this.changeContentEvent.unsubscribe(O);if(R&&V!==R&&this[R]){F.setStyle(this[R],W,"");}if(V){V=I.trim(V.toLowerCase());Q.subscribeToConfigEvent(W,O,this[V],this);G.textResizeEvent.subscribe(O,this[V],this);this.changeContentEvent.subscribe(O,this[V],this);Q.setProperty(U,V,true);}},configWidth:function(R,O,S){var Q=O[0],P=this.element;F.setStyle(P,"width",Q);this.cfg.refireEvent("iframe");},configzIndex:function(Q,O,R){var S=O[0],P=this.element;if(!S){S=F.getStyle(P,"zIndex");if(!S||isNaN(S)){S=0;}}if(this.iframe||this.cfg.getProperty("iframe")===true){if(S<=0){S=1;}}F.setStyle(P,"zIndex",S);this.cfg.setProperty("zIndex",S,true);if(this.iframe){this.stackIframe();}},configXY:function(Q,P,R){var T=P[0],O=T[0],S=T[1];this.cfg.setProperty("x",O);this.cfg.setProperty("y",S);this.beforeMoveEvent.fire([O,S]);O=this.cfg.getProperty("x");S=this.cfg.getProperty("y");this.cfg.refireEvent("iframe");this.moveEvent.fire([O,S]);},configX:function(Q,P,R){var O=P[0],S=this.cfg.getProperty("y");this.cfg.setProperty("x",O,true);this.cfg.setProperty("y",S,true);this.beforeMoveEvent.fire([O,S]);O=this.cfg.getProperty("x");S=this.cfg.getProperty("y");F.setX(this.element,O,true);this.cfg.setProperty("xy",[O,S],true);this.cfg.refireEvent("iframe");this.moveEvent.fire([O,S]);},configY:function(Q,P,R){var O=this.cfg.getProperty("x"),S=P[0];this.cfg.setProperty("x",O,true);this.cfg.setProperty("y",S,true);this.beforeMoveEvent.fire([O,S]);O=this.cfg.getProperty("x");S=this.cfg.getProperty("y");F.setY(this.element,S,true);this.cfg.setProperty("xy",[O,S],true);this.cfg.refireEvent("iframe");this.moveEvent.fire([O,S]);},showIframe:function(){var P=this.iframe,O;if(P){O=this.element.parentNode;if(O!=P.parentNode){this._addToParent(O,P);}P.style.display="block";}},hideIframe:function(){if(this.iframe){this.iframe.style.display="none";}},syncIframe:function(){var O=this.iframe,Q=this.element,S=B.IFRAME_OFFSET,P=(S*2),R;if(O){O.style.width=(Q.offsetWidth+P+"px");
+O.style.height=(Q.offsetHeight+P+"px");R=this.cfg.getProperty("xy");if(!I.isArray(R)||(isNaN(R[0])||isNaN(R[1]))){this.syncPosition();R=this.cfg.getProperty("xy");}F.setXY(O,[(R[0]-S),(R[1]-S)]);}},stackIframe:function(){if(this.iframe){var O=F.getStyle(this.element,"zIndex");if(!YAHOO.lang.isUndefined(O)&&!isNaN(O)){F.setStyle(this.iframe,"zIndex",(O-1));}}},configIframe:function(R,Q,S){var O=Q[0];function T(){var V=this.iframe,W=this.element,X;if(!V){if(!J){J=document.createElement("iframe");if(this.isSecure){J.src=B.IFRAME_SRC;}if(K.ie){J.style.filter="alpha(opacity=0)";J.frameBorder=0;}else{J.style.opacity="0";}J.style.position="absolute";J.style.border="none";J.style.margin="0";J.style.padding="0";J.style.display="none";J.tabIndex=-1;J.className=B.CSS_IFRAME;}V=J.cloneNode(false);V.id=this.id+"_f";X=W.parentNode;var U=X||document.body;this._addToParent(U,V);this.iframe=V;}this.showIframe();this.syncIframe();this.stackIframe();if(!this._hasIframeEventListeners){this.showEvent.subscribe(this.showIframe);this.hideEvent.subscribe(this.hideIframe);this.changeContentEvent.subscribe(this.syncIframe);this._hasIframeEventListeners=true;}}function P(){T.call(this);this.beforeShowEvent.unsubscribe(P);this._iframeDeferred=false;}if(O){if(this.cfg.getProperty("visible")){T.call(this);}else{if(!this._iframeDeferred){this.beforeShowEvent.subscribe(P);this._iframeDeferred=true;}}}else{this.hideIframe();if(this._hasIframeEventListeners){this.showEvent.unsubscribe(this.showIframe);this.hideEvent.unsubscribe(this.hideIframe);this.changeContentEvent.unsubscribe(this.syncIframe);this._hasIframeEventListeners=false;}}},_primeXYFromDOM:function(){if(YAHOO.lang.isUndefined(this.cfg.getProperty("xy"))){this.syncPosition();this.cfg.refireEvent("xy");this.beforeShowEvent.unsubscribe(this._primeXYFromDOM);}},configConstrainToViewport:function(P,O,Q){var R=O[0];if(R){if(!D.alreadySubscribed(this.beforeMoveEvent,this.enforceConstraints,this)){this.beforeMoveEvent.subscribe(this.enforceConstraints,this,true);}if(!D.alreadySubscribed(this.beforeShowEvent,this._primeXYFromDOM)){this.beforeShowEvent.subscribe(this._primeXYFromDOM);}}else{this.beforeShowEvent.unsubscribe(this._primeXYFromDOM);this.beforeMoveEvent.unsubscribe(this.enforceConstraints,this);}},configContext:function(U,T,Q){var X=T[0],R,O,V,S,P,W=this.CONTEXT_TRIGGERS;if(X){R=X[0];O=X[1];V=X[2];S=X[3];P=X[4];if(W&&W.length>0){S=(S||[]).concat(W);}if(R){if(typeof R=="string"){this.cfg.setProperty("context",[document.getElementById(R),O,V,S,P],true);}if(O&&V){this.align(O,V,P);}if(this._contextTriggers){this._processTriggers(this._contextTriggers,E,this._alignOnTrigger);}if(S){this._processTriggers(S,H,this._alignOnTrigger);this._contextTriggers=S;}}}},_alignOnTrigger:function(P,O){this.align();},_findTriggerCE:function(O){var P=null;if(O instanceof M){P=O;}else{if(B._TRIGGER_MAP[O]){P=B._TRIGGER_MAP[O];}}return P;},_processTriggers:function(S,U,R){var Q,T;for(var P=0,O=S.length;P<O;++P){Q=S[P];T=this._findTriggerCE(Q);if(T){T[U](R,this,true);}else{this[U](Q,R);}}},align:function(P,W,S){var V=this.cfg.getProperty("context"),T=this,O,Q,U;function R(Z,a){var Y=null,X=null;switch(P){case B.TOP_LEFT:Y=a;X=Z;break;case B.TOP_RIGHT:Y=a-Q.offsetWidth;X=Z;break;case B.BOTTOM_LEFT:Y=a;X=Z-Q.offsetHeight;break;case B.BOTTOM_RIGHT:Y=a-Q.offsetWidth;X=Z-Q.offsetHeight;break;}if(Y!==null&&X!==null){if(S){Y+=S[0];X+=S[1];}T.moveTo(Y,X);}}if(V){O=V[0];Q=this.element;T=this;if(!P){P=V[1];}if(!W){W=V[2];}if(!S&&V[4]){S=V[4];}if(Q&&O){U=F.getRegion(O);switch(W){case B.TOP_LEFT:R(U.top,U.left);break;case B.TOP_RIGHT:R(U.top,U.right);break;case B.BOTTOM_LEFT:R(U.bottom,U.left);break;case B.BOTTOM_RIGHT:R(U.bottom,U.right);break;}}}},enforceConstraints:function(P,O,Q){var S=O[0];var R=this.getConstrainedXY(S[0],S[1]);this.cfg.setProperty("x",R[0],true);this.cfg.setProperty("y",R[1],true);this.cfg.setProperty("xy",R,true);},_getConstrainedPos:function(X,P){var T=this.element,R=B.VIEWPORT_OFFSET,Z=(X=="x"),Y=(Z)?T.offsetWidth:T.offsetHeight,S=(Z)?F.getViewportWidth():F.getViewportHeight(),c=(Z)?F.getDocumentScrollLeft():F.getDocumentScrollTop(),b=(Z)?B.PREVENT_OVERLAP_X:B.PREVENT_OVERLAP_Y,O=this.cfg.getProperty("context"),U=(Y+R<S),W=this.cfg.getProperty("preventcontextoverlap")&&O&&b[(O[1]+O[2])],V=c+R,a=c+S-Y-R,Q=P;if(P<V||P>a){if(W){Q=this._preventOverlap(X,O[0],Y,S,c);}else{if(U){if(P<V){Q=V;}else{if(P>a){Q=a;}}}else{Q=V;}}}return Q;},_preventOverlap:function(X,W,Y,U,b){var Z=(X=="x"),T=B.VIEWPORT_OFFSET,S=this,Q=((Z)?F.getX(W):F.getY(W))-b,O=(Z)?W.offsetWidth:W.offsetHeight,P=Q-T,R=(U-(Q+O))-T,c=false,V=function(){var d;if((S.cfg.getProperty(X)-b)>Q){d=(Q-Y);}else{d=(Q+O);}S.cfg.setProperty(X,(d+b),true);return d;},a=function(){var e=((S.cfg.getProperty(X)-b)>Q)?R:P,d;if(Y>e){if(c){V();}else{V();c=true;d=a();}}return d;};a();return this.cfg.getProperty(X);},getConstrainedX:function(O){return this._getConstrainedPos("x",O);},getConstrainedY:function(O){return this._getConstrainedPos("y",O);},getConstrainedXY:function(O,P){return[this.getConstrainedX(O),this.getConstrainedY(P)];},center:function(){var R=B.VIEWPORT_OFFSET,S=this.element.offsetWidth,Q=this.element.offsetHeight,P=F.getViewportWidth(),T=F.getViewportHeight(),O,U;if(S<P){O=(P/2)-(S/2)+F.getDocumentScrollLeft();}else{O=R+F.getDocumentScrollLeft();}if(Q<T){U=(T/2)-(Q/2)+F.getDocumentScrollTop();}else{U=R+F.getDocumentScrollTop();}this.cfg.setProperty("xy",[parseInt(O,10),parseInt(U,10)]);this.cfg.refireEvent("iframe");if(K.webkit){this.forceContainerRedraw();}},syncPosition:function(){var O=F.getXY(this.element);this.cfg.setProperty("x",O[0],true);this.cfg.setProperty("y",O[1],true);this.cfg.setProperty("xy",O,true);},onDomResize:function(Q,P){var O=this;B.superclass.onDomResize.call(this,Q,P);setTimeout(function(){O.syncPosition();O.cfg.refireEvent("iframe");O.cfg.refireEvent("context");},0);},_getComputedHeight:(function(){if(document.defaultView&&document.defaultView.getComputedStyle){return function(P){var O=null;
+if(P.ownerDocument&&P.ownerDocument.defaultView){var Q=P.ownerDocument.defaultView.getComputedStyle(P,"");if(Q){O=parseInt(Q.height,10);}}return(I.isNumber(O))?O:null;};}else{return function(P){var O=null;if(P.style.pixelHeight){O=P.style.pixelHeight;}return(I.isNumber(O))?O:null;};}})(),_validateAutoFillHeight:function(O){return(!O)||(I.isString(O)&&B.STD_MOD_RE.test(O));},_autoFillOnHeightChange:function(R,P,Q){var O=this.cfg.getProperty("height");if((O&&O!=="auto")||(O===0)){this.fillHeight(Q);}},_getPreciseHeight:function(P){var O=P.offsetHeight;if(P.getBoundingClientRect){var Q=P.getBoundingClientRect();O=Q.bottom-Q.top;}return O;},fillHeight:function(R){if(R){var P=this.innerElement||this.element,O=[this.header,this.body,this.footer],V,W=0,X=0,T=0,Q=false;for(var U=0,S=O.length;U<S;U++){V=O[U];if(V){if(R!==V){X+=this._getPreciseHeight(V);}else{Q=true;}}}if(Q){if(K.ie||K.opera){F.setStyle(R,"height",0+"px");}W=this._getComputedHeight(P);if(W===null){F.addClass(P,"yui-override-padding");W=P.clientHeight;F.removeClass(P,"yui-override-padding");}T=Math.max(W-X,0);F.setStyle(R,"height",T+"px");if(R.offsetHeight!=T){T=Math.max(T-(R.offsetHeight-T),0);}F.setStyle(R,"height",T+"px");}}},bringToTop:function(){var S=[],R=this.element;function V(Z,Y){var b=F.getStyle(Z,"zIndex"),a=F.getStyle(Y,"zIndex"),X=(!b||isNaN(b))?0:parseInt(b,10),W=(!a||isNaN(a))?0:parseInt(a,10);if(X>W){return -1;}else{if(X<W){return 1;}else{return 0;}}}function Q(Y){var X=F.hasClass(Y,B.CSS_OVERLAY),W=YAHOO.widget.Panel;if(X&&!F.isAncestor(R,Y)){if(W&&F.hasClass(Y,W.CSS_PANEL)){S[S.length]=Y.parentNode;}else{S[S.length]=Y;}}}F.getElementsBy(Q,"DIV",document.body);S.sort(V);var O=S[0],U;if(O){U=F.getStyle(O,"zIndex");if(!isNaN(U)){var T=false;if(O!=R){T=true;}else{if(S.length>1){var P=F.getStyle(S[1],"zIndex");if(!isNaN(P)&&(U==P)){T=true;}}}if(T){this.cfg.setProperty("zindex",(parseInt(U,10)+2));}}}},destroy:function(){if(this.iframe){this.iframe.parentNode.removeChild(this.iframe);}this.iframe=null;B.windowResizeEvent.unsubscribe(this.doCenterOnDOMEvent,this);B.windowScrollEvent.unsubscribe(this.doCenterOnDOMEvent,this);G.textResizeEvent.unsubscribe(this._autoFillOnHeightChange);if(this._contextTriggers){this._processTriggers(this._contextTriggers,E,this._alignOnTrigger);}B.superclass.destroy.call(this);},forceContainerRedraw:function(){var O=this;F.addClass(O.element,"yui-force-redraw");setTimeout(function(){F.removeClass(O.element,"yui-force-redraw");},0);},toString:function(){return"Overlay "+this.id;}});}());(function(){YAHOO.widget.OverlayManager=function(G){this.init(G);};var D=YAHOO.widget.Overlay,C=YAHOO.util.Event,E=YAHOO.util.Dom,B=YAHOO.util.Config,F=YAHOO.util.CustomEvent,A=YAHOO.widget.OverlayManager;A.CSS_FOCUSED="focused";A.prototype={constructor:A,overlays:null,initDefaultConfig:function(){this.cfg.addProperty("overlays",{suppressEvent:true});this.cfg.addProperty("focusevent",{value:"mousedown"});},init:function(I){this.cfg=new B(this);this.initDefaultConfig();if(I){this.cfg.applyConfig(I,true);}this.cfg.fireQueue();var H=null;this.getActive=function(){return H;};this.focus=function(J){var K=this.find(J);if(K){K.focus();}};this.remove=function(K){var M=this.find(K),J;if(M){if(H==M){H=null;}var L=(M.element===null&&M.cfg===null)?true:false;if(!L){J=E.getStyle(M.element,"zIndex");M.cfg.setProperty("zIndex",-1000,true);}this.overlays.sort(this.compareZIndexDesc);this.overlays=this.overlays.slice(0,(this.overlays.length-1));M.hideEvent.unsubscribe(M.blur);M.destroyEvent.unsubscribe(this._onOverlayDestroy,M);M.focusEvent.unsubscribe(this._onOverlayFocusHandler,M);M.blurEvent.unsubscribe(this._onOverlayBlurHandler,M);if(!L){C.removeListener(M.element,this.cfg.getProperty("focusevent"),this._onOverlayElementFocus);M.cfg.setProperty("zIndex",J,true);M.cfg.setProperty("manager",null);}if(M.focusEvent._managed){M.focusEvent=null;}if(M.blurEvent._managed){M.blurEvent=null;}if(M.focus._managed){M.focus=null;}if(M.blur._managed){M.blur=null;}}};this.blurAll=function(){var K=this.overlays.length,J;if(K>0){J=K-1;do{this.overlays[J].blur();}while(J--);}};this._manageBlur=function(J){var K=false;if(H==J){E.removeClass(H.element,A.CSS_FOCUSED);H=null;K=true;}return K;};this._manageFocus=function(J){var K=false;if(H!=J){if(H){H.blur();}H=J;this.bringToTop(H);E.addClass(H.element,A.CSS_FOCUSED);K=true;}return K;};var G=this.cfg.getProperty("overlays");if(!this.overlays){this.overlays=[];}if(G){this.register(G);this.overlays.sort(this.compareZIndexDesc);}},_onOverlayElementFocus:function(I){var G=C.getTarget(I),H=this.close;if(H&&(G==H||E.isAncestor(H,G))){this.blur();}else{this.focus();}},_onOverlayDestroy:function(H,G,I){this.remove(I);},_onOverlayFocusHandler:function(H,G,I){this._manageFocus(I);},_onOverlayBlurHandler:function(H,G,I){this._manageBlur(I);},_bindFocus:function(G){var H=this;if(!G.focusEvent){G.focusEvent=G.createEvent("focus");G.focusEvent.signature=F.LIST;G.focusEvent._managed=true;}else{G.focusEvent.subscribe(H._onOverlayFocusHandler,G,H);}if(!G.focus){C.on(G.element,H.cfg.getProperty("focusevent"),H._onOverlayElementFocus,null,G);G.focus=function(){if(H._manageFocus(this)){if(this.cfg.getProperty("visible")&&this.focusFirst){this.focusFirst();}this.focusEvent.fire();}};G.focus._managed=true;}},_bindBlur:function(G){var H=this;if(!G.blurEvent){G.blurEvent=G.createEvent("blur");G.blurEvent.signature=F.LIST;G.focusEvent._managed=true;}else{G.blurEvent.subscribe(H._onOverlayBlurHandler,G,H);}if(!G.blur){G.blur=function(){if(H._manageBlur(this)){this.blurEvent.fire();}};G.blur._managed=true;}G.hideEvent.subscribe(G.blur);},_bindDestroy:function(G){var H=this;G.destroyEvent.subscribe(H._onOverlayDestroy,G,H);},_syncZIndex:function(G){var H=E.getStyle(G.element,"zIndex");if(!isNaN(H)){G.cfg.setProperty("zIndex",parseInt(H,10));}else{G.cfg.setProperty("zIndex",0);}},register:function(G){var J=false,H,I;if(G instanceof D){G.cfg.addProperty("manager",{value:this});this._bindFocus(G);this._bindBlur(G);this._bindDestroy(G);
+this._syncZIndex(G);this.overlays.push(G);this.bringToTop(G);J=true;}else{if(G instanceof Array){for(H=0,I=G.length;H<I;H++){J=this.register(G[H])||J;}}}return J;},bringToTop:function(M){var I=this.find(M),L,G,J;if(I){J=this.overlays;J.sort(this.compareZIndexDesc);G=J[0];if(G){L=E.getStyle(G.element,"zIndex");if(!isNaN(L)){var K=false;if(G!==I){K=true;}else{if(J.length>1){var H=E.getStyle(J[1].element,"zIndex");if(!isNaN(H)&&(L==H)){K=true;}}}if(K){I.cfg.setProperty("zindex",(parseInt(L,10)+2));}}J.sort(this.compareZIndexDesc);}}},find:function(G){var K=G instanceof D,I=this.overlays,M=I.length,J=null,L,H;if(K||typeof G=="string"){for(H=M-1;H>=0;H--){L=I[H];if((K&&(L===G))||(L.id==G)){J=L;break;}}}return J;},compareZIndexDesc:function(J,I){var H=(J.cfg)?J.cfg.getProperty("zIndex"):null,G=(I.cfg)?I.cfg.getProperty("zIndex"):null;if(H===null&&G===null){return 0;}else{if(H===null){return 1;}else{if(G===null){return -1;}else{if(H>G){return -1;}else{if(H<G){return 1;}else{return 0;}}}}}},showAll:function(){var H=this.overlays,I=H.length,G;for(G=I-1;G>=0;G--){H[G].show();}},hideAll:function(){var H=this.overlays,I=H.length,G;for(G=I-1;G>=0;G--){H[G].hide();}},toString:function(){return"OverlayManager";}};}());(function(){YAHOO.widget.Tooltip=function(P,O){YAHOO.widget.Tooltip.superclass.constructor.call(this,P,O);};var E=YAHOO.lang,N=YAHOO.util.Event,M=YAHOO.util.CustomEvent,C=YAHOO.util.Dom,J=YAHOO.widget.Tooltip,H=YAHOO.env.ua,G=(H.ie&&(H.ie<=6||document.compatMode=="BackCompat")),F,I={"PREVENT_OVERLAP":{key:"preventoverlap",value:true,validator:E.isBoolean,supercedes:["x","y","xy"]},"SHOW_DELAY":{key:"showdelay",value:200,validator:E.isNumber},"AUTO_DISMISS_DELAY":{key:"autodismissdelay",value:5000,validator:E.isNumber},"HIDE_DELAY":{key:"hidedelay",value:250,validator:E.isNumber},"TEXT":{key:"text",suppressEvent:true},"CONTAINER":{key:"container"},"DISABLED":{key:"disabled",value:false,suppressEvent:true},"XY_OFFSET":{key:"xyoffset",value:[0,25],suppressEvent:true}},A={"CONTEXT_MOUSE_OVER":"contextMouseOver","CONTEXT_MOUSE_OUT":"contextMouseOut","CONTEXT_TRIGGER":"contextTrigger"};J.CSS_TOOLTIP="yui-tt";function K(Q,O){var P=this.cfg,R=P.getProperty("width");if(R==O){P.setProperty("width",Q);}}function D(P,O){if("_originalWidth" in this){K.call(this,this._originalWidth,this._forcedWidth);}var Q=document.body,U=this.cfg,T=U.getProperty("width"),R,S;if((!T||T=="auto")&&(U.getProperty("container")!=Q||U.getProperty("x")>=C.getViewportWidth()||U.getProperty("y")>=C.getViewportHeight())){S=this.element.cloneNode(true);S.style.visibility="hidden";S.style.top="0px";S.style.left="0px";Q.appendChild(S);R=(S.offsetWidth+"px");Q.removeChild(S);S=null;U.setProperty("width",R);U.refireEvent("xy");this._originalWidth=T||"";this._forcedWidth=R;}}function B(P,O,Q){this.render(Q);}function L(){N.onDOMReady(B,this.cfg.getProperty("container"),this);}YAHOO.extend(J,YAHOO.widget.Overlay,{init:function(P,O){J.superclass.init.call(this,P);this.beforeInitEvent.fire(J);C.addClass(this.element,J.CSS_TOOLTIP);if(O){this.cfg.applyConfig(O,true);}this.cfg.queueProperty("visible",false);this.cfg.queueProperty("constraintoviewport",true);this.setBody("");this.subscribe("changeContent",D);this.subscribe("init",L);this.subscribe("render",this.onRender);this.initEvent.fire(J);},initEvents:function(){J.superclass.initEvents.call(this);var O=M.LIST;this.contextMouseOverEvent=this.createEvent(A.CONTEXT_MOUSE_OVER);this.contextMouseOverEvent.signature=O;this.contextMouseOutEvent=this.createEvent(A.CONTEXT_MOUSE_OUT);this.contextMouseOutEvent.signature=O;this.contextTriggerEvent=this.createEvent(A.CONTEXT_TRIGGER);this.contextTriggerEvent.signature=O;},initDefaultConfig:function(){J.superclass.initDefaultConfig.call(this);this.cfg.addProperty(I.PREVENT_OVERLAP.key,{value:I.PREVENT_OVERLAP.value,validator:I.PREVENT_OVERLAP.validator,supercedes:I.PREVENT_OVERLAP.supercedes});this.cfg.addProperty(I.SHOW_DELAY.key,{handler:this.configShowDelay,value:200,validator:I.SHOW_DELAY.validator});this.cfg.addProperty(I.AUTO_DISMISS_DELAY.key,{handler:this.configAutoDismissDelay,value:I.AUTO_DISMISS_DELAY.value,validator:I.AUTO_DISMISS_DELAY.validator});this.cfg.addProperty(I.HIDE_DELAY.key,{handler:this.configHideDelay,value:I.HIDE_DELAY.value,validator:I.HIDE_DELAY.validator});this.cfg.addProperty(I.TEXT.key,{handler:this.configText,suppressEvent:I.TEXT.suppressEvent});this.cfg.addProperty(I.CONTAINER.key,{handler:this.configContainer,value:document.body});this.cfg.addProperty(I.DISABLED.key,{handler:this.configContainer,value:I.DISABLED.value,supressEvent:I.DISABLED.suppressEvent});this.cfg.addProperty(I.XY_OFFSET.key,{value:I.XY_OFFSET.value.concat(),supressEvent:I.XY_OFFSET.suppressEvent});},configText:function(P,O,Q){var R=O[0];if(R){this.setBody(R);}},configContainer:function(Q,P,R){var O=P[0];if(typeof O=="string"){this.cfg.setProperty("container",document.getElementById(O),true);}},_removeEventListeners:function(){var R=this._context,O,Q,P;if(R){O=R.length;if(O>0){P=O-1;do{Q=R[P];N.removeListener(Q,"mouseover",this.onContextMouseOver);N.removeListener(Q,"mousemove",this.onContextMouseMove);N.removeListener(Q,"mouseout",this.onContextMouseOut);}while(P--);}}},configContext:function(T,P,U){var S=P[0],V,O,R,Q;if(S){if(!(S instanceof Array)){if(typeof S=="string"){this.cfg.setProperty("context",[document.getElementById(S)],true);}else{this.cfg.setProperty("context",[S],true);}S=this.cfg.getProperty("context");}this._removeEventListeners();this._context=S;V=this._context;if(V){O=V.length;if(O>0){Q=O-1;do{R=V[Q];N.on(R,"mouseover",this.onContextMouseOver,this);N.on(R,"mousemove",this.onContextMouseMove,this);N.on(R,"mouseout",this.onContextMouseOut,this);}while(Q--);}}}},onContextMouseMove:function(P,O){O.pageX=N.getPageX(P);O.pageY=N.getPageY(P);},onContextMouseOver:function(Q,P){var O=this;if(O.title){P._tempTitle=O.title;O.title="";}if(P.fireEvent("contextMouseOver",O,Q)!==false&&!P.cfg.getProperty("disabled")){if(P.hideProcId){clearTimeout(P.hideProcId);
+P.hideProcId=null;}N.on(O,"mousemove",P.onContextMouseMove,P);P.showProcId=P.doShow(Q,O);}},onContextMouseOut:function(Q,P){var O=this;if(P._tempTitle){O.title=P._tempTitle;P._tempTitle=null;}if(P.showProcId){clearTimeout(P.showProcId);P.showProcId=null;}if(P.hideProcId){clearTimeout(P.hideProcId);P.hideProcId=null;}P.fireEvent("contextMouseOut",O,Q);P.hideProcId=setTimeout(function(){P.hide();},P.cfg.getProperty("hidedelay"));},doShow:function(R,O){var T=this.cfg.getProperty("xyoffset"),P=T[0],S=T[1],Q=this;if(H.opera&&O.tagName&&O.tagName.toUpperCase()=="A"){S+=12;}return setTimeout(function(){var U=Q.cfg.getProperty("text");if(Q._tempTitle&&(U===""||YAHOO.lang.isUndefined(U)||YAHOO.lang.isNull(U))){Q.setBody(Q._tempTitle);}else{Q.cfg.refireEvent("text");}Q.moveTo(Q.pageX+P,Q.pageY+S);if(Q.cfg.getProperty("preventoverlap")){Q.preventOverlap(Q.pageX,Q.pageY);}N.removeListener(O,"mousemove",Q.onContextMouseMove);Q.contextTriggerEvent.fire(O);Q.show();Q.hideProcId=Q.doHide();},this.cfg.getProperty("showdelay"));},doHide:function(){var O=this;return setTimeout(function(){O.hide();},this.cfg.getProperty("autodismissdelay"));},preventOverlap:function(S,R){var O=this.element.offsetHeight,Q=new YAHOO.util.Point(S,R),P=C.getRegion(this.element);P.top-=5;P.left-=5;P.right+=5;P.bottom+=5;if(P.contains(Q)){this.cfg.setProperty("y",(R-O-5));}},onRender:function(S,R){function T(){var W=this.element,V=this.underlay;if(V){V.style.width=(W.offsetWidth+6)+"px";V.style.height=(W.offsetHeight+1)+"px";}}function P(){C.addClass(this.underlay,"yui-tt-shadow-visible");if(H.ie){this.forceUnderlayRedraw();}}function O(){C.removeClass(this.underlay,"yui-tt-shadow-visible");}function U(){var X=this.underlay,W,V,Z,Y;if(!X){W=this.element;V=YAHOO.widget.Module;Z=H.ie;Y=this;if(!F){F=document.createElement("div");F.className="yui-tt-shadow";}X=F.cloneNode(false);W.appendChild(X);this.underlay=X;this._shadow=this.underlay;P.call(this);this.subscribe("beforeShow",P);this.subscribe("hide",O);if(G){window.setTimeout(function(){T.call(Y);},0);this.cfg.subscribeToConfigEvent("width",T);this.cfg.subscribeToConfigEvent("height",T);this.subscribe("changeContent",T);V.textResizeEvent.subscribe(T,this,true);this.subscribe("destroy",function(){V.textResizeEvent.unsubscribe(T,this);});}}}function Q(){U.call(this);this.unsubscribe("beforeShow",Q);}if(this.cfg.getProperty("visible")){U.call(this);}else{this.subscribe("beforeShow",Q);}},forceUnderlayRedraw:function(){var O=this;C.addClass(O.underlay,"yui-force-redraw");setTimeout(function(){C.removeClass(O.underlay,"yui-force-redraw");},0);},destroy:function(){this._removeEventListeners();J.superclass.destroy.call(this);},toString:function(){return"Tooltip "+this.id;}});}());(function(){YAHOO.widget.Panel=function(V,U){YAHOO.widget.Panel.superclass.constructor.call(this,V,U);};var S=null;var E=YAHOO.lang,F=YAHOO.util,A=F.Dom,T=F.Event,M=F.CustomEvent,K=YAHOO.util.KeyListener,I=F.Config,H=YAHOO.widget.Overlay,O=YAHOO.widget.Panel,L=YAHOO.env.ua,P=(L.ie&&(L.ie<=6||document.compatMode=="BackCompat")),G,Q,C,D={"SHOW_MASK":"showMask","HIDE_MASK":"hideMask","DRAG":"drag"},N={"CLOSE":{key:"close",value:true,validator:E.isBoolean,supercedes:["visible"]},"DRAGGABLE":{key:"draggable",value:(F.DD?true:false),validator:E.isBoolean,supercedes:["visible"]},"DRAG_ONLY":{key:"dragonly",value:false,validator:E.isBoolean,supercedes:["draggable"]},"UNDERLAY":{key:"underlay",value:"shadow",supercedes:["visible"]},"MODAL":{key:"modal",value:false,validator:E.isBoolean,supercedes:["visible","zindex"]},"KEY_LISTENERS":{key:"keylisteners",suppressEvent:true,supercedes:["visible"]},"STRINGS":{key:"strings",supercedes:["close"],validator:E.isObject,value:{close:"Close"}}};O.CSS_PANEL="yui-panel";O.CSS_PANEL_CONTAINER="yui-panel-container";O.FOCUSABLE=["a","button","select","textarea","input","iframe"];function J(V,U){if(!this.header&&this.cfg.getProperty("draggable")){this.setHeader("&#160;");}}function R(V,U,W){var Z=W[0],X=W[1],Y=this.cfg,a=Y.getProperty("width");if(a==X){Y.setProperty("width",Z);}this.unsubscribe("hide",R,W);}function B(V,U){var Y,X,W;if(P){Y=this.cfg;X=Y.getProperty("width");if(!X||X=="auto"){W=(this.element.offsetWidth+"px");Y.setProperty("width",W);this.subscribe("hide",R,[(X||""),W]);}}}YAHOO.extend(O,H,{init:function(V,U){O.superclass.init.call(this,V);this.beforeInitEvent.fire(O);A.addClass(this.element,O.CSS_PANEL);this.buildWrapper();if(U){this.cfg.applyConfig(U,true);}this.subscribe("showMask",this._addFocusHandlers);this.subscribe("hideMask",this._removeFocusHandlers);this.subscribe("beforeRender",J);this.subscribe("render",function(){this.setFirstLastFocusable();this.subscribe("changeContent",this.setFirstLastFocusable);});this.subscribe("show",this.focusFirst);this.initEvent.fire(O);},_onElementFocus:function(Z){if(S===this){var Y=T.getTarget(Z),X=document.documentElement,V=(Y!==X&&Y!==window);if(V&&Y!==this.element&&Y!==this.mask&&!A.isAncestor(this.element,Y)){try{if(this.firstElement){this.firstElement.focus();}else{if(this._modalFocus){this._modalFocus.focus();}else{this.innerElement.focus();}}}catch(W){try{if(V&&Y!==document.body){Y.blur();}}catch(U){}}}}},_addFocusHandlers:function(V,U){if(!this.firstElement){if(L.webkit||L.opera){if(!this._modalFocus){this._createHiddenFocusElement();}}else{this.innerElement.tabIndex=0;}}this.setTabLoop(this.firstElement,this.lastElement);T.onFocus(document.documentElement,this._onElementFocus,this,true);S=this;},_createHiddenFocusElement:function(){var U=document.createElement("button");U.style.height="1px";U.style.width="1px";U.style.position="absolute";U.style.left="-10000em";U.style.opacity=0;U.tabIndex=-1;this.innerElement.appendChild(U);this._modalFocus=U;},_removeFocusHandlers:function(V,U){T.removeFocusListener(document.documentElement,this._onElementFocus,this);if(S==this){S=null;}},focusFirst:function(W,U,Y){var V=this.firstElement;if(U&&U[1]){T.stopEvent(U[1]);}if(V){try{V.focus();}catch(X){}}},focusLast:function(W,U,Y){var V=this.lastElement;
+if(U&&U[1]){T.stopEvent(U[1]);}if(V){try{V.focus();}catch(X){}}},setTabLoop:function(X,Z){var V=this.preventBackTab,W=this.preventTabOut,U=this.showEvent,Y=this.hideEvent;if(V){V.disable();U.unsubscribe(V.enable,V);Y.unsubscribe(V.disable,V);V=this.preventBackTab=null;}if(W){W.disable();U.unsubscribe(W.enable,W);Y.unsubscribe(W.disable,W);W=this.preventTabOut=null;}if(X){this.preventBackTab=new K(X,{shift:true,keys:9},{fn:this.focusLast,scope:this,correctScope:true});V=this.preventBackTab;U.subscribe(V.enable,V,true);Y.subscribe(V.disable,V,true);}if(Z){this.preventTabOut=new K(Z,{shift:false,keys:9},{fn:this.focusFirst,scope:this,correctScope:true});W=this.preventTabOut;U.subscribe(W.enable,W,true);Y.subscribe(W.disable,W,true);}},getFocusableElements:function(U){U=U||this.innerElement;var X={};for(var W=0;W<O.FOCUSABLE.length;W++){X[O.FOCUSABLE[W]]=true;}function V(Y){if(Y.focus&&Y.type!=="hidden"&&!Y.disabled&&X[Y.tagName.toLowerCase()]){return true;}return false;}return A.getElementsBy(V,null,U);},setFirstLastFocusable:function(){this.firstElement=null;this.lastElement=null;var U=this.getFocusableElements();this.focusableElements=U;if(U.length>0){this.firstElement=U[0];this.lastElement=U[U.length-1];}if(this.cfg.getProperty("modal")){this.setTabLoop(this.firstElement,this.lastElement);}},initEvents:function(){O.superclass.initEvents.call(this);var U=M.LIST;this.showMaskEvent=this.createEvent(D.SHOW_MASK);this.showMaskEvent.signature=U;this.hideMaskEvent=this.createEvent(D.HIDE_MASK);this.hideMaskEvent.signature=U;this.dragEvent=this.createEvent(D.DRAG);this.dragEvent.signature=U;},initDefaultConfig:function(){O.superclass.initDefaultConfig.call(this);this.cfg.addProperty(N.CLOSE.key,{handler:this.configClose,value:N.CLOSE.value,validator:N.CLOSE.validator,supercedes:N.CLOSE.supercedes});this.cfg.addProperty(N.DRAGGABLE.key,{handler:this.configDraggable,value:(F.DD)?true:false,validator:N.DRAGGABLE.validator,supercedes:N.DRAGGABLE.supercedes});this.cfg.addProperty(N.DRAG_ONLY.key,{value:N.DRAG_ONLY.value,validator:N.DRAG_ONLY.validator,supercedes:N.DRAG_ONLY.supercedes});this.cfg.addProperty(N.UNDERLAY.key,{handler:this.configUnderlay,value:N.UNDERLAY.value,supercedes:N.UNDERLAY.supercedes});this.cfg.addProperty(N.MODAL.key,{handler:this.configModal,value:N.MODAL.value,validator:N.MODAL.validator,supercedes:N.MODAL.supercedes});this.cfg.addProperty(N.KEY_LISTENERS.key,{handler:this.configKeyListeners,suppressEvent:N.KEY_LISTENERS.suppressEvent,supercedes:N.KEY_LISTENERS.supercedes});this.cfg.addProperty(N.STRINGS.key,{value:N.STRINGS.value,handler:this.configStrings,validator:N.STRINGS.validator,supercedes:N.STRINGS.supercedes});},configClose:function(X,V,Y){var Z=V[0],W=this.close,U=this.cfg.getProperty("strings");if(Z){if(!W){if(!C){C=document.createElement("a");C.className="container-close";C.href="#";}W=C.cloneNode(true);this.innerElement.appendChild(W);W.innerHTML=(U&&U.close)?U.close:"&#160;";T.on(W,"click",this._doClose,this,true);this.close=W;}else{W.style.display="block";}}else{if(W){W.style.display="none";}}},_doClose:function(U){T.preventDefault(U);this.hide();},configDraggable:function(V,U,W){var X=U[0];if(X){if(!F.DD){this.cfg.setProperty("draggable",false);return;}if(this.header){A.setStyle(this.header,"cursor","move");this.registerDragDrop();}this.subscribe("beforeShow",B);}else{if(this.dd){this.dd.unreg();}if(this.header){A.setStyle(this.header,"cursor","auto");}this.unsubscribe("beforeShow",B);}},configUnderlay:function(d,c,Z){var b=(this.platform=="mac"&&L.gecko),e=c[0].toLowerCase(),V=this.underlay,W=this.element;function X(){var f=false;if(!V){if(!Q){Q=document.createElement("div");Q.className="underlay";}V=Q.cloneNode(false);this.element.appendChild(V);this.underlay=V;if(P){this.sizeUnderlay();this.cfg.subscribeToConfigEvent("width",this.sizeUnderlay);this.cfg.subscribeToConfigEvent("height",this.sizeUnderlay);this.changeContentEvent.subscribe(this.sizeUnderlay);YAHOO.widget.Module.textResizeEvent.subscribe(this.sizeUnderlay,this,true);}if(L.webkit&&L.webkit<420){this.changeContentEvent.subscribe(this.forceUnderlayRedraw);}f=true;}}function a(){var f=X.call(this);if(!f&&P){this.sizeUnderlay();}this._underlayDeferred=false;this.beforeShowEvent.unsubscribe(a);}function Y(){if(this._underlayDeferred){this.beforeShowEvent.unsubscribe(a);this._underlayDeferred=false;}if(V){this.cfg.unsubscribeFromConfigEvent("width",this.sizeUnderlay);this.cfg.unsubscribeFromConfigEvent("height",this.sizeUnderlay);this.changeContentEvent.unsubscribe(this.sizeUnderlay);this.changeContentEvent.unsubscribe(this.forceUnderlayRedraw);YAHOO.widget.Module.textResizeEvent.unsubscribe(this.sizeUnderlay,this,true);this.element.removeChild(V);this.underlay=null;}}switch(e){case"shadow":A.removeClass(W,"matte");A.addClass(W,"shadow");break;case"matte":if(!b){Y.call(this);}A.removeClass(W,"shadow");A.addClass(W,"matte");break;default:if(!b){Y.call(this);}A.removeClass(W,"shadow");A.removeClass(W,"matte");break;}if((e=="shadow")||(b&&!V)){if(this.cfg.getProperty("visible")){var U=X.call(this);if(!U&&P){this.sizeUnderlay();}}else{if(!this._underlayDeferred){this.beforeShowEvent.subscribe(a);this._underlayDeferred=true;}}}},configModal:function(V,U,X){var W=U[0];if(W){if(!this._hasModalityEventListeners){this.subscribe("beforeShow",this.buildMask);this.subscribe("beforeShow",this.bringToTop);this.subscribe("beforeShow",this.showMask);this.subscribe("hide",this.hideMask);H.windowResizeEvent.subscribe(this.sizeMask,this,true);this._hasModalityEventListeners=true;}}else{if(this._hasModalityEventListeners){if(this.cfg.getProperty("visible")){this.hideMask();this.removeMask();}this.unsubscribe("beforeShow",this.buildMask);this.unsubscribe("beforeShow",this.bringToTop);this.unsubscribe("beforeShow",this.showMask);this.unsubscribe("hide",this.hideMask);H.windowResizeEvent.unsubscribe(this.sizeMask,this);this._hasModalityEventListeners=false;}}},removeMask:function(){var V=this.mask,U;if(V){this.hideMask();U=V.parentNode;
+if(U){U.removeChild(V);}this.mask=null;}},configKeyListeners:function(X,U,a){var W=U[0],Z,Y,V;if(W){if(W instanceof Array){Y=W.length;for(V=0;V<Y;V++){Z=W[V];if(!I.alreadySubscribed(this.showEvent,Z.enable,Z)){this.showEvent.subscribe(Z.enable,Z,true);}if(!I.alreadySubscribed(this.hideEvent,Z.disable,Z)){this.hideEvent.subscribe(Z.disable,Z,true);this.destroyEvent.subscribe(Z.disable,Z,true);}}}else{if(!I.alreadySubscribed(this.showEvent,W.enable,W)){this.showEvent.subscribe(W.enable,W,true);}if(!I.alreadySubscribed(this.hideEvent,W.disable,W)){this.hideEvent.subscribe(W.disable,W,true);this.destroyEvent.subscribe(W.disable,W,true);}}}},configStrings:function(V,U,W){var X=E.merge(N.STRINGS.value,U[0]);this.cfg.setProperty(N.STRINGS.key,X,true);},configHeight:function(X,V,Y){var U=V[0],W=this.innerElement;A.setStyle(W,"height",U);this.cfg.refireEvent("iframe");},_autoFillOnHeightChange:function(X,V,W){O.superclass._autoFillOnHeightChange.apply(this,arguments);if(P){var U=this;setTimeout(function(){U.sizeUnderlay();},0);}},configWidth:function(X,U,Y){var W=U[0],V=this.innerElement;A.setStyle(V,"width",W);this.cfg.refireEvent("iframe");},configzIndex:function(V,U,X){O.superclass.configzIndex.call(this,V,U,X);if(this.mask||this.cfg.getProperty("modal")===true){var W=A.getStyle(this.element,"zIndex");if(!W||isNaN(W)){W=0;}if(W===0){this.cfg.setProperty("zIndex",1);}else{this.stackMask();}}},buildWrapper:function(){var W=this.element.parentNode,U=this.element,V=document.createElement("div");V.className=O.CSS_PANEL_CONTAINER;V.id=U.id+"_c";if(W){W.insertBefore(V,U);}V.appendChild(U);this.element=V;this.innerElement=U;A.setStyle(this.innerElement,"visibility","inherit");},sizeUnderlay:function(){var V=this.underlay,U;if(V){U=this.element;V.style.width=U.offsetWidth+"px";V.style.height=U.offsetHeight+"px";}},registerDragDrop:function(){var V=this;if(this.header){if(!F.DD){return;}var U=(this.cfg.getProperty("dragonly")===true);this.dd=new F.DD(this.element.id,this.id,{dragOnly:U});if(!this.header.id){this.header.id=this.id+"_h";}this.dd.startDrag=function(){var X,Z,W,c,b,a;if(YAHOO.env.ua.ie==6){A.addClass(V.element,"drag");}if(V.cfg.getProperty("constraintoviewport")){var Y=H.VIEWPORT_OFFSET;X=V.element.offsetHeight;Z=V.element.offsetWidth;W=A.getViewportWidth();c=A.getViewportHeight();b=A.getDocumentScrollLeft();a=A.getDocumentScrollTop();if(X+Y<c){this.minY=a+Y;this.maxY=a+c-X-Y;}else{this.minY=a+Y;this.maxY=a+Y;}if(Z+Y<W){this.minX=b+Y;this.maxX=b+W-Z-Y;}else{this.minX=b+Y;this.maxX=b+Y;}this.constrainX=true;this.constrainY=true;}else{this.constrainX=false;this.constrainY=false;}V.dragEvent.fire("startDrag",arguments);};this.dd.onDrag=function(){V.syncPosition();V.cfg.refireEvent("iframe");if(this.platform=="mac"&&YAHOO.env.ua.gecko){this.showMacGeckoScrollbars();}V.dragEvent.fire("onDrag",arguments);};this.dd.endDrag=function(){if(YAHOO.env.ua.ie==6){A.removeClass(V.element,"drag");}V.dragEvent.fire("endDrag",arguments);V.moveEvent.fire(V.cfg.getProperty("xy"));};this.dd.setHandleElId(this.header.id);this.dd.addInvalidHandleType("INPUT");this.dd.addInvalidHandleType("SELECT");this.dd.addInvalidHandleType("TEXTAREA");}},buildMask:function(){var U=this.mask;if(!U){if(!G){G=document.createElement("div");G.className="mask";G.innerHTML="&#160;";}U=G.cloneNode(true);U.id=this.id+"_mask";document.body.insertBefore(U,document.body.firstChild);this.mask=U;if(YAHOO.env.ua.gecko&&this.platform=="mac"){A.addClass(this.mask,"block-scrollbars");}this.stackMask();}},hideMask:function(){if(this.cfg.getProperty("modal")&&this.mask){this.mask.style.display="none";A.removeClass(document.body,"masked");this.hideMaskEvent.fire();}},showMask:function(){if(this.cfg.getProperty("modal")&&this.mask){A.addClass(document.body,"masked");this.sizeMask();this.mask.style.display="block";this.showMaskEvent.fire();}},sizeMask:function(){if(this.mask){var V=this.mask,W=A.getViewportWidth(),U=A.getViewportHeight();if(V.offsetHeight>U){V.style.height=U+"px";}if(V.offsetWidth>W){V.style.width=W+"px";}V.style.height=A.getDocumentHeight()+"px";V.style.width=A.getDocumentWidth()+"px";}},stackMask:function(){if(this.mask){var U=A.getStyle(this.element,"zIndex");if(!YAHOO.lang.isUndefined(U)&&!isNaN(U)){A.setStyle(this.mask,"zIndex",U-1);}}},render:function(U){return O.superclass.render.call(this,U,this.innerElement);},_renderHeader:function(U){U=U||this.innerElement;O.superclass._renderHeader.call(this,U);},_renderBody:function(U){U=U||this.innerElement;O.superclass._renderBody.call(this,U);},_renderFooter:function(U){U=U||this.innerElement;O.superclass._renderFooter.call(this,U);},destroy:function(){H.windowResizeEvent.unsubscribe(this.sizeMask,this);this.removeMask();if(this.close){T.purgeElement(this.close);}O.superclass.destroy.call(this);},forceUnderlayRedraw:function(){var U=this.underlay;A.addClass(U,"yui-force-redraw");setTimeout(function(){A.removeClass(U,"yui-force-redraw");},0);},toString:function(){return"Panel "+this.id;}});}());(function(){YAHOO.widget.Dialog=function(J,I){YAHOO.widget.Dialog.superclass.constructor.call(this,J,I);};var B=YAHOO.util.Event,G=YAHOO.util.CustomEvent,E=YAHOO.util.Dom,A=YAHOO.widget.Dialog,F=YAHOO.lang,H={"BEFORE_SUBMIT":"beforeSubmit","SUBMIT":"submit","MANUAL_SUBMIT":"manualSubmit","ASYNC_SUBMIT":"asyncSubmit","FORM_SUBMIT":"formSubmit","CANCEL":"cancel"},C={"POST_METHOD":{key:"postmethod",value:"async"},"POST_DATA":{key:"postdata",value:null},"BUTTONS":{key:"buttons",value:"none",supercedes:["visible"]},"HIDEAFTERSUBMIT":{key:"hideaftersubmit",value:true}};A.CSS_DIALOG="yui-dialog";function D(){var L=this._aButtons,J,K,I;if(F.isArray(L)){J=L.length;if(J>0){I=J-1;do{K=L[I];if(YAHOO.widget.Button&&K instanceof YAHOO.widget.Button){K.destroy();}else{if(K.tagName.toUpperCase()=="BUTTON"){B.purgeElement(K);B.purgeElement(K,false);}}}while(I--);}}}YAHOO.extend(A,YAHOO.widget.Panel,{form:null,initDefaultConfig:function(){A.superclass.initDefaultConfig.call(this);this.callback={success:null,failure:null,argument:null};
+this.cfg.addProperty(C.POST_METHOD.key,{handler:this.configPostMethod,value:C.POST_METHOD.value,validator:function(I){if(I!="form"&&I!="async"&&I!="none"&&I!="manual"){return false;}else{return true;}}});this.cfg.addProperty(C.POST_DATA.key,{value:C.POST_DATA.value});this.cfg.addProperty(C.HIDEAFTERSUBMIT.key,{value:C.HIDEAFTERSUBMIT.value});this.cfg.addProperty(C.BUTTONS.key,{handler:this.configButtons,value:C.BUTTONS.value,supercedes:C.BUTTONS.supercedes});},initEvents:function(){A.superclass.initEvents.call(this);var I=G.LIST;this.beforeSubmitEvent=this.createEvent(H.BEFORE_SUBMIT);this.beforeSubmitEvent.signature=I;this.submitEvent=this.createEvent(H.SUBMIT);this.submitEvent.signature=I;this.manualSubmitEvent=this.createEvent(H.MANUAL_SUBMIT);this.manualSubmitEvent.signature=I;this.asyncSubmitEvent=this.createEvent(H.ASYNC_SUBMIT);this.asyncSubmitEvent.signature=I;this.formSubmitEvent=this.createEvent(H.FORM_SUBMIT);this.formSubmitEvent.signature=I;this.cancelEvent=this.createEvent(H.CANCEL);this.cancelEvent.signature=I;},init:function(J,I){A.superclass.init.call(this,J);this.beforeInitEvent.fire(A);E.addClass(this.element,A.CSS_DIALOG);this.cfg.setProperty("visible",false);if(I){this.cfg.applyConfig(I,true);}this.showEvent.subscribe(this.focusFirst,this,true);this.beforeHideEvent.subscribe(this.blurButtons,this,true);this.subscribe("changeBody",this.registerForm);this.initEvent.fire(A);},doSubmit:function(){var P=YAHOO.util.Connect,Q=this.form,K=false,N=false,R,M,L,I;switch(this.cfg.getProperty("postmethod")){case"async":R=Q.elements;M=R.length;if(M>0){L=M-1;do{if(R[L].type=="file"){K=true;break;}}while(L--);}if(K&&YAHOO.env.ua.ie&&this.isSecure){N=true;}I=this._getFormAttributes(Q);P.setForm(Q,K,N);var J=this.cfg.getProperty("postdata");var O=P.asyncRequest(I.method,I.action,this.callback,J);this.asyncSubmitEvent.fire(O);break;case"form":Q.submit();this.formSubmitEvent.fire();break;case"none":case"manual":this.manualSubmitEvent.fire();break;}},_getFormAttributes:function(K){var I={method:null,action:null};if(K){if(K.getAttributeNode){var J=K.getAttributeNode("action");var L=K.getAttributeNode("method");if(J){I.action=J.value;}if(L){I.method=L.value;}}else{I.action=K.getAttribute("action");I.method=K.getAttribute("method");}}I.method=(F.isString(I.method)?I.method:"POST").toUpperCase();I.action=F.isString(I.action)?I.action:"";return I;},registerForm:function(){var I=this.element.getElementsByTagName("form")[0];if(this.form){if(this.form==I&&E.isAncestor(this.element,this.form)){return;}else{B.purgeElement(this.form);this.form=null;}}if(!I){I=document.createElement("form");I.name="frm_"+this.id;this.body.appendChild(I);}if(I){this.form=I;B.on(I,"submit",this._submitHandler,this,true);}},_submitHandler:function(I){B.stopEvent(I);this.submit();this.form.blur();},setTabLoop:function(I,J){I=I||this.firstButton;J=this.lastButton||J;A.superclass.setTabLoop.call(this,I,J);},setFirstLastFocusable:function(){A.superclass.setFirstLastFocusable.call(this);var J,I,K,L=this.focusableElements;this.firstFormElement=null;this.lastFormElement=null;if(this.form&&L&&L.length>0){I=L.length;for(J=0;J<I;++J){K=L[J];if(this.form===K.form){this.firstFormElement=K;break;}}for(J=I-1;J>=0;--J){K=L[J];if(this.form===K.form){this.lastFormElement=K;break;}}}},configClose:function(J,I,K){A.superclass.configClose.apply(this,arguments);},_doClose:function(I){B.preventDefault(I);this.cancel();},configButtons:function(S,R,M){var N=YAHOO.widget.Button,U=R[0],K=this.innerElement,T,P,J,Q,O,I,L;D.call(this);this._aButtons=null;if(F.isArray(U)){O=document.createElement("span");O.className="button-group";Q=U.length;this._aButtons=[];this.defaultHtmlButton=null;for(L=0;L<Q;L++){T=U[L];if(N){J=new N({label:T.text});J.appendTo(O);P=J.get("element");if(T.isDefault){J.addClass("default");this.defaultHtmlButton=P;}if(F.isFunction(T.handler)){J.set("onclick",{fn:T.handler,obj:this,scope:this});}else{if(F.isObject(T.handler)&&F.isFunction(T.handler.fn)){J.set("onclick",{fn:T.handler.fn,obj:((!F.isUndefined(T.handler.obj))?T.handler.obj:this),scope:(T.handler.scope||this)});}}this._aButtons[this._aButtons.length]=J;}else{P=document.createElement("button");P.setAttribute("type","button");if(T.isDefault){P.className="default";this.defaultHtmlButton=P;}P.innerHTML=T.text;if(F.isFunction(T.handler)){B.on(P,"click",T.handler,this,true);}else{if(F.isObject(T.handler)&&F.isFunction(T.handler.fn)){B.on(P,"click",T.handler.fn,((!F.isUndefined(T.handler.obj))?T.handler.obj:this),(T.handler.scope||this));}}O.appendChild(P);this._aButtons[this._aButtons.length]=P;}T.htmlButton=P;if(L===0){this.firstButton=P;}if(L==(Q-1)){this.lastButton=P;}}this.setFooter(O);I=this.footer;if(E.inDocument(this.element)&&!E.isAncestor(K,I)){K.appendChild(I);}this.buttonSpan=O;}else{O=this.buttonSpan;I=this.footer;if(O&&I){I.removeChild(O);this.buttonSpan=null;this.firstButton=null;this.lastButton=null;this.defaultHtmlButton=null;}}this.changeContentEvent.fire();},getButtons:function(){return this._aButtons||null;},focusFirst:function(K,I,M){var J=this.firstFormElement;if(I&&I[1]){B.stopEvent(I[1]);}if(J){try{J.focus();}catch(L){}}else{if(this.defaultHtmlButton){this.focusDefaultButton();}else{this.focusFirstButton();}}},focusLast:function(K,I,M){var N=this.cfg.getProperty("buttons"),J=this.lastFormElement;if(I&&I[1]){B.stopEvent(I[1]);}if(N&&F.isArray(N)){this.focusLastButton();}else{if(J){try{J.focus();}catch(L){}}}},_getButton:function(J){var I=YAHOO.widget.Button;if(I&&J&&J.nodeName&&J.id){J=I.getButton(J.id)||J;}return J;},focusDefaultButton:function(){var I=this._getButton(this.defaultHtmlButton);if(I){try{I.focus();}catch(J){}}},blurButtons:function(){var N=this.cfg.getProperty("buttons"),K,M,J,I;if(N&&F.isArray(N)){K=N.length;if(K>0){I=(K-1);do{M=N[I];if(M){J=this._getButton(M.htmlButton);if(J){try{J.blur();}catch(L){}}}}while(I--);}}},focusFirstButton:function(){var L=this.cfg.getProperty("buttons"),K,I;if(L&&F.isArray(L)){K=L[0];if(K){I=this._getButton(K.htmlButton);
+if(I){try{I.focus();}catch(J){}}}}},focusLastButton:function(){var M=this.cfg.getProperty("buttons"),J,L,I;if(M&&F.isArray(M)){J=M.length;if(J>0){L=M[(J-1)];if(L){I=this._getButton(L.htmlButton);if(I){try{I.focus();}catch(K){}}}}}},configPostMethod:function(J,I,K){this.registerForm();},validate:function(){return true;},submit:function(){if(this.validate()){if(this.beforeSubmitEvent.fire()){this.doSubmit();this.submitEvent.fire();if(this.cfg.getProperty("hideaftersubmit")){this.hide();}return true;}else{return false;}}else{return false;}},cancel:function(){this.cancelEvent.fire();this.hide();},getData:function(){var Y=this.form,J,R,U,L,S,P,O,I,V,K,W,Z,N,a,M,X,T;function Q(c){var b=c.tagName.toUpperCase();return((b=="INPUT"||b=="TEXTAREA"||b=="SELECT")&&c.name==L);}if(Y){J=Y.elements;R=J.length;U={};for(X=0;X<R;X++){L=J[X].name;S=E.getElementsBy(Q,"*",Y);P=S.length;if(P>0){if(P==1){S=S[0];O=S.type;I=S.tagName.toUpperCase();switch(I){case"INPUT":if(O=="checkbox"){U[L]=S.checked;}else{if(O!="radio"){U[L]=S.value;}}break;case"TEXTAREA":U[L]=S.value;break;case"SELECT":V=S.options;K=V.length;W=[];for(T=0;T<K;T++){Z=V[T];if(Z.selected){M=Z.attributes.value;W[W.length]=(M&&M.specified)?Z.value:Z.text;}}U[L]=W;break;}}else{O=S[0].type;switch(O){case"radio":for(T=0;T<P;T++){N=S[T];if(N.checked){U[L]=N.value;break;}}break;case"checkbox":W=[];for(T=0;T<P;T++){a=S[T];if(a.checked){W[W.length]=a.value;}}U[L]=W;break;}}}}}return U;},destroy:function(){D.call(this);this._aButtons=null;var I=this.element.getElementsByTagName("form"),J;if(I.length>0){J=I[0];if(J){B.purgeElement(J);if(J.parentNode){J.parentNode.removeChild(J);}this.form=null;}}A.superclass.destroy.call(this);},toString:function(){return"Dialog "+this.id;}});}());(function(){YAHOO.widget.SimpleDialog=function(E,D){YAHOO.widget.SimpleDialog.superclass.constructor.call(this,E,D);};var C=YAHOO.util.Dom,B=YAHOO.widget.SimpleDialog,A={"ICON":{key:"icon",value:"none",suppressEvent:true},"TEXT":{key:"text",value:"",suppressEvent:true,supercedes:["icon"]}};B.ICON_BLOCK="blckicon";B.ICON_ALARM="alrticon";B.ICON_HELP="hlpicon";B.ICON_INFO="infoicon";B.ICON_WARN="warnicon";B.ICON_TIP="tipicon";B.ICON_CSS_CLASSNAME="yui-icon";B.CSS_SIMPLEDIALOG="yui-simple-dialog";YAHOO.extend(B,YAHOO.widget.Dialog,{initDefaultConfig:function(){B.superclass.initDefaultConfig.call(this);this.cfg.addProperty(A.ICON.key,{handler:this.configIcon,value:A.ICON.value,suppressEvent:A.ICON.suppressEvent});this.cfg.addProperty(A.TEXT.key,{handler:this.configText,value:A.TEXT.value,suppressEvent:A.TEXT.suppressEvent,supercedes:A.TEXT.supercedes});},init:function(E,D){B.superclass.init.call(this,E);this.beforeInitEvent.fire(B);C.addClass(this.element,B.CSS_SIMPLEDIALOG);this.cfg.queueProperty("postmethod","manual");if(D){this.cfg.applyConfig(D,true);}this.beforeRenderEvent.subscribe(function(){if(!this.body){this.setBody("");}},this,true);this.initEvent.fire(B);},registerForm:function(){B.superclass.registerForm.call(this);this.form.innerHTML+='<input type="hidden" name="'+this.id+'" value=""/>';},configIcon:function(K,J,H){var D=J[0],E=this.body,F=B.ICON_CSS_CLASSNAME,L,I,G;if(D&&D!="none"){L=C.getElementsByClassName(F,"*",E);if(L.length===1){I=L[0];G=I.parentNode;if(G){G.removeChild(I);I=null;}}if(D.indexOf(".")==-1){I=document.createElement("span");I.className=(F+" "+D);I.innerHTML="&#160;";}else{I=document.createElement("img");I.src=(this.imageRoot+D);I.className=F;}if(I){E.insertBefore(I,E.firstChild);}}},configText:function(E,D,F){var G=D[0];if(G){this.setBody(G);this.cfg.refireEvent("icon");}},toString:function(){return"SimpleDialog "+this.id;}});}());(function(){YAHOO.widget.ContainerEffect=function(E,H,G,D,F){if(!F){F=YAHOO.util.Anim;}this.overlay=E;this.attrIn=H;this.attrOut=G;this.targetElement=D||E.element;this.animClass=F;};var B=YAHOO.util.Dom,C=YAHOO.util.CustomEvent,A=YAHOO.widget.ContainerEffect;A.FADE=function(D,F){var G=YAHOO.util.Easing,I={attributes:{opacity:{from:0,to:1}},duration:F,method:G.easeIn},E={attributes:{opacity:{to:0}},duration:F,method:G.easeOut},H=new A(D,I,E,D.element);H.handleUnderlayStart=function(){var K=this.overlay.underlay;if(K&&YAHOO.env.ua.ie){var J=(K.filters&&K.filters.length>0);if(J){B.addClass(D.element,"yui-effect-fade");}}};H.handleUnderlayComplete=function(){var J=this.overlay.underlay;if(J&&YAHOO.env.ua.ie){B.removeClass(D.element,"yui-effect-fade");}};H.handleStartAnimateIn=function(K,J,L){B.addClass(L.overlay.element,"hide-select");if(!L.overlay.underlay){L.overlay.cfg.refireEvent("underlay");}L.handleUnderlayStart();L.overlay._setDomVisibility(true);B.setStyle(L.overlay.element,"opacity",0);};H.handleCompleteAnimateIn=function(K,J,L){B.removeClass(L.overlay.element,"hide-select");if(L.overlay.element.style.filter){L.overlay.element.style.filter=null;}L.handleUnderlayComplete();L.overlay.cfg.refireEvent("iframe");L.animateInCompleteEvent.fire();};H.handleStartAnimateOut=function(K,J,L){B.addClass(L.overlay.element,"hide-select");L.handleUnderlayStart();};H.handleCompleteAnimateOut=function(K,J,L){B.removeClass(L.overlay.element,"hide-select");if(L.overlay.element.style.filter){L.overlay.element.style.filter=null;}L.overlay._setDomVisibility(false);B.setStyle(L.overlay.element,"opacity",1);L.handleUnderlayComplete();L.overlay.cfg.refireEvent("iframe");L.animateOutCompleteEvent.fire();};H.init();return H;};A.SLIDE=function(F,D){var I=YAHOO.util.Easing,L=F.cfg.getProperty("x")||B.getX(F.element),K=F.cfg.getProperty("y")||B.getY(F.element),M=B.getClientWidth(),H=F.element.offsetWidth,J={attributes:{points:{to:[L,K]}},duration:D,method:I.easeIn},E={attributes:{points:{to:[(M+25),K]}},duration:D,method:I.easeOut},G=new A(F,J,E,F.element,YAHOO.util.Motion);G.handleStartAnimateIn=function(O,N,P){P.overlay.element.style.left=((-25)-H)+"px";P.overlay.element.style.top=K+"px";};G.handleTweenAnimateIn=function(Q,P,R){var S=B.getXY(R.overlay.element),O=S[0],N=S[1];if(B.getStyle(R.overlay.element,"visibility")=="hidden"&&O<L){R.overlay._setDomVisibility(true);
+}R.overlay.cfg.setProperty("xy",[O,N],true);R.overlay.cfg.refireEvent("iframe");};G.handleCompleteAnimateIn=function(O,N,P){P.overlay.cfg.setProperty("xy",[L,K],true);P.startX=L;P.startY=K;P.overlay.cfg.refireEvent("iframe");P.animateInCompleteEvent.fire();};G.handleStartAnimateOut=function(O,N,R){var P=B.getViewportWidth(),S=B.getXY(R.overlay.element),Q=S[1];R.animOut.attributes.points.to=[(P+25),Q];};G.handleTweenAnimateOut=function(P,O,Q){var S=B.getXY(Q.overlay.element),N=S[0],R=S[1];Q.overlay.cfg.setProperty("xy",[N,R],true);Q.overlay.cfg.refireEvent("iframe");};G.handleCompleteAnimateOut=function(O,N,P){P.overlay._setDomVisibility(false);P.overlay.cfg.setProperty("xy",[L,K]);P.animateOutCompleteEvent.fire();};G.init();return G;};A.prototype={init:function(){this.beforeAnimateInEvent=this.createEvent("beforeAnimateIn");this.beforeAnimateInEvent.signature=C.LIST;this.beforeAnimateOutEvent=this.createEvent("beforeAnimateOut");this.beforeAnimateOutEvent.signature=C.LIST;this.animateInCompleteEvent=this.createEvent("animateInComplete");this.animateInCompleteEvent.signature=C.LIST;this.animateOutCompleteEvent=this.createEvent("animateOutComplete");this.animateOutCompleteEvent.signature=C.LIST;this.animIn=new this.animClass(this.targetElement,this.attrIn.attributes,this.attrIn.duration,this.attrIn.method);this.animIn.onStart.subscribe(this.handleStartAnimateIn,this);this.animIn.onTween.subscribe(this.handleTweenAnimateIn,this);this.animIn.onComplete.subscribe(this.handleCompleteAnimateIn,this);this.animOut=new this.animClass(this.targetElement,this.attrOut.attributes,this.attrOut.duration,this.attrOut.method);this.animOut.onStart.subscribe(this.handleStartAnimateOut,this);this.animOut.onTween.subscribe(this.handleTweenAnimateOut,this);this.animOut.onComplete.subscribe(this.handleCompleteAnimateOut,this);},animateIn:function(){this.beforeAnimateInEvent.fire();this.animIn.animate();},animateOut:function(){this.beforeAnimateOutEvent.fire();this.animOut.animate();},handleStartAnimateIn:function(E,D,F){},handleTweenAnimateIn:function(E,D,F){},handleCompleteAnimateIn:function(E,D,F){},handleStartAnimateOut:function(E,D,F){},handleTweenAnimateOut:function(E,D,F){},handleCompleteAnimateOut:function(E,D,F){},toString:function(){var D="ContainerEffect";if(this.overlay){D+=" ["+this.overlay.toString()+"]";}return D;}};YAHOO.lang.augmentProto(A,YAHOO.util.EventProvider);})();YAHOO.register("container",YAHOO.widget.Module,{version:"2.8.2r1",build:"7"}); \ No newline at end of file
diff --git a/js/yui/container/container_core-min.js b/js/yui/container/container_core-min.js
new file mode 100644
index 000000000..e4dc4aea5
--- /dev/null
+++ b/js/yui/container/container_core-min.js
@@ -0,0 +1,14 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+(function(){YAHOO.util.Config=function(D){if(D){this.init(D);}};var B=YAHOO.lang,C=YAHOO.util.CustomEvent,A=YAHOO.util.Config;A.CONFIG_CHANGED_EVENT="configChanged";A.BOOLEAN_TYPE="boolean";A.prototype={owner:null,queueInProgress:false,config:null,initialConfig:null,eventQueue:null,configChangedEvent:null,init:function(D){this.owner=D;this.configChangedEvent=this.createEvent(A.CONFIG_CHANGED_EVENT);this.configChangedEvent.signature=C.LIST;this.queueInProgress=false;this.config={};this.initialConfig={};this.eventQueue=[];},checkBoolean:function(D){return(typeof D==A.BOOLEAN_TYPE);},checkNumber:function(D){return(!isNaN(D));},fireEvent:function(D,F){var E=this.config[D];if(E&&E.event){E.event.fire(F);}},addProperty:function(E,D){E=E.toLowerCase();this.config[E]=D;D.event=this.createEvent(E,{scope:this.owner});D.event.signature=C.LIST;D.key=E;if(D.handler){D.event.subscribe(D.handler,this.owner);}this.setProperty(E,D.value,true);if(!D.suppressEvent){this.queueProperty(E,D.value);}},getConfig:function(){var D={},F=this.config,G,E;for(G in F){if(B.hasOwnProperty(F,G)){E=F[G];if(E&&E.event){D[G]=E.value;}}}return D;},getProperty:function(D){var E=this.config[D.toLowerCase()];if(E&&E.event){return E.value;}else{return undefined;}},resetProperty:function(D){D=D.toLowerCase();var E=this.config[D];if(E&&E.event){if(this.initialConfig[D]&&!B.isUndefined(this.initialConfig[D])){this.setProperty(D,this.initialConfig[D]);return true;}}else{return false;}},setProperty:function(E,G,D){var F;E=E.toLowerCase();if(this.queueInProgress&&!D){this.queueProperty(E,G);return true;}else{F=this.config[E];if(F&&F.event){if(F.validator&&!F.validator(G)){return false;}else{F.value=G;if(!D){this.fireEvent(E,G);this.configChangedEvent.fire([E,G]);}return true;}}else{return false;}}},queueProperty:function(S,P){S=S.toLowerCase();var R=this.config[S],K=false,J,G,H,I,O,Q,F,M,N,D,L,T,E;if(R&&R.event){if(!B.isUndefined(P)&&R.validator&&!R.validator(P)){return false;}else{if(!B.isUndefined(P)){R.value=P;}else{P=R.value;}K=false;J=this.eventQueue.length;for(L=0;L<J;L++){G=this.eventQueue[L];if(G){H=G[0];I=G[1];if(H==S){this.eventQueue[L]=null;this.eventQueue.push([S,(!B.isUndefined(P)?P:I)]);K=true;break;}}}if(!K&&!B.isUndefined(P)){this.eventQueue.push([S,P]);}}if(R.supercedes){O=R.supercedes.length;for(T=0;T<O;T++){Q=R.supercedes[T];F=this.eventQueue.length;for(E=0;E<F;E++){M=this.eventQueue[E];if(M){N=M[0];D=M[1];if(N==Q.toLowerCase()){this.eventQueue.push([N,D]);this.eventQueue[E]=null;break;}}}}}return true;}else{return false;}},refireEvent:function(D){D=D.toLowerCase();var E=this.config[D];if(E&&E.event&&!B.isUndefined(E.value)){if(this.queueInProgress){this.queueProperty(D);}else{this.fireEvent(D,E.value);}}},applyConfig:function(D,G){var F,E;if(G){E={};for(F in D){if(B.hasOwnProperty(D,F)){E[F.toLowerCase()]=D[F];}}this.initialConfig=E;}for(F in D){if(B.hasOwnProperty(D,F)){this.queueProperty(F,D[F]);}}},refresh:function(){var D;for(D in this.config){if(B.hasOwnProperty(this.config,D)){this.refireEvent(D);}}},fireQueue:function(){var E,H,D,G,F;this.queueInProgress=true;for(E=0;E<this.eventQueue.length;E++){H=this.eventQueue[E];if(H){D=H[0];G=H[1];F=this.config[D];F.value=G;this.eventQueue[E]=null;this.fireEvent(D,G);}}this.queueInProgress=false;this.eventQueue=[];},subscribeToConfigEvent:function(D,E,G,H){var F=this.config[D.toLowerCase()];if(F&&F.event){if(!A.alreadySubscribed(F.event,E,G)){F.event.subscribe(E,G,H);}return true;}else{return false;}},unsubscribeFromConfigEvent:function(D,E,G){var F=this.config[D.toLowerCase()];if(F&&F.event){return F.event.unsubscribe(E,G);}else{return false;}},toString:function(){var D="Config";if(this.owner){D+=" ["+this.owner.toString()+"]";}return D;},outputEventQueue:function(){var D="",G,E,F=this.eventQueue.length;for(E=0;E<F;E++){G=this.eventQueue[E];if(G){D+=G[0]+"="+G[1]+", ";}}return D;},destroy:function(){var E=this.config,D,F;for(D in E){if(B.hasOwnProperty(E,D)){F=E[D];F.event.unsubscribeAll();F.event=null;}}this.configChangedEvent.unsubscribeAll();this.configChangedEvent=null;this.owner=null;this.config=null;this.initialConfig=null;this.eventQueue=null;}};A.alreadySubscribed=function(E,H,I){var F=E.subscribers.length,D,G;if(F>0){G=F-1;do{D=E.subscribers[G];if(D&&D.obj==I&&D.fn==H){return true;}}while(G--);}return false;};YAHOO.lang.augmentProto(A,YAHOO.util.EventProvider);}());(function(){YAHOO.widget.Module=function(R,Q){if(R){this.init(R,Q);}else{}};var F=YAHOO.util.Dom,D=YAHOO.util.Config,N=YAHOO.util.Event,M=YAHOO.util.CustomEvent,G=YAHOO.widget.Module,I=YAHOO.env.ua,H,P,O,E,A={"BEFORE_INIT":"beforeInit","INIT":"init","APPEND":"append","BEFORE_RENDER":"beforeRender","RENDER":"render","CHANGE_HEADER":"changeHeader","CHANGE_BODY":"changeBody","CHANGE_FOOTER":"changeFooter","CHANGE_CONTENT":"changeContent","DESTROY":"destroy","BEFORE_SHOW":"beforeShow","SHOW":"show","BEFORE_HIDE":"beforeHide","HIDE":"hide"},J={"VISIBLE":{key:"visible",value:true,validator:YAHOO.lang.isBoolean},"EFFECT":{key:"effect",suppressEvent:true,supercedes:["visible"]},"MONITOR_RESIZE":{key:"monitorresize",value:true},"APPEND_TO_DOCUMENT_BODY":{key:"appendtodocumentbody",value:false}};G.IMG_ROOT=null;G.IMG_ROOT_SSL=null;G.CSS_MODULE="yui-module";G.CSS_HEADER="hd";G.CSS_BODY="bd";G.CSS_FOOTER="ft";G.RESIZE_MONITOR_SECURE_URL="javascript:false;";G.RESIZE_MONITOR_BUFFER=1;G.textResizeEvent=new M("textResize");G.forceDocumentRedraw=function(){var Q=document.documentElement;if(Q){Q.className+=" ";Q.className=YAHOO.lang.trim(Q.className);}};function L(){if(!H){H=document.createElement("div");H.innerHTML=('<div class="'+G.CSS_HEADER+'"></div>'+'<div class="'+G.CSS_BODY+'"></div><div class="'+G.CSS_FOOTER+'"></div>');P=H.firstChild;O=P.nextSibling;E=O.nextSibling;}return H;}function K(){if(!P){L();}return(P.cloneNode(false));}function B(){if(!O){L();}return(O.cloneNode(false));}function C(){if(!E){L();}return(E.cloneNode(false));}G.prototype={constructor:G,element:null,header:null,body:null,footer:null,id:null,imageRoot:G.IMG_ROOT,initEvents:function(){var Q=M.LIST;
+this.beforeInitEvent=this.createEvent(A.BEFORE_INIT);this.beforeInitEvent.signature=Q;this.initEvent=this.createEvent(A.INIT);this.initEvent.signature=Q;this.appendEvent=this.createEvent(A.APPEND);this.appendEvent.signature=Q;this.beforeRenderEvent=this.createEvent(A.BEFORE_RENDER);this.beforeRenderEvent.signature=Q;this.renderEvent=this.createEvent(A.RENDER);this.renderEvent.signature=Q;this.changeHeaderEvent=this.createEvent(A.CHANGE_HEADER);this.changeHeaderEvent.signature=Q;this.changeBodyEvent=this.createEvent(A.CHANGE_BODY);this.changeBodyEvent.signature=Q;this.changeFooterEvent=this.createEvent(A.CHANGE_FOOTER);this.changeFooterEvent.signature=Q;this.changeContentEvent=this.createEvent(A.CHANGE_CONTENT);this.changeContentEvent.signature=Q;this.destroyEvent=this.createEvent(A.DESTROY);this.destroyEvent.signature=Q;this.beforeShowEvent=this.createEvent(A.BEFORE_SHOW);this.beforeShowEvent.signature=Q;this.showEvent=this.createEvent(A.SHOW);this.showEvent.signature=Q;this.beforeHideEvent=this.createEvent(A.BEFORE_HIDE);this.beforeHideEvent.signature=Q;this.hideEvent=this.createEvent(A.HIDE);this.hideEvent.signature=Q;},platform:function(){var Q=navigator.userAgent.toLowerCase();if(Q.indexOf("windows")!=-1||Q.indexOf("win32")!=-1){return"windows";}else{if(Q.indexOf("macintosh")!=-1){return"mac";}else{return false;}}}(),browser:function(){var Q=navigator.userAgent.toLowerCase();if(Q.indexOf("opera")!=-1){return"opera";}else{if(Q.indexOf("msie 7")!=-1){return"ie7";}else{if(Q.indexOf("msie")!=-1){return"ie";}else{if(Q.indexOf("safari")!=-1){return"safari";}else{if(Q.indexOf("gecko")!=-1){return"gecko";}else{return false;}}}}}}(),isSecure:function(){if(window.location.href.toLowerCase().indexOf("https")===0){return true;}else{return false;}}(),initDefaultConfig:function(){this.cfg.addProperty(J.VISIBLE.key,{handler:this.configVisible,value:J.VISIBLE.value,validator:J.VISIBLE.validator});this.cfg.addProperty(J.EFFECT.key,{suppressEvent:J.EFFECT.suppressEvent,supercedes:J.EFFECT.supercedes});this.cfg.addProperty(J.MONITOR_RESIZE.key,{handler:this.configMonitorResize,value:J.MONITOR_RESIZE.value});this.cfg.addProperty(J.APPEND_TO_DOCUMENT_BODY.key,{value:J.APPEND_TO_DOCUMENT_BODY.value});},init:function(V,U){var S,W;this.initEvents();this.beforeInitEvent.fire(G);this.cfg=new D(this);if(this.isSecure){this.imageRoot=G.IMG_ROOT_SSL;}if(typeof V=="string"){S=V;V=document.getElementById(V);if(!V){V=(L()).cloneNode(false);V.id=S;}}this.id=F.generateId(V);this.element=V;W=this.element.firstChild;if(W){var R=false,Q=false,T=false;do{if(1==W.nodeType){if(!R&&F.hasClass(W,G.CSS_HEADER)){this.header=W;R=true;}else{if(!Q&&F.hasClass(W,G.CSS_BODY)){this.body=W;Q=true;}else{if(!T&&F.hasClass(W,G.CSS_FOOTER)){this.footer=W;T=true;}}}}}while((W=W.nextSibling));}this.initDefaultConfig();F.addClass(this.element,G.CSS_MODULE);if(U){this.cfg.applyConfig(U,true);}if(!D.alreadySubscribed(this.renderEvent,this.cfg.fireQueue,this.cfg)){this.renderEvent.subscribe(this.cfg.fireQueue,this.cfg,true);}this.initEvent.fire(G);},initResizeMonitor:function(){var R=(I.gecko&&this.platform=="windows");if(R){var Q=this;setTimeout(function(){Q._initResizeMonitor();},0);}else{this._initResizeMonitor();}},_initResizeMonitor:function(){var Q,S,U;function W(){G.textResizeEvent.fire();}if(!I.opera){S=F.get("_yuiResizeMonitor");var V=this._supportsCWResize();if(!S){S=document.createElement("iframe");if(this.isSecure&&G.RESIZE_MONITOR_SECURE_URL&&I.ie){S.src=G.RESIZE_MONITOR_SECURE_URL;}if(!V){U=["<html><head><script ",'type="text/javascript">',"window.onresize=function(){window.parent.","YAHOO.widget.Module.textResizeEvent.","fire();};<","/script></head>","<body></body></html>"].join("");S.src="data:text/html;charset=utf-8,"+encodeURIComponent(U);}S.id="_yuiResizeMonitor";S.title="Text Resize Monitor";S.style.position="absolute";S.style.visibility="hidden";var R=document.body,T=R.firstChild;if(T){R.insertBefore(S,T);}else{R.appendChild(S);}S.style.backgroundColor="transparent";S.style.borderWidth="0";S.style.width="2em";S.style.height="2em";S.style.left="0";S.style.top=(-1*(S.offsetHeight+G.RESIZE_MONITOR_BUFFER))+"px";S.style.visibility="visible";if(I.webkit){Q=S.contentWindow.document;Q.open();Q.close();}}if(S&&S.contentWindow){G.textResizeEvent.subscribe(this.onDomResize,this,true);if(!G.textResizeInitialized){if(V){if(!N.on(S.contentWindow,"resize",W)){N.on(S,"resize",W);}}G.textResizeInitialized=true;}this.resizeMonitor=S;}}},_supportsCWResize:function(){var Q=true;if(I.gecko&&I.gecko<=1.8){Q=false;}return Q;},onDomResize:function(S,R){var Q=-1*(this.resizeMonitor.offsetHeight+G.RESIZE_MONITOR_BUFFER);this.resizeMonitor.style.top=Q+"px";this.resizeMonitor.style.left="0";},setHeader:function(R){var Q=this.header||(this.header=K());if(R.nodeName){Q.innerHTML="";Q.appendChild(R);}else{Q.innerHTML=R;}if(this._rendered){this._renderHeader();}this.changeHeaderEvent.fire(R);this.changeContentEvent.fire();},appendToHeader:function(R){var Q=this.header||(this.header=K());Q.appendChild(R);this.changeHeaderEvent.fire(R);this.changeContentEvent.fire();},setBody:function(R){var Q=this.body||(this.body=B());if(R.nodeName){Q.innerHTML="";Q.appendChild(R);}else{Q.innerHTML=R;}if(this._rendered){this._renderBody();}this.changeBodyEvent.fire(R);this.changeContentEvent.fire();},appendToBody:function(R){var Q=this.body||(this.body=B());Q.appendChild(R);this.changeBodyEvent.fire(R);this.changeContentEvent.fire();},setFooter:function(R){var Q=this.footer||(this.footer=C());if(R.nodeName){Q.innerHTML="";Q.appendChild(R);}else{Q.innerHTML=R;}if(this._rendered){this._renderFooter();}this.changeFooterEvent.fire(R);this.changeContentEvent.fire();},appendToFooter:function(R){var Q=this.footer||(this.footer=C());Q.appendChild(R);this.changeFooterEvent.fire(R);this.changeContentEvent.fire();},render:function(S,Q){var T=this;function R(U){if(typeof U=="string"){U=document.getElementById(U);}if(U){T._addToParent(U,T.element);T.appendEvent.fire();}}this.beforeRenderEvent.fire();
+if(!Q){Q=this.element;}if(S){R(S);}else{if(!F.inDocument(this.element)){return false;}}this._renderHeader(Q);this._renderBody(Q);this._renderFooter(Q);this._rendered=true;this.renderEvent.fire();return true;},_renderHeader:function(Q){Q=Q||this.element;if(this.header&&!F.inDocument(this.header)){var R=Q.firstChild;if(R){Q.insertBefore(this.header,R);}else{Q.appendChild(this.header);}}},_renderBody:function(Q){Q=Q||this.element;if(this.body&&!F.inDocument(this.body)){if(this.footer&&F.isAncestor(Q,this.footer)){Q.insertBefore(this.body,this.footer);}else{Q.appendChild(this.body);}}},_renderFooter:function(Q){Q=Q||this.element;if(this.footer&&!F.inDocument(this.footer)){Q.appendChild(this.footer);}},destroy:function(){var Q;if(this.element){N.purgeElement(this.element,true);Q=this.element.parentNode;}if(Q){Q.removeChild(this.element);}this.element=null;this.header=null;this.body=null;this.footer=null;G.textResizeEvent.unsubscribe(this.onDomResize,this);this.cfg.destroy();this.cfg=null;this.destroyEvent.fire();},show:function(){this.cfg.setProperty("visible",true);},hide:function(){this.cfg.setProperty("visible",false);},configVisible:function(R,Q,S){var T=Q[0];if(T){this.beforeShowEvent.fire();F.setStyle(this.element,"display","block");this.showEvent.fire();}else{this.beforeHideEvent.fire();F.setStyle(this.element,"display","none");this.hideEvent.fire();}},configMonitorResize:function(S,R,T){var Q=R[0];if(Q){this.initResizeMonitor();}else{G.textResizeEvent.unsubscribe(this.onDomResize,this,true);this.resizeMonitor=null;}},_addToParent:function(Q,R){if(!this.cfg.getProperty("appendtodocumentbody")&&Q===document.body&&Q.firstChild){Q.insertBefore(R,Q.firstChild);}else{Q.appendChild(R);}},toString:function(){return"Module "+this.id;}};YAHOO.lang.augmentProto(G,YAHOO.util.EventProvider);}());(function(){YAHOO.widget.Overlay=function(P,O){YAHOO.widget.Overlay.superclass.constructor.call(this,P,O);};var I=YAHOO.lang,M=YAHOO.util.CustomEvent,G=YAHOO.widget.Module,N=YAHOO.util.Event,F=YAHOO.util.Dom,D=YAHOO.util.Config,K=YAHOO.env.ua,B=YAHOO.widget.Overlay,H="subscribe",E="unsubscribe",C="contained",J,A={"BEFORE_MOVE":"beforeMove","MOVE":"move"},L={"X":{key:"x",validator:I.isNumber,suppressEvent:true,supercedes:["iframe"]},"Y":{key:"y",validator:I.isNumber,suppressEvent:true,supercedes:["iframe"]},"XY":{key:"xy",suppressEvent:true,supercedes:["iframe"]},"CONTEXT":{key:"context",suppressEvent:true,supercedes:["iframe"]},"FIXED_CENTER":{key:"fixedcenter",value:false,supercedes:["iframe","visible"]},"WIDTH":{key:"width",suppressEvent:true,supercedes:["context","fixedcenter","iframe"]},"HEIGHT":{key:"height",suppressEvent:true,supercedes:["context","fixedcenter","iframe"]},"AUTO_FILL_HEIGHT":{key:"autofillheight",supercedes:["height"],value:"body"},"ZINDEX":{key:"zindex",value:null},"CONSTRAIN_TO_VIEWPORT":{key:"constraintoviewport",value:false,validator:I.isBoolean,supercedes:["iframe","x","y","xy"]},"IFRAME":{key:"iframe",value:(K.ie==6?true:false),validator:I.isBoolean,supercedes:["zindex"]},"PREVENT_CONTEXT_OVERLAP":{key:"preventcontextoverlap",value:false,validator:I.isBoolean,supercedes:["constraintoviewport"]}};B.IFRAME_SRC="javascript:false;";B.IFRAME_OFFSET=3;B.VIEWPORT_OFFSET=10;B.TOP_LEFT="tl";B.TOP_RIGHT="tr";B.BOTTOM_LEFT="bl";B.BOTTOM_RIGHT="br";B.PREVENT_OVERLAP_X={"tltr":true,"blbr":true,"brbl":true,"trtl":true};B.PREVENT_OVERLAP_Y={"trbr":true,"tlbl":true,"bltl":true,"brtr":true};B.CSS_OVERLAY="yui-overlay";B.CSS_HIDDEN="yui-overlay-hidden";B.CSS_IFRAME="yui-overlay-iframe";B.STD_MOD_RE=/^\s*?(body|footer|header)\s*?$/i;B.windowScrollEvent=new M("windowScroll");B.windowResizeEvent=new M("windowResize");B.windowScrollHandler=function(P){var O=N.getTarget(P);if(!O||O===window||O===window.document){if(K.ie){if(!window.scrollEnd){window.scrollEnd=-1;}clearTimeout(window.scrollEnd);window.scrollEnd=setTimeout(function(){B.windowScrollEvent.fire();},1);}else{B.windowScrollEvent.fire();}}};B.windowResizeHandler=function(O){if(K.ie){if(!window.resizeEnd){window.resizeEnd=-1;}clearTimeout(window.resizeEnd);window.resizeEnd=setTimeout(function(){B.windowResizeEvent.fire();},100);}else{B.windowResizeEvent.fire();}};B._initialized=null;if(B._initialized===null){N.on(window,"scroll",B.windowScrollHandler);N.on(window,"resize",B.windowResizeHandler);B._initialized=true;}B._TRIGGER_MAP={"windowScroll":B.windowScrollEvent,"windowResize":B.windowResizeEvent,"textResize":G.textResizeEvent};YAHOO.extend(B,G,{CONTEXT_TRIGGERS:[],init:function(P,O){B.superclass.init.call(this,P);this.beforeInitEvent.fire(B);F.addClass(this.element,B.CSS_OVERLAY);if(O){this.cfg.applyConfig(O,true);}if(this.platform=="mac"&&K.gecko){if(!D.alreadySubscribed(this.showEvent,this.showMacGeckoScrollbars,this)){this.showEvent.subscribe(this.showMacGeckoScrollbars,this,true);}if(!D.alreadySubscribed(this.hideEvent,this.hideMacGeckoScrollbars,this)){this.hideEvent.subscribe(this.hideMacGeckoScrollbars,this,true);}}this.initEvent.fire(B);},initEvents:function(){B.superclass.initEvents.call(this);var O=M.LIST;this.beforeMoveEvent=this.createEvent(A.BEFORE_MOVE);this.beforeMoveEvent.signature=O;this.moveEvent=this.createEvent(A.MOVE);this.moveEvent.signature=O;},initDefaultConfig:function(){B.superclass.initDefaultConfig.call(this);var O=this.cfg;O.addProperty(L.X.key,{handler:this.configX,validator:L.X.validator,suppressEvent:L.X.suppressEvent,supercedes:L.X.supercedes});O.addProperty(L.Y.key,{handler:this.configY,validator:L.Y.validator,suppressEvent:L.Y.suppressEvent,supercedes:L.Y.supercedes});O.addProperty(L.XY.key,{handler:this.configXY,suppressEvent:L.XY.suppressEvent,supercedes:L.XY.supercedes});O.addProperty(L.CONTEXT.key,{handler:this.configContext,suppressEvent:L.CONTEXT.suppressEvent,supercedes:L.CONTEXT.supercedes});O.addProperty(L.FIXED_CENTER.key,{handler:this.configFixedCenter,value:L.FIXED_CENTER.value,validator:L.FIXED_CENTER.validator,supercedes:L.FIXED_CENTER.supercedes});O.addProperty(L.WIDTH.key,{handler:this.configWidth,suppressEvent:L.WIDTH.suppressEvent,supercedes:L.WIDTH.supercedes});
+O.addProperty(L.HEIGHT.key,{handler:this.configHeight,suppressEvent:L.HEIGHT.suppressEvent,supercedes:L.HEIGHT.supercedes});O.addProperty(L.AUTO_FILL_HEIGHT.key,{handler:this.configAutoFillHeight,value:L.AUTO_FILL_HEIGHT.value,validator:this._validateAutoFill,supercedes:L.AUTO_FILL_HEIGHT.supercedes});O.addProperty(L.ZINDEX.key,{handler:this.configzIndex,value:L.ZINDEX.value});O.addProperty(L.CONSTRAIN_TO_VIEWPORT.key,{handler:this.configConstrainToViewport,value:L.CONSTRAIN_TO_VIEWPORT.value,validator:L.CONSTRAIN_TO_VIEWPORT.validator,supercedes:L.CONSTRAIN_TO_VIEWPORT.supercedes});O.addProperty(L.IFRAME.key,{handler:this.configIframe,value:L.IFRAME.value,validator:L.IFRAME.validator,supercedes:L.IFRAME.supercedes});O.addProperty(L.PREVENT_CONTEXT_OVERLAP.key,{value:L.PREVENT_CONTEXT_OVERLAP.value,validator:L.PREVENT_CONTEXT_OVERLAP.validator,supercedes:L.PREVENT_CONTEXT_OVERLAP.supercedes});},moveTo:function(O,P){this.cfg.setProperty("xy",[O,P]);},hideMacGeckoScrollbars:function(){F.replaceClass(this.element,"show-scrollbars","hide-scrollbars");},showMacGeckoScrollbars:function(){F.replaceClass(this.element,"hide-scrollbars","show-scrollbars");},_setDomVisibility:function(O){F.setStyle(this.element,"visibility",(O)?"visible":"hidden");var P=B.CSS_HIDDEN;if(O){F.removeClass(this.element,P);}else{F.addClass(this.element,P);}},configVisible:function(R,O,X){var Q=O[0],S=F.getStyle(this.element,"visibility"),Y=this.cfg.getProperty("effect"),V=[],U=(this.platform=="mac"&&K.gecko),g=D.alreadySubscribed,W,P,f,c,b,a,d,Z,T;if(S=="inherit"){f=this.element.parentNode;while(f.nodeType!=9&&f.nodeType!=11){S=F.getStyle(f,"visibility");if(S!="inherit"){break;}f=f.parentNode;}if(S=="inherit"){S="visible";}}if(Y){if(Y instanceof Array){Z=Y.length;for(c=0;c<Z;c++){W=Y[c];V[V.length]=W.effect(this,W.duration);}}else{V[V.length]=Y.effect(this,Y.duration);}}if(Q){if(U){this.showMacGeckoScrollbars();}if(Y){if(Q){if(S!="visible"||S===""){this.beforeShowEvent.fire();T=V.length;for(b=0;b<T;b++){P=V[b];if(b===0&&!g(P.animateInCompleteEvent,this.showEvent.fire,this.showEvent)){P.animateInCompleteEvent.subscribe(this.showEvent.fire,this.showEvent,true);}P.animateIn();}}}}else{if(S!="visible"||S===""){this.beforeShowEvent.fire();this._setDomVisibility(true);this.cfg.refireEvent("iframe");this.showEvent.fire();}else{this._setDomVisibility(true);}}}else{if(U){this.hideMacGeckoScrollbars();}if(Y){if(S=="visible"){this.beforeHideEvent.fire();T=V.length;for(a=0;a<T;a++){d=V[a];if(a===0&&!g(d.animateOutCompleteEvent,this.hideEvent.fire,this.hideEvent)){d.animateOutCompleteEvent.subscribe(this.hideEvent.fire,this.hideEvent,true);}d.animateOut();}}else{if(S===""){this._setDomVisibility(false);}}}else{if(S=="visible"||S===""){this.beforeHideEvent.fire();this._setDomVisibility(false);this.hideEvent.fire();}else{this._setDomVisibility(false);}}}},doCenterOnDOMEvent:function(){var O=this.cfg,P=O.getProperty("fixedcenter");if(O.getProperty("visible")){if(P&&(P!==C||this.fitsInViewport())){this.center();}}},fitsInViewport:function(){var S=B.VIEWPORT_OFFSET,Q=this.element,T=Q.offsetWidth,R=Q.offsetHeight,O=F.getViewportWidth(),P=F.getViewportHeight();return((T+S<O)&&(R+S<P));},configFixedCenter:function(S,Q,T){var U=Q[0],P=D.alreadySubscribed,R=B.windowResizeEvent,O=B.windowScrollEvent;if(U){this.center();if(!P(this.beforeShowEvent,this.center)){this.beforeShowEvent.subscribe(this.center);}if(!P(R,this.doCenterOnDOMEvent,this)){R.subscribe(this.doCenterOnDOMEvent,this,true);}if(!P(O,this.doCenterOnDOMEvent,this)){O.subscribe(this.doCenterOnDOMEvent,this,true);}}else{this.beforeShowEvent.unsubscribe(this.center);R.unsubscribe(this.doCenterOnDOMEvent,this);O.unsubscribe(this.doCenterOnDOMEvent,this);}},configHeight:function(R,P,S){var O=P[0],Q=this.element;F.setStyle(Q,"height",O);this.cfg.refireEvent("iframe");},configAutoFillHeight:function(T,S,P){var V=S[0],Q=this.cfg,U="autofillheight",W="height",R=Q.getProperty(U),O=this._autoFillOnHeightChange;Q.unsubscribeFromConfigEvent(W,O);G.textResizeEvent.unsubscribe(O);this.changeContentEvent.unsubscribe(O);if(R&&V!==R&&this[R]){F.setStyle(this[R],W,"");}if(V){V=I.trim(V.toLowerCase());Q.subscribeToConfigEvent(W,O,this[V],this);G.textResizeEvent.subscribe(O,this[V],this);this.changeContentEvent.subscribe(O,this[V],this);Q.setProperty(U,V,true);}},configWidth:function(R,O,S){var Q=O[0],P=this.element;F.setStyle(P,"width",Q);this.cfg.refireEvent("iframe");},configzIndex:function(Q,O,R){var S=O[0],P=this.element;if(!S){S=F.getStyle(P,"zIndex");if(!S||isNaN(S)){S=0;}}if(this.iframe||this.cfg.getProperty("iframe")===true){if(S<=0){S=1;}}F.setStyle(P,"zIndex",S);this.cfg.setProperty("zIndex",S,true);if(this.iframe){this.stackIframe();}},configXY:function(Q,P,R){var T=P[0],O=T[0],S=T[1];this.cfg.setProperty("x",O);this.cfg.setProperty("y",S);this.beforeMoveEvent.fire([O,S]);O=this.cfg.getProperty("x");S=this.cfg.getProperty("y");this.cfg.refireEvent("iframe");this.moveEvent.fire([O,S]);},configX:function(Q,P,R){var O=P[0],S=this.cfg.getProperty("y");this.cfg.setProperty("x",O,true);this.cfg.setProperty("y",S,true);this.beforeMoveEvent.fire([O,S]);O=this.cfg.getProperty("x");S=this.cfg.getProperty("y");F.setX(this.element,O,true);this.cfg.setProperty("xy",[O,S],true);this.cfg.refireEvent("iframe");this.moveEvent.fire([O,S]);},configY:function(Q,P,R){var O=this.cfg.getProperty("x"),S=P[0];this.cfg.setProperty("x",O,true);this.cfg.setProperty("y",S,true);this.beforeMoveEvent.fire([O,S]);O=this.cfg.getProperty("x");S=this.cfg.getProperty("y");F.setY(this.element,S,true);this.cfg.setProperty("xy",[O,S],true);this.cfg.refireEvent("iframe");this.moveEvent.fire([O,S]);},showIframe:function(){var P=this.iframe,O;if(P){O=this.element.parentNode;if(O!=P.parentNode){this._addToParent(O,P);}P.style.display="block";}},hideIframe:function(){if(this.iframe){this.iframe.style.display="none";}},syncIframe:function(){var O=this.iframe,Q=this.element,S=B.IFRAME_OFFSET,P=(S*2),R;if(O){O.style.width=(Q.offsetWidth+P+"px");
+O.style.height=(Q.offsetHeight+P+"px");R=this.cfg.getProperty("xy");if(!I.isArray(R)||(isNaN(R[0])||isNaN(R[1]))){this.syncPosition();R=this.cfg.getProperty("xy");}F.setXY(O,[(R[0]-S),(R[1]-S)]);}},stackIframe:function(){if(this.iframe){var O=F.getStyle(this.element,"zIndex");if(!YAHOO.lang.isUndefined(O)&&!isNaN(O)){F.setStyle(this.iframe,"zIndex",(O-1));}}},configIframe:function(R,Q,S){var O=Q[0];function T(){var V=this.iframe,W=this.element,X;if(!V){if(!J){J=document.createElement("iframe");if(this.isSecure){J.src=B.IFRAME_SRC;}if(K.ie){J.style.filter="alpha(opacity=0)";J.frameBorder=0;}else{J.style.opacity="0";}J.style.position="absolute";J.style.border="none";J.style.margin="0";J.style.padding="0";J.style.display="none";J.tabIndex=-1;J.className=B.CSS_IFRAME;}V=J.cloneNode(false);V.id=this.id+"_f";X=W.parentNode;var U=X||document.body;this._addToParent(U,V);this.iframe=V;}this.showIframe();this.syncIframe();this.stackIframe();if(!this._hasIframeEventListeners){this.showEvent.subscribe(this.showIframe);this.hideEvent.subscribe(this.hideIframe);this.changeContentEvent.subscribe(this.syncIframe);this._hasIframeEventListeners=true;}}function P(){T.call(this);this.beforeShowEvent.unsubscribe(P);this._iframeDeferred=false;}if(O){if(this.cfg.getProperty("visible")){T.call(this);}else{if(!this._iframeDeferred){this.beforeShowEvent.subscribe(P);this._iframeDeferred=true;}}}else{this.hideIframe();if(this._hasIframeEventListeners){this.showEvent.unsubscribe(this.showIframe);this.hideEvent.unsubscribe(this.hideIframe);this.changeContentEvent.unsubscribe(this.syncIframe);this._hasIframeEventListeners=false;}}},_primeXYFromDOM:function(){if(YAHOO.lang.isUndefined(this.cfg.getProperty("xy"))){this.syncPosition();this.cfg.refireEvent("xy");this.beforeShowEvent.unsubscribe(this._primeXYFromDOM);}},configConstrainToViewport:function(P,O,Q){var R=O[0];if(R){if(!D.alreadySubscribed(this.beforeMoveEvent,this.enforceConstraints,this)){this.beforeMoveEvent.subscribe(this.enforceConstraints,this,true);}if(!D.alreadySubscribed(this.beforeShowEvent,this._primeXYFromDOM)){this.beforeShowEvent.subscribe(this._primeXYFromDOM);}}else{this.beforeShowEvent.unsubscribe(this._primeXYFromDOM);this.beforeMoveEvent.unsubscribe(this.enforceConstraints,this);}},configContext:function(U,T,Q){var X=T[0],R,O,V,S,P,W=this.CONTEXT_TRIGGERS;if(X){R=X[0];O=X[1];V=X[2];S=X[3];P=X[4];if(W&&W.length>0){S=(S||[]).concat(W);}if(R){if(typeof R=="string"){this.cfg.setProperty("context",[document.getElementById(R),O,V,S,P],true);}if(O&&V){this.align(O,V,P);}if(this._contextTriggers){this._processTriggers(this._contextTriggers,E,this._alignOnTrigger);}if(S){this._processTriggers(S,H,this._alignOnTrigger);this._contextTriggers=S;}}}},_alignOnTrigger:function(P,O){this.align();},_findTriggerCE:function(O){var P=null;if(O instanceof M){P=O;}else{if(B._TRIGGER_MAP[O]){P=B._TRIGGER_MAP[O];}}return P;},_processTriggers:function(S,U,R){var Q,T;for(var P=0,O=S.length;P<O;++P){Q=S[P];T=this._findTriggerCE(Q);if(T){T[U](R,this,true);}else{this[U](Q,R);}}},align:function(P,W,S){var V=this.cfg.getProperty("context"),T=this,O,Q,U;function R(Z,a){var Y=null,X=null;switch(P){case B.TOP_LEFT:Y=a;X=Z;break;case B.TOP_RIGHT:Y=a-Q.offsetWidth;X=Z;break;case B.BOTTOM_LEFT:Y=a;X=Z-Q.offsetHeight;break;case B.BOTTOM_RIGHT:Y=a-Q.offsetWidth;X=Z-Q.offsetHeight;break;}if(Y!==null&&X!==null){if(S){Y+=S[0];X+=S[1];}T.moveTo(Y,X);}}if(V){O=V[0];Q=this.element;T=this;if(!P){P=V[1];}if(!W){W=V[2];}if(!S&&V[4]){S=V[4];}if(Q&&O){U=F.getRegion(O);switch(W){case B.TOP_LEFT:R(U.top,U.left);break;case B.TOP_RIGHT:R(U.top,U.right);break;case B.BOTTOM_LEFT:R(U.bottom,U.left);break;case B.BOTTOM_RIGHT:R(U.bottom,U.right);break;}}}},enforceConstraints:function(P,O,Q){var S=O[0];var R=this.getConstrainedXY(S[0],S[1]);this.cfg.setProperty("x",R[0],true);this.cfg.setProperty("y",R[1],true);this.cfg.setProperty("xy",R,true);},_getConstrainedPos:function(X,P){var T=this.element,R=B.VIEWPORT_OFFSET,Z=(X=="x"),Y=(Z)?T.offsetWidth:T.offsetHeight,S=(Z)?F.getViewportWidth():F.getViewportHeight(),c=(Z)?F.getDocumentScrollLeft():F.getDocumentScrollTop(),b=(Z)?B.PREVENT_OVERLAP_X:B.PREVENT_OVERLAP_Y,O=this.cfg.getProperty("context"),U=(Y+R<S),W=this.cfg.getProperty("preventcontextoverlap")&&O&&b[(O[1]+O[2])],V=c+R,a=c+S-Y-R,Q=P;if(P<V||P>a){if(W){Q=this._preventOverlap(X,O[0],Y,S,c);}else{if(U){if(P<V){Q=V;}else{if(P>a){Q=a;}}}else{Q=V;}}}return Q;},_preventOverlap:function(X,W,Y,U,b){var Z=(X=="x"),T=B.VIEWPORT_OFFSET,S=this,Q=((Z)?F.getX(W):F.getY(W))-b,O=(Z)?W.offsetWidth:W.offsetHeight,P=Q-T,R=(U-(Q+O))-T,c=false,V=function(){var d;if((S.cfg.getProperty(X)-b)>Q){d=(Q-Y);}else{d=(Q+O);}S.cfg.setProperty(X,(d+b),true);return d;},a=function(){var e=((S.cfg.getProperty(X)-b)>Q)?R:P,d;if(Y>e){if(c){V();}else{V();c=true;d=a();}}return d;};a();return this.cfg.getProperty(X);},getConstrainedX:function(O){return this._getConstrainedPos("x",O);},getConstrainedY:function(O){return this._getConstrainedPos("y",O);},getConstrainedXY:function(O,P){return[this.getConstrainedX(O),this.getConstrainedY(P)];},center:function(){var R=B.VIEWPORT_OFFSET,S=this.element.offsetWidth,Q=this.element.offsetHeight,P=F.getViewportWidth(),T=F.getViewportHeight(),O,U;if(S<P){O=(P/2)-(S/2)+F.getDocumentScrollLeft();}else{O=R+F.getDocumentScrollLeft();}if(Q<T){U=(T/2)-(Q/2)+F.getDocumentScrollTop();}else{U=R+F.getDocumentScrollTop();}this.cfg.setProperty("xy",[parseInt(O,10),parseInt(U,10)]);this.cfg.refireEvent("iframe");if(K.webkit){this.forceContainerRedraw();}},syncPosition:function(){var O=F.getXY(this.element);this.cfg.setProperty("x",O[0],true);this.cfg.setProperty("y",O[1],true);this.cfg.setProperty("xy",O,true);},onDomResize:function(Q,P){var O=this;B.superclass.onDomResize.call(this,Q,P);setTimeout(function(){O.syncPosition();O.cfg.refireEvent("iframe");O.cfg.refireEvent("context");},0);},_getComputedHeight:(function(){if(document.defaultView&&document.defaultView.getComputedStyle){return function(P){var O=null;
+if(P.ownerDocument&&P.ownerDocument.defaultView){var Q=P.ownerDocument.defaultView.getComputedStyle(P,"");if(Q){O=parseInt(Q.height,10);}}return(I.isNumber(O))?O:null;};}else{return function(P){var O=null;if(P.style.pixelHeight){O=P.style.pixelHeight;}return(I.isNumber(O))?O:null;};}})(),_validateAutoFillHeight:function(O){return(!O)||(I.isString(O)&&B.STD_MOD_RE.test(O));},_autoFillOnHeightChange:function(R,P,Q){var O=this.cfg.getProperty("height");if((O&&O!=="auto")||(O===0)){this.fillHeight(Q);}},_getPreciseHeight:function(P){var O=P.offsetHeight;if(P.getBoundingClientRect){var Q=P.getBoundingClientRect();O=Q.bottom-Q.top;}return O;},fillHeight:function(R){if(R){var P=this.innerElement||this.element,O=[this.header,this.body,this.footer],V,W=0,X=0,T=0,Q=false;for(var U=0,S=O.length;U<S;U++){V=O[U];if(V){if(R!==V){X+=this._getPreciseHeight(V);}else{Q=true;}}}if(Q){if(K.ie||K.opera){F.setStyle(R,"height",0+"px");}W=this._getComputedHeight(P);if(W===null){F.addClass(P,"yui-override-padding");W=P.clientHeight;F.removeClass(P,"yui-override-padding");}T=Math.max(W-X,0);F.setStyle(R,"height",T+"px");if(R.offsetHeight!=T){T=Math.max(T-(R.offsetHeight-T),0);}F.setStyle(R,"height",T+"px");}}},bringToTop:function(){var S=[],R=this.element;function V(Z,Y){var b=F.getStyle(Z,"zIndex"),a=F.getStyle(Y,"zIndex"),X=(!b||isNaN(b))?0:parseInt(b,10),W=(!a||isNaN(a))?0:parseInt(a,10);if(X>W){return -1;}else{if(X<W){return 1;}else{return 0;}}}function Q(Y){var X=F.hasClass(Y,B.CSS_OVERLAY),W=YAHOO.widget.Panel;if(X&&!F.isAncestor(R,Y)){if(W&&F.hasClass(Y,W.CSS_PANEL)){S[S.length]=Y.parentNode;}else{S[S.length]=Y;}}}F.getElementsBy(Q,"DIV",document.body);S.sort(V);var O=S[0],U;if(O){U=F.getStyle(O,"zIndex");if(!isNaN(U)){var T=false;if(O!=R){T=true;}else{if(S.length>1){var P=F.getStyle(S[1],"zIndex");if(!isNaN(P)&&(U==P)){T=true;}}}if(T){this.cfg.setProperty("zindex",(parseInt(U,10)+2));}}}},destroy:function(){if(this.iframe){this.iframe.parentNode.removeChild(this.iframe);}this.iframe=null;B.windowResizeEvent.unsubscribe(this.doCenterOnDOMEvent,this);B.windowScrollEvent.unsubscribe(this.doCenterOnDOMEvent,this);G.textResizeEvent.unsubscribe(this._autoFillOnHeightChange);if(this._contextTriggers){this._processTriggers(this._contextTriggers,E,this._alignOnTrigger);}B.superclass.destroy.call(this);},forceContainerRedraw:function(){var O=this;F.addClass(O.element,"yui-force-redraw");setTimeout(function(){F.removeClass(O.element,"yui-force-redraw");},0);},toString:function(){return"Overlay "+this.id;}});}());(function(){YAHOO.widget.OverlayManager=function(G){this.init(G);};var D=YAHOO.widget.Overlay,C=YAHOO.util.Event,E=YAHOO.util.Dom,B=YAHOO.util.Config,F=YAHOO.util.CustomEvent,A=YAHOO.widget.OverlayManager;A.CSS_FOCUSED="focused";A.prototype={constructor:A,overlays:null,initDefaultConfig:function(){this.cfg.addProperty("overlays",{suppressEvent:true});this.cfg.addProperty("focusevent",{value:"mousedown"});},init:function(I){this.cfg=new B(this);this.initDefaultConfig();if(I){this.cfg.applyConfig(I,true);}this.cfg.fireQueue();var H=null;this.getActive=function(){return H;};this.focus=function(J){var K=this.find(J);if(K){K.focus();}};this.remove=function(K){var M=this.find(K),J;if(M){if(H==M){H=null;}var L=(M.element===null&&M.cfg===null)?true:false;if(!L){J=E.getStyle(M.element,"zIndex");M.cfg.setProperty("zIndex",-1000,true);}this.overlays.sort(this.compareZIndexDesc);this.overlays=this.overlays.slice(0,(this.overlays.length-1));M.hideEvent.unsubscribe(M.blur);M.destroyEvent.unsubscribe(this._onOverlayDestroy,M);M.focusEvent.unsubscribe(this._onOverlayFocusHandler,M);M.blurEvent.unsubscribe(this._onOverlayBlurHandler,M);if(!L){C.removeListener(M.element,this.cfg.getProperty("focusevent"),this._onOverlayElementFocus);M.cfg.setProperty("zIndex",J,true);M.cfg.setProperty("manager",null);}if(M.focusEvent._managed){M.focusEvent=null;}if(M.blurEvent._managed){M.blurEvent=null;}if(M.focus._managed){M.focus=null;}if(M.blur._managed){M.blur=null;}}};this.blurAll=function(){var K=this.overlays.length,J;if(K>0){J=K-1;do{this.overlays[J].blur();}while(J--);}};this._manageBlur=function(J){var K=false;if(H==J){E.removeClass(H.element,A.CSS_FOCUSED);H=null;K=true;}return K;};this._manageFocus=function(J){var K=false;if(H!=J){if(H){H.blur();}H=J;this.bringToTop(H);E.addClass(H.element,A.CSS_FOCUSED);K=true;}return K;};var G=this.cfg.getProperty("overlays");if(!this.overlays){this.overlays=[];}if(G){this.register(G);this.overlays.sort(this.compareZIndexDesc);}},_onOverlayElementFocus:function(I){var G=C.getTarget(I),H=this.close;if(H&&(G==H||E.isAncestor(H,G))){this.blur();}else{this.focus();}},_onOverlayDestroy:function(H,G,I){this.remove(I);},_onOverlayFocusHandler:function(H,G,I){this._manageFocus(I);},_onOverlayBlurHandler:function(H,G,I){this._manageBlur(I);},_bindFocus:function(G){var H=this;if(!G.focusEvent){G.focusEvent=G.createEvent("focus");G.focusEvent.signature=F.LIST;G.focusEvent._managed=true;}else{G.focusEvent.subscribe(H._onOverlayFocusHandler,G,H);}if(!G.focus){C.on(G.element,H.cfg.getProperty("focusevent"),H._onOverlayElementFocus,null,G);G.focus=function(){if(H._manageFocus(this)){if(this.cfg.getProperty("visible")&&this.focusFirst){this.focusFirst();}this.focusEvent.fire();}};G.focus._managed=true;}},_bindBlur:function(G){var H=this;if(!G.blurEvent){G.blurEvent=G.createEvent("blur");G.blurEvent.signature=F.LIST;G.focusEvent._managed=true;}else{G.blurEvent.subscribe(H._onOverlayBlurHandler,G,H);}if(!G.blur){G.blur=function(){if(H._manageBlur(this)){this.blurEvent.fire();}};G.blur._managed=true;}G.hideEvent.subscribe(G.blur);},_bindDestroy:function(G){var H=this;G.destroyEvent.subscribe(H._onOverlayDestroy,G,H);},_syncZIndex:function(G){var H=E.getStyle(G.element,"zIndex");if(!isNaN(H)){G.cfg.setProperty("zIndex",parseInt(H,10));}else{G.cfg.setProperty("zIndex",0);}},register:function(G){var J=false,H,I;if(G instanceof D){G.cfg.addProperty("manager",{value:this});this._bindFocus(G);this._bindBlur(G);this._bindDestroy(G);
+this._syncZIndex(G);this.overlays.push(G);this.bringToTop(G);J=true;}else{if(G instanceof Array){for(H=0,I=G.length;H<I;H++){J=this.register(G[H])||J;}}}return J;},bringToTop:function(M){var I=this.find(M),L,G,J;if(I){J=this.overlays;J.sort(this.compareZIndexDesc);G=J[0];if(G){L=E.getStyle(G.element,"zIndex");if(!isNaN(L)){var K=false;if(G!==I){K=true;}else{if(J.length>1){var H=E.getStyle(J[1].element,"zIndex");if(!isNaN(H)&&(L==H)){K=true;}}}if(K){I.cfg.setProperty("zindex",(parseInt(L,10)+2));}}J.sort(this.compareZIndexDesc);}}},find:function(G){var K=G instanceof D,I=this.overlays,M=I.length,J=null,L,H;if(K||typeof G=="string"){for(H=M-1;H>=0;H--){L=I[H];if((K&&(L===G))||(L.id==G)){J=L;break;}}}return J;},compareZIndexDesc:function(J,I){var H=(J.cfg)?J.cfg.getProperty("zIndex"):null,G=(I.cfg)?I.cfg.getProperty("zIndex"):null;if(H===null&&G===null){return 0;}else{if(H===null){return 1;}else{if(G===null){return -1;}else{if(H>G){return -1;}else{if(H<G){return 1;}else{return 0;}}}}}},showAll:function(){var H=this.overlays,I=H.length,G;for(G=I-1;G>=0;G--){H[G].show();}},hideAll:function(){var H=this.overlays,I=H.length,G;for(G=I-1;G>=0;G--){H[G].hide();}},toString:function(){return"OverlayManager";}};}());(function(){YAHOO.widget.ContainerEffect=function(E,H,G,D,F){if(!F){F=YAHOO.util.Anim;}this.overlay=E;this.attrIn=H;this.attrOut=G;this.targetElement=D||E.element;this.animClass=F;};var B=YAHOO.util.Dom,C=YAHOO.util.CustomEvent,A=YAHOO.widget.ContainerEffect;A.FADE=function(D,F){var G=YAHOO.util.Easing,I={attributes:{opacity:{from:0,to:1}},duration:F,method:G.easeIn},E={attributes:{opacity:{to:0}},duration:F,method:G.easeOut},H=new A(D,I,E,D.element);H.handleUnderlayStart=function(){var K=this.overlay.underlay;if(K&&YAHOO.env.ua.ie){var J=(K.filters&&K.filters.length>0);if(J){B.addClass(D.element,"yui-effect-fade");}}};H.handleUnderlayComplete=function(){var J=this.overlay.underlay;if(J&&YAHOO.env.ua.ie){B.removeClass(D.element,"yui-effect-fade");}};H.handleStartAnimateIn=function(K,J,L){B.addClass(L.overlay.element,"hide-select");if(!L.overlay.underlay){L.overlay.cfg.refireEvent("underlay");}L.handleUnderlayStart();L.overlay._setDomVisibility(true);B.setStyle(L.overlay.element,"opacity",0);};H.handleCompleteAnimateIn=function(K,J,L){B.removeClass(L.overlay.element,"hide-select");if(L.overlay.element.style.filter){L.overlay.element.style.filter=null;}L.handleUnderlayComplete();L.overlay.cfg.refireEvent("iframe");L.animateInCompleteEvent.fire();};H.handleStartAnimateOut=function(K,J,L){B.addClass(L.overlay.element,"hide-select");L.handleUnderlayStart();};H.handleCompleteAnimateOut=function(K,J,L){B.removeClass(L.overlay.element,"hide-select");if(L.overlay.element.style.filter){L.overlay.element.style.filter=null;}L.overlay._setDomVisibility(false);B.setStyle(L.overlay.element,"opacity",1);L.handleUnderlayComplete();L.overlay.cfg.refireEvent("iframe");L.animateOutCompleteEvent.fire();};H.init();return H;};A.SLIDE=function(F,D){var I=YAHOO.util.Easing,L=F.cfg.getProperty("x")||B.getX(F.element),K=F.cfg.getProperty("y")||B.getY(F.element),M=B.getClientWidth(),H=F.element.offsetWidth,J={attributes:{points:{to:[L,K]}},duration:D,method:I.easeIn},E={attributes:{points:{to:[(M+25),K]}},duration:D,method:I.easeOut},G=new A(F,J,E,F.element,YAHOO.util.Motion);G.handleStartAnimateIn=function(O,N,P){P.overlay.element.style.left=((-25)-H)+"px";P.overlay.element.style.top=K+"px";};G.handleTweenAnimateIn=function(Q,P,R){var S=B.getXY(R.overlay.element),O=S[0],N=S[1];if(B.getStyle(R.overlay.element,"visibility")=="hidden"&&O<L){R.overlay._setDomVisibility(true);}R.overlay.cfg.setProperty("xy",[O,N],true);R.overlay.cfg.refireEvent("iframe");};G.handleCompleteAnimateIn=function(O,N,P){P.overlay.cfg.setProperty("xy",[L,K],true);P.startX=L;P.startY=K;P.overlay.cfg.refireEvent("iframe");P.animateInCompleteEvent.fire();};G.handleStartAnimateOut=function(O,N,R){var P=B.getViewportWidth(),S=B.getXY(R.overlay.element),Q=S[1];R.animOut.attributes.points.to=[(P+25),Q];};G.handleTweenAnimateOut=function(P,O,Q){var S=B.getXY(Q.overlay.element),N=S[0],R=S[1];Q.overlay.cfg.setProperty("xy",[N,R],true);Q.overlay.cfg.refireEvent("iframe");};G.handleCompleteAnimateOut=function(O,N,P){P.overlay._setDomVisibility(false);P.overlay.cfg.setProperty("xy",[L,K]);P.animateOutCompleteEvent.fire();};G.init();return G;};A.prototype={init:function(){this.beforeAnimateInEvent=this.createEvent("beforeAnimateIn");this.beforeAnimateInEvent.signature=C.LIST;this.beforeAnimateOutEvent=this.createEvent("beforeAnimateOut");this.beforeAnimateOutEvent.signature=C.LIST;this.animateInCompleteEvent=this.createEvent("animateInComplete");this.animateInCompleteEvent.signature=C.LIST;this.animateOutCompleteEvent=this.createEvent("animateOutComplete");this.animateOutCompleteEvent.signature=C.LIST;this.animIn=new this.animClass(this.targetElement,this.attrIn.attributes,this.attrIn.duration,this.attrIn.method);this.animIn.onStart.subscribe(this.handleStartAnimateIn,this);this.animIn.onTween.subscribe(this.handleTweenAnimateIn,this);this.animIn.onComplete.subscribe(this.handleCompleteAnimateIn,this);this.animOut=new this.animClass(this.targetElement,this.attrOut.attributes,this.attrOut.duration,this.attrOut.method);this.animOut.onStart.subscribe(this.handleStartAnimateOut,this);this.animOut.onTween.subscribe(this.handleTweenAnimateOut,this);this.animOut.onComplete.subscribe(this.handleCompleteAnimateOut,this);},animateIn:function(){this.beforeAnimateInEvent.fire();this.animIn.animate();},animateOut:function(){this.beforeAnimateOutEvent.fire();this.animOut.animate();},handleStartAnimateIn:function(E,D,F){},handleTweenAnimateIn:function(E,D,F){},handleCompleteAnimateIn:function(E,D,F){},handleStartAnimateOut:function(E,D,F){},handleTweenAnimateOut:function(E,D,F){},handleCompleteAnimateOut:function(E,D,F){},toString:function(){var D="ContainerEffect";if(this.overlay){D+=" ["+this.overlay.toString()+"]";}return D;}};YAHOO.lang.augmentProto(A,YAHOO.util.EventProvider);
+})();YAHOO.register("containercore",YAHOO.widget.Module,{version:"2.8.2r1",build:"7"}); \ No newline at end of file
diff --git a/js/yui/cookie/cookie-min.js b/js/yui/cookie/cookie-min.js
new file mode 100644
index 000000000..fdfae10fe
--- /dev/null
+++ b/js/yui/cookie/cookie-min.js
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+YAHOO.namespace("util");YAHOO.util.Cookie={_createCookieString:function(B,D,C,A){var F=YAHOO.lang,E=encodeURIComponent(B)+"="+(C?encodeURIComponent(D):D);if(F.isObject(A)){if(A.expires instanceof Date){E+="; expires="+A.expires.toUTCString();}if(F.isString(A.path)&&A.path!==""){E+="; path="+A.path;}if(F.isString(A.domain)&&A.domain!==""){E+="; domain="+A.domain;}if(A.secure===true){E+="; secure";}}return E;},_createCookieHashString:function(B){var D=YAHOO.lang;if(!D.isObject(B)){throw new TypeError("Cookie._createCookieHashString(): Argument must be an object.");}var C=[];for(var A in B){if(D.hasOwnProperty(B,A)&&!D.isFunction(B[A])&&!D.isUndefined(B[A])){C.push(encodeURIComponent(A)+"="+encodeURIComponent(String(B[A])));}}return C.join("&");},_parseCookieHash:function(E){var D=E.split("&"),F=null,C={};if(E.length>0){for(var B=0,A=D.length;B<A;B++){F=D[B].split("=");C[decodeURIComponent(F[0])]=decodeURIComponent(F[1]);}}return C;},_parseCookieString:function(J,A){var K={};if(YAHOO.lang.isString(J)&&J.length>0){var B=(A===false?function(L){return L;}:decodeURIComponent);var H=J.split(/;\s/g),I=null,C=null,E=null;for(var D=0,F=H.length;D<F;D++){E=H[D].match(/([^=]+)=/i);if(E instanceof Array){try{I=decodeURIComponent(E[1]);C=B(H[D].substring(E[1].length+1));}catch(G){}}else{I=decodeURIComponent(H[D]);C="";}K[I]=C;}}return K;},exists:function(A){if(!YAHOO.lang.isString(A)||A===""){throw new TypeError("Cookie.exists(): Cookie name must be a non-empty string.");}var B=this._parseCookieString(document.cookie,true);return B.hasOwnProperty(A);},get:function(B,A){var E=YAHOO.lang,C;if(E.isFunction(A)){C=A;A={};}else{if(E.isObject(A)){C=A.converter;}else{A={};}}var D=this._parseCookieString(document.cookie,!A.raw);if(!E.isString(B)||B===""){throw new TypeError("Cookie.get(): Cookie name must be a non-empty string.");}if(E.isUndefined(D[B])){return null;}if(!E.isFunction(C)){return D[B];}else{return C(D[B]);}},getSub:function(A,C,B){var E=YAHOO.lang,D=this.getSubs(A);if(D!==null){if(!E.isString(C)||C===""){throw new TypeError("Cookie.getSub(): Subcookie name must be a non-empty string.");}if(E.isUndefined(D[C])){return null;}if(!E.isFunction(B)){return D[C];}else{return B(D[C]);}}else{return null;}},getSubs:function(B){var A=YAHOO.lang.isString;if(!A(B)||B===""){throw new TypeError("Cookie.getSubs(): Cookie name must be a non-empty string.");}var C=this._parseCookieString(document.cookie,false);if(A(C[B])){return this._parseCookieHash(C[B]);}return null;},remove:function(B,A){if(!YAHOO.lang.isString(B)||B===""){throw new TypeError("Cookie.remove(): Cookie name must be a non-empty string.");}A=YAHOO.lang.merge(A||{},{expires:new Date(0)});return this.set(B,"",A);},removeSub:function(B,E,A){var F=YAHOO.lang;A=A||{};if(!F.isString(B)||B===""){throw new TypeError("Cookie.removeSub(): Cookie name must be a non-empty string.");}if(!F.isString(E)||E===""){throw new TypeError("Cookie.removeSub(): Subcookie name must be a non-empty string.");}var D=this.getSubs(B);if(F.isObject(D)&&F.hasOwnProperty(D,E)){delete D[E];if(!A.removeIfEmpty){return this.setSubs(B,D,A);}else{for(var C in D){if(F.hasOwnProperty(D,C)&&!F.isFunction(D[C])&&!F.isUndefined(D[C])){return this.setSubs(B,D,A);}}return this.remove(B,A);}}else{return"";}},set:function(B,C,A){var E=YAHOO.lang;A=A||{};if(!E.isString(B)){throw new TypeError("Cookie.set(): Cookie name must be a string.");}if(E.isUndefined(C)){throw new TypeError("Cookie.set(): Value cannot be undefined.");}var D=this._createCookieString(B,C,!A.raw,A);document.cookie=D;return D;},setSub:function(B,D,C,A){var F=YAHOO.lang;if(!F.isString(B)||B===""){throw new TypeError("Cookie.setSub(): Cookie name must be a non-empty string.");}if(!F.isString(D)||D===""){throw new TypeError("Cookie.setSub(): Subcookie name must be a non-empty string.");}if(F.isUndefined(C)){throw new TypeError("Cookie.setSub(): Subcookie value cannot be undefined.");}var E=this.getSubs(B);if(!F.isObject(E)){E={};}E[D]=C;return this.setSubs(B,E,A);},setSubs:function(B,C,A){var E=YAHOO.lang;if(!E.isString(B)){throw new TypeError("Cookie.setSubs(): Cookie name must be a string.");}if(!E.isObject(C)){throw new TypeError("Cookie.setSubs(): Cookie value must be an object.");}var D=this._createCookieString(B,this._createCookieHashString(C),false,A);document.cookie=D;return D;}};YAHOO.register("cookie",YAHOO.util.Cookie,{version:"2.8.2r1",build:"7"}); \ No newline at end of file
diff --git a/js/yui/datasource/datasource-min.js b/js/yui/datasource/datasource-min.js
new file mode 100644
index 000000000..e59b60ee3
--- /dev/null
+++ b/js/yui/datasource/datasource-min.js
@@ -0,0 +1,12 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+(function(){var lang=YAHOO.lang,util=YAHOO.util,Ev=util.Event;util.DataSourceBase=function(oLiveData,oConfigs){if(oLiveData===null||oLiveData===undefined){return;}this.liveData=oLiveData;this._oQueue={interval:null,conn:null,requests:[]};this.responseSchema={};if(oConfigs&&(oConfigs.constructor==Object)){for(var sConfig in oConfigs){if(sConfig){this[sConfig]=oConfigs[sConfig];}}}var maxCacheEntries=this.maxCacheEntries;if(!lang.isNumber(maxCacheEntries)||(maxCacheEntries<0)){maxCacheEntries=0;}this._aIntervals=[];this.createEvent("cacheRequestEvent");this.createEvent("cacheResponseEvent");this.createEvent("requestEvent");this.createEvent("responseEvent");this.createEvent("responseParseEvent");this.createEvent("responseCacheEvent");this.createEvent("dataErrorEvent");this.createEvent("cacheFlushEvent");var DS=util.DataSourceBase;this._sName="DataSource instance"+DS._nIndex;DS._nIndex++;};var DS=util.DataSourceBase;lang.augmentObject(DS,{TYPE_UNKNOWN:-1,TYPE_JSARRAY:0,TYPE_JSFUNCTION:1,TYPE_XHR:2,TYPE_JSON:3,TYPE_XML:4,TYPE_TEXT:5,TYPE_HTMLTABLE:6,TYPE_SCRIPTNODE:7,TYPE_LOCAL:8,ERROR_DATAINVALID:"Invalid data",ERROR_DATANULL:"Null data",_nIndex:0,_nTransactionId:0,_getLocationValue:function(field,context){var locator=field.locator||field.key||field,xmldoc=context.ownerDocument||context,result,res,value=null;try{if(!lang.isUndefined(xmldoc.evaluate)){result=xmldoc.evaluate(locator,context,xmldoc.createNSResolver(!context.ownerDocument?context.documentElement:context.ownerDocument.documentElement),0,null);while(res=result.iterateNext()){value=res.textContent;}}else{xmldoc.setProperty("SelectionLanguage","XPath");result=context.selectNodes(locator)[0];value=result.value||result.text||null;}return value;}catch(e){}},issueCallback:function(callback,params,error,scope){if(lang.isFunction(callback)){callback.apply(scope,params);}else{if(lang.isObject(callback)){scope=callback.scope||scope||window;var callbackFunc=callback.success;if(error){callbackFunc=callback.failure;}if(callbackFunc){callbackFunc.apply(scope,params.concat([callback.argument]));}}}},parseString:function(oData){if(!lang.isValue(oData)){return null;}var string=oData+"";if(lang.isString(string)){return string;}else{return null;}},parseNumber:function(oData){if(!lang.isValue(oData)||(oData==="")){return null;}var number=oData*1;if(lang.isNumber(number)){return number;}else{return null;}},convertNumber:function(oData){return DS.parseNumber(oData);},parseDate:function(oData){var date=null;if(!(oData instanceof Date)){date=new Date(oData);}else{return oData;}if(date instanceof Date){return date;}else{return null;}},convertDate:function(oData){return DS.parseDate(oData);}});DS.Parser={string:DS.parseString,number:DS.parseNumber,date:DS.parseDate};DS.prototype={_sName:null,_aCache:null,_oQueue:null,_aIntervals:null,maxCacheEntries:0,liveData:null,dataType:DS.TYPE_UNKNOWN,responseType:DS.TYPE_UNKNOWN,responseSchema:null,useXPath:false,toString:function(){return this._sName;},getCachedResponse:function(oRequest,oCallback,oCaller){var aCache=this._aCache;if(this.maxCacheEntries>0){if(!aCache){this._aCache=[];}else{var nCacheLength=aCache.length;if(nCacheLength>0){var oResponse=null;this.fireEvent("cacheRequestEvent",{request:oRequest,callback:oCallback,caller:oCaller});for(var i=nCacheLength-1;i>=0;i--){var oCacheElem=aCache[i];if(this.isCacheHit(oRequest,oCacheElem.request)){oResponse=oCacheElem.response;this.fireEvent("cacheResponseEvent",{request:oRequest,response:oResponse,callback:oCallback,caller:oCaller});if(i<nCacheLength-1){aCache.splice(i,1);this.addToCache(oRequest,oResponse);}oResponse.cached=true;break;}}return oResponse;}}}else{if(aCache){this._aCache=null;}}return null;},isCacheHit:function(oRequest,oCachedRequest){return(oRequest===oCachedRequest);},addToCache:function(oRequest,oResponse){var aCache=this._aCache;if(!aCache){return;}while(aCache.length>=this.maxCacheEntries){aCache.shift();}var oCacheElem={request:oRequest,response:oResponse};aCache[aCache.length]=oCacheElem;this.fireEvent("responseCacheEvent",{request:oRequest,response:oResponse});},flushCache:function(){if(this._aCache){this._aCache=[];this.fireEvent("cacheFlushEvent");}},setInterval:function(nMsec,oRequest,oCallback,oCaller){if(lang.isNumber(nMsec)&&(nMsec>=0)){var oSelf=this;var nId=setInterval(function(){oSelf.makeConnection(oRequest,oCallback,oCaller);},nMsec);this._aIntervals.push(nId);return nId;}else{}},clearInterval:function(nId){var tracker=this._aIntervals||[];for(var i=tracker.length-1;i>-1;i--){if(tracker[i]===nId){tracker.splice(i,1);clearInterval(nId);}}},clearAllIntervals:function(){var tracker=this._aIntervals||[];for(var i=tracker.length-1;i>-1;i--){clearInterval(tracker[i]);}tracker=[];},sendRequest:function(oRequest,oCallback,oCaller){var oCachedResponse=this.getCachedResponse(oRequest,oCallback,oCaller);if(oCachedResponse){DS.issueCallback(oCallback,[oRequest,oCachedResponse],false,oCaller);return null;}return this.makeConnection(oRequest,oCallback,oCaller);},makeConnection:function(oRequest,oCallback,oCaller){var tId=DS._nTransactionId++;this.fireEvent("requestEvent",{tId:tId,request:oRequest,callback:oCallback,caller:oCaller});var oRawResponse=this.liveData;this.handleResponse(oRequest,oRawResponse,oCallback,oCaller,tId);return tId;},handleResponse:function(oRequest,oRawResponse,oCallback,oCaller,tId){this.fireEvent("responseEvent",{tId:tId,request:oRequest,response:oRawResponse,callback:oCallback,caller:oCaller});var xhr=(this.dataType==DS.TYPE_XHR)?true:false;var oParsedResponse=null;var oFullResponse=oRawResponse;if(this.responseType===DS.TYPE_UNKNOWN){var ctype=(oRawResponse&&oRawResponse.getResponseHeader)?oRawResponse.getResponseHeader["Content-Type"]:null;if(ctype){if(ctype.indexOf("text/xml")>-1){this.responseType=DS.TYPE_XML;}else{if(ctype.indexOf("application/json")>-1){this.responseType=DS.TYPE_JSON;}else{if(ctype.indexOf("text/plain")>-1){this.responseType=DS.TYPE_TEXT;}}}}else{if(YAHOO.lang.isArray(oRawResponse)){this.responseType=DS.TYPE_JSARRAY;
+}else{if(oRawResponse&&oRawResponse.nodeType&&(oRawResponse.nodeType===9||oRawResponse.nodeType===1||oRawResponse.nodeType===11)){this.responseType=DS.TYPE_XML;}else{if(oRawResponse&&oRawResponse.nodeName&&(oRawResponse.nodeName.toLowerCase()=="table")){this.responseType=DS.TYPE_HTMLTABLE;}else{if(YAHOO.lang.isObject(oRawResponse)){this.responseType=DS.TYPE_JSON;}else{if(YAHOO.lang.isString(oRawResponse)){this.responseType=DS.TYPE_TEXT;}}}}}}}switch(this.responseType){case DS.TYPE_JSARRAY:if(xhr&&oRawResponse&&oRawResponse.responseText){oFullResponse=oRawResponse.responseText;}try{if(lang.isString(oFullResponse)){var parseArgs=[oFullResponse].concat(this.parseJSONArgs);if(lang.JSON){oFullResponse=lang.JSON.parse.apply(lang.JSON,parseArgs);}else{if(window.JSON&&JSON.parse){oFullResponse=JSON.parse.apply(JSON,parseArgs);}else{if(oFullResponse.parseJSON){oFullResponse=oFullResponse.parseJSON.apply(oFullResponse,parseArgs.slice(1));}else{while(oFullResponse.length>0&&(oFullResponse.charAt(0)!="{")&&(oFullResponse.charAt(0)!="[")){oFullResponse=oFullResponse.substring(1,oFullResponse.length);}if(oFullResponse.length>0){var arrayEnd=Math.max(oFullResponse.lastIndexOf("]"),oFullResponse.lastIndexOf("}"));oFullResponse=oFullResponse.substring(0,arrayEnd+1);oFullResponse=eval("("+oFullResponse+")");}}}}}}catch(e1){}oFullResponse=this.doBeforeParseData(oRequest,oFullResponse,oCallback);oParsedResponse=this.parseArrayData(oRequest,oFullResponse);break;case DS.TYPE_JSON:if(xhr&&oRawResponse&&oRawResponse.responseText){oFullResponse=oRawResponse.responseText;}try{if(lang.isString(oFullResponse)){var parseArgs=[oFullResponse].concat(this.parseJSONArgs);if(lang.JSON){oFullResponse=lang.JSON.parse.apply(lang.JSON,parseArgs);}else{if(window.JSON&&JSON.parse){oFullResponse=JSON.parse.apply(JSON,parseArgs);}else{if(oFullResponse.parseJSON){oFullResponse=oFullResponse.parseJSON.apply(oFullResponse,parseArgs.slice(1));}else{while(oFullResponse.length>0&&(oFullResponse.charAt(0)!="{")&&(oFullResponse.charAt(0)!="[")){oFullResponse=oFullResponse.substring(1,oFullResponse.length);}if(oFullResponse.length>0){var objEnd=Math.max(oFullResponse.lastIndexOf("]"),oFullResponse.lastIndexOf("}"));oFullResponse=oFullResponse.substring(0,objEnd+1);oFullResponse=eval("("+oFullResponse+")");}}}}}}catch(e){}oFullResponse=this.doBeforeParseData(oRequest,oFullResponse,oCallback);oParsedResponse=this.parseJSONData(oRequest,oFullResponse);break;case DS.TYPE_HTMLTABLE:if(xhr&&oRawResponse.responseText){var el=document.createElement("div");el.innerHTML=oRawResponse.responseText;oFullResponse=el.getElementsByTagName("table")[0];}oFullResponse=this.doBeforeParseData(oRequest,oFullResponse,oCallback);oParsedResponse=this.parseHTMLTableData(oRequest,oFullResponse);break;case DS.TYPE_XML:if(xhr&&oRawResponse.responseXML){oFullResponse=oRawResponse.responseXML;}oFullResponse=this.doBeforeParseData(oRequest,oFullResponse,oCallback);oParsedResponse=this.parseXMLData(oRequest,oFullResponse);break;case DS.TYPE_TEXT:if(xhr&&lang.isString(oRawResponse.responseText)){oFullResponse=oRawResponse.responseText;}oFullResponse=this.doBeforeParseData(oRequest,oFullResponse,oCallback);oParsedResponse=this.parseTextData(oRequest,oFullResponse);break;default:oFullResponse=this.doBeforeParseData(oRequest,oFullResponse,oCallback);oParsedResponse=this.parseData(oRequest,oFullResponse);break;}oParsedResponse=oParsedResponse||{};if(!oParsedResponse.results){oParsedResponse.results=[];}if(!oParsedResponse.meta){oParsedResponse.meta={};}if(!oParsedResponse.error){oParsedResponse=this.doBeforeCallback(oRequest,oFullResponse,oParsedResponse,oCallback);this.fireEvent("responseParseEvent",{request:oRequest,response:oParsedResponse,callback:oCallback,caller:oCaller});this.addToCache(oRequest,oParsedResponse);}else{oParsedResponse.error=true;this.fireEvent("dataErrorEvent",{request:oRequest,response:oRawResponse,callback:oCallback,caller:oCaller,message:DS.ERROR_DATANULL});}oParsedResponse.tId=tId;DS.issueCallback(oCallback,[oRequest,oParsedResponse],oParsedResponse.error,oCaller);},doBeforeParseData:function(oRequest,oFullResponse,oCallback){return oFullResponse;},doBeforeCallback:function(oRequest,oFullResponse,oParsedResponse,oCallback){return oParsedResponse;},parseData:function(oRequest,oFullResponse){if(lang.isValue(oFullResponse)){var oParsedResponse={results:oFullResponse,meta:{}};return oParsedResponse;}return null;},parseArrayData:function(oRequest,oFullResponse){if(lang.isArray(oFullResponse)){var results=[],i,j,rec,field,data;if(lang.isArray(this.responseSchema.fields)){var fields=this.responseSchema.fields;for(i=fields.length-1;i>=0;--i){if(typeof fields[i]!=="object"){fields[i]={key:fields[i]};}}var parsers={},p;for(i=fields.length-1;i>=0;--i){p=(typeof fields[i].parser==="function"?fields[i].parser:DS.Parser[fields[i].parser+""])||fields[i].converter;if(p){parsers[fields[i].key]=p;}}var arrType=lang.isArray(oFullResponse[0]);for(i=oFullResponse.length-1;i>-1;i--){var oResult={};rec=oFullResponse[i];if(typeof rec==="object"){for(j=fields.length-1;j>-1;j--){field=fields[j];data=arrType?rec[j]:rec[field.key];if(parsers[field.key]){data=parsers[field.key].call(this,data);}if(data===undefined){data=null;}oResult[field.key]=data;}}else{if(lang.isString(rec)){for(j=fields.length-1;j>-1;j--){field=fields[j];data=rec;if(parsers[field.key]){data=parsers[field.key].call(this,data);}if(data===undefined){data=null;}oResult[field.key]=data;}}}results[i]=oResult;}}else{results=oFullResponse;}var oParsedResponse={results:results};return oParsedResponse;}return null;},parseTextData:function(oRequest,oFullResponse){if(lang.isString(oFullResponse)){if(lang.isString(this.responseSchema.recordDelim)&&lang.isString(this.responseSchema.fieldDelim)){var oParsedResponse={results:[]};var recDelim=this.responseSchema.recordDelim;var fieldDelim=this.responseSchema.fieldDelim;if(oFullResponse.length>0){var newLength=oFullResponse.length-recDelim.length;if(oFullResponse.substr(newLength)==recDelim){oFullResponse=oFullResponse.substr(0,newLength);
+}if(oFullResponse.length>0){var recordsarray=oFullResponse.split(recDelim);for(var i=0,len=recordsarray.length,recIdx=0;i<len;++i){var bError=false,sRecord=recordsarray[i];if(lang.isString(sRecord)&&(sRecord.length>0)){var fielddataarray=recordsarray[i].split(fieldDelim);var oResult={};if(lang.isArray(this.responseSchema.fields)){var fields=this.responseSchema.fields;for(var j=fields.length-1;j>-1;j--){try{var data=fielddataarray[j];if(lang.isString(data)){if(data.charAt(0)=='"'){data=data.substr(1);}if(data.charAt(data.length-1)=='"'){data=data.substr(0,data.length-1);}var field=fields[j];var key=(lang.isValue(field.key))?field.key:field;if(!field.parser&&field.converter){field.parser=field.converter;}var parser=(typeof field.parser==="function")?field.parser:DS.Parser[field.parser+""];if(parser){data=parser.call(this,data);}if(data===undefined){data=null;}oResult[key]=data;}else{bError=true;}}catch(e){bError=true;}}}else{oResult=fielddataarray;}if(!bError){oParsedResponse.results[recIdx++]=oResult;}}}}}return oParsedResponse;}}return null;},parseXMLResult:function(result){var oResult={},schema=this.responseSchema;try{for(var m=schema.fields.length-1;m>=0;m--){var field=schema.fields[m];var key=(lang.isValue(field.key))?field.key:field;var data=null;if(this.useXPath){data=YAHOO.util.DataSource._getLocationValue(field,result);}else{var xmlAttr=result.attributes.getNamedItem(key);if(xmlAttr){data=xmlAttr.value;}else{var xmlNode=result.getElementsByTagName(key);if(xmlNode&&xmlNode.item(0)){var item=xmlNode.item(0);data=(item)?((item.text)?item.text:(item.textContent)?item.textContent:null):null;if(!data){var datapieces=[];for(var j=0,len=item.childNodes.length;j<len;j++){if(item.childNodes[j].nodeValue){datapieces[datapieces.length]=item.childNodes[j].nodeValue;}}if(datapieces.length>0){data=datapieces.join("");}}}}}if(data===null){data="";}if(!field.parser&&field.converter){field.parser=field.converter;}var parser=(typeof field.parser==="function")?field.parser:DS.Parser[field.parser+""];if(parser){data=parser.call(this,data);}if(data===undefined){data=null;}oResult[key]=data;}}catch(e){}return oResult;},parseXMLData:function(oRequest,oFullResponse){var bError=false,schema=this.responseSchema,oParsedResponse={meta:{}},xmlList=null,metaNode=schema.metaNode,metaLocators=schema.metaFields||{},i,k,loc,v;try{if(this.useXPath){for(k in metaLocators){oParsedResponse.meta[k]=YAHOO.util.DataSource._getLocationValue(metaLocators[k],oFullResponse);}}else{metaNode=metaNode?oFullResponse.getElementsByTagName(metaNode)[0]:oFullResponse;if(metaNode){for(k in metaLocators){if(lang.hasOwnProperty(metaLocators,k)){loc=metaLocators[k];v=metaNode.getElementsByTagName(loc)[0];if(v){v=v.firstChild.nodeValue;}else{v=metaNode.attributes.getNamedItem(loc);if(v){v=v.value;}}if(lang.isValue(v)){oParsedResponse.meta[k]=v;}}}}}xmlList=(schema.resultNode)?oFullResponse.getElementsByTagName(schema.resultNode):null;}catch(e){}if(!xmlList||!lang.isArray(schema.fields)){bError=true;}else{oParsedResponse.results=[];for(i=xmlList.length-1;i>=0;--i){var oResult=this.parseXMLResult(xmlList.item(i));oParsedResponse.results[i]=oResult;}}if(bError){oParsedResponse.error=true;}else{}return oParsedResponse;},parseJSONData:function(oRequest,oFullResponse){var oParsedResponse={results:[],meta:{}};if(lang.isObject(oFullResponse)&&this.responseSchema.resultsList){var schema=this.responseSchema,fields=schema.fields,resultsList=oFullResponse,results=[],metaFields=schema.metaFields||{},fieldParsers=[],fieldPaths=[],simpleFields=[],bError=false,i,len,j,v,key,parser,path;var buildPath=function(needle){var path=null,keys=[],i=0;if(needle){needle=needle.replace(/\[(['"])(.*?)\1\]/g,function(x,$1,$2){keys[i]=$2;return".@"+(i++);}).replace(/\[(\d+)\]/g,function(x,$1){keys[i]=parseInt($1,10)|0;return".@"+(i++);}).replace(/^\./,"");if(!/[^\w\.\$@]/.test(needle)){path=needle.split(".");for(i=path.length-1;i>=0;--i){if(path[i].charAt(0)==="@"){path[i]=keys[parseInt(path[i].substr(1),10)];}}}else{}}return path;};var walkPath=function(path,origin){var v=origin,i=0,len=path.length;for(;i<len&&v;++i){v=v[path[i]];}return v;};path=buildPath(schema.resultsList);if(path){resultsList=walkPath(path,oFullResponse);if(resultsList===undefined){bError=true;}}else{bError=true;}if(!resultsList){resultsList=[];}if(!lang.isArray(resultsList)){resultsList=[resultsList];}if(!bError){if(schema.fields){var field;for(i=0,len=fields.length;i<len;i++){field=fields[i];key=field.key||field;parser=((typeof field.parser==="function")?field.parser:DS.Parser[field.parser+""])||field.converter;path=buildPath(key);if(parser){fieldParsers[fieldParsers.length]={key:key,parser:parser};}if(path){if(path.length>1){fieldPaths[fieldPaths.length]={key:key,path:path};}else{simpleFields[simpleFields.length]={key:key,path:path[0]};}}else{}}for(i=resultsList.length-1;i>=0;--i){var r=resultsList[i],rec={};if(r){for(j=simpleFields.length-1;j>=0;--j){rec[simpleFields[j].key]=(r[simpleFields[j].path]!==undefined)?r[simpleFields[j].path]:r[j];}for(j=fieldPaths.length-1;j>=0;--j){rec[fieldPaths[j].key]=walkPath(fieldPaths[j].path,r);}for(j=fieldParsers.length-1;j>=0;--j){var p=fieldParsers[j].key;rec[p]=fieldParsers[j].parser(rec[p]);if(rec[p]===undefined){rec[p]=null;}}}results[i]=rec;}}else{results=resultsList;}for(key in metaFields){if(lang.hasOwnProperty(metaFields,key)){path=buildPath(metaFields[key]);if(path){v=walkPath(path,oFullResponse);oParsedResponse.meta[key]=v;}}}}else{oParsedResponse.error=true;}oParsedResponse.results=results;}else{oParsedResponse.error=true;}return oParsedResponse;},parseHTMLTableData:function(oRequest,oFullResponse){var bError=false;var elTable=oFullResponse;var fields=this.responseSchema.fields;var oParsedResponse={results:[]};if(lang.isArray(fields)){for(var i=0;i<elTable.tBodies.length;i++){var elTbody=elTable.tBodies[i];for(var j=elTbody.rows.length-1;j>-1;j--){var elRow=elTbody.rows[j];var oResult={};for(var k=fields.length-1;k>-1;k--){var field=fields[k];var key=(lang.isValue(field.key))?field.key:field;
+var data=elRow.cells[k].innerHTML;if(!field.parser&&field.converter){field.parser=field.converter;}var parser=(typeof field.parser==="function")?field.parser:DS.Parser[field.parser+""];if(parser){data=parser.call(this,data);}if(data===undefined){data=null;}oResult[key]=data;}oParsedResponse.results[j]=oResult;}}}else{bError=true;}if(bError){oParsedResponse.error=true;}else{}return oParsedResponse;}};lang.augmentProto(DS,util.EventProvider);util.LocalDataSource=function(oLiveData,oConfigs){this.dataType=DS.TYPE_LOCAL;if(oLiveData){if(YAHOO.lang.isArray(oLiveData)){this.responseType=DS.TYPE_JSARRAY;}else{if(oLiveData.nodeType&&oLiveData.nodeType==9){this.responseType=DS.TYPE_XML;}else{if(oLiveData.nodeName&&(oLiveData.nodeName.toLowerCase()=="table")){this.responseType=DS.TYPE_HTMLTABLE;oLiveData=oLiveData.cloneNode(true);}else{if(YAHOO.lang.isString(oLiveData)){this.responseType=DS.TYPE_TEXT;}else{if(YAHOO.lang.isObject(oLiveData)){this.responseType=DS.TYPE_JSON;}}}}}}else{oLiveData=[];this.responseType=DS.TYPE_JSARRAY;}util.LocalDataSource.superclass.constructor.call(this,oLiveData,oConfigs);};lang.extend(util.LocalDataSource,DS);lang.augmentObject(util.LocalDataSource,DS);util.FunctionDataSource=function(oLiveData,oConfigs){this.dataType=DS.TYPE_JSFUNCTION;oLiveData=oLiveData||function(){};util.FunctionDataSource.superclass.constructor.call(this,oLiveData,oConfigs);};lang.extend(util.FunctionDataSource,DS,{scope:null,makeConnection:function(oRequest,oCallback,oCaller){var tId=DS._nTransactionId++;this.fireEvent("requestEvent",{tId:tId,request:oRequest,callback:oCallback,caller:oCaller});var oRawResponse=(this.scope)?this.liveData.call(this.scope,oRequest,this):this.liveData(oRequest);if(this.responseType===DS.TYPE_UNKNOWN){if(YAHOO.lang.isArray(oRawResponse)){this.responseType=DS.TYPE_JSARRAY;}else{if(oRawResponse&&oRawResponse.nodeType&&oRawResponse.nodeType==9){this.responseType=DS.TYPE_XML;}else{if(oRawResponse&&oRawResponse.nodeName&&(oRawResponse.nodeName.toLowerCase()=="table")){this.responseType=DS.TYPE_HTMLTABLE;}else{if(YAHOO.lang.isObject(oRawResponse)){this.responseType=DS.TYPE_JSON;}else{if(YAHOO.lang.isString(oRawResponse)){this.responseType=DS.TYPE_TEXT;}}}}}}this.handleResponse(oRequest,oRawResponse,oCallback,oCaller,tId);return tId;}});lang.augmentObject(util.FunctionDataSource,DS);util.ScriptNodeDataSource=function(oLiveData,oConfigs){this.dataType=DS.TYPE_SCRIPTNODE;oLiveData=oLiveData||"";util.ScriptNodeDataSource.superclass.constructor.call(this,oLiveData,oConfigs);};lang.extend(util.ScriptNodeDataSource,DS,{getUtility:util.Get,asyncMode:"allowAll",scriptCallbackParam:"callback",generateRequestCallback:function(id){return"&"+this.scriptCallbackParam+"=YAHOO.util.ScriptNodeDataSource.callbacks["+id+"]";},doBeforeGetScriptNode:function(sUri){return sUri;},makeConnection:function(oRequest,oCallback,oCaller){var tId=DS._nTransactionId++;this.fireEvent("requestEvent",{tId:tId,request:oRequest,callback:oCallback,caller:oCaller});if(util.ScriptNodeDataSource._nPending===0){util.ScriptNodeDataSource.callbacks=[];util.ScriptNodeDataSource._nId=0;}var id=util.ScriptNodeDataSource._nId;util.ScriptNodeDataSource._nId++;var oSelf=this;util.ScriptNodeDataSource.callbacks[id]=function(oRawResponse){if((oSelf.asyncMode!=="ignoreStaleResponses")||(id===util.ScriptNodeDataSource.callbacks.length-1)){if(oSelf.responseType===DS.TYPE_UNKNOWN){if(YAHOO.lang.isArray(oRawResponse)){oSelf.responseType=DS.TYPE_JSARRAY;}else{if(oRawResponse.nodeType&&oRawResponse.nodeType==9){oSelf.responseType=DS.TYPE_XML;}else{if(oRawResponse.nodeName&&(oRawResponse.nodeName.toLowerCase()=="table")){oSelf.responseType=DS.TYPE_HTMLTABLE;}else{if(YAHOO.lang.isObject(oRawResponse)){oSelf.responseType=DS.TYPE_JSON;}else{if(YAHOO.lang.isString(oRawResponse)){oSelf.responseType=DS.TYPE_TEXT;}}}}}}oSelf.handleResponse(oRequest,oRawResponse,oCallback,oCaller,tId);}else{}delete util.ScriptNodeDataSource.callbacks[id];};util.ScriptNodeDataSource._nPending++;var sUri=this.liveData+oRequest+this.generateRequestCallback(id);sUri=this.doBeforeGetScriptNode(sUri);this.getUtility.script(sUri,{autopurge:true,onsuccess:util.ScriptNodeDataSource._bumpPendingDown,onfail:util.ScriptNodeDataSource._bumpPendingDown});return tId;}});lang.augmentObject(util.ScriptNodeDataSource,DS);lang.augmentObject(util.ScriptNodeDataSource,{_nId:0,_nPending:0,callbacks:[]});util.XHRDataSource=function(oLiveData,oConfigs){this.dataType=DS.TYPE_XHR;this.connMgr=this.connMgr||util.Connect;oLiveData=oLiveData||"";util.XHRDataSource.superclass.constructor.call(this,oLiveData,oConfigs);};lang.extend(util.XHRDataSource,DS,{connMgr:null,connXhrMode:"allowAll",connMethodPost:false,connTimeout:0,makeConnection:function(oRequest,oCallback,oCaller){var oRawResponse=null;var tId=DS._nTransactionId++;this.fireEvent("requestEvent",{tId:tId,request:oRequest,callback:oCallback,caller:oCaller});var oSelf=this;var oConnMgr=this.connMgr;var oQueue=this._oQueue;var _xhrSuccess=function(oResponse){if(oResponse&&(this.connXhrMode=="ignoreStaleResponses")&&(oResponse.tId!=oQueue.conn.tId)){return null;}else{if(!oResponse){this.fireEvent("dataErrorEvent",{request:oRequest,response:null,callback:oCallback,caller:oCaller,message:DS.ERROR_DATANULL});DS.issueCallback(oCallback,[oRequest,{error:true}],true,oCaller);return null;}else{if(this.responseType===DS.TYPE_UNKNOWN){var ctype=(oResponse.getResponseHeader)?oResponse.getResponseHeader["Content-Type"]:null;if(ctype){if(ctype.indexOf("text/xml")>-1){this.responseType=DS.TYPE_XML;}else{if(ctype.indexOf("application/json")>-1){this.responseType=DS.TYPE_JSON;}else{if(ctype.indexOf("text/plain")>-1){this.responseType=DS.TYPE_TEXT;}}}}}this.handleResponse(oRequest,oResponse,oCallback,oCaller,tId);}}};var _xhrFailure=function(oResponse){this.fireEvent("dataErrorEvent",{request:oRequest,response:oResponse,callback:oCallback,caller:oCaller,message:DS.ERROR_DATAINVALID});if(lang.isString(this.liveData)&&lang.isString(oRequest)&&(this.liveData.lastIndexOf("?")!==this.liveData.length-1)&&(oRequest.indexOf("?")!==0)){}oResponse=oResponse||{};
+oResponse.error=true;DS.issueCallback(oCallback,[oRequest,oResponse],true,oCaller);return null;};var _xhrCallback={success:_xhrSuccess,failure:_xhrFailure,scope:this};if(lang.isNumber(this.connTimeout)){_xhrCallback.timeout=this.connTimeout;}if(this.connXhrMode=="cancelStaleRequests"){if(oQueue.conn){if(oConnMgr.abort){oConnMgr.abort(oQueue.conn);oQueue.conn=null;}else{}}}if(oConnMgr&&oConnMgr.asyncRequest){var sLiveData=this.liveData;var isPost=this.connMethodPost;var sMethod=(isPost)?"POST":"GET";var sUri=(isPost||!lang.isValue(oRequest))?sLiveData:sLiveData+oRequest;var sRequest=(isPost)?oRequest:null;if(this.connXhrMode!="queueRequests"){oQueue.conn=oConnMgr.asyncRequest(sMethod,sUri,_xhrCallback,sRequest);}else{if(oQueue.conn){var allRequests=oQueue.requests;allRequests.push({request:oRequest,callback:_xhrCallback});if(!oQueue.interval){oQueue.interval=setInterval(function(){if(oConnMgr.isCallInProgress(oQueue.conn)){return;}else{if(allRequests.length>0){sUri=(isPost||!lang.isValue(allRequests[0].request))?sLiveData:sLiveData+allRequests[0].request;sRequest=(isPost)?allRequests[0].request:null;oQueue.conn=oConnMgr.asyncRequest(sMethod,sUri,allRequests[0].callback,sRequest);allRequests.shift();}else{clearInterval(oQueue.interval);oQueue.interval=null;}}},50);}}else{oQueue.conn=oConnMgr.asyncRequest(sMethod,sUri,_xhrCallback,sRequest);}}}else{DS.issueCallback(oCallback,[oRequest,{error:true}],true,oCaller);}return tId;}});lang.augmentObject(util.XHRDataSource,DS);util.DataSource=function(oLiveData,oConfigs){oConfigs=oConfigs||{};var dataType=oConfigs.dataType;if(dataType){if(dataType==DS.TYPE_LOCAL){lang.augmentObject(util.DataSource,util.LocalDataSource);return new util.LocalDataSource(oLiveData,oConfigs);}else{if(dataType==DS.TYPE_XHR){lang.augmentObject(util.DataSource,util.XHRDataSource);return new util.XHRDataSource(oLiveData,oConfigs);}else{if(dataType==DS.TYPE_SCRIPTNODE){lang.augmentObject(util.DataSource,util.ScriptNodeDataSource);return new util.ScriptNodeDataSource(oLiveData,oConfigs);}else{if(dataType==DS.TYPE_JSFUNCTION){lang.augmentObject(util.DataSource,util.FunctionDataSource);return new util.FunctionDataSource(oLiveData,oConfigs);}}}}}if(YAHOO.lang.isString(oLiveData)){lang.augmentObject(util.DataSource,util.XHRDataSource);return new util.XHRDataSource(oLiveData,oConfigs);}else{if(YAHOO.lang.isFunction(oLiveData)){lang.augmentObject(util.DataSource,util.FunctionDataSource);return new util.FunctionDataSource(oLiveData,oConfigs);}else{lang.augmentObject(util.DataSource,util.LocalDataSource);return new util.LocalDataSource(oLiveData,oConfigs);}}};lang.augmentObject(util.DataSource,DS);})();YAHOO.util.Number={format:function(B,E){if(!isFinite(+B)){return"";}B=!isFinite(+B)?0:+B;E=YAHOO.lang.merge(YAHOO.util.Number.format.defaults,(E||{}));var C=B<0,F=Math.abs(B),A=E.decimalPlaces,I=E.thousandsSeparator,H,G,D;if(A<0){H=F-(F%1)+"";D=H.length+A;if(D>0){H=Number("."+H).toFixed(D).slice(2)+new Array(H.length-D+1).join("0");}else{H="0";}}else{H=F<1&&F>=0.5&&!A?"1":F.toFixed(A);}if(F>1000){G=H.split(/\D/);D=G[0].length%3||3;G[0]=G[0].slice(0,D)+G[0].slice(D).replace(/(\d{3})/g,I+"$1");H=G.join(E.decimalSeparator);}H=E.prefix+H+E.suffix;return C?E.negativeFormat.replace(/#/,H):H;}};YAHOO.util.Number.format.defaults={decimalSeparator:".",decimalPlaces:null,thousandsSeparator:"",prefix:"",suffix:"",negativeFormat:"-#"};(function(){var A=function(C,E,D){if(typeof D==="undefined"){D=10;}for(;parseInt(C,10)<D&&D>1;D/=10){C=E.toString()+C;}return C.toString();};var B={formats:{a:function(D,C){return C.a[D.getDay()];},A:function(D,C){return C.A[D.getDay()];},b:function(D,C){return C.b[D.getMonth()];},B:function(D,C){return C.B[D.getMonth()];},C:function(C){return A(parseInt(C.getFullYear()/100,10),0);},d:["getDate","0"],e:["getDate"," "],g:function(C){return A(parseInt(B.formats.G(C)%100,10),0);},G:function(E){var F=E.getFullYear();var D=parseInt(B.formats.V(E),10);var C=parseInt(B.formats.W(E),10);if(C>D){F++;}else{if(C===0&&D>=52){F--;}}return F;},H:["getHours","0"],I:function(D){var C=D.getHours()%12;return A(C===0?12:C,0);},j:function(G){var F=new Date(""+G.getFullYear()+"/1/1 GMT");var D=new Date(""+G.getFullYear()+"/"+(G.getMonth()+1)+"/"+G.getDate()+" GMT");var C=D-F;var E=parseInt(C/60000/60/24,10)+1;return A(E,0,100);},k:["getHours"," "],l:function(D){var C=D.getHours()%12;return A(C===0?12:C," ");},m:function(C){return A(C.getMonth()+1,0);},M:["getMinutes","0"],p:function(D,C){return C.p[D.getHours()>=12?1:0];},P:function(D,C){return C.P[D.getHours()>=12?1:0];},s:function(D,C){return parseInt(D.getTime()/1000,10);},S:["getSeconds","0"],u:function(C){var D=C.getDay();return D===0?7:D;},U:function(F){var C=parseInt(B.formats.j(F),10);var E=6-F.getDay();var D=parseInt((C+E)/7,10);return A(D,0);},V:function(F){var E=parseInt(B.formats.W(F),10);var C=(new Date(""+F.getFullYear()+"/1/1")).getDay();var D=E+(C>4||C<=1?0:1);if(D===53&&(new Date(""+F.getFullYear()+"/12/31")).getDay()<4){D=1;}else{if(D===0){D=B.formats.V(new Date(""+(F.getFullYear()-1)+"/12/31"));}}return A(D,0);},w:"getDay",W:function(F){var C=parseInt(B.formats.j(F),10);var E=7-B.formats.u(F);var D=parseInt((C+E)/7,10);return A(D,0,10);},y:function(C){return A(C.getFullYear()%100,0);},Y:"getFullYear",z:function(E){var D=E.getTimezoneOffset();var C=A(parseInt(Math.abs(D/60),10),0);var F=A(Math.abs(D%60),0);return(D>0?"-":"+")+C+F;},Z:function(C){var D=C.toString().replace(/^.*:\d\d( GMT[+-]\d+)? \(?([A-Za-z ]+)\)?\d*$/,"$2").replace(/[a-z ]/g,"");if(D.length>4){D=B.formats.z(C);}return D;},"%":function(C){return"%";}},aggregates:{c:"locale",D:"%m/%d/%y",F:"%Y-%m-%d",h:"%b",n:"\n",r:"locale",R:"%H:%M",t:"\t",T:"%H:%M:%S",x:"locale",X:"locale"},format:function(G,F,D){F=F||{};if(!(G instanceof Date)){return YAHOO.lang.isValue(G)?G:"";}var H=F.format||"%m/%d/%Y";if(H==="YYYY/MM/DD"){H="%Y/%m/%d";}else{if(H==="DD/MM/YYYY"){H="%d/%m/%Y";}else{if(H==="MM/DD/YYYY"){H="%m/%d/%Y";}}}D=D||"en";if(!(D in YAHOO.util.DateLocale)){if(D.replace(/-[a-zA-Z]+$/,"") in YAHOO.util.DateLocale){D=D.replace(/-[a-zA-Z]+$/,"");
+}else{D="en";}}var J=YAHOO.util.DateLocale[D];var C=function(L,K){var M=B.aggregates[K];return(M==="locale"?J[K]:M);};var E=function(L,K){var M=B.formats[K];if(typeof M==="string"){return G[M]();}else{if(typeof M==="function"){return M.call(G,G,J);}else{if(typeof M==="object"&&typeof M[0]==="string"){return A(G[M[0]](),M[1]);}else{return K;}}}};while(H.match(/%[cDFhnrRtTxX]/)){H=H.replace(/%([cDFhnrRtTxX])/g,C);}var I=H.replace(/%([aAbBCdegGHIjklmMpPsSuUVwWyYzZ%])/g,E);C=E=undefined;return I;}};YAHOO.namespace("YAHOO.util");YAHOO.util.Date=B;YAHOO.util.DateLocale={a:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],A:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],b:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],B:["January","February","March","April","May","June","July","August","September","October","November","December"],c:"%a %d %b %Y %T %Z",p:["AM","PM"],P:["am","pm"],r:"%I:%M:%S %p",x:"%d/%m/%y",X:"%T"};YAHOO.util.DateLocale["en"]=YAHOO.lang.merge(YAHOO.util.DateLocale,{});YAHOO.util.DateLocale["en-US"]=YAHOO.lang.merge(YAHOO.util.DateLocale["en"],{c:"%a %d %b %Y %I:%M:%S %p %Z",x:"%m/%d/%Y",X:"%I:%M:%S %p"});YAHOO.util.DateLocale["en-GB"]=YAHOO.lang.merge(YAHOO.util.DateLocale["en"],{r:"%l:%M:%S %P %Z"});YAHOO.util.DateLocale["en-AU"]=YAHOO.lang.merge(YAHOO.util.DateLocale["en"]);})();YAHOO.register("datasource",YAHOO.util.DataSource,{version:"2.8.2r1",build:"7"}); \ No newline at end of file
diff --git a/js/yui/datatable/datatable-min.js b/js/yui/datatable/datatable-min.js
new file mode 100644
index 000000000..bccc4607b
--- /dev/null
+++ b/js/yui/datatable/datatable-min.js
@@ -0,0 +1,29 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+YAHOO.util.Chain=function(){this.q=[].slice.call(arguments);this.createEvent("end");};YAHOO.util.Chain.prototype={id:0,run:function(){var F=this.q[0],C;if(!F){this.fireEvent("end");return this;}else{if(this.id){return this;}}C=F.method||F;if(typeof C==="function"){var E=F.scope||{},B=F.argument||[],A=F.timeout||0,D=this;if(!(B instanceof Array)){B=[B];}if(A<0){this.id=A;if(F.until){for(;!F.until();){C.apply(E,B);}}else{if(F.iterations){for(;F.iterations-->0;){C.apply(E,B);}}else{C.apply(E,B);}}this.q.shift();this.id=0;return this.run();}else{if(F.until){if(F.until()){this.q.shift();return this.run();}}else{if(!F.iterations||!--F.iterations){this.q.shift();}}this.id=setTimeout(function(){C.apply(E,B);if(D.id){D.id=0;D.run();}},A);}}return this;},add:function(A){this.q.push(A);return this;},pause:function(){if(this.id>0){clearTimeout(this.id);}this.id=0;return this;},stop:function(){this.pause();this.q=[];return this;}};YAHOO.lang.augmentProto(YAHOO.util.Chain,YAHOO.util.EventProvider);YAHOO.widget.ColumnSet=function(A){this._sId="yui-cs"+YAHOO.widget.ColumnSet._nCount;A=YAHOO.widget.DataTable._cloneObject(A);this._init(A);YAHOO.widget.ColumnSet._nCount++;};YAHOO.widget.ColumnSet._nCount=0;YAHOO.widget.ColumnSet.prototype={_sId:null,_aDefinitions:null,tree:null,flat:null,keys:null,headers:null,_init:function(I){var J=[];var A=[];var G=[];var E=[];var C=-1;var B=function(M,S){C++;if(!J[C]){J[C]=[];}for(var O=0;O<M.length;O++){var K=M[O];var Q=new YAHOO.widget.Column(K);K.yuiColumnId=Q._sId;A.push(Q);if(S){Q._oParent=S;}if(YAHOO.lang.isArray(K.children)){Q.children=K.children;var R=0;var P=function(V){var W=V.children;for(var U=0;U<W.length;U++){if(YAHOO.lang.isArray(W[U].children)){P(W[U]);}else{R++;}}};P(K);Q._nColspan=R;var T=K.children;for(var N=0;N<T.length;N++){var L=T[N];if(Q.className&&(L.className===undefined)){L.className=Q.className;}if(Q.editor&&(L.editor===undefined)){L.editor=Q.editor;}if(Q.editorOptions&&(L.editorOptions===undefined)){L.editorOptions=Q.editorOptions;}if(Q.formatter&&(L.formatter===undefined)){L.formatter=Q.formatter;}if(Q.resizeable&&(L.resizeable===undefined)){L.resizeable=Q.resizeable;}if(Q.sortable&&(L.sortable===undefined)){L.sortable=Q.sortable;}if(Q.hidden){L.hidden=true;}if(Q.width&&(L.width===undefined)){L.width=Q.width;}if(Q.minWidth&&(L.minWidth===undefined)){L.minWidth=Q.minWidth;}if(Q.maxAutoWidth&&(L.maxAutoWidth===undefined)){L.maxAutoWidth=Q.maxAutoWidth;}if(Q.type&&(L.type===undefined)){L.type=Q.type;}if(Q.type&&!Q.formatter){Q.formatter=Q.type;}if(Q.text&&!YAHOO.lang.isValue(Q.label)){Q.label=Q.text;}if(Q.parser){}if(Q.sortOptions&&((Q.sortOptions.ascFunction)||(Q.sortOptions.descFunction))){}}if(!J[C+1]){J[C+1]=[];}B(T,Q);}else{Q._nKeyIndex=G.length;Q._nColspan=1;G.push(Q);}J[C].push(Q);}C--;};if(YAHOO.lang.isArray(I)){B(I);this._aDefinitions=I;}else{return null;}var F;var D=function(L){var M=1;var O;var N;var P=function(T,S){S=S||1;for(var U=0;U<T.length;U++){var R=T[U];if(YAHOO.lang.isArray(R.children)){S++;P(R.children,S);S--;}else{if(S>M){M=S;}}}};for(var K=0;K<L.length;K++){O=L[K];P(O);for(var Q=0;Q<O.length;Q++){N=O[Q];if(!YAHOO.lang.isArray(N.children)){N._nRowspan=M;}else{N._nRowspan=1;}}M=1;}};D(J);for(F=0;F<J[0].length;F++){J[0][F]._nTreeIndex=F;}var H=function(K,L){E[K].push(L.getSanitizedKey());if(L._oParent){H(K,L._oParent);}};for(F=0;F<G.length;F++){E[F]=[];H(F,G[F]);E[F]=E[F].reverse();}this.tree=J;this.flat=A;this.keys=G;this.headers=E;},getId:function(){return this._sId;},toString:function(){return"ColumnSet instance "+this._sId;},getDefinitions:function(){var A=this._aDefinitions;var B=function(E,G){for(var D=0;D<E.length;D++){var F=E[D];var I=G.getColumnById(F.yuiColumnId);if(I){var H=I.getDefinition();for(var C in H){if(YAHOO.lang.hasOwnProperty(H,C)){F[C]=H[C];}}}if(YAHOO.lang.isArray(F.children)){B(F.children,G);}}};B(A,this);this._aDefinitions=A;return A;},getColumnById:function(C){if(YAHOO.lang.isString(C)){var A=this.flat;for(var B=A.length-1;B>-1;B--){if(A[B]._sId===C){return A[B];}}}return null;},getColumn:function(C){if(YAHOO.lang.isNumber(C)&&this.keys[C]){return this.keys[C];}else{if(YAHOO.lang.isString(C)){var A=this.flat;var D=[];for(var B=0;B<A.length;B++){if(A[B].key===C){D.push(A[B]);}}if(D.length===1){return D[0];}else{if(D.length>1){return D;}}}}return null;},getDescendants:function(D){var B=this;var C=[];var A;var E=function(F){C.push(F);if(F.children){for(A=0;A<F.children.length;A++){E(B.getColumn(F.children[A].key));}}};E(D);return C;}};YAHOO.widget.Column=function(B){this._sId="yui-col"+YAHOO.widget.Column._nCount;if(B&&YAHOO.lang.isObject(B)){for(var A in B){if(A){this[A]=B[A];}}}if(!YAHOO.lang.isValue(this.key)){this.key="yui-dt-col"+YAHOO.widget.Column._nCount;}if(!YAHOO.lang.isValue(this.field)){this.field=this.key;}YAHOO.widget.Column._nCount++;if(this.width&&!YAHOO.lang.isNumber(this.width)){this.width=null;}if(this.editor&&YAHOO.lang.isString(this.editor)){this.editor=new YAHOO.widget.CellEditor(this.editor,this.editorOptions);}};YAHOO.lang.augmentObject(YAHOO.widget.Column,{_nCount:0,formatCheckbox:function(B,A,C,D){YAHOO.widget.DataTable.formatCheckbox(B,A,C,D);},formatCurrency:function(B,A,C,D){YAHOO.widget.DataTable.formatCurrency(B,A,C,D);},formatDate:function(B,A,C,D){YAHOO.widget.DataTable.formatDate(B,A,C,D);},formatEmail:function(B,A,C,D){YAHOO.widget.DataTable.formatEmail(B,A,C,D);},formatLink:function(B,A,C,D){YAHOO.widget.DataTable.formatLink(B,A,C,D);},formatNumber:function(B,A,C,D){YAHOO.widget.DataTable.formatNumber(B,A,C,D);},formatSelect:function(B,A,C,D){YAHOO.widget.DataTable.formatDropdown(B,A,C,D);}});YAHOO.widget.Column.prototype={_sId:null,_nKeyIndex:null,_nTreeIndex:null,_nColspan:1,_nRowspan:1,_oParent:null,_elTh:null,_elThLiner:null,_elThLabel:null,_elResizer:null,_nWidth:null,_dd:null,_ddResizer:null,key:null,field:null,label:null,abbr:null,children:null,width:null,minWidth:null,maxAutoWidth:null,hidden:false,selected:false,className:null,formatter:null,currencyOptions:null,dateOptions:null,dropdownOptions:null,editor:null,resizeable:false,sortable:false,sortOptions:null,getId:function(){return this._sId;
+},toString:function(){return"Column instance "+this._sId;},getDefinition:function(){var A={};A.abbr=this.abbr;A.className=this.className;A.editor=this.editor;A.editorOptions=this.editorOptions;A.field=this.field;A.formatter=this.formatter;A.hidden=this.hidden;A.key=this.key;A.label=this.label;A.minWidth=this.minWidth;A.maxAutoWidth=this.maxAutoWidth;A.resizeable=this.resizeable;A.selected=this.selected;A.sortable=this.sortable;A.sortOptions=this.sortOptions;A.width=this.width;return A;},getKey:function(){return this.key;},getField:function(){return this.field;},getSanitizedKey:function(){return this.getKey().replace(/[^\w\-]/g,"");},getKeyIndex:function(){return this._nKeyIndex;},getTreeIndex:function(){return this._nTreeIndex;},getParent:function(){return this._oParent;},getColspan:function(){return this._nColspan;},getColSpan:function(){return this.getColspan();},getRowspan:function(){return this._nRowspan;},getThEl:function(){return this._elTh;},getThLinerEl:function(){return this._elThLiner;},getResizerEl:function(){return this._elResizer;},getColEl:function(){return this.getThEl();},getIndex:function(){return this.getKeyIndex();},format:function(){}};YAHOO.util.Sort={compare:function(B,A,C){if((B===null)||(typeof B=="undefined")){if((A===null)||(typeof A=="undefined")){return 0;}else{return 1;}}else{if((A===null)||(typeof A=="undefined")){return -1;}}if(B.constructor==String){B=B.toLowerCase();}if(A.constructor==String){A=A.toLowerCase();}if(B<A){return(C)?1:-1;}else{if(B>A){return(C)?-1:1;}else{return 0;}}}};YAHOO.widget.ColumnDD=function(D,A,C,B){if(D&&A&&C&&B){this.datatable=D;this.table=D.getTableEl();this.column=A;this.headCell=C;this.pointer=B;this.newIndex=null;this.init(C);this.initFrame();this.invalidHandleTypes={};this.setPadding(10,0,(this.datatable.getTheadEl().offsetHeight+10),0);YAHOO.util.Event.on(window,"resize",function(){this.initConstraints();},this,true);}else{}};if(YAHOO.util.DDProxy){YAHOO.extend(YAHOO.widget.ColumnDD,YAHOO.util.DDProxy,{initConstraints:function(){var G=YAHOO.util.Dom.getRegion(this.table),D=this.getEl(),F=YAHOO.util.Dom.getXY(D),C=parseInt(YAHOO.util.Dom.getStyle(D,"width"),10),A=parseInt(YAHOO.util.Dom.getStyle(D,"height"),10),E=((F[0]-G.left)+15),B=((G.right-F[0]-C)+15);this.setXConstraint(E,B);this.setYConstraint(10,10);},_resizeProxy:function(){YAHOO.widget.ColumnDD.superclass._resizeProxy.apply(this,arguments);var A=this.getDragEl(),B=this.getEl();YAHOO.util.Dom.setStyle(this.pointer,"height",(this.table.parentNode.offsetHeight+10)+"px");YAHOO.util.Dom.setStyle(this.pointer,"display","block");var C=YAHOO.util.Dom.getXY(B);YAHOO.util.Dom.setXY(this.pointer,[C[0],(C[1]-5)]);YAHOO.util.Dom.setStyle(A,"height",this.datatable.getContainerEl().offsetHeight+"px");YAHOO.util.Dom.setStyle(A,"width",(parseInt(YAHOO.util.Dom.getStyle(A,"width"),10)+4)+"px");YAHOO.util.Dom.setXY(this.dragEl,C);},onMouseDown:function(){this.initConstraints();this.resetConstraints();},clickValidator:function(B){if(!this.column.hidden){var A=YAHOO.util.Event.getTarget(B);return(this.isValidHandleChild(A)&&(this.id==this.handleElId||this.DDM.handleWasClicked(A,this.id)));}},onDragOver:function(H,A){var F=this.datatable.getColumn(A);if(F){var C=F.getTreeIndex();while((C===null)&&F.getParent()){F=F.getParent();C=F.getTreeIndex();}if(C!==null){var B=F.getThEl();var K=C;var D=YAHOO.util.Event.getPageX(H),I=YAHOO.util.Dom.getX(B),J=I+((YAHOO.util.Dom.get(B).offsetWidth)/2),E=this.column.getTreeIndex();if(D<J){YAHOO.util.Dom.setX(this.pointer,I);}else{var G=parseInt(B.offsetWidth,10);YAHOO.util.Dom.setX(this.pointer,(I+G));K++;}if(C>E){K--;}if(K<0){K=0;}else{if(K>this.datatable.getColumnSet().tree[0].length){K=this.datatable.getColumnSet().tree[0].length;}}this.newIndex=K;}}},onDragDrop:function(){this.datatable.reorderColumn(this.column,this.newIndex);},endDrag:function(){this.newIndex=null;YAHOO.util.Dom.setStyle(this.pointer,"display","none");}});}YAHOO.util.ColumnResizer=function(E,C,D,A,B){if(E&&C&&D&&A){this.datatable=E;this.column=C;this.headCell=D;this.headCellLiner=C.getThLinerEl();this.resizerLiner=D.firstChild;this.init(A,A,{dragOnly:true,dragElId:B.id});this.initFrame();this.resetResizerEl();this.setPadding(0,1,0,0);}else{}};if(YAHOO.util.DD){YAHOO.extend(YAHOO.util.ColumnResizer,YAHOO.util.DDProxy,{resetResizerEl:function(){var A=YAHOO.util.Dom.get(this.handleElId).style;A.left="auto";A.right=0;A.top="auto";A.bottom=0;A.height=this.headCell.offsetHeight+"px";},onMouseUp:function(G){var E=this.datatable.getColumnSet().keys,B;for(var C=0,A=E.length;C<A;C++){B=E[C];if(B._ddResizer){B._ddResizer.resetResizerEl();}}this.resetResizerEl();var D=this.headCellLiner;var F=D.offsetWidth-(parseInt(YAHOO.util.Dom.getStyle(D,"paddingLeft"),10)|0)-(parseInt(YAHOO.util.Dom.getStyle(D,"paddingRight"),10)|0);this.datatable.fireEvent("columnResizeEvent",{column:this.column,target:this.headCell,width:F});},onMouseDown:function(A){this.startWidth=this.headCellLiner.offsetWidth;this.startX=YAHOO.util.Event.getXY(A)[0];this.nLinerPadding=(parseInt(YAHOO.util.Dom.getStyle(this.headCellLiner,"paddingLeft"),10)|0)+(parseInt(YAHOO.util.Dom.getStyle(this.headCellLiner,"paddingRight"),10)|0);},clickValidator:function(B){if(!this.column.hidden){var A=YAHOO.util.Event.getTarget(B);return(this.isValidHandleChild(A)&&(this.id==this.handleElId||this.DDM.handleWasClicked(A,this.id)));}},startDrag:function(){var E=this.datatable.getColumnSet().keys,D=this.column.getKeyIndex(),B;for(var C=0,A=E.length;C<A;C++){B=E[C];if(B._ddResizer){YAHOO.util.Dom.get(B._ddResizer.handleElId).style.height="1em";}}},onDrag:function(C){var D=YAHOO.util.Event.getXY(C)[0];if(D>YAHOO.util.Dom.getX(this.headCellLiner)){var A=D-this.startX;var B=this.startWidth+A-this.nLinerPadding;if(B>0){this.datatable.setColumnWidth(this.column,B);}}}});}(function(){var G=YAHOO.lang,A=YAHOO.util,E=YAHOO.widget,C=A.Dom,F=A.Event,D=E.DataTable;YAHOO.widget.RecordSet=function(H){this._sId="yui-rs"+E.RecordSet._nCount;E.RecordSet._nCount++;this._records=[];
+if(H){if(G.isArray(H)){this.addRecords(H);}else{if(G.isObject(H)){this.addRecord(H);}}}};var B=E.RecordSet;B._nCount=0;B.prototype={_sId:null,_addRecord:function(J,H){var I=new YAHOO.widget.Record(J);if(YAHOO.lang.isNumber(H)&&(H>-1)){this._records.splice(H,0,I);}else{this._records[this._records.length]=I;}return I;},_setRecord:function(I,H){if(!G.isNumber(H)||H<0){H=this._records.length;}return(this._records[H]=new E.Record(I));},_deleteRecord:function(I,H){if(!G.isNumber(H)||(H<0)){H=1;}this._records.splice(I,H);},getId:function(){return this._sId;},toString:function(){return"RecordSet instance "+this._sId;},getLength:function(){return this._records.length;},getRecord:function(H){var I;if(H instanceof E.Record){for(I=0;I<this._records.length;I++){if(this._records[I]&&(this._records[I]._sId===H._sId)){return H;}}}else{if(G.isNumber(H)){if((H>-1)&&(H<this.getLength())){return this._records[H];}}else{if(G.isString(H)){for(I=0;I<this._records.length;I++){if(this._records[I]&&(this._records[I]._sId===H)){return this._records[I];}}}}}return null;},getRecords:function(I,H){if(!G.isNumber(I)){return this._records;}if(!G.isNumber(H)){return this._records.slice(I);}return this._records.slice(I,I+H);},hasRecords:function(I,H){var K=this.getRecords(I,H);for(var J=0;J<H;++J){if(typeof K[J]==="undefined"){return false;}}return true;},getRecordIndex:function(I){if(I){for(var H=this._records.length-1;H>-1;H--){if(this._records[H]&&I.getId()===this._records[H].getId()){return H;}}}return null;},addRecord:function(J,H){if(G.isObject(J)){var I=this._addRecord(J,H);this.fireEvent("recordAddEvent",{record:I,data:J});return I;}else{return null;}},addRecords:function(L,K){if(G.isArray(L)){var O=[],I,M,H;K=G.isNumber(K)?K:this._records.length;I=K;for(M=0,H=L.length;M<H;++M){if(G.isObject(L[M])){var J=this._addRecord(L[M],I++);O.push(J);}}this.fireEvent("recordsAddEvent",{records:O,data:L});return O;}else{if(G.isObject(L)){var N=this._addRecord(L);this.fireEvent("recordsAddEvent",{records:[N],data:L});return N;}else{return null;}}},setRecord:function(J,H){if(G.isObject(J)){var I=this._setRecord(J,H);this.fireEvent("recordSetEvent",{record:I,data:J});return I;}else{return null;}},setRecords:function(L,K){var O=E.Record,I=G.isArray(L)?L:[L],N=[],M=0,H=I.length,J=0;K=parseInt(K,10)|0;for(;M<H;++M){if(typeof I[M]==="object"&&I[M]){N[J++]=this._records[K+M]=new O(I[M]);}}this.fireEvent("recordsSetEvent",{records:N,data:L});this.fireEvent("recordsSet",{records:N,data:L});if(I.length&&!N.length){}return N.length>1?N:N[0];},updateRecord:function(H,L){var J=this.getRecord(H);if(J&&G.isObject(L)){var K={};for(var I in J._oData){if(G.hasOwnProperty(J._oData,I)){K[I]=J._oData[I];}}J._oData=L;this.fireEvent("recordUpdateEvent",{record:J,newData:L,oldData:K});return J;}else{return null;}},updateKey:function(H,I,J){this.updateRecordValue(H,I,J);},updateRecordValue:function(H,K,N){var J=this.getRecord(H);if(J){var M=null;var L=J._oData[K];if(L&&G.isObject(L)){M={};for(var I in L){if(G.hasOwnProperty(L,I)){M[I]=L[I];}}}else{M=L;}J._oData[K]=N;this.fireEvent("keyUpdateEvent",{record:J,key:K,newData:N,oldData:M});this.fireEvent("recordValueUpdateEvent",{record:J,key:K,newData:N,oldData:M});}else{}},replaceRecords:function(H){this.reset();return this.addRecords(H);},sortRecords:function(H,J,I){return this._records.sort(function(L,K){return H(L,K,J,I);});},reverseRecords:function(){return this._records.reverse();},deleteRecord:function(H){if(G.isNumber(H)&&(H>-1)&&(H<this.getLength())){var I=E.DataTable._cloneObject(this.getRecord(H).getData());this._deleteRecord(H);this.fireEvent("recordDeleteEvent",{data:I,index:H});return I;}else{return null;}},deleteRecords:function(J,H){if(!G.isNumber(H)){H=1;}if(G.isNumber(J)&&(J>-1)&&(J<this.getLength())){var L=this.getRecords(J,H);var I=[];for(var K=0;K<L.length;K++){I[I.length]=E.DataTable._cloneObject(L[K]);}this._deleteRecord(J,H);this.fireEvent("recordsDeleteEvent",{data:I,index:J});return I;}else{return null;}},reset:function(){this._records=[];this.fireEvent("resetEvent");}};G.augmentProto(B,A.EventProvider);YAHOO.widget.Record=function(H){this._nCount=E.Record._nCount;this._sId="yui-rec"+this._nCount;E.Record._nCount++;this._oData={};if(G.isObject(H)){for(var I in H){if(G.hasOwnProperty(H,I)){this._oData[I]=H[I];}}}};YAHOO.widget.Record._nCount=0;YAHOO.widget.Record.prototype={_nCount:null,_sId:null,_oData:null,getCount:function(){return this._nCount;},getId:function(){return this._sId;},getData:function(H){if(G.isString(H)){return this._oData[H];}else{return this._oData;}},setData:function(H,I){this._oData[H]=I;}};})();(function(){var H=YAHOO.lang,A=YAHOO.util,E=YAHOO.widget,B=YAHOO.env.ua,C=A.Dom,G=A.Event,F=A.DataSourceBase;YAHOO.widget.DataTable=function(I,M,O,K){var L=E.DataTable;if(K&&K.scrollable){return new YAHOO.widget.ScrollingDataTable(I,M,O,K);}this._nIndex=L._nCount;this._sId="yui-dt"+this._nIndex;this._oChainRender=new YAHOO.util.Chain();this._oChainRender.subscribe("end",this._onRenderChainEnd,this,true);this._initConfigs(K);this._initDataSource(O);if(!this._oDataSource){return;}this._initColumnSet(M);if(!this._oColumnSet){return;}this._initRecordSet();if(!this._oRecordSet){}L.superclass.constructor.call(this,I,this.configs);var Q=this._initDomElements(I);if(!Q){return;}this.showTableMessage(this.get("MSG_LOADING"),L.CLASS_LOADING);this._initEvents();L._nCount++;L._nCurrentCount++;var N={success:this.onDataReturnSetRows,failure:this.onDataReturnSetRows,scope:this,argument:this.getState()};var P=this.get("initialLoad");if(P===true){this._oDataSource.sendRequest(this.get("initialRequest"),N);}else{if(P===false){this.showTableMessage(this.get("MSG_EMPTY"),L.CLASS_EMPTY);}else{var J=P||{};N.argument=J.argument||{};this._oDataSource.sendRequest(J.request,N);}}};var D=E.DataTable;H.augmentObject(D,{CLASS_DATATABLE:"yui-dt",CLASS_LINER:"yui-dt-liner",CLASS_LABEL:"yui-dt-label",CLASS_MESSAGE:"yui-dt-message",CLASS_MASK:"yui-dt-mask",CLASS_DATA:"yui-dt-data",CLASS_COLTARGET:"yui-dt-coltarget",CLASS_RESIZER:"yui-dt-resizer",CLASS_RESIZERLINER:"yui-dt-resizerliner",CLASS_RESIZERPROXY:"yui-dt-resizerproxy",CLASS_EDITOR:"yui-dt-editor",CLASS_PAGINATOR:"yui-dt-paginator",CLASS_PAGE:"yui-dt-page",CLASS_DEFAULT:"yui-dt-default",CLASS_PREVIOUS:"yui-dt-previous",CLASS_NEXT:"yui-dt-next",CLASS_FIRST:"yui-dt-first",CLASS_LAST:"yui-dt-last",CLASS_EVEN:"yui-dt-even",CLASS_ODD:"yui-dt-odd",CLASS_SELECTED:"yui-dt-selected",CLASS_HIGHLIGHTED:"yui-dt-highlighted",CLASS_HIDDEN:"yui-dt-hidden",CLASS_DISABLED:"yui-dt-disabled",CLASS_EMPTY:"yui-dt-empty",CLASS_LOADING:"yui-dt-loading",CLASS_ERROR:"yui-dt-error",CLASS_EDITABLE:"yui-dt-editable",CLASS_DRAGGABLE:"yui-dt-draggable",CLASS_RESIZEABLE:"yui-dt-resizeable",CLASS_SCROLLABLE:"yui-dt-scrollable",CLASS_SORTABLE:"yui-dt-sortable",CLASS_ASC:"yui-dt-asc",CLASS_DESC:"yui-dt-desc",CLASS_BUTTON:"yui-dt-button",CLASS_CHECKBOX:"yui-dt-checkbox",CLASS_DROPDOWN:"yui-dt-dropdown",CLASS_RADIO:"yui-dt-radio",_nCount:0,_nCurrentCount:0,_elDynStyleNode:null,_bDynStylesFallback:(B.ie)?true:false,_oDynStyles:{},_elColumnDragTarget:null,_elColumnResizerProxy:null,_cloneObject:function(L){if(!H.isValue(L)){return L;
+}var N={};if(L instanceof YAHOO.widget.BaseCellEditor){N=L;}else{if(H.isFunction(L)){N=L;}else{if(H.isArray(L)){var M=[];for(var K=0,J=L.length;K<J;K++){M[K]=D._cloneObject(L[K]);}N=M;}else{if(H.isObject(L)){for(var I in L){if(H.hasOwnProperty(L,I)){if(H.isValue(L[I])&&H.isObject(L[I])||H.isArray(L[I])){N[I]=D._cloneObject(L[I]);}else{N[I]=L[I];}}}}else{N=L;}}}}return N;},_destroyColumnDragTargetEl:function(){if(D._elColumnDragTarget){var I=D._elColumnDragTarget;YAHOO.util.Event.purgeElement(I);I.parentNode.removeChild(I);D._elColumnDragTarget=null;}},_initColumnDragTargetEl:function(){if(!D._elColumnDragTarget){var I=document.createElement("div");I.className=D.CLASS_COLTARGET;I.style.display="none";document.body.insertBefore(I,document.body.firstChild);D._elColumnDragTarget=I;}return D._elColumnDragTarget;},_destroyColumnResizerProxyEl:function(){if(D._elColumnResizerProxy){var I=D._elColumnResizerProxy;YAHOO.util.Event.purgeElement(I);I.parentNode.removeChild(I);D._elColumnResizerProxy=null;}},_initColumnResizerProxyEl:function(){if(!D._elColumnResizerProxy){var I=document.createElement("div");I.id="yui-dt-colresizerproxy";I.className=D.CLASS_RESIZERPROXY;document.body.insertBefore(I,document.body.firstChild);D._elColumnResizerProxy=I;}return D._elColumnResizerProxy;},formatButton:function(I,J,K,M){var L=H.isValue(M)?M:"Click";I.innerHTML='<button type="button" class="'+D.CLASS_BUTTON+'">'+L+"</button>";},formatCheckbox:function(I,J,K,M){var L=M;L=(L)?' checked="checked"':"";I.innerHTML='<input type="checkbox"'+L+' class="'+D.CLASS_CHECKBOX+'" />';},formatCurrency:function(I,J,K,L){I.innerHTML=A.Number.format(L,K.currencyOptions||this.get("currencyOptions"));},formatDate:function(I,K,L,M){var J=L.dateOptions||this.get("dateOptions");I.innerHTML=A.Date.format(M,J,J.locale);},formatDropdown:function(K,R,P,I){var Q=(H.isValue(I))?I:R.getData(P.field),S=(H.isArray(P.dropdownOptions))?P.dropdownOptions:null,J,O=K.getElementsByTagName("select");if(O.length===0){J=document.createElement("select");J.className=D.CLASS_DROPDOWN;J=K.appendChild(J);G.addListener(J,"change",this._onDropdownChange,this);}J=O[0];if(J){J.innerHTML="";if(S){for(var M=0;M<S.length;M++){var N=S[M];var L=document.createElement("option");L.value=(H.isValue(N.value))?N.value:N;L.innerHTML=(H.isValue(N.text))?N.text:(H.isValue(N.label))?N.label:N;L=J.appendChild(L);if(L.value==Q){L.selected=true;}}}else{J.innerHTML='<option selected value="'+Q+'">'+Q+"</option>";}}else{K.innerHTML=H.isValue(I)?I:"";}},formatEmail:function(I,J,K,L){if(H.isString(L)){I.innerHTML='<a href="mailto:'+L+'">'+L+"</a>";}else{I.innerHTML=H.isValue(L)?L:"";}},formatLink:function(I,J,K,L){if(H.isString(L)){I.innerHTML='<a href="'+L+'">'+L+"</a>";}else{I.innerHTML=H.isValue(L)?L:"";}},formatNumber:function(I,J,K,L){I.innerHTML=A.Number.format(L,K.numberOptions||this.get("numberOptions"));},formatRadio:function(I,J,K,M){var L=M;L=(L)?' checked="checked"':"";I.innerHTML='<input type="radio"'+L+' name="'+this.getId()+"-col-"+K.getSanitizedKey()+'"'+' class="'+D.CLASS_RADIO+'" />';},formatText:function(I,J,L,M){var K=(H.isValue(M))?M:"";I.innerHTML=K.toString().replace(/&/g,"&#38;").replace(/</g,"&#60;").replace(/>/g,"&#62;");},formatTextarea:function(J,K,M,N){var L=(H.isValue(N))?N:"",I="<textarea>"+L+"</textarea>";J.innerHTML=I;},formatTextbox:function(J,K,M,N){var L=(H.isValue(N))?N:"",I='<input type="text" value="'+L+'" />';J.innerHTML=I;},formatDefault:function(I,J,K,L){I.innerHTML=L===undefined||L===null||(typeof L==="number"&&isNaN(L))?"&#160;":L.toString();},validateNumber:function(J){var I=J*1;if(H.isNumber(I)){return I;}else{return undefined;}}});D.Formatter={button:D.formatButton,checkbox:D.formatCheckbox,currency:D.formatCurrency,"date":D.formatDate,dropdown:D.formatDropdown,email:D.formatEmail,link:D.formatLink,"number":D.formatNumber,radio:D.formatRadio,text:D.formatText,textarea:D.formatTextarea,textbox:D.formatTextbox,defaultFormatter:D.formatDefault};H.extend(D,A.Element,{initAttributes:function(I){I=I||{};D.superclass.initAttributes.call(this,I);this.setAttributeConfig("summary",{value:"",validator:H.isString,method:function(J){if(this._elTable){this._elTable.summary=J;}}});this.setAttributeConfig("selectionMode",{value:"standard",validator:H.isString});this.setAttributeConfig("sortedBy",{value:null,validator:function(J){if(J){return(H.isObject(J)&&J.key);}else{return(J===null);}},method:function(K){var R=this.get("sortedBy");this._configs.sortedBy.value=K;var J,O,M,Q;if(this._elThead){if(R&&R.key&&R.dir){J=this._oColumnSet.getColumn(R.key);O=J.getKeyIndex();var U=J.getThEl();C.removeClass(U,R.dir);this.formatTheadCell(J.getThLinerEl().firstChild,J,K);}if(K){M=(K.column)?K.column:this._oColumnSet.getColumn(K.key);Q=M.getKeyIndex();var V=M.getThEl();if(K.dir&&((K.dir=="asc")||(K.dir=="desc"))){var P=(K.dir=="desc")?D.CLASS_DESC:D.CLASS_ASC;C.addClass(V,P);}else{var L=K.dir||D.CLASS_ASC;C.addClass(V,L);}this.formatTheadCell(M.getThLinerEl().firstChild,M,K);}}if(this._elTbody){this._elTbody.style.display="none";var S=this._elTbody.rows,T;for(var N=S.length-1;N>-1;N--){T=S[N].childNodes;if(T[O]){C.removeClass(T[O],R.dir);}if(T[Q]){C.addClass(T[Q],K.dir);}}this._elTbody.style.display="";}this._clearTrTemplateEl();}});this.setAttributeConfig("paginator",{value:null,validator:function(J){return J===null||J instanceof E.Paginator;},method:function(){this._updatePaginator.apply(this,arguments);}});this.setAttributeConfig("caption",{value:null,validator:H.isString,method:function(J){this._initCaptionEl(J);}});this.setAttributeConfig("draggableColumns",{value:false,validator:H.isBoolean,method:function(J){if(this._elThead){if(J){this._initDraggableColumns();}else{this._destroyDraggableColumns();}}}});this.setAttributeConfig("renderLoopSize",{value:0,validator:H.isNumber});this.setAttributeConfig("formatRow",{value:null,validator:H.isFunction});this.setAttributeConfig("generateRequest",{value:function(K,N){K=K||{pagination:null,sortedBy:null};var M=encodeURIComponent((K.sortedBy)?K.sortedBy.key:N.getColumnSet().keys[0].getKey());
+var J=(K.sortedBy&&K.sortedBy.dir===YAHOO.widget.DataTable.CLASS_DESC)?"desc":"asc";var O=(K.pagination)?K.pagination.recordOffset:0;var L=(K.pagination)?K.pagination.rowsPerPage:null;return"sort="+M+"&dir="+J+"&startIndex="+O+((L!==null)?"&results="+L:"");},validator:H.isFunction});this.setAttributeConfig("initialRequest",{value:null});this.setAttributeConfig("initialLoad",{value:true});this.setAttributeConfig("dynamicData",{value:false,validator:H.isBoolean});this.setAttributeConfig("MSG_EMPTY",{value:"No records found.",validator:H.isString});this.setAttributeConfig("MSG_LOADING",{value:"Loading...",validator:H.isString});this.setAttributeConfig("MSG_ERROR",{value:"Data error.",validator:H.isString});this.setAttributeConfig("MSG_SORTASC",{value:"Click to sort ascending",validator:H.isString,method:function(K){if(this._elThead){for(var L=0,M=this.getColumnSet().keys,J=M.length;L<J;L++){if(M[L].sortable&&this.getColumnSortDir(M[L])===D.CLASS_ASC){M[L]._elThLabel.firstChild.title=K;}}}}});this.setAttributeConfig("MSG_SORTDESC",{value:"Click to sort descending",validator:H.isString,method:function(K){if(this._elThead){for(var L=0,M=this.getColumnSet().keys,J=M.length;L<J;L++){if(M[L].sortable&&this.getColumnSortDir(M[L])===D.CLASS_DESC){M[L]._elThLabel.firstChild.title=K;}}}}});this.setAttributeConfig("currencySymbol",{value:"$",validator:H.isString});this.setAttributeConfig("currencyOptions",{value:{prefix:this.get("currencySymbol"),decimalPlaces:2,decimalSeparator:".",thousandsSeparator:","}});this.setAttributeConfig("dateOptions",{value:{format:"%m/%d/%Y",locale:"en"}});this.setAttributeConfig("numberOptions",{value:{decimalPlaces:0,thousandsSeparator:","}});},_bInit:true,_nIndex:null,_nTrCount:0,_nTdCount:0,_sId:null,_oChainRender:null,_elContainer:null,_elMask:null,_elTable:null,_elCaption:null,_elColgroup:null,_elThead:null,_elTbody:null,_elMsgTbody:null,_elMsgTr:null,_elMsgTd:null,_oDataSource:null,_oColumnSet:null,_oRecordSet:null,_oCellEditor:null,_sFirstTrId:null,_sLastTrId:null,_elTrTemplate:null,_aDynFunctions:[],clearTextSelection:function(){var I;if(window.getSelection){I=window.getSelection();}else{if(document.getSelection){I=document.getSelection();}else{if(document.selection){I=document.selection;}}}if(I){if(I.empty){I.empty();}else{if(I.removeAllRanges){I.removeAllRanges();}else{if(I.collapse){I.collapse();}}}}},_focusEl:function(I){I=I||this._elTbody;setTimeout(function(){try{I.focus();}catch(J){}},0);},_repaintGecko:(B.gecko)?function(J){J=J||this._elContainer;var I=J.parentNode;var K=J.nextSibling;I.insertBefore(I.removeChild(J),K);}:function(){},_repaintOpera:(B.opera)?function(){if(B.opera){document.documentElement.className+=" ";document.documentElement.className=YAHOO.lang.trim(document.documentElement.className);}}:function(){},_repaintWebkit:(B.webkit)?function(J){J=J||this._elContainer;var I=J.parentNode;var K=J.nextSibling;I.insertBefore(I.removeChild(J),K);}:function(){},_initConfigs:function(I){if(!I||!H.isObject(I)){I={};}this.configs=I;},_initColumnSet:function(M){var L,J,I;if(this._oColumnSet){for(J=0,I=this._oColumnSet.keys.length;J<I;J++){L=this._oColumnSet.keys[J];D._oDynStyles["."+this.getId()+"-col-"+L.getSanitizedKey()+" ."+D.CLASS_LINER]=undefined;if(L.editor&&L.editor.unsubscribeAll){L.editor.unsubscribeAll();}}this._oColumnSet=null;this._clearTrTemplateEl();}if(H.isArray(M)){this._oColumnSet=new YAHOO.widget.ColumnSet(M);}else{if(M instanceof YAHOO.widget.ColumnSet){this._oColumnSet=M;}}var K=this._oColumnSet.keys;for(J=0,I=K.length;J<I;J++){L=K[J];if(L.editor&&L.editor.subscribe){L.editor.subscribe("showEvent",this._onEditorShowEvent,this,true);L.editor.subscribe("keydownEvent",this._onEditorKeydownEvent,this,true);L.editor.subscribe("revertEvent",this._onEditorRevertEvent,this,true);L.editor.subscribe("saveEvent",this._onEditorSaveEvent,this,true);L.editor.subscribe("cancelEvent",this._onEditorCancelEvent,this,true);L.editor.subscribe("blurEvent",this._onEditorBlurEvent,this,true);L.editor.subscribe("blockEvent",this._onEditorBlockEvent,this,true);L.editor.subscribe("unblockEvent",this._onEditorUnblockEvent,this,true);}}},_initDataSource:function(I){this._oDataSource=null;if(I&&(H.isFunction(I.sendRequest))){this._oDataSource=I;}else{var J=null;var N=this._elContainer;var K=0;if(N.hasChildNodes()){var M=N.childNodes;for(K=0;K<M.length;K++){if(M[K].nodeName&&M[K].nodeName.toLowerCase()=="table"){J=M[K];break;}}if(J){var L=[];for(;K<this._oColumnSet.keys.length;K++){L.push({key:this._oColumnSet.keys[K].key});}this._oDataSource=new F(J);this._oDataSource.responseType=F.TYPE_HTMLTABLE;this._oDataSource.responseSchema={fields:L};}}}},_initRecordSet:function(){if(this._oRecordSet){this._oRecordSet.reset();}else{this._oRecordSet=new YAHOO.widget.RecordSet();}},_initDomElements:function(I){this._initContainerEl(I);this._initTableEl(this._elContainer);this._initColgroupEl(this._elTable);this._initTheadEl(this._elTable);this._initMsgTbodyEl(this._elTable);this._initTbodyEl(this._elTable);if(!this._elContainer||!this._elTable||!this._elColgroup||!this._elThead||!this._elTbody||!this._elMsgTbody){return false;}else{return true;}},_destroyContainerEl:function(I){C.removeClass(I,D.CLASS_DATATABLE);G.purgeElement(I,true);I.innerHTML="";this._elContainer=null;this._elColgroup=null;this._elThead=null;this._elTbody=null;},_initContainerEl:function(J){J=C.get(J);if(J&&J.nodeName&&(J.nodeName.toLowerCase()=="div")){this._destroyContainerEl(J);C.addClass(J,D.CLASS_DATATABLE);G.addListener(J,"focus",this._onTableFocus,this);G.addListener(J,"dblclick",this._onTableDblclick,this);this._elContainer=J;var I=document.createElement("div");I.className=D.CLASS_MASK;I.style.display="none";this._elMask=J.appendChild(I);}},_destroyTableEl:function(){var I=this._elTable;if(I){G.purgeElement(I,true);I.parentNode.removeChild(I);this._elCaption=null;this._elColgroup=null;this._elThead=null;this._elTbody=null;}},_initCaptionEl:function(I){if(this._elTable&&I){if(!this._elCaption){this._elCaption=this._elTable.createCaption();
+}this._elCaption.innerHTML=I;}else{if(this._elCaption){this._elCaption.parentNode.removeChild(this._elCaption);}}},_initTableEl:function(I){if(I){this._destroyTableEl();this._elTable=I.appendChild(document.createElement("table"));this._elTable.summary=this.get("summary");if(this.get("caption")){this._initCaptionEl(this.get("caption"));}}},_destroyColgroupEl:function(){var I=this._elColgroup;if(I){var J=I.parentNode;G.purgeElement(I,true);J.removeChild(I);this._elColgroup=null;}},_initColgroupEl:function(R){if(R){this._destroyColgroupEl();var K=this._aColIds||[],Q=this._oColumnSet.keys,L=0,O=K.length,I,N,P=document.createDocumentFragment(),M=document.createElement("col");for(L=0,O=Q.length;L<O;L++){N=Q[L];I=P.appendChild(M.cloneNode(false));}var J=R.insertBefore(document.createElement("colgroup"),R.firstChild);J.appendChild(P);this._elColgroup=J;}},_insertColgroupColEl:function(I){if(H.isNumber(I)&&this._elColgroup){var J=this._elColgroup.childNodes[I]||null;this._elColgroup.insertBefore(document.createElement("col"),J);}},_removeColgroupColEl:function(I){if(H.isNumber(I)&&this._elColgroup&&this._elColgroup.childNodes[I]){this._elColgroup.removeChild(this._elColgroup.childNodes[I]);}},_reorderColgroupColEl:function(K,J){if(H.isArray(K)&&H.isNumber(J)&&this._elColgroup&&(this._elColgroup.childNodes.length>K[K.length-1])){var I,M=[];for(I=K.length-1;I>-1;I--){M.push(this._elColgroup.removeChild(this._elColgroup.childNodes[K[I]]));}var L=this._elColgroup.childNodes[J]||null;for(I=M.length-1;I>-1;I--){this._elColgroup.insertBefore(M[I],L);}}},_destroyTheadEl:function(){var J=this._elThead;if(J){var I=J.parentNode;G.purgeElement(J,true);this._destroyColumnHelpers();I.removeChild(J);this._elThead=null;}},_initTheadEl:function(S){S=S||this._elTable;if(S){this._destroyTheadEl();var N=(this._elColgroup)?S.insertBefore(document.createElement("thead"),this._elColgroup.nextSibling):S.appendChild(document.createElement("thead"));G.addListener(N,"focus",this._onTheadFocus,this);G.addListener(N,"keydown",this._onTheadKeydown,this);G.addListener(N,"mouseover",this._onTableMouseover,this);G.addListener(N,"mouseout",this._onTableMouseout,this);G.addListener(N,"mousedown",this._onTableMousedown,this);G.addListener(N,"mouseup",this._onTableMouseup,this);G.addListener(N,"click",this._onTheadClick,this);var U=this._oColumnSet,Q,O,M,K;var T=U.tree;var L;for(O=0;O<T.length;O++){var J=N.appendChild(document.createElement("tr"));for(M=0;M<T[O].length;M++){Q=T[O][M];L=J.appendChild(document.createElement("th"));this._initThEl(L,Q);}if(O===0){C.addClass(J,D.CLASS_FIRST);}if(O===(T.length-1)){C.addClass(J,D.CLASS_LAST);}}var I=U.headers[0]||[];for(O=0;O<I.length;O++){C.addClass(C.get(this.getId()+"-th-"+I[O]),D.CLASS_FIRST);}var P=U.headers[U.headers.length-1]||[];for(O=0;O<P.length;O++){C.addClass(C.get(this.getId()+"-th-"+P[O]),D.CLASS_LAST);}if(B.webkit&&B.webkit<420){var R=this;setTimeout(function(){N.style.display="";},0);N.style.display="none";}this._elThead=N;this._initColumnHelpers();}},_initThEl:function(M,L){M.id=this.getId()+"-th-"+L.getSanitizedKey();M.innerHTML="";M.rowSpan=L.getRowspan();M.colSpan=L.getColspan();L._elTh=M;var I=M.appendChild(document.createElement("div"));I.id=M.id+"-liner";I.className=D.CLASS_LINER;L._elThLiner=I;var J=I.appendChild(document.createElement("span"));J.className=D.CLASS_LABEL;if(L.abbr){M.abbr=L.abbr;}if(L.hidden){this._clearMinWidth(L);}M.className=this._getColumnClassNames(L);if(L.width){var K=(L.minWidth&&(L.width<L.minWidth))?L.minWidth:L.width;if(D._bDynStylesFallback){M.firstChild.style.overflow="hidden";M.firstChild.style.width=K+"px";}else{this._setColumnWidthDynStyles(L,K+"px","hidden");}}this.formatTheadCell(J,L,this.get("sortedBy"));L._elThLabel=J;},formatTheadCell:function(I,M,K){var Q=M.getKey();var P=H.isValue(M.label)?M.label:Q;if(M.sortable){var N=this.getColumnSortDir(M,K);var J=(N===D.CLASS_DESC);if(K&&(M.key===K.key)){J=!(K.dir===D.CLASS_DESC);}var L=this.getId()+"-href-"+M.getSanitizedKey();var O=(J)?this.get("MSG_SORTDESC"):this.get("MSG_SORTASC");I.innerHTML='<a href="'+L+'" title="'+O+'" class="'+D.CLASS_SORTABLE+'">'+P+"</a>";}else{I.innerHTML=P;}},_destroyDraggableColumns:function(){var K,L;for(var J=0,I=this._oColumnSet.tree[0].length;J<I;J++){K=this._oColumnSet.tree[0][J];if(K._dd){K._dd=K._dd.unreg();C.removeClass(K.getThEl(),D.CLASS_DRAGGABLE);}}},_initDraggableColumns:function(){this._destroyDraggableColumns();if(A.DD){var L,M,J;for(var K=0,I=this._oColumnSet.tree[0].length;K<I;K++){L=this._oColumnSet.tree[0][K];M=L.getThEl();C.addClass(M,D.CLASS_DRAGGABLE);J=D._initColumnDragTargetEl();L._dd=new YAHOO.widget.ColumnDD(this,L,M,J);}}else{}},_destroyResizeableColumns:function(){var J=this._oColumnSet.keys;for(var K=0,I=J.length;K<I;K++){if(J[K]._ddResizer){J[K]._ddResizer=J[K]._ddResizer.unreg();C.removeClass(J[K].getThEl(),D.CLASS_RESIZEABLE);}}},_initResizeableColumns:function(){this._destroyResizeableColumns();if(A.DD){var O,J,M,P,I,Q,L;for(var K=0,N=this._oColumnSet.keys.length;K<N;K++){O=this._oColumnSet.keys[K];if(O.resizeable){J=O.getThEl();C.addClass(J,D.CLASS_RESIZEABLE);M=O.getThLinerEl();P=J.appendChild(document.createElement("div"));P.className=D.CLASS_RESIZERLINER;P.appendChild(M);I=P.appendChild(document.createElement("div"));I.id=J.id+"-resizer";I.className=D.CLASS_RESIZER;O._elResizer=I;Q=D._initColumnResizerProxyEl();O._ddResizer=new YAHOO.util.ColumnResizer(this,O,J,I,Q);L=function(R){G.stopPropagation(R);};G.addListener(I,"click",L);}}}else{}},_destroyColumnHelpers:function(){this._destroyDraggableColumns();this._destroyResizeableColumns();},_initColumnHelpers:function(){if(this.get("draggableColumns")){this._initDraggableColumns();}this._initResizeableColumns();},_destroyTbodyEl:function(){var I=this._elTbody;if(I){var J=I.parentNode;G.purgeElement(I,true);J.removeChild(I);this._elTbody=null;}},_initTbodyEl:function(J){if(J){this._destroyTbodyEl();var I=J.appendChild(document.createElement("tbody"));I.tabIndex=0;I.className=D.CLASS_DATA;
+G.addListener(I,"focus",this._onTbodyFocus,this);G.addListener(I,"mouseover",this._onTableMouseover,this);G.addListener(I,"mouseout",this._onTableMouseout,this);G.addListener(I,"mousedown",this._onTableMousedown,this);G.addListener(I,"mouseup",this._onTableMouseup,this);G.addListener(I,"keydown",this._onTbodyKeydown,this);G.addListener(I,"keypress",this._onTableKeypress,this);G.addListener(I,"click",this._onTbodyClick,this);if(B.ie){I.hideFocus=true;}this._elTbody=I;}},_destroyMsgTbodyEl:function(){var I=this._elMsgTbody;if(I){var J=I.parentNode;G.purgeElement(I,true);J.removeChild(I);this._elTbody=null;}},_initMsgTbodyEl:function(L){if(L){var K=document.createElement("tbody");K.className=D.CLASS_MESSAGE;var J=K.appendChild(document.createElement("tr"));J.className=D.CLASS_FIRST+" "+D.CLASS_LAST;this._elMsgTr=J;var M=J.appendChild(document.createElement("td"));M.colSpan=this._oColumnSet.keys.length||1;M.className=D.CLASS_FIRST+" "+D.CLASS_LAST;this._elMsgTd=M;K=L.insertBefore(K,this._elTbody);var I=M.appendChild(document.createElement("div"));I.className=D.CLASS_LINER;this._elMsgTbody=K;G.addListener(K,"focus",this._onTbodyFocus,this);G.addListener(K,"mouseover",this._onTableMouseover,this);G.addListener(K,"mouseout",this._onTableMouseout,this);G.addListener(K,"mousedown",this._onTableMousedown,this);G.addListener(K,"mouseup",this._onTableMouseup,this);G.addListener(K,"keydown",this._onTbodyKeydown,this);G.addListener(K,"keypress",this._onTableKeypress,this);G.addListener(K,"click",this._onTbodyClick,this);}},_initEvents:function(){this._initColumnSort();YAHOO.util.Event.addListener(document,"click",this._onDocumentClick,this);this.subscribe("paginatorChange",function(){this._handlePaginatorChange.apply(this,arguments);});this.subscribe("initEvent",function(){this.renderPaginator();});this._initCellEditing();},_initColumnSort:function(){this.subscribe("theadCellClickEvent",this.onEventSortColumn);var I=this.get("sortedBy");if(I){if(I.dir=="desc"){this._configs.sortedBy.value.dir=D.CLASS_DESC;}else{if(I.dir=="asc"){this._configs.sortedBy.value.dir=D.CLASS_ASC;}}}},_initCellEditing:function(){this.subscribe("editorBlurEvent",function(){this.onEditorBlurEvent.apply(this,arguments);});this.subscribe("editorBlockEvent",function(){this.onEditorBlockEvent.apply(this,arguments);});this.subscribe("editorUnblockEvent",function(){this.onEditorUnblockEvent.apply(this,arguments);});},_getColumnClassNames:function(L,K){var I;if(H.isString(L.className)){I=[L.className];}else{if(H.isArray(L.className)){I=L.className;}else{I=[];}}I[I.length]=this.getId()+"-col-"+L.getSanitizedKey();I[I.length]="yui-dt-col-"+L.getSanitizedKey();var J=this.get("sortedBy")||{};if(L.key===J.key){I[I.length]=J.dir||"";}if(L.hidden){I[I.length]=D.CLASS_HIDDEN;}if(L.selected){I[I.length]=D.CLASS_SELECTED;}if(L.sortable){I[I.length]=D.CLASS_SORTABLE;}if(L.resizeable){I[I.length]=D.CLASS_RESIZEABLE;}if(L.editor){I[I.length]=D.CLASS_EDITABLE;}if(K){I=I.concat(K);}return I.join(" ");},_clearTrTemplateEl:function(){this._elTrTemplate=null;},_getTrTemplateEl:function(T,N){if(this._elTrTemplate){return this._elTrTemplate;}else{var P=document,R=P.createElement("tr"),K=P.createElement("td"),J=P.createElement("div");K.appendChild(J);var S=document.createDocumentFragment(),Q=this._oColumnSet.keys,M;var O;for(var L=0,I=Q.length;L<I;L++){M=K.cloneNode(true);M=this._formatTdEl(Q[L],M,L,(L===I-1));S.appendChild(M);}R.appendChild(S);this._elTrTemplate=R;return R;}},_formatTdEl:function(M,O,P,L){var S=this._oColumnSet;var I=S.headers,J=I[P],N="",U;for(var K=0,T=J.length;K<T;K++){U=this._sId+"-th-"+J[K]+" ";N+=U;}O.headers=N;var R=[];if(P===0){R[R.length]=D.CLASS_FIRST;}if(L){R[R.length]=D.CLASS_LAST;}O.className=this._getColumnClassNames(M,R);O.firstChild.className=D.CLASS_LINER;if(M.width&&D._bDynStylesFallback){var Q=(M.minWidth&&(M.width<M.minWidth))?M.minWidth:M.width;O.firstChild.style.overflow="hidden";O.firstChild.style.width=Q+"px";}return O;},_addTrEl:function(K){var J=this._getTrTemplateEl();var I=J.cloneNode(true);return this._updateTrEl(I,K);},_updateTrEl:function(J,N){var M=this.get("formatRow")?this.get("formatRow").call(this,J,N):true;if(M){J.style.display="none";var O=J.childNodes,K;for(var L=0,I=O.length;L<I;++L){K=O[L];this.formatCell(O[L].firstChild,N,this._oColumnSet.keys[L]);}J.style.display="";}J.id=N.getId();return J;},_deleteTrEl:function(I){var J;if(!H.isNumber(I)){J=C.get(I).sectionRowIndex;}else{J=I;}if(H.isNumber(J)&&(J>-2)&&(J<this._elTbody.rows.length)){return this._elTbody.removeChild(this.getTrEl(I));}else{return null;}},_unsetFirstRow:function(){if(this._sFirstTrId){C.removeClass(this._sFirstTrId,D.CLASS_FIRST);this._sFirstTrId=null;}},_setFirstRow:function(){this._unsetFirstRow();var I=this.getFirstTrEl();if(I){C.addClass(I,D.CLASS_FIRST);this._sFirstTrId=I.id;}},_unsetLastRow:function(){if(this._sLastTrId){C.removeClass(this._sLastTrId,D.CLASS_LAST);this._sLastTrId=null;}},_setLastRow:function(){this._unsetLastRow();var I=this.getLastTrEl();if(I){C.addClass(I,D.CLASS_LAST);this._sLastTrId=I.id;}},_setRowStripes:function(S,K){var L=this._elTbody.rows,P=0,R=L.length,O=[],Q=0,M=[],I=0;if((S!==null)&&(S!==undefined)){var N=this.getTrEl(S);if(N){P=N.sectionRowIndex;if(H.isNumber(K)&&(K>1)){R=P+K;}}}for(var J=P;J<R;J++){if(J%2){O[Q++]=L[J];}else{M[I++]=L[J];}}if(O.length){C.replaceClass(O,D.CLASS_EVEN,D.CLASS_ODD);}if(M.length){C.replaceClass(M,D.CLASS_ODD,D.CLASS_EVEN);}},_setSelections:function(){var K=this.getSelectedRows();var M=this.getSelectedCells();if((K.length>0)||(M.length>0)){var L=this._oColumnSet,J;for(var I=0;I<K.length;I++){J=C.get(K[I]);if(J){C.addClass(J,D.CLASS_SELECTED);}}for(I=0;I<M.length;I++){J=C.get(M[I].recordId);if(J){C.addClass(J.childNodes[L.getColumn(M[I].columnKey).getKeyIndex()],D.CLASS_SELECTED);}}}},_onRenderChainEnd:function(){this.hideTableMessage();if(this._elTbody.rows.length===0){this.showTableMessage(this.get("MSG_EMPTY"),D.CLASS_EMPTY);}var I=this;setTimeout(function(){if((I instanceof D)&&I._sId){if(I._bInit){I._bInit=false;
+I.fireEvent("initEvent");}I.fireEvent("renderEvent");I.fireEvent("refreshEvent");I.validateColumnWidths();I.fireEvent("postRenderEvent");}},0);},_onDocumentClick:function(L,J){var M=G.getTarget(L);var I=M.nodeName.toLowerCase();if(!C.isAncestor(J._elContainer,M)){J.fireEvent("tableBlurEvent");if(J._oCellEditor){if(J._oCellEditor.getContainerEl){var K=J._oCellEditor.getContainerEl();if(!C.isAncestor(K,M)&&(K.id!==M.id)){J._oCellEditor.fireEvent("blurEvent",{editor:J._oCellEditor});}}else{if(J._oCellEditor.isActive){if(!C.isAncestor(J._oCellEditor.container,M)&&(J._oCellEditor.container.id!==M.id)){J.fireEvent("editorBlurEvent",{editor:J._oCellEditor});}}}}}},_onTableFocus:function(J,I){I.fireEvent("tableFocusEvent");},_onTheadFocus:function(J,I){I.fireEvent("theadFocusEvent");I.fireEvent("tableFocusEvent");},_onTbodyFocus:function(J,I){I.fireEvent("tbodyFocusEvent");I.fireEvent("tableFocusEvent");},_onTableMouseover:function(L,J){var M=G.getTarget(L);var I=M.nodeName.toLowerCase();var K=true;while(M&&(I!="table")){switch(I){case"body":return;case"a":break;case"td":K=J.fireEvent("cellMouseoverEvent",{target:M,event:L});break;case"span":if(C.hasClass(M,D.CLASS_LABEL)){K=J.fireEvent("theadLabelMouseoverEvent",{target:M,event:L});K=J.fireEvent("headerLabelMouseoverEvent",{target:M,event:L});}break;case"th":K=J.fireEvent("theadCellMouseoverEvent",{target:M,event:L});K=J.fireEvent("headerCellMouseoverEvent",{target:M,event:L});break;case"tr":if(M.parentNode.nodeName.toLowerCase()=="thead"){K=J.fireEvent("theadRowMouseoverEvent",{target:M,event:L});K=J.fireEvent("headerRowMouseoverEvent",{target:M,event:L});}else{K=J.fireEvent("rowMouseoverEvent",{target:M,event:L});}break;default:break;}if(K===false){return;}else{M=M.parentNode;if(M){I=M.nodeName.toLowerCase();}}}J.fireEvent("tableMouseoverEvent",{target:(M||J._elContainer),event:L});},_onTableMouseout:function(L,J){var M=G.getTarget(L);var I=M.nodeName.toLowerCase();var K=true;while(M&&(I!="table")){switch(I){case"body":return;case"a":break;case"td":K=J.fireEvent("cellMouseoutEvent",{target:M,event:L});break;case"span":if(C.hasClass(M,D.CLASS_LABEL)){K=J.fireEvent("theadLabelMouseoutEvent",{target:M,event:L});K=J.fireEvent("headerLabelMouseoutEvent",{target:M,event:L});}break;case"th":K=J.fireEvent("theadCellMouseoutEvent",{target:M,event:L});K=J.fireEvent("headerCellMouseoutEvent",{target:M,event:L});break;case"tr":if(M.parentNode.nodeName.toLowerCase()=="thead"){K=J.fireEvent("theadRowMouseoutEvent",{target:M,event:L});K=J.fireEvent("headerRowMouseoutEvent",{target:M,event:L});}else{K=J.fireEvent("rowMouseoutEvent",{target:M,event:L});}break;default:break;}if(K===false){return;}else{M=M.parentNode;if(M){I=M.nodeName.toLowerCase();}}}J.fireEvent("tableMouseoutEvent",{target:(M||J._elContainer),event:L});},_onTableMousedown:function(L,J){var M=G.getTarget(L);var I=M.nodeName.toLowerCase();var K=true;while(M&&(I!="table")){switch(I){case"body":return;case"a":break;case"td":K=J.fireEvent("cellMousedownEvent",{target:M,event:L});break;case"span":if(C.hasClass(M,D.CLASS_LABEL)){K=J.fireEvent("theadLabelMousedownEvent",{target:M,event:L});K=J.fireEvent("headerLabelMousedownEvent",{target:M,event:L});}break;case"th":K=J.fireEvent("theadCellMousedownEvent",{target:M,event:L});K=J.fireEvent("headerCellMousedownEvent",{target:M,event:L});break;case"tr":if(M.parentNode.nodeName.toLowerCase()=="thead"){K=J.fireEvent("theadRowMousedownEvent",{target:M,event:L});K=J.fireEvent("headerRowMousedownEvent",{target:M,event:L});}else{K=J.fireEvent("rowMousedownEvent",{target:M,event:L});}break;default:break;}if(K===false){return;}else{M=M.parentNode;if(M){I=M.nodeName.toLowerCase();}}}J.fireEvent("tableMousedownEvent",{target:(M||J._elContainer),event:L});},_onTableMouseup:function(L,J){var M=G.getTarget(L);var I=M.nodeName.toLowerCase();var K=true;while(M&&(I!="table")){switch(I){case"body":return;case"a":break;case"td":K=J.fireEvent("cellMouseupEvent",{target:M,event:L});break;case"span":if(C.hasClass(M,D.CLASS_LABEL)){K=J.fireEvent("theadLabelMouseupEvent",{target:M,event:L});K=J.fireEvent("headerLabelMouseupEvent",{target:M,event:L});}break;case"th":K=J.fireEvent("theadCellMouseupEvent",{target:M,event:L});K=J.fireEvent("headerCellMouseupEvent",{target:M,event:L});break;case"tr":if(M.parentNode.nodeName.toLowerCase()=="thead"){K=J.fireEvent("theadRowMouseupEvent",{target:M,event:L});K=J.fireEvent("headerRowMouseupEvent",{target:M,event:L});}else{K=J.fireEvent("rowMouseupEvent",{target:M,event:L});}break;default:break;}if(K===false){return;}else{M=M.parentNode;if(M){I=M.nodeName.toLowerCase();}}}J.fireEvent("tableMouseupEvent",{target:(M||J._elContainer),event:L});},_onTableDblclick:function(L,J){var M=G.getTarget(L);var I=M.nodeName.toLowerCase();var K=true;while(M&&(I!="table")){switch(I){case"body":return;case"td":K=J.fireEvent("cellDblclickEvent",{target:M,event:L});break;case"span":if(C.hasClass(M,D.CLASS_LABEL)){K=J.fireEvent("theadLabelDblclickEvent",{target:M,event:L});K=J.fireEvent("headerLabelDblclickEvent",{target:M,event:L});}break;case"th":K=J.fireEvent("theadCellDblclickEvent",{target:M,event:L});K=J.fireEvent("headerCellDblclickEvent",{target:M,event:L});break;case"tr":if(M.parentNode.nodeName.toLowerCase()=="thead"){K=J.fireEvent("theadRowDblclickEvent",{target:M,event:L});K=J.fireEvent("headerRowDblclickEvent",{target:M,event:L});}else{K=J.fireEvent("rowDblclickEvent",{target:M,event:L});}break;default:break;}if(K===false){return;}else{M=M.parentNode;if(M){I=M.nodeName.toLowerCase();}}}J.fireEvent("tableDblclickEvent",{target:(M||J._elContainer),event:L});},_onTheadKeydown:function(L,J){var M=G.getTarget(L);var I=M.nodeName.toLowerCase();var K=true;while(M&&(I!="table")){switch(I){case"body":return;case"input":case"textarea":break;case"thead":K=J.fireEvent("theadKeyEvent",{target:M,event:L});break;default:break;}if(K===false){return;}else{M=M.parentNode;if(M){I=M.nodeName.toLowerCase();}}}J.fireEvent("tableKeyEvent",{target:(M||J._elContainer),event:L});
+},_onTbodyKeydown:function(M,K){var J=K.get("selectionMode");if(J=="standard"){K._handleStandardSelectionByKey(M);}else{if(J=="single"){K._handleSingleSelectionByKey(M);}else{if(J=="cellblock"){K._handleCellBlockSelectionByKey(M);}else{if(J=="cellrange"){K._handleCellRangeSelectionByKey(M);}else{if(J=="singlecell"){K._handleSingleCellSelectionByKey(M);}}}}}if(K._oCellEditor){if(K._oCellEditor.fireEvent){K._oCellEditor.fireEvent("blurEvent",{editor:K._oCellEditor});}else{if(K._oCellEditor.isActive){K.fireEvent("editorBlurEvent",{editor:K._oCellEditor});}}}var N=G.getTarget(M);var I=N.nodeName.toLowerCase();var L=true;while(N&&(I!="table")){switch(I){case"body":return;case"tbody":L=K.fireEvent("tbodyKeyEvent",{target:N,event:M});break;default:break;}if(L===false){return;}else{N=N.parentNode;if(N){I=N.nodeName.toLowerCase();}}}K.fireEvent("tableKeyEvent",{target:(N||K._elContainer),event:M});},_onTableKeypress:function(K,J){if(B.opera||(navigator.userAgent.toLowerCase().indexOf("mac")!==-1)&&(B.webkit<420)){var I=G.getCharCode(K);if(I==40){G.stopEvent(K);}else{if(I==38){G.stopEvent(K);}}}},_onTheadClick:function(L,J){if(J._oCellEditor){if(J._oCellEditor.fireEvent){J._oCellEditor.fireEvent("blurEvent",{editor:J._oCellEditor});}else{if(J._oCellEditor.isActive){J.fireEvent("editorBlurEvent",{editor:J._oCellEditor});}}}var M=G.getTarget(L),I=M.nodeName.toLowerCase(),K=true;while(M&&(I!="table")){switch(I){case"body":return;case"input":var N=M.type.toLowerCase();if(N=="checkbox"){K=J.fireEvent("theadCheckboxClickEvent",{target:M,event:L});}else{if(N=="radio"){K=J.fireEvent("theadRadioClickEvent",{target:M,event:L});}else{if((N=="button")||(N=="image")||(N=="submit")||(N=="reset")){K=J.fireEvent("theadButtonClickEvent",{target:M,event:L});}}}break;case"a":K=J.fireEvent("theadLinkClickEvent",{target:M,event:L});break;case"button":K=J.fireEvent("theadButtonClickEvent",{target:M,event:L});break;case"span":if(C.hasClass(M,D.CLASS_LABEL)){K=J.fireEvent("theadLabelClickEvent",{target:M,event:L});K=J.fireEvent("headerLabelClickEvent",{target:M,event:L});}break;case"th":K=J.fireEvent("theadCellClickEvent",{target:M,event:L});K=J.fireEvent("headerCellClickEvent",{target:M,event:L});break;case"tr":K=J.fireEvent("theadRowClickEvent",{target:M,event:L});K=J.fireEvent("headerRowClickEvent",{target:M,event:L});break;default:break;}if(K===false){return;}else{M=M.parentNode;if(M){I=M.nodeName.toLowerCase();}}}J.fireEvent("tableClickEvent",{target:(M||J._elContainer),event:L});},_onTbodyClick:function(L,J){if(J._oCellEditor){if(J._oCellEditor.fireEvent){J._oCellEditor.fireEvent("blurEvent",{editor:J._oCellEditor});}else{if(J._oCellEditor.isActive){J.fireEvent("editorBlurEvent",{editor:J._oCellEditor});}}}var M=G.getTarget(L),I=M.nodeName.toLowerCase(),K=true;while(M&&(I!="table")){switch(I){case"body":return;case"input":var N=M.type.toLowerCase();if(N=="checkbox"){K=J.fireEvent("checkboxClickEvent",{target:M,event:L});}else{if(N=="radio"){K=J.fireEvent("radioClickEvent",{target:M,event:L});}else{if((N=="button")||(N=="image")||(N=="submit")||(N=="reset")){K=J.fireEvent("buttonClickEvent",{target:M,event:L});}}}break;case"a":K=J.fireEvent("linkClickEvent",{target:M,event:L});break;case"button":K=J.fireEvent("buttonClickEvent",{target:M,event:L});break;case"td":K=J.fireEvent("cellClickEvent",{target:M,event:L});break;case"tr":K=J.fireEvent("rowClickEvent",{target:M,event:L});break;default:break;}if(K===false){return;}else{M=M.parentNode;if(M){I=M.nodeName.toLowerCase();}}}J.fireEvent("tableClickEvent",{target:(M||J._elContainer),event:L});},_onDropdownChange:function(J,I){var K=G.getTarget(J);I.fireEvent("dropdownChangeEvent",{event:J,target:K});},configs:null,getId:function(){return this._sId;},toString:function(){return"DataTable instance "+this._sId;},getDataSource:function(){return this._oDataSource;},getColumnSet:function(){return this._oColumnSet;},getRecordSet:function(){return this._oRecordSet;},getState:function(){return{totalRecords:this.get("paginator")?this.get("paginator").get("totalRecords"):this._oRecordSet.getLength(),pagination:this.get("paginator")?this.get("paginator").getState():null,sortedBy:this.get("sortedBy"),selectedRows:this.getSelectedRows(),selectedCells:this.getSelectedCells()};},getContainerEl:function(){return this._elContainer;},getTableEl:function(){return this._elTable;},getTheadEl:function(){return this._elThead;},getTbodyEl:function(){return this._elTbody;},getMsgTbodyEl:function(){return this._elMsgTbody;},getMsgTdEl:function(){return this._elMsgTd;},getTrEl:function(K){if(K instanceof YAHOO.widget.Record){return document.getElementById(K.getId());}else{if(H.isNumber(K)){var J=this._elTbody.rows;return((K>-1)&&(K<J.length))?J[K]:null;}else{var I=(H.isString(K))?document.getElementById(K):K;if(I&&(I.ownerDocument==document)){if(I.nodeName.toLowerCase()!="tr"){I=C.getAncestorByTagName(I,"tr");}return I;}}}return null;},getFirstTrEl:function(){return this._elTbody.rows[0]||null;},getLastTrEl:function(){var I=this._elTbody.rows;if(I.length>0){return I[I.length-1]||null;}},getNextTrEl:function(K){var I=this.getTrIndex(K);if(I!==null){var J=this._elTbody.rows;if(I<J.length-1){return J[I+1];}}return null;},getPreviousTrEl:function(K){var I=this.getTrIndex(K);if(I!==null){var J=this._elTbody.rows;if(I>0){return J[I-1];}}return null;},getTdLinerEl:function(I){var J=this.getTdEl(I);return J.firstChild||null;},getTdEl:function(I){var N;var L=C.get(I);if(L&&(L.ownerDocument==document)){if(L.nodeName.toLowerCase()!="td"){N=C.getAncestorByTagName(L,"td");}else{N=L;}if(N&&((N.parentNode.parentNode==this._elTbody)||(N.parentNode.parentNode===null))){return N;}}else{if(I){var M,K;if(H.isString(I.columnKey)&&H.isString(I.recordId)){M=this.getRecord(I.recordId);var O=this.getColumn(I.columnKey);if(O){K=O.getKeyIndex();}}if(I.record&&I.column&&I.column.getKeyIndex){M=I.record;K=I.column.getKeyIndex();}var J=this.getTrEl(M);if((K!==null)&&J&&J.cells&&J.cells.length>0){return J.cells[K]||null;}}}return null;
+},getFirstTdEl:function(J){var I=this.getTrEl(J)||this.getFirstTrEl();if(I&&(I.cells.length>0)){return I.cells[0];}return null;},getLastTdEl:function(J){var I=this.getTrEl(J)||this.getLastTrEl();if(I&&(I.cells.length>0)){return I.cells[I.cells.length-1];}return null;},getNextTdEl:function(I){var M=this.getTdEl(I);if(M){var K=M.cellIndex;var J=this.getTrEl(M);if(K<J.cells.length-1){return J.cells[K+1];}else{var L=this.getNextTrEl(J);if(L){return L.cells[0];}}}return null;},getPreviousTdEl:function(I){var M=this.getTdEl(I);if(M){var K=M.cellIndex;var J=this.getTrEl(M);if(K>0){return J.cells[K-1];}else{var L=this.getPreviousTrEl(J);if(L){return this.getLastTdEl(L);}}}return null;},getAboveTdEl:function(I){var K=this.getTdEl(I);if(K){var J=this.getPreviousTrEl(K);if(J){return J.cells[K.cellIndex];}}return null;},getBelowTdEl:function(I){var K=this.getTdEl(I);if(K){var J=this.getNextTrEl(K);if(J){return J.cells[K.cellIndex];}}return null;},getThLinerEl:function(J){var I=this.getColumn(J);return(I)?I.getThLinerEl():null;},getThEl:function(K){var L;if(K instanceof YAHOO.widget.Column){var J=K;L=J.getThEl();if(L){return L;}}else{var I=C.get(K);if(I&&(I.ownerDocument==document)){if(I.nodeName.toLowerCase()!="th"){L=C.getAncestorByTagName(I,"th");}else{L=I;}return L;}}return null;},getTrIndex:function(M){var L;if(M instanceof YAHOO.widget.Record){L=this._oRecordSet.getRecordIndex(M);if(L===null){return null;}}else{if(H.isNumber(M)){L=M;}}if(H.isNumber(L)){if((L>-1)&&(L<this._oRecordSet.getLength())){var K=this.get("paginator");if(K){var J=K.getPageRecords();if(J&&L>=J[0]&&L<=J[1]){return L-J[0];}else{return null;}}else{return L;}}else{return null;}}else{var I=this.getTrEl(M);if(I&&(I.ownerDocument==document)&&(I.parentNode==this._elTbody)){return I.sectionRowIndex;}}return null;},initializeTable:function(){this._bInit=true;this._oRecordSet.reset();var I=this.get("paginator");if(I){I.set("totalRecords",0);}this._unselectAllTrEls();this._unselectAllTdEls();this._aSelections=null;this._oAnchorRecord=null;this._oAnchorCell=null;this.set("sortedBy",null);},_runRenderChain:function(){this._oChainRender.run();},render:function(){this._oChainRender.stop();this.fireEvent("beforeRenderEvent");var O,M,L,P,I;var R=this.get("paginator");if(R){I=this._oRecordSet.getRecords(R.getStartIndex(),R.getRowsPerPage());}else{I=this._oRecordSet.getRecords();}var J=this._elTbody,N=this.get("renderLoopSize"),Q=I.length;if(Q>0){J.style.display="none";while(J.lastChild){J.removeChild(J.lastChild);}J.style.display="";this._oChainRender.add({method:function(U){if((this instanceof D)&&this._sId){var T=U.nCurrentRecord,W=((U.nCurrentRecord+U.nLoopLength)>Q)?Q:(U.nCurrentRecord+U.nLoopLength),S,V;J.style.display="none";for(;T<W;T++){S=C.get(I[T].getId());S=S||this._addTrEl(I[T]);V=J.childNodes[T]||null;J.insertBefore(S,V);}J.style.display="";U.nCurrentRecord=T;}},scope:this,iterations:(N>0)?Math.ceil(Q/N):1,argument:{nCurrentRecord:0,nLoopLength:(N>0)?N:Q},timeout:(N>0)?0:-1});this._oChainRender.add({method:function(S){if((this instanceof D)&&this._sId){while(J.rows.length>Q){J.removeChild(J.lastChild);}this._setFirstRow();this._setLastRow();this._setRowStripes();this._setSelections();}},scope:this,timeout:(N>0)?0:-1});}else{var K=J.rows.length;if(K>0){this._oChainRender.add({method:function(T){if((this instanceof D)&&this._sId){var S=T.nCurrent,V=T.nLoopLength,U=(S-V<0)?-1:S-V;J.style.display="none";for(;S>U;S--){J.deleteRow(-1);}J.style.display="";T.nCurrent=S;}},scope:this,iterations:(N>0)?Math.ceil(K/N):1,argument:{nCurrent:K,nLoopLength:(N>0)?N:K},timeout:(N>0)?0:-1});}}this._runRenderChain();},disable:function(){var I=this._elTable;var J=this._elMask;J.style.width=I.offsetWidth+"px";J.style.height=I.offsetHeight+"px";J.style.display="";this.fireEvent("disableEvent");},undisable:function(){this._elMask.style.display="none";this.fireEvent("undisableEvent");},destroy:function(){var J=this.toString();this._oChainRender.stop();D._destroyColumnDragTargetEl();D._destroyColumnResizerProxyEl();this._destroyColumnHelpers();var L;for(var K=0,I=this._oColumnSet.flat.length;K<I;K++){L=this._oColumnSet.flat[K].editor;if(L&&L.destroy){L.destroy();this._oColumnSet.flat[K].editor=null;}}this._destroyPaginator();this._oRecordSet.unsubscribeAll();this.unsubscribeAll();G.removeListener(document,"click",this._onDocumentClick);this._destroyContainerEl(this._elContainer);for(var M in this){if(H.hasOwnProperty(this,M)){this[M]=null;}}D._nCurrentCount--;if(D._nCurrentCount<1){if(D._elDynStyleNode){document.getElementsByTagName("head")[0].removeChild(D._elDynStyleNode);D._elDynStyleNode=null;}}},showTableMessage:function(J,I){var K=this._elMsgTd;if(H.isString(J)){K.firstChild.innerHTML=J;}if(H.isString(I)){K.className=I;}this._elMsgTbody.style.display="";this.fireEvent("tableMsgShowEvent",{html:J,className:I});},hideTableMessage:function(){if(this._elMsgTbody.style.display!="none"){this._elMsgTbody.style.display="none";this._elMsgTbody.parentNode.style.width="";this.fireEvent("tableMsgHideEvent");}},focus:function(){this.focusTbodyEl();},focusTheadEl:function(){this._focusEl(this._elThead);},focusTbodyEl:function(){this._focusEl(this._elTbody);},onShow:function(){this.validateColumnWidths();for(var L=this._oColumnSet.keys,K=0,I=L.length,J;K<I;K++){J=L[K];if(J._ddResizer){J._ddResizer.resetResizerEl();}}},getRecordIndex:function(L){var K;if(!H.isNumber(L)){if(L instanceof YAHOO.widget.Record){return this._oRecordSet.getRecordIndex(L);}else{var J=this.getTrEl(L);if(J){K=J.sectionRowIndex;}}}else{K=L;}if(H.isNumber(K)){var I=this.get("paginator");if(I){return I.get("recordOffset")+K;}else{return K;}}return null;},getRecord:function(K){var J=this._oRecordSet.getRecord(K);if(!J){var I=this.getTrEl(K);if(I){J=this._oRecordSet.getRecord(I.id);}}if(J instanceof YAHOO.widget.Record){return this._oRecordSet.getRecord(J);}else{return null;}},getColumn:function(L){var N=this._oColumnSet.getColumn(L);if(!N){var M=this.getTdEl(L);if(M){N=this._oColumnSet.getColumn(M.cellIndex);
+}else{M=this.getThEl(L);if(M){var J=this._oColumnSet.flat;for(var K=0,I=J.length;K<I;K++){if(J[K].getThEl().id===M.id){N=J[K];}}}}}if(!N){}return N;},getColumnById:function(I){return this._oColumnSet.getColumnById(I);},getColumnSortDir:function(K,L){if(K.sortOptions&&K.sortOptions.defaultOrder){if(K.sortOptions.defaultOrder=="asc"){K.sortOptions.defaultDir=D.CLASS_ASC;}else{if(K.sortOptions.defaultOrder=="desc"){K.sortOptions.defaultDir=D.CLASS_DESC;}}}var J=(K.sortOptions&&K.sortOptions.defaultDir)?K.sortOptions.defaultDir:D.CLASS_ASC;var I=false;L=L||this.get("sortedBy");if(L&&(L.key===K.key)){I=true;if(L.dir){J=(L.dir===D.CLASS_ASC)?D.CLASS_DESC:D.CLASS_ASC;}else{J=(J===D.CLASS_ASC)?D.CLASS_DESC:D.CLASS_ASC;}}return J;},doBeforeSortColumn:function(J,I){this.showTableMessage(this.get("MSG_LOADING"),D.CLASS_LOADING);return true;},sortColumn:function(N,K){if(N&&(N instanceof YAHOO.widget.Column)){if(!N.sortable){C.addClass(this.getThEl(N),D.CLASS_SORTABLE);}if(K&&(K!==D.CLASS_ASC)&&(K!==D.CLASS_DESC)){K=null;}var O=K||this.getColumnSortDir(N);var M=this.get("sortedBy")||{};var U=(M.key===N.key)?true:false;var Q=this.doBeforeSortColumn(N,O);if(Q){if(this.get("dynamicData")){var T=this.getState();if(T.pagination){T.pagination.recordOffset=0;}T.sortedBy={key:N.key,dir:O};var L=this.get("generateRequest")(T,this);this.unselectAllRows();this.unselectAllCells();var S={success:this.onDataReturnSetRows,failure:this.onDataReturnSetRows,argument:T,scope:this};this._oDataSource.sendRequest(L,S);}else{var I=(N.sortOptions&&H.isFunction(N.sortOptions.sortFunction))?N.sortOptions.sortFunction:null;if(!U||K||I){var J=YAHOO.util.Sort.compare;I=I||function(W,V,Z,Y){var X=J(W.getData(Y),V.getData(Y),Z);if(X===0){return J(W.getCount(),V.getCount(),Z);}else{return X;}};var R=(N.sortOptions&&N.sortOptions.field)?N.sortOptions.field:N.field;this._oRecordSet.sortRecords(I,((O==D.CLASS_DESC)?true:false),R);}else{this._oRecordSet.reverseRecords();}var P=this.get("paginator");if(P){P.setPage(1,true);}this.render();this.set("sortedBy",{key:N.key,dir:O,column:N});}this.fireEvent("columnSortEvent",{column:N,dir:O});return;}}},setColumnWidth:function(J,I){if(!(J instanceof YAHOO.widget.Column)){J=this.getColumn(J);}if(J){if(H.isNumber(I)){I=(I>J.minWidth)?I:J.minWidth;J.width=I;this._setColumnWidth(J,I+"px");this.fireEvent("columnSetWidthEvent",{column:J,width:I});}else{if(I===null){J.width=I;this._setColumnWidth(J,"auto");this.validateColumnWidths(J);this.fireEvent("columnUnsetWidthEvent",{column:J});}}this._clearTrTemplateEl();}else{}},_setColumnWidth:function(J,I,K){if(J&&(J.getKeyIndex()!==null)){K=K||(((I==="")||(I==="auto"))?"visible":"hidden");if(!D._bDynStylesFallback){this._setColumnWidthDynStyles(J,I,K);}else{this._setColumnWidthDynFunction(J,I,K);}}else{}},_setColumnWidthDynStyles:function(M,L,N){var J=D._elDynStyleNode,K;if(!J){J=document.createElement("style");J.type="text/css";J=document.getElementsByTagName("head").item(0).appendChild(J);D._elDynStyleNode=J;}if(J){var I="."+this.getId()+"-col-"+M.getSanitizedKey()+" ."+D.CLASS_LINER;if(this._elTbody){this._elTbody.style.display="none";}K=D._oDynStyles[I];if(!K){if(J.styleSheet&&J.styleSheet.addRule){J.styleSheet.addRule(I,"overflow:"+N);J.styleSheet.addRule(I,"width:"+L);K=J.styleSheet.rules[J.styleSheet.rules.length-1];D._oDynStyles[I]=K;}else{if(J.sheet&&J.sheet.insertRule){J.sheet.insertRule(I+" {overflow:"+N+";width:"+L+";}",J.sheet.cssRules.length);K=J.sheet.cssRules[J.sheet.cssRules.length-1];D._oDynStyles[I]=K;}}}else{K.style.overflow=N;K.style.width=L;}if(this._elTbody){this._elTbody.style.display="";}}if(!K){D._bDynStylesFallback=true;this._setColumnWidthDynFunction(M,L);}},_setColumnWidthDynFunction:function(O,J,P){if(J=="auto"){J="";}var I=this._elTbody?this._elTbody.rows.length:0;if(!this._aDynFunctions[I]){var N,M,L;var Q=["var colIdx=oColumn.getKeyIndex();","oColumn.getThLinerEl().style.overflow="];for(N=I-1,M=2;N>=0;--N){Q[M++]="this._elTbody.rows[";Q[M++]=N;Q[M++]="].cells[colIdx].firstChild.style.overflow=";}Q[M]="sOverflow;";Q[M+1]="oColumn.getThLinerEl().style.width=";for(N=I-1,L=M+2;N>=0;--N){Q[L++]="this._elTbody.rows[";Q[L++]=N;Q[L++]="].cells[colIdx].firstChild.style.width=";}Q[L]="sWidth;";this._aDynFunctions[I]=new Function("oColumn","sWidth","sOverflow",Q.join(""));}var K=this._aDynFunctions[I];if(K){K.call(this,O,J,P);}},validateColumnWidths:function(N){var K=this._elColgroup;var P=K.cloneNode(true);var O=false;var M=this._oColumnSet.keys;var J;if(N&&!N.hidden&&!N.width&&(N.getKeyIndex()!==null)){J=N.getThLinerEl();if((N.minWidth>0)&&(J.offsetWidth<N.minWidth)){P.childNodes[N.getKeyIndex()].style.width=N.minWidth+(parseInt(C.getStyle(J,"paddingLeft"),10)|0)+(parseInt(C.getStyle(J,"paddingRight"),10)|0)+"px";O=true;}else{if((N.maxAutoWidth>0)&&(J.offsetWidth>N.maxAutoWidth)){this._setColumnWidth(N,N.maxAutoWidth+"px","hidden");}}}else{for(var L=0,I=M.length;L<I;L++){N=M[L];if(!N.hidden&&!N.width){J=N.getThLinerEl();if((N.minWidth>0)&&(J.offsetWidth<N.minWidth)){P.childNodes[L].style.width=N.minWidth+(parseInt(C.getStyle(J,"paddingLeft"),10)|0)+(parseInt(C.getStyle(J,"paddingRight"),10)|0)+"px";O=true;}else{if((N.maxAutoWidth>0)&&(J.offsetWidth>N.maxAutoWidth)){this._setColumnWidth(N,N.maxAutoWidth+"px","hidden");}}}}}if(O){K.parentNode.replaceChild(P,K);this._elColgroup=P;}},_clearMinWidth:function(I){if(I.getKeyIndex()!==null){this._elColgroup.childNodes[I.getKeyIndex()].style.width="";}},_restoreMinWidth:function(I){if(I.minWidth&&(I.getKeyIndex()!==null)){this._elColgroup.childNodes[I.getKeyIndex()].style.width=I.minWidth+"px";}},hideColumn:function(N){if(!(N instanceof YAHOO.widget.Column)){N=this.getColumn(N);}if(N&&!N.hidden&&N.getTreeIndex()!==null){var O=this.getTbodyEl().rows;var I=O.length;var M=this._oColumnSet.getDescendants(N);for(var L=0;L<M.length;L++){var K=M[L];K.hidden=true;C.addClass(K.getThEl(),D.CLASS_HIDDEN);var P=K.getKeyIndex();if(P!==null){this._clearMinWidth(N);for(var J=0;J<I;J++){C.addClass(O[J].cells[P],D.CLASS_HIDDEN);
+}}this.fireEvent("columnHideEvent",{column:K});}this._repaintOpera();this._clearTrTemplateEl();}else{}},showColumn:function(N){if(!(N instanceof YAHOO.widget.Column)){N=this.getColumn(N);}if(N&&N.hidden&&(N.getTreeIndex()!==null)){var O=this.getTbodyEl().rows;var I=O.length;var M=this._oColumnSet.getDescendants(N);for(var L=0;L<M.length;L++){var K=M[L];K.hidden=false;C.removeClass(K.getThEl(),D.CLASS_HIDDEN);var P=K.getKeyIndex();if(P!==null){this._restoreMinWidth(N);for(var J=0;J<I;J++){C.removeClass(O[J].cells[P],D.CLASS_HIDDEN);}}this.fireEvent("columnShowEvent",{column:K});}this._clearTrTemplateEl();}else{}},removeColumn:function(O){if(!(O instanceof YAHOO.widget.Column)){O=this.getColumn(O);}if(O){var L=O.getTreeIndex();if(L!==null){var N,Q,P=O.getKeyIndex();if(P===null){var T=[];var I=this._oColumnSet.getDescendants(O);for(N=0,Q=I.length;N<Q;N++){var R=I[N].getKeyIndex();if(R!==null){T[T.length]=R;}}if(T.length>0){P=T;}}else{P=[P];}if(P!==null){P.sort(function(V,U){return YAHOO.util.Sort.compare(V,U);});this._destroyTheadEl();var J=this._oColumnSet.getDefinitions();O=J.splice(L,1)[0];this._initColumnSet(J);this._initTheadEl();for(N=P.length-1;N>-1;N--){this._removeColgroupColEl(P[N]);}var S=this._elTbody.rows;if(S.length>0){var M=this.get("renderLoopSize"),K=S.length;this._oChainRender.add({method:function(X){if((this instanceof D)&&this._sId){var W=X.nCurrentRow,U=M>0?Math.min(W+M,S.length):S.length,Y=X.aIndexes,V;for(;W<U;++W){for(V=Y.length-1;V>-1;V--){S[W].removeChild(S[W].childNodes[Y[V]]);}}X.nCurrentRow=W;}},iterations:(M>0)?Math.ceil(K/M):1,argument:{nCurrentRow:0,aIndexes:P},scope:this,timeout:(M>0)?0:-1});this._runRenderChain();}this.fireEvent("columnRemoveEvent",{column:O});return O;}}}},insertColumn:function(Q,R){if(Q instanceof YAHOO.widget.Column){Q=Q.getDefinition();}else{if(Q.constructor!==Object){return;}}var W=this._oColumnSet;if(!H.isValue(R)||!H.isNumber(R)){R=W.tree[0].length;}this._destroyTheadEl();var Y=this._oColumnSet.getDefinitions();Y.splice(R,0,Q);this._initColumnSet(Y);this._initTheadEl();W=this._oColumnSet;var M=W.tree[0][R];var O,S,V=[];var K=W.getDescendants(M);for(O=0,S=K.length;O<S;O++){var T=K[O].getKeyIndex();if(T!==null){V[V.length]=T;}}if(V.length>0){var X=V.sort(function(c,Z){return YAHOO.util.Sort.compare(c,Z);})[0];for(O=V.length-1;O>-1;O--){this._insertColgroupColEl(V[O]);}var U=this._elTbody.rows;if(U.length>0){var N=this.get("renderLoopSize"),L=U.length;var J=[],P;for(O=0,S=V.length;O<S;O++){var I=V[O];P=this._getTrTemplateEl().childNodes[O].cloneNode(true);P=this._formatTdEl(this._oColumnSet.keys[I],P,I,(I===this._oColumnSet.keys.length-1));J[I]=P;}this._oChainRender.add({method:function(c){if((this instanceof D)&&this._sId){var b=c.nCurrentRow,a,e=c.descKeyIndexes,Z=N>0?Math.min(b+N,U.length):U.length,d;for(;b<Z;++b){d=U[b].childNodes[X]||null;for(a=e.length-1;a>-1;a--){U[b].insertBefore(c.aTdTemplates[e[a]].cloneNode(true),d);}}c.nCurrentRow=b;}},iterations:(N>0)?Math.ceil(L/N):1,argument:{nCurrentRow:0,aTdTemplates:J,descKeyIndexes:V},scope:this,timeout:(N>0)?0:-1});this._runRenderChain();}this.fireEvent("columnInsertEvent",{column:Q,index:R});return M;}},reorderColumn:function(P,Q){if(!(P instanceof YAHOO.widget.Column)){P=this.getColumn(P);}if(P&&YAHOO.lang.isNumber(Q)){var Y=P.getTreeIndex();if((Y!==null)&&(Y!==Q)){var O,R,K=P.getKeyIndex(),J,U=[],S;if(K===null){J=this._oColumnSet.getDescendants(P);for(O=0,R=J.length;O<R;O++){S=J[O].getKeyIndex();if(S!==null){U[U.length]=S;}}if(U.length>0){K=U;}}else{K=[K];}if(K!==null){K.sort(function(c,Z){return YAHOO.util.Sort.compare(c,Z);});this._destroyTheadEl();var V=this._oColumnSet.getDefinitions();var I=V.splice(Y,1)[0];V.splice(Q,0,I);this._initColumnSet(V);this._initTheadEl();var M=this._oColumnSet.tree[0][Q];var X=M.getKeyIndex();if(X===null){U=[];J=this._oColumnSet.getDescendants(M);for(O=0,R=J.length;O<R;O++){S=J[O].getKeyIndex();if(S!==null){U[U.length]=S;}}if(U.length>0){X=U;}}else{X=[X];}var W=X.sort(function(c,Z){return YAHOO.util.Sort.compare(c,Z);})[0];this._reorderColgroupColEl(K,W);var T=this._elTbody.rows;if(T.length>0){var N=this.get("renderLoopSize"),L=T.length;this._oChainRender.add({method:function(c){if((this instanceof D)&&this._sId){var b=c.nCurrentRow,a,e,d,Z=N>0?Math.min(b+N,T.length):T.length,g=c.aIndexes,f;for(;b<Z;++b){e=[];f=T[b];for(a=g.length-1;a>-1;a--){e.push(f.removeChild(f.childNodes[g[a]]));}d=f.childNodes[W]||null;for(a=e.length-1;a>-1;a--){f.insertBefore(e[a],d);}}c.nCurrentRow=b;}},iterations:(N>0)?Math.ceil(L/N):1,argument:{nCurrentRow:0,aIndexes:K},scope:this,timeout:(N>0)?0:-1});this._runRenderChain();}this.fireEvent("columnReorderEvent",{column:M});return M;}}}},selectColumn:function(K){K=this.getColumn(K);if(K&&!K.selected){if(K.getKeyIndex()!==null){K.selected=true;var L=K.getThEl();C.addClass(L,D.CLASS_SELECTED);var J=this.getTbodyEl().rows;var I=this._oChainRender;I.add({method:function(M){if((this instanceof D)&&this._sId&&J[M.rowIndex]&&J[M.rowIndex].cells[M.cellIndex]){C.addClass(J[M.rowIndex].cells[M.cellIndex],D.CLASS_SELECTED);}M.rowIndex++;},scope:this,iterations:J.length,argument:{rowIndex:0,cellIndex:K.getKeyIndex()}});this._clearTrTemplateEl();this._elTbody.style.display="none";this._runRenderChain();this._elTbody.style.display="";this.fireEvent("columnSelectEvent",{column:K});}else{}}},unselectColumn:function(K){K=this.getColumn(K);if(K&&K.selected){if(K.getKeyIndex()!==null){K.selected=false;var L=K.getThEl();C.removeClass(L,D.CLASS_SELECTED);var J=this.getTbodyEl().rows;var I=this._oChainRender;I.add({method:function(M){if((this instanceof D)&&this._sId&&J[M.rowIndex]&&J[M.rowIndex].cells[M.cellIndex]){C.removeClass(J[M.rowIndex].cells[M.cellIndex],D.CLASS_SELECTED);}M.rowIndex++;},scope:this,iterations:J.length,argument:{rowIndex:0,cellIndex:K.getKeyIndex()}});this._clearTrTemplateEl();this._elTbody.style.display="none";this._runRenderChain();this._elTbody.style.display="";this.fireEvent("columnUnselectEvent",{column:K});}else{}}},getSelectedColumns:function(M){var J=[];
+var K=this._oColumnSet.keys;for(var L=0,I=K.length;L<I;L++){if(K[L].selected){J[J.length]=K[L];}}return J;},highlightColumn:function(I){var L=this.getColumn(I);if(L&&(L.getKeyIndex()!==null)){var M=L.getThEl();C.addClass(M,D.CLASS_HIGHLIGHTED);var K=this.getTbodyEl().rows;var J=this._oChainRender;J.add({method:function(N){if((this instanceof D)&&this._sId&&K[N.rowIndex]&&K[N.rowIndex].cells[N.cellIndex]){C.addClass(K[N.rowIndex].cells[N.cellIndex],D.CLASS_HIGHLIGHTED);}N.rowIndex++;},scope:this,iterations:K.length,argument:{rowIndex:0,cellIndex:L.getKeyIndex()},timeout:-1});this._elTbody.style.display="none";this._runRenderChain();this._elTbody.style.display="";this.fireEvent("columnHighlightEvent",{column:L});}else{}},unhighlightColumn:function(I){var L=this.getColumn(I);if(L&&(L.getKeyIndex()!==null)){var M=L.getThEl();C.removeClass(M,D.CLASS_HIGHLIGHTED);var K=this.getTbodyEl().rows;var J=this._oChainRender;J.add({method:function(N){if((this instanceof D)&&this._sId&&K[N.rowIndex]&&K[N.rowIndex].cells[N.cellIndex]){C.removeClass(K[N.rowIndex].cells[N.cellIndex],D.CLASS_HIGHLIGHTED);}N.rowIndex++;},scope:this,iterations:K.length,argument:{rowIndex:0,cellIndex:L.getKeyIndex()},timeout:-1});this._elTbody.style.display="none";this._runRenderChain();this._elTbody.style.display="";this.fireEvent("columnUnhighlightEvent",{column:L});}else{}},addRow:function(O,K){if(H.isNumber(K)&&(K<0||K>this._oRecordSet.getLength())){return;}if(O&&H.isObject(O)){var M=this._oRecordSet.addRecord(O,K);if(M){var I;var J=this.get("paginator");if(J){var N=J.get("totalRecords");if(N!==E.Paginator.VALUE_UNLIMITED){J.set("totalRecords",N+1);}I=this.getRecordIndex(M);var L=(J.getPageRecords())[1];if(I<=L){this.render();}this.fireEvent("rowAddEvent",{record:M});return;}else{I=this.getTrIndex(M);if(H.isNumber(I)){this._oChainRender.add({method:function(R){if((this instanceof D)&&this._sId){var S=R.record;var P=R.recIndex;var T=this._addTrEl(S);if(T){var Q=(this._elTbody.rows[P])?this._elTbody.rows[P]:null;this._elTbody.insertBefore(T,Q);if(P===0){this._setFirstRow();}if(Q===null){this._setLastRow();}this._setRowStripes();this.hideTableMessage();this.fireEvent("rowAddEvent",{record:S});}}},argument:{record:M,recIndex:I},scope:this,timeout:(this.get("renderLoopSize")>0)?0:-1});this._runRenderChain();return;}}}}},addRows:function(K,N){if(H.isNumber(N)&&(N<0||N>this._oRecordSet.getLength())){return;}if(H.isArray(K)){var O=this._oRecordSet.addRecords(K,N);if(O){var S=this.getRecordIndex(O[0]);var R=this.get("paginator");if(R){var P=R.get("totalRecords");if(P!==E.Paginator.VALUE_UNLIMITED){R.set("totalRecords",P+O.length);}var Q=(R.getPageRecords())[1];if(S<=Q){this.render();}this.fireEvent("rowsAddEvent",{records:O});return;}else{var M=this.get("renderLoopSize");var J=S+K.length;var I=(J-S);var L=(S>=this._elTbody.rows.length);this._oChainRender.add({method:function(X){if((this instanceof D)&&this._sId){var Y=X.aRecords,W=X.nCurrentRow,V=X.nCurrentRecord,T=M>0?Math.min(W+M,J):J,Z=document.createDocumentFragment(),U=(this._elTbody.rows[W])?this._elTbody.rows[W]:null;for(;W<T;W++,V++){Z.appendChild(this._addTrEl(Y[V]));}this._elTbody.insertBefore(Z,U);X.nCurrentRow=W;X.nCurrentRecord=V;}},iterations:(M>0)?Math.ceil(J/M):1,argument:{nCurrentRow:S,nCurrentRecord:0,aRecords:O},scope:this,timeout:(M>0)?0:-1});this._oChainRender.add({method:function(U){var T=U.recIndex;if(T===0){this._setFirstRow();}if(U.isLast){this._setLastRow();}this._setRowStripes();this.fireEvent("rowsAddEvent",{records:O});},argument:{recIndex:S,isLast:L},scope:this,timeout:-1});this._runRenderChain();this.hideTableMessage();return;}}}},updateRow:function(T,J){var Q=T;if(!H.isNumber(Q)){Q=this.getRecordIndex(T);}if(H.isNumber(Q)&&(Q>=0)){var R=this._oRecordSet,P=R.getRecord(Q);if(P){var N=this._oRecordSet.setRecord(J,Q),I=this.getTrEl(P),O=P?P.getData():null;if(N){var S=this._aSelections||[],M=0,K=P.getId(),L=N.getId();for(;M<S.length;M++){if((S[M]===K)){S[M]=L;}else{if(S[M].recordId===K){S[M].recordId=L;}}}this._oChainRender.add({method:function(){if((this instanceof D)&&this._sId){var V=this.get("paginator");if(V){var U=(V.getPageRecords())[0],W=(V.getPageRecords())[1];if((Q>=U)||(Q<=W)){this.render();}}else{if(I){this._updateTrEl(I,N);}else{this.getTbodyEl().appendChild(this._addTrEl(N));}}this.fireEvent("rowUpdateEvent",{record:N,oldData:O});}},scope:this,timeout:(this.get("renderLoopSize")>0)?0:-1});this._runRenderChain();return;}}}return;},updateRows:function(V,K){if(H.isArray(K)){var O=V,J=this._oRecordSet;if(!H.isNumber(V)){O=this.getRecordIndex(V);}if(H.isNumber(O)&&(O>=0)&&(O<J.getLength())){var Z=O+K.length,W=J.getRecords(O,K.length),b=J.setRecords(K,O);if(b){var Q=this._aSelections||[],Y=0,X,T,U;for(;Y<Q.length;Y++){for(X=0;X<W.length;X++){U=W[X].getId();if((Q[Y]===U)){Q[Y]=b[X].getId();}else{if(Q[Y].recordId===U){Q[Y].recordId=b[X].getId();}}}}var a=this.get("paginator");if(a){var P=(a.getPageRecords())[0],M=(a.getPageRecords())[1];if((O>=P)||(Z<=M)){this.render();}this.fireEvent("rowsAddEvent",{newRecords:b,oldRecords:W});return;}else{var I=this.get("renderLoopSize"),R=K.length,L=this._elTbody.rows.length,S=(Z>=L),N=(Z>L);this._oChainRender.add({method:function(f){if((this instanceof D)&&this._sId){var g=f.aRecords,e=f.nCurrentRow,d=f.nDataPointer,c=I>0?Math.min(e+I,O+g.length):O+g.length;for(;e<c;e++,d++){if(N&&(e>=L)){this._elTbody.appendChild(this._addTrEl(g[d]));}else{this._updateTrEl(this._elTbody.rows[e],g[d]);}}f.nCurrentRow=e;f.nDataPointer=d;}},iterations:(I>0)?Math.ceil(R/I):1,argument:{nCurrentRow:O,aRecords:b,nDataPointer:0,isAdding:N},scope:this,timeout:(I>0)?0:-1});this._oChainRender.add({method:function(d){var c=d.recIndex;if(c===0){this._setFirstRow();}if(d.isLast){this._setLastRow();}this._setRowStripes();this.fireEvent("rowsAddEvent",{newRecords:b,oldRecords:W});},argument:{recIndex:O,isLast:S},scope:this,timeout:-1});this._runRenderChain();this.hideTableMessage();return;}}}}},deleteRow:function(R){var J=(H.isNumber(R))?R:this.getRecordIndex(R);
+if(H.isNumber(J)){var S=this.getRecord(J);if(S){var L=this.getTrIndex(J);var O=S.getId();var Q=this._aSelections||[];for(var M=Q.length-1;M>-1;M--){if((H.isString(Q[M])&&(Q[M]===O))||(H.isObject(Q[M])&&(Q[M].recordId===O))){Q.splice(M,1);}}var K=this._oRecordSet.deleteRecord(J);if(K){var P=this.get("paginator");if(P){var N=P.get("totalRecords"),I=P.getPageRecords();if(N!==E.Paginator.VALUE_UNLIMITED){P.set("totalRecords",N-1);}if(!I||J<=I[1]){this.render();}this._oChainRender.add({method:function(){if((this instanceof D)&&this._sId){this.fireEvent("rowDeleteEvent",{recordIndex:J,oldData:K,trElIndex:L});}},scope:this,timeout:(this.get("renderLoopSize")>0)?0:-1});this._runRenderChain();}else{if(H.isNumber(L)){this._oChainRender.add({method:function(){if((this instanceof D)&&this._sId){var T=(J===this._oRecordSet.getLength());this._deleteTrEl(L);if(this._elTbody.rows.length>0){if(L===0){this._setFirstRow();}if(T){this._setLastRow();}if(L!=this._elTbody.rows.length){this._setRowStripes(L);}}this.fireEvent("rowDeleteEvent",{recordIndex:J,oldData:K,trElIndex:L});}},scope:this,timeout:(this.get("renderLoopSize")>0)?0:-1});this._runRenderChain();return;}}}}}return null;},deleteRows:function(X,R){var K=(H.isNumber(X))?X:this.getRecordIndex(X);if(H.isNumber(K)){var Y=this.getRecord(K);if(Y){var L=this.getTrIndex(K);var T=Y.getId();var W=this._aSelections||[];for(var P=W.length-1;P>-1;P--){if((H.isString(W[P])&&(W[P]===T))||(H.isObject(W[P])&&(W[P].recordId===T))){W.splice(P,1);}}var M=K;var V=K;if(R&&H.isNumber(R)){M=(R>0)?K+R-1:K;V=(R>0)?K:K+R+1;R=(R>0)?R:R*-1;if(V<0){V=0;R=M-V+1;}}else{R=1;}var O=this._oRecordSet.deleteRecords(V,R);if(O){var U=this.get("paginator"),Q=this.get("renderLoopSize");if(U){var S=U.get("totalRecords"),J=U.getPageRecords();if(S!==E.Paginator.VALUE_UNLIMITED){U.set("totalRecords",S-O.length);}if(!J||V<=J[1]){this.render();}this._oChainRender.add({method:function(Z){if((this instanceof D)&&this._sId){this.fireEvent("rowsDeleteEvent",{recordIndex:V,oldData:O,count:R});}},scope:this,timeout:(Q>0)?0:-1});this._runRenderChain();return;}else{if(H.isNumber(L)){var N=V;var I=R;this._oChainRender.add({method:function(b){if((this instanceof D)&&this._sId){var a=b.nCurrentRow,Z=(Q>0)?(Math.max(a-Q,N)-1):N-1;for(;a>Z;--a){this._deleteTrEl(a);}b.nCurrentRow=a;}},iterations:(Q>0)?Math.ceil(R/Q):1,argument:{nCurrentRow:M},scope:this,timeout:(Q>0)?0:-1});this._oChainRender.add({method:function(){if(this._elTbody.rows.length>0){this._setFirstRow();this._setLastRow();this._setRowStripes();}this.fireEvent("rowsDeleteEvent",{recordIndex:V,oldData:O,count:R});},scope:this,timeout:-1});this._runRenderChain();return;}}}}}return null;},formatCell:function(J,L,M){if(!L){L=this.getRecord(J);}if(!M){M=this.getColumn(J.parentNode.cellIndex);}if(L&&M){var I=M.field;var N=L.getData(I);var K=typeof M.formatter==="function"?M.formatter:D.Formatter[M.formatter+""]||D.Formatter.defaultFormatter;if(K){K.call(this,J,L,M,N);}else{J.innerHTML=N;}this.fireEvent("cellFormatEvent",{record:L,column:M,key:M.key,el:J});}else{}},updateCell:function(J,L,N){L=(L instanceof YAHOO.widget.Column)?L:this.getColumn(L);if(L&&L.getField()&&(J instanceof YAHOO.widget.Record)){var K=L.getField(),M=J.getData(K);this._oRecordSet.updateRecordValue(J,K,N);var I=this.getTdEl({record:J,column:L});if(I){this._oChainRender.add({method:function(){if((this instanceof D)&&this._sId){this.formatCell(I.firstChild);this.fireEvent("cellUpdateEvent",{record:J,column:L,oldData:M});}},scope:this,timeout:(this.get("renderLoopSize")>0)?0:-1});this._runRenderChain();}else{this.fireEvent("cellUpdateEvent",{record:J,column:L,oldData:M});}}},_updatePaginator:function(J){var I=this.get("paginator");if(I&&J!==I){I.unsubscribe("changeRequest",this.onPaginatorChangeRequest,this,true);}if(J){J.subscribe("changeRequest",this.onPaginatorChangeRequest,this,true);}},_handlePaginatorChange:function(K){if(K.prevValue===K.newValue){return;}var M=K.newValue,L=K.prevValue,J=this._defaultPaginatorContainers();if(L){if(L.getContainerNodes()[0]==J[0]){L.set("containers",[]);}L.destroy();if(J[0]){if(M&&!M.getContainerNodes().length){M.set("containers",J);}else{for(var I=J.length-1;I>=0;--I){if(J[I]){J[I].parentNode.removeChild(J[I]);}}}}}if(!this._bInit){this.render();}if(M){this.renderPaginator();}},_defaultPaginatorContainers:function(L){var J=this._sId+"-paginator0",K=this._sId+"-paginator1",I=C.get(J),M=C.get(K);if(L&&(!I||!M)){if(!I){I=document.createElement("div");I.id=J;C.addClass(I,D.CLASS_PAGINATOR);this._elContainer.insertBefore(I,this._elContainer.firstChild);}if(!M){M=document.createElement("div");M.id=K;C.addClass(M,D.CLASS_PAGINATOR);this._elContainer.appendChild(M);}}return[I,M];},_destroyPaginator:function(){var I=this.get("paginator");if(I){I.destroy();}},renderPaginator:function(){var I=this.get("paginator");if(!I){return;}if(!I.getContainerNodes().length){I.set("containers",this._defaultPaginatorContainers(true));}I.render();},doBeforePaginatorChange:function(I){this.showTableMessage(this.get("MSG_LOADING"),D.CLASS_LOADING);return true;},onPaginatorChangeRequest:function(L){var J=this.doBeforePaginatorChange(L);if(J){if(this.get("dynamicData")){var I=this.getState();I.pagination=L;var K=this.get("generateRequest")(I,this);this.unselectAllRows();this.unselectAllCells();var M={success:this.onDataReturnSetRows,failure:this.onDataReturnSetRows,argument:I,scope:this};this._oDataSource.sendRequest(K,M);}else{L.paginator.setStartIndex(L.recordOffset,true);L.paginator.setRowsPerPage(L.rowsPerPage,true);this.render();}}else{}},_elLastHighlightedTd:null,_aSelections:null,_oAnchorRecord:null,_oAnchorCell:null,_unselectAllTrEls:function(){var I=C.getElementsByClassName(D.CLASS_SELECTED,"tr",this._elTbody);C.removeClass(I,D.CLASS_SELECTED);},_getSelectionTrigger:function(){var L=this.get("selectionMode");var K={};var O,I,J,N,M;if((L=="cellblock")||(L=="cellrange")||(L=="singlecell")){O=this.getLastSelectedCell();if(!O){return null;}else{I=this.getRecord(O.recordId);
+J=this.getRecordIndex(I);N=this.getTrEl(I);M=this.getTrIndex(N);if(M===null){return null;}else{K.record=I;K.recordIndex=J;K.el=this.getTdEl(O);K.trIndex=M;K.column=this.getColumn(O.columnKey);K.colKeyIndex=K.column.getKeyIndex();K.cell=O;return K;}}}else{I=this.getLastSelectedRecord();if(!I){return null;}else{I=this.getRecord(I);J=this.getRecordIndex(I);N=this.getTrEl(I);M=this.getTrIndex(N);if(M===null){return null;}else{K.record=I;K.recordIndex=J;K.el=N;K.trIndex=M;return K;}}}},_getSelectionAnchor:function(K){var J=this.get("selectionMode");var L={};var M,O,I;if((J=="cellblock")||(J=="cellrange")||(J=="singlecell")){var N=this._oAnchorCell;if(!N){if(K){N=this._oAnchorCell=K.cell;}else{return null;}}M=this._oAnchorCell.record;O=this._oRecordSet.getRecordIndex(M);I=this.getTrIndex(M);if(I===null){if(O<this.getRecordIndex(this.getFirstTrEl())){I=0;}else{I=this.getRecordIndex(this.getLastTrEl());}}L.record=M;L.recordIndex=O;L.trIndex=I;L.column=this._oAnchorCell.column;L.colKeyIndex=L.column.getKeyIndex();L.cell=N;return L;}else{M=this._oAnchorRecord;if(!M){if(K){M=this._oAnchorRecord=K.record;}else{return null;}}O=this.getRecordIndex(M);I=this.getTrIndex(M);if(I===null){if(O<this.getRecordIndex(this.getFirstTrEl())){I=0;}else{I=this.getRecordIndex(this.getLastTrEl());}}L.record=M;L.recordIndex=O;L.trIndex=I;return L;}},_handleStandardSelectionByMouse:function(J){var I=J.target;var L=this.getTrEl(I);if(L){var O=J.event;var R=O.shiftKey;var N=O.ctrlKey||((navigator.userAgent.toLowerCase().indexOf("mac")!=-1)&&O.metaKey);var Q=this.getRecord(L);var K=this._oRecordSet.getRecordIndex(Q);var P=this._getSelectionAnchor();var M;if(R&&N){if(P){if(this.isSelected(P.record)){if(P.recordIndex<K){for(M=P.recordIndex+1;M<=K;M++){if(!this.isSelected(M)){this.selectRow(M);}}}else{for(M=P.recordIndex-1;M>=K;M--){if(!this.isSelected(M)){this.selectRow(M);}}}}else{if(P.recordIndex<K){for(M=P.recordIndex+1;M<=K-1;M++){if(this.isSelected(M)){this.unselectRow(M);}}}else{for(M=K+1;M<=P.recordIndex-1;M++){if(this.isSelected(M)){this.unselectRow(M);}}}this.selectRow(Q);}}else{this._oAnchorRecord=Q;if(this.isSelected(Q)){this.unselectRow(Q);}else{this.selectRow(Q);}}}else{if(R){this.unselectAllRows();if(P){if(P.recordIndex<K){for(M=P.recordIndex;M<=K;M++){this.selectRow(M);}}else{for(M=P.recordIndex;M>=K;M--){this.selectRow(M);}}}else{this._oAnchorRecord=Q;this.selectRow(Q);}}else{if(N){this._oAnchorRecord=Q;if(this.isSelected(Q)){this.unselectRow(Q);}else{this.selectRow(Q);}}else{this._handleSingleSelectionByMouse(J);return;}}}}},_handleStandardSelectionByKey:function(M){var I=G.getCharCode(M);if((I==38)||(I==40)){var K=M.shiftKey;var J=this._getSelectionTrigger();if(!J){return null;}G.stopEvent(M);var L=this._getSelectionAnchor(J);if(K){if((I==40)&&(L.recordIndex<=J.trIndex)){this.selectRow(this.getNextTrEl(J.el));}else{if((I==38)&&(L.recordIndex>=J.trIndex)){this.selectRow(this.getPreviousTrEl(J.el));}else{this.unselectRow(J.el);}}}else{this._handleSingleSelectionByKey(M);}}},_handleSingleSelectionByMouse:function(K){var L=K.target;var J=this.getTrEl(L);if(J){var I=this.getRecord(J);this._oAnchorRecord=I;this.unselectAllRows();this.selectRow(I);}},_handleSingleSelectionByKey:function(L){var I=G.getCharCode(L);if((I==38)||(I==40)){var J=this._getSelectionTrigger();if(!J){return null;}G.stopEvent(L);var K;if(I==38){K=this.getPreviousTrEl(J.el);if(K===null){K=this.getFirstTrEl();}}else{if(I==40){K=this.getNextTrEl(J.el);if(K===null){K=this.getLastTrEl();}}}this.unselectAllRows();this.selectRow(K);this._oAnchorRecord=this.getRecord(K);}},_handleCellBlockSelectionByMouse:function(Y){var Z=Y.target;var J=this.getTdEl(Z);if(J){var X=Y.event;var O=X.shiftKey;var K=X.ctrlKey||((navigator.userAgent.toLowerCase().indexOf("mac")!=-1)&&X.metaKey);var Q=this.getTrEl(J);var P=this.getTrIndex(Q);var T=this.getColumn(J);var U=T.getKeyIndex();var S=this.getRecord(Q);var b=this._oRecordSet.getRecordIndex(S);var N={record:S,column:T};var R=this._getSelectionAnchor();var M=this.getTbodyEl().rows;var L,I,a,W,V;if(O&&K){if(R){if(this.isSelected(R.cell)){if(R.recordIndex===b){if(R.colKeyIndex<U){for(W=R.colKeyIndex+1;W<=U;W++){this.selectCell(Q.cells[W]);}}else{if(U<R.colKeyIndex){for(W=U;W<R.colKeyIndex;W++){this.selectCell(Q.cells[W]);}}}}else{if(R.recordIndex<b){L=Math.min(R.colKeyIndex,U);I=Math.max(R.colKeyIndex,U);for(W=R.trIndex;W<=P;W++){for(V=L;V<=I;V++){this.selectCell(M[W].cells[V]);}}}else{L=Math.min(R.trIndex,U);I=Math.max(R.trIndex,U);for(W=R.trIndex;W>=P;W--){for(V=I;V>=L;V--){this.selectCell(M[W].cells[V]);}}}}}else{if(R.recordIndex===b){if(R.colKeyIndex<U){for(W=R.colKeyIndex+1;W<U;W++){this.unselectCell(Q.cells[W]);}}else{if(U<R.colKeyIndex){for(W=U+1;W<R.colKeyIndex;W++){this.unselectCell(Q.cells[W]);}}}}if(R.recordIndex<b){for(W=R.trIndex;W<=P;W++){a=M[W];for(V=0;V<a.cells.length;V++){if(a.sectionRowIndex===R.trIndex){if(V>R.colKeyIndex){this.unselectCell(a.cells[V]);}}else{if(a.sectionRowIndex===P){if(V<U){this.unselectCell(a.cells[V]);}}else{this.unselectCell(a.cells[V]);}}}}}else{for(W=P;W<=R.trIndex;W++){a=M[W];for(V=0;V<a.cells.length;V++){if(a.sectionRowIndex==P){if(V>U){this.unselectCell(a.cells[V]);}}else{if(a.sectionRowIndex==R.trIndex){if(V<R.colKeyIndex){this.unselectCell(a.cells[V]);}}else{this.unselectCell(a.cells[V]);}}}}}this.selectCell(J);}}else{this._oAnchorCell=N;if(this.isSelected(N)){this.unselectCell(N);}else{this.selectCell(N);}}}else{if(O){this.unselectAllCells();if(R){if(R.recordIndex===b){if(R.colKeyIndex<U){for(W=R.colKeyIndex;W<=U;W++){this.selectCell(Q.cells[W]);}}else{if(U<R.colKeyIndex){for(W=U;W<=R.colKeyIndex;W++){this.selectCell(Q.cells[W]);}}}}else{if(R.recordIndex<b){L=Math.min(R.colKeyIndex,U);I=Math.max(R.colKeyIndex,U);for(W=R.trIndex;W<=P;W++){for(V=L;V<=I;V++){this.selectCell(M[W].cells[V]);}}}else{L=Math.min(R.colKeyIndex,U);I=Math.max(R.colKeyIndex,U);for(W=P;W<=R.trIndex;W++){for(V=L;V<=I;V++){this.selectCell(M[W].cells[V]);}}}}}else{this._oAnchorCell=N;this.selectCell(N);
+}}else{if(K){this._oAnchorCell=N;if(this.isSelected(N)){this.unselectCell(N);}else{this.selectCell(N);}}else{this._handleSingleCellSelectionByMouse(Y);}}}}},_handleCellBlockSelectionByKey:function(N){var I=G.getCharCode(N);var S=N.shiftKey;if((I==9)||!S){this._handleSingleCellSelectionByKey(N);return;}if((I>36)&&(I<41)){var T=this._getSelectionTrigger();if(!T){return null;}G.stopEvent(N);var Q=this._getSelectionAnchor(T);var J,R,K,P,L;var O=this.getTbodyEl().rows;var M=T.el.parentNode;if(I==40){if(Q.recordIndex<=T.recordIndex){L=this.getNextTrEl(T.el);if(L){R=Q.colKeyIndex;K=T.colKeyIndex;if(R>K){for(J=R;J>=K;J--){P=L.cells[J];this.selectCell(P);}}else{for(J=R;J<=K;J++){P=L.cells[J];this.selectCell(P);}}}}else{R=Math.min(Q.colKeyIndex,T.colKeyIndex);K=Math.max(Q.colKeyIndex,T.colKeyIndex);for(J=R;J<=K;J++){this.unselectCell(M.cells[J]);}}}else{if(I==38){if(Q.recordIndex>=T.recordIndex){L=this.getPreviousTrEl(T.el);if(L){R=Q.colKeyIndex;K=T.colKeyIndex;if(R>K){for(J=R;J>=K;J--){P=L.cells[J];this.selectCell(P);}}else{for(J=R;J<=K;J++){P=L.cells[J];this.selectCell(P);}}}}else{R=Math.min(Q.colKeyIndex,T.colKeyIndex);K=Math.max(Q.colKeyIndex,T.colKeyIndex);for(J=R;J<=K;J++){this.unselectCell(M.cells[J]);}}}else{if(I==39){if(Q.colKeyIndex<=T.colKeyIndex){if(T.colKeyIndex<M.cells.length-1){R=Q.trIndex;K=T.trIndex;if(R>K){for(J=R;J>=K;J--){P=O[J].cells[T.colKeyIndex+1];this.selectCell(P);}}else{for(J=R;J<=K;J++){P=O[J].cells[T.colKeyIndex+1];this.selectCell(P);}}}}else{R=Math.min(Q.trIndex,T.trIndex);K=Math.max(Q.trIndex,T.trIndex);for(J=R;J<=K;J++){this.unselectCell(O[J].cells[T.colKeyIndex]);}}}else{if(I==37){if(Q.colKeyIndex>=T.colKeyIndex){if(T.colKeyIndex>0){R=Q.trIndex;K=T.trIndex;if(R>K){for(J=R;J>=K;J--){P=O[J].cells[T.colKeyIndex-1];this.selectCell(P);}}else{for(J=R;J<=K;J++){P=O[J].cells[T.colKeyIndex-1];this.selectCell(P);}}}}else{R=Math.min(Q.trIndex,T.trIndex);K=Math.max(Q.trIndex,T.trIndex);for(J=R;J<=K;J++){this.unselectCell(O[J].cells[T.colKeyIndex]);}}}}}}}},_handleCellRangeSelectionByMouse:function(W){var X=W.target;var I=this.getTdEl(X);if(I){var V=W.event;var M=V.shiftKey;var J=V.ctrlKey||((navigator.userAgent.toLowerCase().indexOf("mac")!=-1)&&V.metaKey);var O=this.getTrEl(I);var N=this.getTrIndex(O);var R=this.getColumn(I);var S=R.getKeyIndex();var Q=this.getRecord(O);var Z=this._oRecordSet.getRecordIndex(Q);var L={record:Q,column:R};var P=this._getSelectionAnchor();var K=this.getTbodyEl().rows;var Y,U,T;if(M&&J){if(P){if(this.isSelected(P.cell)){if(P.recordIndex===Z){if(P.colKeyIndex<S){for(U=P.colKeyIndex+1;U<=S;U++){this.selectCell(O.cells[U]);}}else{if(S<P.colKeyIndex){for(U=S;U<P.colKeyIndex;U++){this.selectCell(O.cells[U]);}}}}else{if(P.recordIndex<Z){for(U=P.colKeyIndex+1;U<O.cells.length;U++){this.selectCell(O.cells[U]);}for(U=P.trIndex+1;U<N;U++){for(T=0;T<K[U].cells.length;T++){this.selectCell(K[U].cells[T]);}}for(U=0;U<=S;U++){this.selectCell(O.cells[U]);}}else{for(U=S;U<O.cells.length;U++){this.selectCell(O.cells[U]);}for(U=N+1;U<P.trIndex;U++){for(T=0;T<K[U].cells.length;T++){this.selectCell(K[U].cells[T]);}}for(U=0;U<P.colKeyIndex;U++){this.selectCell(O.cells[U]);}}}}else{if(P.recordIndex===Z){if(P.colKeyIndex<S){for(U=P.colKeyIndex+1;U<S;U++){this.unselectCell(O.cells[U]);}}else{if(S<P.colKeyIndex){for(U=S+1;U<P.colKeyIndex;U++){this.unselectCell(O.cells[U]);}}}}if(P.recordIndex<Z){for(U=P.trIndex;U<=N;U++){Y=K[U];for(T=0;T<Y.cells.length;T++){if(Y.sectionRowIndex===P.trIndex){if(T>P.colKeyIndex){this.unselectCell(Y.cells[T]);}}else{if(Y.sectionRowIndex===N){if(T<S){this.unselectCell(Y.cells[T]);}}else{this.unselectCell(Y.cells[T]);}}}}}else{for(U=N;U<=P.trIndex;U++){Y=K[U];for(T=0;T<Y.cells.length;T++){if(Y.sectionRowIndex==N){if(T>S){this.unselectCell(Y.cells[T]);}}else{if(Y.sectionRowIndex==P.trIndex){if(T<P.colKeyIndex){this.unselectCell(Y.cells[T]);}}else{this.unselectCell(Y.cells[T]);}}}}}this.selectCell(I);}}else{this._oAnchorCell=L;if(this.isSelected(L)){this.unselectCell(L);}else{this.selectCell(L);}}}else{if(M){this.unselectAllCells();if(P){if(P.recordIndex===Z){if(P.colKeyIndex<S){for(U=P.colKeyIndex;U<=S;U++){this.selectCell(O.cells[U]);}}else{if(S<P.colKeyIndex){for(U=S;U<=P.colKeyIndex;U++){this.selectCell(O.cells[U]);}}}}else{if(P.recordIndex<Z){for(U=P.trIndex;U<=N;U++){Y=K[U];for(T=0;T<Y.cells.length;T++){if(Y.sectionRowIndex==P.trIndex){if(T>=P.colKeyIndex){this.selectCell(Y.cells[T]);}}else{if(Y.sectionRowIndex==N){if(T<=S){this.selectCell(Y.cells[T]);}}else{this.selectCell(Y.cells[T]);}}}}}else{for(U=N;U<=P.trIndex;U++){Y=K[U];for(T=0;T<Y.cells.length;T++){if(Y.sectionRowIndex==N){if(T>=S){this.selectCell(Y.cells[T]);}}else{if(Y.sectionRowIndex==P.trIndex){if(T<=P.colKeyIndex){this.selectCell(Y.cells[T]);}}else{this.selectCell(Y.cells[T]);}}}}}}}else{this._oAnchorCell=L;this.selectCell(L);}}else{if(J){this._oAnchorCell=L;if(this.isSelected(L)){this.unselectCell(L);}else{this.selectCell(L);}}else{this._handleSingleCellSelectionByMouse(W);}}}}},_handleCellRangeSelectionByKey:function(M){var I=G.getCharCode(M);var Q=M.shiftKey;if((I==9)||!Q){this._handleSingleCellSelectionByKey(M);return;}if((I>36)&&(I<41)){var R=this._getSelectionTrigger();if(!R){return null;}G.stopEvent(M);var P=this._getSelectionAnchor(R);var J,K,O;var N=this.getTbodyEl().rows;var L=R.el.parentNode;if(I==40){K=this.getNextTrEl(R.el);if(P.recordIndex<=R.recordIndex){for(J=R.colKeyIndex+1;J<L.cells.length;J++){O=L.cells[J];this.selectCell(O);}if(K){for(J=0;J<=R.colKeyIndex;J++){O=K.cells[J];this.selectCell(O);}}}else{for(J=R.colKeyIndex;J<L.cells.length;J++){this.unselectCell(L.cells[J]);}if(K){for(J=0;J<R.colKeyIndex;J++){this.unselectCell(K.cells[J]);}}}}else{if(I==38){K=this.getPreviousTrEl(R.el);if(P.recordIndex>=R.recordIndex){for(J=R.colKeyIndex-1;J>-1;J--){O=L.cells[J];this.selectCell(O);}if(K){for(J=L.cells.length-1;J>=R.colKeyIndex;J--){O=K.cells[J];this.selectCell(O);}}}else{for(J=R.colKeyIndex;J>-1;J--){this.unselectCell(L.cells[J]);}if(K){for(J=L.cells.length-1;J>R.colKeyIndex;
+J--){this.unselectCell(K.cells[J]);}}}}else{if(I==39){K=this.getNextTrEl(R.el);if(P.recordIndex<R.recordIndex){if(R.colKeyIndex<L.cells.length-1){O=L.cells[R.colKeyIndex+1];this.selectCell(O);}else{if(K){O=K.cells[0];this.selectCell(O);}}}else{if(P.recordIndex>R.recordIndex){this.unselectCell(L.cells[R.colKeyIndex]);if(R.colKeyIndex<L.cells.length-1){}else{}}else{if(P.colKeyIndex<=R.colKeyIndex){if(R.colKeyIndex<L.cells.length-1){O=L.cells[R.colKeyIndex+1];this.selectCell(O);}else{if(R.trIndex<N.length-1){O=K.cells[0];this.selectCell(O);}}}else{this.unselectCell(L.cells[R.colKeyIndex]);}}}}else{if(I==37){K=this.getPreviousTrEl(R.el);if(P.recordIndex<R.recordIndex){this.unselectCell(L.cells[R.colKeyIndex]);if(R.colKeyIndex>0){}else{}}else{if(P.recordIndex>R.recordIndex){if(R.colKeyIndex>0){O=L.cells[R.colKeyIndex-1];this.selectCell(O);}else{if(R.trIndex>0){O=K.cells[K.cells.length-1];this.selectCell(O);}}}else{if(P.colKeyIndex>=R.colKeyIndex){if(R.colKeyIndex>0){O=L.cells[R.colKeyIndex-1];this.selectCell(O);}else{if(R.trIndex>0){O=K.cells[K.cells.length-1];this.selectCell(O);}}}else{this.unselectCell(L.cells[R.colKeyIndex]);if(R.colKeyIndex>0){}else{}}}}}}}}}},_handleSingleCellSelectionByMouse:function(N){var O=N.target;var K=this.getTdEl(O);if(K){var J=this.getTrEl(K);var I=this.getRecord(J);var M=this.getColumn(K);var L={record:I,column:M};this._oAnchorCell=L;this.unselectAllCells();this.selectCell(L);}},_handleSingleCellSelectionByKey:function(M){var I=G.getCharCode(M);if((I==9)||((I>36)&&(I<41))){var K=M.shiftKey;var J=this._getSelectionTrigger();if(!J){return null;}var L;if(I==40){L=this.getBelowTdEl(J.el);if(L===null){L=J.el;}}else{if(I==38){L=this.getAboveTdEl(J.el);if(L===null){L=J.el;}}else{if((I==39)||(!K&&(I==9))){L=this.getNextTdEl(J.el);if(L===null){return;}}else{if((I==37)||(K&&(I==9))){L=this.getPreviousTdEl(J.el);if(L===null){return;}}}}}G.stopEvent(M);this.unselectAllCells();this.selectCell(L);this._oAnchorCell={record:this.getRecord(L),column:this.getColumn(L)};}},getSelectedTrEls:function(){return C.getElementsByClassName(D.CLASS_SELECTED,"tr",this._elTbody);},selectRow:function(O){var N,I;if(O instanceof YAHOO.widget.Record){N=this._oRecordSet.getRecord(O);I=this.getTrEl(N);}else{if(H.isNumber(O)){N=this.getRecord(O);I=this.getTrEl(N);}else{I=this.getTrEl(O);N=this.getRecord(I);}}if(N){var M=this._aSelections||[];var L=N.getId();var K=-1;if(M.indexOf){K=M.indexOf(L);}else{for(var J=M.length-1;J>-1;J--){if(M[J]===L){K=J;break;}}}if(K>-1){M.splice(K,1);}M.push(L);this._aSelections=M;if(!this._oAnchorRecord){this._oAnchorRecord=N;}if(I){C.addClass(I,D.CLASS_SELECTED);}this.fireEvent("rowSelectEvent",{record:N,el:I});}else{}},unselectRow:function(O){var I=this.getTrEl(O);var N;if(O instanceof YAHOO.widget.Record){N=this._oRecordSet.getRecord(O);}else{if(H.isNumber(O)){N=this.getRecord(O);}else{N=this.getRecord(I);}}if(N){var M=this._aSelections||[];var L=N.getId();var K=-1;if(M.indexOf){K=M.indexOf(L);}else{for(var J=M.length-1;J>-1;J--){if(M[J]===L){K=J;break;}}}if(K>-1){M.splice(K,1);this._aSelections=M;C.removeClass(I,D.CLASS_SELECTED);this.fireEvent("rowUnselectEvent",{record:N,el:I});return;}}},unselectAllRows:function(){var J=this._aSelections||[],L,K=[];for(var I=J.length-1;I>-1;I--){if(H.isString(J[I])){L=J.splice(I,1);K[K.length]=this.getRecord(H.isArray(L)?L[0]:L);}}this._aSelections=J;this._unselectAllTrEls();this.fireEvent("unselectAllRowsEvent",{records:K});},_unselectAllTdEls:function(){var I=C.getElementsByClassName(D.CLASS_SELECTED,"td",this._elTbody);C.removeClass(I,D.CLASS_SELECTED);},getSelectedTdEls:function(){return C.getElementsByClassName(D.CLASS_SELECTED,"td",this._elTbody);},selectCell:function(I){var O=this.getTdEl(I);if(O){var N=this.getRecord(O);var L=this.getColumn(O.cellIndex).getKey();if(N&&L){var M=this._aSelections||[];var K=N.getId();for(var J=M.length-1;J>-1;J--){if((M[J].recordId===K)&&(M[J].columnKey===L)){M.splice(J,1);break;}}M.push({recordId:K,columnKey:L});this._aSelections=M;if(!this._oAnchorCell){this._oAnchorCell={record:N,column:this.getColumn(L)};}C.addClass(O,D.CLASS_SELECTED);this.fireEvent("cellSelectEvent",{record:N,column:this.getColumn(O.cellIndex),key:this.getColumn(O.cellIndex).getKey(),el:O});return;}}},unselectCell:function(I){var N=this.getTdEl(I);if(N){var M=this.getRecord(N);var K=this.getColumn(N.cellIndex).getKey();if(M&&K){var L=this._aSelections||[];var O=M.getId();for(var J=L.length-1;J>-1;J--){if((L[J].recordId===O)&&(L[J].columnKey===K)){L.splice(J,1);this._aSelections=L;C.removeClass(N,D.CLASS_SELECTED);this.fireEvent("cellUnselectEvent",{record:M,column:this.getColumn(N.cellIndex),key:this.getColumn(N.cellIndex).getKey(),el:N});return;}}}}},unselectAllCells:function(){var J=this._aSelections||[];for(var I=J.length-1;I>-1;I--){if(H.isObject(J[I])){J.splice(I,1);}}this._aSelections=J;this._unselectAllTdEls();this.fireEvent("unselectAllCellsEvent");},isSelected:function(N){if(N&&(N.ownerDocument==document)){return(C.hasClass(this.getTdEl(N),D.CLASS_SELECTED)||C.hasClass(this.getTrEl(N),D.CLASS_SELECTED));}else{var M,J,I;var L=this._aSelections;if(L&&L.length>0){if(N instanceof YAHOO.widget.Record){M=N;}else{if(H.isNumber(N)){M=this.getRecord(N);}}if(M){J=M.getId();if(L.indexOf){if(L.indexOf(J)>-1){return true;}}else{for(I=L.length-1;I>-1;I--){if(L[I]===J){return true;}}}}else{if(N.record&&N.column){J=N.record.getId();var K=N.column.getKey();for(I=L.length-1;I>-1;I--){if((L[I].recordId===J)&&(L[I].columnKey===K)){return true;}}}}}}return false;},getSelectedRows:function(){var I=[];var K=this._aSelections||[];for(var J=0;J<K.length;J++){if(H.isString(K[J])){I.push(K[J]);}}return I;},getSelectedCells:function(){var J=[];var K=this._aSelections||[];for(var I=0;I<K.length;I++){if(K[I]&&H.isObject(K[I])){J.push(K[I]);}}return J;},getLastSelectedRecord:function(){var J=this._aSelections;if(J&&J.length>0){for(var I=J.length-1;I>-1;I--){if(H.isString(J[I])){return J[I];}}}},getLastSelectedCell:function(){var J=this._aSelections;
+if(J&&J.length>0){for(var I=J.length-1;I>-1;I--){if(J[I].recordId&&J[I].columnKey){return J[I];}}}},highlightRow:function(K){var I=this.getTrEl(K);if(I){var J=this.getRecord(I);C.addClass(I,D.CLASS_HIGHLIGHTED);this.fireEvent("rowHighlightEvent",{record:J,el:I});return;}},unhighlightRow:function(K){var I=this.getTrEl(K);if(I){var J=this.getRecord(I);C.removeClass(I,D.CLASS_HIGHLIGHTED);this.fireEvent("rowUnhighlightEvent",{record:J,el:I});return;}},highlightCell:function(I){var L=this.getTdEl(I);if(L){if(this._elLastHighlightedTd){this.unhighlightCell(this._elLastHighlightedTd);}var K=this.getRecord(L);var J=this.getColumn(L.cellIndex).getKey();C.addClass(L,D.CLASS_HIGHLIGHTED);this._elLastHighlightedTd=L;this.fireEvent("cellHighlightEvent",{record:K,column:this.getColumn(L.cellIndex),key:this.getColumn(L.cellIndex).getKey(),el:L});return;}},unhighlightCell:function(I){var K=this.getTdEl(I);if(K){var J=this.getRecord(K);C.removeClass(K,D.CLASS_HIGHLIGHTED);this._elLastHighlightedTd=null;this.fireEvent("cellUnhighlightEvent",{record:J,column:this.getColumn(K.cellIndex),key:this.getColumn(K.cellIndex).getKey(),el:K});return;}},getCellEditor:function(){return this._oCellEditor;},showCellEditor:function(P,Q,L){P=this.getTdEl(P);if(P){L=this.getColumn(P);if(L&&L.editor){var J=this._oCellEditor;if(J){if(this._oCellEditor.cancel){this._oCellEditor.cancel();}else{if(J.isActive){this.cancelCellEditor();}}}if(L.editor instanceof YAHOO.widget.BaseCellEditor){J=L.editor;var N=J.attach(this,P);if(N){J.move();N=this.doBeforeShowCellEditor(J);if(N){J.show();this._oCellEditor=J;}}}else{if(!Q||!(Q instanceof YAHOO.widget.Record)){Q=this.getRecord(P);}if(!L||!(L instanceof YAHOO.widget.Column)){L=this.getColumn(P);}if(Q&&L){if(!this._oCellEditor||this._oCellEditor.container){this._initCellEditorEl();}J=this._oCellEditor;J.cell=P;J.record=Q;J.column=L;J.validator=(L.editorOptions&&H.isFunction(L.editorOptions.validator))?L.editorOptions.validator:null;J.value=Q.getData(L.key);J.defaultValue=null;var K=J.container;var O=C.getX(P);var M=C.getY(P);if(isNaN(O)||isNaN(M)){O=P.offsetLeft+C.getX(this._elTbody.parentNode)-this._elTbody.scrollLeft;M=P.offsetTop+C.getY(this._elTbody.parentNode)-this._elTbody.scrollTop+this._elThead.offsetHeight;}K.style.left=O+"px";K.style.top=M+"px";this.doBeforeShowCellEditor(this._oCellEditor);K.style.display="";G.addListener(K,"keydown",function(S,R){if((S.keyCode==27)){R.cancelCellEditor();R.focusTbodyEl();}else{R.fireEvent("editorKeydownEvent",{editor:R._oCellEditor,event:S});}},this);var I;if(H.isString(L.editor)){switch(L.editor){case"checkbox":I=D.editCheckbox;break;case"date":I=D.editDate;break;case"dropdown":I=D.editDropdown;break;case"radio":I=D.editRadio;break;case"textarea":I=D.editTextarea;break;case"textbox":I=D.editTextbox;break;default:I=null;}}else{if(H.isFunction(L.editor)){I=L.editor;}}if(I){I(this._oCellEditor,this);if(!L.editorOptions||!L.editorOptions.disableBtns){this.showCellEditorBtns(K);}J.isActive=true;this.fireEvent("editorShowEvent",{editor:J});return;}}}}}},_initCellEditorEl:function(){var I=document.createElement("div");I.id=this._sId+"-celleditor";I.style.display="none";I.tabIndex=0;C.addClass(I,D.CLASS_EDITOR);var K=C.getFirstChild(document.body);if(K){I=C.insertBefore(I,K);}else{I=document.body.appendChild(I);}var J={};J.container=I;J.value=null;J.isActive=false;this._oCellEditor=J;},doBeforeShowCellEditor:function(I){return true;},saveCellEditor:function(){if(this._oCellEditor){if(this._oCellEditor.save){this._oCellEditor.save();}else{if(this._oCellEditor.isActive){var I=this._oCellEditor.value;var J=this._oCellEditor.record.getData(this._oCellEditor.column.key);if(this._oCellEditor.validator){I=this._oCellEditor.value=this._oCellEditor.validator.call(this,I,J,this._oCellEditor);if(I===null){this.resetCellEditor();this.fireEvent("editorRevertEvent",{editor:this._oCellEditor,oldData:J,newData:I});return;}}this._oRecordSet.updateRecordValue(this._oCellEditor.record,this._oCellEditor.column.key,this._oCellEditor.value);this.formatCell(this._oCellEditor.cell.firstChild);this._oChainRender.add({method:function(){this.validateColumnWidths();},scope:this});this._oChainRender.run();this.resetCellEditor();this.fireEvent("editorSaveEvent",{editor:this._oCellEditor,oldData:J,newData:I});}}}},cancelCellEditor:function(){if(this._oCellEditor){if(this._oCellEditor.cancel){this._oCellEditor.cancel();}else{if(this._oCellEditor.isActive){this.resetCellEditor();this.fireEvent("editorCancelEvent",{editor:this._oCellEditor});}}}},destroyCellEditor:function(){if(this._oCellEditor){this._oCellEditor.destroy();this._oCellEditor=null;}},_onEditorShowEvent:function(I){this.fireEvent("editorShowEvent",I);},_onEditorKeydownEvent:function(I){this.fireEvent("editorKeydownEvent",I);},_onEditorRevertEvent:function(I){this.fireEvent("editorRevertEvent",I);},_onEditorSaveEvent:function(I){this.fireEvent("editorSaveEvent",I);},_onEditorCancelEvent:function(I){this.fireEvent("editorCancelEvent",I);},_onEditorBlurEvent:function(I){this.fireEvent("editorBlurEvent",I);},_onEditorBlockEvent:function(I){this.fireEvent("editorBlockEvent",I);},_onEditorUnblockEvent:function(I){this.fireEvent("editorUnblockEvent",I);},onEditorBlurEvent:function(I){if(I.editor.disableBtns){if(I.editor.save){I.editor.save();}}else{if(I.editor.cancel){I.editor.cancel();}}},onEditorBlockEvent:function(I){this.disable();},onEditorUnblockEvent:function(I){this.undisable();},doBeforeLoadData:function(I,J,K){return true;},onEventSortColumn:function(K){var I=K.event;var M=K.target;var J=this.getThEl(M)||this.getTdEl(M);if(J){var L=this.getColumn(J);if(L.sortable){G.stopEvent(I);this.sortColumn(L);}}else{}},onEventSelectColumn:function(I){this.selectColumn(I.target);},onEventHighlightColumn:function(I){if(!C.isAncestor(I.target,G.getRelatedTarget(I.event))){this.highlightColumn(I.target);}},onEventUnhighlightColumn:function(I){if(!C.isAncestor(I.target,G.getRelatedTarget(I.event))){this.unhighlightColumn(I.target);}},onEventSelectRow:function(J){var I=this.get("selectionMode");
+if(I=="single"){this._handleSingleSelectionByMouse(J);}else{this._handleStandardSelectionByMouse(J);}},onEventSelectCell:function(J){var I=this.get("selectionMode");if(I=="cellblock"){this._handleCellBlockSelectionByMouse(J);}else{if(I=="cellrange"){this._handleCellRangeSelectionByMouse(J);}else{this._handleSingleCellSelectionByMouse(J);}}},onEventHighlightRow:function(I){if(!C.isAncestor(I.target,G.getRelatedTarget(I.event))){this.highlightRow(I.target);}},onEventUnhighlightRow:function(I){if(!C.isAncestor(I.target,G.getRelatedTarget(I.event))){this.unhighlightRow(I.target);}},onEventHighlightCell:function(I){if(!C.isAncestor(I.target,G.getRelatedTarget(I.event))){this.highlightCell(I.target);}},onEventUnhighlightCell:function(I){if(!C.isAncestor(I.target,G.getRelatedTarget(I.event))){this.unhighlightCell(I.target);}},onEventFormatCell:function(I){var L=I.target;var J=this.getTdEl(L);if(J){var K=this.getColumn(J.cellIndex);this.formatCell(J.firstChild,this.getRecord(J),K);}else{}},onEventShowCellEditor:function(I){this.showCellEditor(I.target);},onEventSaveCellEditor:function(I){if(this._oCellEditor){if(this._oCellEditor.save){this._oCellEditor.save();}else{this.saveCellEditor();}}},onEventCancelCellEditor:function(I){if(this._oCellEditor){if(this._oCellEditor.cancel){this._oCellEditor.cancel();}else{this.cancelCellEditor();}}},onDataReturnInitializeTable:function(I,J,K){if((this instanceof D)&&this._sId){this.initializeTable();this.onDataReturnSetRows(I,J,K);}},onDataReturnReplaceRows:function(M,L,N){if((this instanceof D)&&this._sId){this.fireEvent("dataReturnEvent",{request:M,response:L,payload:N});var J=this.doBeforeLoadData(M,L,N),K=this.get("paginator"),I=0;if(J&&L&&!L.error&&H.isArray(L.results)){this._oRecordSet.reset();if(this.get("dynamicData")){if(N&&N.pagination&&H.isNumber(N.pagination.recordOffset)){I=N.pagination.recordOffset;}else{if(K){I=K.getStartIndex();}}}this._oRecordSet.setRecords(L.results,I|0);this._handleDataReturnPayload(M,L,N);this.render();}else{if(J&&L.error){this.showTableMessage(this.get("MSG_ERROR"),D.CLASS_ERROR);}}}},onDataReturnAppendRows:function(J,K,L){if((this instanceof D)&&this._sId){this.fireEvent("dataReturnEvent",{request:J,response:K,payload:L});var I=this.doBeforeLoadData(J,K,L);if(I&&K&&!K.error&&H.isArray(K.results)){this.addRows(K.results);this._handleDataReturnPayload(J,K,L);}else{if(I&&K.error){this.showTableMessage(this.get("MSG_ERROR"),D.CLASS_ERROR);}}}},onDataReturnInsertRows:function(J,K,L){if((this instanceof D)&&this._sId){this.fireEvent("dataReturnEvent",{request:J,response:K,payload:L});var I=this.doBeforeLoadData(J,K,L);if(I&&K&&!K.error&&H.isArray(K.results)){this.addRows(K.results,(L?L.insertIndex:0));this._handleDataReturnPayload(J,K,L);}else{if(I&&K.error){this.showTableMessage(this.get("MSG_ERROR"),D.CLASS_ERROR);}}}},onDataReturnUpdateRows:function(J,K,L){if((this instanceof D)&&this._sId){this.fireEvent("dataReturnEvent",{request:J,response:K,payload:L});var I=this.doBeforeLoadData(J,K,L);if(I&&K&&!K.error&&H.isArray(K.results)){this.updateRows((L?L.updateIndex:0),K.results);this._handleDataReturnPayload(J,K,L);}else{if(I&&K.error){this.showTableMessage(this.get("MSG_ERROR"),D.CLASS_ERROR);}}}},onDataReturnSetRows:function(M,L,N){if((this instanceof D)&&this._sId){this.fireEvent("dataReturnEvent",{request:M,response:L,payload:N});var J=this.doBeforeLoadData(M,L,N),K=this.get("paginator"),I=0;if(J&&L&&!L.error&&H.isArray(L.results)){if(this.get("dynamicData")){if(N&&N.pagination&&H.isNumber(N.pagination.recordOffset)){I=N.pagination.recordOffset;}else{if(K){I=K.getStartIndex();}}this._oRecordSet.reset();}this._oRecordSet.setRecords(L.results,I|0);this._handleDataReturnPayload(M,L,N);this.render();}else{if(J&&L.error){this.showTableMessage(this.get("MSG_ERROR"),D.CLASS_ERROR);}}}else{}},handleDataReturnPayload:function(J,I,K){return K;},_handleDataReturnPayload:function(K,J,L){L=this.handleDataReturnPayload(K,J,L);if(L){var I=this.get("paginator");if(I){if(this.get("dynamicData")){if(E.Paginator.isNumeric(L.totalRecords)){I.set("totalRecords",L.totalRecords);}}else{I.set("totalRecords",this._oRecordSet.getLength());}if(H.isObject(L.pagination)){I.set("rowsPerPage",L.pagination.rowsPerPage);I.set("recordOffset",L.pagination.recordOffset);}}if(L.sortedBy){this.set("sortedBy",L.sortedBy);}else{if(L.sorting){this.set("sortedBy",L.sorting);}}}},showCellEditorBtns:function(K){var L=K.appendChild(document.createElement("div"));C.addClass(L,D.CLASS_BUTTON);var J=L.appendChild(document.createElement("button"));C.addClass(J,D.CLASS_DEFAULT);J.innerHTML="OK";G.addListener(J,"click",function(N,M){M.onEventSaveCellEditor(N,M);M.focusTbodyEl();},this,true);var I=L.appendChild(document.createElement("button"));I.innerHTML="Cancel";G.addListener(I,"click",function(N,M){M.onEventCancelCellEditor(N,M);M.focusTbodyEl();},this,true);},resetCellEditor:function(){var I=this._oCellEditor.container;I.style.display="none";G.purgeElement(I,true);I.innerHTML="";this._oCellEditor.value=null;this._oCellEditor.isActive=false;},getBody:function(){return this.getTbodyEl();},getCell:function(I){return this.getTdEl(I);},getRow:function(I){return this.getTrEl(I);},refreshView:function(){this.render();},select:function(J){if(!H.isArray(J)){J=[J];}for(var I=0;I<J.length;I++){this.selectRow(J[I]);}},onEventEditCell:function(I){this.onEventShowCellEditor(I);},_syncColWidths:function(){this.validateColumnWidths();}});D.prototype.onDataReturnSetRecords=D.prototype.onDataReturnSetRows;D.prototype.onPaginatorChange=D.prototype.onPaginatorChangeRequest;D.formatTheadCell=function(){};D.editCheckbox=function(){};D.editDate=function(){};D.editDropdown=function(){};D.editRadio=function(){};D.editTextarea=function(){};D.editTextbox=function(){};})();(function(){var C=YAHOO.lang,F=YAHOO.util,E=YAHOO.widget,A=YAHOO.env.ua,D=F.Dom,J=F.Event,I=F.DataSourceBase,G=E.DataTable,B=E.Paginator;E.ScrollingDataTable=function(N,M,K,L){L=L||{};if(L.scrollable){L.scrollable=false;}E.ScrollingDataTable.superclass.constructor.call(this,N,M,K,L);
+this.subscribe("columnShowEvent",this._onColumnChange);};var H=E.ScrollingDataTable;C.augmentObject(H,{CLASS_HEADER:"yui-dt-hd",CLASS_BODY:"yui-dt-bd"});C.extend(H,G,{_elHdContainer:null,_elHdTable:null,_elBdContainer:null,_elBdThead:null,_elTmpContainer:null,_elTmpTable:null,_bScrollbarX:null,initAttributes:function(K){K=K||{};H.superclass.initAttributes.call(this,K);this.setAttributeConfig("width",{value:null,validator:C.isString,method:function(L){if(this._elHdContainer&&this._elBdContainer){this._elHdContainer.style.width=L;this._elBdContainer.style.width=L;this._syncScrollX();this._syncScrollOverhang();}}});this.setAttributeConfig("height",{value:null,validator:C.isString,method:function(L){if(this._elHdContainer&&this._elBdContainer){this._elBdContainer.style.height=L;this._syncScrollX();this._syncScrollY();this._syncScrollOverhang();}}});this.setAttributeConfig("COLOR_COLUMNFILLER",{value:"#F2F2F2",validator:C.isString,method:function(L){this._elHdContainer.style.backgroundColor=L;}});},_initDomElements:function(K){this._initContainerEl(K);if(this._elContainer&&this._elHdContainer&&this._elBdContainer){this._initTableEl();if(this._elHdTable&&this._elTable){this._initColgroupEl(this._elHdTable);this._initTheadEl(this._elHdTable,this._elTable);this._initTbodyEl(this._elTable);this._initMsgTbodyEl(this._elTable);}}if(!this._elContainer||!this._elTable||!this._elColgroup||!this._elThead||!this._elTbody||!this._elMsgTbody||!this._elHdTable||!this._elBdThead){return false;}else{return true;}},_destroyContainerEl:function(K){D.removeClass(K,G.CLASS_SCROLLABLE);H.superclass._destroyContainerEl.call(this,K);this._elHdContainer=null;this._elBdContainer=null;},_initContainerEl:function(L){H.superclass._initContainerEl.call(this,L);if(this._elContainer){L=this._elContainer;D.addClass(L,G.CLASS_SCROLLABLE);var K=document.createElement("div");K.style.width=this.get("width")||"";K.style.backgroundColor=this.get("COLOR_COLUMNFILLER");D.addClass(K,H.CLASS_HEADER);this._elHdContainer=K;L.appendChild(K);var M=document.createElement("div");M.style.width=this.get("width")||"";M.style.height=this.get("height")||"";D.addClass(M,H.CLASS_BODY);J.addListener(M,"scroll",this._onScroll,this);this._elBdContainer=M;L.appendChild(M);}},_initCaptionEl:function(K){},_destroyHdTableEl:function(){var K=this._elHdTable;if(K){J.purgeElement(K,true);K.parentNode.removeChild(K);this._elBdThead=null;}},_initTableEl:function(){if(this._elHdContainer){this._destroyHdTableEl();this._elHdTable=this._elHdContainer.appendChild(document.createElement("table"));}H.superclass._initTableEl.call(this,this._elBdContainer);},_initTheadEl:function(L,K){L=L||this._elHdTable;K=K||this._elTable;this._initBdTheadEl(K);H.superclass._initTheadEl.call(this,L);},_initThEl:function(L,K){H.superclass._initThEl.call(this,L,K);L.id=this.getId()+"-fixedth-"+K.getSanitizedKey();},_destroyBdTheadEl:function(){var K=this._elBdThead;if(K){var L=K.parentNode;J.purgeElement(K,true);L.removeChild(K);this._elBdThead=null;this._destroyColumnHelpers();}},_initBdTheadEl:function(S){if(S){this._destroyBdTheadEl();var O=S.insertBefore(document.createElement("thead"),S.firstChild);var U=this._oColumnSet,T=U.tree,N,K,R,P,M,L,Q;for(P=0,L=T.length;P<L;P++){K=O.appendChild(document.createElement("tr"));for(M=0,Q=T[P].length;M<Q;M++){R=T[P][M];N=K.appendChild(document.createElement("th"));this._initBdThEl(N,R,P,M);}}this._elBdThead=O;}},_initBdThEl:function(N,M){N.id=this.getId()+"-th-"+M.getSanitizedKey();N.rowSpan=M.getRowspan();N.colSpan=M.getColspan();if(M.abbr){N.abbr=M.abbr;}var L=M.getKey();var K=C.isValue(M.label)?M.label:L;N.innerHTML=K;},_initTbodyEl:function(K){H.superclass._initTbodyEl.call(this,K);K.style.marginTop=(this._elTbody.offsetTop>0)?"-"+this._elTbody.offsetTop+"px":0;},_focusEl:function(L){L=L||this._elTbody;var K=this;this._storeScrollPositions();setTimeout(function(){setTimeout(function(){try{L.focus();K._restoreScrollPositions();}catch(M){}},0);},0);},_runRenderChain:function(){this._storeScrollPositions();this._oChainRender.run();},_storeScrollPositions:function(){this._nScrollTop=this._elBdContainer.scrollTop;this._nScrollLeft=this._elBdContainer.scrollLeft;},clearScrollPositions:function(){this._nScrollTop=0;this._nScrollLeft=0;},_restoreScrollPositions:function(){if(this._nScrollTop){this._elBdContainer.scrollTop=this._nScrollTop;this._nScrollTop=null;}if(this._nScrollLeft){this._elBdContainer.scrollLeft=this._nScrollLeft;this._nScrollLeft=null;}},_validateColumnWidth:function(N,K){if(!N.width&&!N.hidden){var P=N.getThEl();if(N._calculatedWidth){this._setColumnWidth(N,"auto","visible");}if(P.offsetWidth!==K.offsetWidth){var M=(P.offsetWidth>K.offsetWidth)?N.getThLinerEl():K.firstChild;var L=Math.max(0,(M.offsetWidth-(parseInt(D.getStyle(M,"paddingLeft"),10)|0)-(parseInt(D.getStyle(M,"paddingRight"),10)|0)),N.minWidth);var O="visible";if((N.maxAutoWidth>0)&&(L>N.maxAutoWidth)){L=N.maxAutoWidth;O="hidden";}this._elTbody.style.display="none";this._setColumnWidth(N,L+"px",O);N._calculatedWidth=L;this._elTbody.style.display="";}}},validateColumnWidths:function(S){var U=this._oColumnSet.keys,W=U.length,L=this.getFirstTrEl();if(A.ie){this._setOverhangValue(1);}if(U&&L&&(L.childNodes.length===W)){var M=this.get("width");if(M){this._elHdContainer.style.width="";this._elBdContainer.style.width="";}this._elContainer.style.width="";if(S&&C.isNumber(S.getKeyIndex())){this._validateColumnWidth(S,L.childNodes[S.getKeyIndex()]);}else{var T,K=[],O,Q,R;for(Q=0;Q<W;Q++){S=U[Q];if(!S.width&&!S.hidden&&S._calculatedWidth){K[K.length]=S;}}this._elTbody.style.display="none";for(Q=0,R=K.length;Q<R;Q++){this._setColumnWidth(K[Q],"auto","visible");}this._elTbody.style.display="";K=[];for(Q=0;Q<W;Q++){S=U[Q];T=L.childNodes[Q];if(!S.width&&!S.hidden){var N=S.getThEl();if(N.offsetWidth!==T.offsetWidth){var V=(N.offsetWidth>T.offsetWidth)?S.getThLinerEl():T.firstChild;var P=Math.max(0,(V.offsetWidth-(parseInt(D.getStyle(V,"paddingLeft"),10)|0)-(parseInt(D.getStyle(V,"paddingRight"),10)|0)),S.minWidth);
+var X="visible";if((S.maxAutoWidth>0)&&(P>S.maxAutoWidth)){P=S.maxAutoWidth;X="hidden";}K[K.length]=[S,P,X];}}}this._elTbody.style.display="none";for(Q=0,R=K.length;Q<R;Q++){O=K[Q];this._setColumnWidth(O[0],O[1]+"px",O[2]);O[0]._calculatedWidth=O[1];}this._elTbody.style.display="";}if(M){this._elHdContainer.style.width=M;this._elBdContainer.style.width=M;}}this._syncScroll();this._restoreScrollPositions();},_syncScroll:function(){this._syncScrollX();this._syncScrollY();this._syncScrollOverhang();if(A.opera){this._elHdContainer.scrollLeft=this._elBdContainer.scrollLeft;if(!this.get("width")){document.body.style+="";}}},_syncScrollY:function(){var K=this._elTbody,L=this._elBdContainer;if(!this.get("width")){this._elContainer.style.width=(L.scrollHeight>L.clientHeight)?(K.parentNode.clientWidth+19)+"px":(K.parentNode.clientWidth+2)+"px";}},_syncScrollX:function(){var K=this._elTbody,L=this._elBdContainer;if(!this.get("height")&&(A.ie)){L.style.height=(L.scrollWidth>L.offsetWidth)?(K.parentNode.offsetHeight+18)+"px":K.parentNode.offsetHeight+"px";}if(this._elTbody.rows.length===0){this._elMsgTbody.parentNode.style.width=this.getTheadEl().parentNode.offsetWidth+"px";}else{this._elMsgTbody.parentNode.style.width="";}},_syncScrollOverhang:function(){var L=this._elBdContainer,K=1;if((L.scrollHeight>L.clientHeight)&&(L.scrollWidth>L.clientWidth)){K=18;}this._setOverhangValue(K);},_setOverhangValue:function(N){var P=this._oColumnSet.headers[this._oColumnSet.headers.length-1]||[],L=P.length,K=this._sId+"-fixedth-",O=N+"px solid "+this.get("COLOR_COLUMNFILLER");this._elThead.style.display="none";for(var M=0;M<L;M++){D.get(K+P[M]).style.borderRight=O;}this._elThead.style.display="";},getHdContainerEl:function(){return this._elHdContainer;},getBdContainerEl:function(){return this._elBdContainer;},getHdTableEl:function(){return this._elHdTable;},getBdTableEl:function(){return this._elTable;},disable:function(){var K=this._elMask;K.style.width=this._elBdContainer.offsetWidth+"px";K.style.height=this._elHdContainer.offsetHeight+this._elBdContainer.offsetHeight+"px";K.style.display="";this.fireEvent("disableEvent");},removeColumn:function(M){var K=this._elHdContainer.scrollLeft;var L=this._elBdContainer.scrollLeft;M=H.superclass.removeColumn.call(this,M);this._elHdContainer.scrollLeft=K;this._elBdContainer.scrollLeft=L;return M;},insertColumn:function(N,L){var K=this._elHdContainer.scrollLeft;var M=this._elBdContainer.scrollLeft;var O=H.superclass.insertColumn.call(this,N,L);this._elHdContainer.scrollLeft=K;this._elBdContainer.scrollLeft=M;return O;},reorderColumn:function(N,L){var K=this._elHdContainer.scrollLeft;var M=this._elBdContainer.scrollLeft;var O=H.superclass.reorderColumn.call(this,N,L);this._elHdContainer.scrollLeft=K;this._elBdContainer.scrollLeft=M;return O;},setColumnWidth:function(L,K){L=this.getColumn(L);if(L){this._storeScrollPositions();if(C.isNumber(K)){K=(K>L.minWidth)?K:L.minWidth;L.width=K;this._setColumnWidth(L,K+"px");this._syncScroll();this.fireEvent("columnSetWidthEvent",{column:L,width:K});}else{if(K===null){L.width=K;this._setColumnWidth(L,"auto");this.validateColumnWidths(L);this.fireEvent("columnUnsetWidthEvent",{column:L});}}this._clearTrTemplateEl();}else{}},scrollTo:function(M){var L=this.getTdEl(M);if(L){this.clearScrollPositions();this.getBdContainerEl().scrollLeft=L.offsetLeft;this.getBdContainerEl().scrollTop=L.parentNode.offsetTop;}else{var K=this.getTrEl(M);if(K){this.clearScrollPositions();this.getBdContainerEl().scrollTop=K.offsetTop;}}},showTableMessage:function(O,K){var P=this._elMsgTd;if(C.isString(O)){P.firstChild.innerHTML=O;}if(C.isString(K)){D.addClass(P.firstChild,K);}var N=this.getTheadEl();var L=N.parentNode;var M=L.offsetWidth;this._elMsgTbody.parentNode.style.width=this.getTheadEl().parentNode.offsetWidth+"px";this._elMsgTbody.style.display="";this.fireEvent("tableMsgShowEvent",{html:O,className:K});},_onColumnChange:function(K){var L=(K.column)?K.column:(K.editor)?K.editor.column:null;this._storeScrollPositions();this.validateColumnWidths(L);},_onScroll:function(M,L){L._elHdContainer.scrollLeft=L._elBdContainer.scrollLeft;if(L._oCellEditor&&L._oCellEditor.isActive){L.fireEvent("editorBlurEvent",{editor:L._oCellEditor});L.cancelCellEditor();}var N=J.getTarget(M);var K=N.nodeName.toLowerCase();L.fireEvent("tableScrollEvent",{event:M,target:N});},_onTheadKeydown:function(N,L){if(J.getCharCode(N)===9){setTimeout(function(){if((L instanceof H)&&L._sId){L._elBdContainer.scrollLeft=L._elHdContainer.scrollLeft;}},0);}var O=J.getTarget(N);var K=O.nodeName.toLowerCase();var M=true;while(O&&(K!="table")){switch(K){case"body":return;case"input":case"textarea":break;case"thead":M=L.fireEvent("theadKeyEvent",{target:O,event:N});break;default:break;}if(M===false){return;}else{O=O.parentNode;if(O){K=O.nodeName.toLowerCase();}}}L.fireEvent("tableKeyEvent",{target:(O||L._elContainer),event:N});}});})();(function(){var C=YAHOO.lang,F=YAHOO.util,E=YAHOO.widget,B=YAHOO.env.ua,D=F.Dom,I=F.Event,H=E.DataTable;E.BaseCellEditor=function(K,J){this._sId=this._sId||"yui-ceditor"+YAHOO.widget.BaseCellEditor._nCount++;this._sType=K;this._initConfigs(J);this._initEvents();this.render();};var A=E.BaseCellEditor;C.augmentObject(A,{_nCount:0,CLASS_CELLEDITOR:"yui-ceditor"});A.prototype={_sId:null,_sType:null,_oDataTable:null,_oColumn:null,_oRecord:null,_elTd:null,_elContainer:null,_elCancelBtn:null,_elSaveBtn:null,_initConfigs:function(K){if(K&&YAHOO.lang.isObject(K)){for(var J in K){if(J){this[J]=K[J];}}}},_initEvents:function(){this.createEvent("showEvent");this.createEvent("keydownEvent");this.createEvent("invalidDataEvent");this.createEvent("revertEvent");this.createEvent("saveEvent");this.createEvent("cancelEvent");this.createEvent("blurEvent");this.createEvent("blockEvent");this.createEvent("unblockEvent");},asyncSubmitter:null,value:null,defaultValue:null,validator:null,resetInvalidData:true,isActive:false,LABEL_SAVE:"Save",LABEL_CANCEL:"Cancel",disableBtns:false,toString:function(){return"CellEditor instance "+this._sId;
+},getId:function(){return this._sId;},getDataTable:function(){return this._oDataTable;},getColumn:function(){return this._oColumn;},getRecord:function(){return this._oRecord;},getTdEl:function(){return this._elTd;},getContainerEl:function(){return this._elContainer;},destroy:function(){this.unsubscribeAll();var K=this.getColumn();if(K){K.editor=null;}var J=this.getContainerEl();I.purgeElement(J,true);J.parentNode.removeChild(J);},render:function(){if(this._elContainer){YAHOO.util.Event.purgeElement(this._elContainer,true);this._elContainer.innerHTML="";}var J=document.createElement("div");J.id=this.getId()+"-container";J.style.display="none";J.tabIndex=0;J.className=H.CLASS_EDITOR;document.body.insertBefore(J,document.body.firstChild);this._elContainer=J;I.addListener(J,"keydown",function(M,K){if((M.keyCode==27)){var L=I.getTarget(M);if(L.nodeName&&L.nodeName.toLowerCase()==="select"){L.blur();}K.cancel();}K.fireEvent("keydownEvent",{editor:this,event:M});},this);this.renderForm();if(!this.disableBtns){this.renderBtns();}this.doAfterRender();},renderBtns:function(){var L=this.getContainerEl().appendChild(document.createElement("div"));L.className=H.CLASS_BUTTON;var K=L.appendChild(document.createElement("button"));K.className=H.CLASS_DEFAULT;K.innerHTML=this.LABEL_SAVE;I.addListener(K,"click",function(M){this.save();},this,true);this._elSaveBtn=K;var J=L.appendChild(document.createElement("button"));J.innerHTML=this.LABEL_CANCEL;I.addListener(J,"click",function(M){this.cancel();},this,true);this._elCancelBtn=J;},attach:function(N,L){if(N instanceof YAHOO.widget.DataTable){this._oDataTable=N;L=N.getTdEl(L);if(L){this._elTd=L;var M=N.getColumn(L);if(M){this._oColumn=M;var J=N.getRecord(L);if(J){this._oRecord=J;var K=J.getData(this.getColumn().getField());this.value=(K!==undefined)?K:this.defaultValue;return true;}}}}return false;},move:function(){var M=this.getContainerEl(),L=this.getTdEl(),J=D.getX(L),N=D.getY(L);if(isNaN(J)||isNaN(N)){var K=this.getDataTable().getTbodyEl();J=L.offsetLeft+D.getX(K.parentNode)-K.scrollLeft;N=L.offsetTop+D.getY(K.parentNode)-K.scrollTop+this.getDataTable().getTheadEl().offsetHeight;}M.style.left=J+"px";M.style.top=N+"px";},show:function(){this.resetForm();this.isActive=true;this.getContainerEl().style.display="";this.focus();this.fireEvent("showEvent",{editor:this});},block:function(){this.fireEvent("blockEvent",{editor:this});},unblock:function(){this.fireEvent("unblockEvent",{editor:this});},save:function(){var K=this.getInputValue();var L=K;if(this.validator){L=this.validator.call(this.getDataTable(),K,this.value,this);if(L===undefined){if(this.resetInvalidData){this.resetForm();}this.fireEvent("invalidDataEvent",{editor:this,oldData:this.value,newData:K});return;}}var M=this;var J=function(O,N){var P=M.value;if(O){M.value=N;M.getDataTable().updateCell(M.getRecord(),M.getColumn(),N);M.getContainerEl().style.display="none";M.isActive=false;M.getDataTable()._oCellEditor=null;M.fireEvent("saveEvent",{editor:M,oldData:P,newData:M.value});}else{M.resetForm();M.fireEvent("revertEvent",{editor:M,oldData:P,newData:N});}M.unblock();};this.block();if(C.isFunction(this.asyncSubmitter)){this.asyncSubmitter.call(this,J,L);}else{J(true,L);}},cancel:function(){if(this.isActive){this.getContainerEl().style.display="none";this.isActive=false;this.getDataTable()._oCellEditor=null;this.fireEvent("cancelEvent",{editor:this});}else{}},renderForm:function(){},doAfterRender:function(){},handleDisabledBtns:function(){},resetForm:function(){},focus:function(){},getInputValue:function(){}};C.augmentProto(A,F.EventProvider);E.CheckboxCellEditor=function(J){this._sId="yui-checkboxceditor"+YAHOO.widget.BaseCellEditor._nCount++;E.CheckboxCellEditor.superclass.constructor.call(this,"checkbox",J);};C.extend(E.CheckboxCellEditor,A,{checkboxOptions:null,checkboxes:null,value:null,renderForm:function(){if(C.isArray(this.checkboxOptions)){var M,N,P,K,L,J;for(L=0,J=this.checkboxOptions.length;L<J;L++){M=this.checkboxOptions[L];N=C.isValue(M.value)?M.value:M;P=this.getId()+"-chk"+L;this.getContainerEl().innerHTML+='<input type="checkbox"'+' id="'+P+'"'+' value="'+N+'" />';K=this.getContainerEl().appendChild(document.createElement("label"));K.htmlFor=P;K.innerHTML=C.isValue(M.label)?M.label:M;}var O=[];for(L=0;L<J;L++){O[O.length]=this.getContainerEl().childNodes[L*2];}this.checkboxes=O;if(this.disableBtns){this.handleDisabledBtns();}}else{}},handleDisabledBtns:function(){I.addListener(this.getContainerEl(),"click",function(J){if(I.getTarget(J).tagName.toLowerCase()==="input"){this.save();}},this,true);},resetForm:function(){var N=C.isArray(this.value)?this.value:[this.value];for(var M=0,L=this.checkboxes.length;M<L;M++){this.checkboxes[M].checked=false;for(var K=0,J=N.length;K<J;K++){if(this.checkboxes[M].value===N[K]){this.checkboxes[M].checked=true;}}}},focus:function(){this.checkboxes[0].focus();},getInputValue:function(){var J=[];for(var L=0,K=this.checkboxes.length;L<K;L++){if(this.checkboxes[L].checked){J[J.length]=this.checkboxes[L].value;}}return J;}});C.augmentObject(E.CheckboxCellEditor,A);E.DateCellEditor=function(J){this._sId="yui-dateceditor"+YAHOO.widget.BaseCellEditor._nCount++;E.DateCellEditor.superclass.constructor.call(this,"date",J);};C.extend(E.DateCellEditor,A,{calendar:null,calendarOptions:null,defaultValue:new Date(),renderForm:function(){if(YAHOO.widget.Calendar){var K=this.getContainerEl().appendChild(document.createElement("div"));K.id=this.getId()+"-dateContainer";var L=new YAHOO.widget.Calendar(this.getId()+"-date",K.id,this.calendarOptions);L.render();K.style.cssFloat="none";if(B.ie){var J=this.getContainerEl().appendChild(document.createElement("div"));J.style.clear="both";}this.calendar=L;if(this.disableBtns){this.handleDisabledBtns();}}else{}},handleDisabledBtns:function(){this.calendar.selectEvent.subscribe(function(J){this.save();},this,true);},resetForm:function(){var K=this.value;var J=(K.getMonth()+1)+"/"+K.getDate()+"/"+K.getFullYear();this.calendar.cfg.setProperty("selected",J,false);
+this.calendar.render();},focus:function(){},getInputValue:function(){return this.calendar.getSelectedDates()[0];}});C.augmentObject(E.DateCellEditor,A);E.DropdownCellEditor=function(J){this._sId="yui-dropdownceditor"+YAHOO.widget.BaseCellEditor._nCount++;E.DropdownCellEditor.superclass.constructor.call(this,"dropdown",J);};C.extend(E.DropdownCellEditor,A,{dropdownOptions:null,dropdown:null,multiple:false,size:null,renderForm:function(){var M=this.getContainerEl().appendChild(document.createElement("select"));M.style.zoom=1;if(this.multiple){M.multiple="multiple";}if(C.isNumber(this.size)){M.size=this.size;}this.dropdown=M;if(C.isArray(this.dropdownOptions)){var N,L;for(var K=0,J=this.dropdownOptions.length;K<J;K++){N=this.dropdownOptions[K];L=document.createElement("option");L.value=(C.isValue(N.value))?N.value:N;L.innerHTML=(C.isValue(N.label))?N.label:N;L=M.appendChild(L);}if(this.disableBtns){this.handleDisabledBtns();}}},handleDisabledBtns:function(){if(this.multiple){I.addListener(this.dropdown,"blur",function(J){this.save();},this,true);}else{I.addListener(this.dropdown,"change",function(J){this.save();},this,true);}},resetForm:function(){var P=this.dropdown.options,M=0,L=P.length;if(C.isArray(this.value)){var K=this.value,J=0,O=K.length,N={};for(;M<L;M++){P[M].selected=false;N[P[M].value]=P[M];}for(;J<O;J++){if(N[K[J]]){N[K[J]].selected=true;}}}else{for(;M<L;M++){if(this.value===P[M].value){P[M].selected=true;}}}},focus:function(){this.getDataTable()._focusEl(this.dropdown);},getInputValue:function(){var M=this.dropdown.options;if(this.multiple){var J=[],L=0,K=M.length;for(;L<K;L++){if(M[L].selected){J.push(M[L].value);}}return J;}else{return M[M.selectedIndex].value;}}});C.augmentObject(E.DropdownCellEditor,A);E.RadioCellEditor=function(J){this._sId="yui-radioceditor"+YAHOO.widget.BaseCellEditor._nCount++;E.RadioCellEditor.superclass.constructor.call(this,"radio",J);};C.extend(E.RadioCellEditor,A,{radios:null,radioOptions:null,renderForm:function(){if(C.isArray(this.radioOptions)){var J,K,Q,N;for(var M=0,O=this.radioOptions.length;M<O;M++){J=this.radioOptions[M];K=C.isValue(J.value)?J.value:J;Q=this.getId()+"-radio"+M;this.getContainerEl().innerHTML+='<input type="radio"'+' name="'+this.getId()+'"'+' value="'+K+'"'+' id="'+Q+'" />';N=this.getContainerEl().appendChild(document.createElement("label"));N.htmlFor=Q;N.innerHTML=(C.isValue(J.label))?J.label:J;}var P=[],R;for(var L=0;L<O;L++){R=this.getContainerEl().childNodes[L*2];P[P.length]=R;}this.radios=P;if(this.disableBtns){this.handleDisabledBtns();}}else{}},handleDisabledBtns:function(){I.addListener(this.getContainerEl(),"click",function(J){if(I.getTarget(J).tagName.toLowerCase()==="input"){this.save();}},this,true);},resetForm:function(){for(var L=0,K=this.radios.length;L<K;L++){var J=this.radios[L];if(this.value===J.value){J.checked=true;return;}}},focus:function(){for(var K=0,J=this.radios.length;K<J;K++){if(this.radios[K].checked){this.radios[K].focus();return;}}},getInputValue:function(){for(var K=0,J=this.radios.length;K<J;K++){if(this.radios[K].checked){return this.radios[K].value;}}}});C.augmentObject(E.RadioCellEditor,A);E.TextareaCellEditor=function(J){this._sId="yui-textareaceditor"+YAHOO.widget.BaseCellEditor._nCount++;E.TextareaCellEditor.superclass.constructor.call(this,"textarea",J);};C.extend(E.TextareaCellEditor,A,{textarea:null,renderForm:function(){var J=this.getContainerEl().appendChild(document.createElement("textarea"));this.textarea=J;if(this.disableBtns){this.handleDisabledBtns();}},handleDisabledBtns:function(){I.addListener(this.textarea,"blur",function(J){this.save();},this,true);},move:function(){this.textarea.style.width=this.getTdEl().offsetWidth+"px";this.textarea.style.height="3em";YAHOO.widget.TextareaCellEditor.superclass.move.call(this);},resetForm:function(){this.textarea.value=this.value;},focus:function(){this.getDataTable()._focusEl(this.textarea);this.textarea.select();},getInputValue:function(){return this.textarea.value;}});C.augmentObject(E.TextareaCellEditor,A);E.TextboxCellEditor=function(J){this._sId="yui-textboxceditor"+YAHOO.widget.BaseCellEditor._nCount++;E.TextboxCellEditor.superclass.constructor.call(this,"textbox",J);};C.extend(E.TextboxCellEditor,A,{textbox:null,renderForm:function(){var J;if(B.webkit>420){J=this.getContainerEl().appendChild(document.createElement("form")).appendChild(document.createElement("input"));}else{J=this.getContainerEl().appendChild(document.createElement("input"));}J.type="text";this.textbox=J;I.addListener(J,"keypress",function(K){if((K.keyCode===13)){YAHOO.util.Event.preventDefault(K);this.save();}},this,true);if(this.disableBtns){this.handleDisabledBtns();}},move:function(){this.textbox.style.width=this.getTdEl().offsetWidth+"px";E.TextboxCellEditor.superclass.move.call(this);},resetForm:function(){this.textbox.value=C.isValue(this.value)?this.value.toString():"";},focus:function(){this.getDataTable()._focusEl(this.textbox);this.textbox.select();},getInputValue:function(){return this.textbox.value;}});C.augmentObject(E.TextboxCellEditor,A);H.Editors={checkbox:E.CheckboxCellEditor,"date":E.DateCellEditor,dropdown:E.DropdownCellEditor,radio:E.RadioCellEditor,textarea:E.TextareaCellEditor,textbox:E.TextboxCellEditor};E.CellEditor=function(K,J){if(K&&H.Editors[K]){C.augmentObject(A,H.Editors[K]);return new H.Editors[K](J);}else{return new A(null,J);}};var G=E.CellEditor;C.augmentObject(G,A);})();YAHOO.register("datatable",YAHOO.widget.DataTable,{version:"2.8.2r1",build:"7"}); \ No newline at end of file
diff --git a/js/yui/datemath/datemath-min.js b/js/yui/datemath/datemath-min.js
new file mode 100644
index 000000000..e0240c604
--- /dev/null
+++ b/js/yui/datemath/datemath-min.js
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+YAHOO.widget.DateMath={DAY:"D",WEEK:"W",YEAR:"Y",MONTH:"M",ONE_DAY_MS:1000*60*60*24,WEEK_ONE_JAN_DATE:1,add:function(A,D,C){var F=new Date(A.getTime());switch(D){case this.MONTH:var E=A.getMonth()+C;var B=0;if(E<0){while(E<0){E+=12;B-=1;}}else{if(E>11){while(E>11){E-=12;B+=1;}}}F.setMonth(E);F.setFullYear(A.getFullYear()+B);break;case this.DAY:this._addDays(F,C);break;case this.YEAR:F.setFullYear(A.getFullYear()+C);break;case this.WEEK:this._addDays(F,(C*7));break;}return F;},_addDays:function(D,C){if(YAHOO.env.ua.webkit&&YAHOO.env.ua.webkit<420){if(C<0){for(var B=-128;C<B;C-=B){D.setDate(D.getDate()+B);}}else{for(var A=96;C>A;C-=A){D.setDate(D.getDate()+A);}}}D.setDate(D.getDate()+C);},subtract:function(A,C,B){return this.add(A,C,(B*-1));},before:function(C,B){var A=B.getTime();if(C.getTime()<A){return true;}else{return false;}},after:function(C,B){var A=B.getTime();if(C.getTime()>A){return true;}else{return false;}},between:function(B,A,C){if(this.after(B,A)&&this.before(B,C)){return true;}else{return false;}},getJan1:function(A){return this.getDate(A,0,1);},getDayOffset:function(B,D){var C=this.getJan1(D);var A=Math.ceil((B.getTime()-C.getTime())/this.ONE_DAY_MS);return A;},getWeekNumber:function(D,B,G){B=B||0;G=G||this.WEEK_ONE_JAN_DATE;var H=this.clearTime(D),L,M;if(H.getDay()===B){L=H;}else{L=this.getFirstDayOfWeek(H,B);}var I=L.getFullYear();M=new Date(L.getTime()+6*this.ONE_DAY_MS);var F;if(I!==M.getFullYear()&&M.getDate()>=G){F=1;}else{var E=this.clearTime(this.getDate(I,0,G)),A=this.getFirstDayOfWeek(E,B);var J=Math.round((H.getTime()-A.getTime())/this.ONE_DAY_MS);var K=J%7;var C=(J-K)/7;F=C+1;}return F;},getFirstDayOfWeek:function(D,A){A=A||0;var B=D.getDay(),C=(B-A+7)%7;return this.subtract(D,this.DAY,C);},isYearOverlapWeek:function(A){var C=false;var B=this.add(A,this.DAY,6);if(B.getFullYear()!=A.getFullYear()){C=true;}return C;},isMonthOverlapWeek:function(A){var C=false;var B=this.add(A,this.DAY,6);if(B.getMonth()!=A.getMonth()){C=true;}return C;},findMonthStart:function(A){var B=this.getDate(A.getFullYear(),A.getMonth(),1);return B;},findMonthEnd:function(B){var D=this.findMonthStart(B);var C=this.add(D,this.MONTH,1);var A=this.subtract(C,this.DAY,1);return A;},clearTime:function(A){A.setHours(12,0,0,0);return A;},getDate:function(D,A,C){var B=null;if(YAHOO.lang.isUndefined(C)){C=1;}if(D>=100){B=new Date(D,A,C);}else{B=new Date();B.setFullYear(D);B.setMonth(A);B.setDate(C);B.setHours(0,0,0,0);}return B;}};YAHOO.register("datemath",YAHOO.widget.DateMath,{version:"2.8.2r1",build:"7"}); \ No newline at end of file
diff --git a/js/yui/dom/dom-min.js b/js/yui/dom/dom-min.js
new file mode 100644
index 000000000..6abc47b57
--- /dev/null
+++ b/js/yui/dom/dom-min.js
@@ -0,0 +1,9 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+(function(){YAHOO.env._id_counter=YAHOO.env._id_counter||0;var E=YAHOO.util,L=YAHOO.lang,m=YAHOO.env.ua,A=YAHOO.lang.trim,d={},h={},N=/^t(?:able|d|h)$/i,X=/color$/i,K=window.document,W=K.documentElement,e="ownerDocument",n="defaultView",v="documentElement",t="compatMode",b="offsetLeft",P="offsetTop",u="offsetParent",Z="parentNode",l="nodeType",C="tagName",O="scrollLeft",i="scrollTop",Q="getBoundingClientRect",w="getComputedStyle",a="currentStyle",M="CSS1Compat",c="BackCompat",g="class",F="className",J="",B=" ",s="(?:^|\\s)",k="(?= |$)",U="g",p="position",f="fixed",V="relative",j="left",o="top",r="medium",q="borderLeftWidth",R="borderTopWidth",D=m.opera,I=m.webkit,H=m.gecko,T=m.ie;E.Dom={CUSTOM_ATTRIBUTES:(!W.hasAttribute)?{"for":"htmlFor","class":F}:{"htmlFor":"for","className":g},DOT_ATTRIBUTES:{},get:function(z){var AB,x,AA,y,Y,G;if(z){if(z[l]||z.item){return z;}if(typeof z==="string"){AB=z;z=K.getElementById(z);G=(z)?z.attributes:null;if(z&&G&&G.id&&G.id.value===AB){return z;}else{if(z&&K.all){z=null;x=K.all[AB];for(y=0,Y=x.length;y<Y;++y){if(x[y].id===AB){return x[y];}}}}return z;}if(YAHOO.util.Element&&z instanceof YAHOO.util.Element){z=z.get("element");}if("length" in z){AA=[];for(y=0,Y=z.length;y<Y;++y){AA[AA.length]=E.Dom.get(z[y]);}return AA;}return z;}return null;},getComputedStyle:function(G,Y){if(window[w]){return G[e][n][w](G,null)[Y];}else{if(G[a]){return E.Dom.IE_ComputedStyle.get(G,Y);}}},getStyle:function(G,Y){return E.Dom.batch(G,E.Dom._getStyle,Y);},_getStyle:function(){if(window[w]){return function(G,y){y=(y==="float")?y="cssFloat":E.Dom._toCamel(y);var x=G.style[y],Y;if(!x){Y=G[e][n][w](G,null);if(Y){x=Y[y];}}return x;};}else{if(W[a]){return function(G,y){var x;switch(y){case"opacity":x=100;try{x=G.filters["DXImageTransform.Microsoft.Alpha"].opacity;}catch(z){try{x=G.filters("alpha").opacity;}catch(Y){}}return x/100;case"float":y="styleFloat";default:y=E.Dom._toCamel(y);x=G[a]?G[a][y]:null;return(G.style[y]||x);}};}}}(),setStyle:function(G,Y,x){E.Dom.batch(G,E.Dom._setStyle,{prop:Y,val:x});},_setStyle:function(){if(T){return function(Y,G){var x=E.Dom._toCamel(G.prop),y=G.val;if(Y){switch(x){case"opacity":if(L.isString(Y.style.filter)){Y.style.filter="alpha(opacity="+y*100+")";if(!Y[a]||!Y[a].hasLayout){Y.style.zoom=1;}}break;case"float":x="styleFloat";default:Y.style[x]=y;}}else{}};}else{return function(Y,G){var x=E.Dom._toCamel(G.prop),y=G.val;if(Y){if(x=="float"){x="cssFloat";}Y.style[x]=y;}else{}};}}(),getXY:function(G){return E.Dom.batch(G,E.Dom._getXY);},_canPosition:function(G){return(E.Dom._getStyle(G,"display")!=="none"&&E.Dom._inDoc(G));},_getXY:function(){if(K[v][Q]){return function(y){var z,Y,AA,AF,AE,AD,AC,G,x,AB=Math.floor,AG=false;if(E.Dom._canPosition(y)){AA=y[Q]();AF=y[e];z=E.Dom.getDocumentScrollLeft(AF);Y=E.Dom.getDocumentScrollTop(AF);AG=[AB(AA[j]),AB(AA[o])];if(T&&m.ie<8){AE=2;AD=2;AC=AF[t];if(m.ie===6){if(AC!==c){AE=0;AD=0;}}if((AC===c)){G=S(AF[v],q);x=S(AF[v],R);if(G!==r){AE=parseInt(G,10);}if(x!==r){AD=parseInt(x,10);}}AG[0]-=AE;AG[1]-=AD;}if((Y||z)){AG[0]+=z;AG[1]+=Y;}AG[0]=AB(AG[0]);AG[1]=AB(AG[1]);}else{}return AG;};}else{return function(y){var x,Y,AA,AB,AC,z=false,G=y;if(E.Dom._canPosition(y)){z=[y[b],y[P]];x=E.Dom.getDocumentScrollLeft(y[e]);Y=E.Dom.getDocumentScrollTop(y[e]);AC=((H||m.webkit>519)?true:false);while((G=G[u])){z[0]+=G[b];z[1]+=G[P];if(AC){z=E.Dom._calcBorders(G,z);}}if(E.Dom._getStyle(y,p)!==f){G=y;while((G=G[Z])&&G[C]){AA=G[i];AB=G[O];if(H&&(E.Dom._getStyle(G,"overflow")!=="visible")){z=E.Dom._calcBorders(G,z);}if(AA||AB){z[0]-=AB;z[1]-=AA;}}z[0]+=x;z[1]+=Y;}else{if(D){z[0]-=x;z[1]-=Y;}else{if(I||H){z[0]+=x;z[1]+=Y;}}}z[0]=Math.floor(z[0]);z[1]=Math.floor(z[1]);}else{}return z;};}}(),getX:function(G){var Y=function(x){return E.Dom.getXY(x)[0];};return E.Dom.batch(G,Y,E.Dom,true);},getY:function(G){var Y=function(x){return E.Dom.getXY(x)[1];};return E.Dom.batch(G,Y,E.Dom,true);},setXY:function(G,x,Y){E.Dom.batch(G,E.Dom._setXY,{pos:x,noRetry:Y});},_setXY:function(G,z){var AA=E.Dom._getStyle(G,p),y=E.Dom.setStyle,AD=z.pos,Y=z.noRetry,AB=[parseInt(E.Dom.getComputedStyle(G,j),10),parseInt(E.Dom.getComputedStyle(G,o),10)],AC,x;if(AA=="static"){AA=V;y(G,p,AA);}AC=E.Dom._getXY(G);if(!AD||AC===false){return false;}if(isNaN(AB[0])){AB[0]=(AA==V)?0:G[b];}if(isNaN(AB[1])){AB[1]=(AA==V)?0:G[P];}if(AD[0]!==null){y(G,j,AD[0]-AC[0]+AB[0]+"px");}if(AD[1]!==null){y(G,o,AD[1]-AC[1]+AB[1]+"px");}if(!Y){x=E.Dom._getXY(G);if((AD[0]!==null&&x[0]!=AD[0])||(AD[1]!==null&&x[1]!=AD[1])){E.Dom._setXY(G,{pos:AD,noRetry:true});}}},setX:function(Y,G){E.Dom.setXY(Y,[G,null]);},setY:function(G,Y){E.Dom.setXY(G,[null,Y]);},getRegion:function(G){var Y=function(x){var y=false;if(E.Dom._canPosition(x)){y=E.Region.getRegion(x);}else{}return y;};return E.Dom.batch(G,Y,E.Dom,true);},getClientWidth:function(){return E.Dom.getViewportWidth();},getClientHeight:function(){return E.Dom.getViewportHeight();},getElementsByClassName:function(AB,AF,AC,AE,x,AD){AF=AF||"*";AC=(AC)?E.Dom.get(AC):null||K;if(!AC){return[];}var Y=[],G=AC.getElementsByTagName(AF),z=E.Dom.hasClass;for(var y=0,AA=G.length;y<AA;++y){if(z(G[y],AB)){Y[Y.length]=G[y];}}if(AE){E.Dom.batch(Y,AE,x,AD);}return Y;},hasClass:function(Y,G){return E.Dom.batch(Y,E.Dom._hasClass,G);},_hasClass:function(x,Y){var G=false,y;if(x&&Y){y=E.Dom._getAttribute(x,F)||J;if(Y.exec){G=Y.test(y);}else{G=Y&&(B+y+B).indexOf(B+Y+B)>-1;}}else{}return G;},addClass:function(Y,G){return E.Dom.batch(Y,E.Dom._addClass,G);},_addClass:function(x,Y){var G=false,y;if(x&&Y){y=E.Dom._getAttribute(x,F)||J;if(!E.Dom._hasClass(x,Y)){E.Dom.setAttribute(x,F,A(y+B+Y));G=true;}}else{}return G;},removeClass:function(Y,G){return E.Dom.batch(Y,E.Dom._removeClass,G);},_removeClass:function(y,x){var Y=false,AA,z,G;if(y&&x){AA=E.Dom._getAttribute(y,F)||J;E.Dom.setAttribute(y,F,AA.replace(E.Dom._getClassRegex(x),J));z=E.Dom._getAttribute(y,F);if(AA!==z){E.Dom.setAttribute(y,F,A(z));Y=true;if(E.Dom._getAttribute(y,F)===""){G=(y.hasAttribute&&y.hasAttribute(g))?g:F;
+y.removeAttribute(G);}}}else{}return Y;},replaceClass:function(x,Y,G){return E.Dom.batch(x,E.Dom._replaceClass,{from:Y,to:G});},_replaceClass:function(y,x){var Y,AB,AA,G=false,z;if(y&&x){AB=x.from;AA=x.to;if(!AA){G=false;}else{if(!AB){G=E.Dom._addClass(y,x.to);}else{if(AB!==AA){z=E.Dom._getAttribute(y,F)||J;Y=(B+z.replace(E.Dom._getClassRegex(AB),B+AA)).split(E.Dom._getClassRegex(AA));Y.splice(1,0,B+AA);E.Dom.setAttribute(y,F,A(Y.join(J)));G=true;}}}}else{}return G;},generateId:function(G,x){x=x||"yui-gen";var Y=function(y){if(y&&y.id){return y.id;}var z=x+YAHOO.env._id_counter++;if(y){if(y[e]&&y[e].getElementById(z)){return E.Dom.generateId(y,z+x);}y.id=z;}return z;};return E.Dom.batch(G,Y,E.Dom,true)||Y.apply(E.Dom,arguments);},isAncestor:function(Y,x){Y=E.Dom.get(Y);x=E.Dom.get(x);var G=false;if((Y&&x)&&(Y[l]&&x[l])){if(Y.contains&&Y!==x){G=Y.contains(x);}else{if(Y.compareDocumentPosition){G=!!(Y.compareDocumentPosition(x)&16);}}}else{}return G;},inDocument:function(G,Y){return E.Dom._inDoc(E.Dom.get(G),Y);},_inDoc:function(Y,x){var G=false;if(Y&&Y[C]){x=x||Y[e];G=E.Dom.isAncestor(x[v],Y);}else{}return G;},getElementsBy:function(Y,AF,AB,AD,y,AC,AE){AF=AF||"*";AB=(AB)?E.Dom.get(AB):null||K;if(!AB){return[];}var x=[],G=AB.getElementsByTagName(AF);for(var z=0,AA=G.length;z<AA;++z){if(Y(G[z])){if(AE){x=G[z];break;}else{x[x.length]=G[z];}}}if(AD){E.Dom.batch(x,AD,y,AC);}return x;},getElementBy:function(x,G,Y){return E.Dom.getElementsBy(x,G,Y,null,null,null,true);},batch:function(x,AB,AA,z){var y=[],Y=(z)?AA:window;x=(x&&(x[C]||x.item))?x:E.Dom.get(x);if(x&&AB){if(x[C]||x.length===undefined){return AB.call(Y,x,AA);}for(var G=0;G<x.length;++G){y[y.length]=AB.call(Y,x[G],AA);}}else{return false;}return y;},getDocumentHeight:function(){var Y=(K[t]!=M||I)?K.body.scrollHeight:W.scrollHeight,G=Math.max(Y,E.Dom.getViewportHeight());return G;},getDocumentWidth:function(){var Y=(K[t]!=M||I)?K.body.scrollWidth:W.scrollWidth,G=Math.max(Y,E.Dom.getViewportWidth());return G;},getViewportHeight:function(){var G=self.innerHeight,Y=K[t];if((Y||T)&&!D){G=(Y==M)?W.clientHeight:K.body.clientHeight;}return G;},getViewportWidth:function(){var G=self.innerWidth,Y=K[t];if(Y||T){G=(Y==M)?W.clientWidth:K.body.clientWidth;}return G;},getAncestorBy:function(G,Y){while((G=G[Z])){if(E.Dom._testElement(G,Y)){return G;}}return null;},getAncestorByClassName:function(Y,G){Y=E.Dom.get(Y);if(!Y){return null;}var x=function(y){return E.Dom.hasClass(y,G);};return E.Dom.getAncestorBy(Y,x);},getAncestorByTagName:function(Y,G){Y=E.Dom.get(Y);if(!Y){return null;}var x=function(y){return y[C]&&y[C].toUpperCase()==G.toUpperCase();};return E.Dom.getAncestorBy(Y,x);},getPreviousSiblingBy:function(G,Y){while(G){G=G.previousSibling;if(E.Dom._testElement(G,Y)){return G;}}return null;},getPreviousSibling:function(G){G=E.Dom.get(G);if(!G){return null;}return E.Dom.getPreviousSiblingBy(G);},getNextSiblingBy:function(G,Y){while(G){G=G.nextSibling;if(E.Dom._testElement(G,Y)){return G;}}return null;},getNextSibling:function(G){G=E.Dom.get(G);if(!G){return null;}return E.Dom.getNextSiblingBy(G);},getFirstChildBy:function(G,x){var Y=(E.Dom._testElement(G.firstChild,x))?G.firstChild:null;return Y||E.Dom.getNextSiblingBy(G.firstChild,x);},getFirstChild:function(G,Y){G=E.Dom.get(G);if(!G){return null;}return E.Dom.getFirstChildBy(G);},getLastChildBy:function(G,x){if(!G){return null;}var Y=(E.Dom._testElement(G.lastChild,x))?G.lastChild:null;return Y||E.Dom.getPreviousSiblingBy(G.lastChild,x);},getLastChild:function(G){G=E.Dom.get(G);return E.Dom.getLastChildBy(G);},getChildrenBy:function(Y,y){var x=E.Dom.getFirstChildBy(Y,y),G=x?[x]:[];E.Dom.getNextSiblingBy(x,function(z){if(!y||y(z)){G[G.length]=z;}return false;});return G;},getChildren:function(G){G=E.Dom.get(G);if(!G){}return E.Dom.getChildrenBy(G);},getDocumentScrollLeft:function(G){G=G||K;return Math.max(G[v].scrollLeft,G.body.scrollLeft);},getDocumentScrollTop:function(G){G=G||K;return Math.max(G[v].scrollTop,G.body.scrollTop);},insertBefore:function(Y,G){Y=E.Dom.get(Y);G=E.Dom.get(G);if(!Y||!G||!G[Z]){return null;}return G[Z].insertBefore(Y,G);},insertAfter:function(Y,G){Y=E.Dom.get(Y);G=E.Dom.get(G);if(!Y||!G||!G[Z]){return null;}if(G.nextSibling){return G[Z].insertBefore(Y,G.nextSibling);}else{return G[Z].appendChild(Y);}},getClientRegion:function(){var x=E.Dom.getDocumentScrollTop(),Y=E.Dom.getDocumentScrollLeft(),y=E.Dom.getViewportWidth()+Y,G=E.Dom.getViewportHeight()+x;return new E.Region(x,y,G,Y);},setAttribute:function(Y,G,x){E.Dom.batch(Y,E.Dom._setAttribute,{attr:G,val:x});},_setAttribute:function(x,Y){var G=E.Dom._toCamel(Y.attr),y=Y.val;if(x&&x.setAttribute){if(E.Dom.DOT_ATTRIBUTES[G]){x[G]=y;}else{G=E.Dom.CUSTOM_ATTRIBUTES[G]||G;x.setAttribute(G,y);}}else{}},getAttribute:function(Y,G){return E.Dom.batch(Y,E.Dom._getAttribute,G);},_getAttribute:function(Y,G){var x;G=E.Dom.CUSTOM_ATTRIBUTES[G]||G;if(Y&&Y.getAttribute){x=Y.getAttribute(G,2);}else{}return x;},_toCamel:function(Y){var x=d;function G(y,z){return z.toUpperCase();}return x[Y]||(x[Y]=Y.indexOf("-")===-1?Y:Y.replace(/-([a-z])/gi,G));},_getClassRegex:function(Y){var G;if(Y!==undefined){if(Y.exec){G=Y;}else{G=h[Y];if(!G){Y=Y.replace(E.Dom._patterns.CLASS_RE_TOKENS,"\\$1");G=h[Y]=new RegExp(s+Y+k,U);}}}return G;},_patterns:{ROOT_TAG:/^body|html$/i,CLASS_RE_TOKENS:/([\.\(\)\^\$\*\+\?\|\[\]\{\}\\])/g},_testElement:function(G,Y){return G&&G[l]==1&&(!Y||Y(G));},_calcBorders:function(x,y){var Y=parseInt(E.Dom[w](x,R),10)||0,G=parseInt(E.Dom[w](x,q),10)||0;if(H){if(N.test(x[C])){Y=0;G=0;}}y[0]+=G;y[1]+=Y;return y;}};var S=E.Dom[w];if(m.opera){E.Dom[w]=function(Y,G){var x=S(Y,G);if(X.test(G)){x=E.Dom.Color.toRGB(x);}return x;};}if(m.webkit){E.Dom[w]=function(Y,G){var x=S(Y,G);if(x==="rgba(0, 0, 0, 0)"){x="transparent";}return x;};}if(m.ie&&m.ie>=8&&K.documentElement.hasAttribute){E.Dom.DOT_ATTRIBUTES.type=true;}})();YAHOO.util.Region=function(C,D,A,B){this.top=C;this.y=C;this[1]=C;this.right=D;this.bottom=A;this.left=B;this.x=B;this[0]=B;
+this.width=this.right-this.left;this.height=this.bottom-this.top;};YAHOO.util.Region.prototype.contains=function(A){return(A.left>=this.left&&A.right<=this.right&&A.top>=this.top&&A.bottom<=this.bottom);};YAHOO.util.Region.prototype.getArea=function(){return((this.bottom-this.top)*(this.right-this.left));};YAHOO.util.Region.prototype.intersect=function(E){var C=Math.max(this.top,E.top),D=Math.min(this.right,E.right),A=Math.min(this.bottom,E.bottom),B=Math.max(this.left,E.left);if(A>=C&&D>=B){return new YAHOO.util.Region(C,D,A,B);}else{return null;}};YAHOO.util.Region.prototype.union=function(E){var C=Math.min(this.top,E.top),D=Math.max(this.right,E.right),A=Math.max(this.bottom,E.bottom),B=Math.min(this.left,E.left);return new YAHOO.util.Region(C,D,A,B);};YAHOO.util.Region.prototype.toString=function(){return("Region {"+"top: "+this.top+", right: "+this.right+", bottom: "+this.bottom+", left: "+this.left+", height: "+this.height+", width: "+this.width+"}");};YAHOO.util.Region.getRegion=function(D){var F=YAHOO.util.Dom.getXY(D),C=F[1],E=F[0]+D.offsetWidth,A=F[1]+D.offsetHeight,B=F[0];return new YAHOO.util.Region(C,E,A,B);};YAHOO.util.Point=function(A,B){if(YAHOO.lang.isArray(A)){B=A[1];A=A[0];}YAHOO.util.Point.superclass.constructor.call(this,B,A,B,A);};YAHOO.extend(YAHOO.util.Point,YAHOO.util.Region);(function(){var B=YAHOO.util,A="clientTop",F="clientLeft",J="parentNode",K="right",W="hasLayout",I="px",U="opacity",L="auto",D="borderLeftWidth",G="borderTopWidth",P="borderRightWidth",V="borderBottomWidth",S="visible",Q="transparent",N="height",E="width",H="style",T="currentStyle",R=/^width|height$/,O=/^(\d[.\d]*)+(em|ex|px|gd|rem|vw|vh|vm|ch|mm|cm|in|pt|pc|deg|rad|ms|s|hz|khz|%){1}?/i,M={get:function(X,Z){var Y="",a=X[T][Z];if(Z===U){Y=B.Dom.getStyle(X,U);}else{if(!a||(a.indexOf&&a.indexOf(I)>-1)){Y=a;}else{if(B.Dom.IE_COMPUTED[Z]){Y=B.Dom.IE_COMPUTED[Z](X,Z);}else{if(O.test(a)){Y=B.Dom.IE.ComputedStyle.getPixel(X,Z);}else{Y=a;}}}}return Y;},getOffset:function(Z,e){var b=Z[T][e],X=e.charAt(0).toUpperCase()+e.substr(1),c="offset"+X,Y="pixel"+X,a="",d;if(b==L){d=Z[c];if(d===undefined){a=0;}a=d;if(R.test(e)){Z[H][e]=d;if(Z[c]>d){a=d-(Z[c]-d);}Z[H][e]=L;}}else{if(!Z[H][Y]&&!Z[H][e]){Z[H][e]=b;}a=Z[H][Y];}return a+I;},getBorderWidth:function(X,Z){var Y=null;if(!X[T][W]){X[H].zoom=1;}switch(Z){case G:Y=X[A];break;case V:Y=X.offsetHeight-X.clientHeight-X[A];break;case D:Y=X[F];break;case P:Y=X.offsetWidth-X.clientWidth-X[F];break;}return Y+I;},getPixel:function(Y,X){var a=null,b=Y[T][K],Z=Y[T][X];Y[H][K]=Z;a=Y[H].pixelRight;Y[H][K]=b;return a+I;},getMargin:function(Y,X){var Z;if(Y[T][X]==L){Z=0+I;}else{Z=B.Dom.IE.ComputedStyle.getPixel(Y,X);}return Z;},getVisibility:function(Y,X){var Z;while((Z=Y[T])&&Z[X]=="inherit"){Y=Y[J];}return(Z)?Z[X]:S;},getColor:function(Y,X){return B.Dom.Color.toRGB(Y[T][X])||Q;},getBorderColor:function(Y,X){var Z=Y[T],a=Z[X]||Z.color;return B.Dom.Color.toRGB(B.Dom.Color.toHex(a));}},C={};C.top=C.right=C.bottom=C.left=C[E]=C[N]=M.getOffset;C.color=M.getColor;C[G]=C[P]=C[V]=C[D]=M.getBorderWidth;C.marginTop=C.marginRight=C.marginBottom=C.marginLeft=M.getMargin;C.visibility=M.getVisibility;C.borderColor=C.borderTopColor=C.borderRightColor=C.borderBottomColor=C.borderLeftColor=M.getBorderColor;B.Dom.IE_COMPUTED=C;B.Dom.IE_ComputedStyle=M;})();(function(){var C="toString",A=parseInt,B=RegExp,D=YAHOO.util;D.Dom.Color={KEYWORDS:{black:"000",silver:"c0c0c0",gray:"808080",white:"fff",maroon:"800000",red:"f00",purple:"800080",fuchsia:"f0f",green:"008000",lime:"0f0",olive:"808000",yellow:"ff0",navy:"000080",blue:"00f",teal:"008080",aqua:"0ff"},re_RGB:/^rgb\(([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\)$/i,re_hex:/^#?([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/i,re_hex3:/([0-9A-F])/gi,toRGB:function(E){if(!D.Dom.Color.re_RGB.test(E)){E=D.Dom.Color.toHex(E);}if(D.Dom.Color.re_hex.exec(E)){E="rgb("+[A(B.$1,16),A(B.$2,16),A(B.$3,16)].join(", ")+")";}return E;},toHex:function(H){H=D.Dom.Color.KEYWORDS[H]||H;if(D.Dom.Color.re_RGB.exec(H)){var G=(B.$1.length===1)?"0"+B.$1:Number(B.$1),F=(B.$2.length===1)?"0"+B.$2:Number(B.$2),E=(B.$3.length===1)?"0"+B.$3:Number(B.$3);H=[G[C](16),F[C](16),E[C](16)].join("");}if(H.length<6){H=H.replace(D.Dom.Color.re_hex3,"$1$1");}if(H!=="transparent"&&H.indexOf("#")<0){H="#"+H;}return H.toLowerCase();}};}());YAHOO.register("dom",YAHOO.util.Dom,{version:"2.8.2r1",build:"7"}); \ No newline at end of file
diff --git a/js/yui/dragdrop/dragdrop-min.js b/js/yui/dragdrop/dragdrop-min.js
new file mode 100644
index 000000000..3f86f1184
--- /dev/null
+++ b/js/yui/dragdrop/dragdrop-min.js
@@ -0,0 +1,10 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+if(!YAHOO.util.DragDropMgr){YAHOO.util.DragDropMgr=function(){var A=YAHOO.util.Event,B=YAHOO.util.Dom;return{useShim:false,_shimActive:false,_shimState:false,_debugShim:false,_createShim:function(){var C=document.createElement("div");C.id="yui-ddm-shim";if(document.body.firstChild){document.body.insertBefore(C,document.body.firstChild);}else{document.body.appendChild(C);}C.style.display="none";C.style.backgroundColor="red";C.style.position="absolute";C.style.zIndex="99999";B.setStyle(C,"opacity","0");this._shim=C;A.on(C,"mouseup",this.handleMouseUp,this,true);A.on(C,"mousemove",this.handleMouseMove,this,true);A.on(window,"scroll",this._sizeShim,this,true);},_sizeShim:function(){if(this._shimActive){var C=this._shim;C.style.height=B.getDocumentHeight()+"px";C.style.width=B.getDocumentWidth()+"px";C.style.top="0";C.style.left="0";}},_activateShim:function(){if(this.useShim){if(!this._shim){this._createShim();}this._shimActive=true;var C=this._shim,D="0";if(this._debugShim){D=".5";}B.setStyle(C,"opacity",D);this._sizeShim();C.style.display="block";}},_deactivateShim:function(){this._shim.style.display="none";this._shimActive=false;},_shim:null,ids:{},handleIds:{},dragCurrent:null,dragOvers:{},deltaX:0,deltaY:0,preventDefault:true,stopPropagation:true,initialized:false,locked:false,interactionInfo:null,init:function(){this.initialized=true;},POINT:0,INTERSECT:1,STRICT_INTERSECT:2,mode:0,_execOnAll:function(E,D){for(var F in this.ids){for(var C in this.ids[F]){var G=this.ids[F][C];if(!this.isTypeOfDD(G)){continue;}G[E].apply(G,D);}}},_onLoad:function(){this.init();A.on(document,"mouseup",this.handleMouseUp,this,true);A.on(document,"mousemove",this.handleMouseMove,this,true);A.on(window,"unload",this._onUnload,this,true);A.on(window,"resize",this._onResize,this,true);},_onResize:function(C){this._execOnAll("resetConstraints",[]);},lock:function(){this.locked=true;},unlock:function(){this.locked=false;},isLocked:function(){return this.locked;},locationCache:{},useCache:true,clickPixelThresh:3,clickTimeThresh:1000,dragThreshMet:false,clickTimeout:null,startX:0,startY:0,fromTimeout:false,regDragDrop:function(D,C){if(!this.initialized){this.init();}if(!this.ids[C]){this.ids[C]={};}this.ids[C][D.id]=D;},removeDDFromGroup:function(E,C){if(!this.ids[C]){this.ids[C]={};}var D=this.ids[C];if(D&&D[E.id]){delete D[E.id];}},_remove:function(E){for(var D in E.groups){if(D){var C=this.ids[D];if(C&&C[E.id]){delete C[E.id];}}}delete this.handleIds[E.id];},regHandle:function(D,C){if(!this.handleIds[D]){this.handleIds[D]={};}this.handleIds[D][C]=C;},isDragDrop:function(C){return(this.getDDById(C))?true:false;},getRelated:function(H,D){var G=[];for(var F in H.groups){for(var E in this.ids[F]){var C=this.ids[F][E];if(!this.isTypeOfDD(C)){continue;}if(!D||C.isTarget){G[G.length]=C;}}}return G;},isLegalTarget:function(G,F){var D=this.getRelated(G,true);for(var E=0,C=D.length;E<C;++E){if(D[E].id==F.id){return true;}}return false;},isTypeOfDD:function(C){return(C&&C.__ygDragDrop);},isHandle:function(D,C){return(this.handleIds[D]&&this.handleIds[D][C]);},getDDById:function(D){for(var C in this.ids){if(this.ids[C][D]){return this.ids[C][D];}}return null;},handleMouseDown:function(E,D){this.currentTarget=YAHOO.util.Event.getTarget(E);this.dragCurrent=D;var C=D.getEl();this.startX=YAHOO.util.Event.getPageX(E);this.startY=YAHOO.util.Event.getPageY(E);this.deltaX=this.startX-C.offsetLeft;this.deltaY=this.startY-C.offsetTop;this.dragThreshMet=false;this.clickTimeout=setTimeout(function(){var F=YAHOO.util.DDM;F.startDrag(F.startX,F.startY);F.fromTimeout=true;},this.clickTimeThresh);},startDrag:function(C,E){if(this.dragCurrent&&this.dragCurrent.useShim){this._shimState=this.useShim;this.useShim=true;}this._activateShim();clearTimeout(this.clickTimeout);var D=this.dragCurrent;if(D&&D.events.b4StartDrag){D.b4StartDrag(C,E);D.fireEvent("b4StartDragEvent",{x:C,y:E});}if(D&&D.events.startDrag){D.startDrag(C,E);D.fireEvent("startDragEvent",{x:C,y:E});}this.dragThreshMet=true;},handleMouseUp:function(C){if(this.dragCurrent){clearTimeout(this.clickTimeout);if(this.dragThreshMet){if(this.fromTimeout){this.fromTimeout=false;this.handleMouseMove(C);}this.fromTimeout=false;this.fireEvents(C,true);}else{}this.stopDrag(C);this.stopEvent(C);}},stopEvent:function(C){if(this.stopPropagation){YAHOO.util.Event.stopPropagation(C);}if(this.preventDefault){YAHOO.util.Event.preventDefault(C);}},stopDrag:function(E,D){var C=this.dragCurrent;if(C&&!D){if(this.dragThreshMet){if(C.events.b4EndDrag){C.b4EndDrag(E);C.fireEvent("b4EndDragEvent",{e:E});}if(C.events.endDrag){C.endDrag(E);C.fireEvent("endDragEvent",{e:E});}}if(C.events.mouseUp){C.onMouseUp(E);C.fireEvent("mouseUpEvent",{e:E});}}if(this._shimActive){this._deactivateShim();if(this.dragCurrent&&this.dragCurrent.useShim){this.useShim=this._shimState;this._shimState=false;}}this.dragCurrent=null;this.dragOvers={};},handleMouseMove:function(F){var C=this.dragCurrent;if(C){if(YAHOO.util.Event.isIE&&!F.button){this.stopEvent(F);return this.handleMouseUp(F);}else{if(F.clientX<0||F.clientY<0){}}if(!this.dragThreshMet){var E=Math.abs(this.startX-YAHOO.util.Event.getPageX(F));var D=Math.abs(this.startY-YAHOO.util.Event.getPageY(F));if(E>this.clickPixelThresh||D>this.clickPixelThresh){this.startDrag(this.startX,this.startY);}}if(this.dragThreshMet){if(C&&C.events.b4Drag){C.b4Drag(F);C.fireEvent("b4DragEvent",{e:F});}if(C&&C.events.drag){C.onDrag(F);C.fireEvent("dragEvent",{e:F});}if(C){this.fireEvents(F,false);}}this.stopEvent(F);}},fireEvents:function(V,L){var a=this.dragCurrent;if(!a||a.isLocked()||a.dragOnly){return;}var N=YAHOO.util.Event.getPageX(V),M=YAHOO.util.Event.getPageY(V),P=new YAHOO.util.Point(N,M),K=a.getTargetCoord(P.x,P.y),F=a.getDragEl(),E=["out","over","drop","enter"],U=new YAHOO.util.Region(K.y,K.x+F.offsetWidth,K.y+F.offsetHeight,K.x),I=[],D={},Q=[],c={outEvts:[],overEvts:[],dropEvts:[],enterEvts:[]};for(var S in this.dragOvers){var d=this.dragOvers[S];if(!this.isTypeOfDD(d)){continue;
+}if(!this.isOverTarget(P,d,this.mode,U)){c.outEvts.push(d);}I[S]=true;delete this.dragOvers[S];}for(var R in a.groups){if("string"!=typeof R){continue;}for(S in this.ids[R]){var G=this.ids[R][S];if(!this.isTypeOfDD(G)){continue;}if(G.isTarget&&!G.isLocked()&&G!=a){if(this.isOverTarget(P,G,this.mode,U)){D[R]=true;if(L){c.dropEvts.push(G);}else{if(!I[G.id]){c.enterEvts.push(G);}else{c.overEvts.push(G);}this.dragOvers[G.id]=G;}}}}}this.interactionInfo={out:c.outEvts,enter:c.enterEvts,over:c.overEvts,drop:c.dropEvts,point:P,draggedRegion:U,sourceRegion:this.locationCache[a.id],validDrop:L};for(var C in D){Q.push(C);}if(L&&!c.dropEvts.length){this.interactionInfo.validDrop=false;if(a.events.invalidDrop){a.onInvalidDrop(V);a.fireEvent("invalidDropEvent",{e:V});}}for(S=0;S<E.length;S++){var Y=null;if(c[E[S]+"Evts"]){Y=c[E[S]+"Evts"];}if(Y&&Y.length){var H=E[S].charAt(0).toUpperCase()+E[S].substr(1),X="onDrag"+H,J="b4Drag"+H,O="drag"+H+"Event",W="drag"+H;if(this.mode){if(a.events[J]){a[J](V,Y,Q);a.fireEvent(J+"Event",{event:V,info:Y,group:Q});}if(a.events[W]){a[X](V,Y,Q);a.fireEvent(O,{event:V,info:Y,group:Q});}}else{for(var Z=0,T=Y.length;Z<T;++Z){if(a.events[J]){a[J](V,Y[Z].id,Q[0]);a.fireEvent(J+"Event",{event:V,info:Y[Z].id,group:Q[0]});}if(a.events[W]){a[X](V,Y[Z].id,Q[0]);a.fireEvent(O,{event:V,info:Y[Z].id,group:Q[0]});}}}}}},getBestMatch:function(E){var G=null;var D=E.length;if(D==1){G=E[0];}else{for(var F=0;F<D;++F){var C=E[F];if(this.mode==this.INTERSECT&&C.cursorIsOver){G=C;break;}else{if(!G||!G.overlap||(C.overlap&&G.overlap.getArea()<C.overlap.getArea())){G=C;}}}}return G;},refreshCache:function(D){var F=D||this.ids;for(var C in F){if("string"!=typeof C){continue;}for(var E in this.ids[C]){var G=this.ids[C][E];if(this.isTypeOfDD(G)){var H=this.getLocation(G);if(H){this.locationCache[G.id]=H;}else{delete this.locationCache[G.id];}}}}},verifyEl:function(D){try{if(D){var C=D.offsetParent;if(C){return true;}}}catch(E){}return false;},getLocation:function(H){if(!this.isTypeOfDD(H)){return null;}var F=H.getEl(),K,E,D,M,L,N,C,J,G;try{K=YAHOO.util.Dom.getXY(F);}catch(I){}if(!K){return null;}E=K[0];D=E+F.offsetWidth;M=K[1];L=M+F.offsetHeight;N=M-H.padding[0];C=D+H.padding[1];J=L+H.padding[2];G=E-H.padding[3];return new YAHOO.util.Region(N,C,J,G);},isOverTarget:function(K,C,E,F){var G=this.locationCache[C.id];if(!G||!this.useCache){G=this.getLocation(C);this.locationCache[C.id]=G;}if(!G){return false;}C.cursorIsOver=G.contains(K);var J=this.dragCurrent;if(!J||(!E&&!J.constrainX&&!J.constrainY)){return C.cursorIsOver;}C.overlap=null;if(!F){var H=J.getTargetCoord(K.x,K.y);var D=J.getDragEl();F=new YAHOO.util.Region(H.y,H.x+D.offsetWidth,H.y+D.offsetHeight,H.x);}var I=F.intersect(G);if(I){C.overlap=I;return(E)?true:C.cursorIsOver;}else{return false;}},_onUnload:function(D,C){this.unregAll();},unregAll:function(){if(this.dragCurrent){this.stopDrag();this.dragCurrent=null;}this._execOnAll("unreg",[]);this.ids={};},elementCache:{},getElWrapper:function(D){var C=this.elementCache[D];if(!C||!C.el){C=this.elementCache[D]=new this.ElementWrapper(YAHOO.util.Dom.get(D));}return C;},getElement:function(C){return YAHOO.util.Dom.get(C);},getCss:function(D){var C=YAHOO.util.Dom.get(D);return(C)?C.style:null;},ElementWrapper:function(C){this.el=C||null;this.id=this.el&&C.id;this.css=this.el&&C.style;},getPosX:function(C){return YAHOO.util.Dom.getX(C);},getPosY:function(C){return YAHOO.util.Dom.getY(C);},swapNode:function(E,C){if(E.swapNode){E.swapNode(C);}else{var F=C.parentNode;var D=C.nextSibling;if(D==E){F.insertBefore(E,C);}else{if(C==E.nextSibling){F.insertBefore(C,E);}else{E.parentNode.replaceChild(C,E);F.insertBefore(E,D);}}}},getScroll:function(){var E,C,F=document.documentElement,D=document.body;if(F&&(F.scrollTop||F.scrollLeft)){E=F.scrollTop;C=F.scrollLeft;}else{if(D){E=D.scrollTop;C=D.scrollLeft;}else{}}return{top:E,left:C};},getStyle:function(D,C){return YAHOO.util.Dom.getStyle(D,C);},getScrollTop:function(){return this.getScroll().top;},getScrollLeft:function(){return this.getScroll().left;},moveToEl:function(C,E){var D=YAHOO.util.Dom.getXY(E);YAHOO.util.Dom.setXY(C,D);},getClientHeight:function(){return YAHOO.util.Dom.getViewportHeight();},getClientWidth:function(){return YAHOO.util.Dom.getViewportWidth();},numericSort:function(D,C){return(D-C);},_timeoutCount:0,_addListeners:function(){var C=YAHOO.util.DDM;if(YAHOO.util.Event&&document){C._onLoad();}else{if(C._timeoutCount>2000){}else{setTimeout(C._addListeners,10);if(document&&document.body){C._timeoutCount+=1;}}}},handleWasClicked:function(C,E){if(this.isHandle(E,C.id)){return true;}else{var D=C.parentNode;while(D){if(this.isHandle(E,D.id)){return true;}else{D=D.parentNode;}}}return false;}};}();YAHOO.util.DDM=YAHOO.util.DragDropMgr;YAHOO.util.DDM._addListeners();}(function(){var A=YAHOO.util.Event;var B=YAHOO.util.Dom;YAHOO.util.DragDrop=function(E,C,D){if(E){this.init(E,C,D);}};YAHOO.util.DragDrop.prototype={events:null,on:function(){this.subscribe.apply(this,arguments);},id:null,config:null,dragElId:null,handleElId:null,invalidHandleTypes:null,invalidHandleIds:null,invalidHandleClasses:null,startPageX:0,startPageY:0,groups:null,locked:false,lock:function(){this.locked=true;},unlock:function(){this.locked=false;},isTarget:true,padding:null,dragOnly:false,useShim:false,_domRef:null,__ygDragDrop:true,constrainX:false,constrainY:false,minX:0,maxX:0,minY:0,maxY:0,deltaX:0,deltaY:0,maintainOffset:false,xTicks:null,yTicks:null,primaryButtonOnly:true,available:false,hasOuterHandles:false,cursorIsOver:false,overlap:null,b4StartDrag:function(C,D){},startDrag:function(C,D){},b4Drag:function(C){},onDrag:function(C){},onDragEnter:function(C,D){},b4DragOver:function(C){},onDragOver:function(C,D){},b4DragOut:function(C){},onDragOut:function(C,D){},b4DragDrop:function(C){},onDragDrop:function(C,D){},onInvalidDrop:function(C){},b4EndDrag:function(C){},endDrag:function(C){},b4MouseDown:function(C){},onMouseDown:function(C){},onMouseUp:function(C){},onAvailable:function(){},getEl:function(){if(!this._domRef){this._domRef=B.get(this.id);
+}return this._domRef;},getDragEl:function(){return B.get(this.dragElId);},init:function(F,C,D){this.initTarget(F,C,D);A.on(this._domRef||this.id,"mousedown",this.handleMouseDown,this,true);for(var E in this.events){this.createEvent(E+"Event");}},initTarget:function(E,C,D){this.config=D||{};this.events={};this.DDM=YAHOO.util.DDM;this.groups={};if(typeof E!=="string"){this._domRef=E;E=B.generateId(E);}this.id=E;this.addToGroup((C)?C:"default");this.handleElId=E;A.onAvailable(E,this.handleOnAvailable,this,true);this.setDragElId(E);this.invalidHandleTypes={A:"A"};this.invalidHandleIds={};this.invalidHandleClasses=[];this.applyConfig();},applyConfig:function(){this.events={mouseDown:true,b4MouseDown:true,mouseUp:true,b4StartDrag:true,startDrag:true,b4EndDrag:true,endDrag:true,drag:true,b4Drag:true,invalidDrop:true,b4DragOut:true,dragOut:true,dragEnter:true,b4DragOver:true,dragOver:true,b4DragDrop:true,dragDrop:true};if(this.config.events){for(var C in this.config.events){if(this.config.events[C]===false){this.events[C]=false;}}}this.padding=this.config.padding||[0,0,0,0];this.isTarget=(this.config.isTarget!==false);this.maintainOffset=(this.config.maintainOffset);this.primaryButtonOnly=(this.config.primaryButtonOnly!==false);this.dragOnly=((this.config.dragOnly===true)?true:false);this.useShim=((this.config.useShim===true)?true:false);},handleOnAvailable:function(){this.available=true;this.resetConstraints();this.onAvailable();},setPadding:function(E,C,F,D){if(!C&&0!==C){this.padding=[E,E,E,E];}else{if(!F&&0!==F){this.padding=[E,C,E,C];}else{this.padding=[E,C,F,D];}}},setInitPosition:function(F,E){var G=this.getEl();if(!this.DDM.verifyEl(G)){if(G&&G.style&&(G.style.display=="none")){}else{}return;}var D=F||0;var C=E||0;var H=B.getXY(G);this.initPageX=H[0]-D;this.initPageY=H[1]-C;this.lastPageX=H[0];this.lastPageY=H[1];this.setStartPosition(H);},setStartPosition:function(D){var C=D||B.getXY(this.getEl());this.deltaSetXY=null;this.startPageX=C[0];this.startPageY=C[1];},addToGroup:function(C){this.groups[C]=true;this.DDM.regDragDrop(this,C);},removeFromGroup:function(C){if(this.groups[C]){delete this.groups[C];}this.DDM.removeDDFromGroup(this,C);},setDragElId:function(C){this.dragElId=C;},setHandleElId:function(C){if(typeof C!=="string"){C=B.generateId(C);}this.handleElId=C;this.DDM.regHandle(this.id,C);},setOuterHandleElId:function(C){if(typeof C!=="string"){C=B.generateId(C);}A.on(C,"mousedown",this.handleMouseDown,this,true);this.setHandleElId(C);this.hasOuterHandles=true;},unreg:function(){A.removeListener(this.id,"mousedown",this.handleMouseDown);this._domRef=null;this.DDM._remove(this);},isLocked:function(){return(this.DDM.isLocked()||this.locked);},handleMouseDown:function(J,I){var D=J.which||J.button;if(this.primaryButtonOnly&&D>1){return;}if(this.isLocked()){return;}var C=this.b4MouseDown(J),F=true;if(this.events.b4MouseDown){F=this.fireEvent("b4MouseDownEvent",J);}var E=this.onMouseDown(J),H=true;if(this.events.mouseDown){H=this.fireEvent("mouseDownEvent",J);}if((C===false)||(E===false)||(F===false)||(H===false)){return;}this.DDM.refreshCache(this.groups);var G=new YAHOO.util.Point(A.getPageX(J),A.getPageY(J));if(!this.hasOuterHandles&&!this.DDM.isOverTarget(G,this)){}else{if(this.clickValidator(J)){this.setStartPosition();this.DDM.handleMouseDown(J,this);this.DDM.stopEvent(J);}else{}}},clickValidator:function(D){var C=YAHOO.util.Event.getTarget(D);return(this.isValidHandleChild(C)&&(this.id==this.handleElId||this.DDM.handleWasClicked(C,this.id)));},getTargetCoord:function(E,D){var C=E-this.deltaX;var F=D-this.deltaY;if(this.constrainX){if(C<this.minX){C=this.minX;}if(C>this.maxX){C=this.maxX;}}if(this.constrainY){if(F<this.minY){F=this.minY;}if(F>this.maxY){F=this.maxY;}}C=this.getTick(C,this.xTicks);F=this.getTick(F,this.yTicks);return{x:C,y:F};},addInvalidHandleType:function(C){var D=C.toUpperCase();this.invalidHandleTypes[D]=D;},addInvalidHandleId:function(C){if(typeof C!=="string"){C=B.generateId(C);}this.invalidHandleIds[C]=C;},addInvalidHandleClass:function(C){this.invalidHandleClasses.push(C);},removeInvalidHandleType:function(C){var D=C.toUpperCase();delete this.invalidHandleTypes[D];},removeInvalidHandleId:function(C){if(typeof C!=="string"){C=B.generateId(C);}delete this.invalidHandleIds[C];},removeInvalidHandleClass:function(D){for(var E=0,C=this.invalidHandleClasses.length;E<C;++E){if(this.invalidHandleClasses[E]==D){delete this.invalidHandleClasses[E];}}},isValidHandleChild:function(F){var E=true;var H;try{H=F.nodeName.toUpperCase();}catch(G){H=F.nodeName;}E=E&&!this.invalidHandleTypes[H];E=E&&!this.invalidHandleIds[F.id];for(var D=0,C=this.invalidHandleClasses.length;E&&D<C;++D){E=!B.hasClass(F,this.invalidHandleClasses[D]);}return E;},setXTicks:function(F,C){this.xTicks=[];this.xTickSize=C;var E={};for(var D=this.initPageX;D>=this.minX;D=D-C){if(!E[D]){this.xTicks[this.xTicks.length]=D;E[D]=true;}}for(D=this.initPageX;D<=this.maxX;D=D+C){if(!E[D]){this.xTicks[this.xTicks.length]=D;E[D]=true;}}this.xTicks.sort(this.DDM.numericSort);},setYTicks:function(F,C){this.yTicks=[];this.yTickSize=C;var E={};for(var D=this.initPageY;D>=this.minY;D=D-C){if(!E[D]){this.yTicks[this.yTicks.length]=D;E[D]=true;}}for(D=this.initPageY;D<=this.maxY;D=D+C){if(!E[D]){this.yTicks[this.yTicks.length]=D;E[D]=true;}}this.yTicks.sort(this.DDM.numericSort);},setXConstraint:function(E,D,C){this.leftConstraint=parseInt(E,10);this.rightConstraint=parseInt(D,10);this.minX=this.initPageX-this.leftConstraint;this.maxX=this.initPageX+this.rightConstraint;if(C){this.setXTicks(this.initPageX,C);}this.constrainX=true;},clearConstraints:function(){this.constrainX=false;this.constrainY=false;this.clearTicks();},clearTicks:function(){this.xTicks=null;this.yTicks=null;this.xTickSize=0;this.yTickSize=0;},setYConstraint:function(C,E,D){this.topConstraint=parseInt(C,10);this.bottomConstraint=parseInt(E,10);this.minY=this.initPageY-this.topConstraint;this.maxY=this.initPageY+this.bottomConstraint;if(D){this.setYTicks(this.initPageY,D);
+}this.constrainY=true;},resetConstraints:function(){if(this.initPageX||this.initPageX===0){var D=(this.maintainOffset)?this.lastPageX-this.initPageX:0;var C=(this.maintainOffset)?this.lastPageY-this.initPageY:0;this.setInitPosition(D,C);}else{this.setInitPosition();}if(this.constrainX){this.setXConstraint(this.leftConstraint,this.rightConstraint,this.xTickSize);}if(this.constrainY){this.setYConstraint(this.topConstraint,this.bottomConstraint,this.yTickSize);}},getTick:function(I,F){if(!F){return I;}else{if(F[0]>=I){return F[0];}else{for(var D=0,C=F.length;D<C;++D){var E=D+1;if(F[E]&&F[E]>=I){var H=I-F[D];var G=F[E]-I;return(G>H)?F[D]:F[E];}}return F[F.length-1];}}},toString:function(){return("DragDrop "+this.id);}};YAHOO.augment(YAHOO.util.DragDrop,YAHOO.util.EventProvider);})();YAHOO.util.DD=function(C,A,B){if(C){this.init(C,A,B);}};YAHOO.extend(YAHOO.util.DD,YAHOO.util.DragDrop,{scroll:true,autoOffset:function(C,B){var A=C-this.startPageX;var D=B-this.startPageY;this.setDelta(A,D);},setDelta:function(B,A){this.deltaX=B;this.deltaY=A;},setDragElPos:function(C,B){var A=this.getDragEl();this.alignElWithMouse(A,C,B);},alignElWithMouse:function(C,G,F){var E=this.getTargetCoord(G,F);if(!this.deltaSetXY){var H=[E.x,E.y];YAHOO.util.Dom.setXY(C,H);var D=parseInt(YAHOO.util.Dom.getStyle(C,"left"),10);var B=parseInt(YAHOO.util.Dom.getStyle(C,"top"),10);this.deltaSetXY=[D-E.x,B-E.y];}else{YAHOO.util.Dom.setStyle(C,"left",(E.x+this.deltaSetXY[0])+"px");YAHOO.util.Dom.setStyle(C,"top",(E.y+this.deltaSetXY[1])+"px");}this.cachePosition(E.x,E.y);var A=this;setTimeout(function(){A.autoScroll.call(A,E.x,E.y,C.offsetHeight,C.offsetWidth);},0);},cachePosition:function(B,A){if(B){this.lastPageX=B;this.lastPageY=A;}else{var C=YAHOO.util.Dom.getXY(this.getEl());this.lastPageX=C[0];this.lastPageY=C[1];}},autoScroll:function(J,I,E,K){if(this.scroll){var L=this.DDM.getClientHeight();var B=this.DDM.getClientWidth();var N=this.DDM.getScrollTop();var D=this.DDM.getScrollLeft();var H=E+I;var M=K+J;var G=(L+N-I-this.deltaY);var F=(B+D-J-this.deltaX);var C=40;var A=(document.all)?80:30;if(H>L&&G<C){window.scrollTo(D,N+A);}if(I<N&&N>0&&I-N<C){window.scrollTo(D,N-A);}if(M>B&&F<C){window.scrollTo(D+A,N);}if(J<D&&D>0&&J-D<C){window.scrollTo(D-A,N);}}},applyConfig:function(){YAHOO.util.DD.superclass.applyConfig.call(this);this.scroll=(this.config.scroll!==false);},b4MouseDown:function(A){this.setStartPosition();this.autoOffset(YAHOO.util.Event.getPageX(A),YAHOO.util.Event.getPageY(A));},b4Drag:function(A){this.setDragElPos(YAHOO.util.Event.getPageX(A),YAHOO.util.Event.getPageY(A));},toString:function(){return("DD "+this.id);}});YAHOO.util.DDProxy=function(C,A,B){if(C){this.init(C,A,B);this.initFrame();}};YAHOO.util.DDProxy.dragElId="ygddfdiv";YAHOO.extend(YAHOO.util.DDProxy,YAHOO.util.DD,{resizeFrame:true,centerFrame:false,createFrame:function(){var B=this,A=document.body;if(!A||!A.firstChild){setTimeout(function(){B.createFrame();},50);return;}var F=this.getDragEl(),E=YAHOO.util.Dom;if(!F){F=document.createElement("div");F.id=this.dragElId;var D=F.style;D.position="absolute";D.visibility="hidden";D.cursor="move";D.border="2px solid #aaa";D.zIndex=999;D.height="25px";D.width="25px";var C=document.createElement("div");E.setStyle(C,"height","100%");E.setStyle(C,"width","100%");E.setStyle(C,"background-color","#ccc");E.setStyle(C,"opacity","0");F.appendChild(C);A.insertBefore(F,A.firstChild);}},initFrame:function(){this.createFrame();},applyConfig:function(){YAHOO.util.DDProxy.superclass.applyConfig.call(this);this.resizeFrame=(this.config.resizeFrame!==false);this.centerFrame=(this.config.centerFrame);this.setDragElId(this.config.dragElId||YAHOO.util.DDProxy.dragElId);},showFrame:function(E,D){var C=this.getEl();var A=this.getDragEl();var B=A.style;this._resizeProxy();if(this.centerFrame){this.setDelta(Math.round(parseInt(B.width,10)/2),Math.round(parseInt(B.height,10)/2));}this.setDragElPos(E,D);YAHOO.util.Dom.setStyle(A,"visibility","visible");},_resizeProxy:function(){if(this.resizeFrame){var H=YAHOO.util.Dom;var B=this.getEl();var C=this.getDragEl();var G=parseInt(H.getStyle(C,"borderTopWidth"),10);var I=parseInt(H.getStyle(C,"borderRightWidth"),10);var F=parseInt(H.getStyle(C,"borderBottomWidth"),10);var D=parseInt(H.getStyle(C,"borderLeftWidth"),10);if(isNaN(G)){G=0;}if(isNaN(I)){I=0;}if(isNaN(F)){F=0;}if(isNaN(D)){D=0;}var E=Math.max(0,B.offsetWidth-I-D);var A=Math.max(0,B.offsetHeight-G-F);H.setStyle(C,"width",E+"px");H.setStyle(C,"height",A+"px");}},b4MouseDown:function(B){this.setStartPosition();var A=YAHOO.util.Event.getPageX(B);var C=YAHOO.util.Event.getPageY(B);this.autoOffset(A,C);},b4StartDrag:function(A,B){this.showFrame(A,B);},b4EndDrag:function(A){YAHOO.util.Dom.setStyle(this.getDragEl(),"visibility","hidden");},endDrag:function(D){var C=YAHOO.util.Dom;var B=this.getEl();var A=this.getDragEl();C.setStyle(A,"visibility","");C.setStyle(B,"visibility","hidden");YAHOO.util.DDM.moveToEl(B,A);C.setStyle(A,"visibility","hidden");C.setStyle(B,"visibility","");},toString:function(){return("DDProxy "+this.id);}});YAHOO.util.DDTarget=function(C,A,B){if(C){this.initTarget(C,A,B);}};YAHOO.extend(YAHOO.util.DDTarget,YAHOO.util.DragDrop,{toString:function(){return("DDTarget "+this.id);}});YAHOO.register("dragdrop",YAHOO.util.DragDropMgr,{version:"2.8.2r1",build:"7"}); \ No newline at end of file
diff --git a/js/yui/element-delegate/element-delegate-min.js b/js/yui/element-delegate/element-delegate-min.js
new file mode 100644
index 000000000..2bacdee54
--- /dev/null
+++ b/js/yui/element-delegate/element-delegate-min.js
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+(function(){var A=YAHOO.util.Event,B=[],C={mouseenter:true,mouseleave:true};YAHOO.lang.augmentObject(YAHOO.util.Element.prototype,{delegate:function(J,L,F,H,I){if(YAHOO.lang.isString(F)&&!YAHOO.util.Selector){return false;}if(!A._createDelegate){return false;}var E=A._getType(J),G=this.get("element"),M,K,D=function(N){return M.call(G,N);};if(C[J]){if(!A._createMouseDelegate){return false;}K=A._createMouseDelegate(L,H,I);M=A._createDelegate(function(P,O,N){return K.call(O,P,N);},F,H,I);}else{M=A._createDelegate(L,F,H,I);}B.push([G,E,L,D]);return this.on(E,D);},removeDelegate:function(H,G){var I=A._getType(H),E=A._getCacheIndex(B,this.get("element"),I,G),F,D;if(E>=0){D=B[E];}if(D){F=this.removeListener(D[1],D[3]);if(F){delete B[E][2];delete B[E][3];B.splice(E,1);}}return F;}});}());YAHOO.register("element-delegate",YAHOO.util.Element,{version:"2.8.2r1",build:"7"}); \ No newline at end of file
diff --git a/js/yui/element/element-min.js b/js/yui/element/element-min.js
new file mode 100644
index 000000000..723d452c8
--- /dev/null
+++ b/js/yui/element/element-min.js
@@ -0,0 +1,8 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+YAHOO.util.Attribute=function(B,A){if(A){this.owner=A;this.configure(B,true);}};YAHOO.util.Attribute.prototype={name:undefined,value:null,owner:null,readOnly:false,writeOnce:false,_initialConfig:null,_written:false,method:null,setter:null,getter:null,validator:null,getValue:function(){var A=this.value;if(this.getter){A=this.getter.call(this.owner,this.name,A);}return A;},setValue:function(F,B){var E,A=this.owner,C=this.name;var D={type:C,prevValue:this.getValue(),newValue:F};if(this.readOnly||(this.writeOnce&&this._written)){return false;}if(this.validator&&!this.validator.call(A,F)){return false;}if(!B){E=A.fireBeforeChangeEvent(D);if(E===false){return false;}}if(this.setter){F=this.setter.call(A,F,this.name);if(F===undefined){}}if(this.method){this.method.call(A,F,this.name);}this.value=F;this._written=true;D.type=C;if(!B){this.owner.fireChangeEvent(D);}return true;},configure:function(B,C){B=B||{};if(C){this._written=false;}this._initialConfig=this._initialConfig||{};for(var A in B){if(B.hasOwnProperty(A)){this[A]=B[A];if(C){this._initialConfig[A]=B[A];}}}},resetValue:function(){return this.setValue(this._initialConfig.value);},resetConfig:function(){this.configure(this._initialConfig,true);},refresh:function(A){this.setValue(this.value,A);}};(function(){var A=YAHOO.util.Lang;YAHOO.util.AttributeProvider=function(){};YAHOO.util.AttributeProvider.prototype={_configs:null,get:function(C){this._configs=this._configs||{};var B=this._configs[C];if(!B||!this._configs.hasOwnProperty(C)){return null;}return B.getValue();},set:function(D,E,B){this._configs=this._configs||{};var C=this._configs[D];if(!C){return false;}return C.setValue(E,B);},getAttributeKeys:function(){this._configs=this._configs;var C=[],B;for(B in this._configs){if(A.hasOwnProperty(this._configs,B)&&!A.isUndefined(this._configs[B])){C[C.length]=B;}}return C;},setAttributes:function(D,B){for(var C in D){if(A.hasOwnProperty(D,C)){this.set(C,D[C],B);}}},resetValue:function(C,B){this._configs=this._configs||{};if(this._configs[C]){this.set(C,this._configs[C]._initialConfig.value,B);return true;}return false;},refresh:function(E,C){this._configs=this._configs||{};var F=this._configs;E=((A.isString(E))?[E]:E)||this.getAttributeKeys();for(var D=0,B=E.length;D<B;++D){if(F.hasOwnProperty(E[D])){this._configs[E[D]].refresh(C);}}},register:function(B,C){this.setAttributeConfig(B,C);},getAttributeConfig:function(C){this._configs=this._configs||{};var B=this._configs[C]||{};var D={};for(C in B){if(A.hasOwnProperty(B,C)){D[C]=B[C];}}return D;},setAttributeConfig:function(B,C,D){this._configs=this._configs||{};C=C||{};if(!this._configs[B]){C.name=B;this._configs[B]=this.createAttribute(C);}else{this._configs[B].configure(C,D);}},configureAttribute:function(B,C,D){this.setAttributeConfig(B,C,D);},resetAttributeConfig:function(B){this._configs=this._configs||{};this._configs[B].resetConfig();},subscribe:function(B,C){this._events=this._events||{};if(!(B in this._events)){this._events[B]=this.createEvent(B);}YAHOO.util.EventProvider.prototype.subscribe.apply(this,arguments);},on:function(){this.subscribe.apply(this,arguments);},addListener:function(){this.subscribe.apply(this,arguments);},fireBeforeChangeEvent:function(C){var B="before";B+=C.type.charAt(0).toUpperCase()+C.type.substr(1)+"Change";C.type=B;return this.fireEvent(C.type,C);},fireChangeEvent:function(B){B.type+="Change";return this.fireEvent(B.type,B);},createAttribute:function(B){return new YAHOO.util.Attribute(B,this);}};YAHOO.augment(YAHOO.util.AttributeProvider,YAHOO.util.EventProvider);})();(function(){var B=YAHOO.util.Dom,D=YAHOO.util.AttributeProvider,C={mouseenter:true,mouseleave:true};var A=function(E,F){this.init.apply(this,arguments);};A.DOM_EVENTS={"click":true,"dblclick":true,"keydown":true,"keypress":true,"keyup":true,"mousedown":true,"mousemove":true,"mouseout":true,"mouseover":true,"mouseup":true,"mouseenter":true,"mouseleave":true,"focus":true,"blur":true,"submit":true,"change":true};A.prototype={DOM_EVENTS:null,DEFAULT_HTML_SETTER:function(G,E){var F=this.get("element");if(F){F[E]=G;}return G;},DEFAULT_HTML_GETTER:function(E){var F=this.get("element"),G;if(F){G=F[E];}return G;},appendChild:function(E){E=E.get?E.get("element"):E;return this.get("element").appendChild(E);},getElementsByTagName:function(E){return this.get("element").getElementsByTagName(E);},hasChildNodes:function(){return this.get("element").hasChildNodes();},insertBefore:function(E,F){E=E.get?E.get("element"):E;F=(F&&F.get)?F.get("element"):F;return this.get("element").insertBefore(E,F);},removeChild:function(E){E=E.get?E.get("element"):E;return this.get("element").removeChild(E);},replaceChild:function(E,F){E=E.get?E.get("element"):E;F=F.get?F.get("element"):F;return this.get("element").replaceChild(E,F);},initAttributes:function(E){},addListener:function(J,I,K,H){H=H||this;var E=YAHOO.util.Event,G=this.get("element")||this.get("id"),F=this;if(C[J]&&!E._createMouseDelegate){return false;}if(!this._events[J]){if(G&&this.DOM_EVENTS[J]){E.on(G,J,function(M,L){if(M.srcElement&&!M.target){M.target=M.srcElement;}if((M.toElement&&!M.relatedTarget)||(M.fromElement&&!M.relatedTarget)){M.relatedTarget=E.getRelatedTarget(M);}if(!M.currentTarget){M.currentTarget=G;}F.fireEvent(J,M,L);},K,H);}this.createEvent(J,{scope:this});}return YAHOO.util.EventProvider.prototype.subscribe.apply(this,arguments);},on:function(){return this.addListener.apply(this,arguments);},subscribe:function(){return this.addListener.apply(this,arguments);},removeListener:function(F,E){return this.unsubscribe.apply(this,arguments);},addClass:function(E){B.addClass(this.get("element"),E);},getElementsByClassName:function(F,E){return B.getElementsByClassName(F,E,this.get("element"));},hasClass:function(E){return B.hasClass(this.get("element"),E);},removeClass:function(E){return B.removeClass(this.get("element"),E);},replaceClass:function(F,E){return B.replaceClass(this.get("element"),F,E);},setStyle:function(F,E){return B.setStyle(this.get("element"),F,E);
+},getStyle:function(E){return B.getStyle(this.get("element"),E);},fireQueue:function(){var F=this._queue;for(var G=0,E=F.length;G<E;++G){this[F[G][0]].apply(this,F[G][1]);}},appendTo:function(F,G){F=(F.get)?F.get("element"):B.get(F);this.fireEvent("beforeAppendTo",{type:"beforeAppendTo",target:F});G=(G&&G.get)?G.get("element"):B.get(G);var E=this.get("element");if(!E){return false;}if(!F){return false;}if(E.parent!=F){if(G){F.insertBefore(E,G);}else{F.appendChild(E);}}this.fireEvent("appendTo",{type:"appendTo",target:F});return E;},get:function(E){var G=this._configs||{},F=G.element;if(F&&!G[E]&&!YAHOO.lang.isUndefined(F.value[E])){this._setHTMLAttrConfig(E);}return D.prototype.get.call(this,E);},setAttributes:function(K,H){var F={},I=this._configOrder;for(var J=0,E=I.length;J<E;++J){if(K[I[J]]!==undefined){F[I[J]]=true;this.set(I[J],K[I[J]],H);}}for(var G in K){if(K.hasOwnProperty(G)&&!F[G]){this.set(G,K[G],H);}}},set:function(F,H,E){var G=this.get("element");if(!G){this._queue[this._queue.length]=["set",arguments];if(this._configs[F]){this._configs[F].value=H;}return;}if(!this._configs[F]&&!YAHOO.lang.isUndefined(G[F])){this._setHTMLAttrConfig(F);}return D.prototype.set.apply(this,arguments);},setAttributeConfig:function(E,F,G){this._configOrder.push(E);D.prototype.setAttributeConfig.apply(this,arguments);},createEvent:function(F,E){this._events[F]=true;return D.prototype.createEvent.apply(this,arguments);},init:function(F,E){this._initElement(F,E);},destroy:function(){var E=this.get("element");YAHOO.util.Event.purgeElement(E,true);this.unsubscribeAll();if(E&&E.parentNode){E.parentNode.removeChild(E);}this._queue=[];this._events={};this._configs={};this._configOrder=[];},_initElement:function(G,F){this._queue=this._queue||[];this._events=this._events||{};this._configs=this._configs||{};this._configOrder=[];F=F||{};F.element=F.element||G||null;var I=false;var E=A.DOM_EVENTS;this.DOM_EVENTS=this.DOM_EVENTS||{};for(var H in E){if(E.hasOwnProperty(H)){this.DOM_EVENTS[H]=E[H];}}if(typeof F.element==="string"){this._setHTMLAttrConfig("id",{value:F.element});}if(B.get(F.element)){I=true;this._initHTMLElement(F);this._initContent(F);}YAHOO.util.Event.onAvailable(F.element,function(){if(!I){this._initHTMLElement(F);}this.fireEvent("available",{type:"available",target:B.get(F.element)});},this,true);YAHOO.util.Event.onContentReady(F.element,function(){if(!I){this._initContent(F);}this.fireEvent("contentReady",{type:"contentReady",target:B.get(F.element)});},this,true);},_initHTMLElement:function(E){this.setAttributeConfig("element",{value:B.get(E.element),readOnly:true});},_initContent:function(E){this.initAttributes(E);this.setAttributes(E,true);this.fireQueue();},_setHTMLAttrConfig:function(E,G){var F=this.get("element");G=G||{};G.name=E;G.setter=G.setter||this.DEFAULT_HTML_SETTER;G.getter=G.getter||this.DEFAULT_HTML_GETTER;G.value=G.value||F[E];this._configs[E]=new YAHOO.util.Attribute(G,this);}};YAHOO.augment(A,D);YAHOO.util.Element=A;})();YAHOO.register("element",YAHOO.util.Element,{version:"2.8.2r1",build:"7"}); \ No newline at end of file
diff --git a/js/yui/event-delegate/event-delegate-min.js b/js/yui/event-delegate/event-delegate-min.js
new file mode 100644
index 000000000..1e4b9f470
--- /dev/null
+++ b/js/yui/event-delegate/event-delegate-min.js
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+(function(){var A=YAHOO.util.Event,C=YAHOO.lang,B=[],D=function(H,E,F){var G;if(!H||H===F){G=false;}else{G=YAHOO.util.Selector.test(H,E)?H:D(H.parentNode,E,F);}return G;};C.augmentObject(A,{_createDelegate:function(F,E,G,H){return function(I){var J=this,N=A.getTarget(I),L=E,P=(J.nodeType===9),Q,K,O,M;if(C.isFunction(E)){Q=E(N);}else{if(C.isString(E)){if(!P){O=J.id;if(!O){O=A.generateId(J);}M=("#"+O+" ");L=(M+E).replace(/,/gi,(","+M));}if(YAHOO.util.Selector.test(N,L)){Q=N;}else{if(YAHOO.util.Selector.test(N,((L.replace(/,/gi," *,"))+" *"))){Q=D(N,L,J);}}}}if(Q){K=Q;if(H){if(H===true){K=G;}else{K=H;}}return F.call(K,I,Q,J,G);}};},delegate:function(F,J,L,G,H,I){var E=J,K,M;if(C.isString(G)&&!YAHOO.util.Selector){return false;}if(J=="mouseenter"||J=="mouseleave"){if(!A._createMouseDelegate){return false;}E=A._getType(J);K=A._createMouseDelegate(L,H,I);M=A._createDelegate(function(P,O,N){return K.call(O,P,N);},G,H,I);}else{M=A._createDelegate(L,G,H,I);}B.push([F,E,L,M]);return A.on(F,E,M);},removeDelegate:function(F,J,I){var K=J,H=false,G,E;if(J=="mouseenter"||J=="mouseleave"){K=A._getType(J);}G=A._getCacheIndex(B,F,K,I);if(G>=0){E=B[G];}if(F&&E){H=A.removeListener(E[0],E[1],E[3]);if(H){delete B[G][2];delete B[G][3];B.splice(G,1);}}return H;}});}());YAHOO.register("event-delegate",YAHOO.util.Event,{version:"2.8.2r1",build:"7"}); \ No newline at end of file
diff --git a/js/yui/event-mouseenter/event-mouseenter-min.js b/js/yui/event-mouseenter/event-mouseenter-min.js
new file mode 100644
index 000000000..e2dd40d69
--- /dev/null
+++ b/js/yui/event-mouseenter/event-mouseenter-min.js
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+(function(){var B=YAHOO.util.Event,G=YAHOO.lang,E=B.addListener,F=B.removeListener,C=B.getListeners,D=[],H={mouseenter:"mouseover",mouseleave:"mouseout"},A=function(N,M,L){var J=B._getCacheIndex(D,N,M,L),I,K;if(J>=0){I=D[J];}if(N&&I){K=F.call(B,I[0],M,I[3]);if(K){delete D[J][2];delete D[J][3];D.splice(J,1);}}return K;};G.augmentObject(B._specialTypes,H);G.augmentObject(B,{_createMouseDelegate:function(I,J,K){return function(Q,M){var P=this,L=B.getRelatedTarget(Q),O,N;if(P!=L&&!YAHOO.util.Dom.isAncestor(P,L)){O=P;if(K){if(K===true){O=J;}else{O=K;}}N=[Q,P,J];if(M){N.splice(2,0,M);}return I.apply(O,N);}};},addListener:function(M,L,K,N,O){var I,J;if(H[L]){I=B._createMouseDelegate(K,N,O);I.mouseDelegate=true;D.push([M,L,K,I]);J=E.call(B,M,L,I);}else{J=E.apply(B,arguments);}return J;},removeListener:function(L,K,J){var I;if(H[K]){I=A.apply(B,arguments);}else{I=F.apply(B,arguments);}return I;},getListeners:function(N,M){var L=[],P,K=(M==="mouseover"||M==="mouseout"),O,J,I;if(M&&(K||H[M])){P=C.call(B,N,this._getType(M));if(P){for(J=P.length-1;J>-1;J--){I=P[J];O=I.fn.mouseDelegate;if((H[M]&&O)||(K&&!O)){L.push(I);}}}}else{L=C.apply(B,arguments);}return(L&&L.length)?L:null;}},true);B.on=B.addListener;}());YAHOO.register("event-mouseenter",YAHOO.util.Event,{version:"2.8.2r1",build:"7"}); \ No newline at end of file
diff --git a/js/yui/event-simulate/event-simulate-min.js b/js/yui/event-simulate/event-simulate-min.js
new file mode 100644
index 000000000..a9059cbb8
--- /dev/null
+++ b/js/yui/event-simulate/event-simulate-min.js
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+YAHOO.util.UserAction={simulateKeyEvent:function(F,J,E,C,L,B,A,K,H,N,M){F=YAHOO.util.Dom.get(F);if(!F){throw new Error("simulateKeyEvent(): Invalid target.");}if(YAHOO.lang.isString(J)){J=J.toLowerCase();switch(J){case"keyup":case"keydown":case"keypress":break;case"textevent":J="keypress";break;default:throw new Error("simulateKeyEvent(): Event type '"+J+"' not supported.");}}else{throw new Error("simulateKeyEvent(): Event type must be a string.");}if(!YAHOO.lang.isBoolean(E)){E=true;}if(!YAHOO.lang.isBoolean(C)){C=true;}if(!YAHOO.lang.isObject(L)){L=window;}if(!YAHOO.lang.isBoolean(B)){B=false;}if(!YAHOO.lang.isBoolean(A)){A=false;}if(!YAHOO.lang.isBoolean(K)){K=false;}if(!YAHOO.lang.isBoolean(H)){H=false;}if(!YAHOO.lang.isNumber(N)){N=0;}if(!YAHOO.lang.isNumber(M)){M=0;}var I=null;if(YAHOO.lang.isFunction(document.createEvent)){try{I=document.createEvent("KeyEvents");I.initKeyEvent(J,E,C,L,B,A,K,H,N,M);}catch(G){try{I=document.createEvent("Events");}catch(D){I=document.createEvent("UIEvents");}finally{I.initEvent(J,E,C);I.view=L;I.altKey=A;I.ctrlKey=B;I.shiftKey=K;I.metaKey=H;I.keyCode=N;I.charCode=M;}}F.dispatchEvent(I);}else{if(YAHOO.lang.isObject(document.createEventObject)){I=document.createEventObject();I.bubbles=E;I.cancelable=C;I.view=L;I.ctrlKey=B;I.altKey=A;I.shiftKey=K;I.metaKey=H;I.keyCode=(M>0)?M:N;F.fireEvent("on"+J,I);}else{throw new Error("simulateKeyEvent(): No event simulation framework present.");}}},simulateMouseEvent:function(K,P,H,E,Q,J,G,F,D,B,C,A,O,M,I,L){K=YAHOO.util.Dom.get(K);if(!K){throw new Error("simulateMouseEvent(): Invalid target.");}if(YAHOO.lang.isString(P)){P=P.toLowerCase();switch(P){case"mouseover":case"mouseout":case"mousedown":case"mouseup":case"click":case"dblclick":case"mousemove":break;default:throw new Error("simulateMouseEvent(): Event type '"+P+"' not supported.");}}else{throw new Error("simulateMouseEvent(): Event type must be a string.");}if(!YAHOO.lang.isBoolean(H)){H=true;}if(!YAHOO.lang.isBoolean(E)){E=(P!="mousemove");}if(!YAHOO.lang.isObject(Q)){Q=window;}if(!YAHOO.lang.isNumber(J)){J=1;}if(!YAHOO.lang.isNumber(G)){G=0;}if(!YAHOO.lang.isNumber(F)){F=0;}if(!YAHOO.lang.isNumber(D)){D=0;}if(!YAHOO.lang.isNumber(B)){B=0;}if(!YAHOO.lang.isBoolean(C)){C=false;}if(!YAHOO.lang.isBoolean(A)){A=false;}if(!YAHOO.lang.isBoolean(O)){O=false;}if(!YAHOO.lang.isBoolean(M)){M=false;}if(!YAHOO.lang.isNumber(I)){I=0;}var N=null;if(YAHOO.lang.isFunction(document.createEvent)){N=document.createEvent("MouseEvents");if(N.initMouseEvent){N.initMouseEvent(P,H,E,Q,J,G,F,D,B,C,A,O,M,I,L);}else{N=document.createEvent("UIEvents");N.initEvent(P,H,E);N.view=Q;N.detail=J;N.screenX=G;N.screenY=F;N.clientX=D;N.clientY=B;N.ctrlKey=C;N.altKey=A;N.metaKey=M;N.shiftKey=O;N.button=I;N.relatedTarget=L;}if(L&&!N.relatedTarget){if(P=="mouseout"){N.toElement=L;}else{if(P=="mouseover"){N.fromElement=L;}}}K.dispatchEvent(N);}else{if(YAHOO.lang.isObject(document.createEventObject)){N=document.createEventObject();N.bubbles=H;N.cancelable=E;N.view=Q;N.detail=J;N.screenX=G;N.screenY=F;N.clientX=D;N.clientY=B;N.ctrlKey=C;N.altKey=A;N.metaKey=M;N.shiftKey=O;switch(I){case 0:N.button=1;break;case 1:N.button=4;break;case 2:break;default:N.button=0;}N.relatedTarget=L;K.fireEvent("on"+P,N);}else{throw new Error("simulateMouseEvent(): No event simulation framework present.");}}},fireMouseEvent:function(C,B,A){A=A||{};this.simulateMouseEvent(C,B,A.bubbles,A.cancelable,A.view,A.detail,A.screenX,A.screenY,A.clientX,A.clientY,A.ctrlKey,A.altKey,A.shiftKey,A.metaKey,A.button,A.relatedTarget);},click:function(B,A){this.fireMouseEvent(B,"click",A);},dblclick:function(B,A){this.fireMouseEvent(B,"dblclick",A);},mousedown:function(B,A){this.fireMouseEvent(B,"mousedown",A);},mousemove:function(B,A){this.fireMouseEvent(B,"mousemove",A);},mouseout:function(B,A){this.fireMouseEvent(B,"mouseout",A);},mouseover:function(B,A){this.fireMouseEvent(B,"mouseover",A);},mouseup:function(B,A){this.fireMouseEvent(B,"mouseup",A);},fireKeyEvent:function(B,C,A){A=A||{};this.simulateKeyEvent(C,B,A.bubbles,A.cancelable,A.view,A.ctrlKey,A.altKey,A.shiftKey,A.metaKey,A.keyCode,A.charCode);},keydown:function(B,A){this.fireKeyEvent("keydown",B,A);},keypress:function(B,A){this.fireKeyEvent("keypress",B,A);},keyup:function(B,A){this.fireKeyEvent("keyup",B,A);}};YAHOO.register("event-simulate",YAHOO.util.UserAction,{version:"2.8.2r1",build:"7"}); \ No newline at end of file
diff --git a/js/yui/event/event-min.js b/js/yui/event/event-min.js
new file mode 100644
index 000000000..6f72381e3
--- /dev/null
+++ b/js/yui/event/event-min.js
@@ -0,0 +1,11 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+YAHOO.util.CustomEvent=function(D,C,B,A,E){this.type=D;this.scope=C||window;this.silent=B;this.fireOnce=E;this.fired=false;this.firedWith=null;this.signature=A||YAHOO.util.CustomEvent.LIST;this.subscribers=[];if(!this.silent){}var F="_YUICEOnSubscribe";if(D!==F){this.subscribeEvent=new YAHOO.util.CustomEvent(F,this,true);}this.lastError=null;};YAHOO.util.CustomEvent.LIST=0;YAHOO.util.CustomEvent.FLAT=1;YAHOO.util.CustomEvent.prototype={subscribe:function(B,C,D){if(!B){throw new Error("Invalid callback for subscriber to '"+this.type+"'");}if(this.subscribeEvent){this.subscribeEvent.fire(B,C,D);}var A=new YAHOO.util.Subscriber(B,C,D);if(this.fireOnce&&this.fired){this.notify(A,this.firedWith);}else{this.subscribers.push(A);}},unsubscribe:function(D,F){if(!D){return this.unsubscribeAll();}var E=false;for(var B=0,A=this.subscribers.length;B<A;++B){var C=this.subscribers[B];if(C&&C.contains(D,F)){this._delete(B);E=true;}}return E;},fire:function(){this.lastError=null;var H=[],A=this.subscribers.length;var D=[].slice.call(arguments,0),C=true,F,B=false;if(this.fireOnce){if(this.fired){return true;}else{this.firedWith=D;}}this.fired=true;if(!A&&this.silent){return true;}if(!this.silent){}var E=this.subscribers.slice();for(F=0;F<A;++F){var G=E[F];if(!G){B=true;}else{C=this.notify(G,D);if(false===C){if(!this.silent){}break;}}}return(C!==false);},notify:function(F,C){var B,H=null,E=F.getScope(this.scope),A=YAHOO.util.Event.throwErrors;if(!this.silent){}if(this.signature==YAHOO.util.CustomEvent.FLAT){if(C.length>0){H=C[0];}try{B=F.fn.call(E,H,F.obj);}catch(G){this.lastError=G;if(A){throw G;}}}else{try{B=F.fn.call(E,this.type,C,F.obj);}catch(D){this.lastError=D;if(A){throw D;}}}return B;},unsubscribeAll:function(){var A=this.subscribers.length,B;for(B=A-1;B>-1;B--){this._delete(B);}this.subscribers=[];return A;},_delete:function(A){var B=this.subscribers[A];if(B){delete B.fn;delete B.obj;}this.subscribers.splice(A,1);},toString:function(){return"CustomEvent: "+"'"+this.type+"', "+"context: "+this.scope;}};YAHOO.util.Subscriber=function(A,B,C){this.fn=A;this.obj=YAHOO.lang.isUndefined(B)?null:B;this.overrideContext=C;};YAHOO.util.Subscriber.prototype.getScope=function(A){if(this.overrideContext){if(this.overrideContext===true){return this.obj;}else{return this.overrideContext;}}return A;};YAHOO.util.Subscriber.prototype.contains=function(A,B){if(B){return(this.fn==A&&this.obj==B);}else{return(this.fn==A);}};YAHOO.util.Subscriber.prototype.toString=function(){return"Subscriber { obj: "+this.obj+", overrideContext: "+(this.overrideContext||"no")+" }";};if(!YAHOO.util.Event){YAHOO.util.Event=function(){var G=false,H=[],J=[],A=0,E=[],B=0,C={63232:38,63233:40,63234:37,63235:39,63276:33,63277:34,25:9},D=YAHOO.env.ua.ie,F="focusin",I="focusout";return{POLL_RETRYS:500,POLL_INTERVAL:40,EL:0,TYPE:1,FN:2,WFN:3,UNLOAD_OBJ:3,ADJ_SCOPE:4,OBJ:5,OVERRIDE:6,CAPTURE:7,lastError:null,isSafari:YAHOO.env.ua.webkit,webkit:YAHOO.env.ua.webkit,isIE:D,_interval:null,_dri:null,_specialTypes:{focusin:(D?"focusin":"focus"),focusout:(D?"focusout":"blur")},DOMReady:false,throwErrors:false,startInterval:function(){if(!this._interval){this._interval=YAHOO.lang.later(this.POLL_INTERVAL,this,this._tryPreloadAttach,null,true);}},onAvailable:function(Q,M,O,P,N){var K=(YAHOO.lang.isString(Q))?[Q]:Q;for(var L=0;L<K.length;L=L+1){E.push({id:K[L],fn:M,obj:O,overrideContext:P,checkReady:N});}A=this.POLL_RETRYS;this.startInterval();},onContentReady:function(N,K,L,M){this.onAvailable(N,K,L,M,true);},onDOMReady:function(){this.DOMReadyEvent.subscribe.apply(this.DOMReadyEvent,arguments);},_addListener:function(M,K,V,P,T,Y){if(!V||!V.call){return false;}if(this._isValidCollection(M)){var W=true;for(var Q=0,S=M.length;Q<S;++Q){W=this.on(M[Q],K,V,P,T)&&W;}return W;}else{if(YAHOO.lang.isString(M)){var O=this.getEl(M);if(O){M=O;}else{this.onAvailable(M,function(){YAHOO.util.Event._addListener(M,K,V,P,T,Y);});return true;}}}if(!M){return false;}if("unload"==K&&P!==this){J[J.length]=[M,K,V,P,T];return true;}var L=M;if(T){if(T===true){L=P;}else{L=T;}}var N=function(Z){return V.call(L,YAHOO.util.Event.getEvent(Z,M),P);};var X=[M,K,V,N,L,P,T,Y];var R=H.length;H[R]=X;try{this._simpleAdd(M,K,N,Y);}catch(U){this.lastError=U;this.removeListener(M,K,V);return false;}return true;},_getType:function(K){return this._specialTypes[K]||K;},addListener:function(M,P,L,N,O){var K=((P==F||P==I)&&!YAHOO.env.ua.ie)?true:false;return this._addListener(M,this._getType(P),L,N,O,K);},addFocusListener:function(L,K,M,N){return this.on(L,F,K,M,N);},removeFocusListener:function(L,K){return this.removeListener(L,F,K);},addBlurListener:function(L,K,M,N){return this.on(L,I,K,M,N);},removeBlurListener:function(L,K){return this.removeListener(L,I,K);},removeListener:function(L,K,R){var M,P,U;K=this._getType(K);if(typeof L=="string"){L=this.getEl(L);}else{if(this._isValidCollection(L)){var S=true;for(M=L.length-1;M>-1;M--){S=(this.removeListener(L[M],K,R)&&S);}return S;}}if(!R||!R.call){return this.purgeElement(L,false,K);}if("unload"==K){for(M=J.length-1;M>-1;M--){U=J[M];if(U&&U[0]==L&&U[1]==K&&U[2]==R){J.splice(M,1);return true;}}return false;}var N=null;var O=arguments[3];if("undefined"===typeof O){O=this._getCacheIndex(H,L,K,R);}if(O>=0){N=H[O];}if(!L||!N){return false;}var T=N[this.CAPTURE]===true?true:false;try{this._simpleRemove(L,K,N[this.WFN],T);}catch(Q){this.lastError=Q;return false;}delete H[O][this.WFN];delete H[O][this.FN];H.splice(O,1);return true;},getTarget:function(M,L){var K=M.target||M.srcElement;return this.resolveTextNode(K);},resolveTextNode:function(L){try{if(L&&3==L.nodeType){return L.parentNode;}}catch(K){}return L;},getPageX:function(L){var K=L.pageX;if(!K&&0!==K){K=L.clientX||0;if(this.isIE){K+=this._getScrollLeft();}}return K;},getPageY:function(K){var L=K.pageY;if(!L&&0!==L){L=K.clientY||0;if(this.isIE){L+=this._getScrollTop();}}return L;},getXY:function(K){return[this.getPageX(K),this.getPageY(K)];},getRelatedTarget:function(L){var K=L.relatedTarget;if(!K){if(L.type=="mouseout"){K=L.toElement;
+}else{if(L.type=="mouseover"){K=L.fromElement;}}}return this.resolveTextNode(K);},getTime:function(M){if(!M.time){var L=new Date().getTime();try{M.time=L;}catch(K){this.lastError=K;return L;}}return M.time;},stopEvent:function(K){this.stopPropagation(K);this.preventDefault(K);},stopPropagation:function(K){if(K.stopPropagation){K.stopPropagation();}else{K.cancelBubble=true;}},preventDefault:function(K){if(K.preventDefault){K.preventDefault();}else{K.returnValue=false;}},getEvent:function(M,K){var L=M||window.event;if(!L){var N=this.getEvent.caller;while(N){L=N.arguments[0];if(L&&Event==L.constructor){break;}N=N.caller;}}return L;},getCharCode:function(L){var K=L.keyCode||L.charCode||0;if(YAHOO.env.ua.webkit&&(K in C)){K=C[K];}return K;},_getCacheIndex:function(M,P,Q,O){for(var N=0,L=M.length;N<L;N=N+1){var K=M[N];if(K&&K[this.FN]==O&&K[this.EL]==P&&K[this.TYPE]==Q){return N;}}return -1;},generateId:function(K){var L=K.id;if(!L){L="yuievtautoid-"+B;++B;K.id=L;}return L;},_isValidCollection:function(L){try{return(L&&typeof L!=="string"&&L.length&&!L.tagName&&!L.alert&&typeof L[0]!=="undefined");}catch(K){return false;}},elCache:{},getEl:function(K){return(typeof K==="string")?document.getElementById(K):K;},clearCache:function(){},DOMReadyEvent:new YAHOO.util.CustomEvent("DOMReady",YAHOO,0,0,1),_load:function(L){if(!G){G=true;var K=YAHOO.util.Event;K._ready();K._tryPreloadAttach();}},_ready:function(L){var K=YAHOO.util.Event;if(!K.DOMReady){K.DOMReady=true;K.DOMReadyEvent.fire();K._simpleRemove(document,"DOMContentLoaded",K._ready);}},_tryPreloadAttach:function(){if(E.length===0){A=0;if(this._interval){this._interval.cancel();this._interval=null;}return;}if(this.locked){return;}if(this.isIE){if(!this.DOMReady){this.startInterval();return;}}this.locked=true;var Q=!G;if(!Q){Q=(A>0&&E.length>0);}var P=[];var R=function(T,U){var S=T;if(U.overrideContext){if(U.overrideContext===true){S=U.obj;}else{S=U.overrideContext;}}U.fn.call(S,U.obj);};var L,K,O,N,M=[];for(L=0,K=E.length;L<K;L=L+1){O=E[L];if(O){N=this.getEl(O.id);if(N){if(O.checkReady){if(G||N.nextSibling||!Q){M.push(O);E[L]=null;}}else{R(N,O);E[L]=null;}}else{P.push(O);}}}for(L=0,K=M.length;L<K;L=L+1){O=M[L];R(this.getEl(O.id),O);}A--;if(Q){for(L=E.length-1;L>-1;L--){O=E[L];if(!O||!O.id){E.splice(L,1);}}this.startInterval();}else{if(this._interval){this._interval.cancel();this._interval=null;}}this.locked=false;},purgeElement:function(O,P,R){var M=(YAHOO.lang.isString(O))?this.getEl(O):O;var Q=this.getListeners(M,R),N,K;if(Q){for(N=Q.length-1;N>-1;N--){var L=Q[N];this.removeListener(M,L.type,L.fn);}}if(P&&M&&M.childNodes){for(N=0,K=M.childNodes.length;N<K;++N){this.purgeElement(M.childNodes[N],P,R);}}},getListeners:function(M,K){var P=[],L;if(!K){L=[H,J];}else{if(K==="unload"){L=[J];}else{K=this._getType(K);L=[H];}}var R=(YAHOO.lang.isString(M))?this.getEl(M):M;for(var O=0;O<L.length;O=O+1){var T=L[O];if(T){for(var Q=0,S=T.length;Q<S;++Q){var N=T[Q];if(N&&N[this.EL]===R&&(!K||K===N[this.TYPE])){P.push({type:N[this.TYPE],fn:N[this.FN],obj:N[this.OBJ],adjust:N[this.OVERRIDE],scope:N[this.ADJ_SCOPE],index:Q});}}}}return(P.length)?P:null;},_unload:function(R){var L=YAHOO.util.Event,O,N,M,Q,P,S=J.slice(),K;for(O=0,Q=J.length;O<Q;++O){M=S[O];if(M){K=window;if(M[L.ADJ_SCOPE]){if(M[L.ADJ_SCOPE]===true){K=M[L.UNLOAD_OBJ];}else{K=M[L.ADJ_SCOPE];}}M[L.FN].call(K,L.getEvent(R,M[L.EL]),M[L.UNLOAD_OBJ]);S[O]=null;}}M=null;K=null;J=null;if(H){for(N=H.length-1;N>-1;N--){M=H[N];if(M){L.removeListener(M[L.EL],M[L.TYPE],M[L.FN],N);}}M=null;}L._simpleRemove(window,"unload",L._unload);},_getScrollLeft:function(){return this._getScroll()[1];},_getScrollTop:function(){return this._getScroll()[0];},_getScroll:function(){var K=document.documentElement,L=document.body;if(K&&(K.scrollTop||K.scrollLeft)){return[K.scrollTop,K.scrollLeft];}else{if(L){return[L.scrollTop,L.scrollLeft];}else{return[0,0];}}},regCE:function(){},_simpleAdd:function(){if(window.addEventListener){return function(M,N,L,K){M.addEventListener(N,L,(K));};}else{if(window.attachEvent){return function(M,N,L,K){M.attachEvent("on"+N,L);};}else{return function(){};}}}(),_simpleRemove:function(){if(window.removeEventListener){return function(M,N,L,K){M.removeEventListener(N,L,(K));};}else{if(window.detachEvent){return function(L,M,K){L.detachEvent("on"+M,K);};}else{return function(){};}}}()};}();(function(){var EU=YAHOO.util.Event;EU.on=EU.addListener;EU.onFocus=EU.addFocusListener;EU.onBlur=EU.addBlurListener;
+/* DOMReady: based on work by: Dean Edwards/John Resig/Matthias Miller/Diego Perini */
+if(EU.isIE){if(self!==self.top){document.onreadystatechange=function(){if(document.readyState=="complete"){document.onreadystatechange=null;EU._ready();}};}else{YAHOO.util.Event.onDOMReady(YAHOO.util.Event._tryPreloadAttach,YAHOO.util.Event,true);var n=document.createElement("p");EU._dri=setInterval(function(){try{n.doScroll("left");clearInterval(EU._dri);EU._dri=null;EU._ready();n=null;}catch(ex){}},EU.POLL_INTERVAL);}}else{if(EU.webkit&&EU.webkit<525){EU._dri=setInterval(function(){var rs=document.readyState;if("loaded"==rs||"complete"==rs){clearInterval(EU._dri);EU._dri=null;EU._ready();}},EU.POLL_INTERVAL);}else{EU._simpleAdd(document,"DOMContentLoaded",EU._ready);}}EU._simpleAdd(window,"load",EU._load);EU._simpleAdd(window,"unload",EU._unload);EU._tryPreloadAttach();})();}YAHOO.util.EventProvider=function(){};YAHOO.util.EventProvider.prototype={__yui_events:null,__yui_subscribers:null,subscribe:function(A,C,F,E){this.__yui_events=this.__yui_events||{};var D=this.__yui_events[A];if(D){D.subscribe(C,F,E);}else{this.__yui_subscribers=this.__yui_subscribers||{};var B=this.__yui_subscribers;if(!B[A]){B[A]=[];}B[A].push({fn:C,obj:F,overrideContext:E});}},unsubscribe:function(C,E,G){this.__yui_events=this.__yui_events||{};var A=this.__yui_events;if(C){var F=A[C];if(F){return F.unsubscribe(E,G);}}else{var B=true;for(var D in A){if(YAHOO.lang.hasOwnProperty(A,D)){B=B&&A[D].unsubscribe(E,G);}}return B;}return false;},unsubscribeAll:function(A){return this.unsubscribe(A);
+},createEvent:function(B,G){this.__yui_events=this.__yui_events||{};var E=G||{},D=this.__yui_events,F;if(D[B]){}else{F=new YAHOO.util.CustomEvent(B,E.scope||this,E.silent,YAHOO.util.CustomEvent.FLAT,E.fireOnce);D[B]=F;if(E.onSubscribeCallback){F.subscribeEvent.subscribe(E.onSubscribeCallback);}this.__yui_subscribers=this.__yui_subscribers||{};var A=this.__yui_subscribers[B];if(A){for(var C=0;C<A.length;++C){F.subscribe(A[C].fn,A[C].obj,A[C].overrideContext);}}}return D[B];},fireEvent:function(B){this.__yui_events=this.__yui_events||{};var D=this.__yui_events[B];if(!D){return null;}var A=[];for(var C=1;C<arguments.length;++C){A.push(arguments[C]);}return D.fire.apply(D,A);},hasEvent:function(A){if(this.__yui_events){if(this.__yui_events[A]){return true;}}return false;}};(function(){var A=YAHOO.util.Event,C=YAHOO.lang;YAHOO.util.KeyListener=function(D,I,E,F){if(!D){}else{if(!I){}else{if(!E){}}}if(!F){F=YAHOO.util.KeyListener.KEYDOWN;}var G=new YAHOO.util.CustomEvent("keyPressed");this.enabledEvent=new YAHOO.util.CustomEvent("enabled");this.disabledEvent=new YAHOO.util.CustomEvent("disabled");if(C.isString(D)){D=document.getElementById(D);}if(C.isFunction(E)){G.subscribe(E);}else{G.subscribe(E.fn,E.scope,E.correctScope);}function H(O,N){if(!I.shift){I.shift=false;}if(!I.alt){I.alt=false;}if(!I.ctrl){I.ctrl=false;}if(O.shiftKey==I.shift&&O.altKey==I.alt&&O.ctrlKey==I.ctrl){var J,M=I.keys,L;if(YAHOO.lang.isArray(M)){for(var K=0;K<M.length;K++){J=M[K];L=A.getCharCode(O);if(J==L){G.fire(L,O);break;}}}else{L=A.getCharCode(O);if(M==L){G.fire(L,O);}}}}this.enable=function(){if(!this.enabled){A.on(D,F,H);this.enabledEvent.fire(I);}this.enabled=true;};this.disable=function(){if(this.enabled){A.removeListener(D,F,H);this.disabledEvent.fire(I);}this.enabled=false;};this.toString=function(){return"KeyListener ["+I.keys+"] "+D.tagName+(D.id?"["+D.id+"]":"");};};var B=YAHOO.util.KeyListener;B.KEYDOWN="keydown";B.KEYUP="keyup";B.KEY={ALT:18,BACK_SPACE:8,CAPS_LOCK:20,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,META:224,NUM_LOCK:144,PAGE_DOWN:34,PAGE_UP:33,PAUSE:19,PRINTSCREEN:44,RIGHT:39,SCROLL_LOCK:145,SHIFT:16,SPACE:32,TAB:9,UP:38};})();YAHOO.register("event",YAHOO.util.Event,{version:"2.8.2r1",build:"7"}); \ No newline at end of file
diff --git a/js/yui/fonts/fonts-min.css b/js/yui/fonts/fonts-min.css
new file mode 100644
index 000000000..3e0f84ee7
--- /dev/null
+++ b/js/yui/fonts/fonts-min.css
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+body{font:13px/1.231 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small;}select,input,button,textarea,button{font:99% arial,helvetica,clean,sans-serif;}table{font-size:inherit;font:100%;}pre,code,kbd,samp,tt{font-family:monospace;*font-size:108%;line-height:100%;} \ No newline at end of file
diff --git a/js/yui/fonts/fonts.css b/js/yui/fonts/fonts.css
new file mode 100644
index 000000000..7ee78d549
--- /dev/null
+++ b/js/yui/fonts/fonts.css
@@ -0,0 +1,56 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+/**
+ * YUI Fonts
+ * @module fonts
+ * @namespace yui-
+ * @requires
+ */
+
+/**
+ * Percents could work for IE, but for backCompat purposes, we are using keywords.
+ * x-small is for IE6/7 quirks mode.
+ */
+body {
+ font:13px/1.231 arial,helvetica,clean,sans-serif;
+ /* for IE6/7 */
+ *font-size:small;
+ /* for IE Quirks Mode */
+ *font:x-small;
+}
+
+/**
+ * Nudge down to get to 13px equivalent for these form elements
+ */
+select,
+input,
+button,
+textarea,
+button {
+ font:99% arial,helvetica,clean,sans-serif;
+}
+
+/**
+ * To help tables remember to inherit
+ */
+table {
+ font-size:inherit;
+ font:100%;
+}
+
+/**
+ * Bump up IE to get to 13px equivalent for these fixed-width elements
+ */
+pre,
+code,
+kbd,
+samp,
+tt {
+ font-family:monospace;
+ *font-size:108%;
+ line-height:100%;
+}
diff --git a/js/yui/get/get-min.js b/js/yui/get/get-min.js
new file mode 100644
index 000000000..7b45eca37
--- /dev/null
+++ b/js/yui/get/get-min.js
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+YAHOO.util.Get=function(){var M={},L=0,R=0,E=false,N=YAHOO.env.ua,S=YAHOO.lang;var J=function(W,T,X){var U=X||window,Y=U.document,Z=Y.createElement(W);for(var V in T){if(T[V]&&YAHOO.lang.hasOwnProperty(T,V)){Z.setAttribute(V,T[V]);}}return Z;};var I=function(U,V,T){var W={id:"yui__dyn_"+(R++),type:"text/css",rel:"stylesheet",href:U};if(T){S.augmentObject(W,T);}return J("link",W,V);};var P=function(U,V,T){var W={id:"yui__dyn_"+(R++),type:"text/javascript",src:U};if(T){S.augmentObject(W,T);}return J("script",W,V);};var A=function(T,U){return{tId:T.tId,win:T.win,data:T.data,nodes:T.nodes,msg:U,purge:function(){D(this.tId);}};};var B=function(T,W){var U=M[W],V=(S.isString(T))?U.win.document.getElementById(T):T;if(!V){Q(W,"target node not found: "+T);}return V;};var Q=function(W,V){var T=M[W];if(T.onFailure){var U=T.scope||T.win;T.onFailure.call(U,A(T,V));}};var C=function(W){var T=M[W];T.finished=true;if(T.aborted){var V="transaction "+W+" was aborted";Q(W,V);return;}if(T.onSuccess){var U=T.scope||T.win;T.onSuccess.call(U,A(T));}};var O=function(V){var T=M[V];if(T.onTimeout){var U=T.scope||T;T.onTimeout.call(U,A(T));}};var G=function(V,Z){var U=M[V];if(U.timer){U.timer.cancel();}if(U.aborted){var X="transaction "+V+" was aborted";Q(V,X);return;}if(Z){U.url.shift();if(U.varName){U.varName.shift();}}else{U.url=(S.isString(U.url))?[U.url]:U.url;if(U.varName){U.varName=(S.isString(U.varName))?[U.varName]:U.varName;}}var c=U.win,b=c.document,a=b.getElementsByTagName("head")[0],W;if(U.url.length===0){if(U.type==="script"&&N.webkit&&N.webkit<420&&!U.finalpass&&!U.varName){var Y=P(null,U.win,U.attributes);Y.innerHTML='YAHOO.util.Get._finalize("'+V+'");';U.nodes.push(Y);a.appendChild(Y);}else{C(V);}return;}var T=U.url[0];if(!T){U.url.shift();return G(V);}if(U.timeout){U.timer=S.later(U.timeout,U,O,V);}if(U.type==="script"){W=P(T,c,U.attributes);}else{W=I(T,c,U.attributes);}F(U.type,W,V,T,c,U.url.length);U.nodes.push(W);if(U.insertBefore){var e=B(U.insertBefore,V);if(e){e.parentNode.insertBefore(W,e);}}else{a.appendChild(W);}if((N.webkit||N.gecko)&&U.type==="css"){G(V,T);}};var K=function(){if(E){return;}E=true;for(var T in M){var U=M[T];if(U.autopurge&&U.finished){D(U.tId);delete M[T];}}E=false;};var D=function(Z){if(M[Z]){var T=M[Z],U=T.nodes,X=U.length,c=T.win.document,a=c.getElementsByTagName("head")[0],V,Y,W,b;if(T.insertBefore){V=B(T.insertBefore,Z);if(V){a=V.parentNode;}}for(Y=0;Y<X;Y=Y+1){W=U[Y];if(W.clearAttributes){W.clearAttributes();}else{for(b in W){delete W[b];}}a.removeChild(W);}T.nodes=[];}};var H=function(U,T,V){var X="q"+(L++);V=V||{};if(L%YAHOO.util.Get.PURGE_THRESH===0){K();}M[X]=S.merge(V,{tId:X,type:U,url:T,finished:false,aborted:false,nodes:[]});var W=M[X];W.win=W.win||window;W.scope=W.scope||W.win;W.autopurge=("autopurge" in W)?W.autopurge:(U==="script")?true:false;if(V.charset){W.attributes=W.attributes||{};W.attributes.charset=V.charset;}S.later(0,W,G,X);return{tId:X};};var F=function(c,X,W,U,Y,Z,b){var a=b||G;if(N.ie){X.onreadystatechange=function(){var d=this.readyState;if("loaded"===d||"complete"===d){X.onreadystatechange=null;a(W,U);}};}else{if(N.webkit){if(c==="script"){if(N.webkit>=420){X.addEventListener("load",function(){a(W,U);});}else{var T=M[W];if(T.varName){var V=YAHOO.util.Get.POLL_FREQ;T.maxattempts=YAHOO.util.Get.TIMEOUT/V;T.attempts=0;T._cache=T.varName[0].split(".");T.timer=S.later(V,T,function(j){var f=this._cache,e=f.length,d=this.win,g;for(g=0;g<e;g=g+1){d=d[f[g]];if(!d){this.attempts++;if(this.attempts++>this.maxattempts){var h="Over retry limit, giving up";T.timer.cancel();Q(W,h);}else{}return;}}T.timer.cancel();a(W,U);},null,true);}else{S.later(YAHOO.util.Get.POLL_FREQ,null,a,[W,U]);}}}}else{X.onload=function(){a(W,U);};}}};return{POLL_FREQ:10,PURGE_THRESH:20,TIMEOUT:2000,_finalize:function(T){S.later(0,null,C,T);},abort:function(U){var V=(S.isString(U))?U:U.tId;var T=M[V];if(T){T.aborted=true;}},script:function(T,U){return H("script",T,U);},css:function(T,U){return H("css",T,U);}};}();YAHOO.register("get",YAHOO.util.Get,{version:"2.8.2r1",build:"7"}); \ No newline at end of file
diff --git a/js/yui/grids/grids-min.css b/js/yui/grids/grids-min.css
new file mode 100644
index 000000000..12581db57
--- /dev/null
+++ b/js/yui/grids/grids-min.css
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+body{text-align:center;}#doc,#doc2,#doc3,#doc4,.yui-t1,.yui-t2,.yui-t3,.yui-t4,.yui-t5,.yui-t6,.yui-t7{margin:auto;text-align:left;width:57.69em;*width:56.25em;}#doc2{width:73.076em;*width:71.25em;}#doc3{margin:auto 10px;width:auto;}#doc4{width:74.923em;*width:73.05em;}.yui-b{position:relative;}.yui-b{_position:static;}#yui-main .yui-b{position:static;}#yui-main,.yui-g .yui-u .yui-g{width:100%;}.yui-t1 #yui-main,.yui-t2 #yui-main,.yui-t3 #yui-main{float:right;margin-left:-25em;}.yui-t4 #yui-main,.yui-t5 #yui-main,.yui-t6 #yui-main{float:left;margin-right:-25em;}.yui-t1 .yui-b{float:left;width:12.30769em;*width:12.00em;}.yui-t1 #yui-main .yui-b{margin-left:13.30769em;*margin-left:13.05em;}.yui-t2 .yui-b{float:left;width:13.8461em;*width:13.50em;}.yui-t2 #yui-main .yui-b{margin-left:14.8461em;*margin-left:14.55em;}.yui-t3 .yui-b{float:left;width:23.0769em;*width:22.50em;}.yui-t3 #yui-main .yui-b{margin-left:24.0769em;*margin-left:23.62em;}.yui-t4 .yui-b{float:right;width:13.8456em;*width:13.50em;}.yui-t4 #yui-main .yui-b{margin-right:14.8456em;*margin-right:14.55em;}.yui-t5 .yui-b{float:right;width:18.4615em;*width:18.00em;}.yui-t5 #yui-main .yui-b{margin-right:19.4615em;*margin-right:19.125em;}.yui-t6 .yui-b{float:right;width:23.0769em;*width:22.50em;}.yui-t6 #yui-main .yui-b{margin-right:24.0769em;*margin-right:23.62em;}.yui-t7 #yui-main .yui-b{display:block;margin:0 0 1em 0;}#yui-main .yui-b{float:none;width:auto;}.yui-gb .yui-u,.yui-g .yui-gb .yui-u,.yui-gb .yui-g,.yui-gb .yui-gb,.yui-gb .yui-gc,.yui-gb .yui-gd,.yui-gb .yui-ge,.yui-gb .yui-gf,.yui-gc .yui-u,.yui-gc .yui-g,.yui-gd .yui-u{float:left;}.yui-g .yui-u,.yui-g .yui-g,.yui-g .yui-gb,.yui-g .yui-gc,.yui-g .yui-gd,.yui-g .yui-ge,.yui-g .yui-gf,.yui-gc .yui-u,.yui-gd .yui-g,.yui-g .yui-gc .yui-u,.yui-ge .yui-u,.yui-ge .yui-g,.yui-gf .yui-g,.yui-gf .yui-u{float:right;}.yui-g div.first,.yui-gb div.first,.yui-gc div.first,.yui-gd div.first,.yui-ge div.first,.yui-gf div.first,.yui-g .yui-gc div.first,.yui-g .yui-ge div.first,.yui-gc div.first div.first{float:left;}.yui-g .yui-u,.yui-g .yui-g,.yui-g .yui-gb,.yui-g .yui-gc,.yui-g .yui-gd,.yui-g .yui-ge,.yui-g .yui-gf{width:49.1%;}.yui-gb .yui-u,.yui-g .yui-gb .yui-u,.yui-gb .yui-g,.yui-gb .yui-gb,.yui-gb .yui-gc,.yui-gb .yui-gd,.yui-gb .yui-ge,.yui-gb .yui-gf,.yui-gc .yui-u,.yui-gc .yui-g,.yui-gd .yui-u{width:32%;margin-left:1.99%;}.yui-gb .yui-u{*margin-left:1.9%;*width:31.9%;}.yui-gc div.first,.yui-gd .yui-u{width:66%;}.yui-gd div.first{width:32%;}.yui-ge div.first,.yui-gf .yui-u{width:74.2%;}.yui-ge .yui-u,.yui-gf div.first{width:24%;}.yui-g .yui-gb div.first,.yui-gb div.first,.yui-gc div.first,.yui-gd div.first{margin-left:0;}.yui-g .yui-g .yui-u,.yui-gb .yui-g .yui-u,.yui-gc .yui-g .yui-u,.yui-gd .yui-g .yui-u,.yui-ge .yui-g .yui-u,.yui-gf .yui-g .yui-u{width:49%;*width:48.1%;*margin-left:0;}.yui-g .yui-g .yui-u{width:48.1%;}.yui-g .yui-gb div.first,.yui-gb .yui-gb div.first{*margin-right:0;*width:32%;_width:31.7%;}.yui-g .yui-gc div.first,.yui-gd .yui-g{width:66%;}.yui-gb .yui-g div.first{*margin-right:4%;_margin-right:1.3%;}.yui-gb .yui-gc div.first,.yui-gb .yui-gd div.first{*margin-right:0;}.yui-gb .yui-gb .yui-u,.yui-gb .yui-gc .yui-u{*margin-left:1.8%;_margin-left:4%;}.yui-g .yui-gb .yui-u{_margin-left:1.0%;}.yui-gb .yui-gd .yui-u{*width:66%;_width:61.2%;}.yui-gb .yui-gd div.first{*width:31%;_width:29.5%;}.yui-g .yui-gc .yui-u,.yui-gb .yui-gc .yui-u{width:32%;_float:right;margin-right:0;_margin-left:0;}.yui-gb .yui-gc div.first{width:66%;*float:left;*margin-left:0;}.yui-gb .yui-ge .yui-u,.yui-gb .yui-gf .yui-u{margin:0;}.yui-gb .yui-gb .yui-u{_margin-left:.7%;}.yui-gb .yui-g div.first,.yui-gb .yui-gb div.first{*margin-left:0;}.yui-gc .yui-g .yui-u,.yui-gd .yui-g .yui-u{*width:48.1%;*margin-left:0;}.yui-gb .yui-gd div.first{width:32%;}.yui-g .yui-gd div.first{_width:29.9%;}.yui-ge .yui-g{width:24%;}.yui-gf .yui-g{width:74.2%;}.yui-gb .yui-ge div.yui-u,.yui-gb .yui-gf div.yui-u{float:right;}.yui-gb .yui-ge div.first,.yui-gb .yui-gf div.first{float:left;}.yui-gb .yui-ge .yui-u,.yui-gb .yui-gf div.first{*width:24%;_width:20%;}.yui-gb .yui-ge div.first,.yui-gb .yui-gf .yui-u{*width:73.5%;_width:65.5%;}.yui-ge div.first .yui-gd .yui-u{width:65%;}.yui-ge div.first .yui-gd div.first{width:32%;}#hd:after,#bd:after,#ft:after,.yui-g:after,.yui-gb:after,.yui-gc:after,.yui-gd:after,.yui-ge:after,.yui-gf:after{content:".";display:block;height:0;clear:both;visibility:hidden;}#hd,#bd,#ft,.yui-g,.yui-gb,.yui-gc,.yui-gd,.yui-ge,.yui-gf{zoom:1;} \ No newline at end of file
diff --git a/js/yui/grids/grids.css b/js/yui/grids/grids.css
new file mode 100644
index 000000000..fb64196a7
--- /dev/null
+++ b/js/yui/grids/grids.css
@@ -0,0 +1,467 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+/**
+ * YUI Grids
+ * @module grids
+ * @namespace yui-
+ * @requires reset, fonts
+ */
+
+/**
+ * Note: Throughout this file, the *property (star-property) filter is used
+ * to give a value to IE that other browsers do not see. _property is only seen
+ * by IE7, so the combo of *prop and _prop can target between IE6 and IE7.
+ *
+ * More information on these filters and related validation errors:
+ * http://tech.groups.yahoo.com/group/ydn-javascript/message/40059
+ */
+
+/**
+ * Section: General Rules
+ */
+
+body {
+ text-align: center;
+}
+
+/**
+ * Section: Page Width Rules (#doc, #doc2, #doc3, #doc4)
+ */
+
+#doc,#doc2,#doc3,#doc4,
+.yui-t1,.yui-t2,.yui-t3,.yui-t4,.yui-t5,.yui-t6,.yui-t7 {
+ margin: auto;
+ text-align: left;
+ width: 57.69em;
+ *width: 56.25em;
+}
+
+/* 950 Centered (doc2) */
+#doc2 {
+ width: 73.076em;
+ *width: 71.25em;
+}
+
+/* 100% (doc3) */
+#doc3 {
+/**
+ * Left and Right margins are not a structural part of Grids. Without them
+ * Grids works fine, but content bleeds to the very edge of the document, which
+ * often impairs readability and usability. They are provided because they
+ * prevent the content from "bleeding" into the browser's chrome.
+ */
+ margin: auto 10px;
+ width: auto;
+}
+
+/* 974 Centered (doc4) */
+#doc4 {
+ width: 74.923em;
+ *width: 73.05em;
+}
+
+/**
+ * Section: Preset Template Rules (.yui-t[1-6])
+ */
+
+
+.yui-b {
+ /* to preserve source-order independence for Gecko */
+ position: relative;
+}
+
+.yui-b {
+ /* to preserve source-order independence for IE */
+ _position: static;
+}
+
+#yui-main .yui-b {
+ /* to preserve source-order independence for Gecko */
+ position: static;
+}
+
+#yui-main,
+.yui-g .yui-u .yui-g {
+ width: 100%;
+}
+
+.yui-t1 #yui-main,
+.yui-t2 #yui-main,
+.yui-t3 #yui-main {
+ float: right;
+ /* IE: preserve layout at narrow widths */
+ margin-left: -25em;
+}
+
+.yui-t4 #yui-main,
+.yui-t5 #yui-main,
+.yui-t6 #yui-main {
+ float: left;
+ /* IE: preserve layout at narrow widths */
+ margin-right: -25em;
+}
+
+/**
+ * For Specific Template Presets
+ */
+
+.yui-t1 .yui-b {
+ float: left;
+ width: 12.30769em;
+ *width: 12.00em;
+}
+
+.yui-t1 #yui-main .yui-b {
+ margin-left: 13.30769em;
+ *margin-left: 13.05em;
+}
+
+.yui-t2 .yui-b {
+ float: left;
+ width: 13.8461em;
+ *width: 13.50em;
+}
+
+.yui-t2 #yui-main .yui-b {
+ margin-left: 14.8461em;
+ *margin-left: 14.55em;
+}
+
+.yui-t3 .yui-b {
+ float: left;
+ width: 23.0769em;
+ *width: 22.50em;
+}
+
+.yui-t3 #yui-main .yui-b {
+ margin-left: 24.0769em;
+ *margin-left: 23.62em;
+}
+
+.yui-t4 .yui-b {
+ float: right;
+ width: 13.8456em;
+ *width: 13.50em;
+}
+
+.yui-t4 #yui-main .yui-b {
+ margin-right: 14.8456em;
+ *margin-right: 14.55em;
+}
+
+.yui-t5 .yui-b {
+ float: right;
+ width: 18.4615em;
+ *width: 18.00em;
+}
+
+.yui-t5 #yui-main .yui-b {
+ margin-right: 19.4615em;
+ *margin-right: 19.125em;
+}
+
+.yui-t6 .yui-b {
+ float: right;
+ width: 23.0769em;
+ *width: 22.50em;
+}
+
+.yui-t6 #yui-main .yui-b {
+ margin-right: 24.0769em;
+ *margin-right: 23.62em;
+}
+
+.yui-t7 #yui-main .yui-b {
+ display: block;
+ margin: 0 0 1em 0;
+}
+
+#yui-main .yui-b {
+ float: none;
+ width: auto;
+}
+
+/**
+ * Section: Grids and Nesting Grids
+ */
+
+/* Children generally take half the available space */
+.yui-gb .yui-u,
+.yui-g .yui-gb .yui-u,
+.yui-gb .yui-g,
+.yui-gb .yui-gb,
+.yui-gb .yui-gc,
+.yui-gb .yui-gd,
+.yui-gb .yui-ge,
+.yui-gb .yui-gf,
+.yui-gc .yui-u,
+.yui-gc .yui-g,
+.yui-gd .yui-u {
+ float: left;
+}
+
+/* Float units (and sub grids) to the right */
+.yui-g .yui-u,
+.yui-g .yui-g,
+.yui-g .yui-gb,
+.yui-g .yui-gc,
+.yui-g .yui-gd,
+.yui-g .yui-ge,
+.yui-g .yui-gf,
+.yui-gc .yui-u,
+.yui-gd .yui-g,
+.yui-g .yui-gc .yui-u,
+.yui-ge .yui-u,
+.yui-ge .yui-g,
+.yui-gf .yui-g,
+.yui-gf .yui-u {
+ float: right;
+}
+
+/*Float units (and sub grids) to the left */
+.yui-g div.first,
+.yui-gb div.first,
+.yui-gc div.first,
+.yui-gd div.first,
+.yui-ge div.first,
+.yui-gf div.first,
+.yui-g .yui-gc div.first,
+.yui-g .yui-ge div.first,
+.yui-gc div.first div.first {
+ float: left;
+}
+
+.yui-g .yui-u,
+.yui-g .yui-g,
+.yui-g .yui-gb,
+.yui-g .yui-gc,
+.yui-g .yui-gd,
+.yui-g .yui-ge,
+.yui-g .yui-gf {
+ width: 49.1%;
+}
+
+.yui-gb .yui-u,
+.yui-g .yui-gb .yui-u,
+.yui-gb .yui-g,
+.yui-gb .yui-gb,
+.yui-gb .yui-gc,
+.yui-gb .yui-gd,
+.yui-gb .yui-ge,
+.yui-gb .yui-gf,
+.yui-gc .yui-u,
+.yui-gc .yui-g,
+.yui-gd .yui-u {
+ width: 32%;
+ margin-left: 1.99%;
+}
+
+/* Give IE some extra breathing room for 1/3-based rounding issues */
+.yui-gb .yui-u {
+ *margin-left: 1.9%;
+ *width: 31.9%;
+}
+
+.yui-gc div.first,
+ .yui-gd .yui-u {
+ width: 66%;
+}
+
+.yui-gd div.first {
+ width: 32%;
+}
+
+.yui-ge div.first,
+ .yui-gf .yui-u {
+ width: 74.2%;
+}
+
+.yui-ge .yui-u,
+ .yui-gf div.first {
+ width: 24%;
+}
+
+.yui-g .yui-gb div.first,
+.yui-gb div.first,
+.yui-gc div.first,
+.yui-gd div.first {
+ margin-left: 0;
+}
+
+/**
+ * Section: Deep Nesting
+ */
+
+.yui-g .yui-g .yui-u,
+.yui-gb .yui-g .yui-u,
+.yui-gc .yui-g .yui-u,
+.yui-gd .yui-g .yui-u,
+.yui-ge .yui-g .yui-u,
+.yui-gf .yui-g .yui-u {
+ width: 49%;
+ *width: 48.1%;
+ *margin-left: 0;
+}
+
+.yui-g .yui-g .yui-u {
+ width: 48.1%;
+}
+
+/*YUILibrary bug #1927599 from 1.14 to 2.6.0*/
+.yui-g .yui-gb div.first,
+ .yui-gb .yui-gb div.first {
+ *margin-right: 0;
+ *width: 32%;
+ _width: 31.7%;
+}
+
+.yui-g .yui-gc div.first,
+ .yui-gd .yui-g {
+ width: 66%;
+}
+
+.yui-gb .yui-g div.first {
+ *margin-right: 4%;
+ _margin-right: 1.3%;
+}
+
+.yui-gb .yui-gc div.first,
+ .yui-gb .yui-gd div.first {
+ *margin-right: 0;
+}
+
+.yui-gb .yui-gb .yui-u,
+ .yui-gb .yui-gc .yui-u {
+ *margin-left: 1.8%;
+ _margin-left: 4%;
+}
+
+.yui-g .yui-gb .yui-u {
+ _margin-left: 1.0%;
+}
+
+.yui-gb .yui-gd .yui-u {
+ *width: 66%;
+ _width: 61.2%;
+}
+
+.yui-gb .yui-gd div.first {
+ *width: 31%;
+ _width: 29.5%;
+}
+
+.yui-g .yui-gc .yui-u,
+ .yui-gb .yui-gc .yui-u {
+ width: 32%;
+ _float: right;
+ margin-right: 0;
+ _margin-left: 0;
+}
+
+.yui-gb .yui-gc div.first {
+ width: 66%;
+ *float: left;
+ *margin-left: 0;
+}
+
+.yui-gb .yui-ge .yui-u,
+ .yui-gb .yui-gf .yui-u {
+ margin: 0;
+}
+
+.yui-gb .yui-gb .yui-u {
+ _margin-left: .7%;
+}
+
+.yui-gb .yui-g div.first,
+ .yui-gb .yui-gb div.first {
+ *margin-left: 0;
+}
+
+.yui-gc .yui-g .yui-u,
+ .yui-gd .yui-g .yui-u {
+ *width: 48.1%;
+ *margin-left: 0;
+}
+
+.yui-gb .yui-gd div.first {
+ width: 32%;
+}
+
+.yui-g .yui-gd div.first {
+ _width: 29.9%;
+}
+
+.yui-ge .yui-g {
+ width: 24%;
+}
+
+.yui-gf .yui-g {
+ width: 74.2%;
+}
+
+.yui-gb .yui-ge div.yui-u,
+ .yui-gb .yui-gf div.yui-u {
+ float: right;
+}
+
+.yui-gb .yui-ge div.first,
+ .yui-gb .yui-gf div.first {
+ float: left;
+}
+
+/* Width Accommodation for Nested Contexts */
+.yui-gb .yui-ge .yui-u,
+ .yui-gb .yui-gf div.first {
+ *width: 24%;
+ _width: 20%;
+}
+
+/* Width Accommodation for Nested Contexts */
+.yui-gb .yui-ge div.first,
+ .yui-gb .yui-gf .yui-u {
+ *width: 73.5%;
+ _width: 65.5%;
+}
+
+/* Patch for GD within GE */
+.yui-ge div.first .yui-gd .yui-u {
+ width: 65%;
+}
+
+.yui-ge div.first .yui-gd div.first {
+ width: 32%;
+}
+
+/* @group Clearing */
+#hd:after,
+#bd:after,
+#ft:after,
+.yui-g:after,
+.yui-gb:after,
+.yui-gc:after,
+.yui-gd:after,
+.yui-ge:after,
+.yui-gf:after {
+ content: ".";
+ display: block;
+ height: 0;
+ clear: both;
+ visibility: hidden;
+}
+
+#hd,
+#bd,
+#ft,
+.yui-g,
+.yui-gb,
+.yui-gc,
+.yui-gd,
+.yui-ge,
+.yui-gf {
+ zoom: 1;
+}
diff --git a/js/yui/history/history-min.js b/js/yui/history/history-min.js
new file mode 100644
index 000000000..9de6de454
--- /dev/null
+++ b/js/yui/history/history-min.js
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+YAHOO.util.History=(function(){var C=null;var K=null;var F=false;var D=[];var B=[];function I(){var M,L;L=top.location.href;M=L.indexOf("#");return M>=0?L.substr(M+1):null;}function A(){var M,N,O=[],L=[];for(M in D){if(YAHOO.lang.hasOwnProperty(D,M)){N=D[M];O.push(M+"="+N.initialState);L.push(M+"="+N.currentState);}}K.value=O.join("&")+"|"+L.join("&");if(YAHOO.env.ua.webkit){K.value+="|"+B.join(",");}}function H(L){var Q,R,M,O,P,T,S,N;if(!L){for(M in D){if(YAHOO.lang.hasOwnProperty(D,M)){O=D[M];O.currentState=O.initialState;O.onStateChange(unescape(O.currentState));}}return;}P=[];T=L.split("&");for(Q=0,R=T.length;Q<R;Q++){S=T[Q].split("=");if(S.length===2){M=S[0];N=S[1];P[M]=N;}}for(M in D){if(YAHOO.lang.hasOwnProperty(D,M)){O=D[M];N=P[M];if(!N||O.currentState!==N){O.currentState=N||O.initialState;O.onStateChange(unescape(O.currentState));}}}}function J(O){var L,N;L='<html><body><div id="state">'+O.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")+"</div></body></html>";try{N=C.contentWindow.document;N.open();N.write(L);N.close();return true;}catch(M){return false;}}function G(){var O,L,N,M;if(!C.contentWindow||!C.contentWindow.document){setTimeout(G,10);return;}O=C.contentWindow.document;L=O.getElementById("state");N=L?L.innerText:null;M=I();setInterval(function(){var U,Q,R,S,T,P;O=C.contentWindow.document;L=O.getElementById("state");U=L?L.innerText:null;T=I();if(U!==N){N=U;H(N);if(!N){Q=[];for(R in D){if(YAHOO.lang.hasOwnProperty(D,R)){S=D[R];Q.push(R+"="+S.initialState);}}T=Q.join("&");}else{T=N;}top.location.hash=T;M=T;A();}else{if(T!==M){M=T;J(T);}}},50);F=true;YAHOO.util.History.onLoadEvent.fire();}function E(){var S,U,Q,W,M,O,V,P,T,N,L,R;Q=K.value.split("|");if(Q.length>1){V=Q[0].split("&");for(S=0,U=V.length;S<U;S++){W=V[S].split("=");if(W.length===2){M=W[0];P=W[1];O=D[M];if(O){O.initialState=P;}}}T=Q[1].split("&");for(S=0,U=T.length;S<U;S++){W=T[S].split("=");if(W.length>=2){M=W[0];N=W[1];O=D[M];if(O){O.currentState=N;}}}}if(Q.length>2){B=Q[2].split(",");}if(YAHOO.env.ua.ie){if(typeof document.documentMode==="undefined"||document.documentMode<8){G();}else{YAHOO.util.Event.on(top,"hashchange",function(){var X=I();H(X);A();});F=true;YAHOO.util.History.onLoadEvent.fire();}}else{L=history.length;R=I();setInterval(function(){var Z,X,Y;X=I();Y=history.length;if(X!==R){R=X;L=Y;H(R);A();}else{if(Y!==L&&YAHOO.env.ua.webkit){R=X;L=Y;Z=B[L-1];H(Z);A();}}},50);F=true;YAHOO.util.History.onLoadEvent.fire();}}return{onLoadEvent:new YAHOO.util.CustomEvent("onLoad"),onReady:function(L,M,N){if(F){setTimeout(function(){var O=window;if(N){if(N===true){O=M;}else{O=N;}}L.call(O,"onLoad",[],M);},0);}else{YAHOO.util.History.onLoadEvent.subscribe(L,M,N);}},register:function(N,L,P,Q,R){var O,M;if(typeof N!=="string"||YAHOO.lang.trim(N)===""||typeof L!=="string"||typeof P!=="function"){throw new Error("Missing or invalid argument");}if(D[N]){return;}if(F){throw new Error("All modules must be registered before calling YAHOO.util.History.initialize");}N=escape(N);L=escape(L);O=null;if(R===true){O=Q;}else{O=R;}M=function(S){return P.call(O,S,Q);};D[N]={name:N,initialState:L,currentState:L,onStateChange:M};},initialize:function(L,M){if(F){return;}if(YAHOO.env.ua.opera&&typeof history.navigationMode!=="undefined"){history.navigationMode="compatible";}if(typeof L==="string"){L=document.getElementById(L);}if(!L||L.tagName.toUpperCase()!=="TEXTAREA"&&(L.tagName.toUpperCase()!=="INPUT"||L.type!=="hidden"&&L.type!=="text")){throw new Error("Missing or invalid argument");}K=L;if(YAHOO.env.ua.ie&&(typeof document.documentMode==="undefined"||document.documentMode<8)){if(typeof M==="string"){M=document.getElementById(M);}if(!M||M.tagName.toUpperCase()!=="IFRAME"){throw new Error("Missing or invalid argument");}C=M;}YAHOO.util.Event.onDOMReady(E);},navigate:function(M,N){var L;if(typeof M!=="string"||typeof N!=="string"){throw new Error("Missing or invalid argument");}L={};L[M]=N;return YAHOO.util.History.multiNavigate(L);},multiNavigate:function(M){var L,N,P,O,Q;if(typeof M!=="object"){throw new Error("Missing or invalid argument");}if(!F){throw new Error("The Browser History Manager is not initialized");}for(N in M){if(!D[N]){throw new Error("The following module has not been registered: "+N);}}L=[];for(N in D){if(YAHOO.lang.hasOwnProperty(D,N)){P=D[N];if(YAHOO.lang.hasOwnProperty(M,N)){O=M[unescape(N)];}else{O=unescape(P.currentState);}N=escape(N);O=escape(O);L.push(N+"="+O);}}Q=L.join("&");if(YAHOO.env.ua.ie&&(typeof document.documentMode==="undefined"||document.documentMode<8)){return J(Q);}else{top.location.hash=Q;if(YAHOO.env.ua.webkit){B[history.length]=Q;A();}return true;}},getCurrentState:function(L){var M;if(typeof L!=="string"){throw new Error("Missing or invalid argument");}if(!F){throw new Error("The Browser History Manager is not initialized");}M=D[L];if(!M){throw new Error("No such registered module: "+L);}return unescape(M.currentState);},getBookmarkedState:function(Q){var P,M,L,S,N,R,O;if(typeof Q!=="string"){throw new Error("Missing or invalid argument");}L=top.location.href.indexOf("#");if(L>=0){S=top.location.href.substr(L+1);N=S.split("&");for(P=0,M=N.length;P<M;P++){R=N[P].split("=");if(R.length===2){O=R[0];if(O===Q){return unescape(R[1]);}}}}return null;},getQueryStringParameter:function(Q,N){var O,M,L,S,R,P;N=N||top.location.href;L=N.indexOf("?");S=L>=0?N.substr(L+1):N;L=S.lastIndexOf("#");S=L>=0?S.substr(0,L):S;R=S.split("&");for(O=0,M=R.length;O<M;O++){P=R[O].split("=");if(P.length>=2){if(P[0]===Q){return unescape(P[1]);}}}return null;}};})();YAHOO.register("history",YAHOO.util.History,{version:"2.8.2r1",build:"7"}); \ No newline at end of file
diff --git a/js/yui/imagecropper/imagecropper-min.js b/js/yui/imagecropper/imagecropper-min.js
new file mode 100644
index 000000000..1a837f49a
--- /dev/null
+++ b/js/yui/imagecropper/imagecropper-min.js
@@ -0,0 +1,8 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+(function(){var C=YAHOO.util.Dom,A=YAHOO.util.Event,D=YAHOO.lang;var B=function(F,E){var G={element:F,attributes:E||{}};B.superclass.constructor.call(this,G.element,G.attributes);};B._instances={};B.getCropperById=function(E){if(B._instances[E]){return B._instances[E];}return false;};YAHOO.extend(B,YAHOO.util.Element,{CSS_MAIN:"yui-crop",CSS_MASK:"yui-crop-mask",CSS_RESIZE_MASK:"yui-crop-resize-mask",_image:null,_active:null,_resize:null,_resizeEl:null,_resizeMaskEl:null,_wrap:null,_mask:null,_createWrap:function(){this._wrap=document.createElement("div");this._wrap.id=this.get("element").id+"_wrap";this._wrap.className=this.CSS_MAIN;var F=this.get("element");this._wrap.style.width=F.width?F.width+"px":C.getStyle(F,"width");this._wrap.style.height=F.height?F.height+"px":C.getStyle(F,"height");var E=this.get("element").parentNode;E.replaceChild(this._wrap,this.get("element"));this._wrap.appendChild(this.get("element"));A.on(this._wrap,"mouseover",this._handleMouseOver,this,true);A.on(this._wrap,"mouseout",this._handleMouseOut,this,true);A.on(this._wrap,"click",function(G){A.stopEvent(G);},this,true);},_createMask:function(){this._mask=document.createElement("div");this._mask.className=this.CSS_MASK;this._wrap.appendChild(this._mask);},_createResize:function(){this._resizeEl=document.createElement("div");this._resizeEl.className=YAHOO.util.Resize.prototype.CSS_RESIZE;this._resizeEl.style.position="absolute";this._resizeEl.innerHTML='<div class="'+this.CSS_RESIZE_MASK+'"></div>';this._resizeMaskEl=this._resizeEl.firstChild;this._wrap.appendChild(this._resizeEl);this._resizeEl.style.top=this.get("initialXY")[1]+"px";this._resizeEl.style.left=this.get("initialXY")[0]+"px";this._resizeMaskEl.style.height=Math.floor(this.get("initHeight"))+"px";this._resizeMaskEl.style.width=Math.floor(this.get("initWidth"))+"px";this._resize=new YAHOO.util.Resize(this._resizeEl,{knobHandles:true,handles:"all",draggable:true,status:this.get("status"),minWidth:this.get("minWidth"),minHeight:this.get("minHeight"),ratio:this.get("ratio"),autoRatio:this.get("autoRatio"),height:this.get("initHeight"),width:this.get("initWidth")});this._setBackgroundImage(this.get("element").getAttribute("src",2));this._setBackgroundPosition(-(this.get("initialXY")[0]),-(this.get("initialXY")[1]));this._resize.on("startResize",this._handleStartResizeEvent,this,true);this._resize.on("endResize",this._handleEndResizeEvent,this,true);this._resize.on("dragEvent",this._handleDragEvent,this,true);this._resize.on("beforeResize",this._handleBeforeResizeEvent,this,true);this._resize.on("resize",this._handleResizeEvent,this,true);this._resize.dd.on("b4StartDragEvent",this._handleB4DragEvent,this,true);},_handleMouseOver:function(F){var E="keydown";if(YAHOO.env.ua.gecko||YAHOO.env.ua.opera){E="keypress";}if(!this._active){this._active=true;if(this.get("useKeys")){A.on(document,E,this._handleKeyPress,this,true);}}},_handleMouseOut:function(F){var E="keydown";if(YAHOO.env.ua.gecko||YAHOO.env.ua.opera){E="keypress";}this._active=false;if(this.get("useKeys")){A.removeListener(document,E,this._handleKeyPress);}},_moveEl:function(G,J){var H=0,E=0,I=this._setConstraints(),F=true;switch(G){case"down":H=-(J);if((I.bottom-J)<0){F=false;this._resizeEl.style.top=(I.top+I.bottom)+"px";}break;case"up":H=(J);if((I.top-J)<0){F=false;this._resizeEl.style.top="0px";}break;case"right":E=-(J);if((I.right-J)<0){F=false;this._resizeEl.style.left=(I.left+I.right)+"px";}break;case"left":E=J;if((I.left-J)<0){F=false;this._resizeEl.style.left="0px";}break;}if(F){this._resizeEl.style.left=(parseInt(this._resizeEl.style.left,10)-E)+"px";this._resizeEl.style.top=(parseInt(this._resizeEl.style.top,10)-H)+"px";this.fireEvent("moveEvent",{target:"keypress"});}else{this._setConstraints();}this._syncBackgroundPosition();},_handleKeyPress:function(G){var E=A.getCharCode(G),F=false,H=((G.shiftKey)?this.get("shiftKeyTick"):this.get("keyTick"));switch(E){case 37:this._moveEl("left",H);F=true;break;case 38:this._moveEl("up",H);F=true;break;case 39:this._moveEl("right",H);F=true;break;case 40:this._moveEl("down",H);F=true;break;default:}if(F){A.preventDefault(G);}},_handleB4DragEvent:function(){this._setConstraints();},_handleDragEvent:function(){this._syncBackgroundPosition();this.fireEvent("dragEvent",arguments);this.fireEvent("moveEvent",{target:"dragevent"});},_handleBeforeResizeEvent:function(F){var I=C.getRegion(this.get("element")),J=this._resize._cache,H=this._resize._currentHandle,G=0,E=0;if(F.top&&(F.top<I.top)){G=(J.height+J.top)-I.top;C.setY(this._resize.getWrapEl(),I.top);this._resize.getWrapEl().style.height=G+"px";this._resize._cache.height=G;this._resize._cache.top=I.top;this._syncBackgroundPosition();return false;}if(F.left&&(F.left<I.left)){E=(J.width+J.left)-I.left;C.setX(this._resize.getWrapEl(),I.left);this._resize._cache.left=I.left;this._resize.getWrapEl().style.width=E+"px";this._resize._cache.width=E;this._syncBackgroundPosition();return false;}if(H!="tl"&&H!="l"&&H!="bl"){if(J.left&&F.width&&((J.left+F.width)>I.right)){E=(I.right-J.left);C.setX(this._resize.getWrapEl(),(I.right-E));this._resize.getWrapEl().style.width=E+"px";this._resize._cache.left=(I.right-E);this._resize._cache.width=E;this._syncBackgroundPosition();return false;}}if(H!="t"&&H!="tr"&&H!="tl"){if(J.top&&F.height&&((J.top+F.height)>I.bottom)){G=(I.bottom-J.top);C.setY(this._resize.getWrapEl(),(I.bottom-G));this._resize.getWrapEl().style.height=G+"px";this._resize._cache.height=G;this._resize._cache.top=(I.bottom-G);this._syncBackgroundPosition();return false;}}},_handleResizeMaskEl:function(){var E=this._resize._cache;this._resizeMaskEl.style.height=Math.floor(E.height)+"px";this._resizeMaskEl.style.width=Math.floor(E.width)+"px";},_handleResizeEvent:function(E){this._setConstraints(true);this._syncBackgroundPosition();this.fireEvent("resizeEvent",arguments);this.fireEvent("moveEvent",{target:"resizeevent"});},_syncBackgroundPosition:function(){this._handleResizeMaskEl();this._setBackgroundPosition(-(parseInt(this._resizeEl.style.left,10)),-(parseInt(this._resizeEl.style.top,10)));
+},_setBackgroundPosition:function(F,H){var J=parseInt(C.getStyle(this._resize.get("element"),"borderLeftWidth"),10);var G=parseInt(C.getStyle(this._resize.get("element"),"borderTopWidth"),10);if(isNaN(J)){J=0;}if(isNaN(G)){G=0;}var E=this._resize.getWrapEl().firstChild;var I=(F-J)+"px "+(H-G)+"px";this._resizeMaskEl.style.backgroundPosition=I;},_setBackgroundImage:function(F){var E=this._resize.getWrapEl().firstChild;this._image=F;E.style.backgroundImage="url("+F+"#)";},_handleEndResizeEvent:function(){this._setConstraints(true);},_handleStartResizeEvent:function(){this._setConstraints(true);var I=this._resize._cache.height,F=this._resize._cache.width,H=parseInt(this._resize.getWrapEl().style.top,10),E=parseInt(this._resize.getWrapEl().style.left,10),G=0,J=0;switch(this._resize._currentHandle){case"b":G=(I+this._resize.dd.bottomConstraint);break;case"l":J=(F+this._resize.dd.leftConstraint);break;case"r":G=(I+H);J=(F+this._resize.dd.rightConstraint);break;case"br":G=(I+this._resize.dd.bottomConstraint);J=(F+this._resize.dd.rightConstraint);break;case"tr":G=(I+H);J=(F+this._resize.dd.rightConstraint);break;}if(G){}if(J){}this.fireEvent("startResizeEvent",arguments);},_setConstraints:function(J){var H=this._resize;H.dd.resetConstraints();var N=parseInt(H.get("height"),10),F=parseInt(H.get("width"),10);if(J){N=H._cache.height;F=H._cache.width;}var L=C.getRegion(this.get("element"));var G=H.getWrapEl();var O=C.getXY(G);var I=O[0]-L.left;var M=L.right-O[0]-F;var K=O[1]-L.top;var E=L.bottom-O[1]-N;if(K<0){K=0;}H.dd.setXConstraint(I,M);H.dd.setYConstraint(K,E);return{top:K,right:M,bottom:E,left:I};},getCropCoords:function(){var E={top:parseInt(this._resize.getWrapEl().style.top,10),left:parseInt(this._resize.getWrapEl().style.left,10),height:this._resize._cache.height,width:this._resize._cache.width,image:this._image};return E;},reset:function(){this._resize.resize(null,this.get("initHeight"),this.get("initWidth"),0,0,true);this._resizeEl.style.top=this.get("initialXY")[1]+"px";this._resizeEl.style.left=this.get("initialXY")[0]+"px";this._syncBackgroundPosition();return this;},getEl:function(){return this.get("element");},getResizeEl:function(){return this._resizeEl;},getWrapEl:function(){return this._wrap;},getMaskEl:function(){return this._mask;},getResizeMaskEl:function(){return this._resizeMaskEl;},getResizeObject:function(){return this._resize;},init:function(G,E){B.superclass.init.call(this,G,E);var H=G;if(!D.isString(H)){if(H.tagName&&(H.tagName.toLowerCase()=="img")){H=C.generateId(H);}else{return false;}}else{var F=C.get(H);if(F.tagName&&F.tagName.toLowerCase()=="img"){}else{return false;}}B._instances[H]=this;this._createWrap();this._createMask();this._createResize();this._setConstraints();},initAttributes:function(E){B.superclass.initAttributes.call(this,E);this.setAttributeConfig("initialXY",{writeOnce:true,validator:YAHOO.lang.isArray,value:E.initialXY||[10,10]});this.setAttributeConfig("keyTick",{validator:YAHOO.lang.isNumber,value:E.keyTick||1});this.setAttributeConfig("shiftKeyTick",{validator:YAHOO.lang.isNumber,value:E.shiftKeyTick||10});this.setAttributeConfig("useKeys",{validator:YAHOO.lang.isBoolean,value:((E.useKeys===false)?false:true)});this.setAttributeConfig("status",{validator:YAHOO.lang.isBoolean,value:((E.status===false)?false:true),method:function(F){if(this._resize){this._resize.set("status",F);}}});this.setAttributeConfig("minHeight",{validator:YAHOO.lang.isNumber,value:E.minHeight||50,method:function(F){if(this._resize){this._resize.set("minHeight",F);}}});this.setAttributeConfig("minWidth",{validator:YAHOO.lang.isNumber,value:E.minWidth||50,method:function(F){if(this._resize){this._resize.set("minWidth",F);}}});this.setAttributeConfig("ratio",{validator:YAHOO.lang.isBoolean,value:E.ratio||false,method:function(F){if(this._resize){this._resize.set("ratio",F);}}});this.setAttributeConfig("autoRatio",{validator:YAHOO.lang.isBoolean,value:((E.autoRatio===false)?false:true),method:function(F){if(this._resize){this._resize.set("autoRatio",F);}}});this.setAttributeConfig("initHeight",{writeOnce:true,validator:YAHOO.lang.isNumber,value:E.initHeight||(this.get("element").height/4)});this.setAttributeConfig("initWidth",{validator:YAHOO.lang.isNumber,writeOnce:true,value:E.initWidth||(this.get("element").width/4)});},destroy:function(){this._resize.destroy();this._resizeEl.parentNode.removeChild(this._resizeEl);this._mask.parentNode.removeChild(this._mask);A.purgeElement(this._wrap);this._wrap.parentNode.replaceChild(this.get("element"),this._wrap);for(var E in this){if(D.hasOwnProperty(this,E)){this[E]=null;}}},toString:function(){if(this.get){return"ImageCropper (#"+this.get("id")+")";}return"Image Cropper";}});YAHOO.widget.ImageCropper=B;})();YAHOO.register("imagecropper",YAHOO.widget.ImageCropper,{version:"2.8.2r1",build:"7"}); \ No newline at end of file
diff --git a/js/yui/imageloader/imageloader-min.js b/js/yui/imageloader/imageloader-min.js
new file mode 100644
index 000000000..27cc63300
--- /dev/null
+++ b/js/yui/imageloader/imageloader-min.js
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+if(typeof(YAHOO.util.ImageLoader)=="undefined"){YAHOO.util.ImageLoader={};}YAHOO.util.ImageLoader.group=function(A,B,C){this.name="unnamed";this._imgObjs={};this.timeoutLen=C;this._timeout=null;this._triggers=[];this._customTriggers=[];this.foldConditional=false;this.className=null;this._classImageEls=null;YAHOO.util.Event.addListener(window,"load",this._onloadTasks,this,true);this.addTrigger(A,B);};YAHOO.util.ImageLoader.group.prototype.addTrigger=function(B,C){if(!B||!C){return;}var A=function(){this.fetch();};this._triggers.push([B,C,A]);YAHOO.util.Event.addListener(B,C,A,this,true);};YAHOO.util.ImageLoader.group.prototype.addCustomTrigger=function(B){if(!B||!B instanceof YAHOO.util.CustomEvent){return;}var A=function(){this.fetch();};this._customTriggers.push([B,A]);B.subscribe(A,this,true);};YAHOO.util.ImageLoader.group.prototype._onloadTasks=function(){if(this.timeoutLen&&typeof(this.timeoutLen)=="number"&&this.timeoutLen>0){this._timeout=setTimeout(this._getFetchTimeout(),this.timeoutLen*1000);}if(this.foldConditional){this._foldCheck();}};YAHOO.util.ImageLoader.group.prototype._getFetchTimeout=function(){var A=this;return function(){A.fetch();};};YAHOO.util.ImageLoader.group.prototype.registerBgImage=function(B,A){this._imgObjs[B]=new YAHOO.util.ImageLoader.bgImgObj(B,A);return this._imgObjs[B];};YAHOO.util.ImageLoader.group.prototype.registerSrcImage=function(D,B,C,A){this._imgObjs[D]=new YAHOO.util.ImageLoader.srcImgObj(D,B,C,A);return this._imgObjs[D];};YAHOO.util.ImageLoader.group.prototype.registerPngBgImage=function(C,B,A){this._imgObjs[C]=new YAHOO.util.ImageLoader.pngBgImgObj(C,B,A);return this._imgObjs[C];};YAHOO.util.ImageLoader.group.prototype.fetch=function(){clearTimeout(this._timeout);for(var B=0,A=this._triggers.length;B<A;B++){YAHOO.util.Event.removeListener(this._triggers[B][0],this._triggers[B][1],this._triggers[B][2]);}for(var B=0,A=this._customTriggers.length;B<A;B++){this._customTriggers[B][0].unsubscribe(this._customTriggers[B][1],this);}this._fetchByClass();for(var C in this._imgObjs){if(YAHOO.lang.hasOwnProperty(this._imgObjs,C)){this._imgObjs[C].fetch();}}};YAHOO.util.ImageLoader.group.prototype._foldCheck=function(){var C=(document.compatMode!="CSS1Compat")?document.body.scrollTop:document.documentElement.scrollTop;var D=YAHOO.util.Dom.getViewportHeight();var A=C+D;var E=(document.compatMode!="CSS1Compat")?document.body.scrollLeft:document.documentElement.scrollLeft;var G=YAHOO.util.Dom.getViewportWidth();var I=E+G;for(var B in this._imgObjs){if(YAHOO.lang.hasOwnProperty(this._imgObjs,B)){var J=YAHOO.util.Dom.getXY(this._imgObjs[B].domId);if(J[1]<A&&J[0]<I){this._imgObjs[B].fetch();}}}if(this.className){this._classImageEls=YAHOO.util.Dom.getElementsByClassName(this.className);for(var F=0,H=this._classImageEls.length;F<H;F++){var J=YAHOO.util.Dom.getXY(this._classImageEls[F]);if(J[1]<A&&J[0]<I){YAHOO.util.Dom.removeClass(this._classImageEls[F],this.className);}}}};YAHOO.util.ImageLoader.group.prototype._fetchByClass=function(){if(!this.className){return;}if(this._classImageEls===null){this._classImageEls=YAHOO.util.Dom.getElementsByClassName(this.className);}YAHOO.util.Dom.removeClass(this._classImageEls,this.className);};YAHOO.util.ImageLoader.imgObj=function(B,A){this.domId=B;this.url=A;this.width=null;this.height=null;this.setVisible=false;this._fetched=false;};YAHOO.util.ImageLoader.imgObj.prototype.fetch=function(){if(this._fetched){return;}var A=document.getElementById(this.domId);if(!A){return;}this._applyUrl(A);if(this.setVisible){A.style.visibility="visible";}if(this.width){A.width=this.width;}if(this.height){A.height=this.height;}this._fetched=true;};YAHOO.util.ImageLoader.imgObj.prototype._applyUrl=function(A){};YAHOO.util.ImageLoader.bgImgObj=function(B,A){YAHOO.util.ImageLoader.bgImgObj.superclass.constructor.call(this,B,A);};YAHOO.lang.extend(YAHOO.util.ImageLoader.bgImgObj,YAHOO.util.ImageLoader.imgObj);YAHOO.util.ImageLoader.bgImgObj.prototype._applyUrl=function(A){A.style.backgroundImage="url('"+this.url+"')";};YAHOO.util.ImageLoader.srcImgObj=function(D,B,C,A){YAHOO.util.ImageLoader.srcImgObj.superclass.constructor.call(this,D,B);this.width=C;this.height=A;};YAHOO.lang.extend(YAHOO.util.ImageLoader.srcImgObj,YAHOO.util.ImageLoader.imgObj);YAHOO.util.ImageLoader.srcImgObj.prototype._applyUrl=function(A){A.src=this.url;};YAHOO.util.ImageLoader.pngBgImgObj=function(C,B,A){YAHOO.util.ImageLoader.pngBgImgObj.superclass.constructor.call(this,C,B);this.props=A||{};};YAHOO.lang.extend(YAHOO.util.ImageLoader.pngBgImgObj,YAHOO.util.ImageLoader.imgObj);YAHOO.util.ImageLoader.pngBgImgObj.prototype._applyUrl=function(B){if(YAHOO.env.ua.ie&&YAHOO.env.ua.ie<=6){var C=(YAHOO.lang.isUndefined(this.props.sizingMethod))?"scale":this.props.sizingMethod;var A=(YAHOO.lang.isUndefined(this.props.enabled))?"true":this.props.enabled;B.style.filter='progid:DXImageTransform.Microsoft.AlphaImageLoader(src="'+this.url+'", sizingMethod="'+C+'", enabled="'+A+'")';}else{B.style.backgroundImage="url('"+this.url+"')";}};YAHOO.register("imageloader",YAHOO.util.ImageLoader,{version:"2.8.2r1",build:"7"}); \ No newline at end of file
diff --git a/js/yui/json/json-min.js b/js/yui/json/json-min.js
new file mode 100644
index 000000000..263ce8cff
--- /dev/null
+++ b/js/yui/json/json-min.js
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+(function(){var l=YAHOO.lang,isFunction=l.isFunction,isObject=l.isObject,isArray=l.isArray,_toStr=Object.prototype.toString,Native=(YAHOO.env.ua.caja?window:this).JSON,_UNICODE_EXCEPTIONS=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,_ESCAPES=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,_VALUES=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,_BRACKETS=/(?:^|:|,)(?:\s*\[)+/g,_UNSAFE=/^[\],:{}\s]*$/,_SPECIAL_CHARS=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,_CHARS={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},UNDEFINED="undefined",OBJECT="object",NULL="null",STRING="string",NUMBER="number",BOOLEAN="boolean",DATE="date",_allowable={"undefined":UNDEFINED,"string":STRING,"[object String]":STRING,"number":NUMBER,"[object Number]":NUMBER,"boolean":BOOLEAN,"[object Boolean]":BOOLEAN,"[object Date]":DATE,"[object RegExp]":OBJECT},EMPTY="",OPEN_O="{",CLOSE_O="}",OPEN_A="[",CLOSE_A="]",COMMA=",",COMMA_CR=",\n",CR="\n",COLON=":",COLON_SP=": ",QUOTE='"';Native=_toStr.call(Native)==="[object JSON]"&&Native;function _char(c){if(!_CHARS[c]){_CHARS[c]="\\u"+("0000"+(+(c.charCodeAt(0))).toString(16)).slice(-4);}return _CHARS[c];}function _revive(data,reviver){var walk=function(o,key){var k,v,value=o[key];if(value&&typeof value==="object"){for(k in value){if(l.hasOwnProperty(value,k)){v=walk(value,k);if(v===undefined){delete value[k];}else{value[k]=v;}}}}return reviver.call(o,key,value);};return typeof reviver==="function"?walk({"":data},""):data;}function _prepare(s){return s.replace(_UNICODE_EXCEPTIONS,_char);}function _isSafe(str){return l.isString(str)&&_UNSAFE.test(str.replace(_ESCAPES,"@").replace(_VALUES,"]").replace(_BRACKETS,""));}function _parse(s,reviver){s=_prepare(s);if(_isSafe(s)){return _revive(eval("("+s+")"),reviver);}throw new SyntaxError("JSON.parse");}function _type(o){var t=typeof o;return _allowable[t]||_allowable[_toStr.call(o)]||(t===OBJECT?(o?OBJECT:NULL):UNDEFINED);}function _string(s){return QUOTE+s.replace(_SPECIAL_CHARS,_char)+QUOTE;}function _indent(s,space){return s.replace(/^/gm,space);}function _stringify(o,w,space){if(o===undefined){return undefined;}var replacer=isFunction(w)?w:null,format=_toStr.call(space).match(/String|Number/)||[],_date=YAHOO.lang.JSON.dateToString,stack=[],tmp,i,len;if(replacer||!isArray(w)){w=undefined;}if(w){tmp={};for(i=0,len=w.length;i<len;++i){tmp[w[i]]=true;}w=tmp;}space=format[0]==="Number"?new Array(Math.min(Math.max(0,space),10)+1).join(" "):(space||EMPTY).slice(0,10);function _serialize(h,key){var value=h[key],t=_type(value),a=[],colon=space?COLON_SP:COLON,arr,i,keys,k,v;if(isObject(value)&&isFunction(value.toJSON)){value=value.toJSON(key);}else{if(t===DATE){value=_date(value);}}if(isFunction(replacer)){value=replacer.call(h,key,value);}if(value!==h[key]){t=_type(value);}switch(t){case DATE:case OBJECT:break;case STRING:return _string(value);case NUMBER:return isFinite(value)?value+EMPTY:NULL;case BOOLEAN:return value+EMPTY;case NULL:return NULL;default:return undefined;}for(i=stack.length-1;i>=0;--i){if(stack[i]===value){throw new Error("JSON.stringify. Cyclical reference");}}arr=isArray(value);stack.push(value);if(arr){for(i=value.length-1;i>=0;--i){a[i]=_serialize(value,i)||NULL;}}else{keys=w||value;i=0;for(k in keys){if(keys.hasOwnProperty(k)){v=_serialize(value,k);if(v){a[i++]=_string(k)+colon+v;}}}}stack.pop();if(space&&a.length){return arr?OPEN_A+CR+_indent(a.join(COMMA_CR),space)+CR+CLOSE_A:OPEN_O+CR+_indent(a.join(COMMA_CR),space)+CR+CLOSE_O;}else{return arr?OPEN_A+a.join(COMMA)+CLOSE_A:OPEN_O+a.join(COMMA)+CLOSE_O;}}return _serialize({"":o},"");}YAHOO.lang.JSON={useNativeParse:!!Native,useNativeStringify:!!Native,isSafe:function(s){return _isSafe(_prepare(s));},parse:function(s,reviver){return Native&&YAHOO.lang.JSON.useNativeParse?Native.parse(s,reviver):_parse(s,reviver);},stringify:function(o,w,space){return Native&&YAHOO.lang.JSON.useNativeStringify?Native.stringify(o,w,space):_stringify(o,w,space);},dateToString:function(d){function _zeroPad(v){return v<10?"0"+v:v;}return d.getUTCFullYear()+"-"+_zeroPad(d.getUTCMonth()+1)+"-"+_zeroPad(d.getUTCDate())+"T"+_zeroPad(d.getUTCHours())+COLON+_zeroPad(d.getUTCMinutes())+COLON+_zeroPad(d.getUTCSeconds())+"Z";},stringToDate:function(str){var m=str.match(/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{3}))?Z$/);if(m){var d=new Date();d.setUTCFullYear(m[1],m[2]-1,m[3]);d.setUTCHours(m[4],m[5],m[6],(m[7]||0));return d;}return str;}};YAHOO.lang.JSON.isValid=YAHOO.lang.JSON.isSafe;})();YAHOO.register("json",YAHOO.lang.JSON,{version:"2.8.2r1",build:"7"}); \ No newline at end of file
diff --git a/js/yui/layout/layout-min.js b/js/yui/layout/layout-min.js
new file mode 100644
index 000000000..e4ddb547d
--- /dev/null
+++ b/js/yui/layout/layout-min.js
@@ -0,0 +1,11 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+(function(){var C=YAHOO.util.Dom,A=YAHOO.util.Event,D=YAHOO.lang;var B=function(F,E){if(D.isObject(F)&&!F.tagName){E=F;F=null;}if(D.isString(F)){if(C.get(F)){F=C.get(F);}}if(!F){F=document.body;}var G={element:F,attributes:E||{}};B.superclass.constructor.call(this,G.element,G.attributes);};B._instances={};B.getLayoutById=function(E){if(B._instances[E]){return B._instances[E];}return false;};YAHOO.extend(B,YAHOO.util.Element,{browser:function(){var E=YAHOO.env.ua;E.standardsMode=false;E.secure=false;return E;}(),_units:null,_rendered:null,_zIndex:null,_sizes:null,_setBodySize:function(G){var F=0,E=0;G=((G===false)?false:true);if(this._isBody){F=C.getClientHeight();E=C.getClientWidth();}else{F=parseInt(this.getStyle("height"),10);E=parseInt(this.getStyle("width"),10);if(isNaN(E)){E=this.get("element").clientWidth;}if(isNaN(F)){F=this.get("element").clientHeight;}}if(this.get("minWidth")){if(E<this.get("minWidth")){E=this.get("minWidth");}}if(this.get("minHeight")){if(F<this.get("minHeight")){F=this.get("minHeight");}}if(G){if(F<0){F=0;}if(E<0){E=0;}C.setStyle(this._doc,"height",F+"px");C.setStyle(this._doc,"width",E+"px");}this._sizes.doc={h:F,w:E};this._setSides(G);},_setSides:function(J){var H=((this._units.top)?this._units.top.get("height"):0),G=((this._units.bottom)?this._units.bottom.get("height"):0),I=this._sizes.doc.h,E=this._sizes.doc.w;J=((J===false)?false:true);this._sizes.top={h:H,w:((this._units.top)?E:0),t:0};this._sizes.bottom={h:G,w:((this._units.bottom)?E:0)};var F=(I-(H+G));this._sizes.left={h:F,w:((this._units.left)?this._units.left.get("width"):0)};this._sizes.right={h:F,w:((this._units.right)?this._units.right.get("width"):0),l:((this._units.right)?(E-this._units.right.get("width")):0),t:((this._units.top)?this._sizes.top.h:0)};if(this._units.right&&J){this._units.right.set("top",this._sizes.right.t);if(!this._units.right._collapsing){this._units.right.set("left",this._sizes.right.l);}this._units.right.set("height",this._sizes.right.h,true);}if(this._units.left){this._sizes.left.l=0;if(this._units.top){this._sizes.left.t=this._sizes.top.h;}else{this._sizes.left.t=0;}if(J){this._units.left.set("top",this._sizes.left.t);this._units.left.set("height",this._sizes.left.h,true);this._units.left.set("left",0);}}if(this._units.bottom){this._sizes.bottom.t=this._sizes.top.h+this._sizes.left.h;if(J){this._units.bottom.set("top",this._sizes.bottom.t);this._units.bottom.set("width",this._sizes.bottom.w,true);}}if(this._units.top){if(J){this._units.top.set("width",this._sizes.top.w,true);}}this._setCenter(J);},_setCenter:function(G){G=((G===false)?false:true);var F=this._sizes.left.h;var E=(this._sizes.doc.w-(this._sizes.left.w+this._sizes.right.w));if(G){this._units.center.set("height",F,true);this._units.center.set("width",E,true);this._units.center.set("top",this._sizes.top.h);this._units.center.set("left",this._sizes.left.w);}this._sizes.center={h:F,w:E,t:this._sizes.top.h,l:this._sizes.left.w};},getSizes:function(){return this._sizes;},getUnitById:function(E){return YAHOO.widget.LayoutUnit.getLayoutUnitById(E);},getUnitByPosition:function(E){if(E){E=E.toLowerCase();if(this._units[E]){return this._units[E];}return false;}return false;},removeUnit:function(E){delete this._units[E.get("position")];this.resize();},addUnit:function(G){if(!G.position){return false;}if(this._units[G.position]){return false;}var H=null,J=null;if(G.id){if(C.get(G.id)){H=C.get(G.id);delete G.id;}}if(G.element){H=G.element;}if(!J){J=document.createElement("div");var L=C.generateId();J.id=L;}if(!H){H=document.createElement("div");}C.addClass(H,"yui-layout-wrap");if(this.browser.ie&&!this.browser.standardsMode){J.style.zoom=1;H.style.zoom=1;}if(J.firstChild){J.insertBefore(H,J.firstChild);}else{J.appendChild(H);}this._doc.appendChild(J);var I=false,F=false;if(G.height){I=parseInt(G.height,10);}if(G.width){F=parseInt(G.width,10);}var E={};YAHOO.lang.augmentObject(E,G);E.parent=this;E.wrap=H;E.height=I;E.width=F;var K=new YAHOO.widget.LayoutUnit(J,E);K.on("heightChange",this.resize,{unit:K},this);K.on("widthChange",this.resize,{unit:K},this);K.on("gutterChange",this.resize,{unit:K},this);this._units[G.position]=K;if(this._rendered){this.resize();}return K;},_createUnits:function(){var E=this.get("units");for(var F in E){if(D.hasOwnProperty(E,F)){this.addUnit(E[F]);}}},resize:function(H,G){var E=H;if(E&&E.prevValue&&E.newValue){if(E.prevValue==E.newValue){if(G){if(G.unit){if(!G.unit.get("animate")){H=false;}}}}}H=((H===false)?false:true);if(H){var F=this.fireEvent("beforeResize");if(F===false){H=false;}if(this.browser.ie){if(this._isBody){C.removeClass(document.documentElement,"yui-layout");C.addClass(document.documentElement,"yui-layout");}else{this.removeClass("yui-layout");this.addClass("yui-layout");}}}this._setBodySize(H);if(H){this.fireEvent("resize",{target:this,sizes:this._sizes,event:E});}return this;},_setupBodyElements:function(){this._doc=C.get("layout-doc");if(!this._doc){this._doc=document.createElement("div");this._doc.id="layout-doc";if(document.body.firstChild){document.body.insertBefore(this._doc,document.body.firstChild);}else{document.body.appendChild(this._doc);}}this._createUnits();this._setBodySize();A.on(window,"resize",this.resize,this,true);C.addClass(this._doc,"yui-layout-doc");},_setupElements:function(){this._doc=this.getElementsByClassName("yui-layout-doc")[0];if(!this._doc){this._doc=document.createElement("div");this.get("element").appendChild(this._doc);}this._createUnits();this._setBodySize();C.addClass(this._doc,"yui-layout-doc");},_isBody:null,_doc:null,init:function(F,E){this._zIndex=0;B.superclass.init.call(this,F,E);if(this.get("parent")){this._zIndex=this.get("parent")._zIndex+10;}this._sizes={};this._units={};var G=F;if(!D.isString(G)){G=C.generateId(G);}B._instances[G]=this;},render:function(){this._stamp();var E=this.get("element");if(E&&E.tagName&&(E.tagName.toLowerCase()=="body")){this._isBody=true;C.addClass(document.body,"yui-layout");if(C.hasClass(document.body,"yui-skin-sam")){C.addClass(document.documentElement,"yui-skin-sam");
+C.removeClass(document.body,"yui-skin-sam");}this._setupBodyElements();}else{this._isBody=false;this.addClass("yui-layout");this._setupElements();}this.resize();this._rendered=true;this.fireEvent("render");return this;},_stamp:function(){if(document.compatMode=="CSS1Compat"){this.browser.standardsMode=true;}if(window.location.href.toLowerCase().indexOf("https")===0){C.addClass(document.documentElement,"secure");this.browser.secure=true;}},initAttributes:function(E){B.superclass.initAttributes.call(this,E);this.setAttributeConfig("units",{writeOnce:true,validator:YAHOO.lang.isArray,value:E.units||[]});this.setAttributeConfig("minHeight",{value:E.minHeight||false,validator:YAHOO.lang.isNumber});this.setAttributeConfig("minWidth",{value:E.minWidth||false,validator:YAHOO.lang.isNumber});this.setAttributeConfig("height",{value:E.height||false,validator:YAHOO.lang.isNumber,method:function(F){if(F<0){F=0;}this.setStyle("height",F+"px");}});this.setAttributeConfig("width",{value:E.width||false,validator:YAHOO.lang.isNumber,method:function(F){if(F<0){F=0;}this.setStyle("width",F+"px");}});this.setAttributeConfig("parent",{writeOnce:true,value:E.parent||false,method:function(F){if(F){F.on("resize",this.resize,this,true);}}});},destroy:function(){var G=this.get("parent");if(G){G.removeListener("resize",this.resize,this,true);}A.removeListener(window,"resize",this.resize,this,true);this.unsubscribeAll();for(var E in this._units){if(D.hasOwnProperty(this._units,E)){if(this._units[E]){this._units[E].destroy(true);}}}A.purgeElement(this.get("element"));this.get("parentNode").removeChild(this.get("element"));delete YAHOO.widget.Layout._instances[this.get("id")];for(var F in this){if(D.hasOwnProperty(this,F)){this[F]=null;delete this[F];}}if(G){G.resize();}},toString:function(){if(this.get){return"Layout #"+this.get("id");}return"Layout";}});YAHOO.widget.Layout=B;})();(function(){var D=YAHOO.util.Dom,C=YAHOO.util.Selector,A=YAHOO.util.Event,E=YAHOO.lang;var B=function(G,F){var H={element:G,attributes:F||{}};B.superclass.constructor.call(this,H.element,H.attributes);};B._instances={};B.getLayoutUnitById=function(F){if(B._instances[F]){return B._instances[F];}return false;};YAHOO.extend(B,YAHOO.util.Element,{STR_CLOSE:"Click to close this pane.",STR_COLLAPSE:"Click to collapse this pane.",STR_EXPAND:"Click to expand this pane.",LOADING_CLASSNAME:"loading",browser:null,_sizes:null,_anim:null,_resize:null,_clip:null,_gutter:null,header:null,body:null,footer:null,_collapsed:null,_collapsing:null,_lastWidth:null,_lastHeight:null,_lastTop:null,_lastLeft:null,_lastScroll:null,_lastCenterScroll:null,_lastScrollTop:null,resize:function(F){var G=this.fireEvent("beforeResize");if(G===false){return this;}if(!this._collapsing||(F===true)){var N=this.get("scroll");this.set("scroll",false);var K=this._getBoxSize(this.header),J=this._getBoxSize(this.footer),L=[this.get("height"),this.get("width")];var H=(L[0]-K[0]-J[0])-(this._gutter.top+this._gutter.bottom),M=L[1]-(this._gutter.left+this._gutter.right);var O=(H+(K[0]+J[0])),I=M;if(this._collapsed&&!this._collapsing){this._setHeight(this._clip,O);this._setWidth(this._clip,I);D.setStyle(this._clip,"top",this.get("top")+this._gutter.top+"px");D.setStyle(this._clip,"left",this.get("left")+this._gutter.left+"px");}else{if(!this._collapsed||(this._collapsed&&this._collapsing)){O=this._setHeight(this.get("wrap"),O);I=this._setWidth(this.get("wrap"),I);this._sizes.wrap.h=O;this._sizes.wrap.w=I;D.setStyle(this.get("wrap"),"top",this._gutter.top+"px");D.setStyle(this.get("wrap"),"left",this._gutter.left+"px");this._sizes.header.w=this._setWidth(this.header,I);this._sizes.header.h=K[0];this._sizes.footer.w=this._setWidth(this.footer,I);this._sizes.footer.h=J[0];D.setStyle(this.footer,"bottom","0px");this._sizes.body.h=this._setHeight(this.body,(O-(K[0]+J[0])));this._sizes.body.w=this._setWidth(this.body,I);D.setStyle(this.body,"top",K[0]+"px");this.set("scroll",N);this.fireEvent("resize");}}}return this;},_setWidth:function(H,G){if(H){var F=this._getBorderSizes(H);G=(G-(F[1]+F[3]));G=this._fixQuirks(H,G,"w");if(G<0){G=0;}D.setStyle(H,"width",G+"px");}return G;},_setHeight:function(H,G){if(H){var F=this._getBorderSizes(H);G=(G-(F[0]+F[2]));G=this._fixQuirks(H,G,"h");if(G<0){G=0;}D.setStyle(H,"height",G+"px");}return G;},_fixQuirks:function(I,L,G){var K=0,H=2;if(G=="w"){K=1;H=3;}if((this.browser.ie<8)&&!this.browser.standardsMode){var F=this._getBorderSizes(I),J=this._getBorderSizes(I.parentNode);if((F[K]===0)&&(F[H]===0)){if((J[K]!==0)&&(J[H]!==0)){L=(L-(J[K]+J[H]));}}else{if((J[K]===0)&&(J[H]===0)){L=(L+(F[K]+F[H]));}}}return L;},_getBoxSize:function(H){var G=[0,0];if(H){if(this.browser.ie&&!this.browser.standardsMode){H.style.zoom=1;}var F=this._getBorderSizes(H);G[0]=H.clientHeight+(F[0]+F[2]);G[1]=H.clientWidth+(F[1]+F[3]);}return G;},_getBorderSizes:function(H){var G=[];H=H||this.get("element");if(this.browser.ie&&!this.browser.standardsMode){H.style.zoom=1;}G[0]=parseInt(D.getStyle(H,"borderTopWidth"),10);G[1]=parseInt(D.getStyle(H,"borderRightWidth"),10);G[2]=parseInt(D.getStyle(H,"borderBottomWidth"),10);G[3]=parseInt(D.getStyle(H,"borderLeftWidth"),10);for(var F=0;F<G.length;F++){if(isNaN(G[F])){G[F]=0;}}return G;},_createClip:function(){if(!this._clip){this._clip=document.createElement("div");this._clip.className="yui-layout-clip yui-layout-clip-"+this.get("position");this._clip.innerHTML='<div class="collapse"></div>';var F=this._clip.firstChild;F.title=this.STR_EXPAND;A.on(F,"click",this.expand,this,true);this.get("element").parentNode.appendChild(this._clip);}},_toggleClip:function(){if(!this._collapsed){var J=this._getBoxSize(this.header),K=this._getBoxSize(this.footer),I=[this.get("height"),this.get("width")];var H=(I[0]-J[0]-K[0])-(this._gutter.top+this._gutter.bottom),F=I[1]-(this._gutter.left+this._gutter.right),G=(H+(J[0]+K[0]));switch(this.get("position")){case"top":case"bottom":this._setWidth(this._clip,F);this._setHeight(this._clip,this.get("collapseSize"));D.setStyle(this._clip,"left",(this._lastLeft+this._gutter.left)+"px");
+if(this.get("position")=="bottom"){D.setStyle(this._clip,"top",((this._lastTop+this._lastHeight)-(this.get("collapseSize")-this._gutter.top))+"px");}else{D.setStyle(this._clip,"top",this.get("top")+this._gutter.top+"px");}break;case"left":case"right":this._setWidth(this._clip,this.get("collapseSize"));this._setHeight(this._clip,G);D.setStyle(this._clip,"top",(this.get("top")+this._gutter.top)+"px");if(this.get("position")=="right"){D.setStyle(this._clip,"left",(((this._lastLeft+this._lastWidth)-this.get("collapseSize"))-this._gutter.left)+"px");}else{D.setStyle(this._clip,"left",(this.get("left")+this._gutter.left)+"px");}break;}D.setStyle(this._clip,"display","block");this.setStyle("display","none");}else{D.setStyle(this._clip,"display","none");}},getSizes:function(){return this._sizes;},toggle:function(){if(this._collapsed){this.expand();}else{this.collapse();}return this;},expand:function(){if(!this._collapsed){return this;}var L=this.fireEvent("beforeExpand");if(L===false){return this;}this._collapsing=true;this.setStyle("zIndex",this.get("parent")._zIndex+1);if(this._anim){this.setStyle("display","none");var F={},H;switch(this.get("position")){case"left":case"right":this.set("width",this._lastWidth,true);this.setStyle("width",this._lastWidth+"px");this.get("parent").resize(false);H=this.get("parent").getSizes()[this.get("position")];this.set("height",H.h,true);var K=H.l;F={left:{to:K}};if(this.get("position")=="left"){F.left.from=(K-H.w);this.setStyle("left",(K-H.w)+"px");}break;case"top":case"bottom":this.set("height",this._lastHeight,true);this.setStyle("height",this._lastHeight+"px");this.get("parent").resize(false);H=this.get("parent").getSizes()[this.get("position")];this.set("width",H.w,true);var J=H.t;F={top:{to:J}};if(this.get("position")=="top"){this.setStyle("top",(J-H.h)+"px");F.top.from=(J-H.h);}break;}this._anim.attributes=F;var I=function(){this.setStyle("display","block");this.resize(true);this._anim.onStart.unsubscribe(I,this,true);};var G=function(){this._collapsing=false;this.setStyle("zIndex",this.get("parent")._zIndex);this.set("width",this._lastWidth);this.set("height",this._lastHeight);this._collapsed=false;this.resize();this.set("scroll",this._lastScroll);if(this._lastScrollTop>0){this.body.scrollTop=this._lastScrollTop;}this._anim.onComplete.unsubscribe(G,this,true);this.fireEvent("expand");};this._anim.onStart.subscribe(I,this,true);this._anim.onComplete.subscribe(G,this,true);this._anim.animate();this._toggleClip();}else{this._collapsing=false;this._toggleClip();this._collapsed=false;this.setStyle("zIndex",this.get("parent")._zIndex);this.setStyle("display","block");this.set("width",this._lastWidth);this.set("height",this._lastHeight);this.resize();this.set("scroll",this._lastScroll);if(this._lastScrollTop>0){this.body.scrollTop=this._lastScrollTop;}this.fireEvent("expand");}return this;},collapse:function(){if(this._collapsed){return this;}var J=this.fireEvent("beforeCollapse");if(J===false){return this;}if(!this._clip){this._createClip();}this._collapsing=true;var G=this.get("width"),H=this.get("height"),F={};this._lastWidth=G;this._lastHeight=H;this._lastScroll=this.get("scroll");this._lastScrollTop=this.body.scrollTop;this.set("scroll",false,true);this._lastLeft=parseInt(this.get("element").style.left,10);this._lastTop=parseInt(this.get("element").style.top,10);if(isNaN(this._lastTop)){this._lastTop=0;this.set("top",0);}if(isNaN(this._lastLeft)){this._lastLeft=0;this.set("left",0);}this.setStyle("zIndex",this.get("parent")._zIndex+1);var K=this.get("position");switch(K){case"top":case"bottom":this.set("height",(this.get("collapseSize")+(this._gutter.top+this._gutter.bottom)));F={top:{to:(this.get("top")-H)}};if(K=="bottom"){F.top.to=(this.get("top")+H);}break;case"left":case"right":this.set("width",(this.get("collapseSize")+(this._gutter.left+this._gutter.right)));F={left:{to:-(this._lastWidth)}};if(K=="right"){F.left={to:(this.get("left")+G)};}break;}if(this._anim){this._anim.attributes=F;var I=function(){this._collapsing=false;this._toggleClip();this.setStyle("zIndex",this.get("parent")._zIndex);this._collapsed=true;this.get("parent").resize();this._anim.onComplete.unsubscribe(I,this,true);this.fireEvent("collapse");};this._anim.onComplete.subscribe(I,this,true);this._anim.animate();}else{this._collapsing=false;this.setStyle("display","none");this._toggleClip();this.setStyle("zIndex",this.get("parent")._zIndex);this.get("parent").resize();this._collapsed=true;this.fireEvent("collapse");}return this;},close:function(){this.setStyle("display","none");this.get("parent").removeUnit(this);this.fireEvent("close");if(this._clip){this._clip.parentNode.removeChild(this._clip);this._clip=null;}return this.get("parent");},loadHandler:{success:function(F){this.body.innerHTML=F.responseText;this.resize(true);},failure:function(F){}},dataConnection:null,_loading:false,loadContent:function(){if(YAHOO.util.Connect&&this.get("dataSrc")&&!this._loading&&!this.get("dataLoaded")){this._loading=true;D.addClass(this.body,this.LOADING_CLASSNAME);this.dataConnection=YAHOO.util.Connect.asyncRequest(this.get("loadMethod"),this.get("dataSrc"),{success:function(F){this.loadHandler.success.call(this,F);this.set("dataLoaded",true);this.dataConnection=null;D.removeClass(this.body,this.LOADING_CLASSNAME);this._loading=false;this.fireEvent("load");},failure:function(F){this.loadHandler.failure.call(this,F);this.dataConnection=null;D.removeClass(this.body,this.LOADING_CLASSNAME);this._loading=false;this.fireEvent("loadError",{error:F});},scope:this,timeout:this.get("dataTimeout")});return this.dataConnection;}return false;},init:function(H,G){this._gutter={left:0,right:0,top:0,bottom:0};this._sizes={wrap:{h:0,w:0},header:{h:0,w:0},body:{h:0,w:0},footer:{h:0,w:0}};B.superclass.init.call(this,H,G);this.browser=this.get("parent").browser;var K=H;if(!E.isString(K)){K=D.generateId(K);}B._instances[K]=this;this.setStyle("position","absolute");this.addClass("yui-layout-unit");this.addClass("yui-layout-unit-"+this.get("position"));
+var J=this.getElementsByClassName("yui-layout-hd","div")[0];if(J){this.header=J;}var F=this.getElementsByClassName("yui-layout-bd","div")[0];if(F){this.body=F;}var I=this.getElementsByClassName("yui-layout-ft","div")[0];if(I){this.footer=I;}this.on("contentChange",this.resize,this,true);this._lastScrollTop=0;this.set("animate",this.get("animate"));},initAttributes:function(F){B.superclass.initAttributes.call(this,F);this.setAttributeConfig("wrap",{value:F.wrap||null,method:function(G){if(G){var H=D.generateId(G);B._instances[H]=this;}}});this.setAttributeConfig("grids",{value:F.grids||false});this.setAttributeConfig("top",{value:F.top||0,validator:E.isNumber,method:function(G){if(!this._collapsing){this.setStyle("top",G+"px");}}});this.setAttributeConfig("left",{value:F.left||0,validator:E.isNumber,method:function(G){if(!this._collapsing){this.setStyle("left",G+"px");}}});this.setAttributeConfig("minWidth",{value:F.minWidth||false,method:function(G){if(this._resize){this._resize.set("minWidth",G);}},validator:YAHOO.lang.isNumber});this.setAttributeConfig("maxWidth",{value:F.maxWidth||false,method:function(G){if(this._resize){this._resize.set("maxWidth",G);}},validator:YAHOO.lang.isNumber});this.setAttributeConfig("minHeight",{value:F.minHeight||false,method:function(G){if(this._resize){this._resize.set("minHeight",G);}},validator:YAHOO.lang.isNumber});this.setAttributeConfig("maxHeight",{value:F.maxHeight||false,method:function(G){if(this._resize){this._resize.set("maxHeight",G);}},validator:YAHOO.lang.isNumber});this.setAttributeConfig("height",{value:F.height,validator:E.isNumber,method:function(G){if(!this._collapsing){if(G<0){G=0;}this.setStyle("height",G+"px");}}});this.setAttributeConfig("width",{value:F.width,validator:E.isNumber,method:function(G){if(!this._collapsing){if(G<0){G=0;}this.setStyle("width",G+"px");}}});this.setAttributeConfig("zIndex",{value:F.zIndex||false,method:function(G){this.setStyle("zIndex",G);}});this.setAttributeConfig("position",{value:F.position});this.setAttributeConfig("gutter",{value:F.gutter||0,validator:YAHOO.lang.isString,method:function(H){var G=H.split(" ");if(G.length){this._gutter.top=parseInt(G[0],10);if(G[1]){this._gutter.right=parseInt(G[1],10);}else{this._gutter.right=this._gutter.top;}if(G[2]){this._gutter.bottom=parseInt(G[2],10);}else{this._gutter.bottom=this._gutter.top;}if(G[3]){this._gutter.left=parseInt(G[3],10);}else{if(G[1]){this._gutter.left=this._gutter.right;}else{this._gutter.left=this._gutter.top;}}}}});this.setAttributeConfig("parent",{writeOnce:true,value:F.parent||false,method:function(G){if(G){G.on("resize",this.resize,this,true);}}});this.setAttributeConfig("collapseSize",{value:F.collapseSize||25,validator:YAHOO.lang.isNumber});this.setAttributeConfig("duration",{value:F.duration||0.5});this.setAttributeConfig("easing",{value:F.easing||((YAHOO.util&&YAHOO.util.Easing)?YAHOO.util.Easing.BounceIn:"false")});this.setAttributeConfig("animate",{value:((F.animate===false)?false:true),validator:function(){var G=false;if(YAHOO.util.Anim){G=true;}return G;},method:function(G){if(G){this._anim=new YAHOO.util.Anim(this.get("element"),{},this.get("duration"),this.get("easing"));}else{this._anim=false;}}});this.setAttributeConfig("header",{value:F.header||false,method:function(G){if(G===false){if(this.header){D.addClass(this.body,"yui-layout-bd-nohd");this.header.parentNode.removeChild(this.header);this.header=null;}}else{if(!this.header){var I=this.getElementsByClassName("yui-layout-hd","div")[0];if(!I){I=this._createHeader();}this.header=I;}var H=this.header.getElementsByTagName("h2")[0];if(!H){H=document.createElement("h2");this.header.appendChild(H);}H.innerHTML=G;if(this.body){D.removeClass(this.body,"yui-layout-bd-nohd");}}this.fireEvent("contentChange",{target:"header"});}});this.setAttributeConfig("proxy",{writeOnce:true,value:((F.proxy===false)?false:true)});this.setAttributeConfig("body",{value:F.body||false,method:function(I){if(!this.body){var G=this.getElementsByClassName("yui-layout-bd","div")[0];if(G){this.body=G;}else{G=document.createElement("div");G.className="yui-layout-bd";this.body=G;this.get("wrap").appendChild(G);}}if(!this.header){D.addClass(this.body,"yui-layout-bd-nohd");}D.addClass(this.body,"yui-layout-bd-noft");var H=null;if(E.isString(I)){H=D.get(I);}else{if(I&&I.tagName){H=I;}}if(H){var J=D.generateId(H);B._instances[J]=this;this.body.appendChild(H);}else{this.body.innerHTML=I;}this._cleanGrids();this.fireEvent("contentChange",{target:"body"});}});this.setAttributeConfig("footer",{value:F.footer||false,method:function(H){if(H===false){if(this.footer){D.addClass(this.body,"yui-layout-bd-noft");this.footer.parentNode.removeChild(this.footer);this.footer=null;}}else{if(!this.footer){var I=this.getElementsByClassName("yui-layout-ft","div")[0];if(!I){I=document.createElement("div");I.className="yui-layout-ft";this.footer=I;this.get("wrap").appendChild(I);}else{this.footer=I;}}var G=null;if(E.isString(H)){G=D.get(H);}else{if(H&&H.tagName){G=H;}}if(G){this.footer.appendChild(G);}else{this.footer.innerHTML=H;}D.removeClass(this.body,"yui-layout-bd-noft");}this.fireEvent("contentChange",{target:"footer"});}});this.setAttributeConfig("close",{value:F.close||false,method:function(G){if(this.get("position")=="center"){return false;}if(!this.header&&G){this._createHeader();}var H=D.getElementsByClassName("close","div",this.header)[0];if(G){if(!this.get("header")){this.set("header","&nbsp;");}if(!H){H=document.createElement("div");H.className="close";this.header.appendChild(H);A.on(H,"click",this.close,this,true);}H.title=this.STR_CLOSE;}else{if(H&&H.parentNode){A.purgeElement(H);H.parentNode.removeChild(H);}}this._configs.close.value=G;this.set("collapse",this.get("collapse"));}});this.setAttributeConfig("collapse",{value:F.collapse||false,method:function(G){if(this.get("position")=="center"){return false;}if(!this.header&&G){this._createHeader();}var H=D.getElementsByClassName("collapse","div",this.header)[0];if(G){if(!this.get("header")){this.set("header","&nbsp;");
+}if(!H){H=document.createElement("div");this.header.appendChild(H);A.on(H,"click",this.collapse,this,true);}H.title=this.STR_COLLAPSE;H.className="collapse"+((this.get("close"))?" collapse-close":"");}else{if(H&&H.parentNode){A.purgeElement(H);H.parentNode.removeChild(H);}}}});this.setAttributeConfig("scroll",{value:(((F.scroll===true)||(F.scroll===false)||(F.scroll===null))?F.scroll:false),method:function(G){if((G===false)&&!this._collapsed){if(this.body){if(this.body.scrollTop>0){this._lastScrollTop=this.body.scrollTop;}}}if(G===true){this.addClass("yui-layout-scroll");this.removeClass("yui-layout-noscroll");if(this._lastScrollTop>0){if(this.body){this.body.scrollTop=this._lastScrollTop;}}}else{if(G===false){this.removeClass("yui-layout-scroll");this.addClass("yui-layout-noscroll");}else{if(G===null){this.removeClass("yui-layout-scroll");this.removeClass("yui-layout-noscroll");}}}}});this.setAttributeConfig("hover",{writeOnce:true,value:F.hover||false,validator:YAHOO.lang.isBoolean});this.setAttributeConfig("useShim",{value:F.useShim||false,validator:YAHOO.lang.isBoolean,method:function(G){if(this._resize){this._resize.set("useShim",G);}}});this.setAttributeConfig("resize",{value:F.resize||false,validator:function(G){if(YAHOO.util&&YAHOO.util.Resize){return true;}return false;},method:function(G){if(G&&!this._resize){if(this.get("position")=="center"){return false;}var I=false;switch(this.get("position")){case"top":I="b";break;case"bottom":I="t";break;case"right":I="l";break;case"left":I="r";break;}this.setStyle("position","absolute");if(I){this._resize=new YAHOO.util.Resize(this.get("element"),{proxy:this.get("proxy"),hover:this.get("hover"),status:false,autoRatio:false,handles:[I],minWidth:this.get("minWidth"),maxWidth:this.get("maxWidth"),minHeight:this.get("minHeight"),maxHeight:this.get("maxHeight"),height:this.get("height"),width:this.get("width"),setSize:false,useShim:this.get("useShim"),wrap:false});this._resize._handles[I].innerHTML='<div class="yui-layout-resize-knob"></div>';if(this.get("proxy")){var H=this._resize.getProxyEl();H.innerHTML='<div class="yui-layout-handle-'+I+'"></div>';}this._resize.on("startResize",function(J){this._lastScroll=this.get("scroll");this.set("scroll",false);if(this.get("parent")){this.get("parent").fireEvent("startResize");var K=this.get("parent").getUnitByPosition("center");this._lastCenterScroll=K.get("scroll");K.addClass(this._resize.CSS_RESIZING);K.set("scroll",false);}this.fireEvent("startResize");},this,true);this._resize.on("resize",function(J){this.set("height",J.height);this.set("width",J.width);},this,true);this._resize.on("endResize",function(J){this.set("scroll",this._lastScroll);if(this.get("parent")){var K=this.get("parent").getUnitByPosition("center");K.set("scroll",this._lastCenterScroll);K.removeClass(this._resize.CSS_RESIZING);}this.resize();this.fireEvent("endResize");},this,true);}}else{if(this._resize){this._resize.destroy();}}}});this.setAttributeConfig("dataSrc",{value:F.dataSrc});this.setAttributeConfig("loadMethod",{value:F.loadMethod||"GET",validator:YAHOO.lang.isString});this.setAttributeConfig("dataLoaded",{value:false,validator:YAHOO.lang.isBoolean,writeOnce:true});this.setAttributeConfig("dataTimeout",{value:F.dataTimeout||null,validator:YAHOO.lang.isNumber});},_cleanGrids:function(){if(this.get("grids")){var F=C.query("div.yui-b",this.body,true);if(F){D.removeClass(F,"yui-b");}A.onAvailable("yui-main",function(){D.setStyle(C.query("#yui-main"),"margin-left","0");D.setStyle(C.query("#yui-main"),"margin-right","0");});}},_createHeader:function(){var F=document.createElement("div");F.className="yui-layout-hd";if(this.get("firstChild")){this.get("wrap").insertBefore(F,this.get("wrap").firstChild);}else{this.get("wrap").appendChild(F);}this.header=F;return F;},destroy:function(H){if(this._resize){this._resize.destroy();}var G=this.get("parent");this.setStyle("display","none");if(this._clip){this._clip.parentNode.removeChild(this._clip);this._clip=null;}if(!H){G.removeUnit(this);}if(G){G.removeListener("resize",this.resize,this,true);}this.unsubscribeAll();A.purgeElement(this.get("element"));this.get("parentNode").removeChild(this.get("element"));delete YAHOO.widget.LayoutUnit._instances[this.get("id")];for(var F in this){if(E.hasOwnProperty(this,F)){this[F]=null;delete this[F];}}return G;},toString:function(){if(this.get){return"LayoutUnit #"+this.get("id")+" ("+this.get("position")+")";}return"LayoutUnit";}});YAHOO.widget.LayoutUnit=B;})();YAHOO.register("layout",YAHOO.widget.Layout,{version:"2.8.2r1",build:"7"}); \ No newline at end of file
diff --git a/js/yui/logger/logger-min.js b/js/yui/logger/logger-min.js
new file mode 100644
index 000000000..3561c0015
--- /dev/null
+++ b/js/yui/logger/logger-min.js
@@ -0,0 +1,9 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+YAHOO.widget.LogMsg=function(A){this.msg=this.time=this.category=this.source=this.sourceDetail=null;if(A&&(A.constructor==Object)){for(var B in A){if(A.hasOwnProperty(B)){this[B]=A[B];}}}};YAHOO.widget.LogWriter=function(A){if(!A){YAHOO.log("Could not instantiate LogWriter due to invalid source.","error","LogWriter");return;}this._source=A;};YAHOO.widget.LogWriter.prototype.toString=function(){return"LogWriter "+this._sSource;};YAHOO.widget.LogWriter.prototype.log=function(A,B){YAHOO.widget.Logger.log(A,B,this._source);};YAHOO.widget.LogWriter.prototype.getSource=function(){return this._source;};YAHOO.widget.LogWriter.prototype.setSource=function(A){if(!A){YAHOO.log("Could not set source due to invalid source.","error",this.toString());return;}else{this._source=A;}};YAHOO.widget.LogWriter.prototype._source=null;if(!YAHOO.widget.Logger){YAHOO.widget.Logger={loggerEnabled:true,_browserConsoleEnabled:false,categories:["info","warn","error","time","window"],sources:["global"],_stack:[],maxStackEntries:2500,_startTime:new Date().getTime(),_lastTime:null,_windowErrorsHandled:false,_origOnWindowError:null};YAHOO.widget.Logger.log=function(B,F,G){if(this.loggerEnabled){if(!F){F="info";}else{F=F.toLocaleLowerCase();if(this._isNewCategory(F)){this._createNewCategory(F);}}var C="global";var A=null;if(G){var D=G.indexOf(" ");if(D>0){C=G.substring(0,D);A=G.substring(D,G.length);}else{C=G;}if(this._isNewSource(C)){this._createNewSource(C);}}var H=new Date();var J=new YAHOO.widget.LogMsg({msg:B,time:H,category:F,source:C,sourceDetail:A});var I=this._stack;var E=this.maxStackEntries;if(E&&!isNaN(E)&&(I.length>=E)){I.shift();}I.push(J);this.newLogEvent.fire(J);if(this._browserConsoleEnabled){this._printToBrowserConsole(J);}return true;}else{return false;}};YAHOO.widget.Logger.reset=function(){this._stack=[];this._startTime=new Date().getTime();this.loggerEnabled=true;this.log("Logger reset");this.logResetEvent.fire();};YAHOO.widget.Logger.getStack=function(){return this._stack;};YAHOO.widget.Logger.getStartTime=function(){return this._startTime;};YAHOO.widget.Logger.disableBrowserConsole=function(){YAHOO.log("Logger output to the function console.log() has been disabled.");this._browserConsoleEnabled=false;};YAHOO.widget.Logger.enableBrowserConsole=function(){this._browserConsoleEnabled=true;YAHOO.log("Logger output to the function console.log() has been enabled.");};YAHOO.widget.Logger.handleWindowErrors=function(){if(!YAHOO.widget.Logger._windowErrorsHandled){if(window.error){YAHOO.widget.Logger._origOnWindowError=window.onerror;}window.onerror=YAHOO.widget.Logger._onWindowError;YAHOO.widget.Logger._windowErrorsHandled=true;YAHOO.log("Logger handling of window.onerror has been enabled.");}else{YAHOO.log("Logger handling of window.onerror had already been enabled.");}};YAHOO.widget.Logger.unhandleWindowErrors=function(){if(YAHOO.widget.Logger._windowErrorsHandled){if(YAHOO.widget.Logger._origOnWindowError){window.onerror=YAHOO.widget.Logger._origOnWindowError;YAHOO.widget.Logger._origOnWindowError=null;}else{window.onerror=null;}YAHOO.widget.Logger._windowErrorsHandled=false;YAHOO.log("Logger handling of window.onerror has been disabled.");}else{YAHOO.log("Logger handling of window.onerror had already been disabled.");}};YAHOO.widget.Logger.categoryCreateEvent=new YAHOO.util.CustomEvent("categoryCreate",this,true);YAHOO.widget.Logger.sourceCreateEvent=new YAHOO.util.CustomEvent("sourceCreate",this,true);YAHOO.widget.Logger.newLogEvent=new YAHOO.util.CustomEvent("newLog",this,true);YAHOO.widget.Logger.logResetEvent=new YAHOO.util.CustomEvent("logReset",this,true);YAHOO.widget.Logger._createNewCategory=function(A){this.categories.push(A);this.categoryCreateEvent.fire(A);};YAHOO.widget.Logger._isNewCategory=function(B){for(var A=0;A<this.categories.length;A++){if(B==this.categories[A]){return false;}}return true;};YAHOO.widget.Logger._createNewSource=function(A){this.sources.push(A);this.sourceCreateEvent.fire(A);};YAHOO.widget.Logger._isNewSource=function(A){if(A){for(var B=0;B<this.sources.length;B++){if(A==this.sources[B]){return false;}}return true;}};YAHOO.widget.Logger._printToBrowserConsole=function(C){if(window.console&&console.log){var E=C.category;var D=C.category.substring(0,4).toUpperCase();var G=C.time;var F;if(G.toLocaleTimeString){F=G.toLocaleTimeString();}else{F=G.toString();}var H=G.getTime();var B=(YAHOO.widget.Logger._lastTime)?(H-YAHOO.widget.Logger._lastTime):0;YAHOO.widget.Logger._lastTime=H;var A=F+" ("+B+"ms): "+C.source+": ";if(YAHOO.env.ua.webkit){A+=C.msg;}console.log(A,C.msg);}};YAHOO.widget.Logger._onWindowError=function(A,C,B){try{YAHOO.widget.Logger.log(A+" ("+C+", line "+B+")","window");if(YAHOO.widget.Logger._origOnWindowError){YAHOO.widget.Logger._origOnWindowError();}}catch(D){return false;}};YAHOO.widget.Logger.log("Logger initialized");}(function(){var C=YAHOO.widget.Logger,D=YAHOO.util,E=D.Dom,A=D.Event,G=document;function B(I,H){I=G.createElement(I);if(H){for(var J in H){if(H.hasOwnProperty(J)){I[J]=H[J];}}}return I;}function F(I,H){this._sName=F._index;F._index++;this._init.apply(this,arguments);if(this.autoRender!==false){this.render();}}YAHOO.lang.augmentObject(F,{_index:0,ENTRY_TEMPLATE:(function(){return B("pre",{className:"yui-log-entry"});})(),VERBOSE_TEMPLATE:"<p><span class='{category}'>{label}</span> {totalTime}ms (+{elapsedTime}) {localTime}:</p><p>{sourceAndDetail}</p><p>{message}</p>",BASIC_TEMPLATE:"<p><span class='{category}'>{label}</span> {totalTime}ms (+{elapsedTime}) {localTime}: {sourceAndDetail}: {message}</p>"});F.prototype={logReaderEnabled:true,width:null,height:null,top:null,left:null,right:null,bottom:null,fontSize:null,footerEnabled:true,verboseOutput:true,entryFormat:null,newestOnTop:true,outputBuffer:100,thresholdMax:500,thresholdMin:100,isCollapsed:false,isPaused:false,draggable:true,toString:function(){return"LogReader instance"+this._sName;},pause:function(){this.isPaused=true;this._timeout=null;this.logReaderEnabled=false;if(this._btnPause){this._btnPause.value="Resume";
+}},resume:function(){this.isPaused=false;this.logReaderEnabled=true;this._printBuffer();if(this._btnPause){this._btnPause.value="Pause";}},render:function(){if(this.rendered){return;}this._initContainerEl();this._initHeaderEl();this._initConsoleEl();this._initFooterEl();this._initCategories();this._initSources();this._initDragDrop();C.newLogEvent.subscribe(this._onNewLog,this);C.logResetEvent.subscribe(this._onReset,this);C.categoryCreateEvent.subscribe(this._onCategoryCreate,this);C.sourceCreateEvent.subscribe(this._onSourceCreate,this);this.rendered=true;this._filterLogs();},destroy:function(){A.purgeElement(this._elContainer,true);this._elContainer.innerHTML="";this._elContainer.parentNode.removeChild(this._elContainer);this.rendered=false;},hide:function(){this._elContainer.style.display="none";},show:function(){this._elContainer.style.display="block";},collapse:function(){this._elConsole.style.display="none";if(this._elFt){this._elFt.style.display="none";}this._btnCollapse.value="Expand";this.isCollapsed=true;},expand:function(){this._elConsole.style.display="block";if(this._elFt){this._elFt.style.display="block";}this._btnCollapse.value="Collapse";this.isCollapsed=false;},getCheckbox:function(H){return this._filterCheckboxes[H];},getCategories:function(){return this._categoryFilters;},showCategory:function(I){var K=this._categoryFilters;if(K.indexOf){if(K.indexOf(I)>-1){return;}}else{for(var H=0;H<K.length;H++){if(K[H]===I){return;}}}this._categoryFilters.push(I);this._filterLogs();var J=this.getCheckbox(I);if(J){J.checked=true;}},hideCategory:function(I){var K=this._categoryFilters;for(var H=0;H<K.length;H++){if(I==K[H]){K.splice(H,1);break;}}this._filterLogs();var J=this.getCheckbox(I);if(J){J.checked=false;}},getSources:function(){return this._sourceFilters;},showSource:function(H){var K=this._sourceFilters;if(K.indexOf){if(K.indexOf(H)>-1){return;}}else{for(var I=0;I<K.length;I++){if(H==K[I]){return;}}}K.push(H);this._filterLogs();var J=this.getCheckbox(H);if(J){J.checked=true;}},hideSource:function(H){var K=this._sourceFilters;for(var I=0;I<K.length;I++){if(H==K[I]){K.splice(I,1);break;}}this._filterLogs();var J=this.getCheckbox(H);if(J){J.checked=false;}},clearConsole:function(){this._timeout=null;this._buffer=[];this._consoleMsgCount=0;var H=this._elConsole;H.innerHTML="";},setTitle:function(H){this._title.innerHTML=this.html2Text(H);},getLastTime:function(){return this._lastTime;},formatMsg:function(I){var H=this.entryFormat||(this.verboseOutput?F.VERBOSE_TEMPLATE:F.BASIC_TEMPLATE),J={category:I.category,label:I.category.substring(0,4).toUpperCase(),sourceAndDetail:I.sourceDetail?I.source+" "+I.sourceDetail:I.source,message:this.html2Text(I.msg||I.message||"")};if(I.time&&I.time.getTime){J.localTime=I.time.toLocaleTimeString?I.time.toLocaleTimeString():I.time.toString();J.elapsedTime=I.time.getTime()-this.getLastTime();J.totalTime=I.time.getTime()-C.getStartTime();}var K=F.ENTRY_TEMPLATE.cloneNode(true);if(this.verboseOutput){K.className+=" yui-log-verbose";}K.innerHTML=H.replace(/\{(\w+)\}/g,function(L,M){return(M in J)?J[M]:"";});return K;},html2Text:function(H){if(H){H+="";return H.replace(/&/g,"&#38;").replace(/</g,"&#60;").replace(/>/g,"&#62;");}return"";},_sName:null,_buffer:null,_consoleMsgCount:0,_lastTime:null,_timeout:null,_filterCheckboxes:null,_categoryFilters:null,_sourceFilters:null,_elContainer:null,_elHd:null,_elCollapse:null,_btnCollapse:null,_title:null,_elConsole:null,_elFt:null,_elBtns:null,_elCategoryFilters:null,_elSourceFilters:null,_btnPause:null,_btnClear:null,_init:function(H,I){this._buffer=[];this._filterCheckboxes={};this._lastTime=C.getStartTime();if(I&&(I.constructor==Object)){for(var J in I){if(I.hasOwnProperty(J)){this[J]=I[J];}}}this._elContainer=E.get(H);YAHOO.log("LogReader initialized",null,this.toString());},_initContainerEl:function(){if(!this._elContainer||!/div$/i.test(this._elContainer.tagName)){this._elContainer=G.body.insertBefore(B("div"),G.body.firstChild);E.addClass(this._elContainer,"yui-log-container");}E.addClass(this._elContainer,"yui-log");var J=this._elContainer.style,H=["width","right","top","fontSize"],K,I;for(I=H.length-1;I>=0;--I){K=H[I];if(this[K]){J[K]=this[K];}}if(this.left){J.left=this.left;J.right="auto";}if(this.bottom){J.bottom=this.bottom;J.top="auto";}if(YAHOO.env.ua.opera){G.body.style+="";}},_initHeaderEl:function(){if(this._elHd){A.purgeElement(this._elHd,true);this._elHd.innerHTML="";}this._elHd=B("div",{id:"yui-log-hd"+this._sName,className:"yui-log-hd"});this._elCollapse=B("div",{className:"yui-log-btns"});this._btnCollapse=B("input",{type:"button",className:"yui-log-button",value:"Collapse"});A.on(this._btnCollapse,"click",this._onClickCollapseBtn,this);this._title=B("h4",{innerHTML:"Logger Console"});this._elCollapse.appendChild(this._btnCollapse);this._elHd.appendChild(this._elCollapse);this._elHd.appendChild(this._title);this._elContainer.appendChild(this._elHd);},_initConsoleEl:function(){if(this._elConsole){A.purgeElement(this._elConsole,true);this._elConsole.innerHTML="";}this._elConsole=B("div",{className:"yui-log-bd"});if(this.height){this._elConsole.style.height=this.height;}this._elContainer.appendChild(this._elConsole);},_initFooterEl:function(){if(this.footerEnabled){if(this._elFt){A.purgeElement(this._elFt,true);this._elFt.innerHTML="";}this._elFt=B("div",{className:"yui-log-ft"});this._elBtns=B("div",{className:"yui-log-btns"});this._btnPause=B("input",{type:"button",className:"yui-log-button",value:"Pause"});A.on(this._btnPause,"click",this._onClickPauseBtn,this);this._btnClear=B("input",{type:"button",className:"yui-log-button",value:"Clear"});A.on(this._btnClear,"click",this._onClickClearBtn,this);this._elCategoryFilters=B("div",{className:"yui-log-categoryfilters"});this._elSourceFilters=B("div",{className:"yui-log-sourcefilters"});this._elBtns.appendChild(this._btnPause);this._elBtns.appendChild(this._btnClear);this._elFt.appendChild(this._elBtns);this._elFt.appendChild(this._elCategoryFilters);
+this._elFt.appendChild(this._elSourceFilters);this._elContainer.appendChild(this._elFt);}},_initDragDrop:function(){if(D.DD&&this.draggable&&this._elHd){var H=new D.DD(this._elContainer);H.setHandleElId(this._elHd.id);this._elHd.style.cursor="move";}},_initCategories:function(){this._categoryFilters=[];var J=C.categories;for(var H=0;H<J.length;H++){var I=J[H];this._categoryFilters.push(I);if(this._elCategoryFilters){this._createCategoryCheckbox(I);}}},_initSources:function(){this._sourceFilters=[];var J=C.sources;for(var I=0;I<J.length;I++){var H=J[I];this._sourceFilters.push(H);if(this._elSourceFilters){this._createSourceCheckbox(H);}}},_createCategoryCheckbox:function(K){if(this._elFt){var J=B("span",{className:"yui-log-filtergrp"}),H=B("input",{id:"yui-log-filter-"+K+this._sName,className:"yui-log-filter-"+K,type:"checkbox",category:K}),I=B("label",{htmlFor:H.id,className:K,innerHTML:K});A.on(H,"click",this._onCheckCategory,this);this._filterCheckboxes[K]=H;J.appendChild(H);J.appendChild(I);this._elCategoryFilters.appendChild(J);H.checked=true;}},_createSourceCheckbox:function(H){if(this._elFt){var K=B("span",{className:"yui-log-filtergrp"}),I=B("input",{id:"yui-log-filter-"+H+this._sName,className:"yui-log-filter-"+H,type:"checkbox",source:H}),J=B("label",{htmlFor:I.id,className:H,innerHTML:H});A.on(I,"click",this._onCheckSource,this);this._filterCheckboxes[H]=I;K.appendChild(I);K.appendChild(J);this._elSourceFilters.appendChild(K);I.checked=true;}},_filterLogs:function(){if(this._elConsole!==null){this.clearConsole();this._printToConsole(C.getStack());}},_printBuffer:function(){this._timeout=null;if(this._elConsole!==null){var I=this.thresholdMax;I=(I&&!isNaN(I))?I:500;if(this._consoleMsgCount<I){var H=[];for(var J=0;J<this._buffer.length;J++){H[J]=this._buffer[J];}this._buffer=[];this._printToConsole(H);}else{this._filterLogs();}if(!this.newestOnTop){this._elConsole.scrollTop=this._elConsole.scrollHeight;}}},_printToConsole:function(P){var I=P.length,T=G.createDocumentFragment(),W=[],X=this.thresholdMin,J=this._sourceFilters.length,U=this._categoryFilters.length,R,O,N,M,S;if(isNaN(X)||(X>this.thresholdMax)){X=0;}R=(I>X)?(I-X):0;for(O=R;O<I;O++){var L=false,Q=false,V=P[O],H=V.source,K=V.category;for(N=0;N<J;N++){if(H==this._sourceFilters[N]){Q=true;break;}}if(Q){for(N=0;N<U;N++){if(K==this._categoryFilters[N]){L=true;break;}}}if(L){if(this._consoleMsgCount===0){this._lastTime=V.time.getTime();}M=this.formatMsg(V);if(typeof M==="string"){W[W.length]=M;}else{T.insertBefore(M,this.newestOnTop?T.firstChild||null:null);}this._consoleMsgCount++;this._lastTime=V.time.getTime();}}if(W.length){W.splice(0,0,this._elConsole.innerHTML);this._elConsole.innerHTML=this.newestOnTop?W.reverse().join(""):W.join("");}else{if(T.firstChild){this._elConsole.insertBefore(T,this.newestOnTop?this._elConsole.firstChild||null:null);}}},_onCategoryCreate:function(K,J,H){var I=J[0];H._categoryFilters.push(I);if(H._elFt){H._createCategoryCheckbox(I);}},_onSourceCreate:function(K,J,H){var I=J[0];H._sourceFilters.push(I);if(H._elFt){H._createSourceCheckbox(I);}},_onCheckCategory:function(H,I){var J=this.category;if(!this.checked){I.hideCategory(J);}else{I.showCategory(J);}},_onCheckSource:function(H,I){var J=this.source;if(!this.checked){I.hideSource(J);}else{I.showSource(J);}},_onClickCollapseBtn:function(H,I){if(!I.isCollapsed){I.collapse();}else{I.expand();}},_onClickPauseBtn:function(H,I){if(!I.isPaused){I.pause();}else{I.resume();}},_onClickClearBtn:function(H,I){I.clearConsole();},_onNewLog:function(K,J,H){var I=J[0];H._buffer.push(I);if(H.logReaderEnabled===true&&H._timeout===null){H._timeout=setTimeout(function(){H._printBuffer();},H.outputBuffer);}},_onReset:function(J,I,H){H._filterLogs();}};YAHOO.widget.LogReader=F;})();YAHOO.register("logger",YAHOO.widget.Logger,{version:"2.8.2r1",build:"7"}); \ No newline at end of file
diff --git a/js/yui/menu/menu-min.js b/js/yui/menu/menu-min.js
new file mode 100644
index 000000000..a0b15a56b
--- /dev/null
+++ b/js/yui/menu/menu-min.js
@@ -0,0 +1,16 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+(function(){var K=YAHOO.env.ua,C=YAHOO.util.Dom,Z=YAHOO.util.Event,H=YAHOO.lang,T="DIV",P="hd",M="bd",O="ft",X="LI",A="disabled",D="mouseover",F="mouseout",U="mousedown",G="mouseup",V="click",B="keydown",N="keyup",I="keypress",L="clicktohide",S="position",Q="dynamic",Y="showdelay",J="selected",E="visible",W="UL",R="MenuManager";YAHOO.widget.MenuManager=function(){var l=false,d={},o={},h={},c={"click":"clickEvent","mousedown":"mouseDownEvent","mouseup":"mouseUpEvent","mouseover":"mouseOverEvent","mouseout":"mouseOutEvent","keydown":"keyDownEvent","keyup":"keyUpEvent","keypress":"keyPressEvent","focus":"focusEvent","focusin":"focusEvent","blur":"blurEvent","focusout":"blurEvent"},i=null;function b(r){var p,q;if(r&&r.tagName){switch(r.tagName.toUpperCase()){case T:p=r.parentNode;if((C.hasClass(r,P)||C.hasClass(r,M)||C.hasClass(r,O))&&p&&p.tagName&&p.tagName.toUpperCase()==T){q=p;}else{q=r;}break;case X:q=r;break;default:p=r.parentNode;if(p){q=b(p);}break;}}return q;}function e(t){var p=Z.getTarget(t),q=b(p),u=true,w=t.type,x,r,s,z,y;if(q){r=q.tagName.toUpperCase();if(r==X){s=q.id;if(s&&h[s]){z=h[s];y=z.parent;}}else{if(r==T){if(q.id){y=d[q.id];}}}}if(y){x=c[w];if(w=="click"&&(K.gecko&&y.platform!="mac")&&t.button>0){u=false;}if(u&&z&&!z.cfg.getProperty(A)){z[x].fire(t);}if(u){y[x].fire(t,z);}}else{if(w==U){for(var v in o){if(H.hasOwnProperty(o,v)){y=o[v];if(y.cfg.getProperty(L)&&!(y instanceof YAHOO.widget.MenuBar)&&y.cfg.getProperty(S)==Q){y.hide();if(K.ie&&p.focus){p.setActive();}}else{if(y.cfg.getProperty(Y)>0){y._cancelShowDelay();}if(y.activeItem){y.activeItem.blur();y.activeItem.cfg.setProperty(J,false);y.activeItem=null;}}}}}}}function n(q,p,r){if(d[r.id]){this.removeMenu(r);}}function k(q,p){var r=p[1];if(r){i=r;}}function f(q,p){i=null;}function a(r,q){var p=q[0],s=this.id;if(p){o[s]=this;}else{if(o[s]){delete o[s];}}}function j(q,p){m(this);}function m(q){var p=q.id;if(p&&h[p]){if(i==q){i=null;}delete h[p];q.destroyEvent.unsubscribe(j);}}function g(q,p){var s=p[0],r;if(s instanceof YAHOO.widget.MenuItem){r=s.id;if(!h[r]){h[r]=s;s.destroyEvent.subscribe(j);}}}return{addMenu:function(q){var p;if(q instanceof YAHOO.widget.Menu&&q.id&&!d[q.id]){d[q.id]=q;if(!l){p=document;Z.on(p,D,e,this,true);Z.on(p,F,e,this,true);Z.on(p,U,e,this,true);Z.on(p,G,e,this,true);Z.on(p,V,e,this,true);Z.on(p,B,e,this,true);Z.on(p,N,e,this,true);Z.on(p,I,e,this,true);Z.onFocus(p,e,this,true);Z.onBlur(p,e,this,true);l=true;}q.cfg.subscribeToConfigEvent(E,a);q.destroyEvent.subscribe(n,q,this);q.itemAddedEvent.subscribe(g);q.focusEvent.subscribe(k);q.blurEvent.subscribe(f);}},removeMenu:function(s){var q,p,r;if(s){q=s.id;if((q in d)&&(d[q]==s)){p=s.getItems();if(p&&p.length>0){r=p.length-1;do{m(p[r]);}while(r--);}delete d[q];if((q in o)&&(o[q]==s)){delete o[q];}if(s.cfg){s.cfg.unsubscribeFromConfigEvent(E,a);}s.destroyEvent.unsubscribe(n,s);s.itemAddedEvent.unsubscribe(g);s.focusEvent.unsubscribe(k);s.blurEvent.unsubscribe(f);}}},hideVisible:function(){var p;for(var q in o){if(H.hasOwnProperty(o,q)){p=o[q];if(!(p instanceof YAHOO.widget.MenuBar)&&p.cfg.getProperty(S)==Q){p.hide();}}}},getVisible:function(){return o;},getMenus:function(){return d;},getMenu:function(q){var p;if(q in d){p=d[q];}return p;},getMenuItem:function(q){var p;if(q in h){p=h[q];}return p;},getMenuItemGroup:function(t){var q=C.get(t),p,v,u,r,s;if(q&&q.tagName&&q.tagName.toUpperCase()==W){v=q.firstChild;if(v){p=[];do{r=v.id;if(r){u=this.getMenuItem(r);if(u){p[p.length]=u;}}}while((v=v.nextSibling));if(p.length>0){s=p;}}}return s;},getFocusedMenuItem:function(){return i;},getFocusedMenu:function(){var p;if(i){p=i.parent.getRoot();}return p;},toString:function(){return R;}};}();})();(function(){var AM=YAHOO.lang,Aq="Menu",G="DIV",K="div",Am="id",AH="SELECT",e="xy",R="y",Ax="UL",L="ul",AJ="first-of-type",k="LI",h="OPTGROUP",Az="OPTION",Ah="disabled",AY="none",y="selected",At="groupindex",i="index",O="submenu",Au="visible",AX="hidedelay",Ac="position",AD="dynamic",C="static",An=AD+","+C,Q="url",M="#",V="target",AU="maxheight",T="topscrollbar",x="bottomscrollbar",d="_",P=T+d+Ah,E=x+d+Ah,b="mousemove",Av="showdelay",c="submenuhidedelay",AF="iframe",w="constraintoviewport",A4="preventcontextoverlap",AO="submenualignment",Z="autosubmenudisplay",AC="clicktohide",g="container",j="scrollincrement",Aj="minscrollheight",A2="classname",Ag="shadow",Ar="keepopen",A0="hd",D="hastitle",p="context",u="",Ak="mousedown",Ae="keydown",Ao="height",U="width",AQ="px",Ay="effect",AE="monitorresize",AW="display",AV="block",J="visibility",z="absolute",AS="zindex",l="yui-menu-body-scrolled",AK="&#32;",A1=" ",Ai="mouseover",H="mouseout",AR="itemAdded",n="itemRemoved",AL="hidden",s="yui-menu-shadow",AG=s+"-visible",m=s+A1+AG;YAHOO.widget.Menu=function(A6,A5){if(A5){this.parent=A5.parent;this.lazyLoad=A5.lazyLoad||A5.lazyload;this.itemData=A5.itemData||A5.itemdata;}YAHOO.widget.Menu.superclass.constructor.call(this,A6,A5);};function B(A6){var A5=false;if(AM.isString(A6)){A5=(An.indexOf((A6.toLowerCase()))!=-1);}return A5;}var f=YAHOO.util.Dom,AA=YAHOO.util.Event,Aw=YAHOO.widget.Module,AB=YAHOO.widget.Overlay,r=YAHOO.widget.Menu,A3=YAHOO.widget.MenuManager,F=YAHOO.util.CustomEvent,As=YAHOO.env.ua,Ap,AT=false,Ad,Ab=[["mouseOverEvent",Ai],["mouseOutEvent",H],["mouseDownEvent",Ak],["mouseUpEvent","mouseup"],["clickEvent","click"],["keyPressEvent","keypress"],["keyDownEvent",Ae],["keyUpEvent","keyup"],["focusEvent","focus"],["blurEvent","blur"],["itemAddedEvent",AR],["itemRemovedEvent",n]],AZ={key:Au,value:false,validator:AM.isBoolean},AP={key:w,value:true,validator:AM.isBoolean,supercedes:[AF,"x",R,e]},AI={key:A4,value:true,validator:AM.isBoolean,supercedes:[w]},S={key:Ac,value:AD,validator:B,supercedes:[Au,AF]},A={key:AO,value:["tl","tr"]},t={key:Z,value:true,validator:AM.isBoolean,suppressEvent:true},Y={key:Av,value:250,validator:AM.isNumber,suppressEvent:true},q={key:AX,value:0,validator:AM.isNumber,suppressEvent:true},v={key:c,value:250,validator:AM.isNumber,suppressEvent:true},o={key:AC,value:true,validator:AM.isBoolean,suppressEvent:true},AN={key:g,suppressEvent:true},Af={key:j,value:1,validator:AM.isNumber,supercedes:[AU],suppressEvent:true},N={key:Aj,value:90,validator:AM.isNumber,supercedes:[AU],suppressEvent:true},X={key:AU,value:0,validator:AM.isNumber,supercedes:[AF],suppressEvent:true},W={key:A2,value:null,validator:AM.isString,suppressEvent:true},a={key:Ah,value:false,validator:AM.isBoolean,suppressEvent:true},I={key:Ag,value:true,validator:AM.isBoolean,suppressEvent:true,supercedes:[Au]},Al={key:Ar,value:false,validator:AM.isBoolean};
+function Aa(A5){Ad=AA.getTarget(A5);}YAHOO.lang.extend(r,AB,{CSS_CLASS_NAME:"yuimenu",ITEM_TYPE:null,GROUP_TITLE_TAG_NAME:"h6",OFF_SCREEN_POSITION:"-999em",_useHideDelay:false,_bHandledMouseOverEvent:false,_bHandledMouseOutEvent:false,_aGroupTitleElements:null,_aItemGroups:null,_aListElements:null,_nCurrentMouseX:0,_bStopMouseEventHandlers:false,_sClassName:null,lazyLoad:false,itemData:null,activeItem:null,parent:null,srcElement:null,init:function(A7,A6){this._aItemGroups=[];this._aListElements=[];this._aGroupTitleElements=[];if(!this.ITEM_TYPE){this.ITEM_TYPE=YAHOO.widget.MenuItem;}var A5;if(AM.isString(A7)){A5=f.get(A7);}else{if(A7.tagName){A5=A7;}}if(A5&&A5.tagName){switch(A5.tagName.toUpperCase()){case G:this.srcElement=A5;if(!A5.id){A5.setAttribute(Am,f.generateId());}r.superclass.init.call(this,A5);this.beforeInitEvent.fire(r);break;case AH:this.srcElement=A5;r.superclass.init.call(this,f.generateId());this.beforeInitEvent.fire(r);break;}}else{r.superclass.init.call(this,A7);this.beforeInitEvent.fire(r);}if(this.element){f.addClass(this.element,this.CSS_CLASS_NAME);this.initEvent.subscribe(this._onInit);this.beforeRenderEvent.subscribe(this._onBeforeRender);this.renderEvent.subscribe(this._onRender);this.beforeShowEvent.subscribe(this._onBeforeShow);this.hideEvent.subscribe(this._onHide);this.showEvent.subscribe(this._onShow);this.beforeHideEvent.subscribe(this._onBeforeHide);this.mouseOverEvent.subscribe(this._onMouseOver);this.mouseOutEvent.subscribe(this._onMouseOut);this.clickEvent.subscribe(this._onClick);this.keyDownEvent.subscribe(this._onKeyDown);this.keyPressEvent.subscribe(this._onKeyPress);this.blurEvent.subscribe(this._onBlur);if(!AT){AA.onFocus(document,Aa);AT=true;}if((As.gecko&&As.gecko<1.9)||As.webkit){this.cfg.subscribeToConfigEvent(R,this._onYChange);}if(A6){this.cfg.applyConfig(A6,true);}A3.addMenu(this);this.initEvent.fire(r);}},_initSubTree:function(){var A6=this.srcElement,A5,A8,BB,BC,BA,A9,A7;if(A6){A5=(A6.tagName&&A6.tagName.toUpperCase());if(A5==G){BC=this.body.firstChild;if(BC){A8=0;BB=this.GROUP_TITLE_TAG_NAME.toUpperCase();do{if(BC&&BC.tagName){switch(BC.tagName.toUpperCase()){case BB:this._aGroupTitleElements[A8]=BC;break;case Ax:this._aListElements[A8]=BC;this._aItemGroups[A8]=[];A8++;break;}}}while((BC=BC.nextSibling));if(this._aListElements[0]){f.addClass(this._aListElements[0],AJ);}}}BC=null;if(A5){switch(A5){case G:BA=this._aListElements;A9=BA.length;if(A9>0){A7=A9-1;do{BC=BA[A7].firstChild;if(BC){do{if(BC&&BC.tagName&&BC.tagName.toUpperCase()==k){this.addItem(new this.ITEM_TYPE(BC,{parent:this}),A7);}}while((BC=BC.nextSibling));}}while(A7--);}break;case AH:BC=A6.firstChild;do{if(BC&&BC.tagName){switch(BC.tagName.toUpperCase()){case h:case Az:this.addItem(new this.ITEM_TYPE(BC,{parent:this}));break;}}}while((BC=BC.nextSibling));break;}}}},_getFirstEnabledItem:function(){var A5=this.getItems(),A9=A5.length,A8,A7;for(var A6=0;A6<A9;A6++){A8=A5[A6];if(A8&&!A8.cfg.getProperty(Ah)&&A8.element.style.display!=AY){A7=A8;break;}}return A7;},_addItemToGroup:function(BA,BB,BF){var BD,BG,A8,BE,A9,A6,A7,BC;function A5(BH,BI){return(BH[BI]||A5(BH,(BI+1)));}if(BB instanceof this.ITEM_TYPE){BD=BB;BD.parent=this;}else{if(AM.isString(BB)){BD=new this.ITEM_TYPE(BB,{parent:this});}else{if(AM.isObject(BB)){BB.parent=this;BD=new this.ITEM_TYPE(BB.text,BB);}}}if(BD){if(BD.cfg.getProperty(y)){this.activeItem=BD;}BG=AM.isNumber(BA)?BA:0;A8=this._getItemGroup(BG);if(!A8){A8=this._createItemGroup(BG);}if(AM.isNumber(BF)){A9=(BF>=A8.length);if(A8[BF]){A8.splice(BF,0,BD);}else{A8[BF]=BD;}BE=A8[BF];if(BE){if(A9&&(!BE.element.parentNode||BE.element.parentNode.nodeType==11)){this._aListElements[BG].appendChild(BE.element);}else{A6=A5(A8,(BF+1));if(A6&&(!BE.element.parentNode||BE.element.parentNode.nodeType==11)){this._aListElements[BG].insertBefore(BE.element,A6.element);}}BE.parent=this;this._subscribeToItemEvents(BE);this._configureSubmenu(BE);this._updateItemProperties(BG);this.itemAddedEvent.fire(BE);this.changeContentEvent.fire();BC=BE;}}else{A7=A8.length;A8[A7]=BD;BE=A8[A7];if(BE){if(!f.isAncestor(this._aListElements[BG],BE.element)){this._aListElements[BG].appendChild(BE.element);}BE.element.setAttribute(At,BG);BE.element.setAttribute(i,A7);BE.parent=this;BE.index=A7;BE.groupIndex=BG;this._subscribeToItemEvents(BE);this._configureSubmenu(BE);if(A7===0){f.addClass(BE.element,AJ);}this.itemAddedEvent.fire(BE);this.changeContentEvent.fire();BC=BE;}}}return BC;},_removeItemFromGroupByIndex:function(A8,A6){var A7=AM.isNumber(A8)?A8:0,A9=this._getItemGroup(A7),BB,BA,A5;if(A9){BB=A9.splice(A6,1);BA=BB[0];if(BA){this._updateItemProperties(A7);if(A9.length===0){A5=this._aListElements[A7];if(this.body&&A5){this.body.removeChild(A5);}this._aItemGroups.splice(A7,1);this._aListElements.splice(A7,1);A5=this._aListElements[0];if(A5){f.addClass(A5,AJ);}}this.itemRemovedEvent.fire(BA);this.changeContentEvent.fire();}}return BA;},_removeItemFromGroupByValue:function(A8,A5){var BA=this._getItemGroup(A8),BB,A9,A7,A6;if(BA){BB=BA.length;A9=-1;if(BB>0){A6=BB-1;do{if(BA[A6]==A5){A9=A6;break;}}while(A6--);if(A9>-1){A7=this._removeItemFromGroupByIndex(A8,A9);}}}return A7;},_updateItemProperties:function(A6){var A7=this._getItemGroup(A6),BA=A7.length,A9,A8,A5;if(BA>0){A5=BA-1;do{A9=A7[A5];if(A9){A8=A9.element;A9.index=A5;A9.groupIndex=A6;A8.setAttribute(At,A6);A8.setAttribute(i,A5);f.removeClass(A8,AJ);}}while(A5--);if(A8){f.addClass(A8,AJ);}}},_createItemGroup:function(A7){var A5,A6;if(!this._aItemGroups[A7]){this._aItemGroups[A7]=[];A5=document.createElement(L);this._aListElements[A7]=A5;A6=this._aItemGroups[A7];}return A6;},_getItemGroup:function(A7){var A5=AM.isNumber(A7)?A7:0,A8=this._aItemGroups,A6;if(A5 in A8){A6=A8[A5];}return A6;},_configureSubmenu:function(A5){var A6=A5.cfg.getProperty(O);if(A6){this.cfg.configChangedEvent.subscribe(this._onParentMenuConfigChange,A6,true);this.renderEvent.subscribe(this._onParentMenuRender,A6,true);}},_subscribeToItemEvents:function(A5){A5.destroyEvent.subscribe(this._onMenuItemDestroy,A5,this);
+A5.cfg.configChangedEvent.subscribe(this._onMenuItemConfigChange,A5,this);},_onVisibleChange:function(A7,A6){var A5=A6[0];if(A5){f.addClass(this.element,Au);}else{f.removeClass(this.element,Au);}},_cancelHideDelay:function(){var A5=this.getRoot()._hideDelayTimer;if(A5){A5.cancel();}},_execHideDelay:function(){this._cancelHideDelay();var A5=this.getRoot();A5._hideDelayTimer=AM.later(A5.cfg.getProperty(AX),this,function(){if(A5.activeItem){if(A5.hasFocus()){A5.activeItem.focus();}A5.clearActiveItem();}if(A5==this&&!(this instanceof YAHOO.widget.MenuBar)&&this.cfg.getProperty(Ac)==AD){this.hide();}});},_cancelShowDelay:function(){var A5=this.getRoot()._showDelayTimer;if(A5){A5.cancel();}},_execSubmenuHideDelay:function(A7,A6,A5){A7._submenuHideDelayTimer=AM.later(50,this,function(){if(this._nCurrentMouseX>(A6+10)){A7._submenuHideDelayTimer=AM.later(A5,A7,function(){this.hide();});}else{A7.hide();}});},_disableScrollHeader:function(){if(!this._bHeaderDisabled){f.addClass(this.header,P);this._bHeaderDisabled=true;}},_disableScrollFooter:function(){if(!this._bFooterDisabled){f.addClass(this.footer,E);this._bFooterDisabled=true;}},_enableScrollHeader:function(){if(this._bHeaderDisabled){f.removeClass(this.header,P);this._bHeaderDisabled=false;}},_enableScrollFooter:function(){if(this._bFooterDisabled){f.removeClass(this.footer,E);this._bFooterDisabled=false;}},_onMouseOver:function(BH,BA){var BI=BA[0],BE=BA[1],A5=AA.getTarget(BI),A9=this.getRoot(),BG=this._submenuHideDelayTimer,A6,A8,BD,A7,BC,BB;var BF=function(){if(this.parent.cfg.getProperty(y)){this.show();}};if(!this._bStopMouseEventHandlers){if(!this._bHandledMouseOverEvent&&(A5==this.element||f.isAncestor(this.element,A5))){if(this._useHideDelay){this._cancelHideDelay();}this._nCurrentMouseX=0;AA.on(this.element,b,this._onMouseMove,this,true);if(!(BE&&f.isAncestor(BE.element,AA.getRelatedTarget(BI)))){this.clearActiveItem();}if(this.parent&&BG){BG.cancel();this.parent.cfg.setProperty(y,true);A6=this.parent.parent;A6._bHandledMouseOutEvent=true;A6._bHandledMouseOverEvent=false;}this._bHandledMouseOverEvent=true;this._bHandledMouseOutEvent=false;}if(BE&&!BE.handledMouseOverEvent&&!BE.cfg.getProperty(Ah)&&(A5==BE.element||f.isAncestor(BE.element,A5))){A8=this.cfg.getProperty(Av);BD=(A8>0);if(BD){this._cancelShowDelay();}A7=this.activeItem;if(A7){A7.cfg.setProperty(y,false);}BC=BE.cfg;BC.setProperty(y,true);if(this.hasFocus()||A9._hasFocus){BE.focus();A9._hasFocus=false;}if(this.cfg.getProperty(Z)){BB=BC.getProperty(O);if(BB){if(BD){A9._showDelayTimer=AM.later(A9.cfg.getProperty(Av),BB,BF);}else{BB.show();}}}BE.handledMouseOverEvent=true;BE.handledMouseOutEvent=false;}}},_onMouseOut:function(BD,A7){var BE=A7[0],BB=A7[1],A8=AA.getRelatedTarget(BE),BC=false,BA,A9,A5,A6;if(!this._bStopMouseEventHandlers){if(BB&&!BB.cfg.getProperty(Ah)){BA=BB.cfg;A9=BA.getProperty(O);if(A9&&(A8==A9.element||f.isAncestor(A9.element,A8))){BC=true;}if(!BB.handledMouseOutEvent&&((A8!=BB.element&&!f.isAncestor(BB.element,A8))||BC)){if(!BC){BB.cfg.setProperty(y,false);if(A9){A5=this.cfg.getProperty(c);A6=this.cfg.getProperty(Av);if(!(this instanceof YAHOO.widget.MenuBar)&&A5>0&&A6>=A5){this._execSubmenuHideDelay(A9,AA.getPageX(BE),A5);}else{A9.hide();}}}BB.handledMouseOutEvent=true;BB.handledMouseOverEvent=false;}}if(!this._bHandledMouseOutEvent&&((A8!=this.element&&!f.isAncestor(this.element,A8))||BC)){if(this._useHideDelay){this._execHideDelay();}AA.removeListener(this.element,b,this._onMouseMove);this._nCurrentMouseX=AA.getPageX(BE);this._bHandledMouseOutEvent=true;this._bHandledMouseOverEvent=false;}}},_onMouseMove:function(A6,A5){if(!this._bStopMouseEventHandlers){this._nCurrentMouseX=AA.getPageX(A6);}},_onClick:function(BG,A7){var BH=A7[0],BB=A7[1],BD=false,A9,BE,A6,A5,BA,BC,BF;var A8=function(){A6=this.getRoot();if(A6 instanceof YAHOO.widget.MenuBar||A6.cfg.getProperty(Ac)==C){A6.clearActiveItem();}else{A6.hide();}};if(BB){if(BB.cfg.getProperty(Ah)){AA.preventDefault(BH);A8.call(this);}else{A9=BB.cfg.getProperty(O);BA=BB.cfg.getProperty(Q);if(BA){BC=BA.indexOf(M);BF=BA.length;if(BC!=-1){BA=BA.substr(BC,BF);BF=BA.length;if(BF>1){A5=BA.substr(1,BF);BE=YAHOO.widget.MenuManager.getMenu(A5);if(BE){BD=(this.getRoot()===BE.getRoot());}}else{if(BF===1){BD=true;}}}}if(BD&&!BB.cfg.getProperty(V)){AA.preventDefault(BH);if(As.webkit){BB.focus();}else{BB.focusEvent.fire();}}if(!A9&&!this.cfg.getProperty(Ar)){A8.call(this);}}}},_onKeyDown:function(BK,BE){var BH=BE[0],BG=BE[1],BD,BI,A6,BA,BL,A5,BO,A9,BJ,A8,BF,BN,BB,BC;if(this._useHideDelay){this._cancelHideDelay();}function A7(){this._bStopMouseEventHandlers=true;AM.later(10,this,function(){this._bStopMouseEventHandlers=false;});}if(BG&&!BG.cfg.getProperty(Ah)){BI=BG.cfg;A6=this.parent;switch(BH.keyCode){case 38:case 40:BL=(BH.keyCode==38)?BG.getPreviousEnabledSibling():BG.getNextEnabledSibling();if(BL){this.clearActiveItem();BL.cfg.setProperty(y,true);BL.focus();if(this.cfg.getProperty(AU)>0){A5=this.body;BO=A5.scrollTop;A9=A5.offsetHeight;BJ=this.getItems();A8=BJ.length-1;BF=BL.element.offsetTop;if(BH.keyCode==40){if(BF>=(A9+BO)){A5.scrollTop=BF-A9;}else{if(BF<=BO){A5.scrollTop=0;}}if(BL==BJ[A8]){A5.scrollTop=BL.element.offsetTop;}}else{if(BF<=BO){A5.scrollTop=BF-BL.element.offsetHeight;}else{if(BF>=(BO+A9)){A5.scrollTop=BF;}}if(BL==BJ[0]){A5.scrollTop=0;}}BO=A5.scrollTop;BN=A5.scrollHeight-A5.offsetHeight;if(BO===0){this._disableScrollHeader();this._enableScrollFooter();}else{if(BO==BN){this._enableScrollHeader();this._disableScrollFooter();}else{this._enableScrollHeader();this._enableScrollFooter();}}}}AA.preventDefault(BH);A7();break;case 39:BD=BI.getProperty(O);if(BD){if(!BI.getProperty(y)){BI.setProperty(y,true);}BD.show();BD.setInitialFocus();BD.setInitialSelection();}else{BA=this.getRoot();if(BA instanceof YAHOO.widget.MenuBar){BL=BA.activeItem.getNextEnabledSibling();if(BL){BA.clearActiveItem();BL.cfg.setProperty(y,true);BD=BL.cfg.getProperty(O);if(BD){BD.show();BD.setInitialFocus();}else{BL.focus();}}}}AA.preventDefault(BH);
+A7();break;case 37:if(A6){BB=A6.parent;if(BB instanceof YAHOO.widget.MenuBar){BL=BB.activeItem.getPreviousEnabledSibling();if(BL){BB.clearActiveItem();BL.cfg.setProperty(y,true);BD=BL.cfg.getProperty(O);if(BD){BD.show();BD.setInitialFocus();}else{BL.focus();}}}else{this.hide();A6.focus();}}AA.preventDefault(BH);A7();break;}}if(BH.keyCode==27){if(this.cfg.getProperty(Ac)==AD){this.hide();if(this.parent){this.parent.focus();}else{BC=this._focusedElement;if(BC&&BC.focus){try{BC.focus();}catch(BM){}}}}else{if(this.activeItem){BD=this.activeItem.cfg.getProperty(O);if(BD&&BD.cfg.getProperty(Au)){BD.hide();this.activeItem.focus();}else{this.activeItem.blur();this.activeItem.cfg.setProperty(y,false);}}}AA.preventDefault(BH);}},_onKeyPress:function(A7,A6){var A5=A6[0];if(A5.keyCode==40||A5.keyCode==38){AA.preventDefault(A5);}},_onBlur:function(A6,A5){if(this._hasFocus){this._hasFocus=false;}},_onYChange:function(A6,A5){var A8=this.parent,BA,A7,A9;if(A8){BA=A8.parent.body.scrollTop;if(BA>0){A9=(this.cfg.getProperty(R)-BA);f.setY(this.element,A9);A7=this.iframe;if(A7){f.setY(A7,A9);}this.cfg.setProperty(R,A9,true);}}},_onScrollTargetMouseOver:function(BB,BE){var BD=this._bodyScrollTimer;if(BD){BD.cancel();}this._cancelHideDelay();var A7=AA.getTarget(BB),A9=this.body,A8=this.cfg.getProperty(j),A5,A6;function BC(){var BF=A9.scrollTop;if(BF<A5){A9.scrollTop=(BF+A8);this._enableScrollHeader();}else{A9.scrollTop=A5;this._bodyScrollTimer.cancel();this._disableScrollFooter();}}function BA(){var BF=A9.scrollTop;if(BF>0){A9.scrollTop=(BF-A8);this._enableScrollFooter();}else{A9.scrollTop=0;this._bodyScrollTimer.cancel();this._disableScrollHeader();}}if(f.hasClass(A7,A0)){A6=BA;}else{A5=A9.scrollHeight-A9.offsetHeight;A6=BC;}this._bodyScrollTimer=AM.later(10,this,A6,null,true);},_onScrollTargetMouseOut:function(A7,A5){var A6=this._bodyScrollTimer;if(A6){A6.cancel();}this._cancelHideDelay();},_onInit:function(A6,A5){this.cfg.subscribeToConfigEvent(Au,this._onVisibleChange);var A7=!this.parent,A8=this.lazyLoad;if(((A7&&!A8)||(A7&&(this.cfg.getProperty(Au)||this.cfg.getProperty(Ac)==C))||(!A7&&!A8))&&this.getItemGroups().length===0){if(this.srcElement){this._initSubTree();}if(this.itemData){this.addItems(this.itemData);}}else{if(A8){this.cfg.fireQueue();}}},_onBeforeRender:function(A8,A7){var A9=this.element,BC=this._aListElements.length,A6=true,BB=0,A5,BA;if(BC>0){do{A5=this._aListElements[BB];if(A5){if(A6){f.addClass(A5,AJ);A6=false;}if(!f.isAncestor(A9,A5)){this.appendToBody(A5);}BA=this._aGroupTitleElements[BB];if(BA){if(!f.isAncestor(A9,BA)){A5.parentNode.insertBefore(BA,A5);}f.addClass(A5,D);}}BB++;}while(BB<BC);}},_onRender:function(A6,A5){if(this.cfg.getProperty(Ac)==AD){if(!this.cfg.getProperty(Au)){this.positionOffScreen();}}},_onBeforeShow:function(A7,A6){var A9,BC,A8,BA=this.cfg.getProperty(g);if(this.lazyLoad&&this.getItemGroups().length===0){if(this.srcElement){this._initSubTree();}if(this.itemData){if(this.parent&&this.parent.parent&&this.parent.parent.srcElement&&this.parent.parent.srcElement.tagName.toUpperCase()==AH){A9=this.itemData.length;for(BC=0;BC<A9;BC++){if(this.itemData[BC].tagName){this.addItem((new this.ITEM_TYPE(this.itemData[BC])));}}}else{this.addItems(this.itemData);}}A8=this.srcElement;if(A8){if(A8.tagName.toUpperCase()==AH){if(f.inDocument(A8)){this.render(A8.parentNode);}else{this.render(BA);}}else{this.render();}}else{if(this.parent){this.render(this.parent.element);}else{this.render(BA);}}}var BB=this.parent,A5;if(!BB&&this.cfg.getProperty(Ac)==AD){this.cfg.refireEvent(e);}if(BB){A5=BB.parent.cfg.getProperty(AO);this.cfg.setProperty(p,[BB.element,A5[0],A5[1]]);this.align();}},getConstrainedY:function(BH){var BS=this,BO=BS.cfg.getProperty(p),BV=BS.cfg.getProperty(AU),BR,BG={"trbr":true,"tlbl":true,"bltl":true,"brtr":true},BA=(BO&&BG[BO[1]+BO[2]]),BC=BS.element,BW=BC.offsetHeight,BQ=AB.VIEWPORT_OFFSET,BL=f.getViewportHeight(),BP=f.getDocumentScrollTop(),BM=(BS.cfg.getProperty(Aj)+BQ<BL),BU,BD,BJ,BK,BF=false,BE,A7,BI=BP+BQ,A9=BP+BL-BW-BQ,A5=BH;var BB=function(){var BX;if((BS.cfg.getProperty(R)-BP)>BJ){BX=(BJ-BW);}else{BX=(BJ+BK);}BS.cfg.setProperty(R,(BX+BP),true);return BX;};var A8=function(){if((BS.cfg.getProperty(R)-BP)>BJ){return(A7-BQ);}else{return(BE-BQ);}};var BN=function(){var BX;if((BS.cfg.getProperty(R)-BP)>BJ){BX=(BJ+BK);}else{BX=(BJ-BC.offsetHeight);}BS.cfg.setProperty(R,(BX+BP),true);};var A6=function(){BS._setScrollHeight(this.cfg.getProperty(AU));BS.hideEvent.unsubscribe(A6);};var BT=function(){var Ba=A8(),BX=(BS.getItems().length>0),BZ,BY;if(BW>Ba){BZ=BX?BS.cfg.getProperty(Aj):BW;if((Ba>BZ)&&BX){BR=Ba;}else{BR=BV;}BS._setScrollHeight(BR);BS.hideEvent.subscribe(A6);BN();if(Ba<BZ){if(BF){BB();}else{BB();BF=true;BY=BT();}}}else{if(BR&&(BR!==BV)){BS._setScrollHeight(BV);BS.hideEvent.subscribe(A6);BN();}}return BY;};if(BH<BI||BH>A9){if(BM){if(BS.cfg.getProperty(A4)&&BA){BD=BO[0];BK=BD.offsetHeight;BJ=(f.getY(BD)-BP);BE=BJ;A7=(BL-(BJ+BK));BT();A5=BS.cfg.getProperty(R);}else{if(!(BS instanceof YAHOO.widget.MenuBar)&&BW>=BL){BU=(BL-(BQ*2));if(BU>BS.cfg.getProperty(Aj)){BS._setScrollHeight(BU);BS.hideEvent.subscribe(A6);BN();A5=BS.cfg.getProperty(R);}}else{if(BH<BI){A5=BI;}else{if(BH>A9){A5=A9;}}}}}else{A5=BQ+BP;}}return A5;},_onHide:function(A6,A5){if(this.cfg.getProperty(Ac)===AD){this.positionOffScreen();}},_onShow:function(BD,BB){var A5=this.parent,A7,A8,BA,A6;function A9(BF){var BE;if(BF.type==Ak||(BF.type==Ae&&BF.keyCode==27)){BE=AA.getTarget(BF);if(BE!=A7.element||!f.isAncestor(A7.element,BE)){A7.cfg.setProperty(Z,false);AA.removeListener(document,Ak,A9);AA.removeListener(document,Ae,A9);}}}function BC(BF,BE,BG){this.cfg.setProperty(U,u);this.hideEvent.unsubscribe(BC,BG);}if(A5){A7=A5.parent;if(!A7.cfg.getProperty(Z)&&(A7 instanceof YAHOO.widget.MenuBar||A7.cfg.getProperty(Ac)==C)){A7.cfg.setProperty(Z,true);AA.on(document,Ak,A9);AA.on(document,Ae,A9);}if((this.cfg.getProperty("x")<A7.cfg.getProperty("x"))&&(As.gecko&&As.gecko<1.9)&&!this.cfg.getProperty(U)){A8=this.element;
+BA=A8.offsetWidth;A8.style.width=BA+AQ;A6=(BA-(A8.offsetWidth-BA))+AQ;this.cfg.setProperty(U,A6);this.hideEvent.subscribe(BC,A6);}}if(this===this.getRoot()&&this.cfg.getProperty(Ac)===AD){this._focusedElement=Ad;this.focus();}},_onBeforeHide:function(A7,A6){var A5=this.activeItem,A9=this.getRoot(),BA,A8;if(A5){BA=A5.cfg;BA.setProperty(y,false);A8=BA.getProperty(O);if(A8){A8.hide();}}if(As.ie&&this.cfg.getProperty(Ac)===AD&&this.parent){A9._hasFocus=this.hasFocus();}if(A9==this){A9.blur();}},_onParentMenuConfigChange:function(A6,A5,A9){var A7=A5[0][0],A8=A5[0][1];switch(A7){case AF:case w:case AX:case Av:case c:case AC:case Ay:case A2:case j:case AU:case Aj:case AE:case Ag:case A4:case Ar:A9.cfg.setProperty(A7,A8);break;case AO:if(!(this.parent.parent instanceof YAHOO.widget.MenuBar)){A9.cfg.setProperty(A7,A8);}break;}},_onParentMenuRender:function(A6,A5,BB){var A8=BB.parent.parent,A7=A8.cfg,A9={constraintoviewport:A7.getProperty(w),xy:[0,0],clicktohide:A7.getProperty(AC),effect:A7.getProperty(Ay),showdelay:A7.getProperty(Av),hidedelay:A7.getProperty(AX),submenuhidedelay:A7.getProperty(c),classname:A7.getProperty(A2),scrollincrement:A7.getProperty(j),maxheight:A7.getProperty(AU),minscrollheight:A7.getProperty(Aj),iframe:A7.getProperty(AF),shadow:A7.getProperty(Ag),preventcontextoverlap:A7.getProperty(A4),monitorresize:A7.getProperty(AE),keepopen:A7.getProperty(Ar)},BA;if(!(A8 instanceof YAHOO.widget.MenuBar)){A9[AO]=A7.getProperty(AO);}BB.cfg.applyConfig(A9);if(!this.lazyLoad){BA=this.parent.element;if(this.element.parentNode==BA){this.render();}else{this.render(BA);}}},_onMenuItemDestroy:function(A7,A6,A5){this._removeItemFromGroupByValue(A5.groupIndex,A5);},_onMenuItemConfigChange:function(A7,A6,A5){var A9=A6[0][0],BA=A6[0][1],A8;switch(A9){case y:if(BA===true){this.activeItem=A5;}break;case O:A8=A6[0][1];if(A8){this._configureSubmenu(A5);}break;}},configVisible:function(A7,A6,A8){var A5,A9;if(this.cfg.getProperty(Ac)==AD){r.superclass.configVisible.call(this,A7,A6,A8);}else{A5=A6[0];A9=f.getStyle(this.element,AW);f.setStyle(this.element,J,Au);if(A5){if(A9!=AV){this.beforeShowEvent.fire();f.setStyle(this.element,AW,AV);this.showEvent.fire();}}else{if(A9==AV){this.beforeHideEvent.fire();f.setStyle(this.element,AW,AY);this.hideEvent.fire();}}}},configPosition:function(A7,A6,BA){var A9=this.element,A8=A6[0]==C?C:z,BB=this.cfg,A5;f.setStyle(A9,Ac,A8);if(A8==C){f.setStyle(A9,AW,AV);BB.setProperty(Au,true);}else{f.setStyle(A9,J,AL);}if(A8==z){A5=BB.getProperty(AS);if(!A5||A5===0){BB.setProperty(AS,1);}}},configIframe:function(A6,A5,A7){if(this.cfg.getProperty(Ac)==AD){r.superclass.configIframe.call(this,A6,A5,A7);}},configHideDelay:function(A6,A5,A7){var A8=A5[0];this._useHideDelay=(A8>0);},configContainer:function(A6,A5,A8){var A7=A5[0];if(AM.isString(A7)){this.cfg.setProperty(g,f.get(A7),true);}},_clearSetWidthFlag:function(){this._widthSetForScroll=false;this.cfg.unsubscribeFromConfigEvent(U,this._clearSetWidthFlag);},_setScrollHeight:function(BG){var BC=BG,BB=false,BH=false,A8,A9,BF,A6,BE,BI,A5,BD,BA,A7;if(this.getItems().length>0){A8=this.element;A9=this.body;BF=this.header;A6=this.footer;BE=this._onScrollTargetMouseOver;BI=this._onScrollTargetMouseOut;A5=this.cfg.getProperty(Aj);if(BC>0&&BC<A5){BC=A5;}f.setStyle(A9,Ao,u);f.removeClass(A9,l);A9.scrollTop=0;BH=((As.gecko&&As.gecko<1.9)||As.ie);if(BC>0&&BH&&!this.cfg.getProperty(U)){BA=A8.offsetWidth;A8.style.width=BA+AQ;A7=(BA-(A8.offsetWidth-BA))+AQ;this.cfg.unsubscribeFromConfigEvent(U,this._clearSetWidthFlag);this.cfg.setProperty(U,A7);this._widthSetForScroll=true;this.cfg.subscribeToConfigEvent(U,this._clearSetWidthFlag);}if(BC>0&&(!BF&&!A6)){this.setHeader(AK);this.setFooter(AK);BF=this.header;A6=this.footer;f.addClass(BF,T);f.addClass(A6,x);A8.insertBefore(BF,A9);A8.appendChild(A6);}BD=BC;if(BF&&A6){BD=(BD-(BF.offsetHeight+A6.offsetHeight));}if((BD>0)&&(A9.offsetHeight>BC)){f.addClass(A9,l);f.setStyle(A9,Ao,(BD+AQ));if(!this._hasScrollEventHandlers){AA.on(BF,Ai,BE,this,true);AA.on(BF,H,BI,this,true);AA.on(A6,Ai,BE,this,true);AA.on(A6,H,BI,this,true);this._hasScrollEventHandlers=true;}this._disableScrollHeader();this._enableScrollFooter();BB=true;}else{if(BF&&A6){if(this._widthSetForScroll){this._widthSetForScroll=false;this.cfg.unsubscribeFromConfigEvent(U,this._clearSetWidthFlag);this.cfg.setProperty(U,u);}this._enableScrollHeader();this._enableScrollFooter();if(this._hasScrollEventHandlers){AA.removeListener(BF,Ai,BE);AA.removeListener(BF,H,BI);AA.removeListener(A6,Ai,BE);AA.removeListener(A6,H,BI);this._hasScrollEventHandlers=false;}A8.removeChild(BF);A8.removeChild(A6);this.header=null;this.footer=null;BB=true;}}if(BB){this.cfg.refireEvent(AF);this.cfg.refireEvent(Ag);}}},_setMaxHeight:function(A6,A5,A7){this._setScrollHeight(A7);this.renderEvent.unsubscribe(this._setMaxHeight);},configMaxHeight:function(A6,A5,A7){var A8=A5[0];if(this.lazyLoad&&!this.body&&A8>0){this.renderEvent.subscribe(this._setMaxHeight,A8,this);}else{this._setScrollHeight(A8);}},configClassName:function(A7,A6,A8){var A5=A6[0];if(this._sClassName){f.removeClass(this.element,this._sClassName);}f.addClass(this.element,A5);this._sClassName=A5;},_onItemAdded:function(A6,A5){var A7=A5[0];if(A7){A7.cfg.setProperty(Ah,true);}},configDisabled:function(A7,A6,BA){var A9=A6[0],A5=this.getItems(),BB,A8;if(AM.isArray(A5)){BB=A5.length;if(BB>0){A8=BB-1;do{A5[A8].cfg.setProperty(Ah,A9);}while(A8--);}if(A9){this.clearActiveItem(true);f.addClass(this.element,Ah);this.itemAddedEvent.subscribe(this._onItemAdded);}else{f.removeClass(this.element,Ah);this.itemAddedEvent.unsubscribe(this._onItemAdded);}}},configShadow:function(BD,A7,BC){var BB=function(){var BG=this.element,BF=this._shadow;if(BF&&BG){if(BF.style.width&&BF.style.height){BF.style.width=u;BF.style.height=u;}BF.style.width=(BG.offsetWidth+6)+AQ;BF.style.height=(BG.offsetHeight+1)+AQ;}};var BE=function(){this.element.appendChild(this._shadow);};var A9=function(){f.addClass(this._shadow,AG);};var BA=function(){f.removeClass(this._shadow,AG);
+};var A6=function(){var BG=this._shadow,BF;if(!BG){BF=this.element;if(!Ap){Ap=document.createElement(K);Ap.className=m;}BG=Ap.cloneNode(false);BF.appendChild(BG);this._shadow=BG;this.beforeShowEvent.subscribe(A9);this.beforeHideEvent.subscribe(BA);if(As.ie){AM.later(0,this,function(){BB.call(this);this.syncIframe();});this.cfg.subscribeToConfigEvent(U,BB);this.cfg.subscribeToConfigEvent(Ao,BB);this.cfg.subscribeToConfigEvent(AU,BB);this.changeContentEvent.subscribe(BB);Aw.textResizeEvent.subscribe(BB,this,true);this.destroyEvent.subscribe(function(){Aw.textResizeEvent.unsubscribe(BB,this);});}this.cfg.subscribeToConfigEvent(AU,BE);}};var A8=function(){if(this._shadow){BE.call(this);if(As.ie){BB.call(this);}}else{A6.call(this);}this.beforeShowEvent.unsubscribe(A8);};var A5=A7[0];if(A5&&this.cfg.getProperty(Ac)==AD){if(this.cfg.getProperty(Au)){if(this._shadow){BE.call(this);if(As.ie){BB.call(this);}}else{A6.call(this);}}else{this.beforeShowEvent.subscribe(A8);}}},initEvents:function(){r.superclass.initEvents.call(this);var A6=Ab.length-1,A7,A5;do{A7=Ab[A6];A5=this.createEvent(A7[1]);A5.signature=F.LIST;this[A7[0]]=A5;}while(A6--);},positionOffScreen:function(){var A6=this.iframe,A7=this.element,A5=this.OFF_SCREEN_POSITION;A7.style.top=u;A7.style.left=u;if(A6){A6.style.top=A5;A6.style.left=A5;}},getRoot:function(){var A7=this.parent,A6,A5;if(A7){A6=A7.parent;A5=A6?A6.getRoot():this;}else{A5=this;}return A5;},toString:function(){var A6=Aq,A5=this.id;if(A5){A6+=(A1+A5);}return A6;},setItemGroupTitle:function(BA,A9){var A8,A7,A6,A5;if(AM.isString(BA)&&BA.length>0){A8=AM.isNumber(A9)?A9:0;A7=this._aGroupTitleElements[A8];if(A7){A7.innerHTML=BA;}else{A7=document.createElement(this.GROUP_TITLE_TAG_NAME);A7.innerHTML=BA;this._aGroupTitleElements[A8]=A7;}A6=this._aGroupTitleElements.length-1;do{if(this._aGroupTitleElements[A6]){f.removeClass(this._aGroupTitleElements[A6],AJ);A5=A6;}}while(A6--);if(A5!==null){f.addClass(this._aGroupTitleElements[A5],AJ);}this.changeContentEvent.fire();}},addItem:function(A5,A6){return this._addItemToGroup(A6,A5);},addItems:function(A9,A8){var BB,A5,BA,A6,A7;if(AM.isArray(A9)){BB=A9.length;A5=[];for(A6=0;A6<BB;A6++){BA=A9[A6];if(BA){if(AM.isArray(BA)){A5[A5.length]=this.addItems(BA,A6);}else{A5[A5.length]=this._addItemToGroup(A8,BA);}}}if(A5.length){A7=A5;}}return A7;},insertItem:function(A5,A6,A7){return this._addItemToGroup(A7,A5,A6);},removeItem:function(A5,A7){var A8,A6;if(!AM.isUndefined(A5)){if(A5 instanceof YAHOO.widget.MenuItem){A8=this._removeItemFromGroupByValue(A7,A5);}else{if(AM.isNumber(A5)){A8=this._removeItemFromGroupByIndex(A7,A5);}}if(A8){A8.destroy();A6=A8;}}return A6;},getItems:function(){var A8=this._aItemGroups,A6,A7,A5=[];if(AM.isArray(A8)){A6=A8.length;A7=((A6==1)?A8[0]:(Array.prototype.concat.apply(A5,A8)));}return A7;},getItemGroups:function(){return this._aItemGroups;},getItem:function(A6,A7){var A8,A5;if(AM.isNumber(A6)){A8=this._getItemGroup(A7);if(A8){A5=A8[A6];}}return A5;},getSubmenus:function(){var A6=this.getItems(),BA=A6.length,A5,A7,A9,A8;if(BA>0){A5=[];for(A8=0;A8<BA;A8++){A9=A6[A8];if(A9){A7=A9.cfg.getProperty(O);if(A7){A5[A5.length]=A7;}}}}return A5;},clearContent:function(){var A9=this.getItems(),A6=A9.length,A7=this.element,A8=this.body,BD=this.header,A5=this.footer,BC,BB,BA;if(A6>0){BA=A6-1;do{BC=A9[BA];if(BC){BB=BC.cfg.getProperty(O);if(BB){this.cfg.configChangedEvent.unsubscribe(this._onParentMenuConfigChange,BB);this.renderEvent.unsubscribe(this._onParentMenuRender,BB);}this.removeItem(BC,BC.groupIndex);}}while(BA--);}if(BD){AA.purgeElement(BD);A7.removeChild(BD);}if(A5){AA.purgeElement(A5);A7.removeChild(A5);}if(A8){AA.purgeElement(A8);A8.innerHTML=u;}this.activeItem=null;this._aItemGroups=[];this._aListElements=[];this._aGroupTitleElements=[];this.cfg.setProperty(U,null);},destroy:function(){this.clearContent();this._aItemGroups=null;this._aListElements=null;this._aGroupTitleElements=null;r.superclass.destroy.call(this);},setInitialFocus:function(){var A5=this._getFirstEnabledItem();if(A5){A5.focus();}},setInitialSelection:function(){var A5=this._getFirstEnabledItem();if(A5){A5.cfg.setProperty(y,true);}},clearActiveItem:function(A7){if(this.cfg.getProperty(Av)>0){this._cancelShowDelay();}var A5=this.activeItem,A8,A6;if(A5){A8=A5.cfg;if(A7){A5.blur();this.getRoot()._hasFocus=true;}A8.setProperty(y,false);A6=A8.getProperty(O);if(A6){A6.hide();}this.activeItem=null;}},focus:function(){if(!this.hasFocus()){this.setInitialFocus();}},blur:function(){var A5;if(this.hasFocus()){A5=A3.getFocusedMenuItem();if(A5){A5.blur();}}},hasFocus:function(){return(A3.getFocusedMenu()==this.getRoot());},_doItemSubmenuSubscribe:function(A6,A5,A8){var A9=A5[0],A7=A9.cfg.getProperty(O);if(A7){A7.subscribe.apply(A7,A8);}},_doSubmenuSubscribe:function(A6,A5,A8){var A7=this.cfg.getProperty(O);if(A7){A7.subscribe.apply(A7,A8);}},subscribe:function(){r.superclass.subscribe.apply(this,arguments);r.superclass.subscribe.call(this,AR,this._doItemSubmenuSubscribe,arguments);var A5=this.getItems(),A9,A8,A6,A7;if(A5){A9=A5.length;if(A9>0){A7=A9-1;do{A8=A5[A7];A6=A8.cfg.getProperty(O);if(A6){A6.subscribe.apply(A6,arguments);}else{A8.cfg.subscribeToConfigEvent(O,this._doSubmenuSubscribe,arguments);}}while(A7--);}}},unsubscribe:function(){r.superclass.unsubscribe.apply(this,arguments);r.superclass.unsubscribe.call(this,AR,this._doItemSubmenuSubscribe,arguments);var A5=this.getItems(),A9,A8,A6,A7;if(A5){A9=A5.length;if(A9>0){A7=A9-1;do{A8=A5[A7];A6=A8.cfg.getProperty(O);if(A6){A6.unsubscribe.apply(A6,arguments);}else{A8.cfg.unsubscribeFromConfigEvent(O,this._doSubmenuSubscribe,arguments);}}while(A7--);}}},initDefaultConfig:function(){r.superclass.initDefaultConfig.call(this);var A5=this.cfg;A5.addProperty(AZ.key,{handler:this.configVisible,value:AZ.value,validator:AZ.validator});A5.addProperty(AP.key,{handler:this.configConstrainToViewport,value:AP.value,validator:AP.validator,supercedes:AP.supercedes});A5.addProperty(AI.key,{value:AI.value,validator:AI.validator,supercedes:AI.supercedes});
+A5.addProperty(S.key,{handler:this.configPosition,value:S.value,validator:S.validator,supercedes:S.supercedes});A5.addProperty(A.key,{value:A.value,suppressEvent:A.suppressEvent});A5.addProperty(t.key,{value:t.value,validator:t.validator,suppressEvent:t.suppressEvent});A5.addProperty(Y.key,{value:Y.value,validator:Y.validator,suppressEvent:Y.suppressEvent});A5.addProperty(q.key,{handler:this.configHideDelay,value:q.value,validator:q.validator,suppressEvent:q.suppressEvent});A5.addProperty(v.key,{value:v.value,validator:v.validator,suppressEvent:v.suppressEvent});A5.addProperty(o.key,{value:o.value,validator:o.validator,suppressEvent:o.suppressEvent});A5.addProperty(AN.key,{handler:this.configContainer,value:document.body,suppressEvent:AN.suppressEvent});A5.addProperty(Af.key,{value:Af.value,validator:Af.validator,supercedes:Af.supercedes,suppressEvent:Af.suppressEvent});A5.addProperty(N.key,{value:N.value,validator:N.validator,supercedes:N.supercedes,suppressEvent:N.suppressEvent});A5.addProperty(X.key,{handler:this.configMaxHeight,value:X.value,validator:X.validator,suppressEvent:X.suppressEvent,supercedes:X.supercedes});A5.addProperty(W.key,{handler:this.configClassName,value:W.value,validator:W.validator,supercedes:W.supercedes});A5.addProperty(a.key,{handler:this.configDisabled,value:a.value,validator:a.validator,suppressEvent:a.suppressEvent});A5.addProperty(I.key,{handler:this.configShadow,value:I.value,validator:I.validator});A5.addProperty(Al.key,{value:Al.value,validator:Al.validator});}});})();(function(){YAHOO.widget.MenuItem=function(AS,AR){if(AS){if(AR){this.parent=AR.parent;this.value=AR.value;this.id=AR.id;}this.init(AS,AR);}};var x=YAHOO.util.Dom,j=YAHOO.widget.Module,AB=YAHOO.widget.Menu,c=YAHOO.widget.MenuItem,AK=YAHOO.util.CustomEvent,k=YAHOO.env.ua,AQ=YAHOO.lang,AL="text",O="#",Q="-",L="helptext",n="url",AH="target",A="emphasis",N="strongemphasis",b="checked",w="submenu",H="disabled",B="selected",P="hassubmenu",U="checked-disabled",AI="hassubmenu-disabled",AD="hassubmenu-selected",T="checked-selected",q="onclick",J="classname",AJ="",i="OPTION",v="OPTGROUP",K="LI",AE="href",r="SELECT",X="DIV",AN='<em class="helptext">',a="<em>",I="</em>",W="<strong>",y="</strong>",Y="preventcontextoverlap",h="obj",AG="scope",t="none",V="visible",E=" ",m="MenuItem",AA="click",D="show",M="hide",S="li",AF='<a href="#"></a>',p=[["mouseOverEvent","mouseover"],["mouseOutEvent","mouseout"],["mouseDownEvent","mousedown"],["mouseUpEvent","mouseup"],["clickEvent",AA],["keyPressEvent","keypress"],["keyDownEvent","keydown"],["keyUpEvent","keyup"],["focusEvent","focus"],["blurEvent","blur"],["destroyEvent","destroy"]],o={key:AL,value:AJ,validator:AQ.isString,suppressEvent:true},s={key:L,supercedes:[AL],suppressEvent:true},G={key:n,value:O,suppressEvent:true},AO={key:AH,suppressEvent:true},AP={key:A,value:false,validator:AQ.isBoolean,suppressEvent:true,supercedes:[AL]},d={key:N,value:false,validator:AQ.isBoolean,suppressEvent:true,supercedes:[AL]},l={key:b,value:false,validator:AQ.isBoolean,suppressEvent:true,supercedes:[H,B]},F={key:w,suppressEvent:true,supercedes:[H,B]},AM={key:H,value:false,validator:AQ.isBoolean,suppressEvent:true,supercedes:[AL,B]},f={key:B,value:false,validator:AQ.isBoolean,suppressEvent:true},u={key:q,suppressEvent:true},AC={key:J,value:null,validator:AQ.isString,suppressEvent:true},z={key:"keylistener",value:null,suppressEvent:true},C=null,e={};var Z=function(AU,AT){var AR=e[AU];if(!AR){e[AU]={};AR=e[AU];}var AS=AR[AT];if(!AS){AS=AU+Q+AT;AR[AT]=AS;}return AS;};var g=function(AR){x.addClass(this.element,Z(this.CSS_CLASS_NAME,AR));x.addClass(this._oAnchor,Z(this.CSS_LABEL_CLASS_NAME,AR));};var R=function(AR){x.removeClass(this.element,Z(this.CSS_CLASS_NAME,AR));x.removeClass(this._oAnchor,Z(this.CSS_LABEL_CLASS_NAME,AR));};c.prototype={CSS_CLASS_NAME:"yuimenuitem",CSS_LABEL_CLASS_NAME:"yuimenuitemlabel",SUBMENU_TYPE:null,_oAnchor:null,_oHelpTextEM:null,_oSubmenu:null,_oOnclickAttributeValue:null,_sClassName:null,constructor:c,index:null,groupIndex:null,parent:null,element:null,srcElement:null,value:null,browser:j.prototype.browser,id:null,init:function(AR,Ab){if(!this.SUBMENU_TYPE){this.SUBMENU_TYPE=AB;}this.cfg=new YAHOO.util.Config(this);this.initDefaultConfig();var AX=this.cfg,AY=O,AT,Aa,AZ,AS,AV,AU,AW;if(AQ.isString(AR)){this._createRootNodeStructure();AX.queueProperty(AL,AR);}else{if(AR&&AR.tagName){switch(AR.tagName.toUpperCase()){case i:this._createRootNodeStructure();AX.queueProperty(AL,AR.text);AX.queueProperty(H,AR.disabled);this.value=AR.value;this.srcElement=AR;break;case v:this._createRootNodeStructure();AX.queueProperty(AL,AR.label);AX.queueProperty(H,AR.disabled);this.srcElement=AR;this._initSubTree();break;case K:AZ=x.getFirstChild(AR);if(AZ){AY=AZ.getAttribute(AE,2);AS=AZ.getAttribute(AH);AV=AZ.innerHTML;}this.srcElement=AR;this.element=AR;this._oAnchor=AZ;AX.setProperty(AL,AV,true);AX.setProperty(n,AY,true);AX.setProperty(AH,AS,true);this._initSubTree();break;}}}if(this.element){AU=(this.srcElement||this.element).id;if(!AU){AU=this.id||x.generateId();this.element.id=AU;}this.id=AU;x.addClass(this.element,this.CSS_CLASS_NAME);x.addClass(this._oAnchor,this.CSS_LABEL_CLASS_NAME);AW=p.length-1;do{Aa=p[AW];AT=this.createEvent(Aa[1]);AT.signature=AK.LIST;this[Aa[0]]=AT;}while(AW--);if(Ab){AX.applyConfig(Ab);}AX.fireQueue();}},_createRootNodeStructure:function(){var AR,AS;if(!C){C=document.createElement(S);C.innerHTML=AF;}AR=C.cloneNode(true);AR.className=this.CSS_CLASS_NAME;AS=AR.firstChild;AS.className=this.CSS_LABEL_CLASS_NAME;this.element=AR;this._oAnchor=AS;},_initSubTree:function(){var AX=this.srcElement,AT=this.cfg,AV,AU,AS,AR,AW;if(AX.childNodes.length>0){if(this.parent.lazyLoad&&this.parent.srcElement&&this.parent.srcElement.tagName.toUpperCase()==r){AT.setProperty(w,{id:x.generateId(),itemdata:AX.childNodes});}else{AV=AX.firstChild;AU=[];do{if(AV&&AV.tagName){switch(AV.tagName.toUpperCase()){case X:AT.setProperty(w,AV);break;case i:AU[AU.length]=AV;break;}}}while((AV=AV.nextSibling));
+AS=AU.length;if(AS>0){AR=new this.SUBMENU_TYPE(x.generateId());AT.setProperty(w,AR);for(AW=0;AW<AS;AW++){AR.addItem((new AR.ITEM_TYPE(AU[AW])));}}}}},configText:function(Aa,AT,AV){var AS=AT[0],AU=this.cfg,AY=this._oAnchor,AR=AU.getProperty(L),AZ=AJ,AW=AJ,AX=AJ;if(AS){if(AR){AZ=AN+AR+I;}if(AU.getProperty(A)){AW=a;AX=I;}if(AU.getProperty(N)){AW=W;AX=y;}AY.innerHTML=(AW+AS+AX+AZ);}},configHelpText:function(AT,AS,AR){this.cfg.refireEvent(AL);},configURL:function(AT,AS,AR){var AV=AS[0];if(!AV){AV=O;}var AU=this._oAnchor;if(k.opera){AU.removeAttribute(AE);}AU.setAttribute(AE,AV);},configTarget:function(AU,AT,AS){var AR=AT[0],AV=this._oAnchor;if(AR&&AR.length>0){AV.setAttribute(AH,AR);}else{AV.removeAttribute(AH);}},configEmphasis:function(AT,AS,AR){var AV=AS[0],AU=this.cfg;if(AV&&AU.getProperty(N)){AU.setProperty(N,false);}AU.refireEvent(AL);},configStrongEmphasis:function(AU,AT,AS){var AR=AT[0],AV=this.cfg;if(AR&&AV.getProperty(A)){AV.setProperty(A,false);}AV.refireEvent(AL);},configChecked:function(AT,AS,AR){var AV=AS[0],AU=this.cfg;if(AV){g.call(this,b);}else{R.call(this,b);}AU.refireEvent(AL);if(AU.getProperty(H)){AU.refireEvent(H);}if(AU.getProperty(B)){AU.refireEvent(B);}},configDisabled:function(AT,AS,AR){var AV=AS[0],AW=this.cfg,AU=AW.getProperty(w),AX=AW.getProperty(b);if(AV){if(AW.getProperty(B)){AW.setProperty(B,false);}g.call(this,H);if(AU){g.call(this,AI);}if(AX){g.call(this,U);}}else{R.call(this,H);if(AU){R.call(this,AI);}if(AX){R.call(this,U);}}},configSelected:function(AT,AS,AR){var AX=this.cfg,AW=this._oAnchor,AV=AS[0],AY=AX.getProperty(b),AU=AX.getProperty(w);if(k.opera){AW.blur();}if(AV&&!AX.getProperty(H)){g.call(this,B);if(AU){g.call(this,AD);}if(AY){g.call(this,T);}}else{R.call(this,B);if(AU){R.call(this,AD);}if(AY){R.call(this,T);}}if(this.hasFocus()&&k.opera){AW.focus();}},_onSubmenuBeforeHide:function(AU,AT){var AV=this.parent,AR;function AS(){AV._oAnchor.blur();AR.beforeHideEvent.unsubscribe(AS);}if(AV.hasFocus()){AR=AV.parent;AR.beforeHideEvent.subscribe(AS);}},configSubmenu:function(AY,AT,AW){var AV=AT[0],AU=this.cfg,AS=this.parent&&this.parent.lazyLoad,AX,AZ,AR;if(AV){if(AV instanceof AB){AX=AV;AX.parent=this;AX.lazyLoad=AS;}else{if(AQ.isObject(AV)&&AV.id&&!AV.nodeType){AZ=AV.id;AR=AV;AR.lazyload=AS;AR.parent=this;AX=new this.SUBMENU_TYPE(AZ,AR);AU.setProperty(w,AX,true);}else{AX=new this.SUBMENU_TYPE(AV,{lazyload:AS,parent:this});AU.setProperty(w,AX,true);}}if(AX){AX.cfg.setProperty(Y,true);g.call(this,P);if(AU.getProperty(n)===O){AU.setProperty(n,(O+AX.id));}this._oSubmenu=AX;if(k.opera){AX.beforeHideEvent.subscribe(this._onSubmenuBeforeHide);}}}else{R.call(this,P);if(this._oSubmenu){this._oSubmenu.destroy();}}if(AU.getProperty(H)){AU.refireEvent(H);}if(AU.getProperty(B)){AU.refireEvent(B);}},configOnClick:function(AT,AS,AR){var AU=AS[0];if(this._oOnclickAttributeValue&&(this._oOnclickAttributeValue!=AU)){this.clickEvent.unsubscribe(this._oOnclickAttributeValue.fn,this._oOnclickAttributeValue.obj);this._oOnclickAttributeValue=null;}if(!this._oOnclickAttributeValue&&AQ.isObject(AU)&&AQ.isFunction(AU.fn)){this.clickEvent.subscribe(AU.fn,((h in AU)?AU.obj:this),((AG in AU)?AU.scope:null));this._oOnclickAttributeValue=AU;}},configClassName:function(AU,AT,AS){var AR=AT[0];if(this._sClassName){x.removeClass(this.element,this._sClassName);}x.addClass(this.element,AR);this._sClassName=AR;},_dispatchClickEvent:function(){var AT=this,AS,AR;if(!AT.cfg.getProperty(H)){AS=x.getFirstChild(AT.element);if(k.ie){AS.fireEvent(q);}else{if((k.gecko&&k.gecko>=1.9)||k.opera||k.webkit){AR=document.createEvent("HTMLEvents");AR.initEvent(AA,true,true);}else{AR=document.createEvent("MouseEvents");AR.initMouseEvent(AA,true,true,window,0,0,0,0,0,false,false,false,false,0,null);}AS.dispatchEvent(AR);}}},_createKeyListener:function(AU,AT,AW){var AV=this,AS=AV.parent;var AR=new YAHOO.util.KeyListener(AS.element.ownerDocument,AW,{fn:AV._dispatchClickEvent,scope:AV,correctScope:true});if(AS.cfg.getProperty(V)){AR.enable();}AS.subscribe(D,AR.enable,null,AR);AS.subscribe(M,AR.disable,null,AR);AV._keyListener=AR;AS.unsubscribe(D,AV._createKeyListener,AW);},configKeyListener:function(AT,AS){var AV=AS[0],AU=this,AR=AU.parent;if(AU._keyData){AR.unsubscribe(D,AU._createKeyListener,AU._keyData);AU._keyData=null;}if(AU._keyListener){AR.unsubscribe(D,AU._keyListener.enable);AR.unsubscribe(M,AU._keyListener.disable);AU._keyListener.disable();AU._keyListener=null;}if(AV){AU._keyData=AV;AR.subscribe(D,AU._createKeyListener,AV,AU);}},initDefaultConfig:function(){var AR=this.cfg;AR.addProperty(o.key,{handler:this.configText,value:o.value,validator:o.validator,suppressEvent:o.suppressEvent});AR.addProperty(s.key,{handler:this.configHelpText,supercedes:s.supercedes,suppressEvent:s.suppressEvent});AR.addProperty(G.key,{handler:this.configURL,value:G.value,suppressEvent:G.suppressEvent});AR.addProperty(AO.key,{handler:this.configTarget,suppressEvent:AO.suppressEvent});AR.addProperty(AP.key,{handler:this.configEmphasis,value:AP.value,validator:AP.validator,suppressEvent:AP.suppressEvent,supercedes:AP.supercedes});AR.addProperty(d.key,{handler:this.configStrongEmphasis,value:d.value,validator:d.validator,suppressEvent:d.suppressEvent,supercedes:d.supercedes});AR.addProperty(l.key,{handler:this.configChecked,value:l.value,validator:l.validator,suppressEvent:l.suppressEvent,supercedes:l.supercedes});AR.addProperty(AM.key,{handler:this.configDisabled,value:AM.value,validator:AM.validator,suppressEvent:AM.suppressEvent});AR.addProperty(f.key,{handler:this.configSelected,value:f.value,validator:f.validator,suppressEvent:f.suppressEvent});AR.addProperty(F.key,{handler:this.configSubmenu,supercedes:F.supercedes,suppressEvent:F.suppressEvent});AR.addProperty(u.key,{handler:this.configOnClick,suppressEvent:u.suppressEvent});AR.addProperty(AC.key,{handler:this.configClassName,value:AC.value,validator:AC.validator,suppressEvent:AC.suppressEvent});AR.addProperty(z.key,{handler:this.configKeyListener,value:z.value,suppressEvent:z.suppressEvent});
+},getNextSibling:function(){var AR=function(AX){return(AX.nodeName.toLowerCase()==="ul");},AV=this.element,AU=x.getNextSibling(AV),AT,AS,AW;if(!AU){AT=AV.parentNode;AS=x.getNextSiblingBy(AT,AR);if(AS){AW=AS;}else{AW=x.getFirstChildBy(AT.parentNode,AR);}AU=x.getFirstChild(AW);}return YAHOO.widget.MenuManager.getMenuItem(AU.id);},getNextEnabledSibling:function(){var AR=this.getNextSibling();return(AR.cfg.getProperty(H)||AR.element.style.display==t)?AR.getNextEnabledSibling():AR;},getPreviousSibling:function(){var AR=function(AX){return(AX.nodeName.toLowerCase()==="ul");},AV=this.element,AU=x.getPreviousSibling(AV),AT,AS,AW;if(!AU){AT=AV.parentNode;AS=x.getPreviousSiblingBy(AT,AR);if(AS){AW=AS;}else{AW=x.getLastChildBy(AT.parentNode,AR);}AU=x.getLastChild(AW);}return YAHOO.widget.MenuManager.getMenuItem(AU.id);},getPreviousEnabledSibling:function(){var AR=this.getPreviousSibling();return(AR.cfg.getProperty(H)||AR.element.style.display==t)?AR.getPreviousEnabledSibling():AR;},focus:function(){var AU=this.parent,AT=this._oAnchor,AR=AU.activeItem;function AS(){try{if(!(k.ie&&!document.hasFocus())){if(AR){AR.blurEvent.fire();}AT.focus();this.focusEvent.fire();}}catch(AV){}}if(!this.cfg.getProperty(H)&&AU&&AU.cfg.getProperty(V)&&this.element.style.display!=t){AQ.later(0,this,AS);}},blur:function(){var AR=this.parent;if(!this.cfg.getProperty(H)&&AR&&AR.cfg.getProperty(V)){AQ.later(0,this,function(){try{this._oAnchor.blur();this.blurEvent.fire();}catch(AS){}},0);}},hasFocus:function(){return(YAHOO.widget.MenuManager.getFocusedMenuItem()==this);},destroy:function(){var AT=this.element,AS,AR,AV,AU;if(AT){AS=this.cfg.getProperty(w);if(AS){AS.destroy();}AR=AT.parentNode;if(AR){AR.removeChild(AT);this.destroyEvent.fire();}AU=p.length-1;do{AV=p[AU];this[AV[0]].unsubscribeAll();}while(AU--);this.cfg.configChangedEvent.unsubscribeAll();}},toString:function(){var AS=m,AR=this.id;if(AR){AS+=(E+AR);}return AS;}};AQ.augmentProto(c,YAHOO.util.EventProvider);})();(function(){var B="xy",C="mousedown",F="ContextMenu",J=" ";YAHOO.widget.ContextMenu=function(L,K){YAHOO.widget.ContextMenu.superclass.constructor.call(this,L,K);};var I=YAHOO.util.Event,E=YAHOO.env.ua,G=YAHOO.widget.ContextMenu,A={"TRIGGER_CONTEXT_MENU":"triggerContextMenu","CONTEXT_MENU":(E.opera?C:"contextmenu"),"CLICK":"click"},H={key:"trigger",suppressEvent:true};function D(L,K,M){this.cfg.setProperty(B,M);this.beforeShowEvent.unsubscribe(D,M);}YAHOO.lang.extend(G,YAHOO.widget.Menu,{_oTrigger:null,_bCancelled:false,contextEventTarget:null,triggerContextMenuEvent:null,init:function(L,K){G.superclass.init.call(this,L);this.beforeInitEvent.fire(G);if(K){this.cfg.applyConfig(K,true);}this.initEvent.fire(G);},initEvents:function(){G.superclass.initEvents.call(this);this.triggerContextMenuEvent=this.createEvent(A.TRIGGER_CONTEXT_MENU);this.triggerContextMenuEvent.signature=YAHOO.util.CustomEvent.LIST;},cancel:function(){this._bCancelled=true;},_removeEventHandlers:function(){var K=this._oTrigger;if(K){I.removeListener(K,A.CONTEXT_MENU,this._onTriggerContextMenu);if(E.opera){I.removeListener(K,A.CLICK,this._onTriggerClick);}}},_onTriggerClick:function(L,K){if(L.ctrlKey){I.stopEvent(L);}},_onTriggerContextMenu:function(M,K){var L;if(!(M.type==C&&!M.ctrlKey)){this.contextEventTarget=I.getTarget(M);this.triggerContextMenuEvent.fire(M);if(!this._bCancelled){I.stopEvent(M);YAHOO.widget.MenuManager.hideVisible();L=I.getXY(M);if(!YAHOO.util.Dom.inDocument(this.element)){this.beforeShowEvent.subscribe(D,L);}else{this.cfg.setProperty(B,L);}this.show();}this._bCancelled=false;}},toString:function(){var L=F,K=this.id;if(K){L+=(J+K);}return L;},initDefaultConfig:function(){G.superclass.initDefaultConfig.call(this);this.cfg.addProperty(H.key,{handler:this.configTrigger,suppressEvent:H.suppressEvent});},destroy:function(){this._removeEventHandlers();G.superclass.destroy.call(this);},configTrigger:function(L,K,N){var M=K[0];if(M){if(this._oTrigger){this._removeEventHandlers();}this._oTrigger=M;I.on(M,A.CONTEXT_MENU,this._onTriggerContextMenu,this,true);if(E.opera){I.on(M,A.CLICK,this._onTriggerClick,this,true);}}else{this._removeEventHandlers();}}});}());YAHOO.widget.ContextMenuItem=YAHOO.widget.MenuItem;(function(){var D=YAHOO.lang,N="static",M="dynamic,"+N,A="disabled",F="selected",B="autosubmenudisplay",G="submenu",C="visible",Q=" ",H="submenutoggleregion",P="MenuBar";YAHOO.widget.MenuBar=function(T,S){YAHOO.widget.MenuBar.superclass.constructor.call(this,T,S);};function O(T){var S=false;if(D.isString(T)){S=(M.indexOf((T.toLowerCase()))!=-1);}return S;}var R=YAHOO.util.Event,L=YAHOO.widget.MenuBar,K={key:"position",value:N,validator:O,supercedes:[C]},E={key:"submenualignment",value:["tl","bl"]},J={key:B,value:false,validator:D.isBoolean,suppressEvent:true},I={key:H,value:false,validator:D.isBoolean};D.extend(L,YAHOO.widget.Menu,{init:function(T,S){if(!this.ITEM_TYPE){this.ITEM_TYPE=YAHOO.widget.MenuBarItem;}L.superclass.init.call(this,T);this.beforeInitEvent.fire(L);if(S){this.cfg.applyConfig(S,true);}this.initEvent.fire(L);},CSS_CLASS_NAME:"yuimenubar",SUBMENU_TOGGLE_REGION_WIDTH:20,_onKeyDown:function(U,T,Y){var S=T[0],Z=T[1],W,X,V;if(Z&&!Z.cfg.getProperty(A)){X=Z.cfg;switch(S.keyCode){case 37:case 39:if(Z==this.activeItem&&!X.getProperty(F)){X.setProperty(F,true);}else{V=(S.keyCode==37)?Z.getPreviousEnabledSibling():Z.getNextEnabledSibling();if(V){this.clearActiveItem();V.cfg.setProperty(F,true);W=V.cfg.getProperty(G);if(W){W.show();W.setInitialFocus();}else{V.focus();}}}R.preventDefault(S);break;case 40:if(this.activeItem!=Z){this.clearActiveItem();X.setProperty(F,true);Z.focus();}W=X.getProperty(G);if(W){if(W.cfg.getProperty(C)){W.setInitialSelection();W.setInitialFocus();}else{W.show();W.setInitialFocus();}}R.preventDefault(S);break;}}if(S.keyCode==27&&this.activeItem){W=this.activeItem.cfg.getProperty(G);if(W&&W.cfg.getProperty(C)){W.hide();this.activeItem.focus();}else{this.activeItem.cfg.setProperty(F,false);this.activeItem.blur();}R.preventDefault(S);}},_onClick:function(e,Y,b){L.superclass._onClick.call(this,e,Y,b);
+var d=Y[1],T=true,S,f,U,W,Z,a,c,V;var X=function(){if(a.cfg.getProperty(C)){a.hide();}else{a.show();}};if(d&&!d.cfg.getProperty(A)){f=Y[0];U=R.getTarget(f);W=this.activeItem;Z=this.cfg;if(W&&W!=d){this.clearActiveItem();}d.cfg.setProperty(F,true);a=d.cfg.getProperty(G);if(a){S=d.element;c=YAHOO.util.Dom.getX(S);V=c+(S.offsetWidth-this.SUBMENU_TOGGLE_REGION_WIDTH);if(Z.getProperty(H)){if(R.getPageX(f)>V){X();R.preventDefault(f);T=false;}}else{X();}}}return T;},configSubmenuToggle:function(U,T){var S=T[0];if(S){this.cfg.setProperty(B,false);}},toString:function(){var T=P,S=this.id;if(S){T+=(Q+S);}return T;},initDefaultConfig:function(){L.superclass.initDefaultConfig.call(this);var S=this.cfg;S.addProperty(K.key,{handler:this.configPosition,value:K.value,validator:K.validator,supercedes:K.supercedes});S.addProperty(E.key,{value:E.value,suppressEvent:E.suppressEvent});S.addProperty(J.key,{value:J.value,validator:J.validator,suppressEvent:J.suppressEvent});S.addProperty(I.key,{value:I.value,validator:I.validator,handler:this.configSubmenuToggle});}});}());YAHOO.widget.MenuBarItem=function(B,A){YAHOO.widget.MenuBarItem.superclass.constructor.call(this,B,A);};YAHOO.lang.extend(YAHOO.widget.MenuBarItem,YAHOO.widget.MenuItem,{init:function(B,A){if(!this.SUBMENU_TYPE){this.SUBMENU_TYPE=YAHOO.widget.Menu;}YAHOO.widget.MenuBarItem.superclass.init.call(this,B);var C=this.cfg;if(A){C.applyConfig(A,true);}C.fireQueue();},CSS_CLASS_NAME:"yuimenubaritem",CSS_LABEL_CLASS_NAME:"yuimenubaritemlabel",toString:function(){var A="MenuBarItem";if(this.cfg&&this.cfg.getProperty("text")){A+=(": "+this.cfg.getProperty("text"));}return A;}});YAHOO.register("menu",YAHOO.widget.Menu,{version:"2.8.2r1",build:"7"}); \ No newline at end of file
diff --git a/js/yui/paginator/paginator-min.js b/js/yui/paginator/paginator-min.js
new file mode 100644
index 000000000..8e981834f
--- /dev/null
+++ b/js/yui/paginator/paginator-min.js
@@ -0,0 +1,10 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+(function(){var D=YAHOO.util.Dom,F=YAHOO.lang,B=F.isObject,E=F.isFunction,C=F.isArray,A=F.isString;function G(K){var N=G.VALUE_UNLIMITED,L,H,I,J,M;K=B(K)?K:{};this.initConfig();this.initEvents();this.set("rowsPerPage",K.rowsPerPage,true);if(G.isNumeric(K.totalRecords)){this.set("totalRecords",K.totalRecords,true);}this.initUIComponents();for(L in K){if(K.hasOwnProperty(L)){this.set(L,K[L],true);}}H=this.get("initialPage");I=this.get("totalRecords");J=this.get("rowsPerPage");if(H>1&&J!==N){M=(H-1)*J;if(I===N||M<I){this.set("recordOffset",M,true);}}}F.augmentObject(G,{id:0,ID_BASE:"yui-pg",VALUE_UNLIMITED:-1,TEMPLATE_DEFAULT:"{FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink}",TEMPLATE_ROWS_PER_PAGE:"{FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink} {RowsPerPageDropdown}",ui:{},isNumeric:function(H){return isFinite(+H);},toNumber:function(H){return isFinite(+H)?+H:null;}},true);G.prototype={_containers:[],_batch:false,_pageChanged:false,_state:null,initConfig:function(){var H=G.VALUE_UNLIMITED;this.setAttributeConfig("rowsPerPage",{value:0,validator:G.isNumeric,setter:G.toNumber});this.setAttributeConfig("containers",{value:null,validator:function(K){if(!C(K)){K=[K];}for(var J=0,I=K.length;J<I;++J){if(A(K[J])||(B(K[J])&&K[J].nodeType===1)){continue;}return false;}return true;},method:function(I){I=D.get(I);if(!C(I)){I=[I];}this._containers=I;}});this.setAttributeConfig("totalRecords",{value:0,validator:G.isNumeric,setter:G.toNumber});this.setAttributeConfig("recordOffset",{value:0,validator:function(J){var I=this.get("totalRecords");if(G.isNumeric(J)){J=+J;return I===H||I>J||(I===0&&J===0);}return false;},setter:G.toNumber});this.setAttributeConfig("initialPage",{value:1,validator:G.isNumeric,setter:G.toNumber});this.setAttributeConfig("template",{value:G.TEMPLATE_DEFAULT,validator:A});this.setAttributeConfig("containerClass",{value:"yui-pg-container",validator:A});this.setAttributeConfig("alwaysVisible",{value:true,validator:F.isBoolean});this.setAttributeConfig("updateOnChange",{value:false,validator:F.isBoolean});this.setAttributeConfig("id",{value:G.id++,readOnly:true});this.setAttributeConfig("rendered",{value:false,readOnly:true});},initUIComponents:function(){var J=G.ui,I,H;for(I in J){if(J.hasOwnProperty(I)){H=J[I];if(B(H)&&E(H.init)){H.init(this);}}}},initEvents:function(){this.createEvent("render");this.createEvent("rendered");this.createEvent("changeRequest");this.createEvent("pageChange");this.createEvent("beforeDestroy");this.createEvent("destroy");this._selfSubscribe();},_selfSubscribe:function(){this.subscribe("totalRecordsChange",this.updateVisibility,this,true);this.subscribe("alwaysVisibleChange",this.updateVisibility,this,true);this.subscribe("totalRecordsChange",this._handleStateChange,this,true);this.subscribe("recordOffsetChange",this._handleStateChange,this,true);this.subscribe("rowsPerPageChange",this._handleStateChange,this,true);this.subscribe("totalRecordsChange",this._syncRecordOffset,this,true);},_syncRecordOffset:function(K){var H=K.newValue,J,I;if(K.prevValue!==H){if(H!==G.VALUE_UNLIMITED){J=this.get("rowsPerPage");if(J&&this.get("recordOffset")>=H){I=this.getState({totalRecords:K.prevValue,recordOffset:this.get("recordOffset")});this.set("recordOffset",I.before.recordOffset);this._firePageChange(I);}}}},_handleStateChange:function(I){if(I.prevValue!==I.newValue){var J=this._state||{},H;J[I.type.replace(/Change$/,"")]=I.prevValue;H=this.getState(J);if(H.page!==H.before.page){if(this._batch){this._pageChanged=true;}else{this._firePageChange(H);}}}},_firePageChange:function(H){if(B(H)){var I=H.before;delete H.before;this.fireEvent("pageChange",{type:"pageChange",prevValue:H.page,newValue:I.page,prevState:H,newState:I});}},render:function(){if(this.get("rendered")){return this;}var K=this.get("template"),L=this.getState(),J=G.ID_BASE+this.get("id")+"-",I,H;for(I=0,H=this._containers.length;I<H;++I){this._renderTemplate(this._containers[I],K,J+I,true);}this.updateVisibility();if(this._containers.length){this.setAttributeConfig("rendered",{value:true});this.fireEvent("render",L);this.fireEvent("rendered",L);}return this;},_renderTemplate:function(I,M,L,K){var O=this.get("containerClass"),N,J,H;if(!I){return;}D.setStyle(I,"display","none");D.addClass(I,O);I.innerHTML=M.replace(/\{([a-z0-9_ \-]+)\}/gi,'<span class="yui-pg-ui yui-pg-ui-$1"></span>');N=D.getElementsByClassName("yui-pg-ui","span",I);for(J=0,H=N.length;J<H;++J){this.renderUIComponent(N[J],L);}if(!K){D.setStyle(I,"display","");}},renderUIComponent:function(H,M){var L=H.parentNode,K=/yui-pg-ui-(\w+)/.exec(H.className),J=K&&G.ui[K[1]],I;if(E(J)){I=new J(this);if(E(I.render)){L.replaceChild(I.render(M),H);}}},destroy:function(){this.fireEvent("beforeDestroy");this.fireEvent("destroy");this.setAttributeConfig("rendered",{value:false});this.unsubscribeAll();},updateVisibility:function(M){var I=this.get("alwaysVisible"),O,N,K,L,J,H;if(!M||M.type==="alwaysVisibleChange"||!I){O=this.get("totalRecords");N=true;K=this.get("rowsPerPage");L=this.get("rowsPerPageOptions");if(C(L)){for(J=0,H=L.length;J<H;++J){K=Math.min(K,L[J]);}}if(O!==G.VALUE_UNLIMITED&&O<=K){N=false;}N=N||I;for(J=0,H=this._containers.length;J<H;++J){D.setStyle(this._containers[J],"display",N?"":"none");}}},getContainerNodes:function(){return this._containers;},getTotalPages:function(){var H=this.get("totalRecords"),I=this.get("rowsPerPage");if(!I){return null;}if(H===G.VALUE_UNLIMITED){return G.VALUE_UNLIMITED;}return Math.ceil(H/I);},hasPage:function(I){if(!F.isNumber(I)||I<1){return false;}var H=this.getTotalPages();return(H===G.VALUE_UNLIMITED||H>=I);},getCurrentPage:function(){var H=this.get("rowsPerPage");if(!H||!this.get("totalRecords")){return 0;}return Math.floor(this.get("recordOffset")/H)+1;},hasNextPage:function(){var H=this.getCurrentPage(),I=this.getTotalPages();return H&&(I===G.VALUE_UNLIMITED||H<I);},getNextPage:function(){return this.hasNextPage()?this.getCurrentPage()+1:null;},hasPreviousPage:function(){return(this.getCurrentPage()>1);
+},getPreviousPage:function(){return(this.hasPreviousPage()?this.getCurrentPage()-1:1);},getPageRecords:function(K){if(!F.isNumber(K)){K=this.getCurrentPage();}var J=this.get("rowsPerPage"),I=this.get("totalRecords"),L,H;if(!K||!J){return null;}L=(K-1)*J;if(I!==G.VALUE_UNLIMITED){if(L>=I){return null;}H=Math.min(L+J,I)-1;}else{H=L+J-1;}return[L,H];},setPage:function(I,H){if(this.hasPage(I)&&I!==this.getCurrentPage()){if(this.get("updateOnChange")||H){this.set("recordOffset",(I-1)*this.get("rowsPerPage"));}else{this.fireEvent("changeRequest",this.getState({"page":I}));}}},getRowsPerPage:function(){return this.get("rowsPerPage");},setRowsPerPage:function(I,H){if(G.isNumeric(I)&&+I>0&&+I!==this.get("rowsPerPage")){if(this.get("updateOnChange")||H){this.set("rowsPerPage",I);}else{this.fireEvent("changeRequest",this.getState({"rowsPerPage":+I}));}}},getTotalRecords:function(){return this.get("totalRecords");},setTotalRecords:function(I,H){if(G.isNumeric(I)&&+I>=0&&+I!==this.get("totalRecords")){if(this.get("updateOnChange")||H){this.set("totalRecords",I);}else{this.fireEvent("changeRequest",this.getState({"totalRecords":+I}));}}},getStartIndex:function(){return this.get("recordOffset");},setStartIndex:function(I,H){if(G.isNumeric(I)&&+I>=0&&+I!==this.get("recordOffset")){if(this.get("updateOnChange")||H){this.set("recordOffset",I);}else{this.fireEvent("changeRequest",this.getState({"recordOffset":+I}));}}},getState:function(O){var Q=G.VALUE_UNLIMITED,L=Math,N=L.max,P=L.ceil,J,H,K;function I(S,M,R){if(S<=0||M===0){return 0;}if(M===Q||M>S){return S-(S%R);}return M-(M%R||R);}J={paginator:this,totalRecords:this.get("totalRecords"),rowsPerPage:this.get("rowsPerPage"),records:this.getPageRecords()};J.recordOffset=I(this.get("recordOffset"),J.totalRecords,J.rowsPerPage);J.page=P(J.recordOffset/J.rowsPerPage)+1;if(!O){return J;}H={paginator:this,before:J,rowsPerPage:O.rowsPerPage||J.rowsPerPage,totalRecords:(G.isNumeric(O.totalRecords)?N(O.totalRecords,Q):+J.totalRecords)};if(H.totalRecords===0){H.recordOffset=H.page=0;}else{K=G.isNumeric(O.page)?(O.page-1)*H.rowsPerPage:G.isNumeric(O.recordOffset)?+O.recordOffset:J.recordOffset;H.recordOffset=I(K,H.totalRecords,H.rowsPerPage);H.page=P(H.recordOffset/H.rowsPerPage)+1;}H.records=[H.recordOffset,H.recordOffset+H.rowsPerPage-1];if(H.totalRecords!==Q&&H.recordOffset<H.totalRecords&&H.records&&H.records[1]>H.totalRecords-1){H.records[1]=H.totalRecords-1;}return H;},setState:function(I){if(B(I)){this._state=this.getState({});I={page:I.page,rowsPerPage:I.rowsPerPage,totalRecords:I.totalRecords,recordOffset:I.recordOffset};if(I.page&&I.recordOffset===undefined){I.recordOffset=(I.page-1)*(I.rowsPerPage||this.get("rowsPerPage"));}this._batch=true;this._pageChanged=false;for(var H in I){if(I.hasOwnProperty(H)&&this._configs.hasOwnProperty(H)){this.set(H,I[H]);}}this._batch=false;if(this._pageChanged){this._pageChanged=false;this._firePageChange(this.getState(this._state));}}}};F.augmentProto(G,YAHOO.util.AttributeProvider);YAHOO.widget.Paginator=G;})();(function(){var B=YAHOO.widget.Paginator,A=YAHOO.lang;B.ui.CurrentPageReport=function(C){this.paginator=C;C.subscribe("recordOffsetChange",this.update,this,true);C.subscribe("rowsPerPageChange",this.update,this,true);C.subscribe("totalRecordsChange",this.update,this,true);C.subscribe("pageReportTemplateChange",this.update,this,true);C.subscribe("destroy",this.destroy,this,true);C.subscribe("pageReportClassChange",this.update,this,true);};B.ui.CurrentPageReport.init=function(C){C.setAttributeConfig("pageReportClass",{value:"yui-pg-current",validator:A.isString});C.setAttributeConfig("pageReportTemplate",{value:"({currentPage} of {totalPages})",validator:A.isString});C.setAttributeConfig("pageReportValueGenerator",{value:function(F){var E=F.getCurrentPage(),D=F.getPageRecords();return{"currentPage":D?E:0,"totalPages":F.getTotalPages(),"startIndex":D?D[0]:0,"endIndex":D?D[1]:0,"startRecord":D?D[0]+1:0,"endRecord":D?D[1]+1:0,"totalRecords":F.get("totalRecords")};},validator:A.isFunction});};B.ui.CurrentPageReport.sprintf=function(D,C){return D.replace(/\{([\w\s\-]+)\}/g,function(E,F){return(F in C)?C[F]:"";});};B.ui.CurrentPageReport.prototype={span:null,render:function(C){this.span=document.createElement("span");this.span.id=C+"-page-report";this.span.className=this.paginator.get("pageReportClass");this.update();return this.span;},update:function(C){if(C&&C.prevValue===C.newValue){return;}this.span.innerHTML=B.ui.CurrentPageReport.sprintf(this.paginator.get("pageReportTemplate"),this.paginator.get("pageReportValueGenerator")(this.paginator));},destroy:function(){this.span.parentNode.removeChild(this.span);this.span=null;}};})();(function(){var B=YAHOO.widget.Paginator,A=YAHOO.lang;B.ui.PageLinks=function(C){this.paginator=C;C.subscribe("recordOffsetChange",this.update,this,true);C.subscribe("rowsPerPageChange",this.update,this,true);C.subscribe("totalRecordsChange",this.update,this,true);C.subscribe("pageLinksChange",this.rebuild,this,true);C.subscribe("pageLinkClassChange",this.rebuild,this,true);C.subscribe("currentPageClassChange",this.rebuild,this,true);C.subscribe("destroy",this.destroy,this,true);C.subscribe("pageLinksContainerClassChange",this.rebuild,this,true);};B.ui.PageLinks.init=function(C){C.setAttributeConfig("pageLinkClass",{value:"yui-pg-page",validator:A.isString});C.setAttributeConfig("currentPageClass",{value:"yui-pg-current-page",validator:A.isString});C.setAttributeConfig("pageLinksContainerClass",{value:"yui-pg-pages",validator:A.isString});C.setAttributeConfig("pageLinks",{value:10,validator:B.isNumeric});C.setAttributeConfig("pageLabelBuilder",{value:function(D,E){return D;},validator:A.isFunction});};B.ui.PageLinks.calculateRange=function(E,F,D){var I=B.VALUE_UNLIMITED,H,C,G;if(!E||D===0||F===0||(F===I&&D===I)){return[0,-1];}if(F!==I){D=D===I?F:Math.min(D,F);}H=Math.max(1,Math.ceil(E-(D/2)));if(F===I){C=H+D-1;}else{C=Math.min(F,H+D-1);}G=D-(C-H+1);H=Math.max(1,H-G);return[H,C];};B.ui.PageLinks.prototype={current:0,container:null,render:function(C){var D=this.paginator;
+this.container=document.createElement("span");this.container.id=C+"-pages";this.container.className=D.get("pageLinksContainerClass");YAHOO.util.Event.on(this.container,"click",this.onClick,this,true);this.update({newValue:null,rebuild:true});return this.container;},update:function(J){if(J&&J.prevValue===J.newValue){return;}var E=this.paginator,I=E.getCurrentPage();if(this.current!==I||!I||J.rebuild){var L=E.get("pageLabelBuilder"),H=B.ui.PageLinks.calculateRange(I,E.getTotalPages(),E.get("pageLinks")),D=H[0],F=H[1],K="",C,G;C='<a href="#" class="'+E.get("pageLinkClass")+'" page="';for(G=D;G<=F;++G){if(G===I){K+='<span class="'+E.get("currentPageClass")+" "+E.get("pageLinkClass")+'">'+L(G,E)+"</span>";}else{K+=C+G+'">'+L(G,E)+"</a>";}}this.container.innerHTML=K;}},rebuild:function(C){C.rebuild=true;this.update(C);},destroy:function(){YAHOO.util.Event.purgeElement(this.container,true);this.container.parentNode.removeChild(this.container);this.container=null;},onClick:function(D){var C=YAHOO.util.Event.getTarget(D);if(C&&YAHOO.util.Dom.hasClass(C,this.paginator.get("pageLinkClass"))){YAHOO.util.Event.stopEvent(D);this.paginator.setPage(parseInt(C.getAttribute("page"),10));}}};})();(function(){var B=YAHOO.widget.Paginator,A=YAHOO.lang;B.ui.FirstPageLink=function(C){this.paginator=C;C.subscribe("recordOffsetChange",this.update,this,true);C.subscribe("rowsPerPageChange",this.update,this,true);C.subscribe("totalRecordsChange",this.update,this,true);C.subscribe("destroy",this.destroy,this,true);C.subscribe("firstPageLinkLabelChange",this.update,this,true);C.subscribe("firstPageLinkClassChange",this.update,this,true);};B.ui.FirstPageLink.init=function(C){C.setAttributeConfig("firstPageLinkLabel",{value:"&lt;&lt; first",validator:A.isString});C.setAttributeConfig("firstPageLinkClass",{value:"yui-pg-first",validator:A.isString});};B.ui.FirstPageLink.prototype={current:null,link:null,span:null,render:function(D){var E=this.paginator,F=E.get("firstPageLinkClass"),C=E.get("firstPageLinkLabel");this.link=document.createElement("a");this.span=document.createElement("span");this.link.id=D+"-first-link";this.link.href="#";this.link.className=F;this.link.innerHTML=C;YAHOO.util.Event.on(this.link,"click",this.onClick,this,true);this.span.id=D+"-first-span";this.span.className=F;this.span.innerHTML=C;this.current=E.getCurrentPage()>1?this.link:this.span;return this.current;},update:function(D){if(D&&D.prevValue===D.newValue){return;}var C=this.current?this.current.parentNode:null;if(this.paginator.getCurrentPage()>1){if(C&&this.current===this.span){C.replaceChild(this.link,this.current);this.current=this.link;}}else{if(C&&this.current===this.link){C.replaceChild(this.span,this.current);this.current=this.span;}}},destroy:function(){YAHOO.util.Event.purgeElement(this.link);this.current.parentNode.removeChild(this.current);this.link=this.span=null;},onClick:function(C){YAHOO.util.Event.stopEvent(C);this.paginator.setPage(1);}};})();(function(){var B=YAHOO.widget.Paginator,A=YAHOO.lang;B.ui.LastPageLink=function(C){this.paginator=C;C.subscribe("recordOffsetChange",this.update,this,true);C.subscribe("rowsPerPageChange",this.update,this,true);C.subscribe("totalRecordsChange",this.update,this,true);C.subscribe("destroy",this.destroy,this,true);C.subscribe("lastPageLinkLabelChange",this.update,this,true);C.subscribe("lastPageLinkClassChange",this.update,this,true);};B.ui.LastPageLink.init=function(C){C.setAttributeConfig("lastPageLinkLabel",{value:"last &gt;&gt;",validator:A.isString});C.setAttributeConfig("lastPageLinkClass",{value:"yui-pg-last",validator:A.isString});};B.ui.LastPageLink.prototype={current:null,link:null,span:null,na:null,render:function(D){var F=this.paginator,G=F.get("lastPageLinkClass"),C=F.get("lastPageLinkLabel"),E=F.getTotalPages();this.link=document.createElement("a");this.span=document.createElement("span");this.na=this.span.cloneNode(false);this.link.id=D+"-last-link";this.link.href="#";this.link.className=G;this.link.innerHTML=C;YAHOO.util.Event.on(this.link,"click",this.onClick,this,true);this.span.id=D+"-last-span";this.span.className=G;this.span.innerHTML=C;this.na.id=D+"-last-na";switch(E){case B.VALUE_UNLIMITED:this.current=this.na;break;case F.getCurrentPage():this.current=this.span;break;default:this.current=this.link;}return this.current;},update:function(D){if(D&&D.prevValue===D.newValue){return;}var C=this.current?this.current.parentNode:null,E=this.link;if(C){switch(this.paginator.getTotalPages()){case B.VALUE_UNLIMITED:E=this.na;break;case this.paginator.getCurrentPage():E=this.span;break;}if(this.current!==E){C.replaceChild(E,this.current);this.current=E;}}},destroy:function(){YAHOO.util.Event.purgeElement(this.link);this.current.parentNode.removeChild(this.current);this.link=this.span=null;},onClick:function(C){YAHOO.util.Event.stopEvent(C);this.paginator.setPage(this.paginator.getTotalPages());}};})();(function(){var B=YAHOO.widget.Paginator,A=YAHOO.lang;B.ui.NextPageLink=function(C){this.paginator=C;C.subscribe("recordOffsetChange",this.update,this,true);C.subscribe("rowsPerPageChange",this.update,this,true);C.subscribe("totalRecordsChange",this.update,this,true);C.subscribe("destroy",this.destroy,this,true);C.subscribe("nextPageLinkLabelChange",this.update,this,true);C.subscribe("nextPageLinkClassChange",this.update,this,true);};B.ui.NextPageLink.init=function(C){C.setAttributeConfig("nextPageLinkLabel",{value:"next &gt;",validator:A.isString});C.setAttributeConfig("nextPageLinkClass",{value:"yui-pg-next",validator:A.isString});};B.ui.NextPageLink.prototype={current:null,link:null,span:null,render:function(D){var F=this.paginator,G=F.get("nextPageLinkClass"),C=F.get("nextPageLinkLabel"),E=F.getTotalPages();this.link=document.createElement("a");this.span=document.createElement("span");this.link.id=D+"-next-link";this.link.href="#";this.link.className=G;this.link.innerHTML=C;YAHOO.util.Event.on(this.link,"click",this.onClick,this,true);this.span.id=D+"-next-span";this.span.className=G;
+this.span.innerHTML=C;this.current=F.getCurrentPage()===E?this.span:this.link;return this.current;},update:function(E){if(E&&E.prevValue===E.newValue){return;}var D=this.paginator.getTotalPages(),C=this.current?this.current.parentNode:null;if(this.paginator.getCurrentPage()!==D){if(C&&this.current===this.span){C.replaceChild(this.link,this.current);this.current=this.link;}}else{if(this.current===this.link){if(C){C.replaceChild(this.span,this.current);this.current=this.span;}}}},destroy:function(){YAHOO.util.Event.purgeElement(this.link);this.current.parentNode.removeChild(this.current);this.link=this.span=null;},onClick:function(C){YAHOO.util.Event.stopEvent(C);this.paginator.setPage(this.paginator.getNextPage());}};})();(function(){var B=YAHOO.widget.Paginator,A=YAHOO.lang;B.ui.PreviousPageLink=function(C){this.paginator=C;C.subscribe("recordOffsetChange",this.update,this,true);C.subscribe("rowsPerPageChange",this.update,this,true);C.subscribe("totalRecordsChange",this.update,this,true);C.subscribe("destroy",this.destroy,this,true);C.subscribe("previousPageLinkLabelChange",this.update,this,true);C.subscribe("previousPageLinkClassChange",this.update,this,true);};B.ui.PreviousPageLink.init=function(C){C.setAttributeConfig("previousPageLinkLabel",{value:"&lt; prev",validator:A.isString});C.setAttributeConfig("previousPageLinkClass",{value:"yui-pg-previous",validator:A.isString});};B.ui.PreviousPageLink.prototype={current:null,link:null,span:null,render:function(D){var E=this.paginator,F=E.get("previousPageLinkClass"),C=E.get("previousPageLinkLabel");this.link=document.createElement("a");this.span=document.createElement("span");this.link.id=D+"-prev-link";this.link.href="#";this.link.className=F;this.link.innerHTML=C;YAHOO.util.Event.on(this.link,"click",this.onClick,this,true);this.span.id=D+"-prev-span";this.span.className=F;this.span.innerHTML=C;this.current=E.getCurrentPage()>1?this.link:this.span;return this.current;},update:function(D){if(D&&D.prevValue===D.newValue){return;}var C=this.current?this.current.parentNode:null;if(this.paginator.getCurrentPage()>1){if(C&&this.current===this.span){C.replaceChild(this.link,this.current);this.current=this.link;}}else{if(C&&this.current===this.link){C.replaceChild(this.span,this.current);this.current=this.span;}}},destroy:function(){YAHOO.util.Event.purgeElement(this.link);this.current.parentNode.removeChild(this.current);this.link=this.span=null;},onClick:function(C){YAHOO.util.Event.stopEvent(C);this.paginator.setPage(this.paginator.getPreviousPage());}};})();(function(){var B=YAHOO.widget.Paginator,A=YAHOO.lang;B.ui.RowsPerPageDropdown=function(C){this.paginator=C;C.subscribe("rowsPerPageChange",this.update,this,true);C.subscribe("rowsPerPageOptionsChange",this.rebuild,this,true);C.subscribe("totalRecordsChange",this._handleTotalRecordsChange,this,true);C.subscribe("destroy",this.destroy,this,true);C.subscribe("rowsPerPageDropdownClassChange",this.rebuild,this,true);};B.ui.RowsPerPageDropdown.init=function(C){C.setAttributeConfig("rowsPerPageOptions",{value:[],validator:A.isArray});C.setAttributeConfig("rowsPerPageDropdownClass",{value:"yui-pg-rpp-options",validator:A.isString});};B.ui.RowsPerPageDropdown.prototype={select:null,all:null,render:function(C){this.select=document.createElement("select");this.select.id=C+"-rpp";this.select.className=this.paginator.get("rowsPerPageDropdownClass");this.select.title="Rows per page";YAHOO.util.Event.on(this.select,"change",this.onChange,this,true);this.rebuild();return this.select;},rebuild:function(J){var C=this.paginator,E=this.select,K=C.get("rowsPerPageOptions"),D,I,F,G,H;this.all=null;for(G=0,H=K.length;G<H;++G){I=K[G];D=E.options[G]||E.appendChild(document.createElement("option"));F=A.isValue(I.value)?I.value:I;D.innerHTML=A.isValue(I.text)?I.text:I;if(A.isString(F)&&F.toLowerCase()==="all"){this.all=D;D.value=C.get("totalRecords");}else{D.value=F;}}while(E.options.length>K.length){E.removeChild(E.firstChild);}this.update();},update:function(G){if(G&&G.prevValue===G.newValue){return;}var F=this.paginator.get("rowsPerPage")+"",D=this.select.options,E,C;for(E=0,C=D.length;E<C;++E){if(D[E].value===F){D[E].selected=true;break;}}},onChange:function(C){this.paginator.setRowsPerPage(parseInt(this.select.options[this.select.selectedIndex].value,10));},_handleTotalRecordsChange:function(C){if(!this.all||(C&&C.prevValue===C.newValue)){return;}this.all.value=C.newValue;if(this.all.selected){this.paginator.set("rowsPerPage",C.newValue);}},destroy:function(){YAHOO.util.Event.purgeElement(this.select);this.select.parentNode.removeChild(this.select);this.select=null;}};})();YAHOO.register("paginator",YAHOO.widget.Paginator,{version:"2.8.2r1",build:"7"}); \ No newline at end of file
diff --git a/js/yui/profiler/profiler-min.js b/js/yui/profiler/profiler-min.js
new file mode 100644
index 000000000..467286a98
--- /dev/null
+++ b/js/yui/profiler/profiler-min.js
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+YAHOO.namespace("tool");YAHOO.tool.Profiler=function(){var container={},report={},stopwatches={},WATCH_STARTED=0,WATCH_STOPPED=1,WATCH_PAUSED=2,lang=YAHOO.lang;function createReport(name){report[name]={calls:0,max:0,min:0,avg:0,points:[]};}function saveDataPoint(name,duration){var functionData=report[name];if(!functionData){functionData=createReport(name);}functionData.calls++;functionData.points.push(duration);if(functionData.calls>1){functionData.avg=((functionData.avg*(functionData.calls-1))+duration)/functionData.calls;functionData.min=Math.min(functionData.min,duration);functionData.max=Math.max(functionData.max,duration);}else{functionData.avg=duration;functionData.min=duration;functionData.max=duration;}}return{clear:function(name){if(lang.isString(name)){delete report[name];delete stopwatches[name];}else{report={};stopwatches={};}},getOriginal:function(name){return container[name];},instrument:function(name,method){var newMethod=function(){var start=new Date(),retval=method.apply(this,arguments),stop=new Date();saveDataPoint(name,stop-start);return retval;};lang.augmentObject(newMethod,method);newMethod.__yuiProfiled=true;newMethod.prototype=method.prototype;container[name]=method;container[name].__yuiFuncName=name;createReport(name);return newMethod;},pause:function(name){var now=new Date(),stopwatch=stopwatches[name];if(stopwatch&&stopwatch.state==WATCH_STARTED){stopwatch.total+=(now-stopwatch.start);stopwatch.start=0;stopwatch.state=WATCH_PAUSED;}},start:function(name){if(container[name]){throw new Error("Cannot use '"+name+"' for profiling through start(), name is already in use.");}else{if(!report[name]){createReport(name);}if(!stopwatches[name]){stopwatches[name]={state:WATCH_STOPPED,start:0,total:0};}if(stopwatches[name].state==WATCH_STOPPED){stopwatches[name].state=WATCH_STARTED;stopwatches[name].start=new Date();}}},stop:function(name){var now=new Date(),stopwatch=stopwatches[name];if(stopwatch){if(stopwatch.state==WATCH_STARTED){saveDataPoint(name,stopwatch.total+(now-stopwatch.start));}else{if(stopwatch.state==WATCH_PAUSED){saveDataPoint(name,stopwatch.total);}}stopwatch.start=0;stopwatch.total=0;stopwatch.state=WATCH_STOPPED;}},getAverage:function(name){return report[name].avg;},getCallCount:function(name){return report[name].calls;},getMax:function(name){return report[name].max;},getMin:function(name){return report[name].min;},getFunctionReport:function(name){return report[name];},getReport:function(name){return report[name];},getFullReport:function(filter){filter=filter||function(){return true;};if(lang.isFunction(filter)){var fullReport={};for(var name in report){if(filter(report[name])){fullReport[name]=report[name];}}return fullReport;}},registerConstructor:function(name,owner){this.registerFunction(name,owner,true);},registerFunction:function(name,owner,registerPrototype){var funcName=(name.indexOf(".")>-1?name.substring(name.lastIndexOf(".")+1):name),method,prototype;if(!lang.isObject(owner)){owner=eval(name.substring(0,name.lastIndexOf(".")));}method=owner[funcName];prototype=method.prototype;if(lang.isFunction(method)&&!method.__yuiProfiled){owner[funcName]=this.instrument(name,method);container[name].__yuiOwner=owner;container[name].__yuiFuncName=funcName;if(registerPrototype){this.registerObject(name+".prototype",prototype);}}},registerObject:function(name,object,recurse){object=(lang.isObject(object)?object:eval(name));container[name]=object;for(var prop in object){if(typeof object[prop]=="function"){if(prop!="constructor"&&prop!="superclass"){this.registerFunction(name+"."+prop,object);}}else{if(typeof object[prop]=="object"&&recurse){this.registerObject(name+"."+prop,object[prop],recurse);}}}},unregisterConstructor:function(name){if(lang.isFunction(container[name])){this.unregisterFunction(name,true);}},unregisterFunction:function(name,unregisterPrototype){if(lang.isFunction(container[name])){if(unregisterPrototype){this.unregisterObject(name+".prototype",container[name].prototype);}var owner=container[name].__yuiOwner,funcName=container[name].__yuiFuncName;delete container[name].__yuiOwner;delete container[name].__yuiFuncName;owner[funcName]=container[name];delete container[name];}},unregisterObject:function(name,recurse){if(lang.isObject(container[name])){var object=container[name];for(var prop in object){if(typeof object[prop]=="function"){this.unregisterFunction(name+"."+prop);}else{if(typeof object[prop]=="object"&&recurse){this.unregisterObject(name+"."+prop,recurse);}}}delete container[name];}}};}();YAHOO.register("profiler",YAHOO.tool.Profiler,{version:"2.8.2r1",build:"7"}); \ No newline at end of file
diff --git a/js/yui/profilerviewer/profilerviewer-min.js b/js/yui/profilerviewer/profilerviewer-min.js
new file mode 100644
index 000000000..59fadb5f2
--- /dev/null
+++ b/js/yui/profilerviewer/profilerviewer-min.js
@@ -0,0 +1,9 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+(function(){YAHOO.widget.ProfilerViewer=function(H,G){G=G||{};if(arguments.length==1&&!YAHOO.lang.isString(H)&&!H.nodeName){G=H;H=G.element||null;}if(!H&&!G.element){H=this._createProfilerViewerElement();}YAHOO.widget.ProfilerViewer.superclass.constructor.call(this,H,G);this._init();};YAHOO.extend(YAHOO.widget.ProfilerViewer,YAHOO.util.Element);YAHOO.lang.augmentObject(YAHOO.widget.ProfilerViewer,{CLASS:"yui-pv",CLASS_DASHBOARD:"yui-pv-dashboard",CLASS_REFRESH:"yui-pv-refresh",CLASS_BUSY:"yui-pv-busy",CLASS_CHART_CONTAINER:"yui-pv-chartcontainer",CLASS_CHART:"yui-pv-chart",CLASS_CHART_LEGEND:"yui-pv-chartlegend",CLASS_TABLE:"yui-pv-table",STRINGS:{title:"YUI Profiler (beta)",buttons:{viewprofiler:"View Profiler Data",hideprofiler:"Hide Profiler Report",showchart:"Show Chart",hidechart:"Hide Chart",refreshdata:"Refresh Data"},colHeads:{fn:["Function/Method",null],calls:["Calls",40],avg:["Average",80],min:["Shortest",70],max:["Longest",70],total:["Total Time",70],pct:["Percent",70]},millisecondsAbbrev:"ms",initMessage:"initialiazing chart...",installFlashMessage:"Unable to load Flash content. The YUI Charts Control requires Flash Player 9.0.45 or higher. You can download the latest version of Flash Player from the <a href='http://www.adobe.com/go/getflashplayer'>Adobe Flash Player Download Center</a>."},timeAxisLabelFunction:function(H){var G=(H===Math.floor(H))?H:(Math.round(H*1000))/1000;return(G+" "+YAHOO.widget.ProfilerViewer.STRINGS.millisecondsAbbrev);},percentAxisLabelFunction:function(H){var G=(H===Math.floor(H))?H:(Math.round(H*100))/100;return(G+"%");}},true);var C=YAHOO.util.Dom;var A=YAHOO.util.Event;var B=YAHOO.tool.Profiler;var E=YAHOO.widget.ProfilerViewer;var D=E.prototype;D.refreshData=function(){this.fireEvent("dataRefreshEvent");};D.getHeadEl=function(){return(this._headEl)?C.get(this._headEl):false;};D.getBodyEl=function(){return(this._bodyEl)?C.get(this._bodyEl):false;};D.getChartEl=function(){return(this._chartEl)?C.get(this._chartEl):false;};D.getTableEl=function(){return(this._tableEl)?C.get(this._tableEl):false;};D.getDataTable=function(){return this._dataTable;};D.getChart=function(){return this._chart;};D._rendered=false;D._headEl=null;D._bodyEl=null;D._toggleVisibleEl=null;D._busyEl=null;D._busy=false;D._tableEl=null;D._dataTable=null;D._chartEl=null;D._chartLegendEl=null;D._chartElHeight=250;D._chart=null;D._chartInitialized=false;D._init=function(){this.createEvent("dataRefreshEvent");this.createEvent("renderEvent");this.on("dataRefreshEvent",this._refreshDataTable,this,true);this._initLauncherDOM();if(this.get("showChart")){this.on("sortedByChange",this._refreshChart);}};D._createProfilerViewerElement=function(){var G=document.createElement("div");document.body.insertBefore(G,document.body.firstChild);C.addClass(G,this.SKIN_CLASS);C.addClass(G,E.CLASS);return G;};D.toString=function(){return"ProfilerViewer "+(this.get("id")||this.get("tagName"));};D._toggleVisible=function(){var G=(this.get("visible"))?false:true;this.set("visible",G);};D._show=function(){if(!this._busy){this._setBusyState(true);if(!this._rendered){var G=new YAHOO.util.YUILoader();if(this.get("base")){G.base=this.get("base");}var H=["datatable"];if(this.get("showChart")){H.push("charts");}G.insert({require:H,onSuccess:function(){this._render();},scope:this});}else{var I=this.get("element");C.removeClass(I,"yui-pv-minimized");this._toggleVisibleEl.innerHTML=E.STRINGS.buttons.hideprofiler;C.addClass(I,"yui-pv-null");C.removeClass(I,"yui-pv-null");this.refreshData();}}};D._hide=function(){this._toggleVisibleEl.innerHTML=E.STRINGS.buttons.viewprofiler;C.addClass(this.get("element"),"yui-pv-minimized");};D._render=function(){C.removeClass(this.get("element"),"yui-pv-minimized");this._initViewerDOM();this._initDataTable();if(this.get("showChart")){this._initChartDOM();this._initChart();}this._rendered=true;this._toggleVisibleEl.innerHTML=E.STRINGS.buttons.hideprofiler;this.fireEvent("renderEvent");};D._initLauncherDOM=function(){var I=this.get("element");C.addClass(I,E.CLASS);C.addClass(I,"yui-pv-minimized");this._headEl=document.createElement("div");C.addClass(this._headEl,"hd");var H=E.STRINGS.buttons;var G=(this.get("visible"))?H.hideprofiler:H.viewprofiler;this._toggleVisibleEl=this._createButton(G,this._headEl);this._refreshEl=this._createButton(H.refreshdata,this._headEl);C.addClass(this._refreshEl,E.CLASS_REFRESH);this._busyEl=document.createElement("span");this._headEl.appendChild(this._busyEl);var J=document.createElement("h4");J.innerHTML=E.STRINGS.title;this._headEl.appendChild(J);I.appendChild(this._headEl);A.on(this._toggleVisibleEl,"click",this._toggleVisible,this,true);A.on(this._refreshEl,"click",function(){if(!this._busy){this._setBusyState(true);this.fireEvent("dataRefreshEvent");}},this,true);};D._initViewerDOM=function(){var G=this.get("element");this._bodyEl=document.createElement("div");C.addClass(this._bodyEl,"bd");this._tableEl=document.createElement("div");C.addClass(this._tableEl,E.CLASS_TABLE);this._bodyEl.appendChild(this._tableEl);G.appendChild(this._bodyEl);};D._initChartDOM=function(){this._chartContainer=document.createElement("div");C.addClass(this._chartContainer,E.CLASS_CHART_CONTAINER);var H=document.createElement("div");C.addClass(H,E.CLASS_CHART_LEGEND);var G=document.createElement("div");this._chartLegendEl=document.createElement("dl");this._chartLegendEl.innerHTML="<dd>"+E.STRINGS.initMessage+"</dd>";this._chartEl=document.createElement("div");C.addClass(this._chartEl,E.CLASS_CHART);var I=document.createElement("p");I.innerHTML=E.STRINGS.installFlashMessage;this._chartEl.appendChild(I);this._chartContainer.appendChild(H);H.appendChild(G);G.appendChild(this._chartLegendEl);this._chartContainer.appendChild(this._chartEl);this._bodyEl.insertBefore(this._chartContainer,this._tableEl);};D._createButton=function(I,J,H){var G=document.createElement("a");G.innerHTML=G.title=I;if(J){if(!H){J.appendChild(G);}else{J.insertBefore(G,J.firstChild);}}return G;};D._setBusyState=function(G){if(G){C.addClass(this._busyEl,E.CLASS_BUSY);
+this._busy=true;}else{C.removeClass(this._busyEl,E.CLASS_BUSY);this._busy=false;}};D._genSortFunction=function(H,G){var J=H;var I=G;return function(L,K){if(I==YAHOO.widget.DataTable.CLASS_ASC){return L[J]-K[J];}else{return((L[J]-K[J])*-1);}};};var F=function(G){var I=0;for(var H=0;H<G.length;I+=G[H++]){}return I;};D._getProfilerData=function(){var L=B.getFullReport();var N=[];var H=0;for(name in L){if(YAHOO.lang.hasOwnProperty(L,name)){var G=L[name];var I={};I.fn=name;I.points=G.points.slice();I.calls=G.calls;I.min=G.min;I.max=G.max;I.avg=G.avg;I.total=F(I.points);I.points=G.points;var P=this.get("filter");if((!P)||(P(I))){N.push(I);H+=I.total;}}}for(var M=0,K=N.length;M<K;M++){N[M].pct=(H)?(N[M].total*100)/H:0;}var O=this.get("sortedBy");var Q=O.key;var J=O.dir;N.sort(this._genSortFunction(Q,J));return N;};D._initDataTable=function(){var P=this;this._dataSource=new YAHOO.util.DataSource(function(){return P._getProfilerData.call(P);},{responseType:YAHOO.util.DataSource.TYPE_JSARRAY,maxCacheEntries:0});var H=this._dataSource;H.responseSchema={fields:["fn","avg","calls","max","min","total","pct","points"]};var O=function(S,R,T,U){var Q=(U===Math.floor(U))?U:(Math.round(U*1000))/1000;S.innerHTML=Q+" "+E.STRINGS.millisecondsAbbrev;};var N=function(S,R,T,U){var Q=(U===Math.floor(U))?U:(Math.round(U*100))/100;S.innerHTML=Q+"%";};var M=YAHOO.widget.DataTable.CLASS_ASC;var J=YAHOO.widget.DataTable.CLASS_DESC;var K=E.STRINGS.colHeads;var I=O;var L=[{key:"fn",sortable:true,label:K.fn[0],sortOptions:{defaultDir:M},resizeable:(YAHOO.util.DragDrop)?true:false,minWidth:K.fn[1]},{key:"calls",sortable:true,label:K.calls[0],sortOptions:{defaultDir:J},width:K.calls[1]},{key:"avg",sortable:true,label:K.avg[0],sortOptions:{defaultDir:J},formatter:I,width:K.avg[1]},{key:"min",sortable:true,label:K.min[0],sortOptions:{defaultDir:M},formatter:I,width:K.min[1]},{key:"max",sortable:true,label:K.max[0],sortOptions:{defaultDir:J},formatter:I,width:K.max[1]},{key:"total",sortable:true,label:K.total[0],sortOptions:{defaultDir:J},formatter:I,width:K.total[1]},{key:"pct",sortable:true,label:K.pct[0],sortOptions:{defaultDir:J},formatter:N,width:K.pct[1]}];this._dataTable=new YAHOO.widget.DataTable(this._tableEl,L,H,{scrollable:true,height:this.get("tableHeight"),initialRequest:null,sortedBy:{key:"total",dir:YAHOO.widget.DataTable.CLASS_DESC}});var G=this._dataTable;G.subscribe("sortedByChange",this._sortedByChange,this,true);G.subscribe("renderEvent",this._dataTableRenderHandler,this,true);G.subscribe("initEvent",this._dataTableRenderHandler,this,true);A.on(this._tableEl.getElementsByTagName("th"),"click",this._thClickHandler,this,true);};D._sortedByChange=function(G){if(G.newValue&&G.newValue.key){this.set("sortedBy",{key:G.newValue.key,dir:G.newValue.dir});}};D._dataTableRenderHandler=function(G){this._setBusyState(false);};D._thClickHandler=function(G){this._setBusyState(true);};D._refreshDataTable=function(G){var H=this._dataTable;H.getDataSource().sendRequest("",H.onDataReturnInitializeTable,H);};D._refreshChart=function(){switch(this.get("sortedBy").key){case"fn":this._chart.set("dataSource",this._chart.get("dataSource"));return;case"calls":this._chart.set("xAxis",this._chartAxisDefinitionPlain);break;case"pct":this._chart.set("xAxis",this._chartAxisDefinitionPercent);break;default:this._chart.set("xAxis",this._chartAxisDefinitionTime);break;}this._drawChartLegend();this._chart.set("series",this._getSeriesDef(this.get("sortedBy").key));};D._getChartData=function(){var H=this._dataTable.getRecordSet().getRecords(0,this.get("maxChartFunctions"));var G=[];for(var J=0,I=H.length;J<I;J++){G.push(H[J].getData());}return G;};D._getSeriesDef=function(K){var J=this.get("chartSeriesDefinitions")[K];var G=[];for(var I=0,H=J.group.length;I<H;I++){var L=this.get("chartSeriesDefinitions")[J.group[I]];G.push({displayName:L.displayName,xField:L.xField,style:{color:L.style.color,size:L.style.size}});}return G;};D._initChart=function(){this._sizeChartCanvas();YAHOO.widget.Chart.SWFURL=this.get("swfUrl");var G=this;var H=new YAHOO.util.DataSource(function(){return G._getChartData.call(G);},{responseType:YAHOO.util.DataSource.TYPE_JSARRAY,maxCacheEntries:0});H.responseSchema={fields:["fn","avg","calls","max","min","total","pct"]};H.subscribe("responseEvent",this._sizeChartCanvas,this,true);this._chartAxisDefinitionTime=new YAHOO.widget.NumericAxis();this._chartAxisDefinitionTime.labelFunction="YAHOO.widget.ProfilerViewer.timeAxisLabelFunction";this._chartAxisDefinitionPercent=new YAHOO.widget.NumericAxis();this._chartAxisDefinitionPercent.labelFunction="YAHOO.widget.ProfilerViewer.percentAxisLabelFunction";this._chartAxisDefinitionPlain=new YAHOO.widget.NumericAxis();this._chart=new YAHOO.widget.BarChart(this._chartEl,H,{yField:"fn",series:this._getSeriesDef(this.get("sortedBy").key),style:this.get("chartStyle"),xAxis:this._chartAxisDefinitionTime});this._drawChartLegend();this._chartInitialized=true;this._dataTable.unsubscribe("initEvent",this._initChart,this);this._dataTable.subscribe("initEvent",this._refreshChart,this,true);};D._drawChartLegend=function(){var M=this.get("chartSeriesDefinitions");var I=M[this.get("sortedBy").key];var H=this._chartLegendEl;H.innerHTML="";for(var K=0,J=I.group.length;K<J;K++){var N=M[I.group[K]];var L=document.createElement("dt");C.setStyle(L,"backgroundColor","#"+N.style.color);var G=document.createElement("dd");G.innerHTML=N.displayName;H.appendChild(L);H.appendChild(G);}};D._sizeChartCanvas=function(I){var G=(I)?I.response.length:this.get("maxChartFunctions");var H=(G*36)+34;if(H!=parseInt(this._chartElHeight,10)){this._chartElHeight=H;C.setStyle(this._chartEl,"height",H+"px");}};D.initAttributes=function(G){YAHOO.widget.ProfilerViewer.superclass.initAttributes.call(this,G);this.setAttributeConfig("base",{value:G.base});this.setAttributeConfig("tableHeight",{value:G.tableHeight||"15em",method:function(H){if(this._dataTable){this._dataTable.set("height",H);}}});this.setAttributeConfig("sortedBy",{value:G.sortedBy||{key:"total",dir:"yui-dt-desc"}});
+this.setAttributeConfig("filter",{value:G.filter||null,validator:YAHOO.lang.isFunction});this.setAttributeConfig("swfUrl",{value:G.swfUrl||"http://yui.yahooapis.com/2.5.0/build/charts/assets/charts.swf"});this.setAttributeConfig("maxChartFunctions",{value:G.maxChartFunctions||6,method:function(H){if(this._rendered){this._sizeChartCanvas();}},validator:YAHOO.lang.isNumber});this.setAttributeConfig("chartStyle",{value:G.chartStyle||{font:{name:"Arial",color:15658588,size:12},background:{color:"6e6e63"}},method:function(){if(this._rendered&&this.get("showChart")){this._refreshChart();}}});this.setAttributeConfig("chartSeriesDefinitions",{value:G.chartSeriesDefinitions||{total:{displayName:E.STRINGS.colHeads.total[0],xField:"total",style:{color:"4d95dd",size:20},group:["total"]},calls:{displayName:E.STRINGS.colHeads.calls[0],xField:"calls",style:{color:"edff9f",size:20},group:["calls"]},avg:{displayName:E.STRINGS.colHeads.avg[0],xField:"avg",style:{color:"209daf",size:9},group:["avg","min","max"]},min:{displayName:E.STRINGS.colHeads.min[0],xField:"min",style:{color:"b6ecf4",size:9},group:["avg","min","max"]},max:{displayName:E.STRINGS.colHeads.max[0],xField:"max",style:{color:"29c7de",size:9},group:["avg","min","max"]},pct:{displayName:E.STRINGS.colHeads.pct[0],xField:"pct",style:{color:"C96EDB",size:20},group:["pct"]}},method:function(){if(this._rendered&&this.get("showChart")){this._refreshChart();}}});this.setAttributeConfig("visible",{value:G.visible||false,validator:YAHOO.lang.isBoolean,method:function(H){if(H){this._show();}else{if(this._rendered){this._hide();}}}});this.setAttributeConfig("showChart",{value:G.showChart||true,validator:YAHOO.lang.isBoolean,writeOnce:true});YAHOO.widget.ProfilerViewer.superclass.initAttributes.call(this,G);};})();YAHOO.register("profilerviewer",YAHOO.widget.ProfilerViewer,{version:"2.8.2r1",build:"7"}); \ No newline at end of file
diff --git a/js/yui/progressbar/progressbar-min.js b/js/yui/progressbar/progressbar-min.js
new file mode 100644
index 000000000..a46831e45
--- /dev/null
+++ b/js/yui/progressbar/progressbar-min.js
@@ -0,0 +1,8 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+(function(){var B=YAHOO.util.Dom,H=YAHOO.lang,X="yui-pb",Z=X+"-mask",W=X+"-bar",V=X+"-anim",M=X+"-tl",K=X+"-tr",J=X+"-bl",F=X+"-br",G="width",S="height",L="minValue",U="maxValue",I="value",A="anim",T="direction",D="ltr",P="rtl",d="ttb",O="btt",E="barEl",C="maskEl",R="ariaTextTemplate",Y="start",c="progress",Q="complete";var N=function(b){N.superclass.constructor.call(this,document.createElement("div"),b);this._init(b);};YAHOO.widget.ProgressBar=N;N.MARKUP=['<div class="',W,'"></div><div class="',Z,'"><div class="',M,'"></div><div class="',K,'"></div><div class="',J,'"></div><div class="',F,'"></div></div>'].join("");H.extend(N,YAHOO.util.Element,{_init:function(b){},initAttributes:function(f){N.superclass.initAttributes.call(this,f);this.set("innerHTML",N.MARKUP);this.addClass(X);var e,b=["id",G,S,"class","style"];while((e=b.pop())){if(e in f){this.set(e,f[e]);}}this.setAttributeConfig(E,{readOnly:true,value:this.getElementsByClassName(W)[0]});this.setAttributeConfig(C,{readOnly:true,value:this.getElementsByClassName(Z)[0]});this.setAttributeConfig(T,{value:D,validator:function(g){if(this._rendered){return false;}switch(g){case D:case P:case d:case O:return true;default:return false;}},method:function(g){this._barSizeFunction=this._barSizeFunctions[this.get(A)?1:0][g];}});this.setAttributeConfig(U,{value:100,validator:H.isNumber,method:function(g){this.get("element").setAttribute("aria-valuemax",g);if(this.get(I)>g){this.set(I,g);}}});this.setAttributeConfig(L,{value:0,validator:H.isNumber,method:function(g){this.get("element").setAttribute("aria-valuemin",g);if(this.get(I)<g){this.set(I,g);}}});this.setAttributeConfig(G,{getter:function(){return this.getStyle(G);},method:this._widthChange});this.setAttributeConfig(S,{getter:function(){return this.getStyle(S);},method:this._heightChange});this.setAttributeConfig(R,{value:"{value}"});this.setAttributeConfig(I,{value:0,validator:function(g){return H.isNumber(g)&&g>=this.get(L)&&g<=this.get(U);},method:this._valueChange});this.setAttributeConfig(A,{validator:function(g){return !!YAHOO.util.Anim;},setter:this._animSetter});},render:function(e,f){if(this._rendered){return;}this._rendered=true;var g=this.get(T);this.addClass(X);this.addClass(X+"-"+g);var b=this.get("element");b.tabIndex=0;b.setAttribute("role","progressbar");b.setAttribute("aria-valuemin",this.get(L));b.setAttribute("aria-valuemax",this.get(U));this.appendTo(e,f);this._barSizeFunction=this._barSizeFunctions[0][g];this.redraw();this._previousValue=this.get(I);this._fixEdges();if(this.get(A)){this._barSizeFunction=this._barSizeFunctions[1][g];}this.on("minValueChange",this.redraw);this.on("maxValueChange",this.redraw);return this;},redraw:function(){this._recalculateConstants();this._valueChange(this.get(I));},destroy:function(){this.set(A,false);this.unsubscribeAll();var b=this.get("element");if(b.parentNode){b.parentNode.removeChild(b);}},_previousValue:0,_barSpace:100,_barFactor:1,_rendered:false,_barSizeFunction:null,_heightChange:function(b){if(H.isNumber(b)){b+="px";}this.setStyle(S,b);this._fixEdges();this.redraw();},_widthChange:function(b){if(H.isNumber(b)){b+="px";}this.setStyle(G,b);this._fixEdges();this.redraw();},_fixEdges:function(){if(!this._rendered||YAHOO.env.ua.ie||YAHOO.env.ua.gecko){return;}var g=this.get(C),i=B.getElementsByClassName(M,undefined,g)[0],f=B.getElementsByClassName(K,undefined,g)[0],h=B.getElementsByClassName(J,undefined,g)[0],e=B.getElementsByClassName(F,undefined,g)[0],b=(parseInt(B.getStyle(g,S),10)-parseInt(B.getStyle(i,S),10))+"px";B.setStyle(h,S,b);B.setStyle(e,S,b);b=(parseInt(B.getStyle(g,G),10)-parseInt(B.getStyle(i,G),10))+"px";B.setStyle(f,G,b);B.setStyle(e,G,b);},_recalculateConstants:function(){var b=this.get(E);switch(this.get(T)){case D:case P:this._barSpace=parseInt(this.get(G),10)-(parseInt(B.getStyle(b,"marginLeft"),10)||0)-(parseInt(B.getStyle(b,"marginRight"),10)||0);break;case d:case O:this._barSpace=parseInt(this.get(S),10)-(parseInt(B.getStyle(b,"marginTop"),10)||0)-(parseInt(B.getStyle(b,"marginBottom"),10)||0);break;}this._barFactor=this._barSpace/(this.get(U)-(this.get(L)||0))||1;},_animSetter:function(g){var f,b=this.get(E);if(g){if(g instanceof YAHOO.util.Anim){f=g;}else{f=new YAHOO.util.Anim(b);}f.onTween.subscribe(this._animOnTween,this,true);f.onComplete.subscribe(this._animComplete,this,true);var h=f.setAttribute,e=this;switch(this.get(T)){case O:f.setAttribute=function(i,k,j){k=Math.round(k);h.call(this,i,k,j);B.setStyle(b,"top",(e._barSpace-k)+"px");};break;case P:f.setAttribute=function(i,k,j){k=Math.round(k);h.call(this,i,k,j);B.setStyle(b,"left",(e._barSpace-k)+"px");};break;}}else{f=this.get(A);if(f){f.onTween.unsubscribeAll();f.onComplete.unsubscribeAll();}f=null;}this._barSizeFunction=this._barSizeFunctions[f?1:0][this.get(T)];return f;},_animComplete:function(){var b=this.get(I);this._previousValue=b;this.fireEvent(c,b);this.fireEvent(Q,b);B.removeClass(this.get(E),V);},_animOnTween:function(b,e){var f=Math.floor(this._tweenFactor*e[0].currentFrame+this._previousValue);this.fireEvent(c,f);},_valueChange:function(g){var f=this.get(A),b=Math.floor((g-this.get(L))*this._barFactor),e=this.get(E);this._setAriaText(g);if(this._rendered){if(f){f.stop();if(f.isAnimated()){f._onComplete.fire();}}this.fireEvent(Y,this._previousValue);this._barSizeFunction(g,b,e,f);}},_setAriaText:function(e){var b=this.get("element"),f=H.substitute(this.get(R),{value:e,minValue:this.get(L),maxValue:this.get(U)});b.setAttribute("aria-valuenow",e);b.setAttribute("aria-valuetext",f);}});var a=[{},{}];N.prototype._barSizeFunctions=a;a[0][D]=function(g,b,e,f){B.setStyle(e,G,b+"px");this.fireEvent(c,g);this.fireEvent(Q,g);};a[0][P]=function(g,b,e,f){B.setStyle(e,G,b+"px");B.setStyle(e,"left",(this._barSpace-b)+"px");this.fireEvent(c,g);this.fireEvent(Q,g);};a[0][d]=function(g,b,e,f){B.setStyle(e,S,b+"px");this.fireEvent(c,g);this.fireEvent(Q,g);};a[0][O]=function(g,b,e,f){B.setStyle(e,S,b+"px");B.setStyle(e,"top",(this._barSpace-b)+"px");
+this.fireEvent(c,g);this.fireEvent(Q,g);};a[1][D]=function(g,b,e,f){B.addClass(e,V);this._tweenFactor=(g-this._previousValue)/f.totalFrames/f.duration;f.attributes={width:{to:b}};f.animate();};a[1][P]=a[1][D];a[1][d]=function(g,b,e,f){B.addClass(e,V);this._tweenFactor=(g-this._previousValue)/f.totalFrames/f.duration;f.attributes={height:{to:b}};f.animate();};a[1][O]=a[1][d];})();YAHOO.register("progressbar",YAHOO.widget.ProgressBar,{version:"2.8.2r1",build:"7"}); \ No newline at end of file
diff --git a/js/yui/reset-fonts-grids/reset-fonts-grids.css b/js/yui/reset-fonts-grids/reset-fonts-grids.css
new file mode 100644
index 000000000..70bf2a0ff
--- /dev/null
+++ b/js/yui/reset-fonts-grids/reset-fonts-grids.css
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+html{color:#000;background:#FFF;}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,button,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var,optgroup{font-style:inherit;font-weight:inherit;}del,ins{text-decoration:none;}li{list-style:none;}caption,th{text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym{border:0;font-variant:normal;}sup{vertical-align:baseline;}sub{vertical-align:baseline;}legend{color:#000;}input,button,textarea,select,optgroup,option{font-family:inherit;font-size:inherit;font-style:inherit;font-weight:inherit;}input,button,textarea,select{*font-size:100%;}body{font:13px/1.231 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small;}select,input,button,textarea,button{font:99% arial,helvetica,clean,sans-serif;}table{font-size:inherit;font:100%;}pre,code,kbd,samp,tt{font-family:monospace;*font-size:108%;line-height:100%;}body{text-align:center;}#doc,#doc2,#doc3,#doc4,.yui-t1,.yui-t2,.yui-t3,.yui-t4,.yui-t5,.yui-t6,.yui-t7{margin:auto;text-align:left;width:57.69em;*width:56.25em;}#doc2{width:73.076em;*width:71.25em;}#doc3{margin:auto 10px;width:auto;}#doc4{width:74.923em;*width:73.05em;}.yui-b{position:relative;}.yui-b{_position:static;}#yui-main .yui-b{position:static;}#yui-main,.yui-g .yui-u .yui-g{width:100%;}.yui-t1 #yui-main,.yui-t2 #yui-main,.yui-t3 #yui-main{float:right;margin-left:-25em;}.yui-t4 #yui-main,.yui-t5 #yui-main,.yui-t6 #yui-main{float:left;margin-right:-25em;}.yui-t1 .yui-b{float:left;width:12.30769em;*width:12.00em;}.yui-t1 #yui-main .yui-b{margin-left:13.30769em;*margin-left:13.05em;}.yui-t2 .yui-b{float:left;width:13.8461em;*width:13.50em;}.yui-t2 #yui-main .yui-b{margin-left:14.8461em;*margin-left:14.55em;}.yui-t3 .yui-b{float:left;width:23.0769em;*width:22.50em;}.yui-t3 #yui-main .yui-b{margin-left:24.0769em;*margin-left:23.62em;}.yui-t4 .yui-b{float:right;width:13.8456em;*width:13.50em;}.yui-t4 #yui-main .yui-b{margin-right:14.8456em;*margin-right:14.55em;}.yui-t5 .yui-b{float:right;width:18.4615em;*width:18.00em;}.yui-t5 #yui-main .yui-b{margin-right:19.4615em;*margin-right:19.125em;}.yui-t6 .yui-b{float:right;width:23.0769em;*width:22.50em;}.yui-t6 #yui-main .yui-b{margin-right:24.0769em;*margin-right:23.62em;}.yui-t7 #yui-main .yui-b{display:block;margin:0 0 1em 0;}#yui-main .yui-b{float:none;width:auto;}.yui-gb .yui-u,.yui-g .yui-gb .yui-u,.yui-gb .yui-g,.yui-gb .yui-gb,.yui-gb .yui-gc,.yui-gb .yui-gd,.yui-gb .yui-ge,.yui-gb .yui-gf,.yui-gc .yui-u,.yui-gc .yui-g,.yui-gd .yui-u{float:left;}.yui-g .yui-u,.yui-g .yui-g,.yui-g .yui-gb,.yui-g .yui-gc,.yui-g .yui-gd,.yui-g .yui-ge,.yui-g .yui-gf,.yui-gc .yui-u,.yui-gd .yui-g,.yui-g .yui-gc .yui-u,.yui-ge .yui-u,.yui-ge .yui-g,.yui-gf .yui-g,.yui-gf .yui-u{float:right;}.yui-g div.first,.yui-gb div.first,.yui-gc div.first,.yui-gd div.first,.yui-ge div.first,.yui-gf div.first,.yui-g .yui-gc div.first,.yui-g .yui-ge div.first,.yui-gc div.first div.first{float:left;}.yui-g .yui-u,.yui-g .yui-g,.yui-g .yui-gb,.yui-g .yui-gc,.yui-g .yui-gd,.yui-g .yui-ge,.yui-g .yui-gf{width:49.1%;}.yui-gb .yui-u,.yui-g .yui-gb .yui-u,.yui-gb .yui-g,.yui-gb .yui-gb,.yui-gb .yui-gc,.yui-gb .yui-gd,.yui-gb .yui-ge,.yui-gb .yui-gf,.yui-gc .yui-u,.yui-gc .yui-g,.yui-gd .yui-u{width:32%;margin-left:1.99%;}.yui-gb .yui-u{*margin-left:1.9%;*width:31.9%;}.yui-gc div.first,.yui-gd .yui-u{width:66%;}.yui-gd div.first{width:32%;}.yui-ge div.first,.yui-gf .yui-u{width:74.2%;}.yui-ge .yui-u,.yui-gf div.first{width:24%;}.yui-g .yui-gb div.first,.yui-gb div.first,.yui-gc div.first,.yui-gd div.first{margin-left:0;}.yui-g .yui-g .yui-u,.yui-gb .yui-g .yui-u,.yui-gc .yui-g .yui-u,.yui-gd .yui-g .yui-u,.yui-ge .yui-g .yui-u,.yui-gf .yui-g .yui-u{width:49%;*width:48.1%;*margin-left:0;}.yui-g .yui-g .yui-u{width:48.1%;}.yui-g .yui-gb div.first,.yui-gb .yui-gb div.first{*margin-right:0;*width:32%;_width:31.7%;}.yui-g .yui-gc div.first,.yui-gd .yui-g{width:66%;}.yui-gb .yui-g div.first{*margin-right:4%;_margin-right:1.3%;}.yui-gb .yui-gc div.first,.yui-gb .yui-gd div.first{*margin-right:0;}.yui-gb .yui-gb .yui-u,.yui-gb .yui-gc .yui-u{*margin-left:1.8%;_margin-left:4%;}.yui-g .yui-gb .yui-u{_margin-left:1.0%;}.yui-gb .yui-gd .yui-u{*width:66%;_width:61.2%;}.yui-gb .yui-gd div.first{*width:31%;_width:29.5%;}.yui-g .yui-gc .yui-u,.yui-gb .yui-gc .yui-u{width:32%;_float:right;margin-right:0;_margin-left:0;}.yui-gb .yui-gc div.first{width:66%;*float:left;*margin-left:0;}.yui-gb .yui-ge .yui-u,.yui-gb .yui-gf .yui-u{margin:0;}.yui-gb .yui-gb .yui-u{_margin-left:.7%;}.yui-gb .yui-g div.first,.yui-gb .yui-gb div.first{*margin-left:0;}.yui-gc .yui-g .yui-u,.yui-gd .yui-g .yui-u{*width:48.1%;*margin-left:0;}.yui-gb .yui-gd div.first{width:32%;}.yui-g .yui-gd div.first{_width:29.9%;}.yui-ge .yui-g{width:24%;}.yui-gf .yui-g{width:74.2%;}.yui-gb .yui-ge div.yui-u,.yui-gb .yui-gf div.yui-u{float:right;}.yui-gb .yui-ge div.first,.yui-gb .yui-gf div.first{float:left;}.yui-gb .yui-ge .yui-u,.yui-gb .yui-gf div.first{*width:24%;_width:20%;}.yui-gb .yui-ge div.first,.yui-gb .yui-gf .yui-u{*width:73.5%;_width:65.5%;}.yui-ge div.first .yui-gd .yui-u{width:65%;}.yui-ge div.first .yui-gd div.first{width:32%;}#hd:after,#bd:after,#ft:after,.yui-g:after,.yui-gb:after,.yui-gc:after,.yui-gd:after,.yui-ge:after,.yui-gf:after{content:".";display:block;height:0;clear:both;visibility:hidden;}#hd,#bd,#ft,.yui-g,.yui-gb,.yui-gc,.yui-gd,.yui-ge,.yui-gf{zoom:1;} \ No newline at end of file
diff --git a/js/yui/reset-fonts/reset-fonts.css b/js/yui/reset-fonts/reset-fonts.css
new file mode 100644
index 000000000..dbeb7959a
--- /dev/null
+++ b/js/yui/reset-fonts/reset-fonts.css
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+html{color:#000;background:#FFF;}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,button,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var,optgroup{font-style:inherit;font-weight:inherit;}del,ins{text-decoration:none;}li{list-style:none;}caption,th{text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym{border:0;font-variant:normal;}sup{vertical-align:baseline;}sub{vertical-align:baseline;}legend{color:#000;}input,button,textarea,select,optgroup,option{font-family:inherit;font-size:inherit;font-style:inherit;font-weight:inherit;}input,button,textarea,select{*font-size:100%;}body{font:13px/1.231 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small;}select,input,button,textarea,button{font:99% arial,helvetica,clean,sans-serif;}table{font-size:inherit;font:100%;}pre,code,kbd,samp,tt{font-family:monospace;*font-size:108%;line-height:100%;} \ No newline at end of file
diff --git a/js/yui/reset/reset-min.css b/js/yui/reset/reset-min.css
new file mode 100644
index 000000000..edee770b9
--- /dev/null
+++ b/js/yui/reset/reset-min.css
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+html{color:#000;background:#FFF;}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,button,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var,optgroup{font-style:inherit;font-weight:inherit;}del,ins{text-decoration:none;}li{list-style:none;}caption,th{text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym{border:0;font-variant:normal;}sup{vertical-align:baseline;}sub{vertical-align:baseline;}legend{color:#000;}input,button,textarea,select,optgroup,option{font-family:inherit;font-size:inherit;font-style:inherit;font-weight:inherit;}input,button,textarea,select{*font-size:100%;} \ No newline at end of file
diff --git a/js/yui/reset/reset.css b/js/yui/reset/reset.css
new file mode 100644
index 000000000..256daeab2
--- /dev/null
+++ b/js/yui/reset/reset.css
@@ -0,0 +1,142 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+/**
+ * YUI Reset
+ * @module reset
+ * @namespace
+ * @requires
+ */
+html {
+ color: #000;
+ background: #FFF;
+}
+
+body,
+div,
+dl,
+dt,
+dd,
+ul,
+ol,
+li,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+pre,
+code,
+form,
+fieldset,
+legend,
+input,
+button,
+textarea,
+p,
+blockquote,
+th,
+td {
+ margin: 0;
+ padding: 0;
+}
+
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+
+fieldset,
+img {
+ border: 0;
+}
+
+address,
+caption,
+cite,
+code,
+dfn,
+em,
+strong,
+th,
+var,
+optgroup {
+ font-style: inherit;
+ font-weight: inherit;
+}
+
+del,
+ins {
+ text-decoration: none;
+}
+
+li {
+ list-style: none;
+}
+
+caption,
+th {
+ text-align: left;
+}
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ font-size: 100%;
+ font-weight: normal;
+}
+
+q:before,
+q:after {
+ content: '';
+}
+
+abbr,
+acronym {
+ border: 0;
+ font-variant: normal;
+}
+
+sup {
+ vertical-align: baseline;
+}
+
+sub {
+ vertical-align: baseline;
+}
+
+/*because legend doesn't inherit in IE */
+legend {
+ color: #000;
+}
+
+input,
+button,
+textarea,
+select,
+optgroup,
+option {
+ font-family: inherit;
+ font-size: inherit;
+ font-style: inherit;
+ font-weight: inherit;
+}
+
+/*@purpose To enable resizing for IE */
+/*@branch For IE6-Win, IE7-Win */
+input,
+button,
+textarea,
+select {
+ *font-size: 100%;
+}
+
+
+
diff --git a/js/yui/resize/resize-min.js b/js/yui/resize/resize-min.js
new file mode 100644
index 000000000..1d6994dea
--- /dev/null
+++ b/js/yui/resize/resize-min.js
@@ -0,0 +1,10 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+(function(){var E=YAHOO.util.Dom,A=YAHOO.util.Event,C=YAHOO.lang;var B=function(F,D){var G={element:F,attributes:D||{}};B.superclass.constructor.call(this,G.element,G.attributes);};B._instances={};B.getResizeById=function(D){if(B._instances[D]){return B._instances[D];}return false;};YAHOO.extend(B,YAHOO.util.Element,{CSS_RESIZE:"yui-resize",CSS_DRAG:"yui-draggable",CSS_HOVER:"yui-resize-hover",CSS_PROXY:"yui-resize-proxy",CSS_WRAP:"yui-resize-wrap",CSS_KNOB:"yui-resize-knob",CSS_HIDDEN:"yui-resize-hidden",CSS_HANDLE:"yui-resize-handle",CSS_STATUS:"yui-resize-status",CSS_GHOST:"yui-resize-ghost",CSS_RESIZING:"yui-resize-resizing",_resizeEvent:null,dd:null,browser:YAHOO.env.ua,_locked:null,_positioned:null,_dds:null,_wrap:null,_proxy:null,_handles:null,_currentHandle:null,_currentDD:null,_cache:null,_active:null,_createProxy:function(){if(this.get("proxy")){this._proxy=document.createElement("div");this._proxy.className=this.CSS_PROXY;this._proxy.style.height=this.get("element").clientHeight+"px";this._proxy.style.width=this.get("element").clientWidth+"px";this._wrap.parentNode.appendChild(this._proxy);}else{this.set("animate",false);}},_createWrap:function(){this._positioned=false;if(this.get("wrap")===false){switch(this.get("element").tagName.toLowerCase()){case"img":case"textarea":case"input":case"iframe":case"select":this.set("wrap",true);break;}}if(this.get("wrap")===true){this._wrap=document.createElement("div");this._wrap.id=this.get("element").id+"_wrap";this._wrap.className=this.CSS_WRAP;if(this.get("element").tagName.toLowerCase()=="textarea"){E.addClass(this._wrap,"yui-resize-textarea");}E.setStyle(this._wrap,"width",this.get("width")+"px");E.setStyle(this._wrap,"height",this.get("height")+"px");E.setStyle(this._wrap,"z-index",this.getStyle("z-index"));this.setStyle("z-index",0);var F=E.getStyle(this.get("element"),"position");E.setStyle(this._wrap,"position",((F=="static")?"relative":F));E.setStyle(this._wrap,"top",E.getStyle(this.get("element"),"top"));E.setStyle(this._wrap,"left",E.getStyle(this.get("element"),"left"));if(E.getStyle(this.get("element"),"position")=="absolute"){this._positioned=true;E.setStyle(this.get("element"),"position","relative");E.setStyle(this.get("element"),"top","0");E.setStyle(this.get("element"),"left","0");}var D=this.get("element").parentNode;D.replaceChild(this._wrap,this.get("element"));this._wrap.appendChild(this.get("element"));}else{this._wrap=this.get("element");if(E.getStyle(this._wrap,"position")=="absolute"){this._positioned=true;}}if(this.get("draggable")){this._setupDragDrop();}if(this.get("hover")){E.addClass(this._wrap,this.CSS_HOVER);}if(this.get("knobHandles")){E.addClass(this._wrap,this.CSS_KNOB);}if(this.get("hiddenHandles")){E.addClass(this._wrap,this.CSS_HIDDEN);}E.addClass(this._wrap,this.CSS_RESIZE);},_setupDragDrop:function(){E.addClass(this._wrap,this.CSS_DRAG);this.dd=new YAHOO.util.DD(this._wrap,this.get("id")+"-resize",{dragOnly:true,useShim:this.get("useShim")});this.dd.on("dragEvent",function(){this.fireEvent("dragEvent",arguments);},this,true);},_createHandles:function(){this._handles={};this._dds={};var G=this.get("handles");for(var F=0;F<G.length;F++){this._handles[G[F]]=document.createElement("div");this._handles[G[F]].id=E.generateId(this._handles[G[F]]);this._handles[G[F]].className=this.CSS_HANDLE+" "+this.CSS_HANDLE+"-"+G[F];var D=document.createElement("div");D.className=this.CSS_HANDLE+"-inner-"+G[F];this._handles[G[F]].appendChild(D);this._wrap.appendChild(this._handles[G[F]]);A.on(this._handles[G[F]],"mouseover",this._handleMouseOver,this,true);A.on(this._handles[G[F]],"mouseout",this._handleMouseOut,this,true);this._dds[G[F]]=new YAHOO.util.DragDrop(this._handles[G[F]],this.get("id")+"-handle-"+G,{useShim:this.get("useShim")});this._dds[G[F]].setPadding(15,15,15,15);this._dds[G[F]].on("startDragEvent",this._handleStartDrag,this._dds[G[F]],this);this._dds[G[F]].on("mouseDownEvent",this._handleMouseDown,this._dds[G[F]],this);}this._status=document.createElement("span");this._status.className=this.CSS_STATUS;document.body.insertBefore(this._status,document.body.firstChild);},_ieSelectFix:function(){return false;},_ieSelectBack:null,_setAutoRatio:function(D){if(this.get("autoRatio")){if(D&&D.shiftKey){this.set("ratio",true);}else{this.set("ratio",this._configs.ratio._initialConfig.value);}}},_handleMouseDown:function(D){if(this._locked){return false;}if(E.getStyle(this._wrap,"position")=="absolute"){this._positioned=true;}if(D){this._setAutoRatio(D);}if(this.browser.ie){this._ieSelectBack=document.body.onselectstart;document.body.onselectstart=this._ieSelectFix;}},_handleMouseOver:function(G){if(this._locked){return false;}E.removeClass(this._wrap,this.CSS_RESIZE);if(this.get("hover")){E.removeClass(this._wrap,this.CSS_HOVER);}var D=A.getTarget(G);if(!E.hasClass(D,this.CSS_HANDLE)){D=D.parentNode;}if(E.hasClass(D,this.CSS_HANDLE)&&!this._active){E.addClass(D,this.CSS_HANDLE+"-active");for(var F in this._handles){if(C.hasOwnProperty(this._handles,F)){if(this._handles[F]==D){E.addClass(D,this.CSS_HANDLE+"-"+F+"-active");break;}}}}E.addClass(this._wrap,this.CSS_RESIZE);},_handleMouseOut:function(G){E.removeClass(this._wrap,this.CSS_RESIZE);if(this.get("hover")&&!this._active){E.addClass(this._wrap,this.CSS_HOVER);}var D=A.getTarget(G);if(!E.hasClass(D,this.CSS_HANDLE)){D=D.parentNode;}if(E.hasClass(D,this.CSS_HANDLE)&&!this._active){E.removeClass(D,this.CSS_HANDLE+"-active");for(var F in this._handles){if(C.hasOwnProperty(this._handles,F)){if(this._handles[F]==D){E.removeClass(D,this.CSS_HANDLE+"-"+F+"-active");break;}}}}E.addClass(this._wrap,this.CSS_RESIZE);},_handleStartDrag:function(G,F){var D=F.getDragEl();if(E.hasClass(D,this.CSS_HANDLE)){if(E.getStyle(this._wrap,"position")=="absolute"){this._positioned=true;}this._active=true;this._currentDD=F;if(this._proxy){this._proxy.style.visibility="visible";this._proxy.style.zIndex="1000";this._proxy.style.height=this.get("element").clientHeight+"px";this._proxy.style.width=this.get("element").clientWidth+"px";
+}for(var H in this._handles){if(C.hasOwnProperty(this._handles,H)){if(this._handles[H]==D){this._currentHandle=H;var I="_handle_for_"+H;E.addClass(D,this.CSS_HANDLE+"-"+H+"-active");F.on("dragEvent",this[I],this,true);F.on("mouseUpEvent",this._handleMouseUp,this,true);break;}}}E.addClass(D,this.CSS_HANDLE+"-active");if(this.get("proxy")){var J=E.getXY(this.get("element"));E.setXY(this._proxy,J);if(this.get("ghost")){this.addClass(this.CSS_GHOST);}}E.addClass(this._wrap,this.CSS_RESIZING);this._setCache();this._updateStatus(this._cache.height,this._cache.width,this._cache.top,this._cache.left);this.fireEvent("startResize",{type:"startresize",target:this});}},_setCache:function(){this._cache.xy=E.getXY(this._wrap);E.setXY(this._wrap,this._cache.xy);this._cache.height=this.get("clientHeight");this._cache.width=this.get("clientWidth");this._cache.start.height=this._cache.height;this._cache.start.width=this._cache.width;this._cache.start.top=this._cache.xy[1];this._cache.start.left=this._cache.xy[0];this._cache.top=this._cache.xy[1];this._cache.left=this._cache.xy[0];this.set("height",this._cache.height,true);this.set("width",this._cache.width,true);},_handleMouseUp:function(F){this._active=false;var G="_handle_for_"+this._currentHandle;this._currentDD.unsubscribe("dragEvent",this[G],this,true);this._currentDD.unsubscribe("mouseUpEvent",this._handleMouseUp,this,true);if(this._proxy){this._proxy.style.visibility="hidden";this._proxy.style.zIndex="-1";if(this.get("setSize")){this.resize(F,this._cache.height,this._cache.width,this._cache.top,this._cache.left,true);}else{this.fireEvent("resize",{ev:"resize",target:this,height:this._cache.height,width:this._cache.width,top:this._cache.top,left:this._cache.left});}if(this.get("ghost")){this.removeClass(this.CSS_GHOST);}}if(this.get("hover")){E.addClass(this._wrap,this.CSS_HOVER);}if(this._status){E.setStyle(this._status,"display","none");}if(this.browser.ie){document.body.onselectstart=this._ieSelectBack;}if(this.browser.ie){E.removeClass(this._wrap,this.CSS_RESIZE);}for(var D in this._handles){if(C.hasOwnProperty(this._handles,D)){E.removeClass(this._handles[D],this.CSS_HANDLE+"-active");}}if(this.get("hover")&&!this._active){E.addClass(this._wrap,this.CSS_HOVER);}E.removeClass(this._wrap,this.CSS_RESIZING);E.removeClass(this._handles[this._currentHandle],this.CSS_HANDLE+"-"+this._currentHandle+"-active");E.removeClass(this._handles[this._currentHandle],this.CSS_HANDLE+"-active");if(this.browser.ie){E.addClass(this._wrap,this.CSS_RESIZE);}this._resizeEvent=null;this._currentHandle=null;if(!this.get("animate")){this.set("height",this._cache.height,true);this.set("width",this._cache.width,true);}this.fireEvent("endResize",{ev:"endResize",target:this,height:this._cache.height,width:this._cache.width,top:this._cache.top,left:this._cache.left});},_setRatio:function(K,N,Q,I){var O=K,G=N;if(this.get("ratio")){var P=this._cache.height,H=this._cache.width,F=parseInt(this.get("height"),10),L=parseInt(this.get("width"),10),M=this.get("maxHeight"),R=this.get("minHeight"),D=this.get("maxWidth"),J=this.get("minWidth");switch(this._currentHandle){case"l":K=F*(N/L);K=Math.min(Math.max(R,K),M);N=L*(K/F);Q=(this._cache.start.top-(-((F-K)/2)));I=(this._cache.start.left-(-((L-N))));break;case"r":K=F*(N/L);K=Math.min(Math.max(R,K),M);N=L*(K/F);Q=(this._cache.start.top-(-((F-K)/2)));break;case"t":N=L*(K/F);K=F*(N/L);I=(this._cache.start.left-(-((L-N)/2)));Q=(this._cache.start.top-(-((F-K))));break;case"b":N=L*(K/F);K=F*(N/L);I=(this._cache.start.left-(-((L-N)/2)));break;case"bl":K=F*(N/L);N=L*(K/F);I=(this._cache.start.left-(-((L-N))));break;case"br":K=F*(N/L);N=L*(K/F);break;case"tl":K=F*(N/L);N=L*(K/F);I=(this._cache.start.left-(-((L-N))));Q=(this._cache.start.top-(-((F-K))));break;case"tr":K=F*(N/L);N=L*(K/F);I=(this._cache.start.left);Q=(this._cache.start.top-(-((F-K))));break;}O=this._checkHeight(K);G=this._checkWidth(N);if((O!=K)||(G!=N)){Q=0;I=0;if(O!=K){G=this._cache.width;}if(G!=N){O=this._cache.height;}}}return[O,G,Q,I];},_updateStatus:function(K,G,J,F){if(this._resizeEvent&&(!C.isString(this._resizeEvent))){K=((K===0)?this._cache.start.height:K);G=((G===0)?this._cache.start.width:G);var I=parseInt(this.get("height"),10),D=parseInt(this.get("width"),10);if(isNaN(I)){I=parseInt(K,10);}if(isNaN(D)){D=parseInt(G,10);}var L=(parseInt(K,10)-I);var H=(parseInt(G,10)-D);this._cache.offsetHeight=L;this._cache.offsetWidth=H;if(this.get("status")){E.setStyle(this._status,"display","inline");this._status.innerHTML="<strong>"+parseInt(K,10)+" x "+parseInt(G,10)+"</strong><em>"+((L>0)?"+":"")+L+" x "+((H>0)?"+":"")+H+"</em>";E.setXY(this._status,[A.getPageX(this._resizeEvent)+12,A.getPageY(this._resizeEvent)+12]);}}},lock:function(D){this._locked=true;if(D&&this.dd){E.removeClass(this._wrap,"yui-draggable");this.dd.lock();}return this;},unlock:function(D){this._locked=false;if(D&&this.dd){E.addClass(this._wrap,"yui-draggable");this.dd.unlock();}return this;},isLocked:function(){return this._locked;},reset:function(){this.resize(null,this._cache.start.height,this._cache.start.width,this._cache.start.top,this._cache.start.left,true);return this;},resize:function(M,J,P,Q,H,F,K){if(this._locked){return false;}this._resizeEvent=M;var G=this._wrap,I=this.get("animate"),O=true;if(this._proxy&&!F){G=this._proxy;I=false;}this._setAutoRatio(M);if(this._positioned){if(this._proxy){Q=this._cache.top-Q;H=this._cache.left-H;}}var L=this._setRatio(J,P,Q,H);J=parseInt(L[0],10);P=parseInt(L[1],10);Q=parseInt(L[2],10);H=parseInt(L[3],10);if(Q==0){Q=E.getY(G);}if(H==0){H=E.getX(G);}if(this._positioned){if(this._proxy&&F){if(!I){G.style.top=this._proxy.style.top;G.style.left=this._proxy.style.left;}else{Q=this._proxy.style.top;H=this._proxy.style.left;}}else{if(!this.get("ratio")&&!this._proxy){Q=this._cache.top+-(Q);H=this._cache.left+-(H);}if(Q){if(this.get("minY")){if(Q<this.get("minY")){Q=this.get("minY");}}if(this.get("maxY")){if(Q>this.get("maxY")){Q=this.get("maxY");}}}if(H){if(this.get("minX")){if(H<this.get("minX")){H=this.get("minX");
+}}if(this.get("maxX")){if((H+P)>this.get("maxX")){H=(this.get("maxX")-P);}}}}}if(!K){var N=this.fireEvent("beforeResize",{ev:"beforeResize",target:this,height:J,width:P,top:Q,left:H});if(N===false){return false;}}this._updateStatus(J,P,Q,H);if(this._positioned){if(this._proxy&&F){}else{if(Q){E.setY(G,Q);this._cache.top=Q;}if(H){E.setX(G,H);this._cache.left=H;}}}if(J){if(!I){O=true;if(this._proxy&&F){if(!this.get("setSize")){O=false;}}if(O){G.style.height=J+"px";}if((this._proxy&&F)||!this._proxy){if(this._wrap!=this.get("element")){this.get("element").style.height=J+"px";}}}this._cache.height=J;}if(P){this._cache.width=P;if(!I){O=true;if(this._proxy&&F){if(!this.get("setSize")){O=false;}}if(O){G.style.width=P+"px";}if((this._proxy&&F)||!this._proxy){if(this._wrap!=this.get("element")){this.get("element").style.width=P+"px";}}}}if(I){if(YAHOO.util.Anim){var D=new YAHOO.util.Anim(G,{height:{to:this._cache.height},width:{to:this._cache.width}},this.get("animateDuration"),this.get("animateEasing"));if(this._positioned){if(Q){D.attributes.top={to:parseInt(Q,10)};}if(H){D.attributes.left={to:parseInt(H,10)};}}if(this._wrap!=this.get("element")){D.onTween.subscribe(function(){this.get("element").style.height=G.style.height;this.get("element").style.width=G.style.width;},this,true);}D.onComplete.subscribe(function(){this.set("height",J);this.set("width",P);this.fireEvent("resize",{ev:"resize",target:this,height:J,width:P,top:Q,left:H});},this,true);D.animate();}}else{if(this._proxy&&!F){this.fireEvent("proxyResize",{ev:"proxyresize",target:this,height:J,width:P,top:Q,left:H});}else{this.fireEvent("resize",{ev:"resize",target:this,height:J,width:P,top:Q,left:H});}}return this;},_handle_for_br:function(F){var G=this._setWidth(F.e);var D=this._setHeight(F.e);this.resize(F.e,D,G,0,0);},_handle_for_bl:function(G){var H=this._setWidth(G.e,true);var F=this._setHeight(G.e);var D=(H-this._cache.width);this.resize(G.e,F,H,0,D);},_handle_for_tl:function(G){var I=this._setWidth(G.e,true);var F=this._setHeight(G.e,true);var H=(F-this._cache.height);var D=(I-this._cache.width);this.resize(G.e,F,I,H,D);},_handle_for_tr:function(F){var H=this._setWidth(F.e);var D=this._setHeight(F.e,true);var G=(D-this._cache.height);this.resize(F.e,D,H,G,0);},_handle_for_r:function(D){this._dds.r.setYConstraint(0,0);var F=this._setWidth(D.e);this.resize(D.e,0,F,0,0);},_handle_for_l:function(F){this._dds.l.setYConstraint(0,0);var G=this._setWidth(F.e,true);var D=(G-this._cache.width);this.resize(F.e,0,G,0,D);},_handle_for_b:function(F){this._dds.b.setXConstraint(0,0);var D=this._setHeight(F.e);this.resize(F.e,D,0,0,0);},_handle_for_t:function(F){this._dds.t.setXConstraint(0,0);var D=this._setHeight(F.e,true);var G=(D-this._cache.height);this.resize(F.e,D,0,G,0);},_setWidth:function(H,J){var I=this._cache.xy[0],G=this._cache.width,D=A.getPageX(H),F=(D-I);if(J){F=(I-D)+parseInt(this.get("width"),10);}F=this._snapTick(F,this.get("xTicks"));F=this._checkWidth(F);return F;},_checkWidth:function(D){if(this.get("minWidth")){if(D<=this.get("minWidth")){D=this.get("minWidth");}}if(this.get("maxWidth")){if(D>=this.get("maxWidth")){D=this.get("maxWidth");}}return D;},_checkHeight:function(D){if(this.get("minHeight")){if(D<=this.get("minHeight")){D=this.get("minHeight");}}if(this.get("maxHeight")){if(D>=this.get("maxHeight")){D=this.get("maxHeight");}}return D;},_setHeight:function(G,I){var H=this._cache.xy[1],F=this._cache.height,J=A.getPageY(G),D=(J-H);if(I){D=(H-J)+parseInt(this.get("height"),10);}D=this._snapTick(D,this.get("yTicks"));D=this._checkHeight(D);return D;},_snapTick:function(G,F){if(!G||!F){return G;}var H=G;var D=G%F;if(D>0){if(D>(F/2)){H=G+(F-D);}else{H=G-D;}}return H;},init:function(H,F){this._locked=false;this._cache={xy:[],height:0,width:0,top:0,left:0,offsetHeight:0,offsetWidth:0,start:{height:0,width:0,top:0,left:0}};B.superclass.init.call(this,H,F);this.set("setSize",this.get("setSize"));if(F.height){this.set("height",parseInt(F.height,10));}else{var G=this.getStyle("height");if(G=="auto"){this.set("height",parseInt(this.get("element").offsetHeight,10));}}if(F.width){this.set("width",parseInt(F.width,10));}else{var D=this.getStyle("width");if(D=="auto"){this.set("width",parseInt(this.get("element").offsetWidth,10));}}var I=H;if(!C.isString(I)){I=E.generateId(I);}B._instances[I]=this;this._active=false;this._createWrap();this._createProxy();this._createHandles();},getProxyEl:function(){return this._proxy;},getWrapEl:function(){return this._wrap;},getStatusEl:function(){return this._status;},getActiveHandleEl:function(){return this._handles[this._currentHandle];},isActive:function(){return((this._active)?true:false);},initAttributes:function(D){B.superclass.initAttributes.call(this,D);this.setAttributeConfig("useShim",{value:((D.useShim===true)?true:false),validator:YAHOO.lang.isBoolean,method:function(F){for(var G in this._dds){if(C.hasOwnProperty(this._dds,G)){this._dds[G].useShim=F;}}if(this.dd){this.dd.useShim=F;}}});this.setAttributeConfig("setSize",{value:((D.setSize===false)?false:true),validator:YAHOO.lang.isBoolean});this.setAttributeConfig("wrap",{writeOnce:true,validator:YAHOO.lang.isBoolean,value:D.wrap||false});this.setAttributeConfig("handles",{writeOnce:true,value:D.handles||["r","b","br"],validator:function(F){if(C.isString(F)&&F.toLowerCase()=="all"){F=["t","b","r","l","bl","br","tl","tr"];}if(!C.isArray(F)){F=F.replace(/, /g,",");F=F.split(",");}this._configs.handles.value=F;}});this.setAttributeConfig("width",{value:D.width||parseInt(this.getStyle("width"),10),validator:YAHOO.lang.isNumber,method:function(F){F=parseInt(F,10);if(F>0){if(this.get("setSize")){this.setStyle("width",F+"px");}this._cache.width=F;this._configs.width.value=F;}}});this.setAttributeConfig("height",{value:D.height||parseInt(this.getStyle("height"),10),validator:YAHOO.lang.isNumber,method:function(F){F=parseInt(F,10);if(F>0){if(this.get("setSize")){this.setStyle("height",F+"px");}this._cache.height=F;this._configs.height.value=F;
+}}});this.setAttributeConfig("minWidth",{value:D.minWidth||15,validator:YAHOO.lang.isNumber});this.setAttributeConfig("minHeight",{value:D.minHeight||15,validator:YAHOO.lang.isNumber});this.setAttributeConfig("maxWidth",{value:D.maxWidth||10000,validator:YAHOO.lang.isNumber});this.setAttributeConfig("maxHeight",{value:D.maxHeight||10000,validator:YAHOO.lang.isNumber});this.setAttributeConfig("minY",{value:D.minY||false});this.setAttributeConfig("minX",{value:D.minX||false});this.setAttributeConfig("maxY",{value:D.maxY||false});this.setAttributeConfig("maxX",{value:D.maxX||false});this.setAttributeConfig("animate",{value:D.animate||false,validator:function(G){var F=true;if(!YAHOO.util.Anim){F=false;}return F;}});this.setAttributeConfig("animateEasing",{value:D.animateEasing||function(){var F=false;if(YAHOO.util.Easing&&YAHOO.util.Easing.easeOut){F=YAHOO.util.Easing.easeOut;}return F;}()});this.setAttributeConfig("animateDuration",{value:D.animateDuration||0.5});this.setAttributeConfig("proxy",{value:D.proxy||false,validator:YAHOO.lang.isBoolean});this.setAttributeConfig("ratio",{value:D.ratio||false,validator:YAHOO.lang.isBoolean});this.setAttributeConfig("ghost",{value:D.ghost||false,validator:YAHOO.lang.isBoolean});this.setAttributeConfig("draggable",{value:D.draggable||false,validator:YAHOO.lang.isBoolean,method:function(F){if(F&&this._wrap){this._setupDragDrop();}else{if(this.dd){E.removeClass(this._wrap,this.CSS_DRAG);this.dd.unreg();}}}});this.setAttributeConfig("hover",{value:D.hover||false,validator:YAHOO.lang.isBoolean});this.setAttributeConfig("hiddenHandles",{value:D.hiddenHandles||false,validator:YAHOO.lang.isBoolean});this.setAttributeConfig("knobHandles",{value:D.knobHandles||false,validator:YAHOO.lang.isBoolean});this.setAttributeConfig("xTicks",{value:D.xTicks||false});this.setAttributeConfig("yTicks",{value:D.yTicks||false});this.setAttributeConfig("status",{value:D.status||false,validator:YAHOO.lang.isBoolean});this.setAttributeConfig("autoRatio",{value:D.autoRatio||false,validator:YAHOO.lang.isBoolean});},destroy:function(){for(var F in this._handles){if(C.hasOwnProperty(this._handles,F)){A.purgeElement(this._handles[F]);this._handles[F].parentNode.removeChild(this._handles[F]);}}if(this._proxy){this._proxy.parentNode.removeChild(this._proxy);}if(this._status){this._status.parentNode.removeChild(this._status);}if(this.dd){this.dd.unreg();E.removeClass(this._wrap,this.CSS_DRAG);}if(this._wrap!=this.get("element")){this.setStyle("position","");this.setStyle("top","");this.setStyle("left","");this._wrap.parentNode.replaceChild(this.get("element"),this._wrap);}this.removeClass(this.CSS_RESIZE);delete YAHOO.util.Resize._instances[this.get("id")];for(var D in this){if(C.hasOwnProperty(this,D)){this[D]=null;delete this[D];}}},toString:function(){if(this.get){return"Resize (#"+this.get("id")+")";}return"Resize Utility";}});YAHOO.util.Resize=B;})();YAHOO.register("resize",YAHOO.util.Resize,{version:"2.8.2r1",build:"7"}); \ No newline at end of file
diff --git a/js/yui/selector/selector-min.js b/js/yui/selector/selector-min.js
new file mode 100644
index 000000000..579280f41
--- /dev/null
+++ b/js/yui/selector/selector-min.js
@@ -0,0 +1,8 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+(function(){var A=YAHOO.util;A.Selector={_foundCache:[],_regexCache:{},_re:{nth:/^(?:([-]?\d*)(n){1}|(odd|even)$)*([-+]?\d*)$/,attr:/(\[.*\])/g,urls:/^(?:href|src)/},document:window.document,attrAliases:{},shorthand:{"\\#(-?[_a-z]+[-\\w]*)":"[id=$1]","\\.(-?[_a-z]+[-\\w]*)":"[class~=$1]"},operators:{"=":function(B,C){return B===C;},"!=":function(B,C){return B!==C;},"~=":function(B,D){var C=" ";return(C+B+C).indexOf((C+D+C))>-1;},"|=":function(B,C){return B===C||B.slice(0,C.length+1)===C+"-";},"^=":function(B,C){return B.indexOf(C)===0;},"$=":function(B,C){return B.slice(-C.length)===C;},"*=":function(B,C){return B.indexOf(C)>-1;},"":function(B,C){return B;}},pseudos:{"root":function(B){return B===B.ownerDocument.documentElement;},"nth-child":function(B,C){return A.Selector._getNth(B,C);},"nth-last-child":function(B,C){return A.Selector._getNth(B,C,null,true);},"nth-of-type":function(B,C){return A.Selector._getNth(B,C,B.tagName);},"nth-last-of-type":function(B,C){return A.Selector._getNth(B,C,B.tagName,true);},"first-child":function(B){return A.Selector._getChildren(B.parentNode)[0]===B;},"last-child":function(C){var B=A.Selector._getChildren(C.parentNode);return B[B.length-1]===C;},"first-of-type":function(B,C){return A.Selector._getChildren(B.parentNode,B.tagName)[0];},"last-of-type":function(C,D){var B=A.Selector._getChildren(C.parentNode,C.tagName);return B[B.length-1];},"only-child":function(C){var B=A.Selector._getChildren(C.parentNode);return B.length===1&&B[0]===C;},"only-of-type":function(B){return A.Selector._getChildren(B.parentNode,B.tagName).length===1;},"empty":function(B){return B.childNodes.length===0;},"not":function(B,C){return !A.Selector.test(B,C);},"contains":function(B,D){var C=B.innerText||B.textContent||"";return C.indexOf(D)>-1;},"checked":function(B){return B.checked===true;}},test:function(F,D){F=A.Selector.document.getElementById(F)||F;if(!F){return false;}var C=D?D.split(","):[];if(C.length){for(var E=0,B=C.length;E<B;++E){if(A.Selector._test(F,C[E])){return true;}}return false;}return A.Selector._test(F,D);},_test:function(D,G,F,E){F=F||A.Selector._tokenize(G).pop()||{};if(!D.tagName||(F.tag!=="*"&&D.tagName!==F.tag)||(E&&D._found)){return false;}if(F.attributes.length){var B,H,C=A.Selector._re.urls;if(!D.attributes||!D.attributes.length){return false;}for(var I=0,K;K=F.attributes[I++];){H=(C.test(K[0]))?2:0;B=D.getAttribute(K[0],H);if(B===null||B===undefined){return false;}if(A.Selector.operators[K[1]]&&!A.Selector.operators[K[1]](B,K[2])){return false;}}}if(F.pseudos.length){for(var I=0,J=F.pseudos.length;I<J;++I){if(A.Selector.pseudos[F.pseudos[I][0]]&&!A.Selector.pseudos[F.pseudos[I][0]](D,F.pseudos[I][1])){return false;}}}return(F.previous&&F.previous.combinator!==",")?A.Selector._combinators[F.previous.combinator](D,F):true;},filter:function(E,D){E=E||[];var G,C=[],H=A.Selector._tokenize(D);if(!E.item){for(var F=0,B=E.length;F<B;++F){if(!E[F].tagName){G=A.Selector.document.getElementById(E[F]);if(G){E[F]=G;}else{}}}}C=A.Selector._filter(E,A.Selector._tokenize(D)[0]);return C;},_filter:function(E,G,H,D){var C=H?null:[],I=A.Selector._foundCache;for(var F=0,B=E.length;F<B;F++){if(!A.Selector._test(E[F],"",G,D)){continue;}if(H){return E[F];}if(D){if(E[F]._found){continue;}E[F]._found=true;I[I.length]=E[F];}C[C.length]=E[F];}return C;},query:function(C,D,E){var B=A.Selector._query(C,D,E);return B;},_query:function(H,M,N,F){var P=(N)?null:[],E;if(!H){return P;}var D=H.split(",");if(D.length>1){var O;for(var I=0,J=D.length;I<J;++I){O=A.Selector._query(D[I],M,N,true);P=N?O:P.concat(O);}A.Selector._clearFoundCache();return P;}if(M&&!M.nodeName){M=A.Selector.document.getElementById(M);if(!M){return P;}}M=M||A.Selector.document;if(M.nodeName!=="#document"){A.Dom.generateId(M);H=M.tagName+"#"+M.id+" "+H;E=M;M=M.ownerDocument;}var L=A.Selector._tokenize(H);var K=L[A.Selector._getIdTokenIndex(L)],B=[],C,G=L.pop()||{};if(K){C=A.Selector._getId(K.attributes);}if(C){E=E||A.Selector.document.getElementById(C);if(E&&(M.nodeName==="#document"||A.Dom.isAncestor(M,E))){if(A.Selector._test(E,null,K)){if(K===G){B=[E];}else{if(K.combinator===" "||K.combinator===">"){M=E;}}}}else{return P;}}if(M&&!B.length){B=M.getElementsByTagName(G.tag);}if(B.length){P=A.Selector._filter(B,G,N,F);}return P;},_clearFoundCache:function(){var E=A.Selector._foundCache;for(var C=0,B=E.length;C<B;++C){try{delete E[C]._found;}catch(D){E[C].removeAttribute("_found");}}E=[];},_getRegExp:function(D,B){var C=A.Selector._regexCache;B=B||"";if(!C[D+B]){C[D+B]=new RegExp(D,B);}return C[D+B];},_getChildren:function(){if(document.documentElement.children&&document.documentElement.children.tags){return function(C,B){return(B)?C.children.tags(B):C.children||[];};}else{return function(F,C){var E=[],G=F.childNodes;for(var D=0,B=G.length;D<B;++D){if(G[D].tagName){if(!C||G[D].tagName===C){E.push(G[D]);}}}return E;};}}(),_combinators:{" ":function(C,B){while((C=C.parentNode)){if(A.Selector._test(C,"",B.previous)){return true;}}return false;},">":function(C,B){return A.Selector._test(C.parentNode,null,B.previous);},"+":function(D,C){var B=D.previousSibling;while(B&&B.nodeType!==1){B=B.previousSibling;}if(B&&A.Selector._test(B,null,C.previous)){return true;}return false;},"~":function(D,C){var B=D.previousSibling;while(B){if(B.nodeType===1&&A.Selector._test(B,null,C.previous)){return true;}B=B.previousSibling;}return false;}},_getNth:function(C,L,N,G){A.Selector._re.nth.test(L);var K=parseInt(RegExp.$1,10),B=RegExp.$2,H=RegExp.$3,I=parseInt(RegExp.$4,10)||0,M=[],E;var J=A.Selector._getChildren(C.parentNode,N);if(H){K=2;E="+";B="n";I=(H==="odd")?1:0;}else{if(isNaN(K)){K=(B)?1:0;}}if(K===0){if(G){I=J.length-I+1;}if(J[I-1]===C){return true;}else{return false;}}else{if(K<0){G=!!G;K=Math.abs(K);}}if(!G){for(var D=I-1,F=J.length;D<F;D+=K){if(D>=0&&J[D]===C){return true;}}}else{for(var D=J.length-I,F=J.length;D>=0;D-=K){if(D<F&&J[D]===C){return true;}}}return false;},_getId:function(C){for(var D=0,B=C.length;D<B;++D){if(C[D][0]=="id"&&C[D][1]==="="){return C[D][2];
+}}},_getIdTokenIndex:function(D){for(var C=0,B=D.length;C<B;++C){if(A.Selector._getId(D[C].attributes)){return C;}}return -1;},_patterns:{tag:/^((?:-?[_a-z]+[\w-]*)|\*)/i,attributes:/^\[([a-z]+\w*)+([~\|\^\$\*!=]=?)?['"]?([^\]]*?)['"]?\]/i,pseudos:/^:([-\w]+)(?:\(['"]?(.+)['"]?\))*/i,combinator:/^\s*([>+~]|\s)\s*/},_tokenize:function(B){var D={},H=[],I,G=false,F=A.Selector._patterns,C;B=A.Selector._replaceShorthand(B);do{G=false;for(var E in F){if(YAHOO.lang.hasOwnProperty(F,E)){if(E!="tag"&&E!="combinator"){D[E]=D[E]||[];}if((C=F[E].exec(B))){G=true;if(E!="tag"&&E!="combinator"){if(E==="attributes"&&C[1]==="id"){D.id=C[3];}D[E].push(C.slice(1));}else{D[E]=C[1];}B=B.replace(C[0],"");if(E==="combinator"||!B.length){D.attributes=A.Selector._fixAttributes(D.attributes);D.pseudos=D.pseudos||[];D.tag=D.tag?D.tag.toUpperCase():"*";H.push(D);D={previous:D};}}}}}while(G);return H;},_fixAttributes:function(C){var D=A.Selector.attrAliases;C=C||[];for(var E=0,B=C.length;E<B;++E){if(D[C[E][0]]){C[E][0]=D[C[E][0]];}if(!C[E][1]){C[E][1]="";}}return C;},_replaceShorthand:function(C){var D=A.Selector.shorthand;var E=C.match(A.Selector._re.attr);if(E){C=C.replace(A.Selector._re.attr,"REPLACED_ATTRIBUTE");}for(var G in D){if(YAHOO.lang.hasOwnProperty(D,G)){C=C.replace(A.Selector._getRegExp(G,"gi"),D[G]);}}if(E){for(var F=0,B=E.length;F<B;++F){C=C.replace("REPLACED_ATTRIBUTE",E[F]);}}return C;}};if(YAHOO.env.ua.ie&&YAHOO.env.ua.ie<8){A.Selector.attrAliases["class"]="className";A.Selector.attrAliases["for"]="htmlFor";}})();YAHOO.register("selector",YAHOO.util.Selector,{version:"2.8.2r1",build:"7"}); \ No newline at end of file
diff --git a/js/yui/slider/slider-min.js b/js/yui/slider/slider-min.js
new file mode 100644
index 000000000..95b57a7a0
--- /dev/null
+++ b/js/yui/slider/slider-min.js
@@ -0,0 +1,9 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+(function(){var B=YAHOO.util.Dom.getXY,A=YAHOO.util.Event,D=Array.prototype.slice;function C(G,E,F,H){C.ANIM_AVAIL=(!YAHOO.lang.isUndefined(YAHOO.util.Anim));if(G){this.init(G,E,true);this.initSlider(H);this.initThumb(F);}}YAHOO.lang.augmentObject(C,{getHorizSlider:function(F,G,I,H,E){return new C(F,F,new YAHOO.widget.SliderThumb(G,F,I,H,0,0,E),"horiz");},getVertSlider:function(G,H,E,I,F){return new C(G,G,new YAHOO.widget.SliderThumb(H,G,0,0,E,I,F),"vert");},getSliderRegion:function(G,H,J,I,E,K,F){return new C(G,G,new YAHOO.widget.SliderThumb(H,G,J,I,E,K,F),"region");},SOURCE_UI_EVENT:1,SOURCE_SET_VALUE:2,SOURCE_KEY_EVENT:3,ANIM_AVAIL:false},true);YAHOO.extend(C,YAHOO.util.DragDrop,{_mouseDown:false,dragOnly:true,initSlider:function(E){this.type=E;this.createEvent("change",this);this.createEvent("slideStart",this);this.createEvent("slideEnd",this);this.isTarget=false;this.animate=C.ANIM_AVAIL;this.backgroundEnabled=true;this.tickPause=40;this.enableKeys=true;this.keyIncrement=20;this.moveComplete=true;this.animationDuration=0.2;this.SOURCE_UI_EVENT=1;this.SOURCE_SET_VALUE=2;this.valueChangeSource=0;this._silent=false;this.lastOffset=[0,0];},initThumb:function(F){var E=this;this.thumb=F;F.cacheBetweenDrags=true;if(F._isHoriz&&F.xTicks&&F.xTicks.length){this.tickPause=Math.round(360/F.xTicks.length);}else{if(F.yTicks&&F.yTicks.length){this.tickPause=Math.round(360/F.yTicks.length);}}F.onAvailable=function(){return E.setStartSliderState();};F.onMouseDown=function(){E._mouseDown=true;return E.focus();};F.startDrag=function(){E._slideStart();};F.onDrag=function(){E.fireEvents(true);};F.onMouseUp=function(){E.thumbMouseUp();};},onAvailable:function(){this._bindKeyEvents();},_bindKeyEvents:function(){A.on(this.id,"keydown",this.handleKeyDown,this,true);A.on(this.id,"keypress",this.handleKeyPress,this,true);},handleKeyPress:function(F){if(this.enableKeys){var E=A.getCharCode(F);switch(E){case 37:case 38:case 39:case 40:case 36:case 35:A.preventDefault(F);break;default:}}},handleKeyDown:function(J){if(this.enableKeys){var G=A.getCharCode(J),F=this.thumb,H=this.getXValue(),E=this.getYValue(),I=true;switch(G){case 37:H-=this.keyIncrement;break;case 38:E-=this.keyIncrement;break;case 39:H+=this.keyIncrement;break;case 40:E+=this.keyIncrement;break;case 36:H=F.leftConstraint;E=F.topConstraint;break;case 35:H=F.rightConstraint;E=F.bottomConstraint;break;default:I=false;}if(I){if(F._isRegion){this._setRegionValue(C.SOURCE_KEY_EVENT,H,E,true);}else{this._setValue(C.SOURCE_KEY_EVENT,(F._isHoriz?H:E),true);}A.stopEvent(J);}}},setStartSliderState:function(){this.setThumbCenterPoint();this.baselinePos=B(this.getEl());this.thumb.startOffset=this.thumb.getOffsetFromParent(this.baselinePos);if(this.thumb._isRegion){if(this.deferredSetRegionValue){this._setRegionValue.apply(this,this.deferredSetRegionValue);this.deferredSetRegionValue=null;}else{this.setRegionValue(0,0,true,true,true);}}else{if(this.deferredSetValue){this._setValue.apply(this,this.deferredSetValue);this.deferredSetValue=null;}else{this.setValue(0,true,true,true);}}},setThumbCenterPoint:function(){var E=this.thumb.getEl();if(E){this.thumbCenterPoint={x:parseInt(E.offsetWidth/2,10),y:parseInt(E.offsetHeight/2,10)};}},lock:function(){this.thumb.lock();this.locked=true;},unlock:function(){this.thumb.unlock();this.locked=false;},thumbMouseUp:function(){this._mouseDown=false;if(!this.isLocked()){this.endMove();}},onMouseUp:function(){this._mouseDown=false;if(this.backgroundEnabled&&!this.isLocked()){this.endMove();}},getThumb:function(){return this.thumb;},focus:function(){this.valueChangeSource=C.SOURCE_UI_EVENT;var E=this.getEl();if(E.focus){try{E.focus();}catch(F){}}this.verifyOffset();return !this.isLocked();},onChange:function(E,F){},onSlideStart:function(){},onSlideEnd:function(){},getValue:function(){return this.thumb.getValue();},getXValue:function(){return this.thumb.getXValue();},getYValue:function(){return this.thumb.getYValue();},setValue:function(){var E=D.call(arguments);E.unshift(C.SOURCE_SET_VALUE);return this._setValue.apply(this,E);},_setValue:function(I,L,G,H,E){var F=this.thumb,K,J;if(!F.available){this.deferredSetValue=arguments;return false;}if(this.isLocked()&&!H){return false;}if(isNaN(L)){return false;}if(F._isRegion){return false;}this._silent=E;this.valueChangeSource=I||C.SOURCE_SET_VALUE;F.lastOffset=[L,L];this.verifyOffset();this._slideStart();if(F._isHoriz){K=F.initPageX+L+this.thumbCenterPoint.x;this.moveThumb(K,F.initPageY,G);}else{J=F.initPageY+L+this.thumbCenterPoint.y;this.moveThumb(F.initPageX,J,G);}return true;},setRegionValue:function(){var E=D.call(arguments);E.unshift(C.SOURCE_SET_VALUE);return this._setRegionValue.apply(this,E);},_setRegionValue:function(F,J,H,I,G,K){var L=this.thumb,E,M;if(!L.available){this.deferredSetRegionValue=arguments;return false;}if(this.isLocked()&&!G){return false;}if(isNaN(J)){return false;}if(!L._isRegion){return false;}this._silent=K;this.valueChangeSource=F||C.SOURCE_SET_VALUE;L.lastOffset=[J,H];this.verifyOffset();this._slideStart();E=L.initPageX+J+this.thumbCenterPoint.x;M=L.initPageY+H+this.thumbCenterPoint.y;this.moveThumb(E,M,I);return true;},verifyOffset:function(){var F=B(this.getEl()),E=this.thumb;if(!this.thumbCenterPoint||!this.thumbCenterPoint.x){this.setThumbCenterPoint();}if(F){if(F[0]!=this.baselinePos[0]||F[1]!=this.baselinePos[1]){this.setInitPosition();this.baselinePos=F;E.initPageX=this.initPageX+E.startOffset[0];E.initPageY=this.initPageY+E.startOffset[1];E.deltaSetXY=null;this.resetThumbConstraints();return false;}}return true;},moveThumb:function(K,J,I,G){var L=this.thumb,M=this,F,E,H;if(!L.available){return;}L.setDelta(this.thumbCenterPoint.x,this.thumbCenterPoint.y);E=L.getTargetCoord(K,J);F=[Math.round(E.x),Math.round(E.y)];if(this.animate&&L._graduated&&!I){this.lock();this.curCoord=B(this.thumb.getEl());this.curCoord=[Math.round(this.curCoord[0]),Math.round(this.curCoord[1])];setTimeout(function(){M.moveOneTick(F);},this.tickPause);}else{if(this.animate&&C.ANIM_AVAIL&&!I){this.lock();
+H=new YAHOO.util.Motion(L.id,{points:{to:F}},this.animationDuration,YAHOO.util.Easing.easeOut);H.onComplete.subscribe(function(){M.unlock();if(!M._mouseDown){M.endMove();}});H.animate();}else{L.setDragElPos(K,J);if(!G&&!this._mouseDown){this.endMove();}}}},_slideStart:function(){if(!this._sliding){if(!this._silent){this.onSlideStart();this.fireEvent("slideStart");}this._sliding=true;this.moveComplete=false;}},_slideEnd:function(){if(this._sliding){var E=this._silent;this._sliding=false;this.moveComplete=true;this._silent=false;if(!E){this.onSlideEnd();this.fireEvent("slideEnd");}}},moveOneTick:function(F){var H=this.thumb,G=this,I=null,E,J;if(H._isRegion){I=this._getNextX(this.curCoord,F);E=(I!==null)?I[0]:this.curCoord[0];I=this._getNextY(this.curCoord,F);J=(I!==null)?I[1]:this.curCoord[1];I=E!==this.curCoord[0]||J!==this.curCoord[1]?[E,J]:null;}else{if(H._isHoriz){I=this._getNextX(this.curCoord,F);}else{I=this._getNextY(this.curCoord,F);}}if(I){this.curCoord=I;this.thumb.alignElWithMouse(H.getEl(),I[0]+this.thumbCenterPoint.x,I[1]+this.thumbCenterPoint.y);if(!(I[0]==F[0]&&I[1]==F[1])){setTimeout(function(){G.moveOneTick(F);},this.tickPause);}else{this.unlock();if(!this._mouseDown){this.endMove();}}}else{this.unlock();if(!this._mouseDown){this.endMove();}}},_getNextX:function(E,F){var H=this.thumb,J,G=[],I=null;if(E[0]>F[0]){J=H.tickSize-this.thumbCenterPoint.x;G=H.getTargetCoord(E[0]-J,E[1]);I=[G.x,G.y];}else{if(E[0]<F[0]){J=H.tickSize+this.thumbCenterPoint.x;G=H.getTargetCoord(E[0]+J,E[1]);I=[G.x,G.y];}else{}}return I;},_getNextY:function(E,F){var H=this.thumb,J,G=[],I=null;if(E[1]>F[1]){J=H.tickSize-this.thumbCenterPoint.y;G=H.getTargetCoord(E[0],E[1]-J);I=[G.x,G.y];}else{if(E[1]<F[1]){J=H.tickSize+this.thumbCenterPoint.y;G=H.getTargetCoord(E[0],E[1]+J);I=[G.x,G.y];}else{}}return I;},b4MouseDown:function(E){if(!this.backgroundEnabled){return false;}this.thumb.autoOffset();this.baselinePos=[];},onMouseDown:function(F){if(!this.backgroundEnabled||this.isLocked()){return false;}this._mouseDown=true;var E=A.getPageX(F),G=A.getPageY(F);this.focus();this._slideStart();this.moveThumb(E,G);},onDrag:function(F){if(this.backgroundEnabled&&!this.isLocked()){var E=A.getPageX(F),G=A.getPageY(F);this.moveThumb(E,G,true,true);this.fireEvents();}},endMove:function(){this.unlock();this.fireEvents();this._slideEnd();},resetThumbConstraints:function(){var E=this.thumb;E.setXConstraint(E.leftConstraint,E.rightConstraint,E.xTickSize);E.setYConstraint(E.topConstraint,E.bottomConstraint,E.xTickSize);},fireEvents:function(G){var F=this.thumb,I,H,E;if(!G){F.cachePosition();}if(!this.isLocked()){if(F._isRegion){I=F.getXValue();H=F.getYValue();if(I!=this.previousX||H!=this.previousY){if(!this._silent){this.onChange(I,H);this.fireEvent("change",{x:I,y:H});}}this.previousX=I;this.previousY=H;}else{E=F.getValue();if(E!=this.previousVal){if(!this._silent){this.onChange(E);this.fireEvent("change",E);}}this.previousVal=E;}}},toString:function(){return("Slider ("+this.type+") "+this.id);}});YAHOO.lang.augmentProto(C,YAHOO.util.EventProvider);YAHOO.widget.Slider=C;})();YAHOO.widget.SliderThumb=function(G,B,E,D,A,F,C){if(G){YAHOO.widget.SliderThumb.superclass.constructor.call(this,G,B);this.parentElId=B;}this.isTarget=false;this.tickSize=C;this.maintainOffset=true;this.initSlider(E,D,A,F,C);this.scroll=false;};YAHOO.extend(YAHOO.widget.SliderThumb,YAHOO.util.DD,{startOffset:null,dragOnly:true,_isHoriz:false,_prevVal:0,_graduated:false,getOffsetFromParent0:function(C){var A=YAHOO.util.Dom.getXY(this.getEl()),B=C||YAHOO.util.Dom.getXY(this.parentElId);return[(A[0]-B[0]),(A[1]-B[1])];},getOffsetFromParent:function(H){var A=this.getEl(),E,I,F,B,K,D,C,J,G;if(!this.deltaOffset){I=YAHOO.util.Dom.getXY(A);F=H||YAHOO.util.Dom.getXY(this.parentElId);E=[(I[0]-F[0]),(I[1]-F[1])];B=parseInt(YAHOO.util.Dom.getStyle(A,"left"),10);K=parseInt(YAHOO.util.Dom.getStyle(A,"top"),10);D=B-E[0];C=K-E[1];if(isNaN(D)||isNaN(C)){}else{this.deltaOffset=[D,C];}}else{J=parseInt(YAHOO.util.Dom.getStyle(A,"left"),10);G=parseInt(YAHOO.util.Dom.getStyle(A,"top"),10);E=[J+this.deltaOffset[0],G+this.deltaOffset[1]];}return E;},initSlider:function(D,C,A,E,B){this.initLeft=D;this.initRight=C;this.initUp=A;this.initDown=E;this.setXConstraint(D,C,B);this.setYConstraint(A,E,B);if(B&&B>1){this._graduated=true;}this._isHoriz=(D||C);this._isVert=(A||E);this._isRegion=(this._isHoriz&&this._isVert);},clearTicks:function(){YAHOO.widget.SliderThumb.superclass.clearTicks.call(this);this.tickSize=0;this._graduated=false;},getValue:function(){return(this._isHoriz)?this.getXValue():this.getYValue();},getXValue:function(){if(!this.available){return 0;}var A=this.getOffsetFromParent();if(YAHOO.lang.isNumber(A[0])){this.lastOffset=A;return(A[0]-this.startOffset[0]);}else{return(this.lastOffset[0]-this.startOffset[0]);}},getYValue:function(){if(!this.available){return 0;}var A=this.getOffsetFromParent();if(YAHOO.lang.isNumber(A[1])){this.lastOffset=A;return(A[1]-this.startOffset[1]);}else{return(this.lastOffset[1]-this.startOffset[1]);}},toString:function(){return"SliderThumb "+this.id;},onChange:function(A,B){}});(function(){var A=YAHOO.util.Event,B=YAHOO.widget;function C(I,F,H,D){var G=this,J={min:false,max:false},E,K;this.minSlider=I;this.maxSlider=F;this.activeSlider=I;this.isHoriz=I.thumb._isHoriz;E=this.minSlider.thumb.onMouseDown;K=this.maxSlider.thumb.onMouseDown;this.minSlider.thumb.onMouseDown=function(){G.activeSlider=G.minSlider;E.apply(this,arguments);};this.maxSlider.thumb.onMouseDown=function(){G.activeSlider=G.maxSlider;K.apply(this,arguments);};this.minSlider.thumb.onAvailable=function(){I.setStartSliderState();J.min=true;if(J.max){G.fireEvent("ready",G);}};this.maxSlider.thumb.onAvailable=function(){F.setStartSliderState();J.max=true;if(J.min){G.fireEvent("ready",G);}};I.onMouseDown=F.onMouseDown=function(L){return this.backgroundEnabled&&G._handleMouseDown(L);};I.onDrag=F.onDrag=function(L){G._handleDrag(L);};I.onMouseUp=F.onMouseUp=function(L){G._handleMouseUp(L);
+};I._bindKeyEvents=function(){G._bindKeyEvents(this);};F._bindKeyEvents=function(){};I.subscribe("change",this._handleMinChange,I,this);I.subscribe("slideStart",this._handleSlideStart,I,this);I.subscribe("slideEnd",this._handleSlideEnd,I,this);F.subscribe("change",this._handleMaxChange,F,this);F.subscribe("slideStart",this._handleSlideStart,F,this);F.subscribe("slideEnd",this._handleSlideEnd,F,this);this.createEvent("ready",this);this.createEvent("change",this);this.createEvent("slideStart",this);this.createEvent("slideEnd",this);D=YAHOO.lang.isArray(D)?D:[0,H];D[0]=Math.min(Math.max(parseInt(D[0],10)|0,0),H);D[1]=Math.max(Math.min(parseInt(D[1],10)|0,H),0);if(D[0]>D[1]){D.splice(0,2,D[1],D[0]);}this.minVal=D[0];this.maxVal=D[1];this.minSlider.setValue(this.minVal,true,true,true);this.maxSlider.setValue(this.maxVal,true,true,true);}C.prototype={minVal:-1,maxVal:-1,minRange:0,_handleSlideStart:function(E,D){this.fireEvent("slideStart",D);},_handleSlideEnd:function(E,D){this.fireEvent("slideEnd",D);},_handleDrag:function(D){B.Slider.prototype.onDrag.call(this.activeSlider,D);},_handleMinChange:function(){this.activeSlider=this.minSlider;this.updateValue();},_handleMaxChange:function(){this.activeSlider=this.maxSlider;this.updateValue();},_bindKeyEvents:function(D){A.on(D.id,"keydown",this._handleKeyDown,this,true);A.on(D.id,"keypress",this._handleKeyPress,this,true);},_handleKeyDown:function(D){this.activeSlider.handleKeyDown.apply(this.activeSlider,arguments);},_handleKeyPress:function(D){this.activeSlider.handleKeyPress.apply(this.activeSlider,arguments);},setValues:function(H,K,I,E,J){var F=this.minSlider,M=this.maxSlider,D=F.thumb,L=M.thumb,N=this,G={min:false,max:false};if(D._isHoriz){D.setXConstraint(D.leftConstraint,L.rightConstraint,D.tickSize);L.setXConstraint(D.leftConstraint,L.rightConstraint,L.tickSize);}else{D.setYConstraint(D.topConstraint,L.bottomConstraint,D.tickSize);L.setYConstraint(D.topConstraint,L.bottomConstraint,L.tickSize);}this._oneTimeCallback(F,"slideEnd",function(){G.min=true;if(G.max){N.updateValue(J);setTimeout(function(){N._cleanEvent(F,"slideEnd");N._cleanEvent(M,"slideEnd");},0);}});this._oneTimeCallback(M,"slideEnd",function(){G.max=true;if(G.min){N.updateValue(J);setTimeout(function(){N._cleanEvent(F,"slideEnd");N._cleanEvent(M,"slideEnd");},0);}});F.setValue(H,I,E,false);M.setValue(K,I,E,false);},setMinValue:function(F,H,I,E){var G=this.minSlider,D=this;this.activeSlider=G;D=this;this._oneTimeCallback(G,"slideEnd",function(){D.updateValue(E);setTimeout(function(){D._cleanEvent(G,"slideEnd");},0);});G.setValue(F,H,I);},setMaxValue:function(D,H,I,F){var G=this.maxSlider,E=this;this.activeSlider=G;this._oneTimeCallback(G,"slideEnd",function(){E.updateValue(F);setTimeout(function(){E._cleanEvent(G,"slideEnd");},0);});G.setValue(D,H,I);},updateValue:function(J){var E=this.minSlider.getValue(),K=this.maxSlider.getValue(),F=false,D,M,H,I,L,G;if(E!=this.minVal||K!=this.maxVal){F=true;D=this.minSlider.thumb;M=this.maxSlider.thumb;H=this.isHoriz?"x":"y";G=this.minSlider.thumbCenterPoint[H]+this.maxSlider.thumbCenterPoint[H];I=Math.max(K-G-this.minRange,0);L=Math.min(-E-G-this.minRange,0);if(this.isHoriz){I=Math.min(I,M.rightConstraint);D.setXConstraint(D.leftConstraint,I,D.tickSize);M.setXConstraint(L,M.rightConstraint,M.tickSize);}else{I=Math.min(I,M.bottomConstraint);D.setYConstraint(D.leftConstraint,I,D.tickSize);M.setYConstraint(L,M.bottomConstraint,M.tickSize);}}this.minVal=E;this.maxVal=K;if(F&&!J){this.fireEvent("change",this);}},selectActiveSlider:function(H){var E=this.minSlider,D=this.maxSlider,J=E.isLocked()||!E.backgroundEnabled,G=D.isLocked()||!E.backgroundEnabled,F=YAHOO.util.Event,I;if(J||G){this.activeSlider=J?D:E;}else{if(this.isHoriz){I=F.getPageX(H)-E.thumb.initPageX-E.thumbCenterPoint.x;}else{I=F.getPageY(H)-E.thumb.initPageY-E.thumbCenterPoint.y;}this.activeSlider=I*2>D.getValue()+E.getValue()?D:E;}},_handleMouseDown:function(D){if(!D._handled&&!this.minSlider._sliding&&!this.maxSlider._sliding){D._handled=true;this.selectActiveSlider(D);return B.Slider.prototype.onMouseDown.call(this.activeSlider,D);}else{return false;}},_handleMouseUp:function(D){B.Slider.prototype.onMouseUp.apply(this.activeSlider,arguments);},_oneTimeCallback:function(G,D,F){var E=function(){G.unsubscribe(D,E);F.apply({},arguments);};G.subscribe(D,E);},_cleanEvent:function(K,E){var J,I,D,G,H,F;if(K.__yui_events&&K.events[E]){for(I=K.__yui_events.length;I>=0;--I){if(K.__yui_events[I].type===E){J=K.__yui_events[I];break;}}if(J){H=J.subscribers;F=[];G=0;for(I=0,D=H.length;I<D;++I){if(H[I]){F[G++]=H[I];}}J.subscribers=F;}}}};YAHOO.lang.augmentProto(C,YAHOO.util.EventProvider);B.Slider.getHorizDualSlider=function(H,J,K,G,F,D){var I=new B.SliderThumb(J,H,0,G,0,0,F),E=new B.SliderThumb(K,H,0,G,0,0,F);return new C(new B.Slider(H,H,I,"horiz"),new B.Slider(H,H,E,"horiz"),G,D);};B.Slider.getVertDualSlider=function(H,J,K,G,F,D){var I=new B.SliderThumb(J,H,0,0,0,G,F),E=new B.SliderThumb(K,H,0,0,0,G,F);return new B.DualSlider(new B.Slider(H,H,I,"vert"),new B.Slider(H,H,E,"vert"),G,D);};YAHOO.widget.DualSlider=C;})();YAHOO.register("slider",YAHOO.widget.Slider,{version:"2.8.2r1",build:"7"}); \ No newline at end of file
diff --git a/js/yui/storage/storage-min.js b/js/yui/storage/storage-min.js
new file mode 100644
index 000000000..08b477a4d
--- /dev/null
+++ b/js/yui/storage/storage-min.js
@@ -0,0 +1,8 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+(function(){var D=YAHOO,B=D.util,A=D.lang,C;if(!B.Storage){C=function(E){D.log("Exception in YAHOO.util.Storage.?? - must be extended by a storage engine".replace("??",E).replace("??",this.getName?this.getName():"Unknown"),"error");};B.Storage=function(E,G,F){var H=this;D.env._id_counter+=1;H._cfg=A.isObject(F)?F:{};H._location=E;H._name=G;H.isReady=false;H.createEvent(H.CE_READY,{scope:H});H.createEvent(H.CE_CHANGE,{scope:H});H.subscribe(H.CE_READY,function(){H.isReady=true;});};B.Storage.prototype={CE_READY:"YUIStorageReady",CE_CHANGE:"YUIStorageChange",DELIMITER:"__",_cfg:"",_name:"",_location:"",length:0,isReady:false,clear:function(){this._clear();this.length=0;},getItem:function(E){D.log("Fetching item at "+E);var F=this._getItem(E);return A.isValue(F)?this._getValue(F):null;},getName:function(){return this._name;},hasKey:function(E){return A.isString(E)&&this._hasKey(E);},key:function(E){D.log("Fetching key at "+E);if(A.isNumber(E)&&-1<E&&this.length>E){var F=this._key(E);if(F){return F;}}throw ("INDEX_SIZE_ERR - Storage.setItem - The provided index ("+E+") is not available");},removeItem:function(F){D.log("removing "+F);if(this.hasKey(F)){var E=this._getItem(F);if(!E){E=null;}this._removeItem(F);this.fireEvent(this.CE_CHANGE,new B.StorageEvent(this,F,E,null,B.StorageEvent.TYPE_REMOVE_ITEM));}else{}},setItem:function(G,H){D.log("SETTING "+H+" to "+G);if(A.isString(G)){var F=this.hasKey(G)?B.StorageEvent.TYPE_UPDATE_ITEM:B.StorageEvent.TYPE_ADD_ITEM,E=this._getItem(G);if(!E){E=null;}if(this._setItem(G,this._createValue(H))){this.fireEvent(this.CE_CHANGE,new B.StorageEvent(this,G,E,H,F));}else{throw ("QUOTA_EXCEEDED_ERROR - Storage.setItem - The choosen storage method ("+this.getName()+") has exceeded capacity");}}else{}},_clear:function(){C("_clear");return"";},_createValue:function(F){var E=(A.isNull(F)||A.isUndefined(F))?(""+F):typeof F;return"string"===E?F:E+this.DELIMITER+F;},_getItem:function(E){C("_getItem");return"";},_getValue:function(F){var E=F?F.split(this.DELIMITER):[];if(1==E.length){return F;}switch(E[0]){case"boolean":return"true"===E[1];case"number":return parseFloat(E[1]);case"null":return null;default:return E[1];}},_key:function(E){C("_key");return"";},_hasKey:function(E){return null!==this._getItem(E);},_removeItem:function(E){C("_removeItem");return"";},_setItem:function(E,F){C("_setItem");return"";}};A.augmentProto(B.Storage,B.EventProvider);}}());(function(){var H=YAHOO.util,B=YAHOO.lang,E={},G=[],F={},C=function(I){return(I&&I.isAvailable())?I:null;},A=function(J,I,K){var L=E[J+I.ENGINE_NAME];if(!L){L=new I(J,K);E[J+I.ENGINE_NAME]=L;}return L;},D=function(I){switch(I){case H.StorageManager.LOCATION_LOCAL:case H.StorageManager.LOCATION_SESSION:return I;default:return H.StorageManager.LOCATION_SESSION;}};H.StorageManager={LOCATION_SESSION:"sessionStorage",LOCATION_LOCAL:"localStorage",get:function(O,J,M){var K=B.isObject(M)?M:{},I=C(F[O]);if(!I&&!K.force){var N,L;if(K.order){L=K.order.length;for(N=0;N<L&&!I;N+=1){I=C(K.order[N]);}}if(!I){L=G.length;for(N=0;N<L&&!I;N+=1){I=C(G[N]);}}}if(I){return A(D(J),I,K.engine);}throw ("YAHOO.util.StorageManager.get - No engine available, please include an engine before calling this function.");},getByteSize:function(I){return encodeURIComponent(""+I).length;},register:function(I){if(B.isFunction(I)&&B.isFunction(I.isAvailable)&&B.isString(I.ENGINE_NAME)){F[I.ENGINE_NAME]=I;G.push(I);return true;}return false;}};YAHOO.register("StorageManager",H.SWFStore,{version:"2.8.2r1",build:"7"});}());(function(){YAHOO.util.StorageEvent=function(D,B,A,E,C){this.key=B;this.oldValue=A;this.newValue=E;this.url=window.location.href;this.window=window;this.storageArea=D;this.type=C;};YAHOO.lang.augmentObject(YAHOO.util.StorageEvent,{TYPE_ADD_ITEM:"addItem",TYPE_REMOVE_ITEM:"removeItem",TYPE_UPDATE_ITEM:"updateItem"});YAHOO.util.StorageEvent.prototype={key:null,newValue:null,oldValue:null,source:null,storageArea:null,type:null,url:null};}());(function(){var B=YAHOO.util,A=YAHOO.lang;B.StorageEngineKeyed=function(){B.StorageEngineKeyed.superclass.constructor.apply(this,arguments);this._keys=[];this._keyMap={};};A.extend(B.StorageEngineKeyed,B.Storage,{_keys:null,_keyMap:null,_addKey:function(C){this._keyMap[C]=this.length;this._keys.push(C);this.length=this._keys.length;},_indexOfKey:function(D){var C=this._keyMap[D];return undefined===C?-1:C;},_removeKey:function(E){var D=this._indexOfKey(E),F=this._keys.slice(D+1);delete this._keyMap[E];for(var C in this._keyMap){if(D<this._keyMap[C]){this._keyMap[C]-=1;}}this._keys.length=D;this._keys=this._keys.concat(F);this.length=this._keys.length;}});}());(function(){var D=YAHOO.util,B=YAHOO.lang,A=function(E){if(E.begin){E.begin();}},C=function(E){if(E.commit){E.commit();}};D.StorageEngineHTML5=function(E,F){var G=this;D.StorageEngineHTML5.superclass.constructor.call(G,E,D.StorageEngineHTML5.ENGINE_NAME,F);G._engine=window[E];G.length=G._engine.length;B.later(250,G,function(){G.fireEvent(G.CE_READY);});};YAHOO.lang.extend(D.StorageEngineHTML5,D.Storage,{_engine:null,_clear:function(){var G=this;if(G._engine.clear){G._engine.clear();}else{for(var F=G.length,E;0<=F;F-=1){E=G._key(F);G._removeItem(E);}}},_getItem:function(E){var F=this._engine.getItem(E);return B.isObject(F)?F.value:F;},_key:function(E){return this._engine.key(E);},_removeItem:function(E){var F=this;A(F._engine);F._engine.removeItem(E);C(F._engine);F.length=F._engine.length;},_setItem:function(E,F){var H=this;try{A(H._engine);H._engine.setItem(E,F);C(H._engine);H.length=H._engine.length;return true;}catch(G){return false;}}},true);D.StorageEngineHTML5.ENGINE_NAME="html5";D.StorageEngineHTML5.isAvailable=function(){return window.localStorage;};D.StorageManager.register(D.StorageEngineHTML5);}());(function(){var G=YAHOO.util,B=YAHOO.lang,D=9948,C="YUIStorageEngine",F=null,E=encodeURIComponent,A=decodeURIComponent;G.StorageEngineGears=function(I,L){var O=this;G.StorageEngineGears.superclass.constructor.call(O,I,G.StorageEngineGears.ENGINE_NAME,L);
+if(!F){F=google.gears.factory.create(G.StorageEngineGears.GEARS);F.open(window.location.host+"-"+G.StorageEngineGears.DATABASE);F.execute("CREATE TABLE IF NOT EXISTS "+C+" (key TEXT, location TEXT, value TEXT)");}var K=G.StorageManager.LOCATION_SESSION===O._location,H=G.Cookie.get("sessionKey"+G.StorageEngineGears.ENGINE_NAME);if(!H){F.execute("BEGIN");F.execute("DELETE FROM "+C+' WHERE location="'+E(G.StorageManager.LOCATION_SESSION)+'"');F.execute("COMMIT");}var J=F.execute("SELECT key FROM "+C+' WHERE location="'+E(O._location)+'"'),N={};try{while(J.isValidRow()){var M=A(J.field(0));if(!N[M]){N[M]=true;O._addKey(M);}J.next();}}finally{J.close();}if(K){G.Cookie.set("sessionKey"+G.StorageEngineGears.ENGINE_NAME,true);}O.length=O._keys.length;B.later(250,O,function(){O.fireEvent(O.CE_READY);});};B.extend(G.StorageEngineGears,G.StorageEngineKeyed,{_clear:function(){F.execute("BEGIN");F.execute("DELETE FROM "+C+' WHERE location="'+E(this._location)+'"');F.execute("COMMIT");this._keys=[];this.length=0;},_getItem:function(J){var I=F.execute("SELECT value FROM "+C+' WHERE key="'+E(J)+'" AND location="'+E(this._location)+'"'),K="";try{while(I.isValidRow()){var H=I.field(0);K+=I.field(0);I.next();}}finally{I.close();}return K?A(K):null;},_key:function(H){return this._keys[H];},_removeItem:function(H){F.execute("BEGIN");F.execute("DELETE FROM "+C+' WHERE key="'+E(H)+'" AND location="'+E(this._location)+'"');F.execute("COMMIT");this._removeKey(H);},_setItem:function(P,M){if(!this.hasKey(P)){this._addKey(P);}var H=E(P),Q=E(this._location),R=E(M),K=[],O=D-(H+Q).length;if(O<R.length){for(var N=0,L=R.length;N<L;N+=O){K.push(R.substr(N,O));}}else{K.push(R);}F.execute("BEGIN");F.execute("DELETE FROM "+C+' WHERE key="'+E(P)+'" AND location="'+E(this._location)+'"');for(var J=0,I=K.length;J<I;J+=1){F.execute("INSERT INTO "+C+' VALUES ("'+H+'", "'+Q+'", "'+K[J]+'")');}F.execute("COMMIT");return true;}});G.Event.on("unload",function(){if(F){F.close();}});G.StorageEngineGears.ENGINE_NAME="gears";G.StorageEngineGears.GEARS="beta.database";G.StorageEngineGears.DATABASE="yui.database";G.StorageEngineGears.isAvailable=function(){if(window.google&&window.google.gears){try{google.gears.factory.create(G.StorageEngineGears.GEARS);return true;}catch(H){}}return false;};G.StorageManager.register(G.StorageEngineGears);}());(function(){var G=YAHOO.util,B=YAHOO.lang,H=G.Dom,C=215,E=138,F=null,D=function(J,I){return J._location+J.DELIMITER+I;},A=function(J){if(!F){if(!B.isString(J.swfURL)){J.swfURL=G.StorageEngineSWF.SWFURL;}if(!J.containerID){var K=document.getElementsByTagName("body")[0],I=K.appendChild(document.createElement("div"));J.containerID=H.generateId(I);}if(!J.attributes){J.attributes={};}if(!J.attributes.flashVars){J.attributes.flashVars={};}J.attributes.flashVars.useCompression="true";J.attributes.version=9.115;F=new YAHOO.widget.SWF(J.containerID,J.swfURL,J.attributes);}};G.StorageEngineSWF=function(I,J){var K=this;G.StorageEngineSWF.superclass.constructor.call(K,I,G.StorageEngineSWF.ENGINE_NAME,J);A(K._cfg);F.unsubscribe("contentReady");F.addListener("contentReady",function(){K._swf=F._swf;F.initialized=true;var N=G.StorageManager.LOCATION_SESSION===K._location,M=G.Cookie.get("sessionKey"+G.StorageEngineSWF.ENGINE_NAME);for(var P=F.callSWF("getLength",[])-1;0<=P;P-=1){var O=F.callSWF("getNameAt",[P]),L=-1<O.indexOf(G.StorageManager.LOCATION_SESSION+K.DELIMITER);if(N&&!M){F.callSWF("removeItem",[O]);}else{if(N===L){K._addKey(O);}}}if(N){G.Cookie.set("sessionKey"+G.StorageEngineSWF.ENGINE_NAME,true);}K.length=K._keys.length;K.fireEvent(K.CE_READY);});if(F.initialized){F.fireEvent("contentReady");}};B.extend(G.StorageEngineSWF,G.StorageEngineKeyed,{_swf:null,_clear:function(){for(var J=this._keys.length-1;0<=J;J-=1){var I=this._keys[J];F.callSWF("removeItem",[I]);}this._keys=[];this.length=0;},_getItem:function(I){var J=D(this,I);return F.callSWF("getValueOf",[J]);},_key:function(I){return(this._keys[I]||"").replace(/^.*?__/,"");},_removeItem:function(I){var J=D(this,I);F.callSWF("removeItem",[J]);this._removeKey(J);},_setItem:function(I,K){var J=D(this,I),L;if(F.callSWF("getValueOf",[J])){this._removeItem(I);}this._addKey(J);if(F.callSWF("setItem",[J,K])){return true;}else{L=H.get(F._id);if(C>H.getStyle(L,"width").replace(/\D+/g,"")){H.setStyle(L,"width",C+"px");}if(E>H.getStyle(L,"height").replace(/\D+/g,"")){H.setStyle(L,"height",E+"px");}return F.callSWF("displaySettings",[]);}}});G.StorageEngineSWF.SWFURL="swfstore.swf";G.StorageEngineSWF.ENGINE_NAME="swf";G.StorageEngineSWF.isAvailable=function(){return(6<=YAHOO.env.ua.flash&&YAHOO.widget.SWF);};G.StorageManager.register(G.StorageEngineSWF);}());YAHOO.register("storage",YAHOO.util.Storage,{version:"2.8.2r1",build:"7"}); \ No newline at end of file
diff --git a/js/yui/stylesheet/stylesheet-min.js b/js/yui/stylesheet/stylesheet-min.js
new file mode 100644
index 000000000..5e538fb83
--- /dev/null
+++ b/js/yui/stylesheet/stylesheet-min.js
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+(function(){var I=document,B=I.createElement("p"),D=B.style,C=YAHOO.lang,L={},H={},E=0,J=("cssFloat" in D)?"cssFloat":"styleFloat",F,A,K;A=("opacity" in D)?function(M){M.opacity="";}:function(M){M.filter="";};D.border="1px solid red";D.border="";K=D.borderLeft?function(M,O){var N;if(O!==J&&O.toLowerCase().indexOf("float")!=-1){O=J;}if(typeof M[O]==="string"){switch(O){case"opacity":case"filter":A(M);break;case"font":M.font=M.fontStyle=M.fontVariant=M.fontWeight=M.fontSize=M.lineHeight=M.fontFamily="";break;default:for(N in M){if(N.indexOf(O)===0){M[N]="";}}}}}:function(M,N){if(N!==J&&N.toLowerCase().indexOf("float")!=-1){N=J;}if(C.isString(M[N])){if(N==="opacity"){A(M);}else{M[N]="";}}};function G(T,O){var W,R,V,U={},N,X,Q,S,M,P;if(!(this instanceof G)){return new G(T,O);}R=T&&(T.nodeName?T:I.getElementById(T));if(T&&H[T]){return H[T];}else{if(R&&R.yuiSSID&&H[R.yuiSSID]){return H[R.yuiSSID];}}if(!R||!/^(?:style|link)$/i.test(R.nodeName)){R=I.createElement("style");R.type="text/css";}if(C.isString(T)){if(T.indexOf("{")!=-1){if(R.styleSheet){R.styleSheet.cssText=T;}else{R.appendChild(I.createTextNode(T));}}else{if(!O){O=T;}}}if(!R.parentNode||R.parentNode.nodeName.toLowerCase()!=="head"){W=(R.ownerDocument||I).getElementsByTagName("head")[0];W.appendChild(R);}V=R.sheet||R.styleSheet;N=V&&("cssRules" in V)?"cssRules":"rules";Q=("deleteRule" in V)?function(Y){V.deleteRule(Y);}:function(Y){V.removeRule(Y);};X=("insertRule" in V)?function(a,Z,Y){V.insertRule(a+" {"+Z+"}",Y);}:function(a,Z,Y){V.addRule(a,Z,Y);};for(S=V[N].length-1;S>=0;--S){M=V[N][S];P=M.selectorText;if(U[P]){U[P].style.cssText+=";"+M.style.cssText;Q(S);}else{U[P]=M;}}R.yuiSSID="yui-stylesheet-"+(E++);G.register(R.yuiSSID,this);if(O){G.register(O,this);}C.augmentObject(this,{getId:function(){return R.yuiSSID;},node:R,enable:function(){V.disabled=false;return this;},disable:function(){V.disabled=true;return this;},isEnabled:function(){return !V.disabled;},set:function(b,a){var d=U[b],c=b.split(/\s*,\s*/),Z,Y;if(c.length>1){for(Z=c.length-1;Z>=0;--Z){this.set(c[Z],a);}return this;}if(!G.isValidSelector(b)){return this;}if(d){d.style.cssText=G.toCssText(a,d.style.cssText);}else{Y=V[N].length;a=G.toCssText(a);if(a){X(b,a,Y);U[b]=V[N][Y];}}return this;},unset:function(b,a){var d=U[b],c=b.split(/\s*,\s*/),Y=!a,e,Z;if(c.length>1){for(Z=c.length-1;Z>=0;--Z){this.unset(c[Z],a);}return this;}if(d){if(!Y){if(!C.isArray(a)){a=[a];}D.cssText=d.style.cssText;for(Z=a.length-1;Z>=0;--Z){K(D,a[Z]);}if(D.cssText){d.style.cssText=D.cssText;}else{Y=true;}}if(Y){e=V[N];for(Z=e.length-1;Z>=0;--Z){if(e[Z]===d){delete U[b];Q(Z);break;}}}}return this;},getCssText:function(Z){var a,Y;if(C.isString(Z)){a=U[Z.split(/\s*,\s*/)[0]];return a?a.style.cssText:null;}else{Y=[];for(Z in U){if(U.hasOwnProperty(Z)){a=U[Z];Y.push(a.selectorText+" {"+a.style.cssText+"}");}}return Y.join("\n");}}},true);}F=function(M,O){var N=M.styleFloat||M.cssFloat||M["float"],Q;D.cssText=O||"";if(C.isString(M)){D.cssText+=";"+M;}else{if(N&&!M[J]){M=C.merge(M);delete M.styleFloat;delete M.cssFloat;delete M["float"];M[J]=N;}for(Q in M){if(M.hasOwnProperty(Q)){try{D[Q]=C.trim(M[Q]);}catch(P){}}}}return D.cssText;};C.augmentObject(G,{toCssText:(("opacity" in D)?F:function(M,N){if(C.isObject(M)&&"opacity" in M){M=C.merge(M,{filter:"alpha(opacity="+(M.opacity*100)+")"});delete M.opacity;}return F(M,N);}),register:function(M,N){return !!(M&&N instanceof G&&!H[M]&&(H[M]=N));},isValidSelector:function(N){var M=false;if(N&&C.isString(N)){if(!L.hasOwnProperty(N)){L[N]=!/\S/.test(N.replace(/\s+|\s*[+~>]\s*/g," ").replace(/([^ ])\[.*?\]/g,"$1").replace(/([^ ])::?[a-z][a-z\-]+[a-z](?:\(.*?\))?/ig,"$1").replace(/(?:^| )[a-z0-6]+/ig," ").replace(/\\./g,"").replace(/[.#]\w[\w\-]*/g,""));}M=L[N];}return M;}},true);YAHOO.util.StyleSheet=G;})();YAHOO.register("stylesheet",YAHOO.util.StyleSheet,{version:"2.8.2r1",build:"7"}); \ No newline at end of file
diff --git a/js/yui/swf/swf-min.js b/js/yui/swf/swf-min.js
new file mode 100644
index 000000000..e551e36d3
--- /dev/null
+++ b/js/yui/swf/swf-min.js
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+YAHOO.namespace("widget");(function(){var M=0;var L=YAHOO.env.ua;var P="ShockwaveFlash";if(L.gecko||L.webkit||L.opera){if((mF=navigator.mimeTypes["application/x-shockwave-flash"])){if((eP=mF.enabledPlugin)){var F=[];F=eP.description.replace(/\s[rd]/g,".").replace(/[A-Za-z\s]+/g,"").split(".");M=F[0]+".";switch((F[2].toString()).length){case 1:M+="00";break;case 2:M+="0";break;}M+=F[2];M=parseFloat(M);}}}else{if(L.ie){try{var Q=new ActiveXObject(P+"."+P+".6");Q.AllowScriptAccess="always";}catch(K){if(Q!=null){M=6;}}if(M==0){try{var B=new ActiveXObject(P+"."+P);var F=[];F=B.GetVariable("$version").replace(/[A-Za-z\s]+/g,"").split(",");M=F[0]+".";switch((F[2].toString()).length){case 1:M+="00";break;case 2:M+="0";break;}M+=F[2];M=parseFloat(M);}catch(K){}}}}L.flash=M;YAHOO.util.SWFDetect={getFlashVersion:function(){return M;},isFlashVersionAtLeast:function(R){return M>=R;}};var H=YAHOO.util.Dom,O=YAHOO.util.Event,I=YAHOO.util.SWFDetect,J=YAHOO.lang,G="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000",E="application/x-shockwave-flash",D="10.22",A="http://fpdownload.macromedia.com/pub/flashplayer/update/current/swf/autoUpdater.swf?"+Math.random(),C="YAHOO.widget.SWF.eventHandler",N={align:"",allowNetworking:"",allowScriptAccess:"",base:"",bgcolor:"",menu:"",name:"",quality:"",salign:"",scale:"",tabindex:"",wmode:""};YAHOO.widget.SWF=function(R,i,c){this._queue=this._queue||[];this._events=this._events||{};this._configs=this._configs||{};this._id=H.generateId(null,"yuiswf");if(c.host){this._host=c.host;}var e=this._id;var U=H.get(R);var S=(c["version"]||D);var b=I.isFlashVersionAtLeast(S);var a=(L.flash>=8);var V=a&&!b&&c["useExpressInstall"];var Z=(V)?A:i;var Y="<object ";var f,X;var g="YUISwfId="+e+"&YUIBridgeCallback="+C;YAHOO.widget.SWF._instances[e]=this;if(U&&(b||V)&&Z){Y+='id="'+e+'" ';if(L.ie){Y+='classid="'+G+'" ';}else{Y+='type="'+E+'" data="'+Z+'" ';}f="100%";X="100%";Y+='width="'+f+'" height="'+X+'">';if(L.ie){Y+='<param name="movie" value="'+Z+'"/>';}for(var T in c.fixedAttributes){if(N.hasOwnProperty(T)){Y+='<param name="'+T+'" value="'+c.fixedAttributes[T]+'"/>';}}for(var d in c.flashVars){var W=c.flashVars[d];if(J.isString(W)){g+="&"+d+"="+encodeURIComponent(W);}}if(g){Y+='<param name="flashVars" value="'+g+'"/>';}Y+="</object>";U.innerHTML=Y;}YAHOO.widget.SWF.superclass.constructor.call(this,H.get(e));this._swf=H.get(e);};YAHOO.widget.SWF._instances=YAHOO.widget.SWF._instances||{};YAHOO.widget.SWF.eventHandler=function(R,S){YAHOO.widget.SWF._instances[R]._eventHandler(S);};YAHOO.extend(YAHOO.widget.SWF,YAHOO.util.Element,{_eventHandler:function(R){if(R.type=="swfReady"){this.createEvent("swfReady",{fireOnce:true});this.fireEvent("swfReady",R);}else{if(R.type=="log"){}else{if(this._host&&this._host.fireEvent){this._host.fireEvent(R.type,R);}else{this.fireEvent(R.type,R);}}}},callSWF:function(S,R){if(!R){R=[];}if(this._swf[S]){return(this._swf[S].apply(this._swf,R));}else{return null;}},toString:function(){return"SWF "+this._id;}});})();YAHOO.register("swf",YAHOO.widget.SWF,{version:"2.8.2r1",build:"7"}); \ No newline at end of file
diff --git a/js/yui/swfdetect/swfdetect-min.js b/js/yui/swfdetect/swfdetect-min.js
new file mode 100644
index 000000000..3f7502426
--- /dev/null
+++ b/js/yui/swfdetect/swfdetect-min.js
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+YAHOO.namespace("util");(function(){var A=0;var B=YAHOO.env.ua;var C="ShockwaveFlash";if(B.gecko||B.webkit||B.opera){if((mF=navigator.mimeTypes["application/x-shockwave-flash"])){if((eP=mF.enabledPlugin)){var G=[];G=eP.description.replace(/\s[rd]/g,".").replace(/[A-Za-z\s]+/g,"").split(".");A=G[0]+".";switch((G[2].toString()).length){case 1:A+="00";break;case 2:A+="0";break;}A+=G[2];A=parseFloat(A);}}}else{if(B.ie){try{var D=new ActiveXObject(C+"."+C+".6");D.AllowScriptAccess="always";}catch(F){if(D!=null){A=6;}}if(A==0){try{var E=new ActiveXObject(C+"."+C);var G=[];G=E.GetVariable("$version").replace(/[A-Za-z\s]+/g,"").split(",");A=G[0]+".";switch((G[2].toString()).length){case 1:A+="00";break;case 2:A+="0";break;}A+=G[2];A=parseFloat(A);}catch(F){}}}}B.flash=A;YAHOO.util.SWFDetect={getFlashVersion:function(){return A;},isFlashVersionAtLeast:function(H){return A>=H;}};})();YAHOO.register("swfdetect",YAHOO.util.SWFDetect,{version:"2.8.2r1",build:"7"}); \ No newline at end of file
diff --git a/js/yui/swfstore/swfstore-min.js b/js/yui/swfstore/swfstore-min.js
new file mode 100644
index 000000000..e96421745
--- /dev/null
+++ b/js/yui/swfstore/swfstore-min.js
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+YAHOO.util.SWFStore=function(A,C,D){var B;var E;C=C.toString();D=D.toString();if(YAHOO.env.ua.ie){B="ie";}else{if(YAHOO.env.ua.gecko){B="gecko";}else{if(YAHOO.env.ua.webkit){B="webkit";}else{if(YAHOO.env.ua.caja){B="caja";}else{if(YAHOO.env.ua.opera){B="opera";}else{B="other";}}}}}if(YAHOO.util.Cookie.get("swfstore")==null||YAHOO.util.Cookie.get("swfstore")=="null"||YAHOO.util.Cookie.get("swfstore")==""){E=Math.round(Math.random()*Math.PI*100000);YAHOO.util.Cookie.set("swfstore",E);}else{E=YAHOO.util.Cookie.get("swfstore");}var F={version:9.115,useExpressInstall:false,fixedAttributes:{allowScriptAccess:"always",allowNetworking:"all",scale:"noScale"},flashVars:{shareData:C,browser:E,useCompression:D}};this.embeddedSWF=new YAHOO.widget.SWF(A,YAHOO.util.SWFStore.SWFURL,F);this.createEvent("error");this.createEvent("quotaExceededError");this.createEvent("securityError");this.createEvent("save");this.createEvent("clear");this.createEvent("pending");this.createEvent("openingDialog");this.createEvent("inadequateDimensions");};YAHOO.extend(YAHOO.util.SWFStore,YAHOO.util.AttributeProvider,{on:function(A,B){this.embeddedSWF.addListener(A,B);},addListener:function(A,B){this.embeddedSWF.addListener(A,B);},toString:function(){return"SWFStore "+this._id;},getShareData:function(){return this.embeddedSWF.callSWF("getShareData");},setShareData:function(A){this.embeddedSWF.callSWF("setShareData",[A]);},hasAdequateDimensions:function(){return this.embeddedSWF.callSWF("hasAdequateDimensions");},getUseCompression:function(){return this.embeddedSWF.callSWF("getUseCompression");},setUseCompression:function(A){this.embeddedSWF.callSWF("setUseCompression",[A]);},setItem:function(A,B){return this.embeddedSWF.callSWF("setItem",[A,B]);},getValueAt:function(A){return this.embeddedSWF.callSWF("getValueAt",[A]);},getNameAt:function(A){return this.embeddedSWF.callSWF("getNameAt",[A]);},getValueOf:function(A){return this.embeddedSWF.callSWF("getValueOf",[A]);},getTypeOf:function(A){return this.embeddedSWF.callSWF("getTypeOf",[A]);},getTypeAt:function(A){return this.embeddedSWF.callSWF("getTypeAt",[A]);},getItems:function(){return this.embeddedSWF.callSWF("getItems",[]);},removeItem:function(A){return this.embeddedSWF.callSWF("removeItem",[A]);},removeItemAt:function(A){return this.embeddedSWF.callSWF("removeItemAt",[A]);},getLength:function(){return this.embeddedSWF.callSWF("getLength",[]);},clear:function(){return this.embeddedSWF.callSWF("clear",[]);},calculateCurrentSize:function(){return this.embeddedSWF.callSWF("calculateCurrentSize",[]);},getModificationDate:function(){return this.embeddedSWF.callSWF("getModificationDate",[]);},setSize:function(B){var A=this.embeddedSWF.callSWF("setSize",[B]);return A;},displaySettings:function(){this.embeddedSWF.callSWF("displaySettings",[]);}});YAHOO.util.SWFStore.SWFURL="swfstore.swf";YAHOO.register("swfstore",YAHOO.util.SWFStore,{version:"2.8.2r1",build:"7"});YAHOO.register("swfstore",YAHOO.util.SWFStore,{version:"2.8.2r1",build:"7"}); \ No newline at end of file
diff --git a/js/yui/swfstore/swfstore.swf b/js/yui/swfstore/swfstore.swf
new file mode 100644
index 000000000..b2f5cd071
--- /dev/null
+++ b/js/yui/swfstore/swfstore.swf
Binary files differ
diff --git a/js/yui/tabview/tabview-min.js b/js/yui/tabview/tabview-min.js
new file mode 100644
index 000000000..84f2623af
--- /dev/null
+++ b/js/yui/tabview/tabview-min.js
@@ -0,0 +1,8 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+(function(){var B=YAHOO.util,C=B.Dom,H=B.Event,F=window.document,J="active",D="activeIndex",E="activeTab",A="contentEl",G="element",I=function(L,K){K=K||{};if(arguments.length==1&&!YAHOO.lang.isString(L)&&!L.nodeName){K=L;L=K.element||null;}if(!L&&!K.element){L=this._createTabViewElement(K);}I.superclass.constructor.call(this,L,K);};YAHOO.extend(I,B.Element,{CLASSNAME:"yui-navset",TAB_PARENT_CLASSNAME:"yui-nav",CONTENT_PARENT_CLASSNAME:"yui-content",_tabParent:null,_contentParent:null,addTab:function(P,L){var N=this.get("tabs"),Q=this.getTab(L),R=this._tabParent,K=this._contentParent,M=P.get(G),O=P.get(A);if(!N){this._queue[this._queue.length]=["addTab",arguments];return false;}L=(L===undefined)?N.length:L;N.splice(L,0,P);if(Q){R.insertBefore(M,Q.get(G));}else{R.appendChild(M);}if(O&&!C.isAncestor(K,O)){K.appendChild(O);}if(!P.get(J)){P.set("contentVisible",false,true);}else{this.set(E,P,true);this.set("activeIndex",L,true);}this._initTabEvents(P);},_initTabEvents:function(K){K.addListener(K.get("activationEvent"),K._onActivate,this,K);K.addListener(K.get("activationEventChange"),K._onActivationEventChange,this,K);},_removeTabEvents:function(K){K.removeListener(K.get("activationEvent"),K._onActivate,this,K);K.removeListener("activationEventChange",K._onActivationEventChange,this,K);},DOMEventHandler:function(P){var Q=H.getTarget(P),S=this._tabParent,R=this.get("tabs"),M,L,K;if(C.isAncestor(S,Q)){for(var N=0,O=R.length;N<O;N++){L=R[N].get(G);K=R[N].get(A);if(Q==L||C.isAncestor(L,Q)){M=R[N];break;}}if(M){M.fireEvent(P.type,P);}}},getTab:function(K){return this.get("tabs")[K];},getTabIndex:function(O){var L=null,N=this.get("tabs");for(var M=0,K=N.length;M<K;++M){if(O==N[M]){L=M;break;}}return L;},removeTab:function(M){var L=this.get("tabs").length,K=this.getTabIndex(M);if(M===this.get(E)){if(L>1){if(K+1===L){this.set(D,K-1);}else{this.set(D,K+1);}}else{this.set(E,null);}}this._removeTabEvents(M);this._tabParent.removeChild(M.get(G));this._contentParent.removeChild(M.get(A));this._configs.tabs.value.splice(K,1);M.fireEvent("remove",{type:"remove",tabview:this});},toString:function(){var K=this.get("id")||this.get("tagName");return"TabView "+K;},contentTransition:function(L,K){if(L){L.set("contentVisible",true);}if(K){K.set("contentVisible",false);}},initAttributes:function(K){I.superclass.initAttributes.call(this,K);if(!K.orientation){K.orientation="top";}var M=this.get(G);if(!C.hasClass(M,this.CLASSNAME)){C.addClass(M,this.CLASSNAME);}this.setAttributeConfig("tabs",{value:[],readOnly:true});this._tabParent=this.getElementsByClassName(this.TAB_PARENT_CLASSNAME,"ul")[0]||this._createTabParent();this._contentParent=this.getElementsByClassName(this.CONTENT_PARENT_CLASSNAME,"div")[0]||this._createContentParent();this.setAttributeConfig("orientation",{value:K.orientation,method:function(N){var O=this.get("orientation");this.addClass("yui-navset-"+N);if(O!=N){this.removeClass("yui-navset-"+O);}if(N==="bottom"){this.appendChild(this._tabParent);}}});this.setAttributeConfig(D,{value:K.activeIndex,validator:function(O){var N=true;if(O&&this.getTab(O).get("disabled")){N=false;}return N;}});this.setAttributeConfig(E,{value:K.activeTab,method:function(O){var N=this.get(E);if(O){O.set(J,true);}if(N&&N!==O){N.set(J,false);}if(N&&O!==N){this.contentTransition(O,N);}else{if(O){O.set("contentVisible",true);}}},validator:function(O){var N=true;if(O&&O.get("disabled")){N=false;}return N;}});this.on("activeTabChange",this._onActiveTabChange);this.on("activeIndexChange",this._onActiveIndexChange);if(this._tabParent){this._initTabs();}this.DOM_EVENTS.submit=false;this.DOM_EVENTS.focus=false;this.DOM_EVENTS.blur=false;for(var L in this.DOM_EVENTS){if(YAHOO.lang.hasOwnProperty(this.DOM_EVENTS,L)){this.addListener.call(this,L,this.DOMEventHandler);}}},deselectTab:function(K){if(this.getTab(K)===this.get("activeTab")){this.set("activeTab",null);}},selectTab:function(K){this.set("activeTab",this.getTab(K));},_onActiveTabChange:function(M){var K=this.get(D),L=this.getTabIndex(M.newValue);if(K!==L){if(!(this.set(D,L))){this.set(E,M.prevValue);}}},_onActiveIndexChange:function(K){if(K.newValue!==this.getTabIndex(this.get(E))){if(!(this.set(E,this.getTab(K.newValue)))){this.set(D,K.prevValue);}}},_initTabs:function(){var P=C.getChildren(this._tabParent),N=C.getChildren(this._contentParent),M=this.get(D),Q,L,R;for(var O=0,K=P.length;O<K;++O){L={};if(N[O]){L.contentEl=N[O];}Q=new YAHOO.widget.Tab(P[O],L);this.addTab(Q);if(Q.hasClass(Q.ACTIVE_CLASSNAME)){R=Q;}}if(M){this.set(E,this.getTab(M));}else{this._configs.activeTab.value=R;this._configs.activeIndex.value=this.getTabIndex(R);}},_createTabViewElement:function(K){var L=F.createElement("div");if(this.CLASSNAME){L.className=this.CLASSNAME;}return L;},_createTabParent:function(K){var L=F.createElement("ul");if(this.TAB_PARENT_CLASSNAME){L.className=this.TAB_PARENT_CLASSNAME;}this.get(G).appendChild(L);return L;},_createContentParent:function(K){var L=F.createElement("div");if(this.CONTENT_PARENT_CLASSNAME){L.className=this.CONTENT_PARENT_CLASSNAME;}this.get(G).appendChild(L);return L;}});YAHOO.widget.TabView=I;})();(function(){var D=YAHOO.util,I=D.Dom,L=YAHOO.lang,M="activeTab",J="label",G="labelEl",Q="content",C="contentEl",O="element",P="cacheData",B="dataSrc",H="dataLoaded",A="dataTimeout",N="loadMethod",F="postData",K="disabled",E=function(S,R){R=R||{};if(arguments.length==1&&!L.isString(S)&&!S.nodeName){R=S;S=R.element;}if(!S&&!R.element){S=this._createTabElement(R);}this.loadHandler={success:function(T){this.set(Q,T.responseText);},failure:function(T){}};E.superclass.constructor.call(this,S,R);this.DOM_EVENTS={};};YAHOO.extend(E,YAHOO.util.Element,{LABEL_TAGNAME:"em",ACTIVE_CLASSNAME:"selected",HIDDEN_CLASSNAME:"yui-hidden",ACTIVE_TITLE:"active",DISABLED_CLASSNAME:K,LOADING_CLASSNAME:"loading",dataConnection:null,loadHandler:null,_loading:false,toString:function(){var R=this.get(O),S=R.id||R.tagName;return"Tab "+S;},initAttributes:function(R){R=R||{};E.superclass.initAttributes.call(this,R);
+this.setAttributeConfig("activationEvent",{value:R.activationEvent||"click"});this.setAttributeConfig(G,{value:R[G]||this._getLabelEl(),method:function(S){S=I.get(S);var T=this.get(G);if(T){if(T==S){return false;}T.parentNode.replaceChild(S,T);this.set(J,S.innerHTML);}}});this.setAttributeConfig(J,{value:R.label||this._getLabel(),method:function(T){var S=this.get(G);if(!S){this.set(G,this._createLabelEl());}S.innerHTML=T;}});this.setAttributeConfig(C,{value:R[C]||document.createElement("div"),method:function(S){S=I.get(S);var T=this.get(C);if(T){if(T===S){return false;}if(!this.get("selected")){I.addClass(S,this.HIDDEN_CLASSNAME);}T.parentNode.replaceChild(S,T);this.set(Q,S.innerHTML);}}});this.setAttributeConfig(Q,{value:R[Q],method:function(S){this.get(C).innerHTML=S;}});this.setAttributeConfig(B,{value:R.dataSrc});this.setAttributeConfig(P,{value:R.cacheData||false,validator:L.isBoolean});this.setAttributeConfig(N,{value:R.loadMethod||"GET",validator:L.isString});this.setAttributeConfig(H,{value:false,validator:L.isBoolean,writeOnce:true});this.setAttributeConfig(A,{value:R.dataTimeout||null,validator:L.isNumber});this.setAttributeConfig(F,{value:R.postData||null});this.setAttributeConfig("active",{value:R.active||this.hasClass(this.ACTIVE_CLASSNAME),method:function(S){if(S===true){this.addClass(this.ACTIVE_CLASSNAME);this.set("title",this.ACTIVE_TITLE);}else{this.removeClass(this.ACTIVE_CLASSNAME);this.set("title","");}},validator:function(S){return L.isBoolean(S)&&!this.get(K);}});this.setAttributeConfig(K,{value:R.disabled||this.hasClass(this.DISABLED_CLASSNAME),method:function(S){if(S===true){I.addClass(this.get(O),this.DISABLED_CLASSNAME);}else{I.removeClass(this.get(O),this.DISABLED_CLASSNAME);}},validator:L.isBoolean});this.setAttributeConfig("href",{value:R.href||this.getElementsByTagName("a")[0].getAttribute("href",2)||"#",method:function(S){this.getElementsByTagName("a")[0].href=S;},validator:L.isString});this.setAttributeConfig("contentVisible",{value:R.contentVisible,method:function(S){if(S){I.removeClass(this.get(C),this.HIDDEN_CLASSNAME);if(this.get(B)){if(!this._loading&&!(this.get(H)&&this.get(P))){this._dataConnect();}}}else{I.addClass(this.get(C),this.HIDDEN_CLASSNAME);}},validator:L.isBoolean});},_dataConnect:function(){if(!D.Connect){return false;}I.addClass(this.get(C).parentNode,this.LOADING_CLASSNAME);this._loading=true;this.dataConnection=D.Connect.asyncRequest(this.get(N),this.get(B),{success:function(R){this.loadHandler.success.call(this,R);this.set(H,true);this.dataConnection=null;I.removeClass(this.get(C).parentNode,this.LOADING_CLASSNAME);this._loading=false;},failure:function(R){this.loadHandler.failure.call(this,R);this.dataConnection=null;I.removeClass(this.get(C).parentNode,this.LOADING_CLASSNAME);this._loading=false;},scope:this,timeout:this.get(A)},this.get(F));},_createTabElement:function(R){var V=document.createElement("li"),S=document.createElement("a"),U=R.label||null,T=R.labelEl||null;S.href=R.href||"#";V.appendChild(S);if(T){if(!U){U=this._getLabel();}}else{T=this._createLabelEl();}S.appendChild(T);return V;},_getLabelEl:function(){return this.getElementsByTagName(this.LABEL_TAGNAME)[0];},_createLabelEl:function(){var R=document.createElement(this.LABEL_TAGNAME);return R;},_getLabel:function(){var R=this.get(G);if(!R){return undefined;}return R.innerHTML;},_onActivate:function(U,T){var S=this,R=false;D.Event.preventDefault(U);if(S===T.get(M)){R=true;}T.set(M,S,R);},_onActivationEventChange:function(S){var R=this;if(S.prevValue!=S.newValue){R.removeListener(S.prevValue,R._onActivate);R.addListener(S.newValue,R._onActivate,this,R);}}});YAHOO.widget.Tab=E;})();YAHOO.register("tabview",YAHOO.widget.TabView,{version:"2.8.2r1",build:"7"}); \ No newline at end of file
diff --git a/js/yui/treeview/treeview-min.js b/js/yui/treeview/treeview-min.js
new file mode 100644
index 000000000..96d4fa73c
--- /dev/null
+++ b/js/yui/treeview/treeview-min.js
@@ -0,0 +1,12 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+(function(){var D=YAHOO.util.Dom,B=YAHOO.util.Event,F=YAHOO.lang,E=YAHOO.widget;YAHOO.widget.TreeView=function(H,G){if(H){this.init(H);}if(G){this.buildTreeFromObject(G);}else{if(F.trim(this._el.innerHTML)){this.buildTreeFromMarkup(H);}}};var C=E.TreeView;C.prototype={id:null,_el:null,_nodes:null,locked:false,_expandAnim:null,_collapseAnim:null,_animCount:0,maxAnim:2,_hasDblClickSubscriber:false,_dblClickTimer:null,currentFocus:null,singleNodeHighlight:false,_currentlyHighlighted:null,setExpandAnim:function(G){this._expandAnim=(E.TVAnim.isValid(G))?G:null;},setCollapseAnim:function(G){this._collapseAnim=(E.TVAnim.isValid(G))?G:null;},animateExpand:function(I,J){if(this._expandAnim&&this._animCount<this.maxAnim){var G=this;var H=E.TVAnim.getAnim(this._expandAnim,I,function(){G.expandComplete(J);});if(H){++this._animCount;this.fireEvent("animStart",{"node":J,"type":"expand"});H.animate();}return true;}return false;},animateCollapse:function(I,J){if(this._collapseAnim&&this._animCount<this.maxAnim){var G=this;var H=E.TVAnim.getAnim(this._collapseAnim,I,function(){G.collapseComplete(J);});if(H){++this._animCount;this.fireEvent("animStart",{"node":J,"type":"collapse"});H.animate();}return true;}return false;},expandComplete:function(G){--this._animCount;this.fireEvent("animComplete",{"node":G,"type":"expand"});},collapseComplete:function(G){--this._animCount;this.fireEvent("animComplete",{"node":G,"type":"collapse"});},init:function(I){this._el=D.get(I);this.id=D.generateId(this._el,"yui-tv-auto-id-");this.createEvent("animStart",this);this.createEvent("animComplete",this);this.createEvent("collapse",this);this.createEvent("collapseComplete",this);this.createEvent("expand",this);this.createEvent("expandComplete",this);this.createEvent("enterKeyPressed",this);this.createEvent("clickEvent",this);this.createEvent("focusChanged",this);var G=this;this.createEvent("dblClickEvent",{scope:this,onSubscribeCallback:function(){G._hasDblClickSubscriber=true;}});this.createEvent("labelClick",this);this.createEvent("highlightEvent",this);this._nodes=[];C.trees[this.id]=this;this.root=new E.RootNode(this);var H=E.LogWriter;if(this._initEditor){this._initEditor();}},buildTreeFromObject:function(G){var H=function(P,M){var L,Q,K,J,O,I,N;for(L=0;L<M.length;L++){Q=M[L];if(F.isString(Q)){K=new E.TextNode(Q,P);}else{if(F.isObject(Q)){J=Q.children;delete Q.children;O=Q.type||"text";delete Q.type;switch(F.isString(O)&&O.toLowerCase()){case"text":K=new E.TextNode(Q,P);break;case"menu":K=new E.MenuNode(Q,P);break;case"html":K=new E.HTMLNode(Q,P);break;default:if(F.isString(O)){I=E[O];}else{I=O;}if(F.isObject(I)){for(N=I;N&&N!==E.Node;N=N.superclass.constructor){}if(N){K=new I(Q,P);}else{}}else{}}if(J){H(K,J);}}else{}}}};if(!F.isArray(G)){G=[G];}H(this.root,G);},buildTreeFromMarkup:function(I){var H=function(J){var N,Q,M=[],L={},K,O;for(N=D.getFirstChild(J);N;N=D.getNextSibling(N)){switch(N.tagName.toUpperCase()){case"LI":K="";L={expanded:D.hasClass(N,"expanded"),title:N.title||N.alt||null,className:F.trim(N.className.replace(/\bexpanded\b/,""))||null};Q=N.firstChild;if(Q.nodeType==3){K=F.trim(Q.nodeValue.replace(/[\n\t\r]*/g,""));if(K){L.type="text";L.label=K;}else{Q=D.getNextSibling(Q);}}if(!K){if(Q.tagName.toUpperCase()=="A"){L.type="text";L.label=Q.innerHTML;L.href=Q.href;L.target=Q.target;L.title=Q.title||Q.alt||L.title;}else{L.type="html";var P=document.createElement("div");P.appendChild(Q.cloneNode(true));L.html=P.innerHTML;L.hasIcon=true;}}Q=D.getNextSibling(Q);switch(Q&&Q.tagName.toUpperCase()){case"UL":case"OL":L.children=H(Q);break;}if(YAHOO.lang.JSON){O=N.getAttribute("yuiConfig");if(O){O=YAHOO.lang.JSON.parse(O);L=YAHOO.lang.merge(L,O);}}M.push(L);break;case"UL":case"OL":L={type:"text",label:"",children:H(Q)};M.push(L);break;}}return M;};var G=D.getChildrenBy(D.get(I),function(K){var J=K.tagName.toUpperCase();return J=="UL"||J=="OL";});if(G.length){this.buildTreeFromObject(H(G[0]));}else{}},_getEventTargetTdEl:function(H){var I=B.getTarget(H);while(I&&!(I.tagName.toUpperCase()=="TD"&&D.hasClass(I.parentNode,"ygtvrow"))){I=D.getAncestorByTagName(I,"td");}if(F.isNull(I)){return null;}if(/\bygtv(blank)?depthcell/.test(I.className)){return null;}if(I.id){var G=I.id.match(/\bygtv([^\d]*)(.*)/);if(G&&G[2]&&this._nodes[G[2]]){return I;}}return null;},_onClickEvent:function(J){var H=this,L=this._getEventTargetTdEl(J),I,K,G=function(M){I.focus();if(M||!I.href){I.toggle();try{B.preventDefault(J);}catch(N){}}};if(!L){return;}I=this.getNodeByElement(L);if(!I){return;}K=B.getTarget(J);if(D.hasClass(K,I.labelStyle)||D.getAncestorByClassName(K,I.labelStyle)){this.fireEvent("labelClick",I);}if(/\bygtv[tl][mp]h?h?/.test(L.className)){G(true);}else{if(this._dblClickTimer){window.clearTimeout(this._dblClickTimer);this._dblClickTimer=null;}else{if(this._hasDblClickSubscriber){this._dblClickTimer=window.setTimeout(function(){H._dblClickTimer=null;if(H.fireEvent("clickEvent",{event:J,node:I})!==false){G();}},200);}else{if(H.fireEvent("clickEvent",{event:J,node:I})!==false){G();}}}}},_onDblClickEvent:function(G){if(!this._hasDblClickSubscriber){return;}var H=this._getEventTargetTdEl(G);if(!H){return;}if(!(/\bygtv[tl][mp]h?h?/.test(H.className))){this.fireEvent("dblClickEvent",{event:G,node:this.getNodeByElement(H)});if(this._dblClickTimer){window.clearTimeout(this._dblClickTimer);this._dblClickTimer=null;}}},_onMouseOverEvent:function(G){var H;if((H=this._getEventTargetTdEl(G))&&(H=this.getNodeByElement(H))&&(H=H.getToggleEl())){H.className=H.className.replace(/\bygtv([lt])([mp])\b/gi,"ygtv$1$2h");}},_onMouseOutEvent:function(G){var H;if((H=this._getEventTargetTdEl(G))&&(H=this.getNodeByElement(H))&&(H=H.getToggleEl())){H.className=H.className.replace(/\bygtv([lt])([mp])h\b/gi,"ygtv$1$2");}},_onKeyDownEvent:function(L){var N=B.getTarget(L),K=this.getNodeByElement(N),J=K,G=YAHOO.util.KeyListener.KEY;switch(L.keyCode){case G.UP:do{if(J.previousSibling){J=J.previousSibling;}else{J=J.parent;}}while(J&&!J._canHaveFocus());if(J){J.focus();
+}B.preventDefault(L);break;case G.DOWN:do{if(J.nextSibling){J=J.nextSibling;}else{J.expand();J=(J.children.length||null)&&J.children[0];}}while(J&&!J._canHaveFocus);if(J){J.focus();}B.preventDefault(L);break;case G.LEFT:do{if(J.parent){J=J.parent;}else{J=J.previousSibling;}}while(J&&!J._canHaveFocus());if(J){J.focus();}B.preventDefault(L);break;case G.RIGHT:var I=this,M,H=function(O){I.unsubscribe("expandComplete",H);M(O);};M=function(O){do{if(O.isDynamic()&&!O.childrenRendered){I.subscribe("expandComplete",H);O.expand();O=null;break;}else{O.expand();if(O.children.length){O=O.children[0];}else{O=O.nextSibling;}}}while(O&&!O._canHaveFocus());if(O){O.focus();}};M(J);B.preventDefault(L);break;case G.ENTER:if(K.href){if(K.target){window.open(K.href,K.target);}else{window.location(K.href);}}else{K.toggle();}this.fireEvent("enterKeyPressed",K);B.preventDefault(L);break;case G.HOME:J=this.getRoot();if(J.children.length){J=J.children[0];}if(J._canHaveFocus()){J.focus();}B.preventDefault(L);break;case G.END:J=J.parent.children;J=J[J.length-1];if(J._canHaveFocus()){J.focus();}B.preventDefault(L);break;case 107:if(L.shiftKey){K.parent.expandAll();}else{K.expand();}break;case 109:if(L.shiftKey){K.parent.collapseAll();}else{K.collapse();}break;default:break;}},render:function(){var G=this.root.getHtml(),H=this.getEl();H.innerHTML=G;if(!this._hasEvents){B.on(H,"click",this._onClickEvent,this,true);B.on(H,"dblclick",this._onDblClickEvent,this,true);B.on(H,"mouseover",this._onMouseOverEvent,this,true);B.on(H,"mouseout",this._onMouseOutEvent,this,true);B.on(H,"keydown",this._onKeyDownEvent,this,true);}this._hasEvents=true;},getEl:function(){if(!this._el){this._el=D.get(this.id);}return this._el;},regNode:function(G){this._nodes[G.index]=G;},getRoot:function(){return this.root;},setDynamicLoad:function(G,H){this.root.setDynamicLoad(G,H);},expandAll:function(){if(!this.locked){this.root.expandAll();}},collapseAll:function(){if(!this.locked){this.root.collapseAll();}},getNodeByIndex:function(H){var G=this._nodes[H];return(G)?G:null;},getNodeByProperty:function(I,H){for(var G in this._nodes){if(this._nodes.hasOwnProperty(G)){var J=this._nodes[G];if((I in J&&J[I]==H)||(J.data&&H==J.data[I])){return J;}}}return null;},getNodesByProperty:function(J,I){var G=[];for(var H in this._nodes){if(this._nodes.hasOwnProperty(H)){var K=this._nodes[H];if((J in K&&K[J]==I)||(K.data&&I==K.data[J])){G.push(K);}}}return(G.length)?G:null;},getNodesBy:function(I){var G=[];for(var H in this._nodes){if(this._nodes.hasOwnProperty(H)){var J=this._nodes[H];if(I(J)){G.push(J);}}}return(G.length)?G:null;},getNodeByElement:function(I){var J=I,G,H=/ygtv([^\d]*)(.*)/;do{if(J&&J.id){G=J.id.match(H);if(G&&G[2]){return this.getNodeByIndex(G[2]);}}J=J.parentNode;if(!J||!J.tagName){break;}}while(J.id!==this.id&&J.tagName.toLowerCase()!=="body");return null;},getHighlightedNode:function(){return this._currentlyHighlighted;},removeNode:function(H,G){if(H.isRoot()){return false;}var I=H.parent;if(I.parent){I=I.parent;}this._deleteNode(H);if(G&&I&&I.childrenRendered){I.refresh();}return true;},_removeChildren_animComplete:function(G){this.unsubscribe(this._removeChildren_animComplete);this.removeChildren(G.node);},removeChildren:function(G){if(G.expanded){if(this._collapseAnim){this.subscribe("animComplete",this._removeChildren_animComplete,this,true);E.Node.prototype.collapse.call(G);return;}G.collapse();}while(G.children.length){this._deleteNode(G.children[0]);}if(G.isRoot()){E.Node.prototype.expand.call(G);}G.childrenRendered=false;G.dynamicLoadComplete=false;G.updateIcon();},_deleteNode:function(G){this.removeChildren(G);this.popNode(G);},popNode:function(J){var K=J.parent;var H=[];for(var I=0,G=K.children.length;I<G;++I){if(K.children[I]!=J){H[H.length]=K.children[I];}}K.children=H;K.childrenRendered=false;if(J.previousSibling){J.previousSibling.nextSibling=J.nextSibling;}if(J.nextSibling){J.nextSibling.previousSibling=J.previousSibling;}if(this.currentFocus==J){this.currentFocus=null;}if(this._currentlyHighlighted==J){this._currentlyHighlighted=null;}J.parent=null;J.previousSibling=null;J.nextSibling=null;J.tree=null;delete this._nodes[J.index];},destroy:function(){if(this._destroyEditor){this._destroyEditor();}var H=this.getEl();B.removeListener(H,"click");B.removeListener(H,"dblclick");B.removeListener(H,"mouseover");B.removeListener(H,"mouseout");B.removeListener(H,"keydown");for(var G=0;G<this._nodes.length;G++){var I=this._nodes[G];if(I&&I.destroy){I.destroy();}}H.innerHTML="";this._hasEvents=false;},toString:function(){return"TreeView "+this.id;},getNodeCount:function(){return this.getRoot().getNodeCount();},getTreeDefinition:function(){return this.getRoot().getNodeDefinition();},onExpand:function(G){},onCollapse:function(G){},setNodesProperty:function(G,I,H){this.root.setNodesProperty(G,I);if(H){this.root.refresh();}},onEventToggleHighlight:function(H){var G;if("node" in H&&H.node instanceof E.Node){G=H.node;}else{if(H instanceof E.Node){G=H;}else{return false;}}G.toggleHighlight();return false;}};var A=C.prototype;A.draw=A.render;YAHOO.augment(C,YAHOO.util.EventProvider);C.nodeCount=0;C.trees=[];C.getTree=function(H){var G=C.trees[H];return(G)?G:null;};C.getNode=function(H,I){var G=C.getTree(H);return(G)?G.getNodeByIndex(I):null;};C.FOCUS_CLASS_NAME="ygtvfocus";})();(function(){var B=YAHOO.util.Dom,C=YAHOO.lang,A=YAHOO.util.Event;YAHOO.widget.Node=function(F,E,D){if(F){this.init(F,E,D);}};YAHOO.widget.Node.prototype={index:0,children:null,tree:null,data:null,parent:null,depth:-1,expanded:false,multiExpand:true,renderHidden:false,childrenRendered:false,dynamicLoadComplete:false,previousSibling:null,nextSibling:null,_dynLoad:false,dataLoader:null,isLoading:false,hasIcon:true,iconMode:0,nowrap:false,isLeaf:false,contentStyle:"",contentElId:null,enableHighlight:true,highlightState:0,propagateHighlightUp:false,propagateHighlightDown:false,className:null,_type:"Node",init:function(G,F,D){this.data={};this.children=[];this.index=YAHOO.widget.TreeView.nodeCount;
+++YAHOO.widget.TreeView.nodeCount;this.contentElId="ygtvcontentel"+this.index;if(C.isObject(G)){for(var E in G){if(G.hasOwnProperty(E)){if(E.charAt(0)!="_"&&!C.isUndefined(this[E])&&!C.isFunction(this[E])){this[E]=G[E];}else{this.data[E]=G[E];}}}}if(!C.isUndefined(D)){this.expanded=D;}this.createEvent("parentChange",this);if(F){F.appendChild(this);}},applyParent:function(E){if(!E){return false;}this.tree=E.tree;this.parent=E;this.depth=E.depth+1;this.tree.regNode(this);E.childrenRendered=false;for(var F=0,D=this.children.length;F<D;++F){this.children[F].applyParent(this);}this.fireEvent("parentChange");return true;},appendChild:function(E){if(this.hasChildren()){var D=this.children[this.children.length-1];D.nextSibling=E;E.previousSibling=D;}this.children[this.children.length]=E;E.applyParent(this);if(this.childrenRendered&&this.expanded){this.getChildrenEl().style.display="";}return E;},appendTo:function(D){return D.appendChild(this);},insertBefore:function(D){var F=D.parent;if(F){if(this.tree){this.tree.popNode(this);}var E=D.isChildOf(F);F.children.splice(E,0,this);if(D.previousSibling){D.previousSibling.nextSibling=this;}this.previousSibling=D.previousSibling;this.nextSibling=D;D.previousSibling=this;this.applyParent(F);}return this;},insertAfter:function(D){var F=D.parent;if(F){if(this.tree){this.tree.popNode(this);}var E=D.isChildOf(F);if(!D.nextSibling){this.nextSibling=null;return this.appendTo(F);}F.children.splice(E+1,0,this);D.nextSibling.previousSibling=this;this.previousSibling=D;this.nextSibling=D.nextSibling;D.nextSibling=this;this.applyParent(F);}return this;},isChildOf:function(E){if(E&&E.children){for(var F=0,D=E.children.length;F<D;++F){if(E.children[F]===this){return F;}}}return -1;},getSiblings:function(){var D=this.parent.children.slice(0);for(var E=0;E<D.length&&D[E]!=this;E++){}D.splice(E,1);if(D.length){return D;}return null;},showChildren:function(){if(!this.tree.animateExpand(this.getChildrenEl(),this)){if(this.hasChildren()){this.getChildrenEl().style.display="";}}},hideChildren:function(){if(!this.tree.animateCollapse(this.getChildrenEl(),this)){this.getChildrenEl().style.display="none";}},getElId:function(){return"ygtv"+this.index;},getChildrenElId:function(){return"ygtvc"+this.index;},getToggleElId:function(){return"ygtvt"+this.index;},getEl:function(){return B.get(this.getElId());},getChildrenEl:function(){return B.get(this.getChildrenElId());},getToggleEl:function(){return B.get(this.getToggleElId());},getContentEl:function(){return B.get(this.contentElId);},collapse:function(){if(!this.expanded){return;}var D=this.tree.onCollapse(this);if(false===D){return;}D=this.tree.fireEvent("collapse",this);if(false===D){return;}if(!this.getEl()){this.expanded=false;}else{this.hideChildren();this.expanded=false;this.updateIcon();}D=this.tree.fireEvent("collapseComplete",this);},expand:function(F){if(this.isLoading||(this.expanded&&!F)){return;}var D=true;if(!F){D=this.tree.onExpand(this);if(false===D){return;}D=this.tree.fireEvent("expand",this);}if(false===D){return;}if(!this.getEl()){this.expanded=true;return;}if(!this.childrenRendered){this.getChildrenEl().innerHTML=this.renderChildren();}else{}this.expanded=true;this.updateIcon();if(this.isLoading){this.expanded=false;return;}if(!this.multiExpand){var G=this.getSiblings();for(var E=0;G&&E<G.length;++E){if(G[E]!=this&&G[E].expanded){G[E].collapse();}}}this.showChildren();D=this.tree.fireEvent("expandComplete",this);},updateIcon:function(){if(this.hasIcon){var D=this.getToggleEl();if(D){D.className=D.className.replace(/\bygtv(([tl][pmn]h?)|(loading))\b/gi,this.getStyle());}}},getStyle:function(){if(this.isLoading){return"ygtvloading";}else{var E=(this.nextSibling)?"t":"l";var D="n";if(this.hasChildren(true)||(this.isDynamic()&&!this.getIconMode())){D=(this.expanded)?"m":"p";}return"ygtv"+E+D;}},getHoverStyle:function(){var D=this.getStyle();if(this.hasChildren(true)&&!this.isLoading){D+="h";}return D;},expandAll:function(){var D=this.children.length;for(var E=0;E<D;++E){var F=this.children[E];if(F.isDynamic()){break;}else{if(!F.multiExpand){break;}else{F.expand();F.expandAll();}}}},collapseAll:function(){for(var D=0;D<this.children.length;++D){this.children[D].collapse();this.children[D].collapseAll();}},setDynamicLoad:function(D,E){if(D){this.dataLoader=D;this._dynLoad=true;}else{this.dataLoader=null;this._dynLoad=false;}if(E){this.iconMode=E;}},isRoot:function(){return(this==this.tree.root);},isDynamic:function(){if(this.isLeaf){return false;}else{return(!this.isRoot()&&(this._dynLoad||this.tree.root._dynLoad));}},getIconMode:function(){return(this.iconMode||this.tree.root.iconMode);},hasChildren:function(D){if(this.isLeaf){return false;}else{return(this.children.length>0||(D&&this.isDynamic()&&!this.dynamicLoadComplete));}},toggle:function(){if(!this.tree.locked&&(this.hasChildren(true)||this.isDynamic())){if(this.expanded){this.collapse();}else{this.expand();}}},getHtml:function(){this.childrenRendered=false;return['<div class="ygtvitem" id="',this.getElId(),'">',this.getNodeHtml(),this.getChildrenHtml(),"</div>"].join("");},getChildrenHtml:function(){var D=[];D[D.length]='<div class="ygtvchildren" id="'+this.getChildrenElId()+'"';if(!this.expanded||!this.hasChildren()){D[D.length]=' style="display:none;"';}D[D.length]=">";if((this.hasChildren(true)&&this.expanded)||(this.renderHidden&&!this.isDynamic())){D[D.length]=this.renderChildren();}D[D.length]="</div>";return D.join("");},renderChildren:function(){var D=this;if(this.isDynamic()&&!this.dynamicLoadComplete){this.isLoading=true;this.tree.locked=true;if(this.dataLoader){setTimeout(function(){D.dataLoader(D,function(){D.loadComplete();});},10);}else{if(this.tree.root.dataLoader){setTimeout(function(){D.tree.root.dataLoader(D,function(){D.loadComplete();});},10);}else{return"Error: data loader not found or not specified.";}}return"";}else{return this.completeRender();}},completeRender:function(){var E=[];for(var D=0;D<this.children.length;++D){E[E.length]=this.children[D].getHtml();
+}this.childrenRendered=true;return E.join("");},loadComplete:function(){this.getChildrenEl().innerHTML=this.completeRender();if(this.propagateHighlightDown){if(this.highlightState===1&&!this.tree.singleNodeHighlight){for(var D=0;D<this.children.length;D++){this.children[D].highlight(true);}}else{if(this.highlightState===0||this.tree.singleNodeHighlight){for(D=0;D<this.children.length;D++){this.children[D].unhighlight(true);}}}}this.dynamicLoadComplete=true;this.isLoading=false;this.expand(true);this.tree.locked=false;},getAncestor:function(E){if(E>=this.depth||E<0){return null;}var D=this.parent;while(D.depth>E){D=D.parent;}return D;},getDepthStyle:function(D){return(this.getAncestor(D).nextSibling)?"ygtvdepthcell":"ygtvblankdepthcell";},getNodeHtml:function(){var E=[];E[E.length]='<table id="ygtvtableel'+this.index+'" border="0" cellpadding="0" cellspacing="0" class="ygtvtable ygtvdepth'+this.depth;if(this.enableHighlight){E[E.length]=" ygtv-highlight"+this.highlightState;}if(this.className){E[E.length]=" "+this.className;}E[E.length]='"><tr class="ygtvrow">';for(var D=0;D<this.depth;++D){E[E.length]='<td class="ygtvcell '+this.getDepthStyle(D)+'"><div class="ygtvspacer"></div></td>';}if(this.hasIcon){E[E.length]='<td id="'+this.getToggleElId();E[E.length]='" class="ygtvcell ';E[E.length]=this.getStyle();E[E.length]='"><a href="#" class="ygtvspacer">&#160;</a></td>';}E[E.length]='<td id="'+this.contentElId;E[E.length]='" class="ygtvcell ';E[E.length]=this.contentStyle+' ygtvcontent" ';E[E.length]=(this.nowrap)?' nowrap="nowrap" ':"";E[E.length]=" >";E[E.length]=this.getContentHtml();E[E.length]="</td></tr></table>";return E.join("");},getContentHtml:function(){return"";},refresh:function(){this.getChildrenEl().innerHTML=this.completeRender();if(this.hasIcon){var D=this.getToggleEl();if(D){D.className=D.className.replace(/\bygtv[lt][nmp]h*\b/gi,this.getStyle());}}},toString:function(){return this._type+" ("+this.index+")";},_focusHighlightedItems:[],_focusedItem:null,_canHaveFocus:function(){return this.getEl().getElementsByTagName("a").length>0;},_removeFocus:function(){if(this._focusedItem){A.removeListener(this._focusedItem,"blur");this._focusedItem=null;}var D;while((D=this._focusHighlightedItems.shift())){B.removeClass(D,YAHOO.widget.TreeView.FOCUS_CLASS_NAME);}},focus:function(){var F=false,D=this;if(this.tree.currentFocus){this.tree.currentFocus._removeFocus();}var E=function(G){if(G.parent){E(G.parent);G.parent.expand();}};E(this);B.getElementsBy(function(G){return(/ygtv(([tl][pmn]h?)|(content))/).test(G.className);},"td",D.getEl().firstChild,function(H){B.addClass(H,YAHOO.widget.TreeView.FOCUS_CLASS_NAME);if(!F){var G=H.getElementsByTagName("a");if(G.length){G=G[0];G.focus();D._focusedItem=G;A.on(G,"blur",function(){D.tree.fireEvent("focusChanged",{oldNode:D.tree.currentFocus,newNode:null});D.tree.currentFocus=null;D._removeFocus();});F=true;}}D._focusHighlightedItems.push(H);});if(F){this.tree.fireEvent("focusChanged",{oldNode:this.tree.currentFocus,newNode:this});this.tree.currentFocus=this;}else{this.tree.fireEvent("focusChanged",{oldNode:D.tree.currentFocus,newNode:null});this.tree.currentFocus=null;this._removeFocus();}return F;},getNodeCount:function(){for(var D=0,E=0;D<this.children.length;D++){E+=this.children[D].getNodeCount();}return E+1;},getNodeDefinition:function(){if(this.isDynamic()){return false;}var G,D=C.merge(this.data),F=[];if(this.expanded){D.expanded=this.expanded;}if(!this.multiExpand){D.multiExpand=this.multiExpand;}if(!this.renderHidden){D.renderHidden=this.renderHidden;}if(!this.hasIcon){D.hasIcon=this.hasIcon;}if(this.nowrap){D.nowrap=this.nowrap;}if(this.className){D.className=this.className;}if(this.editable){D.editable=this.editable;}if(this.enableHighlight){D.enableHighlight=this.enableHighlight;}if(this.highlightState){D.highlightState=this.highlightState;}if(this.propagateHighlightUp){D.propagateHighlightUp=this.propagateHighlightUp;}if(this.propagateHighlightDown){D.propagateHighlightDown=this.propagateHighlightDown;}D.type=this._type;for(var E=0;E<this.children.length;E++){G=this.children[E].getNodeDefinition();if(G===false){return false;}F.push(G);}if(F.length){D.children=F;}return D;},getToggleLink:function(){return"return false;";},setNodesProperty:function(D,G,F){if(D.charAt(0)!="_"&&!C.isUndefined(this[D])&&!C.isFunction(this[D])){this[D]=G;}else{this.data[D]=G;}for(var E=0;E<this.children.length;E++){this.children[E].setNodesProperty(D,G);}if(F){this.refresh();}},toggleHighlight:function(){if(this.enableHighlight){if(this.highlightState==1){this.unhighlight();}else{this.highlight();}}},highlight:function(E){if(this.enableHighlight){if(this.tree.singleNodeHighlight){if(this.tree._currentlyHighlighted){this.tree._currentlyHighlighted.unhighlight(E);}this.tree._currentlyHighlighted=this;}this.highlightState=1;this._setHighlightClassName();if(!this.tree.singleNodeHighlight){if(this.propagateHighlightDown){for(var D=0;D<this.children.length;D++){this.children[D].highlight(true);}}if(this.propagateHighlightUp){if(this.parent){this.parent._childrenHighlighted();}}}if(!E){this.tree.fireEvent("highlightEvent",this);}}},unhighlight:function(E){if(this.enableHighlight){this.tree._currentlyHighlighted=null;this.highlightState=0;this._setHighlightClassName();if(!this.tree.singleNodeHighlight){if(this.propagateHighlightDown){for(var D=0;D<this.children.length;D++){this.children[D].unhighlight(true);}}if(this.propagateHighlightUp){if(this.parent){this.parent._childrenHighlighted();}}}if(!E){this.tree.fireEvent("highlightEvent",this);}}},_childrenHighlighted:function(){var F=false,E=false;if(this.enableHighlight){for(var D=0;D<this.children.length;D++){switch(this.children[D].highlightState){case 0:E=true;break;case 1:F=true;break;case 2:F=E=true;break;}}if(F&&E){this.highlightState=2;}else{if(F){this.highlightState=1;}else{this.highlightState=0;}}this._setHighlightClassName();if(this.propagateHighlightUp){if(this.parent){this.parent._childrenHighlighted();
+}}}},_setHighlightClassName:function(){var D=B.get("ygtvtableel"+this.index);if(D){D.className=D.className.replace(/\bygtv-highlight\d\b/gi,"ygtv-highlight"+this.highlightState);}}};YAHOO.augment(YAHOO.widget.Node,YAHOO.util.EventProvider);})();YAHOO.widget.RootNode=function(A){this.init(null,null,true);this.tree=A;};YAHOO.extend(YAHOO.widget.RootNode,YAHOO.widget.Node,{_type:"RootNode",getNodeHtml:function(){return"";},toString:function(){return this._type;},loadComplete:function(){this.tree.draw();},getNodeCount:function(){for(var A=0,B=0;A<this.children.length;A++){B+=this.children[A].getNodeCount();}return B;},getNodeDefinition:function(){for(var C,A=[],B=0;B<this.children.length;B++){C=this.children[B].getNodeDefinition();if(C===false){return false;}A.push(C);}return A;},collapse:function(){},expand:function(){},getSiblings:function(){return null;},focus:function(){}});(function(){var B=YAHOO.util.Dom,C=YAHOO.lang,A=YAHOO.util.Event;YAHOO.widget.TextNode=function(F,E,D){if(F){if(C.isString(F)){F={label:F};}this.init(F,E,D);this.setUpLabel(F);}};YAHOO.extend(YAHOO.widget.TextNode,YAHOO.widget.Node,{labelStyle:"ygtvlabel",labelElId:null,label:null,title:null,href:null,target:"_self",_type:"TextNode",setUpLabel:function(D){if(C.isString(D)){D={label:D};}else{if(D.style){this.labelStyle=D.style;}}this.label=D.label;this.labelElId="ygtvlabelel"+this.index;},getLabelEl:function(){return B.get(this.labelElId);},getContentHtml:function(){var D=[];D[D.length]=this.href?"<a":"<span";D[D.length]=' id="'+this.labelElId+'"';D[D.length]=' class="'+this.labelStyle+'"';if(this.href){D[D.length]=' href="'+this.href+'"';D[D.length]=' target="'+this.target+'"';}if(this.title){D[D.length]=' title="'+this.title+'"';}D[D.length]=" >";D[D.length]=this.label;D[D.length]=this.href?"</a>":"</span>";return D.join("");},getNodeDefinition:function(){var D=YAHOO.widget.TextNode.superclass.getNodeDefinition.call(this);if(D===false){return false;}D.label=this.label;if(this.labelStyle!="ygtvlabel"){D.style=this.labelStyle;}if(this.title){D.title=this.title;}if(this.href){D.href=this.href;}if(this.target!="_self"){D.target=this.target;}return D;},toString:function(){return YAHOO.widget.TextNode.superclass.toString.call(this)+": "+this.label;},onLabelClick:function(){return false;},refresh:function(){YAHOO.widget.TextNode.superclass.refresh.call(this);var D=this.getLabelEl();D.innerHTML=this.label;if(D.tagName.toUpperCase()=="A"){D.href=this.href;D.target=this.target;}}});})();YAHOO.widget.MenuNode=function(C,B,A){YAHOO.widget.MenuNode.superclass.constructor.call(this,C,B,A);this.multiExpand=false;};YAHOO.extend(YAHOO.widget.MenuNode,YAHOO.widget.TextNode,{_type:"MenuNode"});(function(){var B=YAHOO.util.Dom,C=YAHOO.lang,A=YAHOO.util.Event;YAHOO.widget.HTMLNode=function(G,F,E,D){if(G){this.init(G,F,E);this.initContent(G,D);}};YAHOO.extend(YAHOO.widget.HTMLNode,YAHOO.widget.Node,{contentStyle:"ygtvhtml",html:null,_type:"HTMLNode",initContent:function(E,D){this.setHtml(E);this.contentElId="ygtvcontentel"+this.index;if(!C.isUndefined(D)){this.hasIcon=D;}},setHtml:function(E){this.html=(typeof E==="string")?E:E.html;var D=this.getContentEl();if(D){D.innerHTML=this.html;}},getContentHtml:function(){return this.html;},getNodeDefinition:function(){var D=YAHOO.widget.HTMLNode.superclass.getNodeDefinition.call(this);if(D===false){return false;}D.html=this.html;return D;}});})();(function(){var B=YAHOO.util.Dom,C=YAHOO.lang,A=YAHOO.util.Event,D=YAHOO.widget.Calendar;YAHOO.widget.DateNode=function(G,F,E){YAHOO.widget.DateNode.superclass.constructor.call(this,G,F,E);};YAHOO.extend(YAHOO.widget.DateNode,YAHOO.widget.TextNode,{_type:"DateNode",calendarConfig:null,fillEditorContainer:function(G){var H,F=G.inputContainer;if(C.isUndefined(D)){B.replaceClass(G.editorPanel,"ygtv-edit-DateNode","ygtv-edit-TextNode");YAHOO.widget.DateNode.superclass.fillEditorContainer.call(this,G);return;}if(G.nodeType!=this._type){G.nodeType=this._type;G.saveOnEnter=false;G.node.destroyEditorContents(G);G.inputObject=H=new D(F.appendChild(document.createElement("div")));if(this.calendarConfig){H.cfg.applyConfig(this.calendarConfig,true);H.cfg.fireQueue();}H.selectEvent.subscribe(function(){this.tree._closeEditor(true);},this,true);}else{H=G.inputObject;}G.oldValue=this.label;H.cfg.setProperty("selected",this.label,false);var I=H.cfg.getProperty("DATE_FIELD_DELIMITER");var E=this.label.split(I);H.cfg.setProperty("pagedate",E[H.cfg.getProperty("MDY_MONTH_POSITION")-1]+I+E[H.cfg.getProperty("MDY_YEAR_POSITION")-1]);H.cfg.fireQueue();H.render();H.oDomContainer.focus();},getEditorValue:function(F){if(C.isUndefined(D)){return F.inputElement.value;}else{var H=F.inputObject,G=H.getSelectedDates()[0],E=[];E[H.cfg.getProperty("MDY_DAY_POSITION")-1]=G.getDate();E[H.cfg.getProperty("MDY_MONTH_POSITION")-1]=G.getMonth()+1;E[H.cfg.getProperty("MDY_YEAR_POSITION")-1]=G.getFullYear();return E.join(H.cfg.getProperty("DATE_FIELD_DELIMITER"));}},displayEditedValue:function(G,E){var F=E.node;F.label=G;F.getLabelEl().innerHTML=G;},getNodeDefinition:function(){var E=YAHOO.widget.DateNode.superclass.getNodeDefinition.call(this);if(E===false){return false;}if(this.calendarConfig){E.calendarConfig=this.calendarConfig;}return E;}});})();(function(){var E=YAHOO.util.Dom,F=YAHOO.lang,B=YAHOO.util.Event,D=YAHOO.widget.TreeView,C=D.prototype;D.editorData={active:false,whoHasIt:null,nodeType:null,editorPanel:null,inputContainer:null,buttonsContainer:null,node:null,saveOnEnter:true,oldValue:undefined};C.validator=null;C._initEditor=function(){this.createEvent("editorSaveEvent",this);this.createEvent("editorCancelEvent",this);};C._nodeEditing=function(M){if(M.fillEditorContainer&&M.editable){var I,K,L,J,H=D.editorData;H.active=true;H.whoHasIt=this;if(!H.nodeType){H.editorPanel=I=document.body.appendChild(document.createElement("div"));E.addClass(I,"ygtv-label-editor");L=H.buttonsContainer=I.appendChild(document.createElement("div"));E.addClass(L,"ygtv-button-container");J=L.appendChild(document.createElement("button"));
+E.addClass(J,"ygtvok");J.innerHTML=" ";J=L.appendChild(document.createElement("button"));E.addClass(J,"ygtvcancel");J.innerHTML=" ";B.on(L,"click",function(O){var P=B.getTarget(O);var N=D.editorData.node;if(E.hasClass(P,"ygtvok")){B.stopEvent(O);this._closeEditor(true);}if(E.hasClass(P,"ygtvcancel")){B.stopEvent(O);this._closeEditor(false);}},this,true);H.inputContainer=I.appendChild(document.createElement("div"));E.addClass(H.inputContainer,"ygtv-input");B.on(I,"keydown",function(P){var O=D.editorData,N=YAHOO.util.KeyListener.KEY;switch(P.keyCode){case N.ENTER:B.stopEvent(P);if(O.saveOnEnter){this._closeEditor(true);}break;case N.ESCAPE:B.stopEvent(P);this._closeEditor(false);break;}},this,true);}else{I=H.editorPanel;}H.node=M;if(H.nodeType){E.removeClass(I,"ygtv-edit-"+H.nodeType);}E.addClass(I," ygtv-edit-"+M._type);K=E.getXY(M.getContentEl());E.setStyle(I,"left",K[0]+"px");E.setStyle(I,"top",K[1]+"px");E.setStyle(I,"display","block");I.focus();M.fillEditorContainer(H);return true;}};C.onEventEditNode=function(H){if(H instanceof YAHOO.widget.Node){H.editNode();}else{if(H.node instanceof YAHOO.widget.Node){H.node.editNode();}}};C._closeEditor=function(J){var H=D.editorData,I=H.node,K=true;if(J){K=H.node.saveEditorValue(H)!==false;}else{this.fireEvent("editorCancelEvent",I);}if(K){E.setStyle(H.editorPanel,"display","none");H.active=false;I.focus();}};C._destroyEditor=function(){var H=D.editorData;if(H&&H.nodeType&&(!H.active||H.whoHasIt===this)){B.removeListener(H.editorPanel,"keydown");B.removeListener(H.buttonContainer,"click");H.node.destroyEditorContents(H);document.body.removeChild(H.editorPanel);H.nodeType=H.editorPanel=H.inputContainer=H.buttonsContainer=H.whoHasIt=H.node=null;H.active=false;}};var G=YAHOO.widget.Node.prototype;G.editable=false;G.editNode=function(){this.tree._nodeEditing(this);};G.fillEditorContainer=null;G.destroyEditorContents=function(H){B.purgeElement(H.inputContainer,true);H.inputContainer.innerHTML="";};G.saveEditorValue=function(H){var J=H.node,K,I=J.tree.validator;K=this.getEditorValue(H);if(F.isFunction(I)){K=I(K,H.oldValue,J);if(F.isUndefined(K)){return false;}}if(this.tree.fireEvent("editorSaveEvent",{newValue:K,oldValue:H.oldValue,node:J})!==false){this.displayEditedValue(K,H);}};G.getEditorValue=function(H){};G.displayEditedValue=function(I,H){};var A=YAHOO.widget.TextNode.prototype;A.fillEditorContainer=function(I){var H;if(I.nodeType!=this._type){I.nodeType=this._type;I.saveOnEnter=true;I.node.destroyEditorContents(I);I.inputElement=H=I.inputContainer.appendChild(document.createElement("input"));}else{H=I.inputElement;}I.oldValue=this.label;H.value=this.label;H.focus();H.select();};A.getEditorValue=function(H){return H.inputElement.value;};A.displayEditedValue=function(J,H){var I=H.node;I.label=J;I.getLabelEl().innerHTML=J;};A.destroyEditorContents=function(H){H.inputContainer.innerHTML="";};})();YAHOO.widget.TVAnim=function(){return{FADE_IN:"TVFadeIn",FADE_OUT:"TVFadeOut",getAnim:function(B,A,C){if(YAHOO.widget[B]){return new YAHOO.widget[B](A,C);}else{return null;}},isValid:function(A){return(YAHOO.widget[A]);}};}();YAHOO.widget.TVFadeIn=function(A,B){this.el=A;this.callback=B;};YAHOO.widget.TVFadeIn.prototype={animate:function(){var D=this;var C=this.el.style;C.opacity=0.1;C.filter="alpha(opacity=10)";C.display="";var B=0.4;var A=new YAHOO.util.Anim(this.el,{opacity:{from:0.1,to:1,unit:""}},B);A.onComplete.subscribe(function(){D.onComplete();});A.animate();},onComplete:function(){this.callback();},toString:function(){return"TVFadeIn";}};YAHOO.widget.TVFadeOut=function(A,B){this.el=A;this.callback=B;};YAHOO.widget.TVFadeOut.prototype={animate:function(){var C=this;var B=0.4;var A=new YAHOO.util.Anim(this.el,{opacity:{from:1,to:0.1,unit:""}},B);A.onComplete.subscribe(function(){C.onComplete();});A.animate();},onComplete:function(){var A=this.el.style;A.display="none";A.opacity=1;A.filter="alpha(opacity=100)";this.callback();},toString:function(){return"TVFadeOut";}};YAHOO.register("treeview",YAHOO.widget.TreeView,{version:"2.8.2r1",build:"7"}); \ No newline at end of file
diff --git a/js/yui/uploader/uploader-min.js b/js/yui/uploader/uploader-min.js
new file mode 100644
index 000000000..24bcc36b0
--- /dev/null
+++ b/js/yui/uploader/uploader-min.js
@@ -0,0 +1,15 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+/*
+ * SWFObject v1.5: Flash Player detection and embed - http://blog.deconcept.com/swfobject/
+ *
+ * SWFObject is (c) 2007 Geoff Stearns and is released under the MIT License:
+ * http://www.opensource.org/licenses/mit-license.php
+ * @namespace YAHOO
+ */
+YAHOO.namespace("deconcept");YAHOO.deconcept=YAHOO.deconcept||{};if(typeof YAHOO.deconcept.util=="undefined"||!YAHOO.deconcept.util){YAHOO.deconcept.util={};}if(typeof YAHOO.deconcept.SWFObjectUtil=="undefined"||!YAHOO.deconcept.SWFObjectUtil){YAHOO.deconcept.SWFObjectUtil={};}YAHOO.deconcept.SWFObject=function(E,C,K,F,H,J,L,G,A,D){if(!document.getElementById){return;}this.DETECT_KEY=D?D:"detectflash";this.skipDetect=YAHOO.deconcept.util.getRequestParameter(this.DETECT_KEY);this.params={};this.variables={};this.attributes=[];if(E){this.setAttribute("swf",E);}if(C){this.setAttribute("id",C);}if(K){this.setAttribute("width",K);}if(F){this.setAttribute("height",F);}if(H){this.setAttribute("version",new YAHOO.deconcept.PlayerVersion(H.toString().split(".")));}this.installedVer=YAHOO.deconcept.SWFObjectUtil.getPlayerVersion();if(!window.opera&&document.all&&this.installedVer.major>7){YAHOO.deconcept.SWFObject.doPrepUnload=true;}if(J){this.addParam("bgcolor",J);}var B=L?L:"high";this.addParam("quality",B);this.setAttribute("useExpressInstall",false);this.setAttribute("doExpressInstall",false);var I=(G)?G:window.location;this.setAttribute("xiRedirectUrl",I);this.setAttribute("redirectUrl","");if(A){this.setAttribute("redirectUrl",A);}};YAHOO.deconcept.SWFObject.prototype={useExpressInstall:function(A){this.xiSWFPath=!A?"expressinstall.swf":A;this.setAttribute("useExpressInstall",true);},setAttribute:function(A,B){this.attributes[A]=B;},getAttribute:function(A){return this.attributes[A];},addParam:function(A,B){this.params[A]=B;},getParams:function(){return this.params;},addVariable:function(A,B){this.variables[A]=B;},getVariable:function(A){return this.variables[A];},getVariables:function(){return this.variables;},getVariablePairs:function(){var A=[];var B;var C=this.getVariables();for(B in C){if(C.hasOwnProperty(B)){A[A.length]=B+"="+C[B];}}return A;},getSWFHTML:function(){var D="";var C={};var A="";var B="";if(navigator.plugins&&navigator.mimeTypes&&navigator.mimeTypes.length){if(this.getAttribute("doExpressInstall")){this.addVariable("MMplayerType","PlugIn");this.setAttribute("swf",this.xiSWFPath);}D='<embed type="application/x-shockwave-flash" src="'+this.getAttribute("swf")+'" width="'+this.getAttribute("width")+'" height="'+this.getAttribute("height")+'" style="'+this.getAttribute("style")+'"';D+=' id="'+this.getAttribute("id")+'" name="'+this.getAttribute("id")+'" ';C=this.getParams();for(A in C){if(C.hasOwnProperty(A)){D+=[A]+'="'+C[A]+'" ';}}B=this.getVariablePairs().join("&");if(B.length>0){D+='flashvars="'+B+'"';}D+="/>";}else{if(this.getAttribute("doExpressInstall")){this.addVariable("MMplayerType","ActiveX");this.setAttribute("swf",this.xiSWFPath);}D='<object id="'+this.getAttribute("id")+'" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="'+this.getAttribute("width")+'" height="'+this.getAttribute("height")+'" style="'+this.getAttribute("style")+'">';D+='<param name="movie" value="'+this.getAttribute("swf")+'" />';C=this.getParams();for(A in C){if(C.hasOwnProperty(A)){D+='<param name="'+A+'" value="'+C[A]+'" />';}}B=this.getVariablePairs().join("&");if(B.length>0){D+='<param name="flashvars" value="'+B+'" />';}D+="</object>";}return D;},write:function(A){if(this.getAttribute("useExpressInstall")){var B=new YAHOO.deconcept.PlayerVersion([6,0,65]);if(this.installedVer.versionIsValid(B)&&!this.installedVer.versionIsValid(this.getAttribute("version"))){this.setAttribute("doExpressInstall",true);this.addVariable("MMredirectURL",escape(this.getAttribute("xiRedirectUrl")));document.title=document.title.slice(0,47)+" - Flash Player Installation";this.addVariable("MMdoctitle",document.title);}}if(this.skipDetect||this.getAttribute("doExpressInstall")||this.installedVer.versionIsValid(this.getAttribute("version"))){var C=(typeof A=="string")?document.getElementById(A):A;C.innerHTML=this.getSWFHTML();return true;}else{if(this.getAttribute("redirectUrl")!==""){document.location.replace(this.getAttribute("redirectUrl"));}}return false;}};YAHOO.deconcept.SWFObjectUtil.getPlayerVersion=function(){var D=null;var C=new YAHOO.deconcept.PlayerVersion([0,0,0]);if(navigator.plugins&&navigator.mimeTypes.length){var A=navigator.plugins["Shockwave Flash"];if(A&&A.description){C=new YAHOO.deconcept.PlayerVersion(A.description.replace(/([a-zA-Z]|\s)+/,"").replace(/(\s+r|\s+b[0-9]+)/,".").split("."));}}else{if(navigator.userAgent&&navigator.userAgent.indexOf("Windows CE")>=0){var B=3;while(D){try{B++;D=new ActiveXObject("ShockwaveFlash.ShockwaveFlash."+B);C=new YAHOO.deconcept.PlayerVersion([B,0,0]);}catch(E){D=null;}}}else{try{D=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7");}catch(E){try{D=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6");C=new YAHOO.deconcept.PlayerVersion([6,0,21]);D.AllowScriptAccess="always";}catch(E){if(C.major==6){return C;}}try{D=new ActiveXObject("ShockwaveFlash.ShockwaveFlash");}catch(E){}}if(D!==null){C=new YAHOO.deconcept.PlayerVersion(D.GetVariable("$version").split(" ")[1].split(","));}}}return C;};YAHOO.deconcept.PlayerVersion=function(A){this.major=A[0]!==null?parseInt(A[0],0):0;this.minor=A[1]!==null?parseInt(A[1],0):0;this.rev=A[2]!==null?parseInt(A[2],0):0;};YAHOO.deconcept.PlayerVersion.prototype.versionIsValid=function(A){if(this.major<A.major){return false;}if(this.major>A.major){return true;}if(this.minor<A.minor){return false;}if(this.minor>A.minor){return true;}if(this.rev<A.rev){return false;}return true;};YAHOO.deconcept.util={getRequestParameter:function(D){var C=document.location.search||document.location.hash;if(D===null){return C;}if(C){var B=C.substring(1).split("&");for(var A=0;A<B.length;A++){if(B[A].substring(0,B[A].indexOf("="))==D){return B[A].substring((B[A].indexOf("=")+1));}}}return"";
+}};YAHOO.deconcept.SWFObjectUtil.cleanupSWFs=function(){var C=document.getElementsByTagName("OBJECT");for(var B=C.length-1;B>=0;B--){C[B].style.display="none";for(var A in C[B]){if(typeof C[B][A]=="function"){C[B][A]=function(){};}}}};if(YAHOO.deconcept.SWFObject.doPrepUnload){if(!YAHOO.deconcept.unloadSet){YAHOO.deconcept.SWFObjectUtil.prepUnload=function(){__flash_unloadHandler=function(){};__flash_savedUnloadHandler=function(){};window.attachEvent("onunload",YAHOO.deconcept.SWFObjectUtil.cleanupSWFs);};window.attachEvent("onbeforeunload",YAHOO.deconcept.SWFObjectUtil.prepUnload);YAHOO.deconcept.unloadSet=true;}}if(!document.getElementById&&document.all){document.getElementById=function(A){return document.all[A];};}YAHOO.widget.FlashAdapter=function(E,A,B,C){this._queue=this._queue||[];this._events=this._events||{};this._configs=this._configs||{};B=B||{};this._id=B.id=B.id||YAHOO.util.Dom.generateId(null,"yuigen");B.version=B.version||"9.0.45";B.backgroundColor=B.backgroundColor||"#ffffff";this._attributes=B;this._swfURL=E;this._containerID=A;this._embedSWF(this._swfURL,this._containerID,B.id,B.version,B.backgroundColor,B.expressInstall,B.wmode,C);try{this.createEvent("contentReady");}catch(D){}};YAHOO.widget.FlashAdapter.owners=YAHOO.widget.FlashAdapter.owners||{};YAHOO.extend(YAHOO.widget.FlashAdapter,YAHOO.util.AttributeProvider,{_swfURL:null,_containerID:null,_swf:null,_id:null,_initialized:false,_attributes:null,toString:function(){return"FlashAdapter "+this._id;},destroy:function(){if(this._swf){var B=YAHOO.util.Dom.get(this._containerID);B.removeChild(this._swf);}var A=this._id;for(var C in this){if(YAHOO.lang.hasOwnProperty(this,C)){this[C]=null;}}},_embedSWF:function(J,I,E,C,F,G,B,H){var D=new YAHOO.deconcept.SWFObject(J,E,"100%","100%",C,F);if(G){D.useExpressInstall(G);}D.addParam("allowScriptAccess","always");if(B){D.addParam("wmode",B);}D.addParam("menu","false");D.addVariable("allowedDomain",document.location.hostname);D.addVariable("YUISwfId",E);D.addVariable("YUIBridgeCallback","YAHOO.widget.FlashAdapter.eventHandler");if(H){D.addVariable("buttonSkin",H);}var A=YAHOO.util.Dom.get(I);var K=D.write(A);if(K){this._swf=YAHOO.util.Dom.get(E);YAHOO.widget.FlashAdapter.owners[E]=this;}else{}},_eventHandler:function(B){var A=B.type;switch(A){case"swfReady":this._loadHandler();return;case"log":return;}this.fireEvent(A,B);},_loadHandler:function(){this._initialized=false;this._initAttributes(this._attributes);this.setAttributes(this._attributes,true);this._initialized=true;this.fireEvent("contentReady");},set:function(A,B){this._attributes[A]=B;YAHOO.widget.FlashAdapter.superclass.set.call(this,A,B);},_initAttributes:function(A){this.getAttributeConfig("altText",{method:this._getAltText});this.setAttributeConfig("altText",{method:this._setAltText});this.getAttributeConfig("swfURL",{method:this._getSWFURL});},_getSWFURL:function(){return this._swfURL;},_getAltText:function(){return this._swf.getAltText();},_setAltText:function(A){return this._swf.setAltText(A);}});YAHOO.widget.FlashAdapter.eventHandler=function(A,B){if(!YAHOO.widget.FlashAdapter.owners[A]){setTimeout(function(){YAHOO.widget.FlashAdapter.eventHandler(A,B);},0);}else{YAHOO.widget.FlashAdapter.owners[A]._eventHandler(B);}};YAHOO.widget.FlashAdapter.proxyFunctionCount=0;YAHOO.widget.FlashAdapter.createProxyFunction=function(B){var A=YAHOO.widget.FlashAdapter.proxyFunctionCount;YAHOO.widget.FlashAdapter["proxyFunction"+A]=function(){return B.apply(null,arguments);};YAHOO.widget.FlashAdapter.proxyFunctionCount++;return"YAHOO.widget.FlashAdapter.proxyFunction"+A.toString();};YAHOO.widget.FlashAdapter.removeProxyFunction=function(A){if(!A||A.indexOf("YAHOO.widget.FlashAdapter.proxyFunction")<0){return;}A=A.substr(26);YAHOO.widget.FlashAdapter[A]=null;};YAHOO.widget.Uploader=function(A,B,D){var C="window";if(!(B)||(B&&D)){C="transparent";}YAHOO.widget.Uploader.superclass.constructor.call(this,YAHOO.widget.Uploader.SWFURL,A,{wmode:C},B);this.createEvent("mouseDown");this.createEvent("mouseUp");this.createEvent("rollOver");this.createEvent("rollOut");this.createEvent("click");this.createEvent("fileSelect");this.createEvent("uploadStart");this.createEvent("uploadProgress");this.createEvent("uploadCancel");this.createEvent("uploadComplete");this.createEvent("uploadCompleteData");this.createEvent("uploadError");};YAHOO.widget.Uploader.SWFURL="assets/uploader.swf";YAHOO.extend(YAHOO.widget.Uploader,YAHOO.widget.FlashAdapter,{upload:function(A,B,E,C,D){this._swf.upload(A,B,E,C,D);},uploadThese:function(B,A,E,C,D){this._swf.uploadThese(B,A,E,C,D);},uploadAll:function(A,D,B,C){this._swf.uploadAll(A,D,B,C);},cancel:function(A){this._swf.cancel(A);},clearFileList:function(){this._swf.clearFileList();},removeFile:function(A){this._swf.removeFile(A);},setAllowLogging:function(A){this._swf.setAllowLogging(A);},setSimUploadLimit:function(A){this._swf.setSimUploadLimit(A);},setAllowMultipleFiles:function(A){this._swf.setAllowMultipleFiles(A);},setFileFilters:function(A){this._swf.setFileFilters(A);},enable:function(){this._swf.enable();},disable:function(){this._swf.disable();}});YAHOO.register("uploader",YAHOO.widget.Uploader,{version:"2.8.2r1",build:"7"}); \ No newline at end of file
diff --git a/js/yui/yahoo-dom-event/yahoo-dom-event.js b/js/yui/yahoo-dom-event/yahoo-dom-event.js
new file mode 100644
index 000000000..8ab7c8685
--- /dev/null
+++ b/js/yui/yahoo-dom-event/yahoo-dom-event.js
@@ -0,0 +1,14 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+if(typeof YAHOO=="undefined"||!YAHOO){var YAHOO={};}YAHOO.namespace=function(){var A=arguments,E=null,C,B,D;for(C=0;C<A.length;C=C+1){D=(""+A[C]).split(".");E=YAHOO;for(B=(D[0]=="YAHOO")?1:0;B<D.length;B=B+1){E[D[B]]=E[D[B]]||{};E=E[D[B]];}}return E;};YAHOO.log=function(D,A,C){var B=YAHOO.widget.Logger;if(B&&B.log){return B.log(D,A,C);}else{return false;}};YAHOO.register=function(A,E,D){var I=YAHOO.env.modules,B,H,G,F,C;if(!I[A]){I[A]={versions:[],builds:[]};}B=I[A];H=D.version;G=D.build;F=YAHOO.env.listeners;B.name=A;B.version=H;B.build=G;B.versions.push(H);B.builds.push(G);B.mainClass=E;for(C=0;C<F.length;C=C+1){F[C](B);}if(E){E.VERSION=H;E.BUILD=G;}else{YAHOO.log("mainClass is undefined for module "+A,"warn");}};YAHOO.env=YAHOO.env||{modules:[],listeners:[]};YAHOO.env.getVersion=function(A){return YAHOO.env.modules[A]||null;};YAHOO.env.ua=function(){var D=function(H){var I=0;return parseFloat(H.replace(/\./g,function(){return(I++==1)?"":".";}));},G=navigator,F={ie:0,opera:0,gecko:0,webkit:0,mobile:null,air:0,caja:G.cajaVersion,secure:false,os:null},C=navigator&&navigator.userAgent,E=window&&window.location,B=E&&E.href,A;F.secure=B&&(B.toLowerCase().indexOf("https")===0);if(C){if((/windows|win32/i).test(C)){F.os="windows";}else{if((/macintosh/i).test(C)){F.os="macintosh";}}if((/KHTML/).test(C)){F.webkit=1;}A=C.match(/AppleWebKit\/([^\s]*)/);if(A&&A[1]){F.webkit=D(A[1]);if(/ Mobile\//.test(C)){F.mobile="Apple";}else{A=C.match(/NokiaN[^\/]*/);if(A){F.mobile=A[0];}}A=C.match(/AdobeAIR\/([^\s]*)/);if(A){F.air=A[0];}}if(!F.webkit){A=C.match(/Opera[\s\/]([^\s]*)/);if(A&&A[1]){F.opera=D(A[1]);A=C.match(/Opera Mini[^;]*/);if(A){F.mobile=A[0];}}else{A=C.match(/MSIE\s([^;]*)/);if(A&&A[1]){F.ie=D(A[1]);}else{A=C.match(/Gecko\/([^\s]*)/);if(A){F.gecko=1;A=C.match(/rv:([^\s\)]*)/);if(A&&A[1]){F.gecko=D(A[1]);}}}}}}return F;}();(function(){YAHOO.namespace("util","widget","example");if("undefined"!==typeof YAHOO_config){var B=YAHOO_config.listener,A=YAHOO.env.listeners,D=true,C;if(B){for(C=0;C<A.length;C++){if(A[C]==B){D=false;break;}}if(D){A.push(B);}}}})();YAHOO.lang=YAHOO.lang||{};(function(){var B=YAHOO.lang,A=Object.prototype,H="[object Array]",C="[object Function]",G="[object Object]",E=[],F=["toString","valueOf"],D={isArray:function(I){return A.toString.apply(I)===H;},isBoolean:function(I){return typeof I==="boolean";},isFunction:function(I){return(typeof I==="function")||A.toString.apply(I)===C;},isNull:function(I){return I===null;},isNumber:function(I){return typeof I==="number"&&isFinite(I);},isObject:function(I){return(I&&(typeof I==="object"||B.isFunction(I)))||false;},isString:function(I){return typeof I==="string";},isUndefined:function(I){return typeof I==="undefined";},_IEEnumFix:(YAHOO.env.ua.ie)?function(K,J){var I,M,L;for(I=0;I<F.length;I=I+1){M=F[I];L=J[M];if(B.isFunction(L)&&L!=A[M]){K[M]=L;}}}:function(){},extend:function(L,M,K){if(!M||!L){throw new Error("extend failed, please check that "+"all dependencies are included.");}var J=function(){},I;J.prototype=M.prototype;L.prototype=new J();L.prototype.constructor=L;L.superclass=M.prototype;if(M.prototype.constructor==A.constructor){M.prototype.constructor=M;}if(K){for(I in K){if(B.hasOwnProperty(K,I)){L.prototype[I]=K[I];}}B._IEEnumFix(L.prototype,K);}},augmentObject:function(M,L){if(!L||!M){throw new Error("Absorb failed, verify dependencies.");}var I=arguments,K,N,J=I[2];if(J&&J!==true){for(K=2;K<I.length;K=K+1){M[I[K]]=L[I[K]];}}else{for(N in L){if(J||!(N in M)){M[N]=L[N];}}B._IEEnumFix(M,L);}},augmentProto:function(L,K){if(!K||!L){throw new Error("Augment failed, verify dependencies.");}var I=[L.prototype,K.prototype],J;for(J=2;J<arguments.length;J=J+1){I.push(arguments[J]);}B.augmentObject.apply(this,I);},dump:function(I,N){var K,M,P=[],Q="{...}",J="f(){...}",O=", ",L=" => ";if(!B.isObject(I)){return I+"";}else{if(I instanceof Date||("nodeType" in I&&"tagName" in I)){return I;}else{if(B.isFunction(I)){return J;}}}N=(B.isNumber(N))?N:3;if(B.isArray(I)){P.push("[");for(K=0,M=I.length;K<M;K=K+1){if(B.isObject(I[K])){P.push((N>0)?B.dump(I[K],N-1):Q);}else{P.push(I[K]);}P.push(O);}if(P.length>1){P.pop();}P.push("]");}else{P.push("{");for(K in I){if(B.hasOwnProperty(I,K)){P.push(K+L);if(B.isObject(I[K])){P.push((N>0)?B.dump(I[K],N-1):Q);}else{P.push(I[K]);}P.push(O);}}if(P.length>1){P.pop();}P.push("}");}return P.join("");},substitute:function(Y,J,R){var N,M,L,U,V,X,T=[],K,O="dump",S=" ",I="{",W="}",Q,P;for(;;){N=Y.lastIndexOf(I);if(N<0){break;}M=Y.indexOf(W,N);if(N+1>=M){break;}K=Y.substring(N+1,M);U=K;X=null;L=U.indexOf(S);if(L>-1){X=U.substring(L+1);U=U.substring(0,L);}V=J[U];if(R){V=R(U,V,X);}if(B.isObject(V)){if(B.isArray(V)){V=B.dump(V,parseInt(X,10));}else{X=X||"";Q=X.indexOf(O);if(Q>-1){X=X.substring(4);}P=V.toString();if(P===G||Q>-1){V=B.dump(V,parseInt(X,10));}else{V=P;}}}else{if(!B.isString(V)&&!B.isNumber(V)){V="~-"+T.length+"-~";T[T.length]=K;}}Y=Y.substring(0,N)+V+Y.substring(M+1);}for(N=T.length-1;N>=0;N=N-1){Y=Y.replace(new RegExp("~-"+N+"-~"),"{"+T[N]+"}","g");}return Y;},trim:function(I){try{return I.replace(/^\s+|\s+$/g,"");}catch(J){return I;}},merge:function(){var L={},J=arguments,I=J.length,K;for(K=0;K<I;K=K+1){B.augmentObject(L,J[K],true);}return L;},later:function(P,J,Q,L,M){P=P||0;J=J||{};var K=Q,O=L,N,I;if(B.isString(Q)){K=J[Q];}if(!K){throw new TypeError("method undefined");}if(O&&!B.isArray(O)){O=[L];}N=function(){K.apply(J,O||E);};I=(M)?setInterval(N,P):setTimeout(N,P);return{interval:M,cancel:function(){if(this.interval){clearInterval(I);}else{clearTimeout(I);}}};},isValue:function(I){return(B.isObject(I)||B.isString(I)||B.isNumber(I)||B.isBoolean(I));}};B.hasOwnProperty=(A.hasOwnProperty)?function(I,J){return I&&I.hasOwnProperty(J);}:function(I,J){return !B.isUndefined(I[J])&&I.constructor.prototype[J]!==I[J];};D.augmentObject(B,D,true);YAHOO.util.Lang=B;B.augment=B.augmentProto;YAHOO.augment=B.augmentProto;YAHOO.extend=B.extend;})();YAHOO.register("yahoo",YAHOO,{version:"2.8.2r1",build:"7"});
+(function(){YAHOO.env._id_counter=YAHOO.env._id_counter||0;var E=YAHOO.util,L=YAHOO.lang,m=YAHOO.env.ua,A=YAHOO.lang.trim,d={},h={},N=/^t(?:able|d|h)$/i,X=/color$/i,K=window.document,W=K.documentElement,e="ownerDocument",n="defaultView",v="documentElement",t="compatMode",b="offsetLeft",P="offsetTop",u="offsetParent",Z="parentNode",l="nodeType",C="tagName",O="scrollLeft",i="scrollTop",Q="getBoundingClientRect",w="getComputedStyle",a="currentStyle",M="CSS1Compat",c="BackCompat",g="class",F="className",J="",B=" ",s="(?:^|\\s)",k="(?= |$)",U="g",p="position",f="fixed",V="relative",j="left",o="top",r="medium",q="borderLeftWidth",R="borderTopWidth",D=m.opera,I=m.webkit,H=m.gecko,T=m.ie;E.Dom={CUSTOM_ATTRIBUTES:(!W.hasAttribute)?{"for":"htmlFor","class":F}:{"htmlFor":"for","className":g},DOT_ATTRIBUTES:{},get:function(z){var AB,x,AA,y,Y,G;if(z){if(z[l]||z.item){return z;}if(typeof z==="string"){AB=z;z=K.getElementById(z);G=(z)?z.attributes:null;if(z&&G&&G.id&&G.id.value===AB){return z;}else{if(z&&K.all){z=null;x=K.all[AB];for(y=0,Y=x.length;y<Y;++y){if(x[y].id===AB){return x[y];}}}}return z;}if(YAHOO.util.Element&&z instanceof YAHOO.util.Element){z=z.get("element");}if("length" in z){AA=[];for(y=0,Y=z.length;y<Y;++y){AA[AA.length]=E.Dom.get(z[y]);}return AA;}return z;}return null;},getComputedStyle:function(G,Y){if(window[w]){return G[e][n][w](G,null)[Y];}else{if(G[a]){return E.Dom.IE_ComputedStyle.get(G,Y);}}},getStyle:function(G,Y){return E.Dom.batch(G,E.Dom._getStyle,Y);},_getStyle:function(){if(window[w]){return function(G,y){y=(y==="float")?y="cssFloat":E.Dom._toCamel(y);var x=G.style[y],Y;if(!x){Y=G[e][n][w](G,null);if(Y){x=Y[y];}}return x;};}else{if(W[a]){return function(G,y){var x;switch(y){case"opacity":x=100;try{x=G.filters["DXImageTransform.Microsoft.Alpha"].opacity;}catch(z){try{x=G.filters("alpha").opacity;}catch(Y){}}return x/100;case"float":y="styleFloat";default:y=E.Dom._toCamel(y);x=G[a]?G[a][y]:null;return(G.style[y]||x);}};}}}(),setStyle:function(G,Y,x){E.Dom.batch(G,E.Dom._setStyle,{prop:Y,val:x});},_setStyle:function(){if(T){return function(Y,G){var x=E.Dom._toCamel(G.prop),y=G.val;if(Y){switch(x){case"opacity":if(L.isString(Y.style.filter)){Y.style.filter="alpha(opacity="+y*100+")";if(!Y[a]||!Y[a].hasLayout){Y.style.zoom=1;}}break;case"float":x="styleFloat";default:Y.style[x]=y;}}else{}};}else{return function(Y,G){var x=E.Dom._toCamel(G.prop),y=G.val;if(Y){if(x=="float"){x="cssFloat";}Y.style[x]=y;}else{}};}}(),getXY:function(G){return E.Dom.batch(G,E.Dom._getXY);},_canPosition:function(G){return(E.Dom._getStyle(G,"display")!=="none"&&E.Dom._inDoc(G));},_getXY:function(){if(K[v][Q]){return function(y){var z,Y,AA,AF,AE,AD,AC,G,x,AB=Math.floor,AG=false;if(E.Dom._canPosition(y)){AA=y[Q]();AF=y[e];z=E.Dom.getDocumentScrollLeft(AF);Y=E.Dom.getDocumentScrollTop(AF);AG=[AB(AA[j]),AB(AA[o])];if(T&&m.ie<8){AE=2;AD=2;AC=AF[t];if(m.ie===6){if(AC!==c){AE=0;AD=0;}}if((AC===c)){G=S(AF[v],q);x=S(AF[v],R);if(G!==r){AE=parseInt(G,10);}if(x!==r){AD=parseInt(x,10);}}AG[0]-=AE;AG[1]-=AD;}if((Y||z)){AG[0]+=z;AG[1]+=Y;}AG[0]=AB(AG[0]);AG[1]=AB(AG[1]);}else{}return AG;};}else{return function(y){var x,Y,AA,AB,AC,z=false,G=y;if(E.Dom._canPosition(y)){z=[y[b],y[P]];x=E.Dom.getDocumentScrollLeft(y[e]);Y=E.Dom.getDocumentScrollTop(y[e]);AC=((H||m.webkit>519)?true:false);while((G=G[u])){z[0]+=G[b];z[1]+=G[P];if(AC){z=E.Dom._calcBorders(G,z);}}if(E.Dom._getStyle(y,p)!==f){G=y;while((G=G[Z])&&G[C]){AA=G[i];AB=G[O];if(H&&(E.Dom._getStyle(G,"overflow")!=="visible")){z=E.Dom._calcBorders(G,z);}if(AA||AB){z[0]-=AB;z[1]-=AA;}}z[0]+=x;z[1]+=Y;}else{if(D){z[0]-=x;z[1]-=Y;}else{if(I||H){z[0]+=x;z[1]+=Y;}}}z[0]=Math.floor(z[0]);z[1]=Math.floor(z[1]);}else{}return z;};}}(),getX:function(G){var Y=function(x){return E.Dom.getXY(x)[0];};return E.Dom.batch(G,Y,E.Dom,true);},getY:function(G){var Y=function(x){return E.Dom.getXY(x)[1];};return E.Dom.batch(G,Y,E.Dom,true);},setXY:function(G,x,Y){E.Dom.batch(G,E.Dom._setXY,{pos:x,noRetry:Y});},_setXY:function(G,z){var AA=E.Dom._getStyle(G,p),y=E.Dom.setStyle,AD=z.pos,Y=z.noRetry,AB=[parseInt(E.Dom.getComputedStyle(G,j),10),parseInt(E.Dom.getComputedStyle(G,o),10)],AC,x;if(AA=="static"){AA=V;y(G,p,AA);}AC=E.Dom._getXY(G);if(!AD||AC===false){return false;}if(isNaN(AB[0])){AB[0]=(AA==V)?0:G[b];}if(isNaN(AB[1])){AB[1]=(AA==V)?0:G[P];}if(AD[0]!==null){y(G,j,AD[0]-AC[0]+AB[0]+"px");}if(AD[1]!==null){y(G,o,AD[1]-AC[1]+AB[1]+"px");}if(!Y){x=E.Dom._getXY(G);if((AD[0]!==null&&x[0]!=AD[0])||(AD[1]!==null&&x[1]!=AD[1])){E.Dom._setXY(G,{pos:AD,noRetry:true});}}},setX:function(Y,G){E.Dom.setXY(Y,[G,null]);},setY:function(G,Y){E.Dom.setXY(G,[null,Y]);},getRegion:function(G){var Y=function(x){var y=false;if(E.Dom._canPosition(x)){y=E.Region.getRegion(x);}else{}return y;};return E.Dom.batch(G,Y,E.Dom,true);},getClientWidth:function(){return E.Dom.getViewportWidth();},getClientHeight:function(){return E.Dom.getViewportHeight();},getElementsByClassName:function(AB,AF,AC,AE,x,AD){AF=AF||"*";AC=(AC)?E.Dom.get(AC):null||K;if(!AC){return[];}var Y=[],G=AC.getElementsByTagName(AF),z=E.Dom.hasClass;for(var y=0,AA=G.length;y<AA;++y){if(z(G[y],AB)){Y[Y.length]=G[y];}}if(AE){E.Dom.batch(Y,AE,x,AD);}return Y;},hasClass:function(Y,G){return E.Dom.batch(Y,E.Dom._hasClass,G);},_hasClass:function(x,Y){var G=false,y;if(x&&Y){y=E.Dom._getAttribute(x,F)||J;if(Y.exec){G=Y.test(y);}else{G=Y&&(B+y+B).indexOf(B+Y+B)>-1;}}else{}return G;},addClass:function(Y,G){return E.Dom.batch(Y,E.Dom._addClass,G);},_addClass:function(x,Y){var G=false,y;if(x&&Y){y=E.Dom._getAttribute(x,F)||J;if(!E.Dom._hasClass(x,Y)){E.Dom.setAttribute(x,F,A(y+B+Y));G=true;}}else{}return G;},removeClass:function(Y,G){return E.Dom.batch(Y,E.Dom._removeClass,G);},_removeClass:function(y,x){var Y=false,AA,z,G;if(y&&x){AA=E.Dom._getAttribute(y,F)||J;E.Dom.setAttribute(y,F,AA.replace(E.Dom._getClassRegex(x),J));z=E.Dom._getAttribute(y,F);if(AA!==z){E.Dom.setAttribute(y,F,A(z));Y=true;if(E.Dom._getAttribute(y,F)===""){G=(y.hasAttribute&&y.hasAttribute(g))?g:F;
+y.removeAttribute(G);}}}else{}return Y;},replaceClass:function(x,Y,G){return E.Dom.batch(x,E.Dom._replaceClass,{from:Y,to:G});},_replaceClass:function(y,x){var Y,AB,AA,G=false,z;if(y&&x){AB=x.from;AA=x.to;if(!AA){G=false;}else{if(!AB){G=E.Dom._addClass(y,x.to);}else{if(AB!==AA){z=E.Dom._getAttribute(y,F)||J;Y=(B+z.replace(E.Dom._getClassRegex(AB),B+AA)).split(E.Dom._getClassRegex(AA));Y.splice(1,0,B+AA);E.Dom.setAttribute(y,F,A(Y.join(J)));G=true;}}}}else{}return G;},generateId:function(G,x){x=x||"yui-gen";var Y=function(y){if(y&&y.id){return y.id;}var z=x+YAHOO.env._id_counter++;if(y){if(y[e]&&y[e].getElementById(z)){return E.Dom.generateId(y,z+x);}y.id=z;}return z;};return E.Dom.batch(G,Y,E.Dom,true)||Y.apply(E.Dom,arguments);},isAncestor:function(Y,x){Y=E.Dom.get(Y);x=E.Dom.get(x);var G=false;if((Y&&x)&&(Y[l]&&x[l])){if(Y.contains&&Y!==x){G=Y.contains(x);}else{if(Y.compareDocumentPosition){G=!!(Y.compareDocumentPosition(x)&16);}}}else{}return G;},inDocument:function(G,Y){return E.Dom._inDoc(E.Dom.get(G),Y);},_inDoc:function(Y,x){var G=false;if(Y&&Y[C]){x=x||Y[e];G=E.Dom.isAncestor(x[v],Y);}else{}return G;},getElementsBy:function(Y,AF,AB,AD,y,AC,AE){AF=AF||"*";AB=(AB)?E.Dom.get(AB):null||K;if(!AB){return[];}var x=[],G=AB.getElementsByTagName(AF);for(var z=0,AA=G.length;z<AA;++z){if(Y(G[z])){if(AE){x=G[z];break;}else{x[x.length]=G[z];}}}if(AD){E.Dom.batch(x,AD,y,AC);}return x;},getElementBy:function(x,G,Y){return E.Dom.getElementsBy(x,G,Y,null,null,null,true);},batch:function(x,AB,AA,z){var y=[],Y=(z)?AA:window;x=(x&&(x[C]||x.item))?x:E.Dom.get(x);if(x&&AB){if(x[C]||x.length===undefined){return AB.call(Y,x,AA);}for(var G=0;G<x.length;++G){y[y.length]=AB.call(Y,x[G],AA);}}else{return false;}return y;},getDocumentHeight:function(){var Y=(K[t]!=M||I)?K.body.scrollHeight:W.scrollHeight,G=Math.max(Y,E.Dom.getViewportHeight());return G;},getDocumentWidth:function(){var Y=(K[t]!=M||I)?K.body.scrollWidth:W.scrollWidth,G=Math.max(Y,E.Dom.getViewportWidth());return G;},getViewportHeight:function(){var G=self.innerHeight,Y=K[t];if((Y||T)&&!D){G=(Y==M)?W.clientHeight:K.body.clientHeight;}return G;},getViewportWidth:function(){var G=self.innerWidth,Y=K[t];if(Y||T){G=(Y==M)?W.clientWidth:K.body.clientWidth;}return G;},getAncestorBy:function(G,Y){while((G=G[Z])){if(E.Dom._testElement(G,Y)){return G;}}return null;},getAncestorByClassName:function(Y,G){Y=E.Dom.get(Y);if(!Y){return null;}var x=function(y){return E.Dom.hasClass(y,G);};return E.Dom.getAncestorBy(Y,x);},getAncestorByTagName:function(Y,G){Y=E.Dom.get(Y);if(!Y){return null;}var x=function(y){return y[C]&&y[C].toUpperCase()==G.toUpperCase();};return E.Dom.getAncestorBy(Y,x);},getPreviousSiblingBy:function(G,Y){while(G){G=G.previousSibling;if(E.Dom._testElement(G,Y)){return G;}}return null;},getPreviousSibling:function(G){G=E.Dom.get(G);if(!G){return null;}return E.Dom.getPreviousSiblingBy(G);},getNextSiblingBy:function(G,Y){while(G){G=G.nextSibling;if(E.Dom._testElement(G,Y)){return G;}}return null;},getNextSibling:function(G){G=E.Dom.get(G);if(!G){return null;}return E.Dom.getNextSiblingBy(G);},getFirstChildBy:function(G,x){var Y=(E.Dom._testElement(G.firstChild,x))?G.firstChild:null;return Y||E.Dom.getNextSiblingBy(G.firstChild,x);},getFirstChild:function(G,Y){G=E.Dom.get(G);if(!G){return null;}return E.Dom.getFirstChildBy(G);},getLastChildBy:function(G,x){if(!G){return null;}var Y=(E.Dom._testElement(G.lastChild,x))?G.lastChild:null;return Y||E.Dom.getPreviousSiblingBy(G.lastChild,x);},getLastChild:function(G){G=E.Dom.get(G);return E.Dom.getLastChildBy(G);},getChildrenBy:function(Y,y){var x=E.Dom.getFirstChildBy(Y,y),G=x?[x]:[];E.Dom.getNextSiblingBy(x,function(z){if(!y||y(z)){G[G.length]=z;}return false;});return G;},getChildren:function(G){G=E.Dom.get(G);if(!G){}return E.Dom.getChildrenBy(G);},getDocumentScrollLeft:function(G){G=G||K;return Math.max(G[v].scrollLeft,G.body.scrollLeft);},getDocumentScrollTop:function(G){G=G||K;return Math.max(G[v].scrollTop,G.body.scrollTop);},insertBefore:function(Y,G){Y=E.Dom.get(Y);G=E.Dom.get(G);if(!Y||!G||!G[Z]){return null;}return G[Z].insertBefore(Y,G);},insertAfter:function(Y,G){Y=E.Dom.get(Y);G=E.Dom.get(G);if(!Y||!G||!G[Z]){return null;}if(G.nextSibling){return G[Z].insertBefore(Y,G.nextSibling);}else{return G[Z].appendChild(Y);}},getClientRegion:function(){var x=E.Dom.getDocumentScrollTop(),Y=E.Dom.getDocumentScrollLeft(),y=E.Dom.getViewportWidth()+Y,G=E.Dom.getViewportHeight()+x;return new E.Region(x,y,G,Y);},setAttribute:function(Y,G,x){E.Dom.batch(Y,E.Dom._setAttribute,{attr:G,val:x});},_setAttribute:function(x,Y){var G=E.Dom._toCamel(Y.attr),y=Y.val;if(x&&x.setAttribute){if(E.Dom.DOT_ATTRIBUTES[G]){x[G]=y;}else{G=E.Dom.CUSTOM_ATTRIBUTES[G]||G;x.setAttribute(G,y);}}else{}},getAttribute:function(Y,G){return E.Dom.batch(Y,E.Dom._getAttribute,G);},_getAttribute:function(Y,G){var x;G=E.Dom.CUSTOM_ATTRIBUTES[G]||G;if(Y&&Y.getAttribute){x=Y.getAttribute(G,2);}else{}return x;},_toCamel:function(Y){var x=d;function G(y,z){return z.toUpperCase();}return x[Y]||(x[Y]=Y.indexOf("-")===-1?Y:Y.replace(/-([a-z])/gi,G));},_getClassRegex:function(Y){var G;if(Y!==undefined){if(Y.exec){G=Y;}else{G=h[Y];if(!G){Y=Y.replace(E.Dom._patterns.CLASS_RE_TOKENS,"\\$1");G=h[Y]=new RegExp(s+Y+k,U);}}}return G;},_patterns:{ROOT_TAG:/^body|html$/i,CLASS_RE_TOKENS:/([\.\(\)\^\$\*\+\?\|\[\]\{\}\\])/g},_testElement:function(G,Y){return G&&G[l]==1&&(!Y||Y(G));},_calcBorders:function(x,y){var Y=parseInt(E.Dom[w](x,R),10)||0,G=parseInt(E.Dom[w](x,q),10)||0;if(H){if(N.test(x[C])){Y=0;G=0;}}y[0]+=G;y[1]+=Y;return y;}};var S=E.Dom[w];if(m.opera){E.Dom[w]=function(Y,G){var x=S(Y,G);if(X.test(G)){x=E.Dom.Color.toRGB(x);}return x;};}if(m.webkit){E.Dom[w]=function(Y,G){var x=S(Y,G);if(x==="rgba(0, 0, 0, 0)"){x="transparent";}return x;};}if(m.ie&&m.ie>=8&&K.documentElement.hasAttribute){E.Dom.DOT_ATTRIBUTES.type=true;}})();YAHOO.util.Region=function(C,D,A,B){this.top=C;this.y=C;this[1]=C;this.right=D;this.bottom=A;this.left=B;this.x=B;this[0]=B;
+this.width=this.right-this.left;this.height=this.bottom-this.top;};YAHOO.util.Region.prototype.contains=function(A){return(A.left>=this.left&&A.right<=this.right&&A.top>=this.top&&A.bottom<=this.bottom);};YAHOO.util.Region.prototype.getArea=function(){return((this.bottom-this.top)*(this.right-this.left));};YAHOO.util.Region.prototype.intersect=function(E){var C=Math.max(this.top,E.top),D=Math.min(this.right,E.right),A=Math.min(this.bottom,E.bottom),B=Math.max(this.left,E.left);if(A>=C&&D>=B){return new YAHOO.util.Region(C,D,A,B);}else{return null;}};YAHOO.util.Region.prototype.union=function(E){var C=Math.min(this.top,E.top),D=Math.max(this.right,E.right),A=Math.max(this.bottom,E.bottom),B=Math.min(this.left,E.left);return new YAHOO.util.Region(C,D,A,B);};YAHOO.util.Region.prototype.toString=function(){return("Region {"+"top: "+this.top+", right: "+this.right+", bottom: "+this.bottom+", left: "+this.left+", height: "+this.height+", width: "+this.width+"}");};YAHOO.util.Region.getRegion=function(D){var F=YAHOO.util.Dom.getXY(D),C=F[1],E=F[0]+D.offsetWidth,A=F[1]+D.offsetHeight,B=F[0];return new YAHOO.util.Region(C,E,A,B);};YAHOO.util.Point=function(A,B){if(YAHOO.lang.isArray(A)){B=A[1];A=A[0];}YAHOO.util.Point.superclass.constructor.call(this,B,A,B,A);};YAHOO.extend(YAHOO.util.Point,YAHOO.util.Region);(function(){var B=YAHOO.util,A="clientTop",F="clientLeft",J="parentNode",K="right",W="hasLayout",I="px",U="opacity",L="auto",D="borderLeftWidth",G="borderTopWidth",P="borderRightWidth",V="borderBottomWidth",S="visible",Q="transparent",N="height",E="width",H="style",T="currentStyle",R=/^width|height$/,O=/^(\d[.\d]*)+(em|ex|px|gd|rem|vw|vh|vm|ch|mm|cm|in|pt|pc|deg|rad|ms|s|hz|khz|%){1}?/i,M={get:function(X,Z){var Y="",a=X[T][Z];if(Z===U){Y=B.Dom.getStyle(X,U);}else{if(!a||(a.indexOf&&a.indexOf(I)>-1)){Y=a;}else{if(B.Dom.IE_COMPUTED[Z]){Y=B.Dom.IE_COMPUTED[Z](X,Z);}else{if(O.test(a)){Y=B.Dom.IE.ComputedStyle.getPixel(X,Z);}else{Y=a;}}}}return Y;},getOffset:function(Z,e){var b=Z[T][e],X=e.charAt(0).toUpperCase()+e.substr(1),c="offset"+X,Y="pixel"+X,a="",d;if(b==L){d=Z[c];if(d===undefined){a=0;}a=d;if(R.test(e)){Z[H][e]=d;if(Z[c]>d){a=d-(Z[c]-d);}Z[H][e]=L;}}else{if(!Z[H][Y]&&!Z[H][e]){Z[H][e]=b;}a=Z[H][Y];}return a+I;},getBorderWidth:function(X,Z){var Y=null;if(!X[T][W]){X[H].zoom=1;}switch(Z){case G:Y=X[A];break;case V:Y=X.offsetHeight-X.clientHeight-X[A];break;case D:Y=X[F];break;case P:Y=X.offsetWidth-X.clientWidth-X[F];break;}return Y+I;},getPixel:function(Y,X){var a=null,b=Y[T][K],Z=Y[T][X];Y[H][K]=Z;a=Y[H].pixelRight;Y[H][K]=b;return a+I;},getMargin:function(Y,X){var Z;if(Y[T][X]==L){Z=0+I;}else{Z=B.Dom.IE.ComputedStyle.getPixel(Y,X);}return Z;},getVisibility:function(Y,X){var Z;while((Z=Y[T])&&Z[X]=="inherit"){Y=Y[J];}return(Z)?Z[X]:S;},getColor:function(Y,X){return B.Dom.Color.toRGB(Y[T][X])||Q;},getBorderColor:function(Y,X){var Z=Y[T],a=Z[X]||Z.color;return B.Dom.Color.toRGB(B.Dom.Color.toHex(a));}},C={};C.top=C.right=C.bottom=C.left=C[E]=C[N]=M.getOffset;C.color=M.getColor;C[G]=C[P]=C[V]=C[D]=M.getBorderWidth;C.marginTop=C.marginRight=C.marginBottom=C.marginLeft=M.getMargin;C.visibility=M.getVisibility;C.borderColor=C.borderTopColor=C.borderRightColor=C.borderBottomColor=C.borderLeftColor=M.getBorderColor;B.Dom.IE_COMPUTED=C;B.Dom.IE_ComputedStyle=M;})();(function(){var C="toString",A=parseInt,B=RegExp,D=YAHOO.util;D.Dom.Color={KEYWORDS:{black:"000",silver:"c0c0c0",gray:"808080",white:"fff",maroon:"800000",red:"f00",purple:"800080",fuchsia:"f0f",green:"008000",lime:"0f0",olive:"808000",yellow:"ff0",navy:"000080",blue:"00f",teal:"008080",aqua:"0ff"},re_RGB:/^rgb\(([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\)$/i,re_hex:/^#?([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/i,re_hex3:/([0-9A-F])/gi,toRGB:function(E){if(!D.Dom.Color.re_RGB.test(E)){E=D.Dom.Color.toHex(E);}if(D.Dom.Color.re_hex.exec(E)){E="rgb("+[A(B.$1,16),A(B.$2,16),A(B.$3,16)].join(", ")+")";}return E;},toHex:function(H){H=D.Dom.Color.KEYWORDS[H]||H;if(D.Dom.Color.re_RGB.exec(H)){var G=(B.$1.length===1)?"0"+B.$1:Number(B.$1),F=(B.$2.length===1)?"0"+B.$2:Number(B.$2),E=(B.$3.length===1)?"0"+B.$3:Number(B.$3);H=[G[C](16),F[C](16),E[C](16)].join("");}if(H.length<6){H=H.replace(D.Dom.Color.re_hex3,"$1$1");}if(H!=="transparent"&&H.indexOf("#")<0){H="#"+H;}return H.toLowerCase();}};}());YAHOO.register("dom",YAHOO.util.Dom,{version:"2.8.2r1",build:"7"});YAHOO.util.CustomEvent=function(D,C,B,A,E){this.type=D;this.scope=C||window;this.silent=B;this.fireOnce=E;this.fired=false;this.firedWith=null;this.signature=A||YAHOO.util.CustomEvent.LIST;this.subscribers=[];if(!this.silent){}var F="_YUICEOnSubscribe";if(D!==F){this.subscribeEvent=new YAHOO.util.CustomEvent(F,this,true);}this.lastError=null;};YAHOO.util.CustomEvent.LIST=0;YAHOO.util.CustomEvent.FLAT=1;YAHOO.util.CustomEvent.prototype={subscribe:function(B,C,D){if(!B){throw new Error("Invalid callback for subscriber to '"+this.type+"'");}if(this.subscribeEvent){this.subscribeEvent.fire(B,C,D);}var A=new YAHOO.util.Subscriber(B,C,D);if(this.fireOnce&&this.fired){this.notify(A,this.firedWith);}else{this.subscribers.push(A);}},unsubscribe:function(D,F){if(!D){return this.unsubscribeAll();}var E=false;for(var B=0,A=this.subscribers.length;B<A;++B){var C=this.subscribers[B];if(C&&C.contains(D,F)){this._delete(B);E=true;}}return E;},fire:function(){this.lastError=null;var H=[],A=this.subscribers.length;var D=[].slice.call(arguments,0),C=true,F,B=false;if(this.fireOnce){if(this.fired){return true;}else{this.firedWith=D;}}this.fired=true;if(!A&&this.silent){return true;}if(!this.silent){}var E=this.subscribers.slice();for(F=0;F<A;++F){var G=E[F];if(!G){B=true;}else{C=this.notify(G,D);if(false===C){if(!this.silent){}break;}}}return(C!==false);},notify:function(F,C){var B,H=null,E=F.getScope(this.scope),A=YAHOO.util.Event.throwErrors;if(!this.silent){}if(this.signature==YAHOO.util.CustomEvent.FLAT){if(C.length>0){H=C[0];}try{B=F.fn.call(E,H,F.obj);}catch(G){this.lastError=G;if(A){throw G;}}}else{try{B=F.fn.call(E,this.type,C,F.obj);}catch(D){this.lastError=D;if(A){throw D;}}}return B;},unsubscribeAll:function(){var A=this.subscribers.length,B;for(B=A-1;B>-1;B--){this._delete(B);}this.subscribers=[];return A;},_delete:function(A){var B=this.subscribers[A];if(B){delete B.fn;delete B.obj;}this.subscribers.splice(A,1);},toString:function(){return"CustomEvent: "+"'"+this.type+"', "+"context: "+this.scope;}};YAHOO.util.Subscriber=function(A,B,C){this.fn=A;this.obj=YAHOO.lang.isUndefined(B)?null:B;this.overrideContext=C;};YAHOO.util.Subscriber.prototype.getScope=function(A){if(this.overrideContext){if(this.overrideContext===true){return this.obj;}else{return this.overrideContext;}}return A;};YAHOO.util.Subscriber.prototype.contains=function(A,B){if(B){return(this.fn==A&&this.obj==B);}else{return(this.fn==A);}};YAHOO.util.Subscriber.prototype.toString=function(){return"Subscriber { obj: "+this.obj+", overrideContext: "+(this.overrideContext||"no")+" }";};if(!YAHOO.util.Event){YAHOO.util.Event=function(){var G=false,H=[],J=[],A=0,E=[],B=0,C={63232:38,63233:40,63234:37,63235:39,63276:33,63277:34,25:9},D=YAHOO.env.ua.ie,F="focusin",I="focusout";return{POLL_RETRYS:500,POLL_INTERVAL:40,EL:0,TYPE:1,FN:2,WFN:3,UNLOAD_OBJ:3,ADJ_SCOPE:4,OBJ:5,OVERRIDE:6,CAPTURE:7,lastError:null,isSafari:YAHOO.env.ua.webkit,webkit:YAHOO.env.ua.webkit,isIE:D,_interval:null,_dri:null,_specialTypes:{focusin:(D?"focusin":"focus"),focusout:(D?"focusout":"blur")},DOMReady:false,throwErrors:false,startInterval:function(){if(!this._interval){this._interval=YAHOO.lang.later(this.POLL_INTERVAL,this,this._tryPreloadAttach,null,true);}},onAvailable:function(Q,M,O,P,N){var K=(YAHOO.lang.isString(Q))?[Q]:Q;for(var L=0;L<K.length;L=L+1){E.push({id:K[L],fn:M,obj:O,overrideContext:P,checkReady:N});}A=this.POLL_RETRYS;this.startInterval();},onContentReady:function(N,K,L,M){this.onAvailable(N,K,L,M,true);},onDOMReady:function(){this.DOMReadyEvent.subscribe.apply(this.DOMReadyEvent,arguments);},_addListener:function(M,K,V,P,T,Y){if(!V||!V.call){return false;}if(this._isValidCollection(M)){var W=true;for(var Q=0,S=M.length;Q<S;++Q){W=this.on(M[Q],K,V,P,T)&&W;}return W;}else{if(YAHOO.lang.isString(M)){var O=this.getEl(M);if(O){M=O;}else{this.onAvailable(M,function(){YAHOO.util.Event._addListener(M,K,V,P,T,Y);});return true;}}}if(!M){return false;}if("unload"==K&&P!==this){J[J.length]=[M,K,V,P,T];return true;}var L=M;if(T){if(T===true){L=P;}else{L=T;}}var N=function(Z){return V.call(L,YAHOO.util.Event.getEvent(Z,M),P);};var X=[M,K,V,N,L,P,T,Y];var R=H.length;H[R]=X;try{this._simpleAdd(M,K,N,Y);}catch(U){this.lastError=U;this.removeListener(M,K,V);return false;}return true;},_getType:function(K){return this._specialTypes[K]||K;},addListener:function(M,P,L,N,O){var K=((P==F||P==I)&&!YAHOO.env.ua.ie)?true:false;return this._addListener(M,this._getType(P),L,N,O,K);},addFocusListener:function(L,K,M,N){return this.on(L,F,K,M,N);},removeFocusListener:function(L,K){return this.removeListener(L,F,K);},addBlurListener:function(L,K,M,N){return this.on(L,I,K,M,N);},removeBlurListener:function(L,K){return this.removeListener(L,I,K);},removeListener:function(L,K,R){var M,P,U;K=this._getType(K);if(typeof L=="string"){L=this.getEl(L);}else{if(this._isValidCollection(L)){var S=true;for(M=L.length-1;M>-1;M--){S=(this.removeListener(L[M],K,R)&&S);}return S;}}if(!R||!R.call){return this.purgeElement(L,false,K);}if("unload"==K){for(M=J.length-1;M>-1;M--){U=J[M];if(U&&U[0]==L&&U[1]==K&&U[2]==R){J.splice(M,1);return true;}}return false;}var N=null;var O=arguments[3];if("undefined"===typeof O){O=this._getCacheIndex(H,L,K,R);}if(O>=0){N=H[O];}if(!L||!N){return false;}var T=N[this.CAPTURE]===true?true:false;try{this._simpleRemove(L,K,N[this.WFN],T);}catch(Q){this.lastError=Q;return false;}delete H[O][this.WFN];delete H[O][this.FN];H.splice(O,1);return true;},getTarget:function(M,L){var K=M.target||M.srcElement;return this.resolveTextNode(K);},resolveTextNode:function(L){try{if(L&&3==L.nodeType){return L.parentNode;}}catch(K){}return L;},getPageX:function(L){var K=L.pageX;if(!K&&0!==K){K=L.clientX||0;if(this.isIE){K+=this._getScrollLeft();}}return K;},getPageY:function(K){var L=K.pageY;if(!L&&0!==L){L=K.clientY||0;if(this.isIE){L+=this._getScrollTop();}}return L;},getXY:function(K){return[this.getPageX(K),this.getPageY(K)];},getRelatedTarget:function(L){var K=L.relatedTarget;if(!K){if(L.type=="mouseout"){K=L.toElement;
+}else{if(L.type=="mouseover"){K=L.fromElement;}}}return this.resolveTextNode(K);},getTime:function(M){if(!M.time){var L=new Date().getTime();try{M.time=L;}catch(K){this.lastError=K;return L;}}return M.time;},stopEvent:function(K){this.stopPropagation(K);this.preventDefault(K);},stopPropagation:function(K){if(K.stopPropagation){K.stopPropagation();}else{K.cancelBubble=true;}},preventDefault:function(K){if(K.preventDefault){K.preventDefault();}else{K.returnValue=false;}},getEvent:function(M,K){var L=M||window.event;if(!L){var N=this.getEvent.caller;while(N){L=N.arguments[0];if(L&&Event==L.constructor){break;}N=N.caller;}}return L;},getCharCode:function(L){var K=L.keyCode||L.charCode||0;if(YAHOO.env.ua.webkit&&(K in C)){K=C[K];}return K;},_getCacheIndex:function(M,P,Q,O){for(var N=0,L=M.length;N<L;N=N+1){var K=M[N];if(K&&K[this.FN]==O&&K[this.EL]==P&&K[this.TYPE]==Q){return N;}}return -1;},generateId:function(K){var L=K.id;if(!L){L="yuievtautoid-"+B;++B;K.id=L;}return L;},_isValidCollection:function(L){try{return(L&&typeof L!=="string"&&L.length&&!L.tagName&&!L.alert&&typeof L[0]!=="undefined");}catch(K){return false;}},elCache:{},getEl:function(K){return(typeof K==="string")?document.getElementById(K):K;},clearCache:function(){},DOMReadyEvent:new YAHOO.util.CustomEvent("DOMReady",YAHOO,0,0,1),_load:function(L){if(!G){G=true;var K=YAHOO.util.Event;K._ready();K._tryPreloadAttach();}},_ready:function(L){var K=YAHOO.util.Event;if(!K.DOMReady){K.DOMReady=true;K.DOMReadyEvent.fire();K._simpleRemove(document,"DOMContentLoaded",K._ready);}},_tryPreloadAttach:function(){if(E.length===0){A=0;if(this._interval){this._interval.cancel();this._interval=null;}return;}if(this.locked){return;}if(this.isIE){if(!this.DOMReady){this.startInterval();return;}}this.locked=true;var Q=!G;if(!Q){Q=(A>0&&E.length>0);}var P=[];var R=function(T,U){var S=T;if(U.overrideContext){if(U.overrideContext===true){S=U.obj;}else{S=U.overrideContext;}}U.fn.call(S,U.obj);};var L,K,O,N,M=[];for(L=0,K=E.length;L<K;L=L+1){O=E[L];if(O){N=this.getEl(O.id);if(N){if(O.checkReady){if(G||N.nextSibling||!Q){M.push(O);E[L]=null;}}else{R(N,O);E[L]=null;}}else{P.push(O);}}}for(L=0,K=M.length;L<K;L=L+1){O=M[L];R(this.getEl(O.id),O);}A--;if(Q){for(L=E.length-1;L>-1;L--){O=E[L];if(!O||!O.id){E.splice(L,1);}}this.startInterval();}else{if(this._interval){this._interval.cancel();this._interval=null;}}this.locked=false;},purgeElement:function(O,P,R){var M=(YAHOO.lang.isString(O))?this.getEl(O):O;var Q=this.getListeners(M,R),N,K;if(Q){for(N=Q.length-1;N>-1;N--){var L=Q[N];this.removeListener(M,L.type,L.fn);}}if(P&&M&&M.childNodes){for(N=0,K=M.childNodes.length;N<K;++N){this.purgeElement(M.childNodes[N],P,R);}}},getListeners:function(M,K){var P=[],L;if(!K){L=[H,J];}else{if(K==="unload"){L=[J];}else{K=this._getType(K);L=[H];}}var R=(YAHOO.lang.isString(M))?this.getEl(M):M;for(var O=0;O<L.length;O=O+1){var T=L[O];if(T){for(var Q=0,S=T.length;Q<S;++Q){var N=T[Q];if(N&&N[this.EL]===R&&(!K||K===N[this.TYPE])){P.push({type:N[this.TYPE],fn:N[this.FN],obj:N[this.OBJ],adjust:N[this.OVERRIDE],scope:N[this.ADJ_SCOPE],index:Q});}}}}return(P.length)?P:null;},_unload:function(R){var L=YAHOO.util.Event,O,N,M,Q,P,S=J.slice(),K;for(O=0,Q=J.length;O<Q;++O){M=S[O];if(M){K=window;if(M[L.ADJ_SCOPE]){if(M[L.ADJ_SCOPE]===true){K=M[L.UNLOAD_OBJ];}else{K=M[L.ADJ_SCOPE];}}M[L.FN].call(K,L.getEvent(R,M[L.EL]),M[L.UNLOAD_OBJ]);S[O]=null;}}M=null;K=null;J=null;if(H){for(N=H.length-1;N>-1;N--){M=H[N];if(M){L.removeListener(M[L.EL],M[L.TYPE],M[L.FN],N);}}M=null;}L._simpleRemove(window,"unload",L._unload);},_getScrollLeft:function(){return this._getScroll()[1];},_getScrollTop:function(){return this._getScroll()[0];},_getScroll:function(){var K=document.documentElement,L=document.body;if(K&&(K.scrollTop||K.scrollLeft)){return[K.scrollTop,K.scrollLeft];}else{if(L){return[L.scrollTop,L.scrollLeft];}else{return[0,0];}}},regCE:function(){},_simpleAdd:function(){if(window.addEventListener){return function(M,N,L,K){M.addEventListener(N,L,(K));};}else{if(window.attachEvent){return function(M,N,L,K){M.attachEvent("on"+N,L);};}else{return function(){};}}}(),_simpleRemove:function(){if(window.removeEventListener){return function(M,N,L,K){M.removeEventListener(N,L,(K));};}else{if(window.detachEvent){return function(L,M,K){L.detachEvent("on"+M,K);};}else{return function(){};}}}()};}();(function(){var EU=YAHOO.util.Event;EU.on=EU.addListener;EU.onFocus=EU.addFocusListener;EU.onBlur=EU.addBlurListener;
+/* DOMReady: based on work by: Dean Edwards/John Resig/Matthias Miller/Diego Perini */
+if(EU.isIE){if(self!==self.top){document.onreadystatechange=function(){if(document.readyState=="complete"){document.onreadystatechange=null;EU._ready();}};}else{YAHOO.util.Event.onDOMReady(YAHOO.util.Event._tryPreloadAttach,YAHOO.util.Event,true);var n=document.createElement("p");EU._dri=setInterval(function(){try{n.doScroll("left");clearInterval(EU._dri);EU._dri=null;EU._ready();n=null;}catch(ex){}},EU.POLL_INTERVAL);}}else{if(EU.webkit&&EU.webkit<525){EU._dri=setInterval(function(){var rs=document.readyState;if("loaded"==rs||"complete"==rs){clearInterval(EU._dri);EU._dri=null;EU._ready();}},EU.POLL_INTERVAL);}else{EU._simpleAdd(document,"DOMContentLoaded",EU._ready);}}EU._simpleAdd(window,"load",EU._load);EU._simpleAdd(window,"unload",EU._unload);EU._tryPreloadAttach();})();}YAHOO.util.EventProvider=function(){};YAHOO.util.EventProvider.prototype={__yui_events:null,__yui_subscribers:null,subscribe:function(A,C,F,E){this.__yui_events=this.__yui_events||{};var D=this.__yui_events[A];if(D){D.subscribe(C,F,E);}else{this.__yui_subscribers=this.__yui_subscribers||{};var B=this.__yui_subscribers;if(!B[A]){B[A]=[];}B[A].push({fn:C,obj:F,overrideContext:E});}},unsubscribe:function(C,E,G){this.__yui_events=this.__yui_events||{};var A=this.__yui_events;if(C){var F=A[C];if(F){return F.unsubscribe(E,G);}}else{var B=true;for(var D in A){if(YAHOO.lang.hasOwnProperty(A,D)){B=B&&A[D].unsubscribe(E,G);}}return B;}return false;},unsubscribeAll:function(A){return this.unsubscribe(A);
+},createEvent:function(B,G){this.__yui_events=this.__yui_events||{};var E=G||{},D=this.__yui_events,F;if(D[B]){}else{F=new YAHOO.util.CustomEvent(B,E.scope||this,E.silent,YAHOO.util.CustomEvent.FLAT,E.fireOnce);D[B]=F;if(E.onSubscribeCallback){F.subscribeEvent.subscribe(E.onSubscribeCallback);}this.__yui_subscribers=this.__yui_subscribers||{};var A=this.__yui_subscribers[B];if(A){for(var C=0;C<A.length;++C){F.subscribe(A[C].fn,A[C].obj,A[C].overrideContext);}}}return D[B];},fireEvent:function(B){this.__yui_events=this.__yui_events||{};var D=this.__yui_events[B];if(!D){return null;}var A=[];for(var C=1;C<arguments.length;++C){A.push(arguments[C]);}return D.fire.apply(D,A);},hasEvent:function(A){if(this.__yui_events){if(this.__yui_events[A]){return true;}}return false;}};(function(){var A=YAHOO.util.Event,C=YAHOO.lang;YAHOO.util.KeyListener=function(D,I,E,F){if(!D){}else{if(!I){}else{if(!E){}}}if(!F){F=YAHOO.util.KeyListener.KEYDOWN;}var G=new YAHOO.util.CustomEvent("keyPressed");this.enabledEvent=new YAHOO.util.CustomEvent("enabled");this.disabledEvent=new YAHOO.util.CustomEvent("disabled");if(C.isString(D)){D=document.getElementById(D);}if(C.isFunction(E)){G.subscribe(E);}else{G.subscribe(E.fn,E.scope,E.correctScope);}function H(O,N){if(!I.shift){I.shift=false;}if(!I.alt){I.alt=false;}if(!I.ctrl){I.ctrl=false;}if(O.shiftKey==I.shift&&O.altKey==I.alt&&O.ctrlKey==I.ctrl){var J,M=I.keys,L;if(YAHOO.lang.isArray(M)){for(var K=0;K<M.length;K++){J=M[K];L=A.getCharCode(O);if(J==L){G.fire(L,O);break;}}}else{L=A.getCharCode(O);if(M==L){G.fire(L,O);}}}}this.enable=function(){if(!this.enabled){A.on(D,F,H);this.enabledEvent.fire(I);}this.enabled=true;};this.disable=function(){if(this.enabled){A.removeListener(D,F,H);this.disabledEvent.fire(I);}this.enabled=false;};this.toString=function(){return"KeyListener ["+I.keys+"] "+D.tagName+(D.id?"["+D.id+"]":"");};};var B=YAHOO.util.KeyListener;B.KEYDOWN="keydown";B.KEYUP="keyup";B.KEY={ALT:18,BACK_SPACE:8,CAPS_LOCK:20,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,META:224,NUM_LOCK:144,PAGE_DOWN:34,PAGE_UP:33,PAUSE:19,PRINTSCREEN:44,RIGHT:39,SCROLL_LOCK:145,SHIFT:16,SPACE:32,TAB:9,UP:38};})();YAHOO.register("event",YAHOO.util.Event,{version:"2.8.2r1",build:"7"});YAHOO.register("yahoo-dom-event", YAHOO, {version: "2.8.2r1", build: "7"}); \ No newline at end of file
diff --git a/js/yui/yahoo/yahoo-min.js b/js/yui/yahoo/yahoo-min.js
new file mode 100644
index 000000000..eb944a61b
--- /dev/null
+++ b/js/yui/yahoo/yahoo-min.js
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+if(typeof YAHOO=="undefined"||!YAHOO){var YAHOO={};}YAHOO.namespace=function(){var A=arguments,E=null,C,B,D;for(C=0;C<A.length;C=C+1){D=(""+A[C]).split(".");E=YAHOO;for(B=(D[0]=="YAHOO")?1:0;B<D.length;B=B+1){E[D[B]]=E[D[B]]||{};E=E[D[B]];}}return E;};YAHOO.log=function(D,A,C){var B=YAHOO.widget.Logger;if(B&&B.log){return B.log(D,A,C);}else{return false;}};YAHOO.register=function(A,E,D){var I=YAHOO.env.modules,B,H,G,F,C;if(!I[A]){I[A]={versions:[],builds:[]};}B=I[A];H=D.version;G=D.build;F=YAHOO.env.listeners;B.name=A;B.version=H;B.build=G;B.versions.push(H);B.builds.push(G);B.mainClass=E;for(C=0;C<F.length;C=C+1){F[C](B);}if(E){E.VERSION=H;E.BUILD=G;}else{YAHOO.log("mainClass is undefined for module "+A,"warn");}};YAHOO.env=YAHOO.env||{modules:[],listeners:[]};YAHOO.env.getVersion=function(A){return YAHOO.env.modules[A]||null;};YAHOO.env.ua=function(){var D=function(H){var I=0;return parseFloat(H.replace(/\./g,function(){return(I++==1)?"":".";}));},G=navigator,F={ie:0,opera:0,gecko:0,webkit:0,mobile:null,air:0,caja:G.cajaVersion,secure:false,os:null},C=navigator&&navigator.userAgent,E=window&&window.location,B=E&&E.href,A;F.secure=B&&(B.toLowerCase().indexOf("https")===0);if(C){if((/windows|win32/i).test(C)){F.os="windows";}else{if((/macintosh/i).test(C)){F.os="macintosh";}}if((/KHTML/).test(C)){F.webkit=1;}A=C.match(/AppleWebKit\/([^\s]*)/);if(A&&A[1]){F.webkit=D(A[1]);if(/ Mobile\//.test(C)){F.mobile="Apple";}else{A=C.match(/NokiaN[^\/]*/);if(A){F.mobile=A[0];}}A=C.match(/AdobeAIR\/([^\s]*)/);if(A){F.air=A[0];}}if(!F.webkit){A=C.match(/Opera[\s\/]([^\s]*)/);if(A&&A[1]){F.opera=D(A[1]);A=C.match(/Opera Mini[^;]*/);if(A){F.mobile=A[0];}}else{A=C.match(/MSIE\s([^;]*)/);if(A&&A[1]){F.ie=D(A[1]);}else{A=C.match(/Gecko\/([^\s]*)/);if(A){F.gecko=1;A=C.match(/rv:([^\s\)]*)/);if(A&&A[1]){F.gecko=D(A[1]);}}}}}}return F;}();(function(){YAHOO.namespace("util","widget","example");if("undefined"!==typeof YAHOO_config){var B=YAHOO_config.listener,A=YAHOO.env.listeners,D=true,C;if(B){for(C=0;C<A.length;C++){if(A[C]==B){D=false;break;}}if(D){A.push(B);}}}})();YAHOO.lang=YAHOO.lang||{};(function(){var B=YAHOO.lang,A=Object.prototype,H="[object Array]",C="[object Function]",G="[object Object]",E=[],F=["toString","valueOf"],D={isArray:function(I){return A.toString.apply(I)===H;},isBoolean:function(I){return typeof I==="boolean";},isFunction:function(I){return(typeof I==="function")||A.toString.apply(I)===C;},isNull:function(I){return I===null;},isNumber:function(I){return typeof I==="number"&&isFinite(I);},isObject:function(I){return(I&&(typeof I==="object"||B.isFunction(I)))||false;},isString:function(I){return typeof I==="string";},isUndefined:function(I){return typeof I==="undefined";},_IEEnumFix:(YAHOO.env.ua.ie)?function(K,J){var I,M,L;for(I=0;I<F.length;I=I+1){M=F[I];L=J[M];if(B.isFunction(L)&&L!=A[M]){K[M]=L;}}}:function(){},extend:function(L,M,K){if(!M||!L){throw new Error("extend failed, please check that "+"all dependencies are included.");}var J=function(){},I;J.prototype=M.prototype;L.prototype=new J();L.prototype.constructor=L;L.superclass=M.prototype;if(M.prototype.constructor==A.constructor){M.prototype.constructor=M;}if(K){for(I in K){if(B.hasOwnProperty(K,I)){L.prototype[I]=K[I];}}B._IEEnumFix(L.prototype,K);}},augmentObject:function(M,L){if(!L||!M){throw new Error("Absorb failed, verify dependencies.");}var I=arguments,K,N,J=I[2];if(J&&J!==true){for(K=2;K<I.length;K=K+1){M[I[K]]=L[I[K]];}}else{for(N in L){if(J||!(N in M)){M[N]=L[N];}}B._IEEnumFix(M,L);}},augmentProto:function(L,K){if(!K||!L){throw new Error("Augment failed, verify dependencies.");}var I=[L.prototype,K.prototype],J;for(J=2;J<arguments.length;J=J+1){I.push(arguments[J]);}B.augmentObject.apply(this,I);},dump:function(I,N){var K,M,P=[],Q="{...}",J="f(){...}",O=", ",L=" => ";if(!B.isObject(I)){return I+"";}else{if(I instanceof Date||("nodeType" in I&&"tagName" in I)){return I;}else{if(B.isFunction(I)){return J;}}}N=(B.isNumber(N))?N:3;if(B.isArray(I)){P.push("[");for(K=0,M=I.length;K<M;K=K+1){if(B.isObject(I[K])){P.push((N>0)?B.dump(I[K],N-1):Q);}else{P.push(I[K]);}P.push(O);}if(P.length>1){P.pop();}P.push("]");}else{P.push("{");for(K in I){if(B.hasOwnProperty(I,K)){P.push(K+L);if(B.isObject(I[K])){P.push((N>0)?B.dump(I[K],N-1):Q);}else{P.push(I[K]);}P.push(O);}}if(P.length>1){P.pop();}P.push("}");}return P.join("");},substitute:function(Y,J,R){var N,M,L,U,V,X,T=[],K,O="dump",S=" ",I="{",W="}",Q,P;for(;;){N=Y.lastIndexOf(I);if(N<0){break;}M=Y.indexOf(W,N);if(N+1>=M){break;}K=Y.substring(N+1,M);U=K;X=null;L=U.indexOf(S);if(L>-1){X=U.substring(L+1);U=U.substring(0,L);}V=J[U];if(R){V=R(U,V,X);}if(B.isObject(V)){if(B.isArray(V)){V=B.dump(V,parseInt(X,10));}else{X=X||"";Q=X.indexOf(O);if(Q>-1){X=X.substring(4);}P=V.toString();if(P===G||Q>-1){V=B.dump(V,parseInt(X,10));}else{V=P;}}}else{if(!B.isString(V)&&!B.isNumber(V)){V="~-"+T.length+"-~";T[T.length]=K;}}Y=Y.substring(0,N)+V+Y.substring(M+1);}for(N=T.length-1;N>=0;N=N-1){Y=Y.replace(new RegExp("~-"+N+"-~"),"{"+T[N]+"}","g");}return Y;},trim:function(I){try{return I.replace(/^\s+|\s+$/g,"");}catch(J){return I;}},merge:function(){var L={},J=arguments,I=J.length,K;for(K=0;K<I;K=K+1){B.augmentObject(L,J[K],true);}return L;},later:function(P,J,Q,L,M){P=P||0;J=J||{};var K=Q,O=L,N,I;if(B.isString(Q)){K=J[Q];}if(!K){throw new TypeError("method undefined");}if(O&&!B.isArray(O)){O=[L];}N=function(){K.apply(J,O||E);};I=(M)?setInterval(N,P):setTimeout(N,P);return{interval:M,cancel:function(){if(this.interval){clearInterval(I);}else{clearTimeout(I);}}};},isValue:function(I){return(B.isObject(I)||B.isString(I)||B.isNumber(I)||B.isBoolean(I));}};B.hasOwnProperty=(A.hasOwnProperty)?function(I,J){return I&&I.hasOwnProperty(J);}:function(I,J){return !B.isUndefined(I[J])&&I.constructor.prototype[J]!==I[J];};D.augmentObject(B,D,true);YAHOO.util.Lang=B;B.augment=B.augmentProto;YAHOO.augment=B.augmentProto;YAHOO.extend=B.extend;})();YAHOO.register("yahoo",YAHOO,{version:"2.8.2r1",build:"7"});
diff --git a/js/yui/yuiloader/yuiloader-min.js b/js/yui/yuiloader/yuiloader-min.js
new file mode 100644
index 000000000..2c1c58c2f
--- /dev/null
+++ b/js/yui/yuiloader/yuiloader-min.js
@@ -0,0 +1,10 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+if(typeof YAHOO=="undefined"||!YAHOO){var YAHOO={};}YAHOO.namespace=function(){var A=arguments,E=null,C,B,D;for(C=0;C<A.length;C=C+1){D=(""+A[C]).split(".");E=YAHOO;for(B=(D[0]=="YAHOO")?1:0;B<D.length;B=B+1){E[D[B]]=E[D[B]]||{};E=E[D[B]];}}return E;};YAHOO.log=function(D,A,C){var B=YAHOO.widget.Logger;if(B&&B.log){return B.log(D,A,C);}else{return false;}};YAHOO.register=function(A,E,D){var I=YAHOO.env.modules,B,H,G,F,C;if(!I[A]){I[A]={versions:[],builds:[]};}B=I[A];H=D.version;G=D.build;F=YAHOO.env.listeners;B.name=A;B.version=H;B.build=G;B.versions.push(H);B.builds.push(G);B.mainClass=E;for(C=0;C<F.length;C=C+1){F[C](B);}if(E){E.VERSION=H;E.BUILD=G;}else{YAHOO.log("mainClass is undefined for module "+A,"warn");}};YAHOO.env=YAHOO.env||{modules:[],listeners:[]};YAHOO.env.getVersion=function(A){return YAHOO.env.modules[A]||null;};YAHOO.env.ua=function(){var D=function(H){var I=0;return parseFloat(H.replace(/\./g,function(){return(I++==1)?"":".";}));},G=navigator,F={ie:0,opera:0,gecko:0,webkit:0,mobile:null,air:0,caja:G.cajaVersion,secure:false,os:null},C=navigator&&navigator.userAgent,E=window&&window.location,B=E&&E.href,A;F.secure=B&&(B.toLowerCase().indexOf("https")===0);if(C){if((/windows|win32/i).test(C)){F.os="windows";}else{if((/macintosh/i).test(C)){F.os="macintosh";}}if((/KHTML/).test(C)){F.webkit=1;}A=C.match(/AppleWebKit\/([^\s]*)/);if(A&&A[1]){F.webkit=D(A[1]);if(/ Mobile\//.test(C)){F.mobile="Apple";}else{A=C.match(/NokiaN[^\/]*/);if(A){F.mobile=A[0];}}A=C.match(/AdobeAIR\/([^\s]*)/);if(A){F.air=A[0];}}if(!F.webkit){A=C.match(/Opera[\s\/]([^\s]*)/);if(A&&A[1]){F.opera=D(A[1]);A=C.match(/Opera Mini[^;]*/);if(A){F.mobile=A[0];}}else{A=C.match(/MSIE\s([^;]*)/);if(A&&A[1]){F.ie=D(A[1]);}else{A=C.match(/Gecko\/([^\s]*)/);if(A){F.gecko=1;A=C.match(/rv:([^\s\)]*)/);if(A&&A[1]){F.gecko=D(A[1]);}}}}}}return F;}();(function(){YAHOO.namespace("util","widget","example");if("undefined"!==typeof YAHOO_config){var B=YAHOO_config.listener,A=YAHOO.env.listeners,D=true,C;if(B){for(C=0;C<A.length;C++){if(A[C]==B){D=false;break;}}if(D){A.push(B);}}}})();YAHOO.lang=YAHOO.lang||{};(function(){var B=YAHOO.lang,A=Object.prototype,H="[object Array]",C="[object Function]",G="[object Object]",E=[],F=["toString","valueOf"],D={isArray:function(I){return A.toString.apply(I)===H;},isBoolean:function(I){return typeof I==="boolean";},isFunction:function(I){return(typeof I==="function")||A.toString.apply(I)===C;},isNull:function(I){return I===null;},isNumber:function(I){return typeof I==="number"&&isFinite(I);},isObject:function(I){return(I&&(typeof I==="object"||B.isFunction(I)))||false;},isString:function(I){return typeof I==="string";},isUndefined:function(I){return typeof I==="undefined";},_IEEnumFix:(YAHOO.env.ua.ie)?function(K,J){var I,M,L;for(I=0;I<F.length;I=I+1){M=F[I];L=J[M];if(B.isFunction(L)&&L!=A[M]){K[M]=L;}}}:function(){},extend:function(L,M,K){if(!M||!L){throw new Error("extend failed, please check that "+"all dependencies are included.");}var J=function(){},I;J.prototype=M.prototype;L.prototype=new J();L.prototype.constructor=L;L.superclass=M.prototype;if(M.prototype.constructor==A.constructor){M.prototype.constructor=M;}if(K){for(I in K){if(B.hasOwnProperty(K,I)){L.prototype[I]=K[I];}}B._IEEnumFix(L.prototype,K);}},augmentObject:function(M,L){if(!L||!M){throw new Error("Absorb failed, verify dependencies.");}var I=arguments,K,N,J=I[2];if(J&&J!==true){for(K=2;K<I.length;K=K+1){M[I[K]]=L[I[K]];}}else{for(N in L){if(J||!(N in M)){M[N]=L[N];}}B._IEEnumFix(M,L);}},augmentProto:function(L,K){if(!K||!L){throw new Error("Augment failed, verify dependencies.");}var I=[L.prototype,K.prototype],J;for(J=2;J<arguments.length;J=J+1){I.push(arguments[J]);}B.augmentObject.apply(this,I);},dump:function(I,N){var K,M,P=[],Q="{...}",J="f(){...}",O=", ",L=" => ";if(!B.isObject(I)){return I+"";}else{if(I instanceof Date||("nodeType" in I&&"tagName" in I)){return I;}else{if(B.isFunction(I)){return J;}}}N=(B.isNumber(N))?N:3;if(B.isArray(I)){P.push("[");for(K=0,M=I.length;K<M;K=K+1){if(B.isObject(I[K])){P.push((N>0)?B.dump(I[K],N-1):Q);}else{P.push(I[K]);}P.push(O);}if(P.length>1){P.pop();}P.push("]");}else{P.push("{");for(K in I){if(B.hasOwnProperty(I,K)){P.push(K+L);if(B.isObject(I[K])){P.push((N>0)?B.dump(I[K],N-1):Q);}else{P.push(I[K]);}P.push(O);}}if(P.length>1){P.pop();}P.push("}");}return P.join("");},substitute:function(Y,J,R){var N,M,L,U,V,X,T=[],K,O="dump",S=" ",I="{",W="}",Q,P;for(;;){N=Y.lastIndexOf(I);if(N<0){break;}M=Y.indexOf(W,N);if(N+1>=M){break;}K=Y.substring(N+1,M);U=K;X=null;L=U.indexOf(S);if(L>-1){X=U.substring(L+1);U=U.substring(0,L);}V=J[U];if(R){V=R(U,V,X);}if(B.isObject(V)){if(B.isArray(V)){V=B.dump(V,parseInt(X,10));}else{X=X||"";Q=X.indexOf(O);if(Q>-1){X=X.substring(4);}P=V.toString();if(P===G||Q>-1){V=B.dump(V,parseInt(X,10));}else{V=P;}}}else{if(!B.isString(V)&&!B.isNumber(V)){V="~-"+T.length+"-~";T[T.length]=K;}}Y=Y.substring(0,N)+V+Y.substring(M+1);}for(N=T.length-1;N>=0;N=N-1){Y=Y.replace(new RegExp("~-"+N+"-~"),"{"+T[N]+"}","g");}return Y;},trim:function(I){try{return I.replace(/^\s+|\s+$/g,"");}catch(J){return I;}},merge:function(){var L={},J=arguments,I=J.length,K;for(K=0;K<I;K=K+1){B.augmentObject(L,J[K],true);}return L;},later:function(P,J,Q,L,M){P=P||0;J=J||{};var K=Q,O=L,N,I;if(B.isString(Q)){K=J[Q];}if(!K){throw new TypeError("method undefined");}if(O&&!B.isArray(O)){O=[L];}N=function(){K.apply(J,O||E);};I=(M)?setInterval(N,P):setTimeout(N,P);return{interval:M,cancel:function(){if(this.interval){clearInterval(I);}else{clearTimeout(I);}}};},isValue:function(I){return(B.isObject(I)||B.isString(I)||B.isNumber(I)||B.isBoolean(I));}};B.hasOwnProperty=(A.hasOwnProperty)?function(I,J){return I&&I.hasOwnProperty(J);}:function(I,J){return !B.isUndefined(I[J])&&I.constructor.prototype[J]!==I[J];};D.augmentObject(B,D,true);YAHOO.util.Lang=B;B.augment=B.augmentProto;YAHOO.augment=B.augmentProto;YAHOO.extend=B.extend;})();YAHOO.register("yahoo",YAHOO,{version:"2.8.2r1",build:"7"});
+YAHOO.util.Get=function(){var M={},L=0,R=0,E=false,N=YAHOO.env.ua,S=YAHOO.lang;var J=function(W,T,X){var U=X||window,Y=U.document,Z=Y.createElement(W);for(var V in T){if(T[V]&&YAHOO.lang.hasOwnProperty(T,V)){Z.setAttribute(V,T[V]);}}return Z;};var I=function(U,V,T){var W={id:"yui__dyn_"+(R++),type:"text/css",rel:"stylesheet",href:U};if(T){S.augmentObject(W,T);}return J("link",W,V);};var P=function(U,V,T){var W={id:"yui__dyn_"+(R++),type:"text/javascript",src:U};if(T){S.augmentObject(W,T);}return J("script",W,V);};var A=function(T,U){return{tId:T.tId,win:T.win,data:T.data,nodes:T.nodes,msg:U,purge:function(){D(this.tId);}};};var B=function(T,W){var U=M[W],V=(S.isString(T))?U.win.document.getElementById(T):T;if(!V){Q(W,"target node not found: "+T);}return V;};var Q=function(W,V){var T=M[W];if(T.onFailure){var U=T.scope||T.win;T.onFailure.call(U,A(T,V));}};var C=function(W){var T=M[W];T.finished=true;if(T.aborted){var V="transaction "+W+" was aborted";Q(W,V);return;}if(T.onSuccess){var U=T.scope||T.win;T.onSuccess.call(U,A(T));}};var O=function(V){var T=M[V];if(T.onTimeout){var U=T.scope||T;T.onTimeout.call(U,A(T));}};var G=function(V,Z){var U=M[V];if(U.timer){U.timer.cancel();}if(U.aborted){var X="transaction "+V+" was aborted";Q(V,X);return;}if(Z){U.url.shift();if(U.varName){U.varName.shift();}}else{U.url=(S.isString(U.url))?[U.url]:U.url;if(U.varName){U.varName=(S.isString(U.varName))?[U.varName]:U.varName;}}var c=U.win,b=c.document,a=b.getElementsByTagName("head")[0],W;if(U.url.length===0){if(U.type==="script"&&N.webkit&&N.webkit<420&&!U.finalpass&&!U.varName){var Y=P(null,U.win,U.attributes);Y.innerHTML='YAHOO.util.Get._finalize("'+V+'");';U.nodes.push(Y);a.appendChild(Y);}else{C(V);}return;}var T=U.url[0];if(!T){U.url.shift();return G(V);}if(U.timeout){U.timer=S.later(U.timeout,U,O,V);}if(U.type==="script"){W=P(T,c,U.attributes);}else{W=I(T,c,U.attributes);}F(U.type,W,V,T,c,U.url.length);U.nodes.push(W);if(U.insertBefore){var e=B(U.insertBefore,V);if(e){e.parentNode.insertBefore(W,e);}}else{a.appendChild(W);}if((N.webkit||N.gecko)&&U.type==="css"){G(V,T);}};var K=function(){if(E){return;}E=true;for(var T in M){var U=M[T];if(U.autopurge&&U.finished){D(U.tId);delete M[T];}}E=false;};var D=function(Z){if(M[Z]){var T=M[Z],U=T.nodes,X=U.length,c=T.win.document,a=c.getElementsByTagName("head")[0],V,Y,W,b;if(T.insertBefore){V=B(T.insertBefore,Z);if(V){a=V.parentNode;}}for(Y=0;Y<X;Y=Y+1){W=U[Y];if(W.clearAttributes){W.clearAttributes();}else{for(b in W){delete W[b];}}a.removeChild(W);}T.nodes=[];}};var H=function(U,T,V){var X="q"+(L++);V=V||{};if(L%YAHOO.util.Get.PURGE_THRESH===0){K();}M[X]=S.merge(V,{tId:X,type:U,url:T,finished:false,aborted:false,nodes:[]});var W=M[X];W.win=W.win||window;W.scope=W.scope||W.win;W.autopurge=("autopurge" in W)?W.autopurge:(U==="script")?true:false;if(V.charset){W.attributes=W.attributes||{};W.attributes.charset=V.charset;}S.later(0,W,G,X);return{tId:X};};var F=function(c,X,W,U,Y,Z,b){var a=b||G;if(N.ie){X.onreadystatechange=function(){var d=this.readyState;if("loaded"===d||"complete"===d){X.onreadystatechange=null;a(W,U);}};}else{if(N.webkit){if(c==="script"){if(N.webkit>=420){X.addEventListener("load",function(){a(W,U);});}else{var T=M[W];if(T.varName){var V=YAHOO.util.Get.POLL_FREQ;T.maxattempts=YAHOO.util.Get.TIMEOUT/V;T.attempts=0;T._cache=T.varName[0].split(".");T.timer=S.later(V,T,function(j){var f=this._cache,e=f.length,d=this.win,g;for(g=0;g<e;g=g+1){d=d[f[g]];if(!d){this.attempts++;if(this.attempts++>this.maxattempts){var h="Over retry limit, giving up";T.timer.cancel();Q(W,h);}else{}return;}}T.timer.cancel();a(W,U);},null,true);}else{S.later(YAHOO.util.Get.POLL_FREQ,null,a,[W,U]);}}}}else{X.onload=function(){a(W,U);};}}};return{POLL_FREQ:10,PURGE_THRESH:20,TIMEOUT:2000,_finalize:function(T){S.later(0,null,C,T);},abort:function(U){var V=(S.isString(U))?U:U.tId;var T=M[V];if(T){T.aborted=true;}},script:function(T,U){return H("script",T,U);},css:function(T,U){return H("css",T,U);}};}();YAHOO.register("get",YAHOO.util.Get,{version:"2.8.2r1",build:"7"});(function(){var Y=YAHOO,util=Y.util,lang=Y.lang,env=Y.env,PROV="_provides",SUPER="_supersedes",REQ="expanded",AFTER="_after";var YUI={dupsAllowed:{"yahoo":true,"get":true},info:{"root":"2.8.2r1/build/","base":"http://yui.yahooapis.com/2.8.2r1/build/","comboBase":"http://yui.yahooapis.com/combo?","skin":{"defaultSkin":"sam","base":"assets/skins/","path":"skin.css","after":["reset","fonts","grids","base"],"rollup":3},dupsAllowed:["yahoo","get"],"moduleInfo":{"animation":{"type":"js","path":"animation/animation-min.js","requires":["dom","event"]},"autocomplete":{"type":"js","path":"autocomplete/autocomplete-min.js","requires":["dom","event","datasource"],"optional":["connection","animation"],"skinnable":true},"base":{"type":"css","path":"base/base-min.css","after":["reset","fonts","grids"]},"button":{"type":"js","path":"button/button-min.js","requires":["element"],"optional":["menu"],"skinnable":true},"calendar":{"type":"js","path":"calendar/calendar-min.js","requires":["event","dom"],supersedes:["datemeth"],"skinnable":true},"carousel":{"type":"js","path":"carousel/carousel-min.js","requires":["element"],"optional":["animation"],"skinnable":true},"charts":{"type":"js","path":"charts/charts-min.js","requires":["element","json","datasource","swf"]},"colorpicker":{"type":"js","path":"colorpicker/colorpicker-min.js","requires":["slider","element"],"optional":["animation"],"skinnable":true},"connection":{"type":"js","path":"connection/connection-min.js","requires":["event"],"supersedes":["connectioncore"]},"connectioncore":{"type":"js","path":"connection/connection_core-min.js","requires":["event"],"pkg":"connection"},"container":{"type":"js","path":"container/container-min.js","requires":["dom","event"],"optional":["dragdrop","animation","connection"],"supersedes":["containercore"],"skinnable":true},"containercore":{"type":"js","path":"container/container_core-min.js","requires":["dom","event"],"pkg":"container"},"cookie":{"type":"js","path":"cookie/cookie-min.js","requires":["yahoo"]},"datasource":{"type":"js","path":"datasource/datasource-min.js","requires":["event"],"optional":["connection"]},"datatable":{"type":"js","path":"datatable/datatable-min.js","requires":["element","datasource"],"optional":["calendar","dragdrop","paginator"],"skinnable":true},datemath:{"type":"js","path":"datemath/datemath-min.js","requires":["yahoo"]},"dom":{"type":"js","path":"dom/dom-min.js","requires":["yahoo"]},"dragdrop":{"type":"js","path":"dragdrop/dragdrop-min.js","requires":["dom","event"]},"editor":{"type":"js","path":"editor/editor-min.js","requires":["menu","element","button"],"optional":["animation","dragdrop"],"supersedes":["simpleeditor"],"skinnable":true},"element":{"type":"js","path":"element/element-min.js","requires":["dom","event"],"optional":["event-mouseenter","event-delegate"]},"element-delegate":{"type":"js","path":"element-delegate/element-delegate-min.js","requires":["element"]},"event":{"type":"js","path":"event/event-min.js","requires":["yahoo"]},"event-simulate":{"type":"js","path":"event-simulate/event-simulate-min.js","requires":["event"]},"event-delegate":{"type":"js","path":"event-delegate/event-delegate-min.js","requires":["event"],"optional":["selector"]},"event-mouseenter":{"type":"js","path":"event-mouseenter/event-mouseenter-min.js","requires":["dom","event"]},"fonts":{"type":"css","path":"fonts/fonts-min.css"},"get":{"type":"js","path":"get/get-min.js","requires":["yahoo"]},"grids":{"type":"css","path":"grids/grids-min.css","requires":["fonts"],"optional":["reset"]},"history":{"type":"js","path":"history/history-min.js","requires":["event"]},"imagecropper":{"type":"js","path":"imagecropper/imagecropper-min.js","requires":["dragdrop","element","resize"],"skinnable":true},"imageloader":{"type":"js","path":"imageloader/imageloader-min.js","requires":["event","dom"]},"json":{"type":"js","path":"json/json-min.js","requires":["yahoo"]},"layout":{"type":"js","path":"layout/layout-min.js","requires":["element"],"optional":["animation","dragdrop","resize","selector"],"skinnable":true},"logger":{"type":"js","path":"logger/logger-min.js","requires":["event","dom"],"optional":["dragdrop"],"skinnable":true},"menu":{"type":"js","path":"menu/menu-min.js","requires":["containercore"],"skinnable":true},"paginator":{"type":"js","path":"paginator/paginator-min.js","requires":["element"],"skinnable":true},"profiler":{"type":"js","path":"profiler/profiler-min.js","requires":["yahoo"]},"profilerviewer":{"type":"js","path":"profilerviewer/profilerviewer-min.js","requires":["profiler","yuiloader","element"],"skinnable":true},"progressbar":{"type":"js","path":"progressbar/progressbar-min.js","requires":["element"],"optional":["animation"],"skinnable":true},"reset":{"type":"css","path":"reset/reset-min.css"},"reset-fonts-grids":{"type":"css","path":"reset-fonts-grids/reset-fonts-grids.css","supersedes":["reset","fonts","grids","reset-fonts"],"rollup":4},"reset-fonts":{"type":"css","path":"reset-fonts/reset-fonts.css","supersedes":["reset","fonts"],"rollup":2},"resize":{"type":"js","path":"resize/resize-min.js","requires":["dragdrop","element"],"optional":["animation"],"skinnable":true},"selector":{"type":"js","path":"selector/selector-min.js","requires":["yahoo","dom"]},"simpleeditor":{"type":"js","path":"editor/simpleeditor-min.js","requires":["element"],"optional":["containercore","menu","button","animation","dragdrop"],"skinnable":true,"pkg":"editor"},"slider":{"type":"js","path":"slider/slider-min.js","requires":["dragdrop"],"optional":["animation"],"skinnable":true},"storage":{"type":"js","path":"storage/storage-min.js","requires":["yahoo","event","cookie"],"optional":["swfstore"]},"stylesheet":{"type":"js","path":"stylesheet/stylesheet-min.js","requires":["yahoo"]},"swf":{"type":"js","path":"swf/swf-min.js","requires":["element"],"supersedes":["swfdetect"]},"swfdetect":{"type":"js","path":"swfdetect/swfdetect-min.js","requires":["yahoo"]},"swfstore":{"type":"js","path":"swfstore/swfstore-min.js","requires":["element","cookie","swf"]},"tabview":{"type":"js","path":"tabview/tabview-min.js","requires":["element"],"optional":["connection"],"skinnable":true},"treeview":{"type":"js","path":"treeview/treeview-min.js","requires":["event","dom"],"optional":["json","animation","calendar"],"skinnable":true},"uploader":{"type":"js","path":"uploader/uploader-min.js","requires":["element"]},"utilities":{"type":"js","path":"utilities/utilities.js","supersedes":["yahoo","event","dragdrop","animation","dom","connection","element","yahoo-dom-event","get","yuiloader","yuiloader-dom-event"],"rollup":8},"yahoo":{"type":"js","path":"yahoo/yahoo-min.js"},"yahoo-dom-event":{"type":"js","path":"yahoo-dom-event/yahoo-dom-event.js","supersedes":["yahoo","event","dom"],"rollup":3},"yuiloader":{"type":"js","path":"yuiloader/yuiloader-min.js","supersedes":["yahoo","get"]},"yuiloader-dom-event":{"type":"js","path":"yuiloader-dom-event/yuiloader-dom-event.js","supersedes":["yahoo","dom","event","get","yuiloader","yahoo-dom-event"],"rollup":5},"yuitest":{"type":"js","path":"yuitest/yuitest-min.js","requires":["logger"],"optional":["event-simulate"],"skinnable":true}}},ObjectUtil:{appendArray:function(o,a){if(a){for(var i=0;
+i<a.length;i=i+1){o[a[i]]=true;}}},keys:function(o,ordered){var a=[],i;for(i in o){if(lang.hasOwnProperty(o,i)){a.push(i);}}return a;}},ArrayUtil:{appendArray:function(a1,a2){Array.prototype.push.apply(a1,a2);},indexOf:function(a,val){for(var i=0;i<a.length;i=i+1){if(a[i]===val){return i;}}return -1;},toObject:function(a){var o={};for(var i=0;i<a.length;i=i+1){o[a[i]]=true;}return o;},uniq:function(a){return YUI.ObjectUtil.keys(YUI.ArrayUtil.toObject(a));}}};YAHOO.util.YUILoader=function(o){this._internalCallback=null;this._useYahooListener=false;this.onSuccess=null;this.onFailure=Y.log;this.onProgress=null;this.onTimeout=null;this.scope=this;this.data=null;this.insertBefore=null;this.charset=null;this.varName=null;this.base=YUI.info.base;this.comboBase=YUI.info.comboBase;this.combine=false;this.root=YUI.info.root;this.timeout=0;this.ignore=null;this.force=null;this.allowRollup=true;this.filter=null;this.required={};this.moduleInfo=lang.merge(YUI.info.moduleInfo);this.rollups=null;this.loadOptional=false;this.sorted=[];this.loaded={};this.dirty=true;this.inserted={};var self=this;env.listeners.push(function(m){if(self._useYahooListener){self.loadNext(m.name);}});this.skin=lang.merge(YUI.info.skin);this._config(o);};Y.util.YUILoader.prototype={FILTERS:{RAW:{"searchExp":"-min\\.js","replaceStr":".js"},DEBUG:{"searchExp":"-min\\.js","replaceStr":"-debug.js"}},SKIN_PREFIX:"skin-",_config:function(o){if(o){for(var i in o){if(lang.hasOwnProperty(o,i)){if(i=="require"){this.require(o[i]);}else{this[i]=o[i];}}}}var f=this.filter;if(lang.isString(f)){f=f.toUpperCase();if(f==="DEBUG"){this.require("logger");}if(!Y.widget.LogWriter){Y.widget.LogWriter=function(){return Y;};}this.filter=this.FILTERS[f];}},addModule:function(o){if(!o||!o.name||!o.type||(!o.path&&!o.fullpath)){return false;}o.ext=("ext" in o)?o.ext:true;o.requires=o.requires||[];this.moduleInfo[o.name]=o;this.dirty=true;return true;},require:function(what){var a=(typeof what==="string")?arguments:what;this.dirty=true;YUI.ObjectUtil.appendArray(this.required,a);},_addSkin:function(skin,mod){var name=this.formatSkin(skin),info=this.moduleInfo,sinf=this.skin,ext=info[mod]&&info[mod].ext;if(!info[name]){this.addModule({"name":name,"type":"css","path":sinf.base+skin+"/"+sinf.path,"after":sinf.after,"rollup":sinf.rollup,"ext":ext});}if(mod){name=this.formatSkin(skin,mod);if(!info[name]){var mdef=info[mod],pkg=mdef.pkg||mod;this.addModule({"name":name,"type":"css","after":sinf.after,"path":pkg+"/"+sinf.base+skin+"/"+mod+".css","ext":ext});}}return name;},getRequires:function(mod){if(!mod){return[];}if(!this.dirty&&mod.expanded){return mod.expanded;}mod.requires=mod.requires||[];var i,d=[],r=mod.requires,o=mod.optional,info=this.moduleInfo,m;for(i=0;i<r.length;i=i+1){d.push(r[i]);m=info[r[i]];YUI.ArrayUtil.appendArray(d,this.getRequires(m));}if(o&&this.loadOptional){for(i=0;i<o.length;i=i+1){d.push(o[i]);YUI.ArrayUtil.appendArray(d,this.getRequires(info[o[i]]));}}mod.expanded=YUI.ArrayUtil.uniq(d);return mod.expanded;},getProvides:function(name,notMe){var addMe=!(notMe),ckey=(addMe)?PROV:SUPER,m=this.moduleInfo[name],o={};if(!m){return o;}if(m[ckey]){return m[ckey];}var s=m.supersedes,done={},me=this;var add=function(mm){if(!done[mm]){done[mm]=true;lang.augmentObject(o,me.getProvides(mm));}};if(s){for(var i=0;i<s.length;i=i+1){add(s[i]);}}m[SUPER]=o;m[PROV]=lang.merge(o);m[PROV][name]=true;return m[ckey];},calculate:function(o){if(o||this.dirty){this._config(o);this._setup();this._explode();if(this.allowRollup){this._rollup();}this._reduce();this._sort();this.dirty=false;}},_setup:function(){var info=this.moduleInfo,name,i,j;for(name in info){if(lang.hasOwnProperty(info,name)){var m=info[name];if(m&&m.skinnable){var o=this.skin.overrides,smod;if(o&&o[name]){for(i=0;i<o[name].length;i=i+1){smod=this._addSkin(o[name][i],name);}}else{smod=this._addSkin(this.skin.defaultSkin,name);}m.requires.push(smod);}}}var l=lang.merge(this.inserted);if(!this._sandbox){l=lang.merge(l,env.modules);}if(this.ignore){YUI.ObjectUtil.appendArray(l,this.ignore);}if(this.force){for(i=0;i<this.force.length;i=i+1){if(this.force[i] in l){delete l[this.force[i]];}}}for(j in l){if(lang.hasOwnProperty(l,j)){lang.augmentObject(l,this.getProvides(j));}}this.loaded=l;},_explode:function(){var r=this.required,i,mod;for(i in r){if(lang.hasOwnProperty(r,i)){mod=this.moduleInfo[i];if(mod){var req=this.getRequires(mod);if(req){YUI.ObjectUtil.appendArray(r,req);}}}}},_skin:function(){},formatSkin:function(skin,mod){var s=this.SKIN_PREFIX+skin;if(mod){s=s+"-"+mod;}return s;},parseSkin:function(mod){if(mod.indexOf(this.SKIN_PREFIX)===0){var a=mod.split("-");return{skin:a[1],module:a[2]};}return null;},_rollup:function(){var i,j,m,s,rollups={},r=this.required,roll,info=this.moduleInfo;if(this.dirty||!this.rollups){for(i in info){if(lang.hasOwnProperty(info,i)){m=info[i];if(m&&m.rollup){rollups[i]=m;}}}this.rollups=rollups;}for(;;){var rolled=false;for(i in rollups){if(!r[i]&&!this.loaded[i]){m=info[i];s=m.supersedes;roll=false;if(!m.rollup){continue;}var skin=(m.ext)?false:this.parseSkin(i),c=0;if(skin){for(j in r){if(lang.hasOwnProperty(r,j)){if(i!==j&&this.parseSkin(j)){c++;roll=(c>=m.rollup);if(roll){break;}}}}}else{for(j=0;j<s.length;j=j+1){if(this.loaded[s[j]]&&(!YUI.dupsAllowed[s[j]])){roll=false;break;}else{if(r[s[j]]){c++;roll=(c>=m.rollup);if(roll){break;}}}}}if(roll){r[i]=true;rolled=true;this.getRequires(m);}}}if(!rolled){break;}}},_reduce:function(){var i,j,s,m,r=this.required;for(i in r){if(i in this.loaded){delete r[i];}else{var skinDef=this.parseSkin(i);if(skinDef){if(!skinDef.module){var skin_pre=this.SKIN_PREFIX+skinDef.skin;for(j in r){if(lang.hasOwnProperty(r,j)){m=this.moduleInfo[j];var ext=m&&m.ext;if(!ext&&j!==i&&j.indexOf(skin_pre)>-1){delete r[j];}}}}}else{m=this.moduleInfo[i];s=m&&m.supersedes;if(s){for(j=0;j<s.length;j=j+1){if(s[j] in r){delete r[s[j]];}}}}}}},_onFailure:function(msg){YAHOO.log("Failure","info","loader");var f=this.onFailure;if(f){f.call(this.scope,{msg:"failure: "+msg,data:this.data,success:false});
+}},_onTimeout:function(){YAHOO.log("Timeout","info","loader");var f=this.onTimeout;if(f){f.call(this.scope,{msg:"timeout",data:this.data,success:false});}},_sort:function(){var s=[],info=this.moduleInfo,loaded=this.loaded,checkOptional=!this.loadOptional,me=this;var requires=function(aa,bb){var mm=info[aa];if(loaded[bb]||!mm){return false;}var ii,rr=mm.expanded,after=mm.after,other=info[bb],optional=mm.optional;if(rr&&YUI.ArrayUtil.indexOf(rr,bb)>-1){return true;}if(after&&YUI.ArrayUtil.indexOf(after,bb)>-1){return true;}if(checkOptional&&optional&&YUI.ArrayUtil.indexOf(optional,bb)>-1){return true;}var ss=info[bb]&&info[bb].supersedes;if(ss){for(ii=0;ii<ss.length;ii=ii+1){if(requires(aa,ss[ii])){return true;}}}if(mm.ext&&mm.type=="css"&&!other.ext&&other.type=="css"){return true;}return false;};for(var i in this.required){if(lang.hasOwnProperty(this.required,i)){s.push(i);}}var p=0;for(;;){var l=s.length,a,b,j,k,moved=false;for(j=p;j<l;j=j+1){a=s[j];for(k=j+1;k<l;k=k+1){if(requires(a,s[k])){b=s.splice(k,1);s.splice(j,0,b[0]);moved=true;break;}}if(moved){break;}else{p=p+1;}}if(!moved){break;}}this.sorted=s;},toString:function(){var o={type:"YUILoader",base:this.base,filter:this.filter,required:this.required,loaded:this.loaded,inserted:this.inserted};lang.dump(o,1);},_combine:function(){this._combining=[];var self=this,s=this.sorted,len=s.length,js=this.comboBase,css=this.comboBase,target,startLen=js.length,i,m,type=this.loadType;YAHOO.log("type "+type);for(i=0;i<len;i=i+1){m=this.moduleInfo[s[i]];if(m&&!m.ext&&(!type||type===m.type)){target=this.root+m.path;target+="&";if(m.type=="js"){js+=target;}else{css+=target;}this._combining.push(s[i]);}}if(this._combining.length){YAHOO.log("Attempting to combine: "+this._combining,"info","loader");var callback=function(o){var c=this._combining,len=c.length,i,m;for(i=0;i<len;i=i+1){this.inserted[c[i]]=true;}this.loadNext(o.data);},loadScript=function(){if(js.length>startLen){YAHOO.util.Get.script(self._filter(js),{data:self._loading,onSuccess:callback,onFailure:self._onFailure,onTimeout:self._onTimeout,insertBefore:self.insertBefore,charset:self.charset,timeout:self.timeout,scope:self});}};if(css.length>startLen){YAHOO.util.Get.css(this._filter(css),{data:this._loading,onSuccess:loadScript,onFailure:this._onFailure,onTimeout:this._onTimeout,insertBefore:this.insertBefore,charset:this.charset,timeout:this.timeout,scope:self});}else{loadScript();}return;}else{this.loadNext(this._loading);}},insert:function(o,type){this.calculate(o);this._loading=true;this.loadType=type;if(this.combine){return this._combine();}if(!type){var self=this;this._internalCallback=function(){self._internalCallback=null;self.insert(null,"js");};this.insert(null,"css");return;}this.loadNext();},sandbox:function(o,type){this._config(o);if(!this.onSuccess){throw new Error("You must supply an onSuccess handler for your sandbox");}this._sandbox=true;var self=this;if(!type||type!=="js"){this._internalCallback=function(){self._internalCallback=null;self.sandbox(null,"js");};this.insert(null,"css");return;}if(!util.Connect){var ld=new YAHOO.util.YUILoader();ld.insert({base:this.base,filter:this.filter,require:"connection",insertBefore:this.insertBefore,charset:this.charset,onSuccess:function(){this.sandbox(null,"js");},scope:this},"js");return;}this._scriptText=[];this._loadCount=0;this._stopCount=this.sorted.length;this._xhr=[];this.calculate();var s=this.sorted,l=s.length,i,m,url;for(i=0;i<l;i=i+1){m=this.moduleInfo[s[i]];if(!m){this._onFailure("undefined module "+m);for(var j=0;j<this._xhr.length;j=j+1){this._xhr[j].abort();}return;}if(m.type!=="js"){this._loadCount++;continue;}url=m.fullpath;url=(url)?this._filter(url):this._url(m.path);var xhrData={success:function(o){var idx=o.argument[0],name=o.argument[2];this._scriptText[idx]=o.responseText;if(this.onProgress){this.onProgress.call(this.scope,{name:name,scriptText:o.responseText,xhrResponse:o,data:this.data});}this._loadCount++;if(this._loadCount>=this._stopCount){var v=this.varName||"YAHOO";var t="(function() {\n";var b="\nreturn "+v+";\n})();";var ref=eval(t+this._scriptText.join("\n")+b);this._pushEvents(ref);if(ref){this.onSuccess.call(this.scope,{reference:ref,data:this.data});}else{this._onFailure.call(this.varName+" reference failure");}}},failure:function(o){this.onFailure.call(this.scope,{msg:"XHR failure",xhrResponse:o,data:this.data});},scope:this,argument:[i,url,s[i]]};this._xhr.push(util.Connect.asyncRequest("GET",url,xhrData));}},loadNext:function(mname){if(!this._loading){return;}if(mname){if(mname!==this._loading){return;}this.inserted[mname]=true;if(this.onProgress){this.onProgress.call(this.scope,{name:mname,data:this.data});}}var s=this.sorted,len=s.length,i,m;for(i=0;i<len;i=i+1){if(s[i] in this.inserted){continue;}if(s[i]===this._loading){return;}m=this.moduleInfo[s[i]];if(!m){this.onFailure.call(this.scope,{msg:"undefined module "+m,data:this.data});return;}if(!this.loadType||this.loadType===m.type){this._loading=s[i];var fn=(m.type==="css")?util.Get.css:util.Get.script,url=m.fullpath,self=this,c=function(o){self.loadNext(o.data);};url=(url)?this._filter(url):this._url(m.path);if(env.ua.webkit&&env.ua.webkit<420&&m.type==="js"&&!m.varName){c=null;this._useYahooListener=true;}fn(url,{data:s[i],onSuccess:c,onFailure:this._onFailure,onTimeout:this._onTimeout,insertBefore:this.insertBefore,charset:this.charset,timeout:this.timeout,varName:m.varName,scope:self});return;}}this._loading=null;if(this._internalCallback){var f=this._internalCallback;this._internalCallback=null;f.call(this);}else{if(this.onSuccess){this._pushEvents();this.onSuccess.call(this.scope,{data:this.data});}}},_pushEvents:function(ref){var r=ref||YAHOO;if(r.util&&r.util.Event){r.util.Event._load();}},_filter:function(str){var f=this.filter;return(f)?str.replace(new RegExp(f.searchExp,"g"),f.replaceStr):str;},_url:function(path){return this._filter((this.base||"")+path);}};})();YAHOO.register("yuiloader",YAHOO.util.YUILoader,{version:"2.8.2r1",build:"7"});
diff --git a/js/yui/yuitest/yuitest-min.js b/js/yui/yuitest/yuitest-min.js
new file mode 100644
index 000000000..9c65bd9a2
--- /dev/null
+++ b/js/yui/yuitest/yuitest-min.js
@@ -0,0 +1,10 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+YAHOO.namespace("tool");(function(){var A=0;YAHOO.tool.TestCase=function(B){this._should={};for(var C in B){this[C]=B[C];}if(!YAHOO.lang.isString(this.name)){this.name="testCase"+(A++);}};YAHOO.tool.TestCase.prototype={resume:function(B){YAHOO.tool.TestRunner.resume(B);},wait:function(D,C){var B=arguments;if(YAHOO.lang.isFunction(B[0])){throw new YAHOO.tool.TestCase.Wait(B[0],B[1]);}else{throw new YAHOO.tool.TestCase.Wait(function(){YAHOO.util.Assert.fail("Timeout: wait() called but resume() never called.");},(YAHOO.lang.isNumber(B[0])?B[0]:10000));}},setUp:function(){},tearDown:function(){}};YAHOO.tool.TestCase.Wait=function(C,B){this.segment=(YAHOO.lang.isFunction(C)?C:null);this.delay=(YAHOO.lang.isNumber(B)?B:0);};})();YAHOO.namespace("tool");YAHOO.tool.TestSuite=function(A){this.name="";this.items=[];if(YAHOO.lang.isString(A)){this.name=A;}else{if(YAHOO.lang.isObject(A)){YAHOO.lang.augmentObject(this,A,true);}}if(this.name===""){this.name=YAHOO.util.Dom.generateId(null,"testSuite");}};YAHOO.tool.TestSuite.prototype={add:function(A){if(A instanceof YAHOO.tool.TestSuite||A instanceof YAHOO.tool.TestCase){this.items.push(A);}},setUp:function(){},tearDown:function(){}};YAHOO.namespace("tool");YAHOO.tool.TestRunner=(function(){function B(C){this.testObject=C;this.firstChild=null;this.lastChild=null;this.parent=null;this.next=null;this.results={passed:0,failed:0,total:0,ignored:0};if(C instanceof YAHOO.tool.TestSuite){this.results.type="testsuite";this.results.name=C.name;}else{if(C instanceof YAHOO.tool.TestCase){this.results.type="testcase";this.results.name=C.name;}}}B.prototype={appendChild:function(C){var D=new B(C);if(this.firstChild===null){this.firstChild=this.lastChild=D;}else{this.lastChild.next=D;this.lastChild=D;}D.parent=this;return D;}};function A(){A.superclass.constructor.apply(this,arguments);this.masterSuite=new YAHOO.tool.TestSuite("YUI Test Results");this._cur=null;this._root=null;var D=[this.TEST_CASE_BEGIN_EVENT,this.TEST_CASE_COMPLETE_EVENT,this.TEST_SUITE_BEGIN_EVENT,this.TEST_SUITE_COMPLETE_EVENT,this.TEST_PASS_EVENT,this.TEST_FAIL_EVENT,this.TEST_IGNORE_EVENT,this.COMPLETE_EVENT,this.BEGIN_EVENT];for(var C=0;C<D.length;C++){this.createEvent(D[C],{scope:this});}}YAHOO.lang.extend(A,YAHOO.util.EventProvider,{TEST_CASE_BEGIN_EVENT:"testcasebegin",TEST_CASE_COMPLETE_EVENT:"testcasecomplete",TEST_SUITE_BEGIN_EVENT:"testsuitebegin",TEST_SUITE_COMPLETE_EVENT:"testsuitecomplete",TEST_PASS_EVENT:"pass",TEST_FAIL_EVENT:"fail",TEST_IGNORE_EVENT:"ignore",COMPLETE_EVENT:"complete",BEGIN_EVENT:"begin",_addTestCaseToTestTree:function(C,D){var E=C.appendChild(D);for(var F in D){if(F.indexOf("test")===0&&YAHOO.lang.isFunction(D[F])){E.appendChild(F);}}},_addTestSuiteToTestTree:function(C,F){var E=C.appendChild(F);for(var D=0;D<F.items.length;D++){if(F.items[D] instanceof YAHOO.tool.TestSuite){this._addTestSuiteToTestTree(E,F.items[D]);}else{if(F.items[D] instanceof YAHOO.tool.TestCase){this._addTestCaseToTestTree(E,F.items[D]);}}}},_buildTestTree:function(){this._root=new B(this.masterSuite);this._cur=this._root;for(var C=0;C<this.masterSuite.items.length;C++){if(this.masterSuite.items[C] instanceof YAHOO.tool.TestSuite){this._addTestSuiteToTestTree(this._root,this.masterSuite.items[C]);}else{if(this.masterSuite.items[C] instanceof YAHOO.tool.TestCase){this._addTestCaseToTestTree(this._root,this.masterSuite.items[C]);}}}},_handleTestObjectComplete:function(C){if(YAHOO.lang.isObject(C.testObject)){C.parent.results.passed+=C.results.passed;C.parent.results.failed+=C.results.failed;C.parent.results.total+=C.results.total;C.parent.results.ignored+=C.results.ignored;C.parent.results[C.testObject.name]=C.results;if(C.testObject instanceof YAHOO.tool.TestSuite){C.testObject.tearDown();this.fireEvent(this.TEST_SUITE_COMPLETE_EVENT,{testSuite:C.testObject,results:C.results});}else{if(C.testObject instanceof YAHOO.tool.TestCase){this.fireEvent(this.TEST_CASE_COMPLETE_EVENT,{testCase:C.testObject,results:C.results});}}}},_next:function(){if(this._cur.firstChild){this._cur=this._cur.firstChild;}else{if(this._cur.next){this._cur=this._cur.next;}else{while(this._cur&&!this._cur.next&&this._cur!==this._root){this._handleTestObjectComplete(this._cur);this._cur=this._cur.parent;}if(this._cur==this._root){this._cur.results.type="report";this._cur.results.timestamp=(new Date()).toLocaleString();this._cur.results.duration=(new Date())-this._cur.results.duration;this.fireEvent(this.COMPLETE_EVENT,{results:this._cur.results});this._cur=null;}else{this._handleTestObjectComplete(this._cur);this._cur=this._cur.next;}}}return this._cur;},_run:function(){var E=false;var D=this._next();if(D!==null){var C=D.testObject;if(YAHOO.lang.isObject(C)){if(C instanceof YAHOO.tool.TestSuite){this.fireEvent(this.TEST_SUITE_BEGIN_EVENT,{testSuite:C});C.setUp();}else{if(C instanceof YAHOO.tool.TestCase){this.fireEvent(this.TEST_CASE_BEGIN_EVENT,{testCase:C});}}if(typeof setTimeout!="undefined"){setTimeout(function(){YAHOO.tool.TestRunner._run();},0);}else{this._run();}}else{this._runTest(D);}}},_resumeTest:function(G){var C=this._cur;var H=C.testObject;var E=C.parent.testObject;if(E.__yui_wait){clearTimeout(E.__yui_wait);delete E.__yui_wait;}var K=(E._should.fail||{})[H];var D=(E._should.error||{})[H];var F=false;var I=null;try{G.apply(E);if(K){I=new YAHOO.util.ShouldFail();F=true;}else{if(D){I=new YAHOO.util.ShouldError();F=true;}}}catch(J){if(J instanceof YAHOO.util.AssertionError){if(!K){I=J;F=true;}}else{if(J instanceof YAHOO.tool.TestCase.Wait){if(YAHOO.lang.isFunction(J.segment)){if(YAHOO.lang.isNumber(J.delay)){if(typeof setTimeout!="undefined"){E.__yui_wait=setTimeout(function(){YAHOO.tool.TestRunner._resumeTest(J.segment);},J.delay);}else{throw new Error("Asynchronous tests not supported in this environment.");}}}return;}else{if(!D){I=new YAHOO.util.UnexpectedError(J);F=true;}else{if(YAHOO.lang.isString(D)){if(J.message!=D){I=new YAHOO.util.UnexpectedError(J);F=true;}}else{if(YAHOO.lang.isFunction(D)){if(!(J instanceof D)){I=new YAHOO.util.UnexpectedError(J);
+F=true;}}else{if(YAHOO.lang.isObject(D)){if(!(J instanceof D.constructor)||J.message!=D.message){I=new YAHOO.util.UnexpectedError(J);F=true;}}}}}}}}if(F){this.fireEvent(this.TEST_FAIL_EVENT,{testCase:E,testName:H,error:I});}else{this.fireEvent(this.TEST_PASS_EVENT,{testCase:E,testName:H});}E.tearDown();C.parent.results[H]={result:F?"fail":"pass",message:I?I.getMessage():"Test passed",type:"test",name:H};if(F){C.parent.results.failed++;}else{C.parent.results.passed++;}C.parent.results.total++;if(typeof setTimeout!="undefined"){setTimeout(function(){YAHOO.tool.TestRunner._run();},0);}else{this._run();}},_runTest:function(F){var C=F.testObject;var D=F.parent.testObject;var G=D[C];var E=(D._should.ignore||{})[C];if(E){F.parent.results[C]={result:"ignore",message:"Test ignored",type:"test",name:C};F.parent.results.ignored++;F.parent.results.total++;this.fireEvent(this.TEST_IGNORE_EVENT,{testCase:D,testName:C});if(typeof setTimeout!="undefined"){setTimeout(function(){YAHOO.tool.TestRunner._run();},0);}else{this._run();}}else{D.setUp();this._resumeTest(G);}},fireEvent:function(C,D){D=D||{};D.type=C;A.superclass.fireEvent.call(this,C,D);},add:function(C){this.masterSuite.add(C);},clear:function(){this.masterSuite.items=[];},resume:function(C){this._resumeTest(C||function(){});},run:function(C){var D=YAHOO.tool.TestRunner;D._buildTestTree();D._root.results.duration=(new Date()).getTime();D.fireEvent(D.BEGIN_EVENT);D._run();}});return new A();})();YAHOO.namespace("util");YAHOO.util.Assert={_formatMessage:function(B,A){var C=B;if(YAHOO.lang.isString(B)&&B.length>0){return YAHOO.lang.substitute(B,{message:A});}else{return A;}},fail:function(A){throw new YAHOO.util.AssertionError(this._formatMessage(A,"Test force-failed."));},areEqual:function(B,C,A){if(B!=C){throw new YAHOO.util.ComparisonFailure(this._formatMessage(A,"Values should be equal."),B,C);}},areNotEqual:function(A,C,B){if(A==C){throw new YAHOO.util.UnexpectedValue(this._formatMessage(B,"Values should not be equal."),A);}},areNotSame:function(A,C,B){if(A===C){throw new YAHOO.util.UnexpectedValue(this._formatMessage(B,"Values should not be the same."),A);}},areSame:function(B,C,A){if(B!==C){throw new YAHOO.util.ComparisonFailure(this._formatMessage(A,"Values should be the same."),B,C);}},isFalse:function(B,A){if(false!==B){throw new YAHOO.util.ComparisonFailure(this._formatMessage(A,"Value should be false."),false,B);}},isTrue:function(B,A){if(true!==B){throw new YAHOO.util.ComparisonFailure(this._formatMessage(A,"Value should be true."),true,B);}},isNaN:function(B,A){if(!isNaN(B)){throw new YAHOO.util.ComparisonFailure(this._formatMessage(A,"Value should be NaN."),NaN,B);}},isNotNaN:function(B,A){if(isNaN(B)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(A,"Values should not be NaN."),NaN);}},isNotNull:function(B,A){if(YAHOO.lang.isNull(B)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(A,"Values should not be null."),null);}},isNotUndefined:function(B,A){if(YAHOO.lang.isUndefined(B)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(A,"Value should not be undefined."),undefined);}},isNull:function(B,A){if(!YAHOO.lang.isNull(B)){throw new YAHOO.util.ComparisonFailure(this._formatMessage(A,"Value should be null."),null,B);}},isUndefined:function(B,A){if(!YAHOO.lang.isUndefined(B)){throw new YAHOO.util.ComparisonFailure(this._formatMessage(A,"Value should be undefined."),undefined,B);}},isArray:function(B,A){if(!YAHOO.lang.isArray(B)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(A,"Value should be an array."),B);}},isBoolean:function(B,A){if(!YAHOO.lang.isBoolean(B)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(A,"Value should be a Boolean."),B);}},isFunction:function(B,A){if(!YAHOO.lang.isFunction(B)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(A,"Value should be a function."),B);}},isInstanceOf:function(B,C,A){if(!(C instanceof B)){throw new YAHOO.util.ComparisonFailure(this._formatMessage(A,"Value isn't an instance of expected type."),B,C);}},isNumber:function(B,A){if(!YAHOO.lang.isNumber(B)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(A,"Value should be a number."),B);}},isObject:function(B,A){if(!YAHOO.lang.isObject(B)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(A,"Value should be an object."),B);}},isString:function(B,A){if(!YAHOO.lang.isString(B)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(A,"Value should be a string."),B);}},isTypeOf:function(B,C,A){if(typeof C!=B){throw new YAHOO.util.ComparisonFailure(this._formatMessage(A,"Value should be of type "+B+"."),B,typeof C);}}};YAHOO.util.AssertionError=function(A){this.message=A;this.name="AssertionError";};YAHOO.lang.extend(YAHOO.util.AssertionError,Object,{getMessage:function(){return this.message;},toString:function(){return this.name+": "+this.getMessage();}});YAHOO.util.ComparisonFailure=function(B,A,C){YAHOO.util.AssertionError.call(this,B);this.expected=A;this.actual=C;this.name="ComparisonFailure";};YAHOO.lang.extend(YAHOO.util.ComparisonFailure,YAHOO.util.AssertionError,{getMessage:function(){return this.message+"\nExpected: "+this.expected+" ("+(typeof this.expected)+")"+"\nActual:"+this.actual+" ("+(typeof this.actual)+")";}});YAHOO.util.UnexpectedValue=function(B,A){YAHOO.util.AssertionError.call(this,B);this.unexpected=A;this.name="UnexpectedValue";};YAHOO.lang.extend(YAHOO.util.UnexpectedValue,YAHOO.util.AssertionError,{getMessage:function(){return this.message+"\nUnexpected: "+this.unexpected+" ("+(typeof this.unexpected)+") ";}});YAHOO.util.ShouldFail=function(A){YAHOO.util.AssertionError.call(this,A||"This test should fail but didn't.");this.name="ShouldFail";};YAHOO.lang.extend(YAHOO.util.ShouldFail,YAHOO.util.AssertionError);YAHOO.util.ShouldError=function(A){YAHOO.util.AssertionError.call(this,A||"This test should have thrown an error but didn't.");this.name="ShouldError";};YAHOO.lang.extend(YAHOO.util.ShouldError,YAHOO.util.AssertionError);YAHOO.util.UnexpectedError=function(A){YAHOO.util.AssertionError.call(this,"Unexpected error: "+A.message);
+this.cause=A;this.name="UnexpectedError";this.stack=A.stack;};YAHOO.lang.extend(YAHOO.util.UnexpectedError,YAHOO.util.AssertionError);YAHOO.util.ArrayAssert={contains:function(E,D,B){var C=false;var F=YAHOO.util.Assert;for(var A=0;A<D.length&&!C;A++){if(D[A]===E){C=true;}}if(!C){F.fail(F._formatMessage(B,"Value "+E+" ("+(typeof E)+") not found in array ["+D+"]."));}},containsItems:function(C,D,B){for(var A=0;A<C.length;A++){this.contains(C[A],D,B);}},containsMatch:function(E,D,B){if(typeof E!="function"){throw new TypeError("ArrayAssert.containsMatch(): First argument must be a function.");}var C=false;var F=YAHOO.util.Assert;for(var A=0;A<D.length&&!C;A++){if(E(D[A])){C=true;}}if(!C){F.fail(F._formatMessage(B,"No match found in array ["+D+"]."));}},doesNotContain:function(E,D,B){var C=false;var F=YAHOO.util.Assert;for(var A=0;A<D.length&&!C;A++){if(D[A]===E){C=true;}}if(C){F.fail(F._formatMessage(B,"Value found in array ["+D+"]."));}},doesNotContainItems:function(C,D,B){for(var A=0;A<C.length;A++){this.doesNotContain(C[A],D,B);}},doesNotContainMatch:function(E,D,B){if(typeof E!="function"){throw new TypeError("ArrayAssert.doesNotContainMatch(): First argument must be a function.");}var C=false;var F=YAHOO.util.Assert;for(var A=0;A<D.length&&!C;A++){if(E(D[A])){C=true;}}if(C){F.fail(F._formatMessage(B,"Value found in array ["+D+"]."));}},indexOf:function(E,D,A,C){for(var B=0;B<D.length;B++){if(D[B]===E){YAHOO.util.Assert.areEqual(A,B,C||"Value exists at index "+B+" but should be at index "+A+".");return;}}var F=YAHOO.util.Assert;F.fail(F._formatMessage(C,"Value doesn't exist in array ["+D+"]."));},itemsAreEqual:function(D,F,C){var A=Math.max(D.length,F.length||0);var E=YAHOO.util.Assert;for(var B=0;B<A;B++){E.areEqual(D[B],F[B],E._formatMessage(C,"Values in position "+B+" are not equal."));}},itemsAreEquivalent:function(E,F,B,D){if(typeof B!="function"){throw new TypeError("ArrayAssert.itemsAreEquivalent(): Third argument must be a function.");}var A=Math.max(E.length,F.length||0);for(var C=0;C<A;C++){if(!B(E[C],F[C])){throw new YAHOO.util.ComparisonFailure(YAHOO.util.Assert._formatMessage(D,"Values in position "+C+" are not equivalent."),E[C],F[C]);}}},isEmpty:function(C,A){if(C.length>0){var B=YAHOO.util.Assert;B.fail(B._formatMessage(A,"Array should be empty."));}},isNotEmpty:function(C,A){if(C.length===0){var B=YAHOO.util.Assert;B.fail(B._formatMessage(A,"Array should not be empty."));}},itemsAreSame:function(D,F,C){var A=Math.max(D.length,F.length||0);var E=YAHOO.util.Assert;for(var B=0;B<A;B++){E.areSame(D[B],F[B],E._formatMessage(C,"Values in position "+B+" are not the same."));}},lastIndexOf:function(E,D,A,C){var F=YAHOO.util.Assert;for(var B=D.length;B>=0;B--){if(D[B]===E){F.areEqual(A,B,F._formatMessage(C,"Value exists at index "+B+" but should be at index "+A+"."));return;}}F.fail(F._formatMessage(C,"Value doesn't exist in array."));}};YAHOO.namespace("util");YAHOO.util.ObjectAssert={propertiesAreEqual:function(D,G,C){var F=YAHOO.util.Assert;var B=[];for(var E in D){B.push(E);}for(var A=0;A<B.length;A++){F.isNotUndefined(G[B[A]],F._formatMessage(C,"Property '"+B[A]+"' expected."));}},hasProperty:function(A,B,C){if(!(A in B)){var D=YAHOO.util.Assert;D.fail(D._formatMessage(C,"Property '"+A+"' not found on object."));}},hasOwnProperty:function(A,B,C){if(!YAHOO.lang.hasOwnProperty(B,A)){var D=YAHOO.util.Assert;D.fail(D._formatMessage(C,"Property '"+A+"' not found on object instance."));}}};YAHOO.util.DateAssert={datesAreEqual:function(B,D,A){if(B instanceof Date&&D instanceof Date){var C=YAHOO.util.Assert;C.areEqual(B.getFullYear(),D.getFullYear(),C._formatMessage(A,"Years should be equal."));C.areEqual(B.getMonth(),D.getMonth(),C._formatMessage(A,"Months should be equal."));C.areEqual(B.getDate(),D.getDate(),C._formatMessage(A,"Day of month should be equal."));}else{throw new TypeError("DateAssert.datesAreEqual(): Expected and actual values must be Date objects.");}},timesAreEqual:function(B,D,A){if(B instanceof Date&&D instanceof Date){var C=YAHOO.util.Assert;C.areEqual(B.getHours(),D.getHours(),C._formatMessage(A,"Hours should be equal."));C.areEqual(B.getMinutes(),D.getMinutes(),C._formatMessage(A,"Minutes should be equal."));C.areEqual(B.getSeconds(),D.getSeconds(),C._formatMessage(A,"Seconds should be equal."));}else{throw new TypeError("DateAssert.timesAreEqual(): Expected and actual values must be Date objects.");}}};YAHOO.namespace("tool");YAHOO.tool.TestManager={TEST_PAGE_BEGIN_EVENT:"testpagebegin",TEST_PAGE_COMPLETE_EVENT:"testpagecomplete",TEST_MANAGER_BEGIN_EVENT:"testmanagerbegin",TEST_MANAGER_COMPLETE_EVENT:"testmanagercomplete",_curPage:null,_frame:null,_logger:null,_timeoutId:0,_pages:[],_results:null,_handleTestRunnerComplete:function(A){this.fireEvent(this.TEST_PAGE_COMPLETE_EVENT,{page:this._curPage,results:A.results});this._processResults(this._curPage,A.results);this._logger.clearTestRunner();if(this._pages.length){this._timeoutId=setTimeout(function(){YAHOO.tool.TestManager._run();},1000);}else{this.fireEvent(this.TEST_MANAGER_COMPLETE_EVENT,this._results);}},_processResults:function(C,A){var B=this._results;B.passed+=A.passed;B.failed+=A.failed;B.ignored+=A.ignored;B.total+=A.total;B.duration+=A.duration;if(A.failed){B.failedPages.push(C);}else{B.passedPages.push(C);}A.name=C;A.type="page";B[C]=A;},_run:function(){this._curPage=this._pages.shift();this.fireEvent(this.TEST_PAGE_BEGIN_EVENT,this._curPage);this._frame.location.replace(this._curPage);},load:function(){if(parent.YAHOO.tool.TestManager!==this){parent.YAHOO.tool.TestManager.load();}else{if(this._frame){var A=this._frame.YAHOO.tool.TestRunner;this._logger.setTestRunner(A);A.subscribe(A.COMPLETE_EVENT,this._handleTestRunnerComplete,this,true);A.run();}}},setPages:function(A){this._pages=A;},start:function(){if(!this._initialized){this.createEvent(this.TEST_PAGE_BEGIN_EVENT);this.createEvent(this.TEST_PAGE_COMPLETE_EVENT);this.createEvent(this.TEST_MANAGER_BEGIN_EVENT);this.createEvent(this.TEST_MANAGER_COMPLETE_EVENT);
+if(!this._frame){var A=document.createElement("iframe");A.style.visibility="hidden";A.style.position="absolute";document.body.appendChild(A);this._frame=A.contentWindow||A.contentDocument.parentWindow;}if(!this._logger){this._logger=new YAHOO.tool.TestLogger();}this._initialized=true;}this._results={passed:0,failed:0,ignored:0,total:0,type:"report",name:"YUI Test Results",duration:0,failedPages:[],passedPages:[]};this.fireEvent(this.TEST_MANAGER_BEGIN_EVENT,null);this._run();},stop:function(){clearTimeout(this._timeoutId);}};YAHOO.lang.augmentObject(YAHOO.tool.TestManager,YAHOO.util.EventProvider.prototype);YAHOO.namespace("tool");YAHOO.tool.TestLogger=function(B,A){YAHOO.tool.TestLogger.superclass.constructor.call(this,B,A);this.init();};YAHOO.lang.extend(YAHOO.tool.TestLogger,YAHOO.widget.LogReader,{footerEnabled:true,newestOnTop:false,formatMsg:function(B){var A=B.category;var C=this.html2Text(B.msg);return'<pre><p><span class="'+A+'">'+A.toUpperCase()+"</span> "+C+"</p></pre>";},init:function(){if(YAHOO.tool.TestRunner){this.setTestRunner(YAHOO.tool.TestRunner);}this.hideSource("global");this.hideSource("LogReader");this.hideCategory("warn");this.hideCategory("window");this.hideCategory("time");this.clearConsole();},clearTestRunner:function(){if(this._runner){this._runner.unsubscribeAll();this._runner=null;}},setTestRunner:function(A){if(this._runner){this.clearTestRunner();}this._runner=A;A.subscribe(A.TEST_PASS_EVENT,this._handleTestRunnerEvent,this,true);A.subscribe(A.TEST_FAIL_EVENT,this._handleTestRunnerEvent,this,true);A.subscribe(A.TEST_IGNORE_EVENT,this._handleTestRunnerEvent,this,true);A.subscribe(A.BEGIN_EVENT,this._handleTestRunnerEvent,this,true);A.subscribe(A.COMPLETE_EVENT,this._handleTestRunnerEvent,this,true);A.subscribe(A.TEST_SUITE_BEGIN_EVENT,this._handleTestRunnerEvent,this,true);A.subscribe(A.TEST_SUITE_COMPLETE_EVENT,this._handleTestRunnerEvent,this,true);A.subscribe(A.TEST_CASE_BEGIN_EVENT,this._handleTestRunnerEvent,this,true);A.subscribe(A.TEST_CASE_COMPLETE_EVENT,this._handleTestRunnerEvent,this,true);},_handleTestRunnerEvent:function(D){var A=YAHOO.tool.TestRunner;var C="";var B="";switch(D.type){case A.BEGIN_EVENT:C="Testing began at "+(new Date()).toString()+".";B="info";break;case A.COMPLETE_EVENT:C="Testing completed at "+(new Date()).toString()+".\nPassed:"+D.results.passed+" Failed:"+D.results.failed+" Total:"+D.results.total;B="info";break;case A.TEST_FAIL_EVENT:C=D.testName+": "+D.error.getMessage();B="fail";break;case A.TEST_IGNORE_EVENT:C=D.testName+": ignored.";B="ignore";break;case A.TEST_PASS_EVENT:C=D.testName+": passed.";B="pass";break;case A.TEST_SUITE_BEGIN_EVENT:C='Test suite "'+D.testSuite.name+'" started.';B="info";break;case A.TEST_SUITE_COMPLETE_EVENT:C='Test suite "'+D.testSuite.name+'" completed.\nPassed:'+D.results.passed+" Failed:"+D.results.failed+" Total:"+D.results.total;B="info";break;case A.TEST_CASE_BEGIN_EVENT:C='Test case "'+D.testCase.name+'" started.';B="info";break;case A.TEST_CASE_COMPLETE_EVENT:C='Test case "'+D.testCase.name+'" completed.\nPassed:'+D.results.passed+" Failed:"+D.results.failed+" Total:"+D.results.total;B="info";break;default:C="Unexpected event "+D.type;C="info";}YAHOO.log(C,B,"TestRunner");}});YAHOO.namespace("tool.TestFormat");YAHOO.tool.TestFormat.JSON=function(A){return YAHOO.lang.JSON.stringify(A);};YAHOO.tool.TestFormat.XML=function(C){var A=YAHOO.lang;var B="<"+C.type+' name="'+C.name.replace(/"/g,"&quot;").replace(/'/g,"&apos;")+'"';if(A.isNumber(C.duration)){B+=' duration="'+C.duration+'"';}if(C.type=="test"){B+=' result="'+C.result+'" message="'+C.message+'">';}else{B+=' passed="'+C.passed+'" failed="'+C.failed+'" ignored="'+C.ignored+'" total="'+C.total+'">';for(var D in C){if(A.hasOwnProperty(C,D)&&A.isObject(C[D])&&!A.isArray(C[D])){B+=arguments.callee(C[D]);}}}B+="</"+C.type+">";return B;};YAHOO.namespace("tool");YAHOO.tool.TestReporter=function(A,B){this.url=A;this.format=B||YAHOO.tool.TestFormat.XML;this._fields=new Object();this._form=null;this._iframe=null;};YAHOO.tool.TestReporter.prototype={constructor:YAHOO.tool.TestReporter,_convertToISOString:function(A){function B(C){return C<10?"0"+C:C;}return A.getUTCFullYear()+"-"+B(A.getUTCMonth()+1)+"-"+B(A.getUTCDate())+"T"+B(A.getUTCHours())+":"+B(A.getUTCMinutes())+":"+B(A.getUTCSeconds())+"Z";},addField:function(A,B){this._fields[A]=B;},clearFields:function(){this._fields=new Object();},destroy:function(){if(this._form){this._form.parentNode.removeChild(this._form);this._form=null;}if(this._iframe){this._iframe.parentNode.removeChild(this._iframe);this._iframe=null;}this._fields=null;},report:function(A){if(!this._form){this._form=document.createElement("form");this._form.method="post";this._form.style.visibility="hidden";this._form.style.position="absolute";this._form.style.top=0;document.body.appendChild(this._form);if(YAHOO.env.ua.ie){this._iframe=document.createElement('<iframe name="yuiTestTarget" />');}else{this._iframe=document.createElement("iframe");this._iframe.name="yuiTestTarget";}this._iframe.src="javascript:false";this._iframe.style.visibility="hidden";this._iframe.style.position="absolute";this._iframe.style.top=0;document.body.appendChild(this._iframe);this._form.target="yuiTestTarget";}this._form.action=this.url;while(this._form.hasChildNodes()){this._form.removeChild(this._form.lastChild);}this._fields.results=this.format(A);this._fields.useragent=navigator.userAgent;this._fields.timestamp=this._convertToISOString(new Date());for(var B in this._fields){if(YAHOO.lang.hasOwnProperty(this._fields,B)&&typeof this._fields[B]!="function"){if(YAHOO.env.ua.ie){input=document.createElement('<input name="'+B+'" >');}else{input=document.createElement("input");input.name=B;}input.type="hidden";input.value=this._fields[B];this._form.appendChild(input);}}delete this._fields.results;delete this._fields.useragent;delete this._fields.timestamp;if(arguments[1]!==false){this._form.submit();}}};YAHOO.register("yuitest",YAHOO.tool.TestRunner,{version:"2.8.2r1",build:"7"});
diff --git a/js/yui/yuitest/yuitest_core-min.js b/js/yui/yuitest/yuitest_core-min.js
new file mode 100644
index 000000000..3308d278b
--- /dev/null
+++ b/js/yui/yuitest/yuitest_core-min.js
@@ -0,0 +1,9 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.8.2r1
+*/
+YAHOO.namespace("tool");(function(){var A=0;YAHOO.tool.TestCase=function(B){this._should={};for(var C in B){this[C]=B[C];}if(!YAHOO.lang.isString(this.name)){this.name="testCase"+(A++);}};YAHOO.tool.TestCase.prototype={resume:function(B){YAHOO.tool.TestRunner.resume(B);},wait:function(D,C){var B=arguments;if(YAHOO.lang.isFunction(B[0])){throw new YAHOO.tool.TestCase.Wait(B[0],B[1]);}else{throw new YAHOO.tool.TestCase.Wait(function(){YAHOO.util.Assert.fail("Timeout: wait() called but resume() never called.");},(YAHOO.lang.isNumber(B[0])?B[0]:10000));}},setUp:function(){},tearDown:function(){}};YAHOO.tool.TestCase.Wait=function(C,B){this.segment=(YAHOO.lang.isFunction(C)?C:null);this.delay=(YAHOO.lang.isNumber(B)?B:0);};})();YAHOO.namespace("tool");YAHOO.tool.TestSuite=function(A){this.name="";this.items=[];if(YAHOO.lang.isString(A)){this.name=A;}else{if(YAHOO.lang.isObject(A)){YAHOO.lang.augmentObject(this,A,true);}}if(this.name===""){this.name=YAHOO.util.Dom.generateId(null,"testSuite");}};YAHOO.tool.TestSuite.prototype={add:function(A){if(A instanceof YAHOO.tool.TestSuite||A instanceof YAHOO.tool.TestCase){this.items.push(A);}},setUp:function(){},tearDown:function(){}};YAHOO.namespace("tool");YAHOO.tool.TestRunner=(function(){function B(C){this.testObject=C;this.firstChild=null;this.lastChild=null;this.parent=null;this.next=null;this.results={passed:0,failed:0,total:0,ignored:0};if(C instanceof YAHOO.tool.TestSuite){this.results.type="testsuite";this.results.name=C.name;}else{if(C instanceof YAHOO.tool.TestCase){this.results.type="testcase";this.results.name=C.name;}}}B.prototype={appendChild:function(C){var D=new B(C);if(this.firstChild===null){this.firstChild=this.lastChild=D;}else{this.lastChild.next=D;this.lastChild=D;}D.parent=this;return D;}};function A(){A.superclass.constructor.apply(this,arguments);this.masterSuite=new YAHOO.tool.TestSuite("YUI Test Results");this._cur=null;this._root=null;var D=[this.TEST_CASE_BEGIN_EVENT,this.TEST_CASE_COMPLETE_EVENT,this.TEST_SUITE_BEGIN_EVENT,this.TEST_SUITE_COMPLETE_EVENT,this.TEST_PASS_EVENT,this.TEST_FAIL_EVENT,this.TEST_IGNORE_EVENT,this.COMPLETE_EVENT,this.BEGIN_EVENT];for(var C=0;C<D.length;C++){this.createEvent(D[C],{scope:this});}}YAHOO.lang.extend(A,YAHOO.util.EventProvider,{TEST_CASE_BEGIN_EVENT:"testcasebegin",TEST_CASE_COMPLETE_EVENT:"testcasecomplete",TEST_SUITE_BEGIN_EVENT:"testsuitebegin",TEST_SUITE_COMPLETE_EVENT:"testsuitecomplete",TEST_PASS_EVENT:"pass",TEST_FAIL_EVENT:"fail",TEST_IGNORE_EVENT:"ignore",COMPLETE_EVENT:"complete",BEGIN_EVENT:"begin",_addTestCaseToTestTree:function(C,D){var E=C.appendChild(D);for(var F in D){if(F.indexOf("test")===0&&YAHOO.lang.isFunction(D[F])){E.appendChild(F);}}},_addTestSuiteToTestTree:function(C,F){var E=C.appendChild(F);for(var D=0;D<F.items.length;D++){if(F.items[D] instanceof YAHOO.tool.TestSuite){this._addTestSuiteToTestTree(E,F.items[D]);}else{if(F.items[D] instanceof YAHOO.tool.TestCase){this._addTestCaseToTestTree(E,F.items[D]);}}}},_buildTestTree:function(){this._root=new B(this.masterSuite);this._cur=this._root;for(var C=0;C<this.masterSuite.items.length;C++){if(this.masterSuite.items[C] instanceof YAHOO.tool.TestSuite){this._addTestSuiteToTestTree(this._root,this.masterSuite.items[C]);}else{if(this.masterSuite.items[C] instanceof YAHOO.tool.TestCase){this._addTestCaseToTestTree(this._root,this.masterSuite.items[C]);}}}},_handleTestObjectComplete:function(C){if(YAHOO.lang.isObject(C.testObject)){C.parent.results.passed+=C.results.passed;C.parent.results.failed+=C.results.failed;C.parent.results.total+=C.results.total;C.parent.results.ignored+=C.results.ignored;C.parent.results[C.testObject.name]=C.results;if(C.testObject instanceof YAHOO.tool.TestSuite){C.testObject.tearDown();this.fireEvent(this.TEST_SUITE_COMPLETE_EVENT,{testSuite:C.testObject,results:C.results});}else{if(C.testObject instanceof YAHOO.tool.TestCase){this.fireEvent(this.TEST_CASE_COMPLETE_EVENT,{testCase:C.testObject,results:C.results});}}}},_next:function(){if(this._cur.firstChild){this._cur=this._cur.firstChild;}else{if(this._cur.next){this._cur=this._cur.next;}else{while(this._cur&&!this._cur.next&&this._cur!==this._root){this._handleTestObjectComplete(this._cur);this._cur=this._cur.parent;}if(this._cur==this._root){this._cur.results.type="report";this._cur.results.timestamp=(new Date()).toLocaleString();this._cur.results.duration=(new Date())-this._cur.results.duration;this.fireEvent(this.COMPLETE_EVENT,{results:this._cur.results});this._cur=null;}else{this._handleTestObjectComplete(this._cur);this._cur=this._cur.next;}}}return this._cur;},_run:function(){var E=false;var D=this._next();if(D!==null){var C=D.testObject;if(YAHOO.lang.isObject(C)){if(C instanceof YAHOO.tool.TestSuite){this.fireEvent(this.TEST_SUITE_BEGIN_EVENT,{testSuite:C});C.setUp();}else{if(C instanceof YAHOO.tool.TestCase){this.fireEvent(this.TEST_CASE_BEGIN_EVENT,{testCase:C});}}if(typeof setTimeout!="undefined"){setTimeout(function(){YAHOO.tool.TestRunner._run();},0);}else{this._run();}}else{this._runTest(D);}}},_resumeTest:function(G){var C=this._cur;var H=C.testObject;var E=C.parent.testObject;if(E.__yui_wait){clearTimeout(E.__yui_wait);delete E.__yui_wait;}var K=(E._should.fail||{})[H];var D=(E._should.error||{})[H];var F=false;var I=null;try{G.apply(E);if(K){I=new YAHOO.util.ShouldFail();F=true;}else{if(D){I=new YAHOO.util.ShouldError();F=true;}}}catch(J){if(J instanceof YAHOO.util.AssertionError){if(!K){I=J;F=true;}}else{if(J instanceof YAHOO.tool.TestCase.Wait){if(YAHOO.lang.isFunction(J.segment)){if(YAHOO.lang.isNumber(J.delay)){if(typeof setTimeout!="undefined"){E.__yui_wait=setTimeout(function(){YAHOO.tool.TestRunner._resumeTest(J.segment);},J.delay);}else{throw new Error("Asynchronous tests not supported in this environment.");}}}return;}else{if(!D){I=new YAHOO.util.UnexpectedError(J);F=true;}else{if(YAHOO.lang.isString(D)){if(J.message!=D){I=new YAHOO.util.UnexpectedError(J);F=true;}}else{if(YAHOO.lang.isFunction(D)){if(!(J instanceof D)){I=new YAHOO.util.UnexpectedError(J);
+F=true;}}else{if(YAHOO.lang.isObject(D)){if(!(J instanceof D.constructor)||J.message!=D.message){I=new YAHOO.util.UnexpectedError(J);F=true;}}}}}}}}if(F){this.fireEvent(this.TEST_FAIL_EVENT,{testCase:E,testName:H,error:I});}else{this.fireEvent(this.TEST_PASS_EVENT,{testCase:E,testName:H});}E.tearDown();C.parent.results[H]={result:F?"fail":"pass",message:I?I.getMessage():"Test passed",type:"test",name:H};if(F){C.parent.results.failed++;}else{C.parent.results.passed++;}C.parent.results.total++;if(typeof setTimeout!="undefined"){setTimeout(function(){YAHOO.tool.TestRunner._run();},0);}else{this._run();}},_runTest:function(F){var C=F.testObject;var D=F.parent.testObject;var G=D[C];var E=(D._should.ignore||{})[C];if(E){F.parent.results[C]={result:"ignore",message:"Test ignored",type:"test",name:C};F.parent.results.ignored++;F.parent.results.total++;this.fireEvent(this.TEST_IGNORE_EVENT,{testCase:D,testName:C});if(typeof setTimeout!="undefined"){setTimeout(function(){YAHOO.tool.TestRunner._run();},0);}else{this._run();}}else{D.setUp();this._resumeTest(G);}},fireEvent:function(C,D){D=D||{};D.type=C;A.superclass.fireEvent.call(this,C,D);},add:function(C){this.masterSuite.add(C);},clear:function(){this.masterSuite.items=[];},resume:function(C){this._resumeTest(C||function(){});},run:function(C){var D=YAHOO.tool.TestRunner;D._buildTestTree();D._root.results.duration=(new Date()).getTime();D.fireEvent(D.BEGIN_EVENT);D._run();}});return new A();})();YAHOO.namespace("util");YAHOO.util.Assert={_formatMessage:function(B,A){var C=B;if(YAHOO.lang.isString(B)&&B.length>0){return YAHOO.lang.substitute(B,{message:A});}else{return A;}},fail:function(A){throw new YAHOO.util.AssertionError(this._formatMessage(A,"Test force-failed."));},areEqual:function(B,C,A){if(B!=C){throw new YAHOO.util.ComparisonFailure(this._formatMessage(A,"Values should be equal."),B,C);}},areNotEqual:function(A,C,B){if(A==C){throw new YAHOO.util.UnexpectedValue(this._formatMessage(B,"Values should not be equal."),A);}},areNotSame:function(A,C,B){if(A===C){throw new YAHOO.util.UnexpectedValue(this._formatMessage(B,"Values should not be the same."),A);}},areSame:function(B,C,A){if(B!==C){throw new YAHOO.util.ComparisonFailure(this._formatMessage(A,"Values should be the same."),B,C);}},isFalse:function(B,A){if(false!==B){throw new YAHOO.util.ComparisonFailure(this._formatMessage(A,"Value should be false."),false,B);}},isTrue:function(B,A){if(true!==B){throw new YAHOO.util.ComparisonFailure(this._formatMessage(A,"Value should be true."),true,B);}},isNaN:function(B,A){if(!isNaN(B)){throw new YAHOO.util.ComparisonFailure(this._formatMessage(A,"Value should be NaN."),NaN,B);}},isNotNaN:function(B,A){if(isNaN(B)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(A,"Values should not be NaN."),NaN);}},isNotNull:function(B,A){if(YAHOO.lang.isNull(B)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(A,"Values should not be null."),null);}},isNotUndefined:function(B,A){if(YAHOO.lang.isUndefined(B)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(A,"Value should not be undefined."),undefined);}},isNull:function(B,A){if(!YAHOO.lang.isNull(B)){throw new YAHOO.util.ComparisonFailure(this._formatMessage(A,"Value should be null."),null,B);}},isUndefined:function(B,A){if(!YAHOO.lang.isUndefined(B)){throw new YAHOO.util.ComparisonFailure(this._formatMessage(A,"Value should be undefined."),undefined,B);}},isArray:function(B,A){if(!YAHOO.lang.isArray(B)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(A,"Value should be an array."),B);}},isBoolean:function(B,A){if(!YAHOO.lang.isBoolean(B)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(A,"Value should be a Boolean."),B);}},isFunction:function(B,A){if(!YAHOO.lang.isFunction(B)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(A,"Value should be a function."),B);}},isInstanceOf:function(B,C,A){if(!(C instanceof B)){throw new YAHOO.util.ComparisonFailure(this._formatMessage(A,"Value isn't an instance of expected type."),B,C);}},isNumber:function(B,A){if(!YAHOO.lang.isNumber(B)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(A,"Value should be a number."),B);}},isObject:function(B,A){if(!YAHOO.lang.isObject(B)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(A,"Value should be an object."),B);}},isString:function(B,A){if(!YAHOO.lang.isString(B)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(A,"Value should be a string."),B);}},isTypeOf:function(B,C,A){if(typeof C!=B){throw new YAHOO.util.ComparisonFailure(this._formatMessage(A,"Value should be of type "+B+"."),B,typeof C);}}};YAHOO.util.AssertionError=function(A){this.message=A;this.name="AssertionError";};YAHOO.lang.extend(YAHOO.util.AssertionError,Object,{getMessage:function(){return this.message;},toString:function(){return this.name+": "+this.getMessage();}});YAHOO.util.ComparisonFailure=function(B,A,C){YAHOO.util.AssertionError.call(this,B);this.expected=A;this.actual=C;this.name="ComparisonFailure";};YAHOO.lang.extend(YAHOO.util.ComparisonFailure,YAHOO.util.AssertionError,{getMessage:function(){return this.message+"\nExpected: "+this.expected+" ("+(typeof this.expected)+")"+"\nActual:"+this.actual+" ("+(typeof this.actual)+")";}});YAHOO.util.UnexpectedValue=function(B,A){YAHOO.util.AssertionError.call(this,B);this.unexpected=A;this.name="UnexpectedValue";};YAHOO.lang.extend(YAHOO.util.UnexpectedValue,YAHOO.util.AssertionError,{getMessage:function(){return this.message+"\nUnexpected: "+this.unexpected+" ("+(typeof this.unexpected)+") ";}});YAHOO.util.ShouldFail=function(A){YAHOO.util.AssertionError.call(this,A||"This test should fail but didn't.");this.name="ShouldFail";};YAHOO.lang.extend(YAHOO.util.ShouldFail,YAHOO.util.AssertionError);YAHOO.util.ShouldError=function(A){YAHOO.util.AssertionError.call(this,A||"This test should have thrown an error but didn't.");this.name="ShouldError";};YAHOO.lang.extend(YAHOO.util.ShouldError,YAHOO.util.AssertionError);YAHOO.util.UnexpectedError=function(A){YAHOO.util.AssertionError.call(this,"Unexpected error: "+A.message);
+this.cause=A;this.name="UnexpectedError";this.stack=A.stack;};YAHOO.lang.extend(YAHOO.util.UnexpectedError,YAHOO.util.AssertionError);YAHOO.util.ArrayAssert={contains:function(E,D,B){var C=false;var F=YAHOO.util.Assert;for(var A=0;A<D.length&&!C;A++){if(D[A]===E){C=true;}}if(!C){F.fail(F._formatMessage(B,"Value "+E+" ("+(typeof E)+") not found in array ["+D+"]."));}},containsItems:function(C,D,B){for(var A=0;A<C.length;A++){this.contains(C[A],D,B);}},containsMatch:function(E,D,B){if(typeof E!="function"){throw new TypeError("ArrayAssert.containsMatch(): First argument must be a function.");}var C=false;var F=YAHOO.util.Assert;for(var A=0;A<D.length&&!C;A++){if(E(D[A])){C=true;}}if(!C){F.fail(F._formatMessage(B,"No match found in array ["+D+"]."));}},doesNotContain:function(E,D,B){var C=false;var F=YAHOO.util.Assert;for(var A=0;A<D.length&&!C;A++){if(D[A]===E){C=true;}}if(C){F.fail(F._formatMessage(B,"Value found in array ["+D+"]."));}},doesNotContainItems:function(C,D,B){for(var A=0;A<C.length;A++){this.doesNotContain(C[A],D,B);}},doesNotContainMatch:function(E,D,B){if(typeof E!="function"){throw new TypeError("ArrayAssert.doesNotContainMatch(): First argument must be a function.");}var C=false;var F=YAHOO.util.Assert;for(var A=0;A<D.length&&!C;A++){if(E(D[A])){C=true;}}if(C){F.fail(F._formatMessage(B,"Value found in array ["+D+"]."));}},indexOf:function(E,D,A,C){for(var B=0;B<D.length;B++){if(D[B]===E){YAHOO.util.Assert.areEqual(A,B,C||"Value exists at index "+B+" but should be at index "+A+".");return;}}var F=YAHOO.util.Assert;F.fail(F._formatMessage(C,"Value doesn't exist in array ["+D+"]."));},itemsAreEqual:function(D,F,C){var A=Math.max(D.length,F.length||0);var E=YAHOO.util.Assert;for(var B=0;B<A;B++){E.areEqual(D[B],F[B],E._formatMessage(C,"Values in position "+B+" are not equal."));}},itemsAreEquivalent:function(E,F,B,D){if(typeof B!="function"){throw new TypeError("ArrayAssert.itemsAreEquivalent(): Third argument must be a function.");}var A=Math.max(E.length,F.length||0);for(var C=0;C<A;C++){if(!B(E[C],F[C])){throw new YAHOO.util.ComparisonFailure(YAHOO.util.Assert._formatMessage(D,"Values in position "+C+" are not equivalent."),E[C],F[C]);}}},isEmpty:function(C,A){if(C.length>0){var B=YAHOO.util.Assert;B.fail(B._formatMessage(A,"Array should be empty."));}},isNotEmpty:function(C,A){if(C.length===0){var B=YAHOO.util.Assert;B.fail(B._formatMessage(A,"Array should not be empty."));}},itemsAreSame:function(D,F,C){var A=Math.max(D.length,F.length||0);var E=YAHOO.util.Assert;for(var B=0;B<A;B++){E.areSame(D[B],F[B],E._formatMessage(C,"Values in position "+B+" are not the same."));}},lastIndexOf:function(E,D,A,C){var F=YAHOO.util.Assert;for(var B=D.length;B>=0;B--){if(D[B]===E){F.areEqual(A,B,F._formatMessage(C,"Value exists at index "+B+" but should be at index "+A+"."));return;}}F.fail(F._formatMessage(C,"Value doesn't exist in array."));}};YAHOO.namespace("util");YAHOO.util.ObjectAssert={propertiesAreEqual:function(D,G,C){var F=YAHOO.util.Assert;var B=[];for(var E in D){B.push(E);}for(var A=0;A<B.length;A++){F.isNotUndefined(G[B[A]],F._formatMessage(C,"Property '"+B[A]+"' expected."));}},hasProperty:function(A,B,C){if(!(A in B)){var D=YAHOO.util.Assert;D.fail(D._formatMessage(C,"Property '"+A+"' not found on object."));}},hasOwnProperty:function(A,B,C){if(!YAHOO.lang.hasOwnProperty(B,A)){var D=YAHOO.util.Assert;D.fail(D._formatMessage(C,"Property '"+A+"' not found on object instance."));}}};YAHOO.util.DateAssert={datesAreEqual:function(B,D,A){if(B instanceof Date&&D instanceof Date){var C=YAHOO.util.Assert;C.areEqual(B.getFullYear(),D.getFullYear(),C._formatMessage(A,"Years should be equal."));C.areEqual(B.getMonth(),D.getMonth(),C._formatMessage(A,"Months should be equal."));C.areEqual(B.getDate(),D.getDate(),C._formatMessage(A,"Day of month should be equal."));}else{throw new TypeError("DateAssert.datesAreEqual(): Expected and actual values must be Date objects.");}},timesAreEqual:function(B,D,A){if(B instanceof Date&&D instanceof Date){var C=YAHOO.util.Assert;C.areEqual(B.getHours(),D.getHours(),C._formatMessage(A,"Hours should be equal."));C.areEqual(B.getMinutes(),D.getMinutes(),C._formatMessage(A,"Minutes should be equal."));C.areEqual(B.getSeconds(),D.getSeconds(),C._formatMessage(A,"Seconds should be equal."));}else{throw new TypeError("DateAssert.timesAreEqual(): Expected and actual values must be Date objects.");}}};YAHOO.register("yuitest_core",YAHOO.tool.TestRunner,{version:"2.8.2r1",build:"7"}); \ No newline at end of file
diff --git a/jsonrpc.cgi b/jsonrpc.cgi
new file mode 100755
index 000000000..ad910e79e
--- /dev/null
+++ b/jsonrpc.cgi
@@ -0,0 +1,41 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla JSON Webservices Interface.
+#
+# The Initial Developer of the Original Code is the San Jose State
+# University Foundation. Portions created by the Initial Developer
+# are Copyright (C) 2008 the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+use strict;
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::WebService::Constants;
+BEGIN {
+ if (!Bugzilla->feature('jsonrpc')) {
+ ThrowCodeError('feature_disabled', { feature => 'jsonrpc' });
+ }
+}
+use Bugzilla::WebService::Server::JSONRPC;
+
+Bugzilla->usage_mode(USAGE_MODE_JSON);
+
+local @INC = (bz_locations()->{extensionsdir}, @INC);
+my $server = new Bugzilla::WebService::Server::JSONRPC;
+$server->dispatch(WS_DISPATCH)->handle();
diff --git a/lib/CGI.pm b/lib/CGI.pm
new file mode 100644
index 000000000..eed7e6b5e
--- /dev/null
+++ b/lib/CGI.pm
@@ -0,0 +1,8149 @@
+package CGI;
+require 5.006;
+use Carp 'croak';
+
+# See the bottom of this file for the POD documentation. Search for the
+# string '=head'.
+
+# You can run this file through either pod2man or pod2html to produce pretty
+# documentation in manual or html file format (these utilities are part of the
+# Perl 5 distribution).
+
+# Copyright 1995-1998 Lincoln D. Stein. All rights reserved.
+# It may be used and modified freely, but I do request that this copyright
+# notice remain attached to the file. You may modify this module as you
+# wish, but if you redistribute a modified version, please attach a note
+# listing the modifications you have made.
+
+# The most recent version and complete docs are available at:
+# http://search.cpan.org/dist/CGI.pm
+
+# The revision is no longer being updated since moving to git.
+$CGI::revision = '$Id: CGI.pm,v 1.266 2009/07/30 16:32:34 lstein Exp $';
+$CGI::VERSION='3.52';
+
+# HARD-CODED LOCATION FOR FILE UPLOAD TEMPORARY FILES.
+# UNCOMMENT THIS ONLY IF YOU KNOW WHAT YOU'RE DOING.
+# $CGITempFile::TMPDIRECTORY = '/usr/tmp';
+use CGI::Util qw(rearrange rearrange_header make_attributes unescape escape expires ebcdic2ascii ascii2ebcdic);
+
+#use constant XHTML_DTD => ['-//W3C//DTD XHTML Basic 1.0//EN',
+# 'http://www.w3.org/TR/xhtml-basic/xhtml-basic10.dtd'];
+
+use constant XHTML_DTD => ['-//W3C//DTD XHTML 1.0 Transitional//EN',
+ 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'];
+
+{
+ local $^W = 0;
+ $TAINTED = substr("$0$^X",0,0);
+}
+
+$MOD_PERL = 0; # no mod_perl by default
+
+#global settings
+$POST_MAX = -1; # no limit to uploaded files
+$DISABLE_UPLOADS = 0;
+
+@SAVED_SYMBOLS = ();
+
+
+# >>>>> Here are some globals that you might want to adjust <<<<<<
+sub initialize_globals {
+ # Set this to 1 to enable copious autoloader debugging messages
+ $AUTOLOAD_DEBUG = 0;
+
+ # Set this to 1 to generate XTML-compatible output
+ $XHTML = 1;
+
+ # Change this to the preferred DTD to print in start_html()
+ # or use default_dtd('text of DTD to use');
+ $DEFAULT_DTD = [ '-//W3C//DTD HTML 4.01 Transitional//EN',
+ 'http://www.w3.org/TR/html4/loose.dtd' ] ;
+
+ # Set this to 1 to enable NOSTICKY scripts
+ # or:
+ # 1) use CGI '-nosticky';
+ # 2) $CGI::NOSTICKY = 1;
+ $NOSTICKY = 0;
+
+ # Set this to 1 to enable NPH scripts
+ # or:
+ # 1) use CGI qw(-nph)
+ # 2) CGI::nph(1)
+ # 3) print header(-nph=>1)
+ $NPH = 0;
+
+ # Set this to 1 to enable debugging from @ARGV
+ # Set to 2 to enable debugging from STDIN
+ $DEBUG = 1;
+
+ # Set this to 1 to make the temporary files created
+ # during file uploads safe from prying eyes
+ # or do...
+ # 1) use CGI qw(:private_tempfiles)
+ # 2) CGI::private_tempfiles(1);
+ $PRIVATE_TEMPFILES = 0;
+
+ # Set this to 1 to generate automatic tab indexes
+ $TABINDEX = 0;
+
+ # Set this to 1 to cause files uploaded in multipart documents
+ # to be closed, instead of caching the file handle
+ # or:
+ # 1) use CGI qw(:close_upload_files)
+ # 2) $CGI::close_upload_files(1);
+ # Uploads with many files run out of file handles.
+ # Also, for performance, since the file is already on disk,
+ # it can just be renamed, instead of read and written.
+ $CLOSE_UPLOAD_FILES = 0;
+
+ # Automatically determined -- don't change
+ $EBCDIC = 0;
+
+ # Change this to 1 to suppress redundant HTTP headers
+ $HEADERS_ONCE = 0;
+
+ # separate the name=value pairs by semicolons rather than ampersands
+ $USE_PARAM_SEMICOLONS = 1;
+
+ # Do not include undefined params parsed from query string
+ # use CGI qw(-no_undef_params);
+ $NO_UNDEF_PARAMS = 0;
+
+ # return everything as utf-8
+ $PARAM_UTF8 = 0;
+
+ # Other globals that you shouldn't worry about.
+ undef $Q;
+ $BEEN_THERE = 0;
+ $DTD_PUBLIC_IDENTIFIER = "";
+ undef @QUERY_PARAM;
+ undef %EXPORT;
+ undef $QUERY_CHARSET;
+ undef %QUERY_FIELDNAMES;
+ undef %QUERY_TMPFILES;
+
+ # prevent complaints by mod_perl
+ 1;
+}
+
+# ------------------ START OF THE LIBRARY ------------
+
+#### Method: endform
+# This method is DEPRECATED
+*endform = \&end_form;
+
+# make mod_perlhappy
+initialize_globals();
+
+# FIGURE OUT THE OS WE'RE RUNNING UNDER
+# Some systems support the $^O variable. If not
+# available then require() the Config library
+unless ($OS) {
+ unless ($OS = $^O) {
+ require Config;
+ $OS = $Config::Config{'osname'};
+ }
+}
+if ($OS =~ /^MSWin/i) {
+ $OS = 'WINDOWS';
+} elsif ($OS =~ /^VMS/i) {
+ $OS = 'VMS';
+} elsif ($OS =~ /^dos/i) {
+ $OS = 'DOS';
+} elsif ($OS =~ /^MacOS/i) {
+ $OS = 'MACINTOSH';
+} elsif ($OS =~ /^os2/i) {
+ $OS = 'OS2';
+} elsif ($OS =~ /^epoc/i) {
+ $OS = 'EPOC';
+} elsif ($OS =~ /^cygwin/i) {
+ $OS = 'CYGWIN';
+} elsif ($OS =~ /^NetWare/i) {
+ $OS = 'NETWARE';
+} else {
+ $OS = 'UNIX';
+}
+
+# Some OS logic. Binary mode enabled on DOS, NT and VMS
+$needs_binmode = $OS=~/^(WINDOWS|DOS|OS2|MSWin|CYGWIN|NETWARE)/;
+
+# This is the default class for the CGI object to use when all else fails.
+$DefaultClass = 'CGI' unless defined $CGI::DefaultClass;
+
+# This is where to look for autoloaded routines.
+$AutoloadClass = $DefaultClass unless defined $CGI::AutoloadClass;
+
+# The path separator is a slash, backslash or semicolon, depending
+# on the paltform.
+$SL = {
+ UNIX => '/', OS2 => '\\', EPOC => '/', CYGWIN => '/', NETWARE => '/',
+ WINDOWS => '\\', DOS => '\\', MACINTOSH => ':', VMS => '/'
+ }->{$OS};
+
+# This no longer seems to be necessary
+# Turn on NPH scripts by default when running under IIS server!
+# $NPH++ if defined($ENV{'SERVER_SOFTWARE'}) && $ENV{'SERVER_SOFTWARE'}=~/IIS/;
+$IIS++ if defined($ENV{'SERVER_SOFTWARE'}) && $ENV{'SERVER_SOFTWARE'}=~/IIS/;
+
+# Turn on special checking for ActiveState's PerlEx
+$PERLEX++ if defined($ENV{'GATEWAY_INTERFACE'}) && $ENV{'GATEWAY_INTERFACE'} =~ /^CGI-PerlEx/;
+
+# Turn on special checking for Doug MacEachern's modperl
+# PerlEx::DBI tries to fool DBI by setting MOD_PERL
+if (exists $ENV{MOD_PERL} && ! $PERLEX) {
+ # mod_perl handlers may run system() on scripts using CGI.pm;
+ # Make sure so we don't get fooled by inherited $ENV{MOD_PERL}
+ if (exists $ENV{MOD_PERL_API_VERSION} && $ENV{MOD_PERL_API_VERSION} == 2) {
+ $MOD_PERL = 2;
+ require Apache2::Response;
+ require Apache2::RequestRec;
+ require Apache2::RequestUtil;
+ require Apache2::RequestIO;
+ require APR::Pool;
+ } else {
+ $MOD_PERL = 1;
+ require Apache;
+ }
+}
+
+# Define the CRLF sequence. I can't use a simple "\r\n" because the meaning
+# of "\n" is different on different OS's (sometimes it generates CRLF, sometimes LF
+# and sometimes CR). The most popular VMS web server
+# doesn't accept CRLF -- instead it wants a LR. EBCDIC machines don't
+# use ASCII, so \015\012 means something different. I find this all
+# really annoying.
+$EBCDIC = "\t" ne "\011";
+if ($OS eq 'VMS') {
+ $CRLF = "\n";
+} elsif ($EBCDIC) {
+ $CRLF= "\r\n";
+} else {
+ $CRLF = "\015\012";
+}
+
+if ($needs_binmode) {
+ $CGI::DefaultClass->binmode(\*main::STDOUT);
+ $CGI::DefaultClass->binmode(\*main::STDIN);
+ $CGI::DefaultClass->binmode(\*main::STDERR);
+}
+
+%EXPORT_TAGS = (
+ ':html2'=>['h1'..'h6',qw/p br hr ol ul li dl dt dd menu code var strong em
+ tt u i b blockquote pre img a address cite samp dfn html head
+ base body Link nextid title meta kbd start_html end_html
+ input Select option comment charset escapeHTML/],
+ ':html3'=>[qw/div table caption th td TR Tr sup Sub strike applet Param nobr
+ embed basefont style span layer ilayer font frameset frame script small big Area Map/],
+ ':html4'=>[qw/abbr acronym bdo col colgroup del fieldset iframe
+ ins label legend noframes noscript object optgroup Q
+ thead tbody tfoot/],
+ ':netscape'=>[qw/blink fontsize center/],
+ ':form'=>[qw/textfield textarea filefield password_field hidden checkbox checkbox_group
+ submit reset defaults radio_group popup_menu button autoEscape
+ scrolling_list image_button start_form end_form startform endform
+ start_multipart_form end_multipart_form isindex tmpFileName uploadInfo URL_ENCODED MULTIPART/],
+ ':cgi'=>[qw/param upload path_info path_translated request_uri url self_url script_name
+ cookie Dump
+ raw_cookie request_method query_string Accept user_agent remote_host content_type
+ remote_addr referer server_name server_software server_port server_protocol virtual_port
+ virtual_host remote_ident auth_type http append
+ save_parameters restore_parameters param_fetch
+ remote_user user_name header redirect import_names put
+ Delete Delete_all url_param cgi_error/],
+ ':ssl' => [qw/https/],
+ ':cgi-lib' => [qw/ReadParse PrintHeader HtmlTop HtmlBot SplitParam Vars/],
+ ':html' => [qw/:html2 :html3 :html4 :netscape/],
+ ':standard' => [qw/:html2 :html3 :html4 :form :cgi/],
+ ':push' => [qw/multipart_init multipart_start multipart_end multipart_final/],
+ ':all' => [qw/:html2 :html3 :netscape :form :cgi :internal :html4/]
+ );
+
+# Custom 'can' method for both autoloaded and non-autoloaded subroutines.
+# Author: Cees Hek <cees@sitesuite.com.au>
+
+sub can {
+ my($class, $method) = @_;
+
+ # See if UNIVERSAL::can finds it.
+
+ if (my $func = $class -> SUPER::can($method) ){
+ return $func;
+ }
+
+ # Try to compile the function.
+
+ eval {
+ # _compile looks at $AUTOLOAD for the function name.
+
+ local $AUTOLOAD = join "::", $class, $method;
+ &_compile;
+ };
+
+ # Now that the function is loaded (if it exists)
+ # just use UNIVERSAL::can again to do the work.
+
+ return $class -> SUPER::can($method);
+}
+
+# to import symbols into caller
+sub import {
+ my $self = shift;
+
+ # This causes modules to clash.
+ undef %EXPORT_OK;
+ undef %EXPORT;
+
+ $self->_setup_symbols(@_);
+ my ($callpack, $callfile, $callline) = caller;
+
+ # To allow overriding, search through the packages
+ # Till we find one in which the correct subroutine is defined.
+ my @packages = ($self,@{"$self\:\:ISA"});
+ for $sym (keys %EXPORT) {
+ my $pck;
+ my $def = ${"$self\:\:AutoloadClass"} || $DefaultClass;
+ for $pck (@packages) {
+ if (defined(&{"$pck\:\:$sym"})) {
+ $def = $pck;
+ last;
+ }
+ }
+ *{"${callpack}::$sym"} = \&{"$def\:\:$sym"};
+ }
+}
+
+sub compile {
+ my $pack = shift;
+ $pack->_setup_symbols('-compile',@_);
+}
+
+sub expand_tags {
+ my($tag) = @_;
+ return ("start_$1","end_$1") if $tag=~/^(?:\*|start_|end_)(.+)/;
+ my(@r);
+ return ($tag) unless $EXPORT_TAGS{$tag};
+ for (@{$EXPORT_TAGS{$tag}}) {
+ push(@r,&expand_tags($_));
+ }
+ return @r;
+}
+
+#### Method: new
+# The new routine. This will check the current environment
+# for an existing query string, and initialize itself, if so.
+####
+sub new {
+ my($class,@initializer) = @_;
+ my $self = {};
+
+ bless $self,ref $class || $class || $DefaultClass;
+
+ # always use a tempfile
+ $self->{'use_tempfile'} = 1;
+
+ if (ref($initializer[0])
+ && (UNIVERSAL::isa($initializer[0],'Apache')
+ ||
+ UNIVERSAL::isa($initializer[0],'Apache2::RequestRec')
+ )) {
+ $self->r(shift @initializer);
+ }
+ if (ref($initializer[0])
+ && (UNIVERSAL::isa($initializer[0],'CODE'))) {
+ $self->upload_hook(shift @initializer, shift @initializer);
+ $self->{'use_tempfile'} = shift @initializer if (@initializer > 0);
+ }
+ if ($MOD_PERL) {
+ if ($MOD_PERL == 1) {
+ $self->r(Apache->request) unless $self->r;
+ my $r = $self->r;
+ $r->register_cleanup(\&CGI::_reset_globals);
+ $self->_setup_symbols(@SAVED_SYMBOLS) if @SAVED_SYMBOLS;
+ }
+ else {
+ # XXX: once we have the new API
+ # will do a real PerlOptions -SetupEnv check
+ $self->r(Apache2::RequestUtil->request) unless $self->r;
+ my $r = $self->r;
+ $r->subprocess_env unless exists $ENV{REQUEST_METHOD};
+ $r->pool->cleanup_register(\&CGI::_reset_globals);
+ $self->_setup_symbols(@SAVED_SYMBOLS) if @SAVED_SYMBOLS;
+ }
+ undef $NPH;
+ }
+ $self->_reset_globals if $PERLEX;
+ $self->init(@initializer);
+ return $self;
+}
+
+# We provide a DESTROY method so that we can ensure that
+# temporary files are closed (via Fh->DESTROY) before they
+# are unlinked (via CGITempFile->DESTROY) because it is not
+# possible to unlink an open file on Win32. We explicitly
+# call DESTROY on each, rather than just undefing them and
+# letting Perl DESTROY them by garbage collection, in case the
+# user is still holding any reference to them as well.
+sub DESTROY {
+ my $self = shift;
+ if ($OS eq 'WINDOWS') {
+ for my $href (values %{$self->{'.tmpfiles'}}) {
+ $href->{hndl}->DESTROY if defined $href->{hndl};
+ $href->{name}->DESTROY if defined $href->{name};
+ }
+ }
+}
+
+sub r {
+ my $self = shift;
+ my $r = $self->{'.r'};
+ $self->{'.r'} = shift if @_;
+ $r;
+}
+
+sub upload_hook {
+ my $self;
+ if (ref $_[0] eq 'CODE') {
+ $CGI::Q = $self = $CGI::DefaultClass->new(@_);
+ } else {
+ $self = shift;
+ }
+ my ($hook,$data,$use_tempfile) = @_;
+ $self->{'.upload_hook'} = $hook;
+ $self->{'.upload_data'} = $data;
+ $self->{'use_tempfile'} = $use_tempfile if defined $use_tempfile;
+}
+
+#### Method: param
+# Returns the value(s)of a named parameter.
+# If invoked in a list context, returns the
+# entire list. Otherwise returns the first
+# member of the list.
+# If name is not provided, return a list of all
+# the known parameters names available.
+# If more than one argument is provided, the
+# second and subsequent arguments are used to
+# set the value of the parameter.
+####
+sub param {
+ my($self,@p) = self_or_default(@_);
+ return $self->all_parameters unless @p;
+ my($name,$value,@other);
+
+ # For compatibility between old calling style and use_named_parameters() style,
+ # we have to special case for a single parameter present.
+ if (@p > 1) {
+ ($name,$value,@other) = rearrange([NAME,[DEFAULT,VALUE,VALUES]],@p);
+ my(@values);
+
+ if (substr($p[0],0,1) eq '-') {
+ @values = defined($value) ? (ref($value) && ref($value) eq 'ARRAY' ? @{$value} : $value) : ();
+ } else {
+ for ($value,@other) {
+ push(@values,$_) if defined($_);
+ }
+ }
+ # If values is provided, then we set it.
+ if (@values or defined $value) {
+ $self->add_parameter($name);
+ $self->{param}{$name}=[@values];
+ }
+ } else {
+ $name = $p[0];
+ }
+
+ return unless defined($name) && $self->{param}{$name};
+
+ my @result = @{$self->{param}{$name}};
+
+ if ($PARAM_UTF8) {
+ eval "require Encode; 1;" unless Encode->can('decode'); # bring in these functions
+ @result = map {ref $_ ? $_ : $self->_decode_utf8($_) } @result;
+ }
+
+ return wantarray ? @result : $result[0];
+}
+
+sub _decode_utf8 {
+ my ($self, $val) = @_;
+
+ if (Encode::is_utf8($val)) {
+ return $val;
+ }
+ else {
+ return Encode::decode(utf8 => $val);
+ }
+}
+
+sub self_or_default {
+ return @_ if defined($_[0]) && (!ref($_[0])) &&($_[0] eq 'CGI');
+ unless (defined($_[0]) &&
+ (ref($_[0]) eq 'CGI' || UNIVERSAL::isa($_[0],'CGI')) # slightly optimized for common case
+ ) {
+ $Q = $CGI::DefaultClass->new unless defined($Q);
+ unshift(@_,$Q);
+ }
+ return wantarray ? @_ : $Q;
+}
+
+sub self_or_CGI {
+ local $^W=0; # prevent a warning
+ if (defined($_[0]) &&
+ (substr(ref($_[0]),0,3) eq 'CGI'
+ || UNIVERSAL::isa($_[0],'CGI'))) {
+ return @_;
+ } else {
+ return ($DefaultClass,@_);
+ }
+}
+
+########################################
+# THESE METHODS ARE MORE OR LESS PRIVATE
+# GO TO THE __DATA__ SECTION TO SEE MORE
+# PUBLIC METHODS
+########################################
+
+# Initialize the query object from the environment.
+# If a parameter list is found, this object will be set
+# to a hash in which parameter names are keys
+# and the values are stored as lists
+# If a keyword list is found, this method creates a bogus
+# parameter list with the single parameter 'keywords'.
+
+sub init {
+ my $self = shift;
+ my($query_string,$meth,$content_length,$fh,@lines) = ('','','','');
+
+ my $is_xforms;
+
+ my $initializer = shift; # for backward compatibility
+ local($/) = "\n";
+
+ # set autoescaping on by default
+ $self->{'escape'} = 1;
+
+ # if we get called more than once, we want to initialize
+ # ourselves from the original query (which may be gone
+ # if it was read from STDIN originally.)
+ if (defined(@QUERY_PARAM) && !defined($initializer)) {
+ for my $name (@QUERY_PARAM) {
+ my $val = $QUERY_PARAM{$name}; # always an arrayref;
+ $self->param('-name'=>$name,'-value'=> $val);
+ if (defined $val and ref $val eq 'ARRAY') {
+ for my $fh (grep {defined(fileno($_))} @$val) {
+ seek($fh,0,0); # reset the filehandle.
+ }
+
+ }
+ }
+ $self->charset($QUERY_CHARSET);
+ $self->{'.fieldnames'} = {%QUERY_FIELDNAMES};
+ $self->{'.tmpfiles'} = {%QUERY_TMPFILES};
+ return;
+ }
+
+ $meth=$ENV{'REQUEST_METHOD'} if defined($ENV{'REQUEST_METHOD'});
+ $content_length = defined($ENV{'CONTENT_LENGTH'}) ? $ENV{'CONTENT_LENGTH'} : 0;
+
+ $fh = to_filehandle($initializer) if $initializer;
+
+ # set charset to the safe ISO-8859-1
+ $self->charset('ISO-8859-1');
+
+ METHOD: {
+
+ # avoid unreasonably large postings
+ if (($POST_MAX > 0) && ($content_length > $POST_MAX)) {
+ #discard the post, unread
+ $self->cgi_error("413 Request entity too large");
+ last METHOD;
+ }
+
+ # Process multipart postings, but only if the initializer is
+ # not defined.
+ if ($meth eq 'POST'
+ && defined($ENV{'CONTENT_TYPE'})
+ && $ENV{'CONTENT_TYPE'}=~m|^multipart/form-data|
+ && !defined($initializer)
+ ) {
+ my($boundary) = $ENV{'CONTENT_TYPE'} =~ /boundary=\"?([^\";,]+)\"?/;
+ $self->read_multipart($boundary,$content_length);
+ last METHOD;
+ }
+
+ # Process XForms postings. We know that we have XForms in the
+ # following cases:
+ # method eq 'POST' && content-type eq 'application/xml'
+ # method eq 'POST' && content-type =~ /multipart\/related.+start=/
+ # There are more cases, actually, but for now, we don't support other
+ # methods for XForm posts.
+ # In a XForm POST, the QUERY_STRING is parsed normally.
+ # If the content-type is 'application/xml', we just set the param
+ # XForms:Model (referring to the xml syntax) param containing the
+ # unparsed XML data.
+ # In the case of multipart/related we set XForms:Model as above, but
+ # the other parts are available as uploads with the Content-ID as the
+ # the key.
+ # See the URL below for XForms specs on this issue.
+ # http://www.w3.org/TR/2006/REC-xforms-20060314/slice11.html#submit-options
+ if ($meth eq 'POST' && defined($ENV{'CONTENT_TYPE'})) {
+ if ($ENV{'CONTENT_TYPE'} eq 'application/xml') {
+ my($param) = 'XForms:Model';
+ my($value) = '';
+ $self->add_parameter($param);
+ $self->read_from_client(\$value,$content_length,0)
+ if $content_length > 0;
+ push (@{$self->{param}{$param}},$value);
+ $is_xforms = 1;
+ } elsif ($ENV{'CONTENT_TYPE'} =~ /multipart\/related.+boundary=\"?([^\";,]+)\"?.+start=\"?\<?([^\"\>]+)\>?\"?/) {
+ my($boundary,$start) = ($1,$2);
+ my($param) = 'XForms:Model';
+ $self->add_parameter($param);
+ my($value) = $self->read_multipart_related($start,$boundary,$content_length,0);
+ push (@{$self->{param}{$param}},$value);
+ if ($MOD_PERL) {
+ $query_string = $self->r->args;
+ } else {
+ $query_string = $ENV{'QUERY_STRING'} if defined $ENV{'QUERY_STRING'};
+ $query_string ||= $ENV{'REDIRECT_QUERY_STRING'} if defined $ENV{'REDIRECT_QUERY_STRING'};
+ }
+ $is_xforms = 1;
+ }
+ }
+
+
+ # If initializer is defined, then read parameters
+ # from it.
+ if (!$is_xforms && defined($initializer)) {
+ if (UNIVERSAL::isa($initializer,'CGI')) {
+ $query_string = $initializer->query_string;
+ last METHOD;
+ }
+ if (ref($initializer) && ref($initializer) eq 'HASH') {
+ for (keys %$initializer) {
+ $self->param('-name'=>$_,'-value'=>$initializer->{$_});
+ }
+ last METHOD;
+ }
+
+ if (defined($fh) && ($fh ne '')) {
+ while (my $line = <$fh>) {
+ chomp $line;
+ last if $line =~ /^=$/;
+ push(@lines,$line);
+ }
+ # massage back into standard format
+ if ("@lines" =~ /=/) {
+ $query_string=join("&",@lines);
+ } else {
+ $query_string=join("+",@lines);
+ }
+ last METHOD;
+ }
+
+ # last chance -- treat it as a string
+ $initializer = $$initializer if ref($initializer) eq 'SCALAR';
+ $query_string = $initializer;
+
+ last METHOD;
+ }
+
+ # If method is GET or HEAD, fetch the query from
+ # the environment.
+ if ($is_xforms || $meth=~/^(GET|HEAD)$/) {
+ if ($MOD_PERL) {
+ $query_string = $self->r->args;
+ } else {
+ $query_string = $ENV{'QUERY_STRING'} if defined $ENV{'QUERY_STRING'};
+ $query_string ||= $ENV{'REDIRECT_QUERY_STRING'} if defined $ENV{'REDIRECT_QUERY_STRING'};
+ }
+ last METHOD;
+ }
+
+ if ($meth eq 'POST' || $meth eq 'PUT') {
+ if ( $content_length > 0 ) {
+ $self->read_from_client(\$query_string,$content_length,0);
+ }
+ elsif (not defined $ENV{CONTENT_LENGTH}) {
+ $self->read_from_stdin(\$query_string);
+ # should this be PUTDATA in case of PUT ?
+ my($param) = $meth . 'DATA' ;
+ $self->add_parameter($param) ;
+ push (@{$self->{param}{$param}},$query_string);
+ undef $query_string ;
+ }
+ # Some people want to have their cake and eat it too!
+ # Uncomment this line to have the contents of the query string
+ # APPENDED to the POST data.
+ # $query_string .= (length($query_string) ? '&' : '') . $ENV{'QUERY_STRING'} if defined $ENV{'QUERY_STRING'};
+ last METHOD;
+ }
+
+ # If $meth is not of GET, POST, PUT or HEAD, assume we're
+ # being debugged offline.
+ # Check the command line and then the standard input for data.
+ # We use the shellwords package in order to behave the way that
+ # UN*X programmers expect.
+ if ($DEBUG)
+ {
+ my $cmdline_ret = read_from_cmdline();
+ $query_string = $cmdline_ret->{'query_string'};
+ if (defined($cmdline_ret->{'subpath'}))
+ {
+ $self->path_info($cmdline_ret->{'subpath'});
+ }
+ }
+ }
+
+# YL: Begin Change for XML handler 10/19/2001
+ if (!$is_xforms && ($meth eq 'POST' || $meth eq 'PUT')
+ && defined($ENV{'CONTENT_TYPE'})
+ && $ENV{'CONTENT_TYPE'} !~ m|^application/x-www-form-urlencoded|
+ && $ENV{'CONTENT_TYPE'} !~ m|^multipart/form-data| ) {
+ my($param) = $meth . 'DATA' ;
+ $self->add_parameter($param) ;
+ push (@{$self->{param}{$param}},$query_string);
+ undef $query_string ;
+ }
+# YL: End Change for XML handler 10/19/2001
+
+ # We now have the query string in hand. We do slightly
+ # different things for keyword lists and parameter lists.
+ if (defined $query_string && length $query_string) {
+ if ($query_string =~ /[&=;]/) {
+ $self->parse_params($query_string);
+ } else {
+ $self->add_parameter('keywords');
+ $self->{param}{'keywords'} = [$self->parse_keywordlist($query_string)];
+ }
+ }
+
+ # Special case. Erase everything if there is a field named
+ # .defaults.
+ if ($self->param('.defaults')) {
+ $self->delete_all();
+ }
+
+ # hash containing our defined fieldnames
+ $self->{'.fieldnames'} = {};
+ for ($self->param('.cgifields')) {
+ $self->{'.fieldnames'}->{$_}++;
+ }
+
+ # Clear out our default submission button flag if present
+ $self->delete('.submit');
+ $self->delete('.cgifields');
+
+ $self->save_request unless defined $initializer;
+}
+
+# FUNCTIONS TO OVERRIDE:
+# Turn a string into a filehandle
+sub to_filehandle {
+ my $thingy = shift;
+ return undef unless $thingy;
+ return $thingy if UNIVERSAL::isa($thingy,'GLOB');
+ return $thingy if UNIVERSAL::isa($thingy,'FileHandle');
+ if (!ref($thingy)) {
+ my $caller = 1;
+ while (my $package = caller($caller++)) {
+ my($tmp) = $thingy=~/[\':]/ ? $thingy : "$package\:\:$thingy";
+ return $tmp if defined(fileno($tmp));
+ }
+ }
+ return undef;
+}
+
+# send output to the browser
+sub put {
+ my($self,@p) = self_or_default(@_);
+ $self->print(@p);
+}
+
+# print to standard output (for overriding in mod_perl)
+sub print {
+ shift;
+ CORE::print(@_);
+}
+
+# get/set last cgi_error
+sub cgi_error {
+ my ($self,$err) = self_or_default(@_);
+ $self->{'.cgi_error'} = $err if defined $err;
+ return $self->{'.cgi_error'};
+}
+
+sub save_request {
+ my($self) = @_;
+ # We're going to play with the package globals now so that if we get called
+ # again, we initialize ourselves in exactly the same way. This allows
+ # us to have several of these objects.
+ @QUERY_PARAM = $self->param; # save list of parameters
+ for (@QUERY_PARAM) {
+ next unless defined $_;
+ $QUERY_PARAM{$_}=$self->{param}{$_};
+ }
+ $QUERY_CHARSET = $self->charset;
+ %QUERY_FIELDNAMES = %{$self->{'.fieldnames'}};
+ %QUERY_TMPFILES = %{ $self->{'.tmpfiles'} || {} };
+}
+
+sub parse_params {
+ my($self,$tosplit) = @_;
+ my(@pairs) = split(/[&;]/,$tosplit);
+ my($param,$value);
+ for (@pairs) {
+ ($param,$value) = split('=',$_,2);
+ next unless defined $param;
+ next if $NO_UNDEF_PARAMS and not defined $value;
+ $value = '' unless defined $value;
+ $param = unescape($param);
+ $value = unescape($value);
+ $self->add_parameter($param);
+ push (@{$self->{param}{$param}},$value);
+ }
+}
+
+sub add_parameter {
+ my($self,$param)=@_;
+ return unless defined $param;
+ push (@{$self->{'.parameters'}},$param)
+ unless defined($self->{param}{$param});
+}
+
+sub all_parameters {
+ my $self = shift;
+ return () unless defined($self) && $self->{'.parameters'};
+ return () unless @{$self->{'.parameters'}};
+ return @{$self->{'.parameters'}};
+}
+
+# put a filehandle into binary mode (DOS)
+sub binmode {
+ return unless defined($_[1]) && defined fileno($_[1]);
+ CORE::binmode($_[1]);
+}
+
+sub _make_tag_func {
+ my ($self,$tagname) = @_;
+ my $func = qq(
+ sub $tagname {
+ my (\$q,\$a,\@rest) = self_or_default(\@_);
+ my(\$attr) = '';
+ if (ref(\$a) && ref(\$a) eq 'HASH') {
+ my(\@attr) = make_attributes(\$a,\$q->{'escape'});
+ \$attr = " \@attr" if \@attr;
+ } else {
+ unshift \@rest,\$a if defined \$a;
+ }
+ );
+ if ($tagname=~/start_(\w+)/i) {
+ $func .= qq! return "<\L$1\E\$attr>";} !;
+ } elsif ($tagname=~/end_(\w+)/i) {
+ $func .= qq! return "<\L/$1\E>"; } !;
+ } else {
+ $func .= qq#
+ return \$XHTML ? "\L<$tagname\E\$attr />" : "\L<$tagname\E\$attr>" unless \@rest;
+ my(\$tag,\$untag) = ("\L<$tagname\E\$attr>","\L</$tagname>\E");
+ my \@result = map { "\$tag\$_\$untag" }
+ (ref(\$rest[0]) eq 'ARRAY') ? \@{\$rest[0]} : "\@rest";
+ return "\@result";
+ }#;
+ }
+return $func;
+}
+
+sub AUTOLOAD {
+ print STDERR "CGI::AUTOLOAD for $AUTOLOAD\n" if $CGI::AUTOLOAD_DEBUG;
+ my $func = &_compile;
+ goto &$func;
+}
+
+sub _compile {
+ my($func) = $AUTOLOAD;
+ my($pack,$func_name);
+ {
+ local($1,$2); # this fixes an obscure variable suicide problem.
+ $func=~/(.+)::([^:]+)$/;
+ ($pack,$func_name) = ($1,$2);
+ $pack=~s/::SUPER$//; # fix another obscure problem
+ $pack = ${"$pack\:\:AutoloadClass"} || $CGI::DefaultClass
+ unless defined(${"$pack\:\:AUTOLOADED_ROUTINES"});
+
+ my($sub) = \%{"$pack\:\:SUBS"};
+ unless (%$sub) {
+ my($auto) = \${"$pack\:\:AUTOLOADED_ROUTINES"};
+ local ($@,$!);
+ eval "package $pack; $$auto";
+ croak("$AUTOLOAD: $@") if $@;
+ $$auto = ''; # Free the unneeded storage (but don't undef it!!!)
+ }
+ my($code) = $sub->{$func_name};
+
+ $code = "sub $AUTOLOAD { }" if (!$code and $func_name eq 'DESTROY');
+ if (!$code) {
+ (my $base = $func_name) =~ s/^(start_|end_)//i;
+ if ($EXPORT{':any'} ||
+ $EXPORT{'-any'} ||
+ $EXPORT{$base} ||
+ (%EXPORT_OK || grep(++$EXPORT_OK{$_},&expand_tags(':html')))
+ && $EXPORT_OK{$base}) {
+ $code = $CGI::DefaultClass->_make_tag_func($func_name);
+ }
+ }
+ croak("Undefined subroutine $AUTOLOAD\n") unless $code;
+ local ($@,$!);
+ eval "package $pack; $code";
+ if ($@) {
+ $@ =~ s/ at .*\n//;
+ croak("$AUTOLOAD: $@");
+ }
+ }
+ CORE::delete($sub->{$func_name}); #free storage
+ return "$pack\:\:$func_name";
+}
+
+sub _selected {
+ my $self = shift;
+ my $value = shift;
+ return '' unless $value;
+ return $XHTML ? qq(selected="selected" ) : qq(selected );
+}
+
+sub _checked {
+ my $self = shift;
+ my $value = shift;
+ return '' unless $value;
+ return $XHTML ? qq(checked="checked" ) : qq(checked );
+}
+
+sub _reset_globals { initialize_globals(); }
+
+sub _setup_symbols {
+ my $self = shift;
+ my $compile = 0;
+
+ # to avoid reexporting unwanted variables
+ undef %EXPORT;
+
+ for (@_) {
+ $HEADERS_ONCE++, next if /^[:-]unique_headers$/;
+ $NPH++, next if /^[:-]nph$/;
+ $NOSTICKY++, next if /^[:-]nosticky$/;
+ $DEBUG=0, next if /^[:-]no_?[Dd]ebug$/;
+ $DEBUG=2, next if /^[:-][Dd]ebug$/;
+ $USE_PARAM_SEMICOLONS++, next if /^[:-]newstyle_urls$/;
+ $PARAM_UTF8++, next if /^[:-]utf8$/;
+ $XHTML++, next if /^[:-]xhtml$/;
+ $XHTML=0, next if /^[:-]no_?xhtml$/;
+ $USE_PARAM_SEMICOLONS=0, next if /^[:-]oldstyle_urls$/;
+ $PRIVATE_TEMPFILES++, next if /^[:-]private_tempfiles$/;
+ $TABINDEX++, next if /^[:-]tabindex$/;
+ $CLOSE_UPLOAD_FILES++, next if /^[:-]close_upload_files$/;
+ $EXPORT{$_}++, next if /^[:-]any$/;
+ $compile++, next if /^[:-]compile$/;
+ $NO_UNDEF_PARAMS++, next if /^[:-]no_undef_params$/;
+
+ # This is probably extremely evil code -- to be deleted some day.
+ if (/^[-]autoload$/) {
+ my($pkg) = caller(1);
+ *{"${pkg}::AUTOLOAD"} = sub {
+ my($routine) = $AUTOLOAD;
+ $routine =~ s/^.*::/CGI::/;
+ &$routine;
+ };
+ next;
+ }
+
+ for (&expand_tags($_)) {
+ tr/a-zA-Z0-9_//cd; # don't allow weird function names
+ $EXPORT{$_}++;
+ }
+ }
+ _compile_all(keys %EXPORT) if $compile;
+ @SAVED_SYMBOLS = @_;
+}
+
+sub charset {
+ my ($self,$charset) = self_or_default(@_);
+ $self->{'.charset'} = $charset if defined $charset;
+ $self->{'.charset'};
+}
+
+sub element_id {
+ my ($self,$new_value) = self_or_default(@_);
+ $self->{'.elid'} = $new_value if defined $new_value;
+ sprintf('%010d',$self->{'.elid'}++);
+}
+
+sub element_tab {
+ my ($self,$new_value) = self_or_default(@_);
+ $self->{'.etab'} ||= 1;
+ $self->{'.etab'} = $new_value if defined $new_value;
+ my $tab = $self->{'.etab'}++;
+ return '' unless $TABINDEX or defined $new_value;
+ return qq(tabindex="$tab" );
+}
+
+###############################################################################
+################# THESE FUNCTIONS ARE AUTOLOADED ON DEMAND ####################
+###############################################################################
+$AUTOLOADED_ROUTINES = ''; # get rid of -w warning
+$AUTOLOADED_ROUTINES=<<'END_OF_AUTOLOAD';
+
+%SUBS = (
+
+'URL_ENCODED'=> <<'END_OF_FUNC',
+sub URL_ENCODED { 'application/x-www-form-urlencoded'; }
+END_OF_FUNC
+
+'MULTIPART' => <<'END_OF_FUNC',
+sub MULTIPART { 'multipart/form-data'; }
+END_OF_FUNC
+
+'SERVER_PUSH' => <<'END_OF_FUNC',
+sub SERVER_PUSH { 'multipart/x-mixed-replace;boundary="' . shift() . '"'; }
+END_OF_FUNC
+
+'new_MultipartBuffer' => <<'END_OF_FUNC',
+# Create a new multipart buffer
+sub new_MultipartBuffer {
+ my($self,$boundary,$length) = @_;
+ return MultipartBuffer->new($self,$boundary,$length);
+}
+END_OF_FUNC
+
+'read_from_client' => <<'END_OF_FUNC',
+# Read data from a file handle
+sub read_from_client {
+ my($self, $buff, $len, $offset) = @_;
+ local $^W=0; # prevent a warning
+ return $MOD_PERL
+ ? $self->r->read($$buff, $len, $offset)
+ : read(\*STDIN, $$buff, $len, $offset);
+}
+END_OF_FUNC
+
+'read_from_stdin' => <<'END_OF_FUNC',
+# Read data from stdin until all is read
+sub read_from_stdin {
+ my($self, $buff) = @_;
+ local $^W=0; # prevent a warning
+
+ #
+ # TODO: loop over STDIN until all is read
+ #
+
+ my($eoffound) = 0;
+ my($localbuf) = '';
+ my($tempbuf) = '';
+ my($bufsiz) = 1024;
+ my($res);
+ while ($eoffound == 0) {
+ if ( $MOD_PERL ) {
+ $res = $self->r->read($tempbuf, $bufsiz, 0)
+ }
+ else {
+ $res = read(\*STDIN, $tempbuf, $bufsiz);
+ }
+
+ if ( !defined($res) ) {
+ # TODO: how to do error reporting ?
+ $eoffound = 1;
+ last;
+ }
+ if ( $res == 0 ) {
+ $eoffound = 1;
+ last;
+ }
+ $localbuf .= $tempbuf;
+ }
+
+ $$buff = $localbuf;
+
+ return $res;
+}
+END_OF_FUNC
+
+'delete' => <<'END_OF_FUNC',
+#### Method: delete
+# Deletes the named parameter entirely.
+####
+sub delete {
+ my($self,@p) = self_or_default(@_);
+ my(@names) = rearrange([NAME],@p);
+ my @to_delete = ref($names[0]) eq 'ARRAY' ? @$names[0] : @names;
+ my %to_delete;
+ for my $name (@to_delete)
+ {
+ CORE::delete $self->{param}{$name};
+ CORE::delete $self->{'.fieldnames'}->{$name};
+ $to_delete{$name}++;
+ }
+ @{$self->{'.parameters'}}=grep { !exists($to_delete{$_}) } $self->param();
+ return;
+}
+END_OF_FUNC
+
+#### Method: import_names
+# Import all parameters into the given namespace.
+# Assumes namespace 'Q' if not specified
+####
+'import_names' => <<'END_OF_FUNC',
+sub import_names {
+ my($self,$namespace,$delete) = self_or_default(@_);
+ $namespace = 'Q' unless defined($namespace);
+ die "Can't import names into \"main\"\n" if \%{"${namespace}::"} == \%::;
+ if ($delete || $MOD_PERL || exists $ENV{'FCGI_ROLE'}) {
+ # can anyone find an easier way to do this?
+ for (keys %{"${namespace}::"}) {
+ local *symbol = "${namespace}::${_}";
+ undef $symbol;
+ undef @symbol;
+ undef %symbol;
+ }
+ }
+ my($param,@value,$var);
+ for $param ($self->param) {
+ # protect against silly names
+ ($var = $param)=~tr/a-zA-Z0-9_/_/c;
+ $var =~ s/^(?=\d)/_/;
+ local *symbol = "${namespace}::$var";
+ @value = $self->param($param);
+ @symbol = @value;
+ $symbol = $value[0];
+ }
+}
+END_OF_FUNC
+
+#### Method: keywords
+# Keywords acts a bit differently. Calling it in a list context
+# returns the list of keywords.
+# Calling it in a scalar context gives you the size of the list.
+####
+'keywords' => <<'END_OF_FUNC',
+sub keywords {
+ my($self,@values) = self_or_default(@_);
+ # If values is provided, then we set it.
+ $self->{param}{'keywords'}=[@values] if @values;
+ my(@result) = defined($self->{param}{'keywords'}) ? @{$self->{param}{'keywords'}} : ();
+ @result;
+}
+END_OF_FUNC
+
+# These are some tie() interfaces for compatibility
+# with Steve Brenner's cgi-lib.pl routines
+'Vars' => <<'END_OF_FUNC',
+sub Vars {
+ my $q = shift;
+ my %in;
+ tie(%in,CGI,$q);
+ return %in if wantarray;
+ return \%in;
+}
+END_OF_FUNC
+
+# These are some tie() interfaces for compatibility
+# with Steve Brenner's cgi-lib.pl routines
+'ReadParse' => <<'END_OF_FUNC',
+sub ReadParse {
+ local(*in);
+ if (@_) {
+ *in = $_[0];
+ } else {
+ my $pkg = caller();
+ *in=*{"${pkg}::in"};
+ }
+ tie(%in,CGI);
+ return scalar(keys %in);
+}
+END_OF_FUNC
+
+'PrintHeader' => <<'END_OF_FUNC',
+sub PrintHeader {
+ my($self) = self_or_default(@_);
+ return $self->header();
+}
+END_OF_FUNC
+
+'HtmlTop' => <<'END_OF_FUNC',
+sub HtmlTop {
+ my($self,@p) = self_or_default(@_);
+ return $self->start_html(@p);
+}
+END_OF_FUNC
+
+'HtmlBot' => <<'END_OF_FUNC',
+sub HtmlBot {
+ my($self,@p) = self_or_default(@_);
+ return $self->end_html(@p);
+}
+END_OF_FUNC
+
+'SplitParam' => <<'END_OF_FUNC',
+sub SplitParam {
+ my ($param) = @_;
+ my (@params) = split ("\0", $param);
+ return (wantarray ? @params : $params[0]);
+}
+END_OF_FUNC
+
+'MethGet' => <<'END_OF_FUNC',
+sub MethGet {
+ return request_method() eq 'GET';
+}
+END_OF_FUNC
+
+'MethPost' => <<'END_OF_FUNC',
+sub MethPost {
+ return request_method() eq 'POST';
+}
+END_OF_FUNC
+
+'MethPut' => <<'END_OF_FUNC',
+sub MethPut {
+ return request_method() eq 'PUT';
+}
+END_OF_FUNC
+
+'TIEHASH' => <<'END_OF_FUNC',
+sub TIEHASH {
+ my $class = shift;
+ my $arg = $_[0];
+ if (ref($arg) && UNIVERSAL::isa($arg,'CGI')) {
+ return $arg;
+ }
+ return $Q ||= $class->new(@_);
+}
+END_OF_FUNC
+
+'STORE' => <<'END_OF_FUNC',
+sub STORE {
+ my $self = shift;
+ my $tag = shift;
+ my $vals = shift;
+ my @vals = index($vals,"\0")!=-1 ? split("\0",$vals) : $vals;
+ $self->param(-name=>$tag,-value=>\@vals);
+}
+END_OF_FUNC
+
+'FETCH' => <<'END_OF_FUNC',
+sub FETCH {
+ return $_[0] if $_[1] eq 'CGI';
+ return undef unless defined $_[0]->param($_[1]);
+ return join("\0",$_[0]->param($_[1]));
+}
+END_OF_FUNC
+
+'FIRSTKEY' => <<'END_OF_FUNC',
+sub FIRSTKEY {
+ $_[0]->{'.iterator'}=0;
+ $_[0]->{'.parameters'}->[$_[0]->{'.iterator'}++];
+}
+END_OF_FUNC
+
+'NEXTKEY' => <<'END_OF_FUNC',
+sub NEXTKEY {
+ $_[0]->{'.parameters'}->[$_[0]->{'.iterator'}++];
+}
+END_OF_FUNC
+
+'EXISTS' => <<'END_OF_FUNC',
+sub EXISTS {
+ exists $_[0]->{param}{$_[1]};
+}
+END_OF_FUNC
+
+'DELETE' => <<'END_OF_FUNC',
+sub DELETE {
+ $_[0]->delete($_[1]);
+}
+END_OF_FUNC
+
+'CLEAR' => <<'END_OF_FUNC',
+sub CLEAR {
+ %{$_[0]}=();
+}
+####
+END_OF_FUNC
+
+####
+# Append a new value to an existing query
+####
+'append' => <<'EOF',
+sub append {
+ my($self,@p) = self_or_default(@_);
+ my($name,$value) = rearrange([NAME,[VALUE,VALUES]],@p);
+ my(@values) = defined($value) ? (ref($value) ? @{$value} : $value) : ();
+ if (@values) {
+ $self->add_parameter($name);
+ push(@{$self->{param}{$name}},@values);
+ }
+ return $self->param($name);
+}
+EOF
+
+#### Method: delete_all
+# Delete all parameters
+####
+'delete_all' => <<'EOF',
+sub delete_all {
+ my($self) = self_or_default(@_);
+ my @param = $self->param();
+ $self->delete(@param);
+}
+EOF
+
+'Delete' => <<'EOF',
+sub Delete {
+ my($self,@p) = self_or_default(@_);
+ $self->delete(@p);
+}
+EOF
+
+'Delete_all' => <<'EOF',
+sub Delete_all {
+ my($self,@p) = self_or_default(@_);
+ $self->delete_all(@p);
+}
+EOF
+
+#### Method: autoescape
+# If you want to turn off the autoescaping features,
+# call this method with undef as the argument
+'autoEscape' => <<'END_OF_FUNC',
+sub autoEscape {
+ my($self,$escape) = self_or_default(@_);
+ my $d = $self->{'escape'};
+ $self->{'escape'} = $escape;
+ $d;
+}
+END_OF_FUNC
+
+
+#### Method: version
+# Return the current version
+####
+'version' => <<'END_OF_FUNC',
+sub version {
+ return $VERSION;
+}
+END_OF_FUNC
+
+#### Method: url_param
+# Return a parameter in the QUERY_STRING, regardless of
+# whether this was a POST or a GET
+####
+'url_param' => <<'END_OF_FUNC',
+sub url_param {
+ my ($self,@p) = self_or_default(@_);
+ my $name = shift(@p);
+ return undef unless exists($ENV{QUERY_STRING});
+ unless (exists($self->{'.url_param'})) {
+ $self->{'.url_param'}={}; # empty hash
+ if ($ENV{QUERY_STRING} =~ /=/) {
+ my(@pairs) = split(/[&;]/,$ENV{QUERY_STRING});
+ my($param,$value);
+ for (@pairs) {
+ ($param,$value) = split('=',$_,2);
+ $param = unescape($param);
+ $value = unescape($value);
+ push(@{$self->{'.url_param'}->{$param}},$value);
+ }
+ } else {
+ my @keywords = $self->parse_keywordlist($ENV{QUERY_STRING});
+ $self->{'.url_param'}{'keywords'} = \@keywords if @keywords;
+ }
+ }
+ return keys %{$self->{'.url_param'}} unless defined($name);
+ return () unless $self->{'.url_param'}->{$name};
+ return wantarray ? @{$self->{'.url_param'}->{$name}}
+ : $self->{'.url_param'}->{$name}->[0];
+}
+END_OF_FUNC
+
+#### Method: Dump
+# Returns a string in which all the known parameter/value
+# pairs are represented as nested lists, mainly for the purposes
+# of debugging.
+####
+'Dump' => <<'END_OF_FUNC',
+sub Dump {
+ my($self) = self_or_default(@_);
+ my($param,$value,@result);
+ return '<ul></ul>' unless $self->param;
+ push(@result,"<ul>");
+ for $param ($self->param) {
+ my($name)=$self->_maybe_escapeHTML($param);
+ push(@result,"<li><strong>$name</strong></li>");
+ push(@result,"<ul>");
+ for $value ($self->param($param)) {
+ $value = $self->_maybe_escapeHTML($value);
+ $value =~ s/\n/<br \/>\n/g;
+ push(@result,"<li>$value</li>");
+ }
+ push(@result,"</ul>");
+ }
+ push(@result,"</ul>");
+ return join("\n",@result);
+}
+END_OF_FUNC
+
+#### Method as_string
+#
+# synonym for "dump"
+####
+'as_string' => <<'END_OF_FUNC',
+sub as_string {
+ &Dump(@_);
+}
+END_OF_FUNC
+
+#### Method: save
+# Write values out to a filehandle in such a way that they can
+# be reinitialized by the filehandle form of the new() method
+####
+'save' => <<'END_OF_FUNC',
+sub save {
+ my($self,$filehandle) = self_or_default(@_);
+ $filehandle = to_filehandle($filehandle);
+ my($param);
+ local($,) = ''; # set print field separator back to a sane value
+ local($\) = ''; # set output line separator to a sane value
+ for $param ($self->param) {
+ my($escaped_param) = escape($param);
+ my($value);
+ for $value ($self->param($param)) {
+ print $filehandle "$escaped_param=",escape("$value"),"\n"
+ if length($escaped_param) or length($value);
+ }
+ }
+ for (keys %{$self->{'.fieldnames'}}) {
+ print $filehandle ".cgifields=",escape("$_"),"\n";
+ }
+ print $filehandle "=\n"; # end of record
+}
+END_OF_FUNC
+
+
+#### Method: save_parameters
+# An alias for save() that is a better name for exportation.
+# Only intended to be used with the function (non-OO) interface.
+####
+'save_parameters' => <<'END_OF_FUNC',
+sub save_parameters {
+ my $fh = shift;
+ return save(to_filehandle($fh));
+}
+END_OF_FUNC
+
+#### Method: restore_parameters
+# A way to restore CGI parameters from an initializer.
+# Only intended to be used with the function (non-OO) interface.
+####
+'restore_parameters' => <<'END_OF_FUNC',
+sub restore_parameters {
+ $Q = $CGI::DefaultClass->new(@_);
+}
+END_OF_FUNC
+
+#### Method: multipart_init
+# Return a Content-Type: style header for server-push
+# This has to be NPH on most web servers, and it is advisable to set $| = 1
+#
+# Many thanks to Ed Jordan <ed@fidalgo.net> for this
+# contribution, updated by Andrew Benham (adsb@bigfoot.com)
+####
+'multipart_init' => <<'END_OF_FUNC',
+sub multipart_init {
+ my($self,@p) = self_or_default(@_);
+ my($boundary,@other) = rearrange_header([BOUNDARY],@p);
+ if (!$boundary) {
+ $boundary = '------- =_';
+ my @chrs = ('0'..'9', 'A'..'Z', 'a'..'z');
+ for (1..17) {
+ $boundary .= $chrs[rand(scalar @chrs)];
+ }
+ }
+
+ $self->{'separator'} = "$CRLF--$boundary$CRLF";
+ $self->{'final_separator'} = "$CRLF--$boundary--$CRLF";
+ $type = SERVER_PUSH($boundary);
+ return $self->header(
+ -nph => 0,
+ -type => $type,
+ (map { split "=", $_, 2 } @other),
+ ) . "WARNING: YOUR BROWSER DOESN'T SUPPORT THIS SERVER-PUSH TECHNOLOGY." . $self->multipart_end;
+}
+END_OF_FUNC
+
+
+#### Method: multipart_start
+# Return a Content-Type: style header for server-push, start of section
+#
+# Many thanks to Ed Jordan <ed@fidalgo.net> for this
+# contribution, updated by Andrew Benham (adsb@bigfoot.com)
+####
+'multipart_start' => <<'END_OF_FUNC',
+sub multipart_start {
+ my(@header);
+ my($self,@p) = self_or_default(@_);
+ my($type,@other) = rearrange([TYPE],@p);
+ $type = $type || 'text/html';
+ push(@header,"Content-Type: $type");
+
+ # rearrange() was designed for the HTML portion, so we
+ # need to fix it up a little.
+ for (@other) {
+ # Don't use \s because of perl bug 21951
+ next unless my($header,$value) = /([^ \r\n\t=]+)=\"?(.+?)\"?$/;
+ ($_ = $header) =~ s/^(\w)(.*)/$1 . lc ($2) . ': '.$self->unescapeHTML($value)/e;
+ }
+ push(@header,@other);
+ my $header = join($CRLF,@header)."${CRLF}${CRLF}";
+ return $header;
+}
+END_OF_FUNC
+
+
+#### Method: multipart_end
+# Return a MIME boundary separator for server-push, end of section
+#
+# Many thanks to Ed Jordan <ed@fidalgo.net> for this
+# contribution
+####
+'multipart_end' => <<'END_OF_FUNC',
+sub multipart_end {
+ my($self,@p) = self_or_default(@_);
+ return $self->{'separator'};
+}
+END_OF_FUNC
+
+
+#### Method: multipart_final
+# Return a MIME boundary separator for server-push, end of all sections
+#
+# Contributed by Andrew Benham (adsb@bigfoot.com)
+####
+'multipart_final' => <<'END_OF_FUNC',
+sub multipart_final {
+ my($self,@p) = self_or_default(@_);
+ return $self->{'final_separator'} . "WARNING: YOUR BROWSER DOESN'T SUPPORT THIS SERVER-PUSH TECHNOLOGY." . $CRLF;
+}
+END_OF_FUNC
+
+
+#### Method: header
+# Return a Content-Type: style header
+#
+####
+'header' => <<'END_OF_FUNC',
+sub header {
+ my($self,@p) = self_or_default(@_);
+ my(@header);
+
+ return "" if $self->{'.header_printed'}++ and $HEADERS_ONCE;
+
+ my($type,$status,$cookie,$target,$expires,$nph,$charset,$attachment,$p3p,@other) =
+ rearrange([['TYPE','CONTENT_TYPE','CONTENT-TYPE'],
+ 'STATUS',['COOKIE','COOKIES'],'TARGET',
+ 'EXPIRES','NPH','CHARSET',
+ 'ATTACHMENT','P3P'],@p);
+
+ # CR escaping for values, per RFC 822
+ for my $header ($type,$status,$cookie,$target,$expires,$nph,$charset,$attachment,$p3p,@other) {
+ if (defined $header) {
+ # From RFC 822:
+ # Unfolding is accomplished by regarding CRLF immediately
+ # followed by a LWSP-char as equivalent to the LWSP-char.
+ $header =~ s/$CRLF(\s)/$1/g;
+
+ # All other uses of newlines are invalid input.
+ if ($header =~ m/$CRLF|\015|\012/) {
+ # shorten very long values in the diagnostic
+ $header = substr($header,0,72).'...' if (length $header > 72);
+ die "Invalid header value contains a newline not followed by whitespace: $header";
+ }
+ }
+ }
+
+ $nph ||= $NPH;
+
+ $type ||= 'text/html' unless defined($type);
+
+ # sets if $charset is given, gets if not
+ $charset = $self->charset( $charset );
+
+ # rearrange() was designed for the HTML portion, so we
+ # need to fix it up a little.
+ for (@other) {
+ # Don't use \s because of perl bug 21951
+ next unless my($header,$value) = /([^ \r\n\t=]+)=\"?(.+?)\"?$/s;
+ ($_ = $header) =~ s/^(\w)(.*)/"\u$1\L$2" . ': '.$self->unescapeHTML($value)/e;
+ }
+
+ $type .= "; charset=$charset"
+ if $type ne ''
+ and $type !~ /\bcharset\b/
+ and defined $charset
+ and $charset ne '';
+
+ # Maybe future compatibility. Maybe not.
+ my $protocol = $ENV{SERVER_PROTOCOL} || 'HTTP/1.0';
+ push(@header,$protocol . ' ' . ($status || '200 OK')) if $nph;
+ push(@header,"Server: " . &server_software()) if $nph;
+
+ push(@header,"Status: $status") if $status;
+ push(@header,"Window-Target: $target") if $target;
+ if ($p3p) {
+ $p3p = join ' ',@$p3p if ref($p3p) eq 'ARRAY';
+ push(@header,qq(P3P: policyref="/w3c/p3p.xml", CP="$p3p"));
+ }
+ # push all the cookies -- there may be several
+ if ($cookie) {
+ my(@cookie) = ref($cookie) && ref($cookie) eq 'ARRAY' ? @{$cookie} : $cookie;
+ for (@cookie) {
+ my $cs = UNIVERSAL::isa($_,'CGI::Cookie') ? $_->as_string : $_;
+ push(@header,"Set-Cookie: $cs") if $cs ne '';
+ }
+ }
+ # if the user indicates an expiration time, then we need
+ # both an Expires and a Date header (so that the browser is
+ # uses OUR clock)
+ push(@header,"Expires: " . expires($expires,'http'))
+ if $expires;
+ push(@header,"Date: " . expires(0,'http')) if $expires || $cookie || $nph;
+ push(@header,"Pragma: no-cache") if $self->cache();
+ push(@header,"Content-Disposition: attachment; filename=\"$attachment\"") if $attachment;
+ push(@header,map {ucfirst $_} @other);
+ push(@header,"Content-Type: $type") if $type ne '';
+ my $header = join($CRLF,@header)."${CRLF}${CRLF}";
+ if (($MOD_PERL >= 1) && !$nph) {
+ $self->r->send_cgi_header($header);
+ return '';
+ }
+ return $header;
+}
+END_OF_FUNC
+
+#### Method: cache
+# Control whether header() will produce the no-cache
+# Pragma directive.
+####
+'cache' => <<'END_OF_FUNC',
+sub cache {
+ my($self,$new_value) = self_or_default(@_);
+ $new_value = '' unless $new_value;
+ if ($new_value ne '') {
+ $self->{'cache'} = $new_value;
+ }
+ return $self->{'cache'};
+}
+END_OF_FUNC
+
+
+#### Method: redirect
+# Return a Location: style header
+#
+####
+'redirect' => <<'END_OF_FUNC',
+sub redirect {
+ my($self,@p) = self_or_default(@_);
+ my($url,$target,$status,$cookie,$nph,@other) =
+ rearrange([[LOCATION,URI,URL],TARGET,STATUS,['COOKIE','COOKIES'],NPH],@p);
+ $status = '302 Found' unless defined $status;
+ $url ||= $self->self_url;
+ my(@o);
+ for (@other) { tr/\"//d; push(@o,split("=",$_,2)); }
+ unshift(@o,
+ '-Status' => $status,
+ '-Location'=> $url,
+ '-nph' => $nph);
+ unshift(@o,'-Target'=>$target) if $target;
+ unshift(@o,'-Type'=>'');
+ my @unescaped;
+ unshift(@unescaped,'-Cookie'=>$cookie) if $cookie;
+ return $self->header((map {$self->unescapeHTML($_)} @o),@unescaped);
+}
+END_OF_FUNC
+
+
+#### Method: start_html
+# Canned HTML header
+#
+# Parameters:
+# $title -> (optional) The title for this HTML document (-title)
+# $author -> (optional) e-mail address of the author (-author)
+# $base -> (optional) if set to true, will enter the BASE address of this document
+# for resolving relative references (-base)
+# $xbase -> (optional) alternative base at some remote location (-xbase)
+# $target -> (optional) target window to load all links into (-target)
+# $script -> (option) Javascript code (-script)
+# $no_script -> (option) Javascript <noscript> tag (-noscript)
+# $meta -> (optional) Meta information tags
+# $head -> (optional) any other elements you'd like to incorporate into the <head> tag
+# (a scalar or array ref)
+# $style -> (optional) reference to an external style sheet
+# @other -> (optional) any other named parameters you'd like to incorporate into
+# the <body> tag.
+####
+'start_html' => <<'END_OF_FUNC',
+sub start_html {
+ my($self,@p) = &self_or_default(@_);
+ my($title,$author,$base,$xbase,$script,$noscript,
+ $target,$meta,$head,$style,$dtd,$lang,$encoding,$declare_xml,@other) =
+ rearrange([TITLE,AUTHOR,BASE,XBASE,SCRIPT,NOSCRIPT,TARGET,
+ META,HEAD,STYLE,DTD,LANG,ENCODING,DECLARE_XML],@p);
+
+ $self->element_id(0);
+ $self->element_tab(0);
+
+ $encoding = lc($self->charset) unless defined $encoding;
+
+ # Need to sort out the DTD before it's okay to call escapeHTML().
+ my(@result,$xml_dtd);
+ if ($dtd) {
+ if (defined(ref($dtd)) and (ref($dtd) eq 'ARRAY')) {
+ $dtd = $DEFAULT_DTD unless $dtd->[0] =~ m|^-//|;
+ } else {
+ $dtd = $DEFAULT_DTD unless $dtd =~ m|^-//|;
+ }
+ } else {
+ $dtd = $XHTML ? XHTML_DTD : $DEFAULT_DTD;
+ }
+
+ $xml_dtd++ if ref($dtd) eq 'ARRAY' && $dtd->[0] =~ /\bXHTML\b/i;
+ $xml_dtd++ if ref($dtd) eq '' && $dtd =~ /\bXHTML\b/i;
+ push @result,qq(<?xml version="1.0" encoding="$encoding"?>) if $xml_dtd && $declare_xml;
+
+ if (ref($dtd) && ref($dtd) eq 'ARRAY') {
+ push(@result,qq(<!DOCTYPE html\n\tPUBLIC "$dtd->[0]"\n\t "$dtd->[1]">));
+ $DTD_PUBLIC_IDENTIFIER = $dtd->[0];
+ } else {
+ push(@result,qq(<!DOCTYPE html\n\tPUBLIC "$dtd">));
+ $DTD_PUBLIC_IDENTIFIER = $dtd;
+ }
+
+ # Now that we know whether we're using the HTML 3.2 DTD or not, it's okay to
+ # call escapeHTML(). Strangely enough, the title needs to be escaped as
+ # HTML while the author needs to be escaped as a URL.
+ $title = $self->_maybe_escapeHTML($title || 'Untitled Document');
+ $author = $self->escape($author);
+
+ if ($DTD_PUBLIC_IDENTIFIER =~ /[^X]HTML (2\.0|3\.2|4\.01?)/i) {
+ $lang = "" unless defined $lang;
+ $XHTML = 0;
+ }
+ else {
+ $lang = 'en-US' unless defined $lang;
+ }
+
+ my $lang_bits = $lang ne '' ? qq( lang="$lang" xml:lang="$lang") : '';
+ my $meta_bits = qq(<meta http-equiv="Content-Type" content="text/html; charset=$encoding" />)
+ if $XHTML && $encoding && !$declare_xml;
+
+ push(@result,$XHTML ? qq(<html xmlns="http://www.w3.org/1999/xhtml"$lang_bits>\n<head>\n<title>$title</title>)
+ : ($lang ? qq(<html lang="$lang">) : "<html>")
+ . "<head><title>$title</title>");
+ if (defined $author) {
+ push(@result,$XHTML ? "<link rev=\"made\" href=\"mailto:$author\" />"
+ : "<link rev=\"made\" href=\"mailto:$author\">");
+ }
+
+ if ($base || $xbase || $target) {
+ my $href = $xbase || $self->url('-path'=>1);
+ my $t = $target ? qq/ target="$target"/ : '';
+ push(@result,$XHTML ? qq(<base href="$href"$t />) : qq(<base href="$href"$t>));
+ }
+
+ if ($meta && ref($meta) && (ref($meta) eq 'HASH')) {
+ for (keys %$meta) { push(@result,$XHTML ? qq(<meta name="$_" content="$meta->{$_}" />)
+ : qq(<meta name="$_" content="$meta->{$_}">)); }
+ }
+
+ my $meta_bits_set = 0;
+ if( $head ) {
+ if( ref $head ) {
+ push @result, @$head;
+ $meta_bits_set = 1 if grep { /http-equiv=["']Content-Type/i }@$head;
+ }
+ else {
+ push @result, $head;
+ $meta_bits_set = 1 if $head =~ /http-equiv=["']Content-Type/i;
+ }
+ }
+
+ # handle the infrequently-used -style and -script parameters
+ push(@result,$self->_style($style)) if defined $style;
+ push(@result,$self->_script($script)) if defined $script;
+ push(@result,$meta_bits) if defined $meta_bits and !$meta_bits_set;
+
+ # handle -noscript parameter
+ push(@result,<<END) if $noscript;
+<noscript>
+$noscript
+</noscript>
+END
+ ;
+ my($other) = @other ? " @other" : '';
+ push(@result,"</head>\n<body$other>\n");
+ return join("\n",@result);
+}
+END_OF_FUNC
+
+### Method: _style
+# internal method for generating a CSS style section
+####
+'_style' => <<'END_OF_FUNC',
+sub _style {
+ my ($self,$style) = @_;
+ my (@result);
+
+ my $type = 'text/css';
+ my $rel = 'stylesheet';
+
+
+ my $cdata_start = $XHTML ? "\n<!--/* <![CDATA[ */" : "\n<!-- ";
+ my $cdata_end = $XHTML ? "\n/* ]]> */-->\n" : " -->\n";
+
+ my @s = ref($style) eq 'ARRAY' ? @$style : $style;
+ my $other = '';
+
+ for my $s (@s) {
+ if (ref($s)) {
+ my($src,$code,$verbatim,$stype,$alternate,$foo,@other) =
+ rearrange([qw(SRC CODE VERBATIM TYPE ALTERNATE FOO)],
+ ('-foo'=>'bar',
+ ref($s) eq 'ARRAY' ? @$s : %$s));
+ my $type = defined $stype ? $stype : 'text/css';
+ my $rel = $alternate ? 'alternate stylesheet' : 'stylesheet';
+ $other = "@other" if @other;
+
+ if (ref($src) eq "ARRAY") # Check to see if the $src variable is an array reference
+ { # If it is, push a LINK tag for each one
+ for $src (@$src)
+ {
+ push(@result,$XHTML ? qq(<link rel="$rel" type="$type" href="$src" $other/>)
+ : qq(<link rel="$rel" type="$type" href="$src"$other>)) if $src;
+ }
+ }
+ else
+ { # Otherwise, push the single -src, if it exists.
+ push(@result,$XHTML ? qq(<link rel="$rel" type="$type" href="$src" $other/>)
+ : qq(<link rel="$rel" type="$type" href="$src"$other>)
+ ) if $src;
+ }
+ if ($verbatim) {
+ my @v = ref($verbatim) eq 'ARRAY' ? @$verbatim : $verbatim;
+ push(@result, "<style type=\"text/css\">\n$_\n</style>") for @v;
+ }
+ my @c = ref($code) eq 'ARRAY' ? @$code : $code if $code;
+ push(@result,style({'type'=>$type},"$cdata_start\n$_\n$cdata_end")) for @c;
+
+ } else {
+ my $src = $s;
+ push(@result,$XHTML ? qq(<link rel="$rel" type="$type" href="$src" $other/>)
+ : qq(<link rel="$rel" type="$type" href="$src"$other>));
+ }
+ }
+ @result;
+}
+END_OF_FUNC
+
+'_script' => <<'END_OF_FUNC',
+sub _script {
+ my ($self,$script) = @_;
+ my (@result);
+
+ my (@scripts) = ref($script) eq 'ARRAY' ? @$script : ($script);
+ for $script (@scripts) {
+ my($src,$code,$language,$charset);
+ if (ref($script)) { # script is a hash
+ ($src,$code,$type,$charset) =
+ rearrange(['SRC','CODE',['LANGUAGE','TYPE'],'CHARSET'],
+ '-foo'=>'bar', # a trick to allow the '-' to be omitted
+ ref($script) eq 'ARRAY' ? @$script : %$script);
+ $type ||= 'text/javascript';
+ unless ($type =~ m!\w+/\w+!) {
+ $type =~ s/[\d.]+$//;
+ $type = "text/$type";
+ }
+ } else {
+ ($src,$code,$type,$charset) = ('',$script, 'text/javascript', '');
+ }
+
+ my $comment = '//'; # javascript by default
+ $comment = '#' if $type=~/perl|tcl/i;
+ $comment = "'" if $type=~/vbscript/i;
+
+ my ($cdata_start,$cdata_end);
+ if ($XHTML) {
+ $cdata_start = "$comment<![CDATA[\n";
+ $cdata_end .= "\n$comment]]>";
+ } else {
+ $cdata_start = "\n<!-- Hide script\n";
+ $cdata_end = $comment;
+ $cdata_end .= " End script hiding -->\n";
+ }
+ my(@satts);
+ push(@satts,'src'=>$src) if $src;
+ push(@satts,'type'=>$type);
+ push(@satts,'charset'=>$charset) if ($src && $charset);
+ $code = $cdata_start . $code . $cdata_end if defined $code;
+ push(@result,$self->script({@satts},$code || ''));
+ }
+ @result;
+}
+END_OF_FUNC
+
+#### Method: end_html
+# End an HTML document.
+# Trivial method for completeness. Just returns "</body>"
+####
+'end_html' => <<'END_OF_FUNC',
+sub end_html {
+ return "\n</body>\n</html>";
+}
+END_OF_FUNC
+
+
+################################
+# METHODS USED IN BUILDING FORMS
+################################
+
+#### Method: isindex
+# Just prints out the isindex tag.
+# Parameters:
+# $action -> optional URL of script to run
+# Returns:
+# A string containing a <isindex> tag
+'isindex' => <<'END_OF_FUNC',
+sub isindex {
+ my($self,@p) = self_or_default(@_);
+ my($action,@other) = rearrange([ACTION],@p);
+ $action = qq/ action="$action"/ if $action;
+ my($other) = @other ? " @other" : '';
+ return $XHTML ? "<isindex$action$other />" : "<isindex$action$other>";
+}
+END_OF_FUNC
+
+
+#### Method: startform
+# This method is DEPRECATED
+# Start a form
+# Parameters:
+# $method -> optional submission method to use (GET or POST)
+# $action -> optional URL of script to run
+# $enctype ->encoding to use (URL_ENCODED or MULTIPART)
+'startform' => <<'END_OF_FUNC',
+sub startform {
+ my($self,@p) = self_or_default(@_);
+
+ my($method,$action,$enctype,@other) =
+ rearrange([METHOD,ACTION,ENCTYPE],@p);
+
+ $method = $self->_maybe_escapeHTML(lc($method || 'post'));
+ $enctype = $self->_maybe_escapeHTML($enctype || &URL_ENCODED);
+ if (defined $action) {
+ $action = $self->_maybe_escapeHTML($action);
+ }
+ else {
+ $action = $self->_maybe_escapeHTML($self->request_uri || $self->self_url);
+ }
+ $action = qq(action="$action");
+ my($other) = @other ? " @other" : '';
+ $self->{'.parametersToAdd'}={};
+ return qq/<form method="$method" $action enctype="$enctype"$other>\n/;
+}
+END_OF_FUNC
+
+#### Method: start_form
+# Start a form
+# Parameters:
+# $method -> optional submission method to use (GET or POST)
+# $action -> optional URL of script to run
+# $enctype ->encoding to use (URL_ENCODED or MULTIPART)
+'start_form' => <<'END_OF_FUNC',
+sub start_form {
+ my($self,@p) = self_or_default(@_);
+
+ my($method,$action,$enctype,@other) =
+ rearrange([METHOD,ACTION,ENCTYPE],@p);
+
+ $method = $self->_maybe_escapeHTML(lc($method || 'post'));
+
+ if( $XHTML ){
+ $enctype = $self->_maybe_escapeHTML($enctype || &MULTIPART);
+ }else{
+ $enctype = $self->_maybe_escapeHTML($enctype || &URL_ENCODED);
+ }
+
+ if (defined $action) {
+ $action = $self->_maybe_escapeHTML($action);
+ }
+ else {
+ $action = $self->_maybe_escapeHTML($self->request_uri || $self->self_url);
+ }
+ $action = qq(action="$action");
+ my($other) = @other ? " @other" : '';
+ $self->{'.parametersToAdd'}={};
+ return qq/<form method="$method" $action enctype="$enctype"$other>\n/;
+}
+END_OF_FUNC
+
+#### Method: start_multipart_form
+'start_multipart_form' => <<'END_OF_FUNC',
+sub start_multipart_form {
+ my($self,@p) = self_or_default(@_);
+ if (defined($p[0]) && substr($p[0],0,1) eq '-') {
+ return $self->start_form(-enctype=>&MULTIPART,@p);
+ } else {
+ my($method,$action,@other) =
+ rearrange([METHOD,ACTION],@p);
+ return $self->start_form($method,$action,&MULTIPART,@other);
+ }
+}
+END_OF_FUNC
+
+
+
+#### Method: end_form
+# End a form
+'end_form' => <<'END_OF_FUNC',
+sub end_form {
+ my($self,@p) = self_or_default(@_);
+ if ( $NOSTICKY ) {
+ return wantarray ? ("</form>") : "\n</form>";
+ } else {
+ if (my @fields = $self->get_fields) {
+ return wantarray ? ("<div>",@fields,"</div>","</form>")
+ : "<div>".(join '',@fields)."</div>\n</form>";
+ } else {
+ return "</form>";
+ }
+ }
+}
+END_OF_FUNC
+
+#### Method: end_multipart_form
+# end a multipart form
+'end_multipart_form' => <<'END_OF_FUNC',
+sub end_multipart_form {
+ &end_form;
+}
+END_OF_FUNC
+
+
+'_textfield' => <<'END_OF_FUNC',
+sub _textfield {
+ my($self,$tag,@p) = self_or_default(@_);
+ my($name,$default,$size,$maxlength,$override,$tabindex,@other) =
+ rearrange([NAME,[DEFAULT,VALUE,VALUES],SIZE,MAXLENGTH,[OVERRIDE,FORCE],TABINDEX],@p);
+
+ my $current = $override ? $default :
+ (defined($self->param($name)) ? $self->param($name) : $default);
+
+ $current = defined($current) ? $self->_maybe_escapeHTML($current,1) : '';
+ $name = defined($name) ? $self->_maybe_escapeHTML($name) : '';
+ my($s) = defined($size) ? qq/ size="$size"/ : '';
+ my($m) = defined($maxlength) ? qq/ maxlength="$maxlength"/ : '';
+ my($other) = @other ? " @other" : '';
+ # this entered at cristy's request to fix problems with file upload fields
+ # and WebTV -- not sure it won't break stuff
+ my($value) = $current ne '' ? qq(value="$current") : '';
+ $tabindex = $self->element_tab($tabindex);
+ return $XHTML ? qq(<input type="$tag" name="$name" $tabindex$value$s$m$other />)
+ : qq(<input type="$tag" name="$name" $value$s$m$other>);
+}
+END_OF_FUNC
+
+#### Method: textfield
+# Parameters:
+# $name -> Name of the text field
+# $default -> Optional default value of the field if not
+# already defined.
+# $size -> Optional width of field in characaters.
+# $maxlength -> Optional maximum number of characters.
+# Returns:
+# A string containing a <input type="text"> field
+#
+'textfield' => <<'END_OF_FUNC',
+sub textfield {
+ my($self,@p) = self_or_default(@_);
+ $self->_textfield('text',@p);
+}
+END_OF_FUNC
+
+
+#### Method: filefield
+# Parameters:
+# $name -> Name of the file upload field
+# $size -> Optional width of field in characaters.
+# $maxlength -> Optional maximum number of characters.
+# Returns:
+# A string containing a <input type="file"> field
+#
+'filefield' => <<'END_OF_FUNC',
+sub filefield {
+ my($self,@p) = self_or_default(@_);
+ $self->_textfield('file',@p);
+}
+END_OF_FUNC
+
+
+#### Method: password
+# Create a "secret password" entry field
+# Parameters:
+# $name -> Name of the field
+# $default -> Optional default value of the field if not
+# already defined.
+# $size -> Optional width of field in characters.
+# $maxlength -> Optional maximum characters that can be entered.
+# Returns:
+# A string containing a <input type="password"> field
+#
+'password_field' => <<'END_OF_FUNC',
+sub password_field {
+ my ($self,@p) = self_or_default(@_);
+ $self->_textfield('password',@p);
+}
+END_OF_FUNC
+
+#### Method: textarea
+# Parameters:
+# $name -> Name of the text field
+# $default -> Optional default value of the field if not
+# already defined.
+# $rows -> Optional number of rows in text area
+# $columns -> Optional number of columns in text area
+# Returns:
+# A string containing a <textarea></textarea> tag
+#
+'textarea' => <<'END_OF_FUNC',
+sub textarea {
+ my($self,@p) = self_or_default(@_);
+ my($name,$default,$rows,$cols,$override,$tabindex,@other) =
+ rearrange([NAME,[DEFAULT,VALUE],ROWS,[COLS,COLUMNS],[OVERRIDE,FORCE],TABINDEX],@p);
+
+ my($current)= $override ? $default :
+ (defined($self->param($name)) ? $self->param($name) : $default);
+
+ $name = defined($name) ? $self->_maybe_escapeHTML($name) : '';
+ $current = defined($current) ? $self->_maybe_escapeHTML($current) : '';
+ my($r) = $rows ? qq/ rows="$rows"/ : '';
+ my($c) = $cols ? qq/ cols="$cols"/ : '';
+ my($other) = @other ? " @other" : '';
+ $tabindex = $self->element_tab($tabindex);
+ return qq{<textarea name="$name" $tabindex$r$c$other>$current</textarea>};
+}
+END_OF_FUNC
+
+
+#### Method: button
+# Create a javascript button.
+# Parameters:
+# $name -> (optional) Name for the button. (-name)
+# $value -> (optional) Value of the button when selected (and visible name) (-value)
+# $onclick -> (optional) Text of the JavaScript to run when the button is
+# clicked.
+# Returns:
+# A string containing a <input type="button"> tag
+####
+'button' => <<'END_OF_FUNC',
+sub button {
+ my($self,@p) = self_or_default(@_);
+
+ my($label,$value,$script,$tabindex,@other) = rearrange([NAME,[VALUE,LABEL],
+ [ONCLICK,SCRIPT],TABINDEX],@p);
+
+ $label=$self->_maybe_escapeHTML($label);
+ $value=$self->_maybe_escapeHTML($value,1);
+ $script=$self->_maybe_escapeHTML($script);
+
+ $script ||= '';
+
+ my($name) = '';
+ $name = qq/ name="$label"/ if $label;
+ $value = $value || $label;
+ my($val) = '';
+ $val = qq/ value="$value"/ if $value;
+ $script = qq/ onclick="$script"/ if $script;
+ my($other) = @other ? " @other" : '';
+ $tabindex = $self->element_tab($tabindex);
+ return $XHTML ? qq(<input type="button" $tabindex$name$val$script$other />)
+ : qq(<input type="button"$name$val$script$other>);
+}
+END_OF_FUNC
+
+
+#### Method: submit
+# Create a "submit query" button.
+# Parameters:
+# $name -> (optional) Name for the button.
+# $value -> (optional) Value of the button when selected (also doubles as label).
+# $label -> (optional) Label printed on the button(also doubles as the value).
+# Returns:
+# A string containing a <input type="submit"> tag
+####
+'submit' => <<'END_OF_FUNC',
+sub submit {
+ my($self,@p) = self_or_default(@_);
+
+ my($label,$value,$tabindex,@other) = rearrange([NAME,[VALUE,LABEL],TABINDEX],@p);
+
+ $label=$self->_maybe_escapeHTML($label);
+ $value=$self->_maybe_escapeHTML($value,1);
+
+ my $name = $NOSTICKY ? '' : 'name=".submit" ';
+ $name = qq/name="$label" / if defined($label);
+ $value = defined($value) ? $value : $label;
+ my $val = '';
+ $val = qq/value="$value" / if defined($value);
+ $tabindex = $self->element_tab($tabindex);
+ my($other) = @other ? "@other " : '';
+ return $XHTML ? qq(<input type="submit" $tabindex$name$val$other/>)
+ : qq(<input type="submit" $name$val$other>);
+}
+END_OF_FUNC
+
+
+#### Method: reset
+# Create a "reset" button.
+# Parameters:
+# $name -> (optional) Name for the button.
+# Returns:
+# A string containing a <input type="reset"> tag
+####
+'reset' => <<'END_OF_FUNC',
+sub reset {
+ my($self,@p) = self_or_default(@_);
+ my($label,$value,$tabindex,@other) = rearrange(['NAME',['VALUE','LABEL'],TABINDEX],@p);
+ $label=$self->_maybe_escapeHTML($label);
+ $value=$self->_maybe_escapeHTML($value,1);
+ my ($name) = ' name=".reset"';
+ $name = qq/ name="$label"/ if defined($label);
+ $value = defined($value) ? $value : $label;
+ my($val) = '';
+ $val = qq/ value="$value"/ if defined($value);
+ my($other) = @other ? " @other" : '';
+ $tabindex = $self->element_tab($tabindex);
+ return $XHTML ? qq(<input type="reset" $tabindex$name$val$other />)
+ : qq(<input type="reset"$name$val$other>);
+}
+END_OF_FUNC
+
+
+#### Method: defaults
+# Create a "defaults" button.
+# Parameters:
+# $name -> (optional) Name for the button.
+# Returns:
+# A string containing a <input type="submit" name=".defaults"> tag
+#
+# Note: this button has a special meaning to the initialization script,
+# and tells it to ERASE the current query string so that your defaults
+# are used again!
+####
+'defaults' => <<'END_OF_FUNC',
+sub defaults {
+ my($self,@p) = self_or_default(@_);
+
+ my($label,$tabindex,@other) = rearrange([[NAME,VALUE],TABINDEX],@p);
+
+ $label=$self->_maybe_escapeHTML($label,1);
+ $label = $label || "Defaults";
+ my($value) = qq/ value="$label"/;
+ my($other) = @other ? " @other" : '';
+ $tabindex = $self->element_tab($tabindex);
+ return $XHTML ? qq(<input type="submit" name=".defaults" $tabindex$value$other />)
+ : qq/<input type="submit" NAME=".defaults"$value$other>/;
+}
+END_OF_FUNC
+
+
+#### Method: comment
+# Create an HTML <!-- comment -->
+# Parameters: a string
+'comment' => <<'END_OF_FUNC',
+sub comment {
+ my($self,@p) = self_or_CGI(@_);
+ return "<!-- @p -->";
+}
+END_OF_FUNC
+
+#### Method: checkbox
+# Create a checkbox that is not logically linked to any others.
+# The field value is "on" when the button is checked.
+# Parameters:
+# $name -> Name of the checkbox
+# $checked -> (optional) turned on by default if true
+# $value -> (optional) value of the checkbox, 'on' by default
+# $label -> (optional) a user-readable label printed next to the box.
+# Otherwise the checkbox name is used.
+# Returns:
+# A string containing a <input type="checkbox"> field
+####
+'checkbox' => <<'END_OF_FUNC',
+sub checkbox {
+ my($self,@p) = self_or_default(@_);
+
+ my($name,$checked,$value,$label,$labelattributes,$override,$tabindex,@other) =
+ rearrange([NAME,[CHECKED,SELECTED,ON],VALUE,LABEL,LABELATTRIBUTES,
+ [OVERRIDE,FORCE],TABINDEX],@p);
+
+ $value = defined $value ? $value : 'on';
+
+ if (!$override && ($self->{'.fieldnames'}->{$name} ||
+ defined $self->param($name))) {
+ $checked = grep($_ eq $value,$self->param($name)) ? $self->_checked(1) : '';
+ } else {
+ $checked = $self->_checked($checked);
+ }
+ my($the_label) = defined $label ? $label : $name;
+ $name = $self->_maybe_escapeHTML($name);
+ $value = $self->_maybe_escapeHTML($value,1);
+ $the_label = $self->_maybe_escapeHTML($the_label);
+ my($other) = @other ? "@other " : '';
+ $tabindex = $self->element_tab($tabindex);
+ $self->register_parameter($name);
+ return $XHTML ? CGI::label($labelattributes,
+ qq{<input type="checkbox" name="$name" value="$value" $tabindex$checked$other/>$the_label})
+ : qq{<input type="checkbox" name="$name" value="$value"$checked$other>$the_label};
+}
+END_OF_FUNC
+
+
+
+# Escape HTML
+'escapeHTML' => <<'END_OF_FUNC',
+sub escapeHTML {
+ # hack to work around earlier hacks
+ push @_,$_[0] if @_==1 && $_[0] eq 'CGI';
+ my ($self,$toencode,$newlinestoo) = CGI::self_or_default(@_);
+ return undef unless defined($toencode);
+ $toencode =~ s{&}{&amp;}gso;
+ $toencode =~ s{<}{&lt;}gso;
+ $toencode =~ s{>}{&gt;}gso;
+ if ($DTD_PUBLIC_IDENTIFIER =~ /[^X]HTML 3\.2/i) {
+ # $quot; was accidentally omitted from the HTML 3.2 DTD -- see
+ # <http://validator.w3.org/docs/errors.html#bad-entity> /
+ # <http://lists.w3.org/Archives/Public/www-html/1997Mar/0003.html>.
+ $toencode =~ s{"}{&#34;}gso;
+ }
+ else {
+ $toencode =~ s{"}{&quot;}gso;
+ }
+
+ # Handle bug in some browsers with Latin charsets
+ if ($self->{'.charset'}
+ && (uc($self->{'.charset'}) eq 'ISO-8859-1'
+ || uc($self->{'.charset'}) eq 'WINDOWS-1252')) {
+ $toencode =~ s{'}{&#39;}gso;
+ $toencode =~ s{\x8b}{&#8249;}gso;
+ $toencode =~ s{\x9b}{&#8250;}gso;
+ if (defined $newlinestoo && $newlinestoo) {
+ $toencode =~ s{\012}{&#10;}gso;
+ $toencode =~ s{\015}{&#13;}gso;
+ }
+ }
+ return $toencode;
+}
+END_OF_FUNC
+
+# unescape HTML -- used internally
+'unescapeHTML' => <<'END_OF_FUNC',
+sub unescapeHTML {
+ # hack to work around earlier hacks
+ push @_,$_[0] if @_==1 && $_[0] eq 'CGI';
+ my ($self,$string) = CGI::self_or_default(@_);
+ return undef unless defined($string);
+ my $latin = defined $self->{'.charset'} ? $self->{'.charset'} =~ /^(ISO-8859-1|WINDOWS-1252)$/i
+ : 1;
+ # thanks to Randal Schwartz for the correct solution to this one
+ $string=~ s[&(\S*?);]{
+ local $_ = $1;
+ /^amp$/i ? "&" :
+ /^quot$/i ? '"' :
+ /^gt$/i ? ">" :
+ /^lt$/i ? "<" :
+ /^#(\d+)$/ && $latin ? chr($1) :
+ /^#x([0-9a-f]+)$/i && $latin ? chr(hex($1)) :
+ $_
+ }gex;
+ return $string;
+}
+END_OF_FUNC
+
+# Internal procedure - don't use
+'_tableize' => <<'END_OF_FUNC',
+sub _tableize {
+ my($rows,$columns,$rowheaders,$colheaders,@elements) = @_;
+ my @rowheaders = $rowheaders ? @$rowheaders : ();
+ my @colheaders = $colheaders ? @$colheaders : ();
+ my($result);
+
+ if (defined($columns)) {
+ $rows = int(0.99 + @elements/$columns) unless defined($rows);
+ }
+ if (defined($rows)) {
+ $columns = int(0.99 + @elements/$rows) unless defined($columns);
+ }
+
+ # rearrange into a pretty table
+ $result = "<table>";
+ my($row,$column);
+ unshift(@colheaders,'') if @colheaders && @rowheaders;
+ $result .= "<tr>" if @colheaders;
+ for (@colheaders) {
+ $result .= "<th>$_</th>";
+ }
+ for ($row=0;$row<$rows;$row++) {
+ $result .= "<tr>";
+ $result .= "<th>$rowheaders[$row]</th>" if @rowheaders;
+ for ($column=0;$column<$columns;$column++) {
+ $result .= "<td>" . $elements[$column*$rows + $row] . "</td>"
+ if defined($elements[$column*$rows + $row]);
+ }
+ $result .= "</tr>";
+ }
+ $result .= "</table>";
+ return $result;
+}
+END_OF_FUNC
+
+
+#### Method: radio_group
+# Create a list of logically-linked radio buttons.
+# Parameters:
+# $name -> Common name for all the buttons.
+# $values -> A pointer to a regular array containing the
+# values for each button in the group.
+# $default -> (optional) Value of the button to turn on by default. Pass '-'
+# to turn _nothing_ on.
+# $linebreak -> (optional) Set to true to place linebreaks
+# between the buttons.
+# $labels -> (optional)
+# A pointer to a hash of labels to print next to each checkbox
+# in the form $label{'value'}="Long explanatory label".
+# Otherwise the provided values are used as the labels.
+# Returns:
+# An ARRAY containing a series of <input type="radio"> fields
+####
+'radio_group' => <<'END_OF_FUNC',
+sub radio_group {
+ my($self,@p) = self_or_default(@_);
+ $self->_box_group('radio',@p);
+}
+END_OF_FUNC
+
+#### Method: checkbox_group
+# Create a list of logically-linked checkboxes.
+# Parameters:
+# $name -> Common name for all the check boxes
+# $values -> A pointer to a regular array containing the
+# values for each checkbox in the group.
+# $defaults -> (optional)
+# 1. If a pointer to a regular array of checkbox values,
+# then this will be used to decide which
+# checkboxes to turn on by default.
+# 2. If a scalar, will be assumed to hold the
+# value of a single checkbox in the group to turn on.
+# $linebreak -> (optional) Set to true to place linebreaks
+# between the buttons.
+# $labels -> (optional)
+# A pointer to a hash of labels to print next to each checkbox
+# in the form $label{'value'}="Long explanatory label".
+# Otherwise the provided values are used as the labels.
+# Returns:
+# An ARRAY containing a series of <input type="checkbox"> fields
+####
+
+'checkbox_group' => <<'END_OF_FUNC',
+sub checkbox_group {
+ my($self,@p) = self_or_default(@_);
+ $self->_box_group('checkbox',@p);
+}
+END_OF_FUNC
+
+'_box_group' => <<'END_OF_FUNC',
+sub _box_group {
+ my $self = shift;
+ my $box_type = shift;
+
+ my($name,$values,$defaults,$linebreak,$labels,$labelattributes,
+ $attributes,$rows,$columns,$rowheaders,$colheaders,
+ $override,$nolabels,$tabindex,$disabled,@other) =
+ rearrange([NAME,[VALUES,VALUE],[DEFAULT,DEFAULTS],LINEBREAK,LABELS,LABELATTRIBUTES,
+ ATTRIBUTES,ROWS,[COLUMNS,COLS],[ROWHEADERS,ROWHEADER],[COLHEADERS,COLHEADER],
+ [OVERRIDE,FORCE],NOLABELS,TABINDEX,DISABLED
+ ],@_);
+
+
+ my($result,$checked,@elements,@values);
+
+ @values = $self->_set_values_and_labels($values,\$labels,$name);
+ my %checked = $self->previous_or_default($name,$defaults,$override);
+
+ # If no check array is specified, check the first by default
+ $checked{$values[0]}++ if $box_type eq 'radio' && !%checked;
+
+ $name=$self->_maybe_escapeHTML($name);
+
+ my %tabs = ();
+ if ($TABINDEX && $tabindex) {
+ if (!ref $tabindex) {
+ $self->element_tab($tabindex);
+ } elsif (ref $tabindex eq 'ARRAY') {
+ %tabs = map {$_=>$self->element_tab} @$tabindex;
+ } elsif (ref $tabindex eq 'HASH') {
+ %tabs = %$tabindex;
+ }
+ }
+ %tabs = map {$_=>$self->element_tab} @values unless %tabs;
+ my $other = @other ? "@other " : '';
+ my $radio_checked;
+
+ # for disabling groups of radio/checkbox buttons
+ my %disabled;
+ for (@{$disabled}) {
+ $disabled{$_}=1;
+ }
+
+ for (@values) {
+ my $disable="";
+ if ($disabled{$_}) {
+ $disable="disabled='1'";
+ }
+
+ my $checkit = $self->_checked($box_type eq 'radio' ? ($checked{$_} && !$radio_checked++)
+ : $checked{$_});
+ my($break);
+ if ($linebreak) {
+ $break = $XHTML ? "<br />" : "<br>";
+ }
+ else {
+ $break = '';
+ }
+ my($label)='';
+ unless (defined($nolabels) && $nolabels) {
+ $label = $_;
+ $label = $labels->{$_} if defined($labels) && defined($labels->{$_});
+ $label = $self->_maybe_escapeHTML($label,1);
+ $label = "<span style=\"color:gray\">$label</span>" if $disabled{$_};
+ }
+ my $attribs = $self->_set_attributes($_, $attributes);
+ my $tab = $tabs{$_};
+ $_=$self->_maybe_escapeHTML($_);
+
+ if ($XHTML) {
+ push @elements,
+ CGI::label($labelattributes,
+ qq(<input type="$box_type" name="$name" value="$_" $checkit$other$tab$attribs$disable/>$label)).${break};
+ } else {
+ push(@elements,qq/<input type="$box_type" name="$name" value="$_" $checkit$other$tab$attribs$disable>${label}${break}/);
+ }
+ }
+ $self->register_parameter($name);
+ return wantarray ? @elements : "@elements"
+ unless defined($columns) || defined($rows);
+ return _tableize($rows,$columns,$rowheaders,$colheaders,@elements);
+}
+END_OF_FUNC
+
+
+#### Method: popup_menu
+# Create a popup menu.
+# Parameters:
+# $name -> Name for all the menu
+# $values -> A pointer to a regular array containing the
+# text of each menu item.
+# $default -> (optional) Default item to display
+# $labels -> (optional)
+# A pointer to a hash of labels to print next to each checkbox
+# in the form $label{'value'}="Long explanatory label".
+# Otherwise the provided values are used as the labels.
+# Returns:
+# A string containing the definition of a popup menu.
+####
+'popup_menu' => <<'END_OF_FUNC',
+sub popup_menu {
+ my($self,@p) = self_or_default(@_);
+
+ my($name,$values,$default,$labels,$attributes,$override,$tabindex,@other) =
+ rearrange([NAME,[VALUES,VALUE],[DEFAULT,DEFAULTS],LABELS,
+ ATTRIBUTES,[OVERRIDE,FORCE],TABINDEX],@p);
+ my($result,%selected);
+
+ if (!$override && defined($self->param($name))) {
+ $selected{$self->param($name)}++;
+ } elsif (defined $default) {
+ %selected = map {$_=>1} ref($default) eq 'ARRAY'
+ ? @$default
+ : $default;
+ }
+ $name=$self->_maybe_escapeHTML($name);
+ my($other) = @other ? " @other" : '';
+
+ my(@values);
+ @values = $self->_set_values_and_labels($values,\$labels,$name);
+ $tabindex = $self->element_tab($tabindex);
+ $name = q{} if ! defined $name;
+ $result = qq/<select name="$name" $tabindex$other>\n/;
+ for (@values) {
+ if (/<optgroup/) {
+ for my $v (split(/\n/)) {
+ my $selectit = $XHTML ? 'selected="selected"' : 'selected';
+ for my $selected (keys %selected) {
+ $v =~ s/(value="\Q$selected\E")/$selectit $1/;
+ }
+ $result .= "$v\n";
+ }
+ }
+ else {
+ my $attribs = $self->_set_attributes($_, $attributes);
+ my($selectit) = $self->_selected($selected{$_});
+ my($label) = $_;
+ $label = $labels->{$_} if defined($labels) && defined($labels->{$_});
+ my($value) = $self->_maybe_escapeHTML($_);
+ $label = $self->_maybe_escapeHTML($label,1);
+ $result .= "<option${attribs} ${selectit}value=\"$value\">$label</option>\n";
+ }
+ }
+
+ $result .= "</select>";
+ return $result;
+}
+END_OF_FUNC
+
+
+#### Method: optgroup
+# Create a optgroup.
+# Parameters:
+# $name -> Label for the group
+# $values -> A pointer to a regular array containing the
+# values for each option line in the group.
+# $labels -> (optional)
+# A pointer to a hash of labels to print next to each item
+# in the form $label{'value'}="Long explanatory label".
+# Otherwise the provided values are used as the labels.
+# $labeled -> (optional)
+# A true value indicates the value should be used as the label attribute
+# in the option elements.
+# The label attribute specifies the option label presented to the user.
+# This defaults to the content of the <option> element, but the label
+# attribute allows authors to more easily use optgroup without sacrificing
+# compatibility with browsers that do not support option groups.
+# $novals -> (optional)
+# A true value indicates to suppress the val attribute in the option elements
+# Returns:
+# A string containing the definition of an option group.
+####
+'optgroup' => <<'END_OF_FUNC',
+sub optgroup {
+ my($self,@p) = self_or_default(@_);
+ my($name,$values,$attributes,$labeled,$noval,$labels,@other)
+ = rearrange([NAME,[VALUES,VALUE],ATTRIBUTES,LABELED,NOVALS,LABELS],@p);
+
+ my($result,@values);
+ @values = $self->_set_values_and_labels($values,\$labels,$name,$labeled,$novals);
+ my($other) = @other ? " @other" : '';
+
+ $name = $self->_maybe_escapeHTML($name) || q{};
+ $result = qq/<optgroup label="$name"$other>\n/;
+ for (@values) {
+ if (/<optgroup/) {
+ for (split(/\n/)) {
+ my $selectit = $XHTML ? 'selected="selected"' : 'selected';
+ s/(value="$selected")/$selectit $1/ if defined $selected;
+ $result .= "$_\n";
+ }
+ }
+ else {
+ my $attribs = $self->_set_attributes($_, $attributes);
+ my($label) = $_;
+ $label = $labels->{$_} if defined($labels) && defined($labels->{$_});
+ $label=$self->_maybe_escapeHTML($label);
+ my($value)=$self->_maybe_escapeHTML($_,1);
+ $result .= $labeled ? $novals ? "<option$attribs label=\"$value\">$label</option>\n"
+ : "<option$attribs label=\"$value\" value=\"$value\">$label</option>\n"
+ : $novals ? "<option$attribs>$label</option>\n"
+ : "<option$attribs value=\"$value\">$label</option>\n";
+ }
+ }
+ $result .= "</optgroup>";
+ return $result;
+}
+END_OF_FUNC
+
+
+#### Method: scrolling_list
+# Create a scrolling list.
+# Parameters:
+# $name -> name for the list
+# $values -> A pointer to a regular array containing the
+# values for each option line in the list.
+# $defaults -> (optional)
+# 1. If a pointer to a regular array of options,
+# then this will be used to decide which
+# lines to turn on by default.
+# 2. Otherwise holds the value of the single line to turn on.
+# $size -> (optional) Size of the list.
+# $multiple -> (optional) If set, allow multiple selections.
+# $labels -> (optional)
+# A pointer to a hash of labels to print next to each checkbox
+# in the form $label{'value'}="Long explanatory label".
+# Otherwise the provided values are used as the labels.
+# Returns:
+# A string containing the definition of a scrolling list.
+####
+'scrolling_list' => <<'END_OF_FUNC',
+sub scrolling_list {
+ my($self,@p) = self_or_default(@_);
+ my($name,$values,$defaults,$size,$multiple,$labels,$attributes,$override,$tabindex,@other)
+ = rearrange([NAME,[VALUES,VALUE],[DEFAULTS,DEFAULT],
+ SIZE,MULTIPLE,LABELS,ATTRIBUTES,[OVERRIDE,FORCE],TABINDEX],@p);
+
+ my($result,@values);
+ @values = $self->_set_values_and_labels($values,\$labels,$name);
+
+ $size = $size || scalar(@values);
+
+ my(%selected) = $self->previous_or_default($name,$defaults,$override);
+
+ my($is_multiple) = $multiple ? qq/ multiple="multiple"/ : '';
+ my($has_size) = $size ? qq/ size="$size"/: '';
+ my($other) = @other ? " @other" : '';
+
+ $name=$self->_maybe_escapeHTML($name);
+ $tabindex = $self->element_tab($tabindex);
+ $result = qq/<select name="$name" $tabindex$has_size$is_multiple$other>\n/;
+ for (@values) {
+ if (/<optgroup/) {
+ for my $v (split(/\n/)) {
+ my $selectit = $XHTML ? 'selected="selected"' : 'selected';
+ for my $selected (keys %selected) {
+ $v =~ s/(value="$selected")/$selectit $1/;
+ }
+ $result .= "$v\n";
+ }
+ }
+ else {
+ my $attribs = $self->_set_attributes($_, $attributes);
+ my($selectit) = $self->_selected($selected{$_});
+ my($label) = $_;
+ $label = $labels->{$_} if defined($labels) && defined($labels->{$_});
+ my($value) = $self->_maybe_escapeHTML($_);
+ $label = $self->_maybe_escapeHTML($label,1);
+ $result .= "<option${attribs} ${selectit}value=\"$value\">$label</option>\n";
+ }
+ }
+
+ $result .= "</select>";
+ $self->register_parameter($name);
+ return $result;
+}
+END_OF_FUNC
+
+
+#### Method: hidden
+# Parameters:
+# $name -> Name of the hidden field
+# @default -> (optional) Initial values of field (may be an array)
+# or
+# $default->[initial values of field]
+# Returns:
+# A string containing a <input type="hidden" name="name" value="value">
+####
+'hidden' => <<'END_OF_FUNC',
+sub hidden {
+ my($self,@p) = self_or_default(@_);
+
+ # this is the one place where we departed from our standard
+ # calling scheme, so we have to special-case (darn)
+ my(@result,@value);
+ my($name,$default,$override,@other) =
+ rearrange([NAME,[DEFAULT,VALUE,VALUES],[OVERRIDE,FORCE]],@p);
+
+ my $do_override = 0;
+ if ( ref($p[0]) || substr($p[0],0,1) eq '-') {
+ @value = ref($default) ? @{$default} : $default;
+ $do_override = $override;
+ } else {
+ for ($default,$override,@other) {
+ push(@value,$_) if defined($_);
+ }
+ undef @other;
+ }
+
+ # use previous values if override is not set
+ my @prev = $self->param($name);
+ @value = @prev if !$do_override && @prev;
+
+ $name=$self->_maybe_escapeHTML($name);
+ for (@value) {
+ $_ = defined($_) ? $self->_maybe_escapeHTML($_,1) : '';
+ push @result,$XHTML ? qq(<input type="hidden" name="$name" value="$_" @other />)
+ : qq(<input type="hidden" name="$name" value="$_" @other>);
+ }
+ return wantarray ? @result : join('',@result);
+}
+END_OF_FUNC
+
+
+#### Method: image_button
+# Parameters:
+# $name -> Name of the button
+# $src -> URL of the image source
+# $align -> Alignment style (TOP, BOTTOM or MIDDLE)
+# Returns:
+# A string containing a <input type="image" name="name" src="url" align="alignment">
+####
+'image_button' => <<'END_OF_FUNC',
+sub image_button {
+ my($self,@p) = self_or_default(@_);
+
+ my($name,$src,$alignment,@other) =
+ rearrange([NAME,SRC,ALIGN],@p);
+
+ my($align) = $alignment ? " align=\L\"$alignment\"" : '';
+ my($other) = @other ? " @other" : '';
+ $name=$self->_maybe_escapeHTML($name);
+ return $XHTML ? qq(<input type="image" name="$name" src="$src"$align$other />)
+ : qq/<input type="image" name="$name" src="$src"$align$other>/;
+}
+END_OF_FUNC
+
+
+#### Method: self_url
+# Returns a URL containing the current script and all its
+# param/value pairs arranged as a query. You can use this
+# to create a link that, when selected, will reinvoke the
+# script with all its state information preserved.
+####
+'self_url' => <<'END_OF_FUNC',
+sub self_url {
+ my($self,@p) = self_or_default(@_);
+ return $self->url('-path_info'=>1,'-query'=>1,'-full'=>1,@p);
+}
+END_OF_FUNC
+
+
+# This is provided as a synonym to self_url() for people unfortunate
+# enough to have incorporated it into their programs already!
+'state' => <<'END_OF_FUNC',
+sub state {
+ &self_url;
+}
+END_OF_FUNC
+
+
+#### Method: url
+# Like self_url, but doesn't return the query string part of
+# the URL.
+####
+'url' => <<'END_OF_FUNC',
+sub url {
+ my($self,@p) = self_or_default(@_);
+ my ($relative,$absolute,$full,$path_info,$query,$base,$rewrite) =
+ rearrange(['RELATIVE','ABSOLUTE','FULL',['PATH','PATH_INFO'],['QUERY','QUERY_STRING'],'BASE','REWRITE'],@p);
+ my $url = '';
+ $full++ if $base || !($relative || $absolute);
+ $rewrite++ unless defined $rewrite;
+
+ my $path = $self->path_info;
+ my $script_name = $self->script_name;
+ my $request_uri = unescape($self->request_uri) || '';
+ my $query_str = $self->query_string;
+
+ my $rewrite_in_use = $request_uri && $request_uri !~ /^\Q$script_name/;
+ undef $path if $rewrite_in_use && $rewrite; # path not valid when rewriting active
+
+ my $uri = $rewrite && $request_uri ? $request_uri : $script_name;
+ $uri =~ s/\?.*$//s; # remove query string
+ $uri =~ s/\Q$ENV{PATH_INFO}\E$// if defined $ENV{PATH_INFO};
+# $uri =~ s/\Q$path\E$// if defined $path; # remove path
+
+ if ($full) {
+ my $protocol = $self->protocol();
+ $url = "$protocol://";
+ my $vh = http('x_forwarded_host') || http('host') || '';
+ $vh =~ s/\:\d+$//; # some clients add the port number (incorrectly). Get rid of it.
+
+ $url .= $vh || server_name();
+
+ my $port = $self->virtual_port;
+
+ # add the port to the url unless it's the protocol's default port
+ $url .= ':' . $port unless (lc($protocol) eq 'http' && $port == 80)
+ or (lc($protocol) eq 'https' && $port == 443);
+
+ return $url if $base;
+
+ $url .= $uri;
+ } elsif ($relative) {
+ ($url) = $uri =~ m!([^/]+)$!;
+ } elsif ($absolute) {
+ $url = $uri;
+ }
+
+ $url .= $path if $path_info and defined $path;
+ $url .= "?$query_str" if $query and $query_str ne '';
+ $url ||= '';
+ $url =~ s/([^a-zA-Z0-9_.%;&?\/\\:+=~-])/sprintf("%%%02X",ord($1))/eg;
+ return $url;
+}
+
+END_OF_FUNC
+
+#### Method: cookie
+# Set or read a cookie from the specified name.
+# Cookie can then be passed to header().
+# Usual rules apply to the stickiness of -value.
+# Parameters:
+# -name -> name for this cookie (optional)
+# -value -> value of this cookie (scalar, array or hash)
+# -path -> paths for which this cookie is valid (optional)
+# -domain -> internet domain in which this cookie is valid (optional)
+# -secure -> if true, cookie only passed through secure channel (optional)
+# -expires -> expiry date in format Wdy, DD-Mon-YYYY HH:MM:SS GMT (optional)
+####
+'cookie' => <<'END_OF_FUNC',
+sub cookie {
+ my($self,@p) = self_or_default(@_);
+ my($name,$value,$path,$domain,$secure,$expires,$httponly) =
+ rearrange([NAME,[VALUE,VALUES],PATH,DOMAIN,SECURE,EXPIRES,HTTPONLY],@p);
+
+ require CGI::Cookie;
+
+ # if no value is supplied, then we retrieve the
+ # value of the cookie, if any. For efficiency, we cache the parsed
+ # cookies in our state variables.
+ unless ( defined($value) ) {
+ $self->{'.cookies'} = CGI::Cookie->fetch;
+
+ # If no name is supplied, then retrieve the names of all our cookies.
+ return () unless $self->{'.cookies'};
+ return keys %{$self->{'.cookies'}} unless $name;
+ return () unless $self->{'.cookies'}->{$name};
+ return $self->{'.cookies'}->{$name}->value if defined($name) && $name ne '';
+ }
+
+ # If we get here, we're creating a new cookie
+ return undef unless defined($name) && $name ne ''; # this is an error
+
+ my @param;
+ push(@param,'-name'=>$name);
+ push(@param,'-value'=>$value);
+ push(@param,'-domain'=>$domain) if $domain;
+ push(@param,'-path'=>$path) if $path;
+ push(@param,'-expires'=>$expires) if $expires;
+ push(@param,'-secure'=>$secure) if $secure;
+ push(@param,'-httponly'=>$httponly) if $httponly;
+
+ return CGI::Cookie->new(@param);
+}
+END_OF_FUNC
+
+'parse_keywordlist' => <<'END_OF_FUNC',
+sub parse_keywordlist {
+ my($self,$tosplit) = @_;
+ $tosplit = unescape($tosplit); # unescape the keywords
+ $tosplit=~tr/+/ /; # pluses to spaces
+ my(@keywords) = split(/\s+/,$tosplit);
+ return @keywords;
+}
+END_OF_FUNC
+
+'param_fetch' => <<'END_OF_FUNC',
+sub param_fetch {
+ my($self,@p) = self_or_default(@_);
+ my($name) = rearrange([NAME],@p);
+ return [] unless defined $name;
+
+ unless (exists($self->{param}{$name})) {
+ $self->add_parameter($name);
+ $self->{param}{$name} = [];
+ }
+
+ return $self->{param}{$name};
+}
+END_OF_FUNC
+
+###############################################
+# OTHER INFORMATION PROVIDED BY THE ENVIRONMENT
+###############################################
+
+#### Method: path_info
+# Return the extra virtual path information provided
+# after the URL (if any)
+####
+'path_info' => <<'END_OF_FUNC',
+sub path_info {
+ my ($self,$info) = self_or_default(@_);
+ if (defined($info)) {
+ $info = "/$info" if $info ne '' && substr($info,0,1) ne '/';
+ $self->{'.path_info'} = $info;
+ } elsif (! defined($self->{'.path_info'}) ) {
+ my (undef,$path_info) = $self->_name_and_path_from_env;
+ $self->{'.path_info'} = $path_info || '';
+ }
+ return $self->{'.path_info'};
+}
+END_OF_FUNC
+
+# This function returns a potentially modified version of SCRIPT_NAME
+# and PATH_INFO. Some HTTP servers do sanitise the paths in those
+# variables. It is the case of at least Apache 2. If for instance the
+# user requests: /path/./to/script.cgi/x//y/z/../x?y, Apache will set:
+# REQUEST_URI=/path/./to/script.cgi/x//y/z/../x?y
+# SCRIPT_NAME=/path/to/env.cgi
+# PATH_INFO=/x/y/x
+#
+# This is all fine except that some bogus CGI scripts expect
+# PATH_INFO=/http://foo when the user requests
+# http://xxx/script.cgi/http://foo
+#
+# Old versions of this module used to accomodate with those scripts, so
+# this is why we do this here to keep those scripts backward compatible.
+# Basically, we accomodate with those scripts but within limits, that is
+# we only try to preserve the number of / that were provided by the user
+# if $REQUEST_URI and "$SCRIPT_NAME$PATH_INFO" only differ by the number
+# of consecutive /.
+#
+# So for instance, in: http://foo/x//y/script.cgi/a//b, we'll return a
+# script_name of /x//y/script.cgi and a path_info of /a//b, but in:
+# http://foo/./x//z/script.cgi/a/../b//c, we'll return the versions
+# possibly sanitised by the HTTP server, so in the case of Apache 2:
+# script_name == /foo/x/z/script.cgi and path_info == /b/c.
+#
+# Future versions of this module may no longer do that, so one should
+# avoid relying on the browser, proxy, server, and CGI.pm preserving the
+# number of consecutive slashes as no guarantee can be made there.
+'_name_and_path_from_env' => <<'END_OF_FUNC',
+sub _name_and_path_from_env {
+ my $self = shift;
+ my $script_name = $ENV{SCRIPT_NAME} || '';
+ my $path_info = $ENV{PATH_INFO} || '';
+ my $uri = $self->request_uri || '';
+
+ $uri =~ s/\?.*//s;
+ $uri = unescape($uri);
+
+ if ($uri ne "$script_name$path_info") {
+ my $script_name_pattern = quotemeta($script_name);
+ my $path_info_pattern = quotemeta($path_info);
+ $script_name_pattern =~ s{(?:\\/)+}{/+}g;
+ $path_info_pattern =~ s{(?:\\/)+}{/+}g;
+
+ if ($uri =~ /^($script_name_pattern)($path_info_pattern)$/s) {
+ # REQUEST_URI and SCRIPT_NAME . PATH_INFO only differ by the
+ # numer of consecutive slashes, so we can extract the info from
+ # REQUEST_URI:
+ ($script_name, $path_info) = ($1, $2);
+ }
+ }
+ return ($script_name,$path_info);
+}
+END_OF_FUNC
+
+
+#### Method: request_method
+# Returns 'POST', 'GET', 'PUT' or 'HEAD'
+####
+'request_method' => <<'END_OF_FUNC',
+sub request_method {
+ return (defined $ENV{'REQUEST_METHOD'}) ? $ENV{'REQUEST_METHOD'} : undef;
+}
+END_OF_FUNC
+
+#### Method: content_type
+# Returns the content_type string
+####
+'content_type' => <<'END_OF_FUNC',
+sub content_type {
+ return (defined $ENV{'CONTENT_TYPE'}) ? $ENV{'CONTENT_TYPE'} : undef;
+}
+END_OF_FUNC
+
+#### Method: path_translated
+# Return the physical path information provided
+# by the URL (if any)
+####
+'path_translated' => <<'END_OF_FUNC',
+sub path_translated {
+ return (defined $ENV{'PATH_TRANSLATED'}) ? $ENV{'PATH_TRANSLATED'} : undef;
+}
+END_OF_FUNC
+
+
+#### Method: request_uri
+# Return the literal request URI
+####
+'request_uri' => <<'END_OF_FUNC',
+sub request_uri {
+ return (defined $ENV{'REQUEST_URI'}) ? $ENV{'REQUEST_URI'} : undef;
+}
+END_OF_FUNC
+
+
+#### Method: query_string
+# Synthesize a query string from our current
+# parameters
+####
+'query_string' => <<'END_OF_FUNC',
+sub query_string {
+ my($self) = self_or_default(@_);
+ my($param,$value,@pairs);
+ for $param ($self->param) {
+ my($eparam) = escape($param);
+ for $value ($self->param($param)) {
+ $value = escape($value);
+ next unless defined $value;
+ push(@pairs,"$eparam=$value");
+ }
+ }
+ for (keys %{$self->{'.fieldnames'}}) {
+ push(@pairs,".cgifields=".escape("$_"));
+ }
+ return join($USE_PARAM_SEMICOLONS ? ';' : '&',@pairs);
+}
+END_OF_FUNC
+
+
+#### Method: accept
+# Without parameters, returns an array of the
+# MIME types the browser accepts.
+# With a single parameter equal to a MIME
+# type, will return undef if the browser won't
+# accept it, 1 if the browser accepts it but
+# doesn't give a preference, or a floating point
+# value between 0.0 and 1.0 if the browser
+# declares a quantitative score for it.
+# This handles MIME type globs correctly.
+####
+'Accept' => <<'END_OF_FUNC',
+sub Accept {
+ my($self,$search) = self_or_CGI(@_);
+ my(%prefs,$type,$pref,$pat);
+
+ my(@accept) = defined $self->http('accept')
+ ? split(',',$self->http('accept'))
+ : ();
+
+ for (@accept) {
+ ($pref) = /q=(\d\.\d+|\d+)/;
+ ($type) = m#(\S+/[^;]+)#;
+ next unless $type;
+ $prefs{$type}=$pref || 1;
+ }
+
+ return keys %prefs unless $search;
+
+ # if a search type is provided, we may need to
+ # perform a pattern matching operation.
+ # The MIME types use a glob mechanism, which
+ # is easily translated into a perl pattern match
+
+ # First return the preference for directly supported
+ # types:
+ return $prefs{$search} if $prefs{$search};
+
+ # Didn't get it, so try pattern matching.
+ for (keys %prefs) {
+ next unless /\*/; # not a pattern match
+ ($pat = $_) =~ s/([^\w*])/\\$1/g; # escape meta characters
+ $pat =~ s/\*/.*/g; # turn it into a pattern
+ return $prefs{$_} if $search=~/$pat/;
+ }
+}
+END_OF_FUNC
+
+
+#### Method: user_agent
+# If called with no parameters, returns the user agent.
+# If called with one parameter, does a pattern match (case
+# insensitive) on the user agent.
+####
+'user_agent' => <<'END_OF_FUNC',
+sub user_agent {
+ my($self,$match)=self_or_CGI(@_);
+ my $user_agent = $self->http('user_agent');
+ return $user_agent unless $match && $user_agent;
+ return $user_agent =~ /$match/i;
+}
+END_OF_FUNC
+
+
+#### Method: raw_cookie
+# Returns the magic cookies for the session.
+# The cookies are not parsed or altered in any way, i.e.
+# cookies are returned exactly as given in the HTTP
+# headers. If a cookie name is given, only that cookie's
+# value is returned, otherwise the entire raw cookie
+# is returned.
+####
+'raw_cookie' => <<'END_OF_FUNC',
+sub raw_cookie {
+ my($self,$key) = self_or_CGI(@_);
+
+ require CGI::Cookie;
+
+ if (defined($key)) {
+ $self->{'.raw_cookies'} = CGI::Cookie->raw_fetch
+ unless $self->{'.raw_cookies'};
+
+ return () unless $self->{'.raw_cookies'};
+ return () unless $self->{'.raw_cookies'}->{$key};
+ return $self->{'.raw_cookies'}->{$key};
+ }
+ return $self->http('cookie') || $ENV{'COOKIE'} || '';
+}
+END_OF_FUNC
+
+#### Method: virtual_host
+# Return the name of the virtual_host, which
+# is not always the same as the server
+######
+'virtual_host' => <<'END_OF_FUNC',
+sub virtual_host {
+ my $vh = http('x_forwarded_host') || http('host') || server_name();
+ $vh =~ s/:\d+$//; # get rid of port number
+ return $vh;
+}
+END_OF_FUNC
+
+#### Method: remote_host
+# Return the name of the remote host, or its IP
+# address if unavailable. If this variable isn't
+# defined, it returns "localhost" for debugging
+# purposes.
+####
+'remote_host' => <<'END_OF_FUNC',
+sub remote_host {
+ return $ENV{'REMOTE_HOST'} || $ENV{'REMOTE_ADDR'}
+ || 'localhost';
+}
+END_OF_FUNC
+
+
+#### Method: remote_addr
+# Return the IP addr of the remote host.
+####
+'remote_addr' => <<'END_OF_FUNC',
+sub remote_addr {
+ return $ENV{'REMOTE_ADDR'} || '127.0.0.1';
+}
+END_OF_FUNC
+
+
+#### Method: script_name
+# Return the partial URL to this script for
+# self-referencing scripts. Also see
+# self_url(), which returns a URL with all state information
+# preserved.
+####
+'script_name' => <<'END_OF_FUNC',
+sub script_name {
+ my ($self,@p) = self_or_default(@_);
+ if (@p) {
+ $self->{'.script_name'} = shift @p;
+ } elsif (!exists $self->{'.script_name'}) {
+ my ($script_name,$path_info) = $self->_name_and_path_from_env();
+ $self->{'.script_name'} = $script_name;
+ }
+ return $self->{'.script_name'};
+}
+END_OF_FUNC
+
+
+#### Method: referer
+# Return the HTTP_REFERER: useful for generating
+# a GO BACK button.
+####
+'referer' => <<'END_OF_FUNC',
+sub referer {
+ my($self) = self_or_CGI(@_);
+ return $self->http('referer');
+}
+END_OF_FUNC
+
+
+#### Method: server_name
+# Return the name of the server
+####
+'server_name' => <<'END_OF_FUNC',
+sub server_name {
+ return $ENV{'SERVER_NAME'} || 'localhost';
+}
+END_OF_FUNC
+
+#### Method: server_software
+# Return the name of the server software
+####
+'server_software' => <<'END_OF_FUNC',
+sub server_software {
+ return $ENV{'SERVER_SOFTWARE'} || 'cmdline';
+}
+END_OF_FUNC
+
+#### Method: virtual_port
+# Return the server port, taking virtual hosts into account
+####
+'virtual_port' => <<'END_OF_FUNC',
+sub virtual_port {
+ my($self) = self_or_default(@_);
+ my $vh = $self->http('x_forwarded_host') || $self->http('host');
+ my $protocol = $self->protocol;
+ if ($vh) {
+ return ($vh =~ /:(\d+)$/)[0] || ($protocol eq 'https' ? 443 : 80);
+ } else {
+ return $self->server_port();
+ }
+}
+END_OF_FUNC
+
+#### Method: server_port
+# Return the tcp/ip port the server is running on
+####
+'server_port' => <<'END_OF_FUNC',
+sub server_port {
+ return $ENV{'SERVER_PORT'} || 80; # for debugging
+}
+END_OF_FUNC
+
+#### Method: server_protocol
+# Return the protocol (usually HTTP/1.0)
+####
+'server_protocol' => <<'END_OF_FUNC',
+sub server_protocol {
+ return $ENV{'SERVER_PROTOCOL'} || 'HTTP/1.0'; # for debugging
+}
+END_OF_FUNC
+
+#### Method: http
+# Return the value of an HTTP variable, or
+# the list of variables if none provided
+####
+'http' => <<'END_OF_FUNC',
+sub http {
+ my ($self,$parameter) = self_or_CGI(@_);
+ if ( defined($parameter) ) {
+ $parameter =~ tr/-a-z/_A-Z/;
+ if ( $parameter =~ /^HTTP(?:_|$)/ ) {
+ return $ENV{$parameter};
+ }
+ return $ENV{"HTTP_$parameter"};
+ }
+ return grep { /^HTTP(?:_|$)/ } keys %ENV;
+}
+END_OF_FUNC
+
+#### Method: https
+# Return the value of HTTPS, or
+# the value of an HTTPS variable, or
+# the list of variables
+####
+'https' => <<'END_OF_FUNC',
+sub https {
+ my ($self,$parameter) = self_or_CGI(@_);
+ if ( defined($parameter) ) {
+ $parameter =~ tr/-a-z/_A-Z/;
+ if ( $parameter =~ /^HTTPS(?:_|$)/ ) {
+ return $ENV{$parameter};
+ }
+ return $ENV{"HTTPS_$parameter"};
+ }
+ return wantarray
+ ? grep { /^HTTPS(?:_|$)/ } keys %ENV
+ : $ENV{'HTTPS'};
+}
+END_OF_FUNC
+
+#### Method: protocol
+# Return the protocol (http or https currently)
+####
+'protocol' => <<'END_OF_FUNC',
+sub protocol {
+ local($^W)=0;
+ my $self = shift;
+ return 'https' if uc($self->https()) eq 'ON';
+ return 'https' if $self->server_port == 443;
+ my $prot = $self->server_protocol;
+ my($protocol,$version) = split('/',$prot);
+ return "\L$protocol\E";
+}
+END_OF_FUNC
+
+#### Method: remote_ident
+# Return the identity of the remote user
+# (but only if his host is running identd)
+####
+'remote_ident' => <<'END_OF_FUNC',
+sub remote_ident {
+ return (defined $ENV{'REMOTE_IDENT'}) ? $ENV{'REMOTE_IDENT'} : undef;
+}
+END_OF_FUNC
+
+
+#### Method: auth_type
+# Return the type of use verification/authorization in use, if any.
+####
+'auth_type' => <<'END_OF_FUNC',
+sub auth_type {
+ return (defined $ENV{'AUTH_TYPE'}) ? $ENV{'AUTH_TYPE'} : undef;
+}
+END_OF_FUNC
+
+
+#### Method: remote_user
+# Return the authorization name used for user
+# verification.
+####
+'remote_user' => <<'END_OF_FUNC',
+sub remote_user {
+ return (defined $ENV{'REMOTE_USER'}) ? $ENV{'REMOTE_USER'} : undef;
+}
+END_OF_FUNC
+
+
+#### Method: user_name
+# Try to return the remote user's name by hook or by
+# crook
+####
+'user_name' => <<'END_OF_FUNC',
+sub user_name {
+ my ($self) = self_or_CGI(@_);
+ return $self->http('from') || $ENV{'REMOTE_IDENT'} || $ENV{'REMOTE_USER'};
+}
+END_OF_FUNC
+
+#### Method: nosticky
+# Set or return the NOSTICKY global flag
+####
+'nosticky' => <<'END_OF_FUNC',
+sub nosticky {
+ my ($self,$param) = self_or_CGI(@_);
+ $CGI::NOSTICKY = $param if defined($param);
+ return $CGI::NOSTICKY;
+}
+END_OF_FUNC
+
+#### Method: nph
+# Set or return the NPH global flag
+####
+'nph' => <<'END_OF_FUNC',
+sub nph {
+ my ($self,$param) = self_or_CGI(@_);
+ $CGI::NPH = $param if defined($param);
+ return $CGI::NPH;
+}
+END_OF_FUNC
+
+#### Method: private_tempfiles
+# Set or return the private_tempfiles global flag
+####
+'private_tempfiles' => <<'END_OF_FUNC',
+sub private_tempfiles {
+ my ($self,$param) = self_or_CGI(@_);
+ $CGI::PRIVATE_TEMPFILES = $param if defined($param);
+ return $CGI::PRIVATE_TEMPFILES;
+}
+END_OF_FUNC
+#### Method: close_upload_files
+# Set or return the close_upload_files global flag
+####
+'close_upload_files' => <<'END_OF_FUNC',
+sub close_upload_files {
+ my ($self,$param) = self_or_CGI(@_);
+ $CGI::CLOSE_UPLOAD_FILES = $param if defined($param);
+ return $CGI::CLOSE_UPLOAD_FILES;
+}
+END_OF_FUNC
+
+
+#### Method: default_dtd
+# Set or return the default_dtd global
+####
+'default_dtd' => <<'END_OF_FUNC',
+sub default_dtd {
+ my ($self,$param,$param2) = self_or_CGI(@_);
+ if (defined $param2 && defined $param) {
+ $CGI::DEFAULT_DTD = [ $param, $param2 ];
+ } elsif (defined $param) {
+ $CGI::DEFAULT_DTD = $param;
+ }
+ return $CGI::DEFAULT_DTD;
+}
+END_OF_FUNC
+
+# -------------- really private subroutines -----------------
+'_maybe_escapeHTML' => <<'END_OF_FUNC',
+sub _maybe_escapeHTML {
+ # hack to work around earlier hacks
+ push @_,$_[0] if @_==1 && $_[0] eq 'CGI';
+ my ($self,$toencode,$newlinestoo) = CGI::self_or_default(@_);
+ return undef unless defined($toencode);
+ return $toencode if ref($self) && !$self->{'escape'};
+ return $self->escapeHTML($toencode, $newlinestoo);
+}
+END_OF_FUNC
+
+'previous_or_default' => <<'END_OF_FUNC',
+sub previous_or_default {
+ my($self,$name,$defaults,$override) = @_;
+ my(%selected);
+
+ if (!$override && ($self->{'.fieldnames'}->{$name} ||
+ defined($self->param($name)) ) ) {
+ $selected{$_}++ for $self->param($name);
+ } elsif (defined($defaults) && ref($defaults) &&
+ (ref($defaults) eq 'ARRAY')) {
+ $selected{$_}++ for @{$defaults};
+ } else {
+ $selected{$defaults}++ if defined($defaults);
+ }
+
+ return %selected;
+}
+END_OF_FUNC
+
+'register_parameter' => <<'END_OF_FUNC',
+sub register_parameter {
+ my($self,$param) = @_;
+ $self->{'.parametersToAdd'}->{$param}++;
+}
+END_OF_FUNC
+
+'get_fields' => <<'END_OF_FUNC',
+sub get_fields {
+ my($self) = @_;
+ return $self->CGI::hidden('-name'=>'.cgifields',
+ '-values'=>[keys %{$self->{'.parametersToAdd'}}],
+ '-override'=>1);
+}
+END_OF_FUNC
+
+'read_from_cmdline' => <<'END_OF_FUNC',
+sub read_from_cmdline {
+ my($input,@words);
+ my($query_string);
+ my($subpath);
+ if ($DEBUG && @ARGV) {
+ @words = @ARGV;
+ } elsif ($DEBUG > 1) {
+ require "shellwords.pl";
+ print STDERR "(offline mode: enter name=value pairs on standard input; press ^D or ^Z when done)\n";
+ chomp(@lines = <STDIN>); # remove newlines
+ $input = join(" ",@lines);
+ @words = &shellwords($input);
+ }
+ for (@words) {
+ s/\\=/%3D/g;
+ s/\\&/%26/g;
+ }
+
+ if ("@words"=~/=/) {
+ $query_string = join('&',@words);
+ } else {
+ $query_string = join('+',@words);
+ }
+ if ($query_string =~ /^(.*?)\?(.*)$/)
+ {
+ $query_string = $2;
+ $subpath = $1;
+ }
+ return { 'query_string' => $query_string, 'subpath' => $subpath };
+}
+END_OF_FUNC
+
+#####
+# subroutine: read_multipart
+#
+# Read multipart data and store it into our parameters.
+# An interesting feature is that if any of the parts is a file, we
+# create a temporary file and open up a filehandle on it so that the
+# caller can read from it if necessary.
+#####
+'read_multipart' => <<'END_OF_FUNC',
+sub read_multipart {
+ my($self,$boundary,$length) = @_;
+ my($buffer) = $self->new_MultipartBuffer($boundary,$length);
+ return unless $buffer;
+ my(%header,$body);
+ my $filenumber = 0;
+ while (!$buffer->eof) {
+ %header = $buffer->readHeader;
+
+ unless (%header) {
+ $self->cgi_error("400 Bad request (malformed multipart POST)");
+ return;
+ }
+
+ $header{'Content-Disposition'} ||= ''; # quench uninit variable warning
+
+ my($param)= $header{'Content-Disposition'}=~/[\s;]name="([^"]*)"/;
+ $param .= $TAINTED;
+
+ # See RFC 1867, 2183, 2045
+ # NB: File content will be loaded into memory should
+ # content-disposition parsing fail.
+ my ($filename) = $header{'Content-Disposition'}
+ =~/ filename=(("[^"]*")|([a-z\d!\#'\*\+,\.^_\`\{\}\|\~]*))/i;
+
+ $filename ||= ''; # quench uninit variable warning
+
+ $filename =~ s/^"([^"]*)"$/$1/;
+ # Test for Opera's multiple upload feature
+ my($multipart) = ( defined( $header{'Content-Type'} ) &&
+ $header{'Content-Type'} =~ /multipart\/mixed/ ) ?
+ 1 : 0;
+
+ # add this parameter to our list
+ $self->add_parameter($param);
+
+ # If no filename specified, then just read the data and assign it
+ # to our parameter list.
+ if ( ( !defined($filename) || $filename eq '' ) && !$multipart ) {
+ my($value) = $buffer->readBody;
+ $value .= $TAINTED;
+ push(@{$self->{param}{$param}},$value);
+ next;
+ }
+
+ my ($tmpfile,$tmp,$filehandle);
+ UPLOADS: {
+ # If we get here, then we are dealing with a potentially large
+ # uploaded form. Save the data to a temporary file, then open
+ # the file for reading.
+
+ # skip the file if uploads disabled
+ if ($DISABLE_UPLOADS) {
+ while (defined($data = $buffer->read)) { }
+ last UPLOADS;
+ }
+
+ # set the filename to some recognizable value
+ if ( ( !defined($filename) || $filename eq '' ) && $multipart ) {
+ $filename = "multipart/mixed";
+ }
+
+ # choose a relatively unpredictable tmpfile sequence number
+ my $seqno = unpack("%16C*",join('',localtime,grep {defined $_} values %ENV));
+ for (my $cnt=10;$cnt>0;$cnt--) {
+ next unless $tmpfile = CGITempFile->new($seqno);
+ $tmp = $tmpfile->as_string;
+ last if defined($filehandle = Fh->new($filename,$tmp,$PRIVATE_TEMPFILES));
+ $seqno += int rand(100);
+ }
+ die "CGI.pm open of tmpfile $tmp/$filename failed: $!\n" unless defined $filehandle;
+ $CGI::DefaultClass->binmode($filehandle) if $CGI::needs_binmode
+ && defined fileno($filehandle);
+
+ # if this is an multipart/mixed attachment, save the header
+ # together with the body for later parsing with an external
+ # MIME parser module
+ if ( $multipart ) {
+ for ( keys %header ) {
+ print $filehandle "$_: $header{$_}${CRLF}";
+ }
+ print $filehandle "${CRLF}";
+ }
+
+ my ($data);
+ local($\) = '';
+ my $totalbytes = 0;
+ while (defined($data = $buffer->read)) {
+ if (defined $self->{'.upload_hook'})
+ {
+ $totalbytes += length($data);
+ &{$self->{'.upload_hook'}}($filename ,$data, $totalbytes, $self->{'.upload_data'});
+ }
+ print $filehandle $data if ($self->{'use_tempfile'});
+ }
+
+ # back up to beginning of file
+ seek($filehandle,0,0);
+
+ ## Close the filehandle if requested this allows a multipart MIME
+ ## upload to contain many files, and we won't die due to too many
+ ## open file handles. The user can access the files using the hash
+ ## below.
+ close $filehandle if $CLOSE_UPLOAD_FILES;
+ $CGI::DefaultClass->binmode($filehandle) if $CGI::needs_binmode;
+
+ # Save some information about the uploaded file where we can get
+ # at it later.
+ # Use the typeglob as the key, as this is guaranteed to be
+ # unique for each filehandle. Don't use the file descriptor as
+ # this will be re-used for each filehandle if the
+ # close_upload_files feature is used.
+ $self->{'.tmpfiles'}->{$$filehandle}= {
+ hndl => $filehandle,
+ name => $tmpfile,
+ info => {%header},
+ };
+ push(@{$self->{param}{$param}},$filehandle);
+ }
+ }
+}
+END_OF_FUNC
+
+#####
+# subroutine: read_multipart_related
+#
+# Read multipart/related data and store it into our parameters. The
+# first parameter sets the start of the data. The part identified by
+# this Content-ID will not be stored as a file upload, but will be
+# returned by this method. All other parts will be available as file
+# uploads accessible by their Content-ID
+#####
+'read_multipart_related' => <<'END_OF_FUNC',
+sub read_multipart_related {
+ my($self,$start,$boundary,$length) = @_;
+ my($buffer) = $self->new_MultipartBuffer($boundary,$length);
+ return unless $buffer;
+ my(%header,$body);
+ my $filenumber = 0;
+ my $returnvalue;
+ while (!$buffer->eof) {
+ %header = $buffer->readHeader;
+
+ unless (%header) {
+ $self->cgi_error("400 Bad request (malformed multipart POST)");
+ return;
+ }
+
+ my($param) = $header{'Content-ID'}=~/\<([^\>]*)\>/;
+ $param .= $TAINTED;
+
+ # If this is the start part, then just read the data and assign it
+ # to our return variable.
+ if ( $param eq $start ) {
+ $returnvalue = $buffer->readBody;
+ $returnvalue .= $TAINTED;
+ next;
+ }
+
+ # add this parameter to our list
+ $self->add_parameter($param);
+
+ my ($tmpfile,$tmp,$filehandle);
+ UPLOADS: {
+ # If we get here, then we are dealing with a potentially large
+ # uploaded form. Save the data to a temporary file, then open
+ # the file for reading.
+
+ # skip the file if uploads disabled
+ if ($DISABLE_UPLOADS) {
+ while (defined($data = $buffer->read)) { }
+ last UPLOADS;
+ }
+
+ # choose a relatively unpredictable tmpfile sequence number
+ my $seqno = unpack("%16C*",join('',localtime,grep {defined $_} values %ENV));
+ for (my $cnt=10;$cnt>0;$cnt--) {
+ next unless $tmpfile = CGITempFile->new($seqno);
+ $tmp = $tmpfile->as_string;
+ last if defined($filehandle = Fh->new($param,$tmp,$PRIVATE_TEMPFILES));
+ $seqno += int rand(100);
+ }
+ die "CGI open of tmpfile: $!\n" unless defined $filehandle;
+ $CGI::DefaultClass->binmode($filehandle) if $CGI::needs_binmode
+ && defined fileno($filehandle);
+
+ my ($data);
+ local($\) = '';
+ my $totalbytes;
+ while (defined($data = $buffer->read)) {
+ if (defined $self->{'.upload_hook'})
+ {
+ $totalbytes += length($data);
+ &{$self->{'.upload_hook'}}($param ,$data, $totalbytes, $self->{'.upload_data'});
+ }
+ print $filehandle $data if ($self->{'use_tempfile'});
+ }
+
+ # back up to beginning of file
+ seek($filehandle,0,0);
+
+ ## Close the filehandle if requested this allows a multipart MIME
+ ## upload to contain many files, and we won't die due to too many
+ ## open file handles. The user can access the files using the hash
+ ## below.
+ close $filehandle if $CLOSE_UPLOAD_FILES;
+ $CGI::DefaultClass->binmode($filehandle) if $CGI::needs_binmode;
+
+ # Save some information about the uploaded file where we can get
+ # at it later.
+ # Use the typeglob as the key, as this is guaranteed to be
+ # unique for each filehandle. Don't use the file descriptor as
+ # this will be re-used for each filehandle if the
+ # close_upload_files feature is used.
+ $self->{'.tmpfiles'}->{$$filehandle}= {
+ hndl => $filehandle,
+ name => $tmpfile,
+ info => {%header},
+ };
+ push(@{$self->{param}{$param}},$filehandle);
+ }
+ }
+ return $returnvalue;
+}
+END_OF_FUNC
+
+
+'upload' =><<'END_OF_FUNC',
+sub upload {
+ my($self,$param_name) = self_or_default(@_);
+ my @param = grep {ref($_) && defined(fileno($_))} $self->param($param_name);
+ return unless @param;
+ return wantarray ? @param : $param[0];
+}
+END_OF_FUNC
+
+'tmpFileName' => <<'END_OF_FUNC',
+sub tmpFileName {
+ my($self,$filename) = self_or_default(@_);
+ return $self->{'.tmpfiles'}->{$$filename}->{name} ?
+ $self->{'.tmpfiles'}->{$$filename}->{name}->as_string
+ : '';
+}
+END_OF_FUNC
+
+'uploadInfo' => <<'END_OF_FUNC',
+sub uploadInfo {
+ my($self,$filename) = self_or_default(@_);
+ return $self->{'.tmpfiles'}->{$$filename}->{info};
+}
+END_OF_FUNC
+
+# internal routine, don't use
+'_set_values_and_labels' => <<'END_OF_FUNC',
+sub _set_values_and_labels {
+ my $self = shift;
+ my ($v,$l,$n) = @_;
+ $$l = $v if ref($v) eq 'HASH' && !ref($$l);
+ return $self->param($n) if !defined($v);
+ return $v if !ref($v);
+ return ref($v) eq 'HASH' ? keys %$v : @$v;
+}
+END_OF_FUNC
+
+# internal routine, don't use
+'_set_attributes' => <<'END_OF_FUNC',
+sub _set_attributes {
+ my $self = shift;
+ my($element, $attributes) = @_;
+ return '' unless defined($attributes->{$element});
+ $attribs = ' ';
+ for my $attrib (keys %{$attributes->{$element}}) {
+ (my $clean_attrib = $attrib) =~ s/^-//;
+ $attribs .= "@{[lc($clean_attrib)]}=\"$attributes->{$element}{$attrib}\" ";
+ }
+ $attribs =~ s/ $//;
+ return $attribs;
+}
+END_OF_FUNC
+
+'_compile_all' => <<'END_OF_FUNC',
+sub _compile_all {
+ for (@_) {
+ next if defined(&$_);
+ $AUTOLOAD = "CGI::$_";
+ _compile();
+ }
+}
+END_OF_FUNC
+
+);
+END_OF_AUTOLOAD
+;
+
+#########################################################
+# Globals and stubs for other packages that we use.
+#########################################################
+
+################### Fh -- lightweight filehandle ###############
+package Fh;
+
+use overload
+ '""' => \&asString,
+ 'cmp' => \&compare,
+ 'fallback'=>1;
+
+$FH='fh00000';
+
+*Fh::AUTOLOAD = \&CGI::AUTOLOAD;
+
+sub DESTROY {
+ my $self = shift;
+ close $self;
+}
+
+$AUTOLOADED_ROUTINES = ''; # prevent -w error
+$AUTOLOADED_ROUTINES=<<'END_OF_AUTOLOAD';
+%SUBS = (
+'asString' => <<'END_OF_FUNC',
+sub asString {
+ my $self = shift;
+ # get rid of package name
+ (my $i = $$self) =~ s/^\*(\w+::fh\d{5})+//;
+ $i =~ s/%(..)/ chr(hex($1)) /eg;
+ return $i.$CGI::TAINTED;
+# BEGIN DEAD CODE
+# This was an extremely clever patch that allowed "use strict refs".
+# Unfortunately it relied on another bug that caused leaky file descriptors.
+# The underlying bug has been fixed, so this no longer works. However
+# "strict refs" still works for some reason.
+# my $self = shift;
+# return ${*{$self}{SCALAR}};
+# END DEAD CODE
+}
+END_OF_FUNC
+
+'compare' => <<'END_OF_FUNC',
+sub compare {
+ my $self = shift;
+ my $value = shift;
+ return "$self" cmp $value;
+}
+END_OF_FUNC
+
+'new' => <<'END_OF_FUNC',
+sub new {
+ my($pack,$name,$file,$delete) = @_;
+ _setup_symbols(@SAVED_SYMBOLS) if @SAVED_SYMBOLS;
+ require Fcntl unless defined &Fcntl::O_RDWR;
+ (my $safename = $name) =~ s/([':%])/ sprintf '%%%02X', ord $1 /eg;
+ my $fv = ++$FH . $safename;
+ my $ref = \*{"Fh::$fv"};
+
+ # Note this same regex is also used elsewhere in the same file for CGITempFile::new
+ $file =~ m!^([a-zA-Z0-9_ \'\":/.\$\\\+-]+)$! || return;
+ my $safe = $1;
+ sysopen($ref,$safe,Fcntl::O_RDWR()|Fcntl::O_CREAT()|Fcntl::O_EXCL(),0600) || return;
+ unlink($safe) if $delete;
+ CORE::delete $Fh::{$fv};
+ return bless $ref,$pack;
+}
+END_OF_FUNC
+
+'handle' => <<'END_OF_FUNC',
+sub handle {
+ my $self = shift;
+ eval "require IO::Handle" unless IO::Handle->can('new_from_fd');
+ return IO::Handle->new_from_fd(fileno $self,"<");
+}
+END_OF_FUNC
+
+);
+END_OF_AUTOLOAD
+
+######################## MultipartBuffer ####################
+package MultipartBuffer;
+
+use constant DEBUG => 0;
+
+# how many bytes to read at a time. We use
+# a 4K buffer by default.
+$INITIAL_FILLUNIT = 1024 * 4;
+$TIMEOUT = 240*60; # 4 hour timeout for big files
+$SPIN_LOOP_MAX = 2000; # bug fix for some Netscape servers
+$CRLF=$CGI::CRLF;
+
+#reuse the autoload function
+*MultipartBuffer::AUTOLOAD = \&CGI::AUTOLOAD;
+
+# avoid autoloader warnings
+sub DESTROY {}
+
+###############################################################################
+################# THESE FUNCTIONS ARE AUTOLOADED ON DEMAND ####################
+###############################################################################
+$AUTOLOADED_ROUTINES = ''; # prevent -w error
+$AUTOLOADED_ROUTINES=<<'END_OF_AUTOLOAD';
+%SUBS = (
+
+'new' => <<'END_OF_FUNC',
+sub new {
+ my($package,$interface,$boundary,$length) = @_;
+ $FILLUNIT = $INITIAL_FILLUNIT;
+ $CGI::DefaultClass->binmode($IN); # if $CGI::needs_binmode; # just do it always
+
+ # If the user types garbage into the file upload field,
+ # then Netscape passes NOTHING to the server (not good).
+ # We may hang on this read in that case. So we implement
+ # a read timeout. If nothing is ready to read
+ # by then, we return.
+
+ # Netscape seems to be a little bit unreliable
+ # about providing boundary strings.
+ my $boundary_read = 0;
+ if ($boundary) {
+
+ # Under the MIME spec, the boundary consists of the
+ # characters "--" PLUS the Boundary string
+
+ # BUG: IE 3.01 on the Macintosh uses just the boundary -- not
+ # the two extra hyphens. We do a special case here on the user-agent!!!!
+ $boundary = "--$boundary" unless CGI::user_agent('MSIE\s+3\.0[12];\s*Mac|DreamPassport');
+
+ } else { # otherwise we find it ourselves
+ my($old);
+ ($old,$/) = ($/,$CRLF); # read a CRLF-delimited line
+ $boundary = <STDIN>; # BUG: This won't work correctly under mod_perl
+ $length -= length($boundary);
+ chomp($boundary); # remove the CRLF
+ $/ = $old; # restore old line separator
+ $boundary_read++;
+ }
+
+ my $self = {LENGTH=>$length,
+ CHUNKED=>!$length,
+ BOUNDARY=>$boundary,
+ INTERFACE=>$interface,
+ BUFFER=>'',
+ };
+
+ $FILLUNIT = length($boundary)
+ if length($boundary) > $FILLUNIT;
+
+ my $retval = bless $self,ref $package || $package;
+
+ # Read the preamble and the topmost (boundary) line plus the CRLF.
+ unless ($boundary_read) {
+ while ($self->read(0)) { }
+ }
+ die "Malformed multipart POST: data truncated\n" if $self->eof;
+
+ return $retval;
+}
+END_OF_FUNC
+
+'readHeader' => <<'END_OF_FUNC',
+sub readHeader {
+ my($self) = @_;
+ my($end);
+ my($ok) = 0;
+ my($bad) = 0;
+
+ local($CRLF) = "\015\012" if $CGI::OS eq 'VMS' || $CGI::EBCDIC;
+
+ do {
+ $self->fillBuffer($FILLUNIT);
+ $ok++ if ($end = index($self->{BUFFER},"${CRLF}${CRLF}")) >= 0;
+ $ok++ if $self->{BUFFER} eq '';
+ $bad++ if !$ok && $self->{LENGTH} <= 0;
+ # this was a bad idea
+ # $FILLUNIT *= 2 if length($self->{BUFFER}) >= $FILLUNIT;
+ } until $ok || $bad;
+ return () if $bad;
+
+ #EBCDIC NOTE: translate header into EBCDIC, but watch out for continuation lines!
+
+ my($header) = substr($self->{BUFFER},0,$end+2);
+ substr($self->{BUFFER},0,$end+4) = '';
+ my %return;
+
+ if ($CGI::EBCDIC) {
+ warn "untranslated header=$header\n" if DEBUG;
+ $header = CGI::Util::ascii2ebcdic($header);
+ warn "translated header=$header\n" if DEBUG;
+ }
+
+ # See RFC 2045 Appendix A and RFC 822 sections 3.4.8
+ # (Folding Long Header Fields), 3.4.3 (Comments)
+ # and 3.4.5 (Quoted-Strings).
+
+ my $token = '[-\w!\#$%&\'*+.^_\`|{}~]';
+ $header=~s/$CRLF\s+/ /og; # merge continuation lines
+
+ while ($header=~/($token+):\s+([^$CRLF]*)/mgox) {
+ my ($field_name,$field_value) = ($1,$2);
+ $field_name =~ s/\b(\w)/uc($1)/eg; #canonicalize
+ $return{$field_name}=$field_value;
+ }
+ return %return;
+}
+END_OF_FUNC
+
+# This reads and returns the body as a single scalar value.
+'readBody' => <<'END_OF_FUNC',
+sub readBody {
+ my($self) = @_;
+ my($data);
+ my($returnval)='';
+
+ #EBCDIC NOTE: want to translate returnval into EBCDIC HERE
+
+ while (defined($data = $self->read)) {
+ $returnval .= $data;
+ }
+
+ if ($CGI::EBCDIC) {
+ warn "untranslated body=$returnval\n" if DEBUG;
+ $returnval = CGI::Util::ascii2ebcdic($returnval);
+ warn "translated body=$returnval\n" if DEBUG;
+ }
+ return $returnval;
+}
+END_OF_FUNC
+
+# This will read $bytes or until the boundary is hit, whichever happens
+# first. After the boundary is hit, we return undef. The next read will
+# skip over the boundary and begin reading again;
+'read' => <<'END_OF_FUNC',
+sub read {
+ my($self,$bytes) = @_;
+
+ # default number of bytes to read
+ $bytes = $bytes || $FILLUNIT;
+
+ # Fill up our internal buffer in such a way that the boundary
+ # is never split between reads.
+ $self->fillBuffer($bytes);
+
+ my $boundary_start = $CGI::EBCDIC ? CGI::Util::ebcdic2ascii($self->{BOUNDARY}) : $self->{BOUNDARY};
+ my $boundary_end = $CGI::EBCDIC ? CGI::Util::ebcdic2ascii($self->{BOUNDARY}.'--') : $self->{BOUNDARY}.'--';
+
+ # Find the boundary in the buffer (it may not be there).
+ my $start = index($self->{BUFFER},$boundary_start);
+
+ warn "boundary=$self->{BOUNDARY} length=$self->{LENGTH} start=$start\n" if DEBUG;
+
+ # protect against malformed multipart POST operations
+ die "Malformed multipart POST\n" unless $self->{CHUNKED} || ($start >= 0 || $self->{LENGTH} > 0);
+
+ #EBCDIC NOTE: want to translate boundary search into ASCII here.
+
+ # If the boundary begins the data, then skip past it
+ # and return undef.
+ if ($start == 0) {
+
+ # clear us out completely if we've hit the last boundary.
+ if (index($self->{BUFFER},$boundary_end)==0) {
+ $self->{BUFFER}='';
+ $self->{LENGTH}=0;
+ return undef;
+ }
+
+ # just remove the boundary.
+ substr($self->{BUFFER},0,length($boundary_start))='';
+ $self->{BUFFER} =~ s/^\012\015?//;
+ return undef;
+ }
+
+ my $bytesToReturn;
+ if ($start > 0) { # read up to the boundary
+ $bytesToReturn = $start-2 > $bytes ? $bytes : $start;
+ } else { # read the requested number of bytes
+ # leave enough bytes in the buffer to allow us to read
+ # the boundary. Thanks to Kevin Hendrick for finding
+ # this one.
+ $bytesToReturn = $bytes - (length($boundary_start)+1);
+ }
+
+ my $returnval=substr($self->{BUFFER},0,$bytesToReturn);
+ substr($self->{BUFFER},0,$bytesToReturn)='';
+
+ # If we hit the boundary, remove the CRLF from the end.
+ return ($bytesToReturn==$start)
+ ? substr($returnval,0,-2) : $returnval;
+}
+END_OF_FUNC
+
+
+# This fills up our internal buffer in such a way that the
+# boundary is never split between reads
+'fillBuffer' => <<'END_OF_FUNC',
+sub fillBuffer {
+ my($self,$bytes) = @_;
+ return unless $self->{CHUNKED} || $self->{LENGTH};
+
+ my($boundaryLength) = length($self->{BOUNDARY});
+ my($bufferLength) = length($self->{BUFFER});
+ my($bytesToRead) = $bytes - $bufferLength + $boundaryLength + 2;
+ $bytesToRead = $self->{LENGTH} if !$self->{CHUNKED} && $self->{LENGTH} < $bytesToRead;
+
+ # Try to read some data. We may hang here if the browser is screwed up.
+ my $bytesRead = $self->{INTERFACE}->read_from_client(\$self->{BUFFER},
+ $bytesToRead,
+ $bufferLength);
+ warn "bytesToRead=$bytesToRead, bufferLength=$bufferLength, buffer=$self->{BUFFER}\n" if DEBUG;
+ $self->{BUFFER} = '' unless defined $self->{BUFFER};
+
+ # An apparent bug in the Apache server causes the read()
+ # to return zero bytes repeatedly without blocking if the
+ # remote user aborts during a file transfer. I don't know how
+ # they manage this, but the workaround is to abort if we get
+ # more than SPIN_LOOP_MAX consecutive zero reads.
+ if ($bytesRead <= 0) {
+ die "CGI.pm: Server closed socket during multipart read (client aborted?).\n"
+ if ($self->{ZERO_LOOP_COUNTER}++ >= $SPIN_LOOP_MAX);
+ } else {
+ $self->{ZERO_LOOP_COUNTER}=0;
+ }
+
+ $self->{LENGTH} -= $bytesRead if !$self->{CHUNKED} && $bytesRead;
+}
+END_OF_FUNC
+
+
+# Return true when we've finished reading
+'eof' => <<'END_OF_FUNC'
+sub eof {
+ my($self) = @_;
+ return 1 if (length($self->{BUFFER}) == 0)
+ && ($self->{LENGTH} <= 0);
+ undef;
+}
+END_OF_FUNC
+
+);
+END_OF_AUTOLOAD
+
+####################################################################################
+################################## TEMPORARY FILES #################################
+####################################################################################
+package CGITempFile;
+
+sub find_tempdir {
+ $SL = $CGI::SL;
+ $MAC = $CGI::OS eq 'MACINTOSH';
+ my ($vol) = $MAC ? MacPerl::Volumes() =~ /:(.*)/ : "";
+ unless (defined $TMPDIRECTORY) {
+ @TEMP=("${SL}usr${SL}tmp","${SL}var${SL}tmp",
+ "C:${SL}temp","${SL}tmp","${SL}temp",
+ "${vol}${SL}Temporary Items",
+ "${SL}WWW_ROOT", "${SL}SYS\$SCRATCH",
+ "C:${SL}system${SL}temp");
+
+ if( $CGI::OS eq 'WINDOWS' ){
+ # PeterH: These evars may not exist if this is invoked within a service and untainting
+ # is in effect - with 'use warnings' the undefined array entries causes Perl to die
+ unshift(@TEMP,$ENV{TEMP}) if defined $ENV{TEMP};
+ unshift(@TEMP,$ENV{TMP}) if defined $ENV{TMP};
+ unshift(@TEMP,$ENV{WINDIR} . $SL . 'TEMP') if defined $ENV{WINDIR};
+ }
+
+ unshift(@TEMP,$ENV{'TMPDIR'}) if defined $ENV{'TMPDIR'};
+
+ # this feature was supposed to provide per-user tmpfiles, but
+ # it is problematic.
+ # unshift(@TEMP,(getpwuid($<))[7].'/tmp') if $CGI::OS eq 'UNIX';
+ # Rob: getpwuid() is unfortunately UNIX specific. On brain dead OS'es this
+ # : can generate a 'getpwuid() not implemented' exception, even though
+ # : it's never called. Found under DOS/Win with the DJGPP perl port.
+ # : Refer to getpwuid() only at run-time if we're fortunate and have UNIX.
+ # unshift(@TEMP,(eval {(getpwuid($>))[7]}).'/tmp') if $CGI::OS eq 'UNIX' and $> != 0;
+
+ for (@TEMP) {
+ do {$TMPDIRECTORY = $_; last} if -d $_ && -w _;
+ }
+ }
+ $TMPDIRECTORY = $MAC ? "" : "." unless $TMPDIRECTORY;
+}
+
+find_tempdir();
+
+$MAXTRIES = 5000;
+
+# cute feature, but overload implementation broke it
+# %OVERLOAD = ('""'=>'as_string');
+*CGITempFile::AUTOLOAD = \&CGI::AUTOLOAD;
+
+sub DESTROY {
+ my($self) = @_;
+ $$self =~ m!^([a-zA-Z0-9_ \'\":/.\$\\~-]+)$! || return;
+ my $safe = $1; # untaint operation
+ unlink $safe; # get rid of the file
+}
+
+###############################################################################
+################# THESE FUNCTIONS ARE AUTOLOADED ON DEMAND ####################
+###############################################################################
+$AUTOLOADED_ROUTINES = ''; # prevent -w error
+$AUTOLOADED_ROUTINES=<<'END_OF_AUTOLOAD';
+%SUBS = (
+
+'new' => <<'END_OF_FUNC',
+sub new {
+ my($package,$sequence) = @_;
+ my $filename;
+ unless (-w $TMPDIRECTORY) {
+ $TMPDIRECTORY = undef;
+ find_tempdir();
+ }
+ for (my $i = 0; $i < $MAXTRIES; $i++) {
+ last if ! -f ($filename = sprintf("\%s${SL}CGItemp%d", $TMPDIRECTORY, $sequence++));
+ }
+ # check that it is a more-or-less valid filename
+ # Note this same regex is also used elsewhere in the same file for Fh::new
+ return unless $filename =~ m!^([a-zA-Z0-9_ \'\":/.\$\\\+-]+)$!;
+ # this used to untaint, now it doesn't
+ # $filename = $1;
+ return bless \$filename;
+}
+END_OF_FUNC
+
+'as_string' => <<'END_OF_FUNC'
+sub as_string {
+ my($self) = @_;
+ return $$self;
+}
+END_OF_FUNC
+
+);
+END_OF_AUTOLOAD
+
+package CGI;
+
+# We get a whole bunch of warnings about "possibly uninitialized variables"
+# when running with the -w switch. Touch them all once to get rid of the
+# warnings. This is ugly and I hate it.
+if ($^W) {
+ $CGI::CGI = '';
+ $CGI::CGI=<<EOF;
+ $CGI::VERSION;
+ $MultipartBuffer::SPIN_LOOP_MAX;
+ $MultipartBuffer::CRLF;
+ $MultipartBuffer::TIMEOUT;
+ $MultipartBuffer::INITIAL_FILLUNIT;
+EOF
+ ;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+CGI - Handle Common Gateway Interface requests and responses
+
+=head1 SYNOPSIS
+
+ use CGI;
+
+ my $q = CGI->new;
+
+ # Process an HTTP request
+ @values = $q->param('form_field');
+
+ $fh = $q->upload('file_field');
+
+ $riddle = $query->cookie('riddle_name');
+ %answers = $query->cookie('answers');
+
+ # Prepare various HTTP responses
+ print $q->header();
+ print $q->header('application/json');
+
+ $cookie1 = $q->cookie(-name=>'riddle_name', -value=>"The Sphynx's Question");
+ $cookie2 = $q->cookie(-name=>'answers', -value=>\%answers);
+ print $q->header(
+ -type => 'image/gif',
+ -expires => '+3d',
+ -cookie => [$cookie1,$cookie2]
+ );
+
+ print $q->redirect('http://somewhere.else/in/movie/land');
+
+=head1 DESCRIPTION
+
+CGI.pm is a stable, complete and mature solution for processing and preparing
+HTTP requests and responses. Major features including processing form
+submissions, file uploads, reading and writing cookies, query string generation
+and manipulation, and processing and preparing HTTP headers. Some HTML
+generation utilities are included as well.
+
+CGI.pm performs very well in in a vanilla CGI.pm environment and also comes
+with built-in support for mod_perl and mod_perl2 as well as FastCGI.
+
+It has the benefit of having developed and refined over 10 years with input
+from dozens of contributors and being deployed on thousands of websites.
+CGI.pm has been included in the Perl distribution since Perl 5.4, and has
+become a de-facto standard.
+
+=head2 PROGRAMMING STYLE
+
+There are two styles of programming with CGI.pm, an object-oriented
+style and a function-oriented style. In the object-oriented style you
+create one or more CGI objects and then use object methods to create
+the various elements of the page. Each CGI object starts out with the
+list of named parameters that were passed to your CGI script by the
+server. You can modify the objects, save them to a file or database
+and recreate them. Because each object corresponds to the "state" of
+the CGI script, and because each object's parameter list is
+independent of the others, this allows you to save the state of the
+script and restore it later.
+
+For example, using the object oriented style, here is how you create
+a simple "Hello World" HTML page:
+
+ #!/usr/local/bin/perl -w
+ use CGI; # load CGI routines
+ $q = CGI->new; # create new CGI object
+ print $q->header, # create the HTTP header
+ $q->start_html('hello world'), # start the HTML
+ $q->h1('hello world'), # level 1 header
+ $q->end_html; # end the HTML
+
+In the function-oriented style, there is one default CGI object that
+you rarely deal with directly. Instead you just call functions to
+retrieve CGI parameters, create HTML tags, manage cookies, and so
+on. This provides you with a cleaner programming interface, but
+limits you to using one CGI object at a time. The following example
+prints the same page, but uses the function-oriented interface.
+The main differences are that we now need to import a set of functions
+into our name space (usually the "standard" functions), and we don't
+need to create the CGI object.
+
+ #!/usr/local/bin/perl
+ use CGI qw/:standard/; # load standard CGI routines
+ print header, # create the HTTP header
+ start_html('hello world'), # start the HTML
+ h1('hello world'), # level 1 header
+ end_html; # end the HTML
+
+The examples in this document mainly use the object-oriented style.
+See HOW TO IMPORT FUNCTIONS for important information on
+function-oriented programming in CGI.pm
+
+=head2 CALLING CGI.PM ROUTINES
+
+Most CGI.pm routines accept several arguments, sometimes as many as 20
+optional ones! To simplify this interface, all routines use a named
+argument calling style that looks like this:
+
+ print $q->header(-type=>'image/gif',-expires=>'+3d');
+
+Each argument name is preceded by a dash. Neither case nor order
+matters in the argument list. -type, -Type, and -TYPE are all
+acceptable. In fact, only the first argument needs to begin with a
+dash. If a dash is present in the first argument, CGI.pm assumes
+dashes for the subsequent ones.
+
+Several routines are commonly called with just one argument. In the
+case of these routines you can provide the single argument without an
+argument name. header() happens to be one of these routines. In this
+case, the single argument is the document type.
+
+ print $q->header('text/html');
+
+Other such routines are documented below.
+
+Sometimes named arguments expect a scalar, sometimes a reference to an
+array, and sometimes a reference to a hash. Often, you can pass any
+type of argument and the routine will do whatever is most appropriate.
+For example, the param() routine is used to set a CGI parameter to a
+single or a multi-valued value. The two cases are shown below:
+
+ $q->param(-name=>'veggie',-value=>'tomato');
+ $q->param(-name=>'veggie',-value=>['tomato','tomahto','potato','potahto']);
+
+A large number of routines in CGI.pm actually aren't specifically
+defined in the module, but are generated automatically as needed.
+These are the "HTML shortcuts," routines that generate HTML tags for
+use in dynamically-generated pages. HTML tags have both attributes
+(the attribute="value" pairs within the tag itself) and contents (the
+part between the opening and closing pairs.) To distinguish between
+attributes and contents, CGI.pm uses the convention of passing HTML
+attributes as a hash reference as the first argument, and the
+contents, if any, as any subsequent arguments. It works out like
+this:
+
+ Code Generated HTML
+ ---- --------------
+ h1() <h1>
+ h1('some','contents'); <h1>some contents</h1>
+ h1({-align=>left}); <h1 align="LEFT">
+ h1({-align=>left},'contents'); <h1 align="LEFT">contents</h1>
+
+HTML tags are described in more detail later.
+
+Many newcomers to CGI.pm are puzzled by the difference between the
+calling conventions for the HTML shortcuts, which require curly braces
+around the HTML tag attributes, and the calling conventions for other
+routines, which manage to generate attributes without the curly
+brackets. Don't be confused. As a convenience the curly braces are
+optional in all but the HTML shortcuts. If you like, you can use
+curly braces when calling any routine that takes named arguments. For
+example:
+
+ print $q->header( {-type=>'image/gif',-expires=>'+3d'} );
+
+If you use the B<-w> switch, you will be warned that some CGI.pm argument
+names conflict with built-in Perl functions. The most frequent of
+these is the -values argument, used to create multi-valued menus,
+radio button clusters and the like. To get around this warning, you
+have several choices:
+
+=over 4
+
+=item 1.
+
+Use another name for the argument, if one is available.
+For example, -value is an alias for -values.
+
+=item 2.
+
+Change the capitalization, e.g. -Values
+
+=item 3.
+
+Put quotes around the argument name, e.g. '-values'
+
+=back
+
+Many routines will do something useful with a named argument that it
+doesn't recognize. For example, you can produce non-standard HTTP
+header fields by providing them as named arguments:
+
+ print $q->header(-type => 'text/html',
+ -cost => 'Three smackers',
+ -annoyance_level => 'high',
+ -complaints_to => 'bit bucket');
+
+This will produce the following nonstandard HTTP header:
+
+ HTTP/1.0 200 OK
+ Cost: Three smackers
+ Annoyance-level: high
+ Complaints-to: bit bucket
+ Content-type: text/html
+
+Notice the way that underscores are translated automatically into
+hyphens. HTML-generating routines perform a different type of
+translation.
+
+This feature allows you to keep up with the rapidly changing HTTP and
+HTML "standards".
+
+=head2 CREATING A NEW QUERY OBJECT (OBJECT-ORIENTED STYLE):
+
+ $query = CGI->new;
+
+This will parse the input (from both POST and GET methods) and store
+it into a perl5 object called $query.
+
+Any filehandles from file uploads will have their position reset to
+the beginning of the file.
+
+=head2 CREATING A NEW QUERY OBJECT FROM AN INPUT FILE
+
+ $query = CGI->new(INPUTFILE);
+
+If you provide a file handle to the new() method, it will read
+parameters from the file (or STDIN, or whatever). The file can be in
+any of the forms describing below under debugging (i.e. a series of
+newline delimited TAG=VALUE pairs will work). Conveniently, this type
+of file is created by the save() method (see below). Multiple records
+can be saved and restored.
+
+Perl purists will be pleased to know that this syntax accepts
+references to file handles, or even references to filehandle globs,
+which is the "official" way to pass a filehandle:
+
+ $query = CGI->new(\*STDIN);
+
+You can also initialize the CGI object with a FileHandle or IO::File
+object.
+
+If you are using the function-oriented interface and want to
+initialize CGI state from a file handle, the way to do this is with
+B<restore_parameters()>. This will (re)initialize the
+default CGI object from the indicated file handle.
+
+ open (IN,"test.in") || die;
+ restore_parameters(IN);
+ close IN;
+
+You can also initialize the query object from a hash
+reference:
+
+ $query = CGI->new( {'dinosaur'=>'barney',
+ 'song'=>'I love you',
+ 'friends'=>[qw/Jessica George Nancy/]}
+ );
+
+or from a properly formatted, URL-escaped query string:
+
+ $query = CGI->new('dinosaur=barney&color=purple');
+
+or from a previously existing CGI object (currently this clones the
+parameter list, but none of the other object-specific fields, such as
+autoescaping):
+
+ $old_query = CGI->new;
+ $new_query = CGI->new($old_query);
+
+To create an empty query, initialize it from an empty string or hash:
+
+ $empty_query = CGI->new("");
+
+ -or-
+
+ $empty_query = CGI->new({});
+
+=head2 FETCHING A LIST OF KEYWORDS FROM THE QUERY:
+
+ @keywords = $query->keywords
+
+If the script was invoked as the result of an <ISINDEX> search, the
+parsed keywords can be obtained as an array using the keywords() method.
+
+=head2 FETCHING THE NAMES OF ALL THE PARAMETERS PASSED TO YOUR SCRIPT:
+
+ @names = $query->param
+
+If the script was invoked with a parameter list
+(e.g. "name1=value1&name2=value2&name3=value3"), the param() method
+will return the parameter names as a list. If the script was invoked
+as an <ISINDEX> script and contains a string without ampersands
+(e.g. "value1+value2+value3") , there will be a single parameter named
+"keywords" containing the "+"-delimited keywords.
+
+NOTE: As of version 1.5, the array of parameter names returned will
+be in the same order as they were submitted by the browser.
+Usually this order is the same as the order in which the
+parameters are defined in the form (however, this isn't part
+of the spec, and so isn't guaranteed).
+
+=head2 FETCHING THE VALUE OR VALUES OF A SINGLE NAMED PARAMETER:
+
+ @values = $query->param('foo');
+
+ -or-
+
+ $value = $query->param('foo');
+
+Pass the param() method a single argument to fetch the value of the
+named parameter. If the parameter is multivalued (e.g. from multiple
+selections in a scrolling list), you can ask to receive an array. Otherwise
+the method will return a single value.
+
+If a value is not given in the query string, as in the queries
+"name1=&name2=", it will be returned as an empty string.
+
+
+If the parameter does not exist at all, then param() will return undef
+in a scalar context, and the empty list in a list context.
+
+
+=head2 SETTING THE VALUE(S) OF A NAMED PARAMETER:
+
+ $query->param('foo','an','array','of','values');
+
+This sets the value for the named parameter 'foo' to an array of
+values. This is one way to change the value of a field AFTER
+the script has been invoked once before. (Another way is with
+the -override parameter accepted by all methods that generate
+form elements.)
+
+param() also recognizes a named parameter style of calling described
+in more detail later:
+
+ $query->param(-name=>'foo',-values=>['an','array','of','values']);
+
+ -or-
+
+ $query->param(-name=>'foo',-value=>'the value');
+
+=head2 APPENDING ADDITIONAL VALUES TO A NAMED PARAMETER:
+
+ $query->append(-name=>'foo',-values=>['yet','more','values']);
+
+This adds a value or list of values to the named parameter. The
+values are appended to the end of the parameter if it already exists.
+Otherwise the parameter is created. Note that this method only
+recognizes the named argument calling syntax.
+
+=head2 IMPORTING ALL PARAMETERS INTO A NAMESPACE:
+
+ $query->import_names('R');
+
+This creates a series of variables in the 'R' namespace. For example,
+$R::foo, @R:foo. For keyword lists, a variable @R::keywords will appear.
+If no namespace is given, this method will assume 'Q'.
+WARNING: don't import anything into 'main'; this is a major security
+risk!!!!
+
+NOTE 1: Variable names are transformed as necessary into legal Perl
+variable names. All non-legal characters are transformed into
+underscores. If you need to keep the original names, you should use
+the param() method instead to access CGI variables by name.
+
+NOTE 2: In older versions, this method was called B<import()>. As of version 2.20,
+this name has been removed completely to avoid conflict with the built-in
+Perl module B<import> operator.
+
+=head2 DELETING A PARAMETER COMPLETELY:
+
+ $query->delete('foo','bar','baz');
+
+This completely clears a list of parameters. It sometimes useful for
+resetting parameters that you don't want passed down between script
+invocations.
+
+If you are using the function call interface, use "Delete()" instead
+to avoid conflicts with Perl's built-in delete operator.
+
+=head2 DELETING ALL PARAMETERS:
+
+ $query->delete_all();
+
+This clears the CGI object completely. It might be useful to ensure
+that all the defaults are taken when you create a fill-out form.
+
+Use Delete_all() instead if you are using the function call interface.
+
+=head2 HANDLING NON-URLENCODED ARGUMENTS
+
+
+If POSTed data is not of type application/x-www-form-urlencoded or
+multipart/form-data, then the POSTed data will not be processed, but
+instead be returned as-is in a parameter named POSTDATA. To retrieve
+it, use code like this:
+
+ my $data = $query->param('POSTDATA');
+
+Likewise if PUTed data can be retrieved with code like this:
+
+ my $data = $query->param('PUTDATA');
+
+(If you don't know what the preceding means, don't worry about it. It
+only affects people trying to use CGI for XML processing and other
+specialized tasks.)
+
+
+=head2 DIRECT ACCESS TO THE PARAMETER LIST:
+
+ $q->param_fetch('address')->[1] = '1313 Mockingbird Lane';
+ unshift @{$q->param_fetch(-name=>'address')},'George Munster';
+
+If you need access to the parameter list in a way that isn't covered
+by the methods given in the previous sections, you can obtain a direct
+reference to it by
+calling the B<param_fetch()> method with the name of the parameter. This
+will return an array reference to the named parameter, which you then
+can manipulate in any way you like.
+
+You can also use a named argument style using the B<-name> argument.
+
+=head2 FETCHING THE PARAMETER LIST AS A HASH:
+
+ $params = $q->Vars;
+ print $params->{'address'};
+ @foo = split("\0",$params->{'foo'});
+ %params = $q->Vars;
+
+ use CGI ':cgi-lib';
+ $params = Vars;
+
+Many people want to fetch the entire parameter list as a hash in which
+the keys are the names of the CGI parameters, and the values are the
+parameters' values. The Vars() method does this. Called in a scalar
+context, it returns the parameter list as a tied hash reference.
+Changing a key changes the value of the parameter in the underlying
+CGI parameter list. Called in a list context, it returns the
+parameter list as an ordinary hash. This allows you to read the
+contents of the parameter list, but not to change it.
+
+When using this, the thing you must watch out for are multivalued CGI
+parameters. Because a hash cannot distinguish between scalar and
+list context, multivalued parameters will be returned as a packed
+string, separated by the "\0" (null) character. You must split this
+packed string in order to get at the individual values. This is the
+convention introduced long ago by Steve Brenner in his cgi-lib.pl
+module for Perl version 4.
+
+If you wish to use Vars() as a function, import the I<:cgi-lib> set of
+function calls (also see the section on CGI-LIB compatibility).
+
+=head2 SAVING THE STATE OF THE SCRIPT TO A FILE:
+
+ $query->save(\*FILEHANDLE)
+
+This will write the current state of the form to the provided
+filehandle. You can read it back in by providing a filehandle
+to the new() method. Note that the filehandle can be a file, a pipe,
+or whatever!
+
+The format of the saved file is:
+
+ NAME1=VALUE1
+ NAME1=VALUE1'
+ NAME2=VALUE2
+ NAME3=VALUE3
+ =
+
+Both name and value are URL escaped. Multi-valued CGI parameters are
+represented as repeated names. A session record is delimited by a
+single = symbol. You can write out multiple records and read them
+back in with several calls to B<new>. You can do this across several
+sessions by opening the file in append mode, allowing you to create
+primitive guest books, or to keep a history of users' queries. Here's
+a short example of creating multiple session records:
+
+ use CGI;
+
+ open (OUT,'>>','test.out') || die;
+ $records = 5;
+ for (0..$records) {
+ my $q = CGI->new;
+ $q->param(-name=>'counter',-value=>$_);
+ $q->save(\*OUT);
+ }
+ close OUT;
+
+ # reopen for reading
+ open (IN,'<','test.out') || die;
+ while (!eof(IN)) {
+ my $q = CGI->new(\*IN);
+ print $q->param('counter'),"\n";
+ }
+
+The file format used for save/restore is identical to that used by the
+Whitehead Genome Center's data exchange format "Boulderio", and can be
+manipulated and even databased using Boulderio utilities. See
+
+ http://stein.cshl.org/boulder/
+
+for further details.
+
+If you wish to use this method from the function-oriented (non-OO)
+interface, the exported name for this method is B<save_parameters()>.
+
+=head2 RETRIEVING CGI ERRORS
+
+Errors can occur while processing user input, particularly when
+processing uploaded files. When these errors occur, CGI will stop
+processing and return an empty parameter list. You can test for
+the existence and nature of errors using the I<cgi_error()> function.
+The error messages are formatted as HTTP status codes. You can either
+incorporate the error text into an HTML page, or use it as the value
+of the HTTP status:
+
+ my $error = $q->cgi_error;
+ if ($error) {
+ print $q->header(-status=>$error),
+ $q->start_html('Problems'),
+ $q->h2('Request not processed'),
+ $q->strong($error);
+ exit 0;
+ }
+
+When using the function-oriented interface (see the next section),
+errors may only occur the first time you call I<param()>. Be ready
+for this!
+
+=head2 USING THE FUNCTION-ORIENTED INTERFACE
+
+To use the function-oriented interface, you must specify which CGI.pm
+routines or sets of routines to import into your script's namespace.
+There is a small overhead associated with this importation, but it
+isn't much.
+
+ use CGI <list of methods>;
+
+The listed methods will be imported into the current package; you can
+call them directly without creating a CGI object first. This example
+shows how to import the B<param()> and B<header()>
+methods, and then use them directly:
+
+ use CGI 'param','header';
+ print header('text/plain');
+ $zipcode = param('zipcode');
+
+More frequently, you'll import common sets of functions by referring
+to the groups by name. All function sets are preceded with a ":"
+character as in ":html3" (for tags defined in the HTML 3 standard).
+
+Here is a list of the function sets you can import:
+
+=over 4
+
+=item B<:cgi>
+
+Import all CGI-handling methods, such as B<param()>, B<path_info()>
+and the like.
+
+=item B<:form>
+
+Import all fill-out form generating methods, such as B<textfield()>.
+
+=item B<:html2>
+
+Import all methods that generate HTML 2.0 standard elements.
+
+=item B<:html3>
+
+Import all methods that generate HTML 3.0 elements (such as
+<table>, <super> and <sub>).
+
+=item B<:html4>
+
+Import all methods that generate HTML 4 elements (such as
+<abbrev>, <acronym> and <thead>).
+
+=item B<:netscape>
+
+Import the <blink>, <fontsize> and <center> tags.
+
+=item B<:html>
+
+Import all HTML-generating shortcuts (i.e. 'html2', 'html3', 'html4' and 'netscape')
+
+=item B<:standard>
+
+Import "standard" features, 'html2', 'html3', 'html4', 'form' and 'cgi'.
+
+=item B<:all>
+
+Import all the available methods. For the full list, see the CGI.pm
+code, where the variable %EXPORT_TAGS is defined.
+
+=back
+
+If you import a function name that is not part of CGI.pm, the module
+will treat it as a new HTML tag and generate the appropriate
+subroutine. You can then use it like any other HTML tag. This is to
+provide for the rapidly-evolving HTML "standard." For example, say
+Microsoft comes out with a new tag called <gradient> (which causes the
+user's desktop to be flooded with a rotating gradient fill until his
+machine reboots). You don't need to wait for a new version of CGI.pm
+to start using it immediately:
+
+ use CGI qw/:standard :html3 gradient/;
+ print gradient({-start=>'red',-end=>'blue'});
+
+Note that in the interests of execution speed CGI.pm does B<not> use
+the standard L<Exporter> syntax for specifying load symbols. This may
+change in the future.
+
+If you import any of the state-maintaining CGI or form-generating
+methods, a default CGI object will be created and initialized
+automatically the first time you use any of the methods that require
+one to be present. This includes B<param()>, B<textfield()>,
+B<submit()> and the like. (If you need direct access to the CGI
+object, you can find it in the global variable B<$CGI::Q>). By
+importing CGI.pm methods, you can create visually elegant scripts:
+
+ use CGI qw/:standard/;
+ print
+ header,
+ start_html('Simple Script'),
+ h1('Simple Script'),
+ start_form,
+ "What's your name? ",textfield('name'),p,
+ "What's the combination?",
+ checkbox_group(-name=>'words',
+ -values=>['eenie','meenie','minie','moe'],
+ -defaults=>['eenie','moe']),p,
+ "What's your favorite color?",
+ popup_menu(-name=>'color',
+ -values=>['red','green','blue','chartreuse']),p,
+ submit,
+ end_form,
+ hr,"\n";
+
+ if (param) {
+ print
+ "Your name is ",em(param('name')),p,
+ "The keywords are: ",em(join(", ",param('words'))),p,
+ "Your favorite color is ",em(param('color')),".\n";
+ }
+ print end_html;
+
+=head2 PRAGMAS
+
+In addition to the function sets, there are a number of pragmas that
+you can import. Pragmas, which are always preceded by a hyphen,
+change the way that CGI.pm functions in various ways. Pragmas,
+function sets, and individual functions can all be imported in the
+same use() line. For example, the following use statement imports the
+standard set of functions and enables debugging mode (pragma
+-debug):
+
+ use CGI qw/:standard -debug/;
+
+The current list of pragmas is as follows:
+
+=over 4
+
+=item -any
+
+When you I<use CGI -any>, then any method that the query object
+doesn't recognize will be interpreted as a new HTML tag. This allows
+you to support the next I<ad hoc> HTML
+extension. This lets you go wild with new and unsupported tags:
+
+ use CGI qw(-any);
+ $q=CGI->new;
+ print $q->gradient({speed=>'fast',start=>'red',end=>'blue'});
+
+Since using <cite>any</cite> causes any mistyped method name
+to be interpreted as an HTML tag, use it with care or not at
+all.
+
+=item -compile
+
+This causes the indicated autoloaded methods to be compiled up front,
+rather than deferred to later. This is useful for scripts that run
+for an extended period of time under FastCGI or mod_perl, and for
+those destined to be crunched by Malcolm Beattie's Perl compiler. Use
+it in conjunction with the methods or method families you plan to use.
+
+ use CGI qw(-compile :standard :html3);
+
+or even
+
+ use CGI qw(-compile :all);
+
+Note that using the -compile pragma in this way will always have
+the effect of importing the compiled functions into the current
+namespace. If you want to compile without importing use the
+compile() method instead:
+
+ use CGI();
+ CGI->compile();
+
+This is particularly useful in a mod_perl environment, in which you
+might want to precompile all CGI routines in a startup script, and
+then import the functions individually in each mod_perl script.
+
+=item -nosticky
+
+By default the CGI module implements a state-preserving behavior
+called "sticky" fields. The way this works is that if you are
+regenerating a form, the methods that generate the form field values
+will interrogate param() to see if similarly-named parameters are
+present in the query string. If they find a like-named parameter, they
+will use it to set their default values.
+
+Sometimes this isn't what you want. The B<-nosticky> pragma prevents
+this behavior. You can also selectively change the sticky behavior in
+each element that you generate.
+
+=item -tabindex
+
+Automatically add tab index attributes to each form field. With this
+option turned off, you can still add tab indexes manually by passing a
+-tabindex option to each field-generating method.
+
+=item -no_undef_params
+
+This keeps CGI.pm from including undef params in the parameter list.
+
+=item -no_xhtml
+
+By default, CGI.pm versions 2.69 and higher emit XHTML
+(http://www.w3.org/TR/xhtml1/). The -no_xhtml pragma disables this
+feature. Thanks to Michalis Kabrianis <kabrianis@hellug.gr> for this
+feature.
+
+If start_html()'s -dtd parameter specifies an HTML 2.0,
+3.2, 4.0 or 4.01 DTD,
+XHTML will automatically be disabled without needing to use this
+pragma.
+
+=item -utf8
+
+This makes CGI.pm treat all parameters as UTF-8 strings. Use this with
+care, as it will interfere with the processing of binary uploads. It
+is better to manually select which fields are expected to return utf-8
+strings and convert them using code like this:
+
+ use Encode;
+ my $arg = decode utf8=>param('foo');
+
+=item -nph
+
+This makes CGI.pm produce a header appropriate for an NPH (no
+parsed header) script. You may need to do other things as well
+to tell the server that the script is NPH. See the discussion
+of NPH scripts below.
+
+=item -newstyle_urls
+
+Separate the name=value pairs in CGI parameter query strings with
+semicolons rather than ampersands. For example:
+
+ ?name=fred;age=24;favorite_color=3
+
+Semicolon-delimited query strings are always accepted, and will be emitted by
+self_url() and query_string(). newstyle_urls became the default in version
+2.64.
+
+=item -oldstyle_urls
+
+Separate the name=value pairs in CGI parameter query strings with
+ampersands rather than semicolons. This is no longer the default.
+
+=item -autoload
+
+This overrides the autoloader so that any function in your program
+that is not recognized is referred to CGI.pm for possible evaluation.
+This allows you to use all the CGI.pm functions without adding them to
+your symbol table, which is of concern for mod_perl users who are
+worried about memory consumption. I<Warning:> when
+I<-autoload> is in effect, you cannot use "poetry mode"
+(functions without the parenthesis). Use I<hr()> rather
+than I<hr>, or add something like I<use subs qw/hr p header/>
+to the top of your script.
+
+=item -no_debug
+
+This turns off the command-line processing features. If you want to
+run a CGI.pm script from the command line to produce HTML, and you
+don't want it to read CGI parameters from the command line or STDIN,
+then use this pragma:
+
+ use CGI qw(-no_debug :standard);
+
+=item -debug
+
+This turns on full debugging. In addition to reading CGI arguments
+from the command-line processing, CGI.pm will pause and try to read
+arguments from STDIN, producing the message "(offline mode: enter
+name=value pairs on standard input)" features.
+
+See the section on debugging for more details.
+
+=item -private_tempfiles
+
+CGI.pm can process uploaded file. Ordinarily it spools the uploaded
+file to a temporary directory, then deletes the file when done.
+However, this opens the risk of eavesdropping as described in the file
+upload section. Another CGI script author could peek at this data
+during the upload, even if it is confidential information. On Unix
+systems, the -private_tempfiles pragma will cause the temporary file
+to be unlinked as soon as it is opened and before any data is written
+into it, reducing, but not eliminating the risk of eavesdropping
+(there is still a potential race condition). To make life harder for
+the attacker, the program chooses tempfile names by calculating a 32
+bit checksum of the incoming HTTP headers.
+
+To ensure that the temporary file cannot be read by other CGI scripts,
+use suEXEC or a CGI wrapper program to run your script. The temporary
+file is created with mode 0600 (neither world nor group readable).
+
+The temporary directory is selected using the following algorithm:
+
+ 1. if $CGITempFile::TMPDIRECTORY is already set, use that
+
+ 2. if the environment variable TMPDIR exists, use the location
+ indicated.
+
+ 3. Otherwise try the locations /usr/tmp, /var/tmp, C:\temp,
+ /tmp, /temp, ::Temporary Items, and \WWW_ROOT.
+
+Each of these locations is checked that it is a directory and is
+writable. If not, the algorithm tries the next choice.
+
+=back
+
+=head2 SPECIAL FORMS FOR IMPORTING HTML-TAG FUNCTIONS
+
+Many of the methods generate HTML tags. As described below, tag
+functions automatically generate both the opening and closing tags.
+For example:
+
+ print h1('Level 1 Header');
+
+produces
+
+ <h1>Level 1 Header</h1>
+
+There will be some times when you want to produce the start and end
+tags yourself. In this case, you can use the form start_I<tag_name>
+and end_I<tag_name>, as in:
+
+ print start_h1,'Level 1 Header',end_h1;
+
+With a few exceptions (described below), start_I<tag_name> and
+end_I<tag_name> functions are not generated automatically when you
+I<use CGI>. However, you can specify the tags you want to generate
+I<start/end> functions for by putting an asterisk in front of their
+name, or, alternatively, requesting either "start_I<tag_name>" or
+"end_I<tag_name>" in the import list.
+
+Example:
+
+ use CGI qw/:standard *table start_ul/;
+
+In this example, the following functions are generated in addition to
+the standard ones:
+
+=over 4
+
+=item 1. start_table() (generates a <table> tag)
+
+=item 2. end_table() (generates a </table> tag)
+
+=item 3. start_ul() (generates a <ul> tag)
+
+=item 4. end_ul() (generates a </ul> tag)
+
+=back
+
+=head1 GENERATING DYNAMIC DOCUMENTS
+
+Most of CGI.pm's functions deal with creating documents on the fly.
+Generally you will produce the HTTP header first, followed by the
+document itself. CGI.pm provides functions for generating HTTP
+headers of various types as well as for generating HTML. For creating
+GIF images, see the GD.pm module.
+
+Each of these functions produces a fragment of HTML or HTTP which you
+can print out directly so that it displays in the browser window,
+append to a string, or save to a file for later use.
+
+=head2 CREATING A STANDARD HTTP HEADER:
+
+Normally the first thing you will do in any CGI script is print out an
+HTTP header. This tells the browser what type of document to expect,
+and gives other optional information, such as the language, expiration
+date, and whether to cache the document. The header can also be
+manipulated for special purposes, such as server push and pay per view
+pages.
+
+ print header;
+
+ -or-
+
+ print header('image/gif');
+
+ -or-
+
+ print header('text/html','204 No response');
+
+ -or-
+
+ print header(-type=>'image/gif',
+ -nph=>1,
+ -status=>'402 Payment required',
+ -expires=>'+3d',
+ -cookie=>$cookie,
+ -charset=>'utf-7',
+ -attachment=>'foo.gif',
+ -Cost=>'$2.00');
+
+header() returns the Content-type: header. You can provide your own
+MIME type if you choose, otherwise it defaults to text/html. An
+optional second parameter specifies the status code and a human-readable
+message. For example, you can specify 204, "No response" to create a
+script that tells the browser to do nothing at all.
+
+The last example shows the named argument style for passing arguments
+to the CGI methods using named parameters. Recognized parameters are
+B<-type>, B<-status>, B<-expires>, and B<-cookie>. Any other named
+parameters will be stripped of their initial hyphens and turned into
+header fields, allowing you to specify any HTTP header you desire.
+Internal underscores will be turned into hyphens:
+
+ print header(-Content_length=>3002);
+
+Most browsers will not cache the output from CGI scripts. Every time
+the browser reloads the page, the script is invoked anew. You can
+change this behavior with the B<-expires> parameter. When you specify
+an absolute or relative expiration interval with this parameter, some
+browsers and proxy servers will cache the script's output until the
+indicated expiration date. The following forms are all valid for the
+-expires field:
+
+ +30s 30 seconds from now
+ +10m ten minutes from now
+ +1h one hour from now
+ -1d yesterday (i.e. "ASAP!")
+ now immediately
+ +3M in three months
+ +10y in ten years time
+ Thursday, 25-Apr-1999 00:40:33 GMT at the indicated time & date
+
+The B<-cookie> parameter generates a header that tells the browser to provide
+a "magic cookie" during all subsequent transactions with your script.
+Some cookies have a special format that includes interesting attributes
+such as expiration time. Use the cookie() method to create and retrieve
+session cookies.
+
+The B<-nph> parameter, if set to a true value, will issue the correct
+headers to work with a NPH (no-parse-header) script. This is important
+to use with certain servers that expect all their scripts to be NPH.
+
+The B<-charset> parameter can be used to control the character set
+sent to the browser. If not provided, defaults to ISO-8859-1. As a
+side effect, this sets the charset() method as well.
+
+The B<-attachment> parameter can be used to turn the page into an
+attachment. Instead of displaying the page, some browsers will prompt
+the user to save it to disk. The value of the argument is the
+suggested name for the saved file. In order for this to work, you may
+have to set the B<-type> to "application/octet-stream".
+
+The B<-p3p> parameter will add a P3P tag to the outgoing header. The
+parameter can be an arrayref or a space-delimited string of P3P tags.
+For example:
+
+ print header(-p3p=>[qw(CAO DSP LAW CURa)]);
+ print header(-p3p=>'CAO DSP LAW CURa');
+
+In either case, the outgoing header will be formatted as:
+
+ P3P: policyref="/w3c/p3p.xml" cp="CAO DSP LAW CURa"
+
+CGI.pm will accept valid multi-line headers when each line is separated with a
+CRLF value ("\r\n" on most platforms) followed by at least one space. For example:
+
+ print header( -ingredients => "ham\r\n\seggs\r\n\sbacon" );
+
+Invalid multi-line header input will trigger in an exception. When multi-line headers
+are received, CGI.pm will always output them back as a single line, according to the
+folding rules of RFC 2616: the newlines will be removed, while the white space remains.
+
+=head2 GENERATING A REDIRECTION HEADER
+
+ print $q->redirect('http://somewhere.else/in/movie/land');
+
+Sometimes you don't want to produce a document yourself, but simply
+redirect the browser elsewhere, perhaps choosing a URL based on the
+time of day or the identity of the user.
+
+The redirect() method redirects the browser to a different URL. If
+you use redirection like this, you should B<not> print out a header as
+well.
+
+You should always use full URLs (including the http: or ftp: part) in
+redirection requests. Relative URLs will not work correctly.
+
+You can also use named arguments:
+
+ print $q->redirect(
+ -uri=>'http://somewhere.else/in/movie/land',
+ -nph=>1,
+ -status=>301);
+
+All names arguments recognized by header() are also recognized by
+redirect(). However, most HTTP headers, including those generated by
+-cookie and -target, are ignored by the browser.
+
+The B<-nph> parameter, if set to a true value, will issue the correct
+headers to work with a NPH (no-parse-header) script. This is important
+to use with certain servers, such as Microsoft IIS, which
+expect all their scripts to be NPH.
+
+The B<-status> parameter will set the status of the redirect. HTTP
+defines three different possible redirection status codes:
+
+ 301 Moved Permanently
+ 302 Found
+ 303 See Other
+
+The default if not specified is 302, which means "moved temporarily."
+You may change the status to another status code if you wish. Be
+advised that changing the status to anything other than 301, 302 or
+303 will probably break redirection.
+
+=head2 CREATING THE HTML DOCUMENT HEADER
+
+ print start_html(-title=>'Secrets of the Pyramids',
+ -author=>'fred@capricorn.org',
+ -base=>'true',
+ -target=>'_blank',
+ -meta=>{'keywords'=>'pharaoh secret mummy',
+ 'copyright'=>'copyright 1996 King Tut'},
+ -style=>{'src'=>'/styles/style1.css'},
+ -BGCOLOR=>'blue');
+
+The start_html() routine creates the top of the
+page, along with a lot of optional information that controls the
+page's appearance and behavior.
+
+This method returns a canned HTML header and the opening <body> tag.
+All parameters are optional. In the named parameter form, recognized
+parameters are -title, -author, -base, -xbase, -dtd, -lang and -target
+(see below for the explanation). Any additional parameters you
+provide, such as the unofficial BGCOLOR attribute, are added
+to the <body> tag. Additional parameters must be proceeded by a
+hyphen.
+
+The argument B<-xbase> allows you to provide an HREF for the <base> tag
+different from the current location, as in
+
+ -xbase=>"http://home.mcom.com/"
+
+All relative links will be interpreted relative to this tag.
+
+The argument B<-target> allows you to provide a default target frame
+for all the links and fill-out forms on the page. B<This is a
+non-standard HTTP feature which only works with some browsers!>
+
+ -target=>"answer_window"
+
+All relative links will be interpreted relative to this tag.
+You add arbitrary meta information to the header with the B<-meta>
+argument. This argument expects a reference to a hash
+containing name/value pairs of meta information. These will be turned
+into a series of header <meta> tags that look something like this:
+
+ <meta name="keywords" content="pharaoh secret mummy">
+ <meta name="description" content="copyright 1996 King Tut">
+
+To create an HTTP-EQUIV type of <meta> tag, use B<-head>, described
+below.
+
+The B<-style> argument is used to incorporate cascading stylesheets
+into your code. See the section on CASCADING STYLESHEETS for more
+information.
+
+The B<-lang> argument is used to incorporate a language attribute into
+the <html> tag. For example:
+
+ print $q->start_html(-lang=>'fr-CA');
+
+The default if not specified is "en-US" for US English, unless the
+-dtd parameter specifies an HTML 2.0 or 3.2 DTD, in which case the
+lang attribute is left off. You can force the lang attribute to left
+off in other cases by passing an empty string (-lang=>'').
+
+The B<-encoding> argument can be used to specify the character set for
+XHTML. It defaults to iso-8859-1 if not specified.
+
+The B<-dtd> argument can be used to specify a public DTD identifier string. For example:
+
+ -dtd => '-//W3C//DTD HTML 4.01 Transitional//EN')
+
+Alternatively, it can take public and system DTD identifiers as an array:
+
+ dtd => [ '-//W3C//DTD HTML 4.01 Transitional//EN', 'http://www.w3.org/TR/html4/loose.dtd' ]
+
+For the public DTD identifier to be considered, it must be valid. Otherwise it
+will be replaced by the default DTD. If the public DTD contains 'XHTML', CGI.pm
+will emit XML.
+
+The B<-declare_xml> argument, when used in conjunction with XHTML,
+will put a <?xml> declaration at the top of the HTML header. The sole
+purpose of this declaration is to declare the character set
+encoding. In the absence of -declare_xml, the output HTML will contain
+a <meta> tag that specifies the encoding, allowing the HTML to pass
+most validators. The default for -declare_xml is false.
+
+You can place other arbitrary HTML elements to the <head> section with the
+B<-head> tag. For example, to place a <link> element in the
+head section, use this:
+
+ print start_html(-head=>Link({-rel=>'shortcut icon',
+ -href=>'favicon.ico'}));
+
+To incorporate multiple HTML elements into the <head> section, just pass an
+array reference:
+
+ print start_html(-head=>[
+ Link({-rel=>'next',
+ -href=>'http://www.capricorn.com/s2.html'}),
+ Link({-rel=>'previous',
+ -href=>'http://www.capricorn.com/s1.html'})
+ ]
+ );
+
+And here's how to create an HTTP-EQUIV <meta> tag:
+
+ print start_html(-head=>meta({-http_equiv => 'Content-Type',
+ -content => 'text/html'}))
+
+
+JAVASCRIPTING: The B<-script>, B<-noScript>, B<-onLoad>,
+B<-onMouseOver>, B<-onMouseOut> and B<-onUnload> parameters are used
+to add JavaScript calls to your pages. B<-script> should
+point to a block of text containing JavaScript function definitions.
+This block will be placed within a <script> block inside the HTML (not
+HTTP) header. The block is placed in the header in order to give your
+page a fighting chance of having all its JavaScript functions in place
+even if the user presses the stop button before the page has loaded
+completely. CGI.pm attempts to format the script in such a way that
+JavaScript-naive browsers will not choke on the code: unfortunately
+there are some browsers, such as Chimera for Unix, that get confused
+by it nevertheless.
+
+The B<-onLoad> and B<-onUnload> parameters point to fragments of JavaScript
+code to execute when the page is respectively opened and closed by the
+browser. Usually these parameters are calls to functions defined in the
+B<-script> field:
+
+ $query = CGI->new;
+ print header;
+ $JSCRIPT=<<END;
+ // Ask a silly question
+ function riddle_me_this() {
+ var r = prompt("What walks on four legs in the morning, " +
+ "two legs in the afternoon, " +
+ "and three legs in the evening?");
+ response(r);
+ }
+ // Get a silly answer
+ function response(answer) {
+ if (answer == "man")
+ alert("Right you are!");
+ else
+ alert("Wrong! Guess again.");
+ }
+ END
+ print start_html(-title=>'The Riddle of the Sphinx',
+ -script=>$JSCRIPT);
+
+Use the B<-noScript> parameter to pass some HTML text that will be displayed on
+browsers that do not have JavaScript (or browsers where JavaScript is turned
+off).
+
+The <script> tag, has several attributes including "type", "charset" and "src".
+"src" allows you to keep JavaScript code in an external file. To use these
+attributes pass a HASH reference in the B<-script> parameter containing one or
+more of -type, -src, or -code:
+
+ print $q->start_html(-title=>'The Riddle of the Sphinx',
+ -script=>{-type=>'JAVASCRIPT',
+ -src=>'/javascript/sphinx.js'}
+ );
+
+ print $q->(-title=>'The Riddle of the Sphinx',
+ -script=>{-type=>'PERLSCRIPT',
+ -code=>'print "hello world!\n;"'}
+ );
+
+
+A final feature allows you to incorporate multiple <script> sections into the
+header. Just pass the list of script sections as an array reference.
+this allows you to specify different source files for different dialects
+of JavaScript. Example:
+
+ print $q->start_html(-title=>'The Riddle of the Sphinx',
+ -script=>[
+ { -type => 'text/javascript',
+ -src => '/javascript/utilities10.js'
+ },
+ { -type => 'text/javascript',
+ -src => '/javascript/utilities11.js'
+ },
+ { -type => 'text/jscript',
+ -src => '/javascript/utilities12.js'
+ },
+ { -type => 'text/ecmascript',
+ -src => '/javascript/utilities219.js'
+ }
+ ]
+ );
+
+The option "-language" is a synonym for -type, and is supported for
+backwards compatibility.
+
+The old-style positional parameters are as follows:
+
+=over 4
+
+=item B<Parameters:>
+
+=item 1.
+
+The title
+
+=item 2.
+
+The author's e-mail address (will create a <link rev="MADE"> tag if present
+
+=item 3.
+
+A 'true' flag if you want to include a <base> tag in the header. This
+helps resolve relative addresses to absolute ones when the document is moved,
+but makes the document hierarchy non-portable. Use with care!
+
+=item 4, 5, 6...
+
+Any other parameters you want to include in the <body> tag. This is a good
+place to put HTML extensions, such as colors and wallpaper patterns.
+
+=back
+
+=head2 ENDING THE HTML DOCUMENT:
+
+ print end_html
+
+This ends an HTML document by printing the </body></html> tags.
+
+=head2 CREATING A SELF-REFERENCING URL THAT PRESERVES STATE INFORMATION:
+
+ $myself = self_url;
+ print q(<a href="$myself">I'm talking to myself.</a>);
+
+self_url() will return a URL, that, when selected, will reinvoke
+this script with all its state information intact. This is most
+useful when you want to jump around within the document using
+internal anchors but you don't want to disrupt the current contents
+of the form(s). Something like this will do the trick.
+
+ $myself = self_url;
+ print "<a href=\"$myself#table1\">See table 1</a>";
+ print "<a href=\"$myself#table2\">See table 2</a>";
+ print "<a href=\"$myself#yourself\">See for yourself</a>";
+
+If you want more control over what's returned, using the B<url()>
+method instead.
+
+You can also retrieve the unprocessed query string with query_string():
+
+ $the_string = query_string;
+
+=head2 OBTAINING THE SCRIPT'S URL
+
+ $full_url = url();
+ $full_url = url(-full=>1); #alternative syntax
+ $relative_url = url(-relative=>1);
+ $absolute_url = url(-absolute=>1);
+ $url_with_path = url(-path_info=>1);
+ $url_with_path_and_query = url(-path_info=>1,-query=>1);
+ $netloc = url(-base => 1);
+
+B<url()> returns the script's URL in a variety of formats. Called
+without any arguments, it returns the full form of the URL, including
+host name and port number
+
+ http://your.host.com/path/to/script.cgi
+
+You can modify this format with the following named arguments:
+
+=over 4
+
+=item B<-absolute>
+
+If true, produce an absolute URL, e.g.
+
+ /path/to/script.cgi
+
+=item B<-relative>
+
+Produce a relative URL. This is useful if you want to reinvoke your
+script with different parameters. For example:
+
+ script.cgi
+
+=item B<-full>
+
+Produce the full URL, exactly as if called without any arguments.
+This overrides the -relative and -absolute arguments.
+
+=item B<-path> (B<-path_info>)
+
+Append the additional path information to the URL. This can be
+combined with B<-full>, B<-absolute> or B<-relative>. B<-path_info>
+is provided as a synonym.
+
+=item B<-query> (B<-query_string>)
+
+Append the query string to the URL. This can be combined with
+B<-full>, B<-absolute> or B<-relative>. B<-query_string> is provided
+as a synonym.
+
+=item B<-base>
+
+Generate just the protocol and net location, as in http://www.foo.com:8000
+
+=item B<-rewrite>
+
+If Apache's mod_rewrite is turned on, then the script name and path
+info probably won't match the request that the user sent. Set
+-rewrite=>1 (default) to return URLs that match what the user sent
+(the original request URI). Set -rewrite=>0 to return URLs that match
+the URL after mod_rewrite's rules have run. Because the additional
+path information only makes sense in the context of the rewritten URL,
+-rewrite is set to false when you request path info in the URL.
+
+=back
+
+=head2 MIXING POST AND URL PARAMETERS
+
+ $color = url_param('color');
+
+It is possible for a script to receive CGI parameters in the URL as
+well as in the fill-out form by creating a form that POSTs to a URL
+containing a query string (a "?" mark followed by arguments). The
+B<param()> method will always return the contents of the POSTed
+fill-out form, ignoring the URL's query string. To retrieve URL
+parameters, call the B<url_param()> method. Use it in the same way as
+B<param()>. The main difference is that it allows you to read the
+parameters, but not set them.
+
+
+Under no circumstances will the contents of the URL query string
+interfere with similarly-named CGI parameters in POSTed forms. If you
+try to mix a URL query string with a form submitted with the GET
+method, the results will not be what you expect.
+
+=head1 CREATING STANDARD HTML ELEMENTS:
+
+CGI.pm defines general HTML shortcut methods for many HTML tags. HTML shortcuts are named after a single
+HTML element and return a fragment of HTML text. Example:
+
+ print $q->blockquote(
+ "Many years ago on the island of",
+ $q->a({href=>"http://crete.org/"},"Crete"),
+ "there lived a Minotaur named",
+ $q->strong("Fred."),
+ ),
+ $q->hr;
+
+This results in the following HTML code (extra newlines have been
+added for readability):
+
+ <blockquote>
+ Many years ago on the island of
+ <a href="http://crete.org/">Crete</a> there lived
+ a minotaur named <strong>Fred.</strong>
+ </blockquote>
+ <hr>
+
+If you find the syntax for calling the HTML shortcuts awkward, you can
+import them into your namespace and dispense with the object syntax
+completely (see the next section for more details):
+
+ use CGI ':standard';
+ print blockquote(
+ "Many years ago on the island of",
+ a({href=>"http://crete.org/"},"Crete"),
+ "there lived a minotaur named",
+ strong("Fred."),
+ ),
+ hr;
+
+=head2 PROVIDING ARGUMENTS TO HTML SHORTCUTS
+
+The HTML methods will accept zero, one or multiple arguments. If you
+provide no arguments, you get a single tag:
+
+ print hr; # <hr>
+
+If you provide one or more string arguments, they are concatenated
+together with spaces and placed between opening and closing tags:
+
+ print h1("Chapter","1"); # <h1>Chapter 1</h1>"
+
+If the first argument is a hash reference, then the keys
+and values of the hash become the HTML tag's attributes:
+
+ print a({-href=>'fred.html',-target=>'_new'},
+ "Open a new frame");
+
+ <a href="fred.html",target="_new">Open a new frame</a>
+
+You may dispense with the dashes in front of the attribute names if
+you prefer:
+
+ print img {src=>'fred.gif',align=>'LEFT'};
+
+ <img align="LEFT" src="fred.gif">
+
+Sometimes an HTML tag attribute has no argument. For example, ordered
+lists can be marked as COMPACT. The syntax for this is an argument that
+that points to an undef string:
+
+ print ol({compact=>undef},li('one'),li('two'),li('three'));
+
+Prior to CGI.pm version 2.41, providing an empty ('') string as an
+attribute argument was the same as providing undef. However, this has
+changed in order to accommodate those who want to create tags of the form
+<img alt="">. The difference is shown in these two pieces of code:
+
+ CODE RESULT
+ img({alt=>undef}) <img alt>
+ img({alt=>''}) <img alt="">
+
+=head2 THE DISTRIBUTIVE PROPERTY OF HTML SHORTCUTS
+
+One of the cool features of the HTML shortcuts is that they are
+distributive. If you give them an argument consisting of a
+B<reference> to a list, the tag will be distributed across each
+element of the list. For example, here's one way to make an ordered
+list:
+
+ print ul(
+ li({-type=>'disc'},['Sneezy','Doc','Sleepy','Happy'])
+ );
+
+This example will result in HTML output that looks like this:
+
+ <ul>
+ <li type="disc">Sneezy</li>
+ <li type="disc">Doc</li>
+ <li type="disc">Sleepy</li>
+ <li type="disc">Happy</li>
+ </ul>
+
+This is extremely useful for creating tables. For example:
+
+ print table({-border=>undef},
+ caption('When Should You Eat Your Vegetables?'),
+ Tr({-align=>'CENTER',-valign=>'TOP'},
+ [
+ th(['Vegetable', 'Breakfast','Lunch','Dinner']),
+ td(['Tomatoes' , 'no', 'yes', 'yes']),
+ td(['Broccoli' , 'no', 'no', 'yes']),
+ td(['Onions' , 'yes','yes', 'yes'])
+ ]
+ )
+ );
+
+=head2 HTML SHORTCUTS AND LIST INTERPOLATION
+
+Consider this bit of code:
+
+ print blockquote(em('Hi'),'mom!'));
+
+It will ordinarily return the string that you probably expect, namely:
+
+ <blockquote><em>Hi</em> mom!</blockquote>
+
+Note the space between the element "Hi" and the element "mom!".
+CGI.pm puts the extra space there using array interpolation, which is
+controlled by the magic $" variable. Sometimes this extra space is
+not what you want, for example, when you are trying to align a series
+of images. In this case, you can simply change the value of $" to an
+empty string.
+
+ {
+ local($") = '';
+ print blockquote(em('Hi'),'mom!'));
+ }
+
+I suggest you put the code in a block as shown here. Otherwise the
+change to $" will affect all subsequent code until you explicitly
+reset it.
+
+=head2 NON-STANDARD HTML SHORTCUTS
+
+A few HTML tags don't follow the standard pattern for various
+reasons.
+
+B<comment()> generates an HTML comment (<!-- comment -->). Call it
+like
+
+ print comment('here is my comment');
+
+Because of conflicts with built-in Perl functions, the following functions
+begin with initial caps:
+
+ Select
+ Tr
+ Link
+ Delete
+ Accept
+ Sub
+
+In addition, start_html(), end_html(), start_form(), end_form(),
+start_multipart_form() and all the fill-out form tags are special.
+See their respective sections.
+
+=head2 AUTOESCAPING HTML
+
+By default, all HTML that is emitted by the form-generating functions
+is passed through a function called escapeHTML():
+
+=over 4
+
+=item $escaped_string = escapeHTML("unescaped string");
+
+Escape HTML formatting characters in a string.
+
+=back
+
+Provided that you have specified a character set of ISO-8859-1 (the
+default), the standard HTML escaping rules will be used. The "<"
+character becomes "&lt;", ">" becomes "&gt;", "&" becomes "&amp;", and
+the quote character becomes "&quot;". In addition, the hexadecimal
+0x8b and 0x9b characters, which some browsers incorrectly interpret
+as the left and right angle-bracket characters, are replaced by their
+numeric character entities ("&#8249" and "&#8250;"). If you manually change
+the charset, either by calling the charset() method explicitly or by
+passing a -charset argument to header(), then B<all> characters will
+be replaced by their numeric entities, since CGI.pm has no lookup
+table for all the possible encodings.
+
+C<escapeHTML()> expects the supplied string to be a character string. This means you
+should Encode::decode data received from "outside" and Encode::encode your
+strings before sending them back outside. If your source code UTF-8 encoded and
+you want to upgrade string literals in your source to character strings, you
+can use "use utf8". See L<perlunitut>, L<perlunifaq> and L<perlunicode> for more
+information on how Perl handles the difference between bytes and characters.
+
+The automatic escaping does not apply to other shortcuts, such as
+h1(). You should call escapeHTML() yourself on untrusted data in
+order to protect your pages against nasty tricks that people may enter
+into guestbooks, etc.. To change the character set, use charset().
+To turn autoescaping off completely, use autoEscape(0):
+
+=over 4
+
+=item $charset = charset([$charset]);
+
+Get or set the current character set.
+
+=item $flag = autoEscape([$flag]);
+
+Get or set the value of the autoescape flag.
+
+=back
+
+=head2 PRETTY-PRINTING HTML
+
+By default, all the HTML produced by these functions comes out as one
+long line without carriage returns or indentation. This is yuck, but
+it does reduce the size of the documents by 10-20%. To get
+pretty-printed output, please use L<CGI::Pretty>, a subclass
+contributed by Brian Paulsen.
+
+=head1 CREATING FILL-OUT FORMS:
+
+I<General note> The various form-creating methods all return strings
+to the caller, containing the tag or tags that will create the requested
+form element. You are responsible for actually printing out these strings.
+It's set up this way so that you can place formatting tags
+around the form elements.
+
+I<Another note> The default values that you specify for the forms are only
+used the B<first> time the script is invoked (when there is no query
+string). On subsequent invocations of the script (when there is a query
+string), the former values are used even if they are blank.
+
+If you want to change the value of a field from its previous value, you have two
+choices:
+
+(1) call the param() method to set it.
+
+(2) use the -override (alias -force) parameter (a new feature in version 2.15).
+This forces the default value to be used, regardless of the previous value:
+
+ print textfield(-name=>'field_name',
+ -default=>'starting value',
+ -override=>1,
+ -size=>50,
+ -maxlength=>80);
+
+I<Yet another note> By default, the text and labels of form elements are
+escaped according to HTML rules. This means that you can safely use
+"<CLICK ME>" as the label for a button. However, it also interferes with
+your ability to incorporate special HTML character sequences, such as &Aacute;,
+into your fields. If you wish to turn off automatic escaping, call the
+autoEscape() method with a false value immediately after creating the CGI object:
+
+ $query = CGI->new;
+ $query->autoEscape(0);
+
+Note that autoEscape() is exclusively used to effect the behavior of how some
+CGI.pm HTML generation functions handle escaping. Calling escapeHTML()
+explicitly will always escape the HTML.
+
+I<A Lurking Trap!> Some of the form-element generating methods return
+multiple tags. In a scalar context, the tags will be concatenated
+together with spaces, or whatever is the current value of the $"
+global. In a list context, the methods will return a list of
+elements, allowing you to modify them if you wish. Usually you will
+not notice this behavior, but beware of this:
+
+ printf("%s\n",end_form())
+
+end_form() produces several tags, and only the first of them will be
+printed because the format only expects one value.
+
+<p>
+
+
+=head2 CREATING AN ISINDEX TAG
+
+ print isindex(-action=>$action);
+
+ -or-
+
+ print isindex($action);
+
+Prints out an <isindex> tag. Not very exciting. The parameter
+-action specifies the URL of the script to process the query. The
+default is to process the query with the current script.
+
+=head2 STARTING AND ENDING A FORM
+
+ print start_form(-method=>$method,
+ -action=>$action,
+ -enctype=>$encoding);
+ <... various form stuff ...>
+ print end_form;
+
+ -or-
+
+ print start_form($method,$action,$encoding);
+ <... various form stuff ...>
+ print end_form;
+
+start_form() will return a <form> tag with the optional method,
+action and form encoding that you specify. The defaults are:
+
+ method: POST
+ action: this script
+ enctype: application/x-www-form-urlencoded for non-XHTML
+ multipart/form-data for XHTML, see multipart/form-data below.
+
+end_form() returns the closing </form> tag.
+
+Start_form()'s enctype argument tells the browser how to package the various
+fields of the form before sending the form to the server. Two
+values are possible:
+
+B<Note:> These methods were previously named startform() and endform().
+These methods are now DEPRECATED.
+Please use start_form() and end_form() instead.
+
+=over 4
+
+=item B<application/x-www-form-urlencoded>
+
+This is the older type of encoding. It is compatible with many CGI scripts and is
+suitable for short fields containing text data. For your
+convenience, CGI.pm stores the name of this encoding
+type in B<&CGI::URL_ENCODED>.
+
+=item B<multipart/form-data>
+
+This is the newer type of encoding.
+It is suitable for forms that contain very large fields or that
+are intended for transferring binary data. Most importantly,
+it enables the "file upload" feature. For
+your convenience, CGI.pm stores the name of this encoding type
+in B<&CGI::MULTIPART>
+
+Forms that use this type of encoding are not easily interpreted
+by CGI scripts unless they use CGI.pm or another library designed
+to handle them.
+
+If XHTML is activated (the default), then forms will be automatically
+created using this type of encoding.
+
+=back
+
+The start_form() method uses the older form of encoding by
+default unless XHTML is requested. If you want to use the
+newer form of encoding by default, you can call
+B<start_multipart_form()> instead of B<start_form()>. The
+method B<end_multipart_form()> is an alias to B<end_form()>.
+
+JAVASCRIPTING: The B<-name> and B<-onSubmit> parameters are provided
+for use with JavaScript. The -name parameter gives the
+form a name so that it can be identified and manipulated by
+JavaScript functions. -onSubmit should point to a JavaScript
+function that will be executed just before the form is submitted to your
+server. You can use this opportunity to check the contents of the form
+for consistency and completeness. If you find something wrong, you
+can put up an alert box or maybe fix things up yourself. You can
+abort the submission by returning false from this function.
+
+Usually the bulk of JavaScript functions are defined in a <script>
+block in the HTML header and -onSubmit points to one of these function
+call. See start_html() for details.
+
+=head2 FORM ELEMENTS
+
+After starting a form, you will typically create one or more
+textfields, popup menus, radio groups and other form elements. Each
+of these elements takes a standard set of named arguments. Some
+elements also have optional arguments. The standard arguments are as
+follows:
+
+=over 4
+
+=item B<-name>
+
+The name of the field. After submission this name can be used to
+retrieve the field's value using the param() method.
+
+=item B<-value>, B<-values>
+
+The initial value of the field which will be returned to the script
+after form submission. Some form elements, such as text fields, take
+a single scalar -value argument. Others, such as popup menus, take a
+reference to an array of values. The two arguments are synonyms.
+
+=item B<-tabindex>
+
+A numeric value that sets the order in which the form element receives
+focus when the user presses the tab key. Elements with lower values
+receive focus first.
+
+=item B<-id>
+
+A string identifier that can be used to identify this element to
+JavaScript and DHTML.
+
+=item B<-override>
+
+A boolean, which, if true, forces the element to take on the value
+specified by B<-value>, overriding the sticky behavior described
+earlier for the B<-nosticky> pragma.
+
+=item B<-onChange>, B<-onFocus>, B<-onBlur>, B<-onMouseOver>, B<-onMouseOut>, B<-onSelect>
+
+These are used to assign JavaScript event handlers. See the
+JavaScripting section for more details.
+
+=back
+
+Other common arguments are described in the next section. In addition
+to these, all attributes described in the HTML specifications are
+supported.
+
+=head2 CREATING A TEXT FIELD
+
+ print textfield(-name=>'field_name',
+ -value=>'starting value',
+ -size=>50,
+ -maxlength=>80);
+ -or-
+
+ print textfield('field_name','starting value',50,80);
+
+textfield() will return a text input field.
+
+=over 4
+
+=item B<Parameters>
+
+=item 1.
+
+The first parameter is the required name for the field (-name).
+
+=item 2.
+
+The optional second parameter is the default starting value for the field
+contents (-value, formerly known as -default).
+
+=item 3.
+
+The optional third parameter is the size of the field in
+ characters (-size).
+
+=item 4.
+
+The optional fourth parameter is the maximum number of characters the
+ field will accept (-maxlength).
+
+=back
+
+As with all these methods, the field will be initialized with its
+previous contents from earlier invocations of the script.
+When the form is processed, the value of the text field can be
+retrieved with:
+
+ $value = param('foo');
+
+If you want to reset it from its initial value after the script has been
+called once, you can do so like this:
+
+ param('foo',"I'm taking over this value!");
+
+=head2 CREATING A BIG TEXT FIELD
+
+ print textarea(-name=>'foo',
+ -default=>'starting value',
+ -rows=>10,
+ -columns=>50);
+
+ -or
+
+ print textarea('foo','starting value',10,50);
+
+textarea() is just like textfield, but it allows you to specify
+rows and columns for a multiline text entry box. You can provide
+a starting value for the field, which can be long and contain
+multiple lines.
+
+=head2 CREATING A PASSWORD FIELD
+
+ print password_field(-name=>'secret',
+ -value=>'starting value',
+ -size=>50,
+ -maxlength=>80);
+ -or-
+
+ print password_field('secret','starting value',50,80);
+
+password_field() is identical to textfield(), except that its contents
+will be starred out on the web page.
+
+=head2 CREATING A FILE UPLOAD FIELD
+
+ print filefield(-name=>'uploaded_file',
+ -default=>'starting value',
+ -size=>50,
+ -maxlength=>80);
+ -or-
+
+ print filefield('uploaded_file','starting value',50,80);
+
+filefield() will return a file upload field.
+In order to take full advantage of this I<you must use the new
+multipart encoding scheme> for the form. You can do this either
+by calling B<start_form()> with an encoding type of B<&CGI::MULTIPART>,
+or by calling the new method B<start_multipart_form()> instead of
+vanilla B<start_form()>.
+
+=over 4
+
+=item B<Parameters>
+
+=item 1.
+
+The first parameter is the required name for the field (-name).
+
+=item 2.
+
+The optional second parameter is the starting value for the field contents
+to be used as the default file name (-default).
+
+For security reasons, browsers don't pay any attention to this field,
+and so the starting value will always be blank. Worse, the field
+loses its "sticky" behavior and forgets its previous contents. The
+starting value field is called for in the HTML specification, however,
+and possibly some browser will eventually provide support for it.
+
+=item 3.
+
+The optional third parameter is the size of the field in
+characters (-size).
+
+=item 4.
+
+The optional fourth parameter is the maximum number of characters the
+field will accept (-maxlength).
+
+=back
+
+JAVASCRIPTING: The B<-onChange>, B<-onFocus>, B<-onBlur>,
+B<-onMouseOver>, B<-onMouseOut> and B<-onSelect> parameters are
+recognized. See textfield() for details.
+
+=head2 PROCESSING A FILE UPLOAD FIELD
+
+=head3 Basics
+
+When the form is processed, you can retrieve an L<IO::Handle> compatible
+handle for a file upload field like this:
+
+ $lightweight_fh = $q->upload('field_name');
+
+ # undef may be returned if it's not a valid file handle
+ if (defined $lightweight_fh) {
+ # Upgrade the handle to one compatible with IO::Handle:
+ my $io_handle = $lightweight_fh->handle;
+
+ open (OUTFILE,'>>','/usr/local/web/users/feedback');
+ while ($bytesread = $io_handle->read($buffer,1024)) {
+ print OUTFILE $buffer;
+ }
+ }
+
+In a list context, upload() will return an array of filehandles.
+This makes it possible to process forms that use the same name for
+multiple upload fields.
+
+If you want the entered file name for the file, you can just call param():
+
+ $filename = $q->param('field_name');
+
+Different browsers will return slightly different things for the
+name. Some browsers return the filename only. Others return the full
+path to the file, using the path conventions of the user's machine.
+Regardless, the name returned is always the name of the file on the
+I<user's> machine, and is unrelated to the name of the temporary file
+that CGI.pm creates during upload spooling (see below).
+
+When a file is uploaded the browser usually sends along some
+information along with it in the format of headers. The information
+usually includes the MIME content type. To
+retrieve this information, call uploadInfo(). It returns a reference to
+a hash containing all the document headers.
+
+ $filename = $q->param('uploaded_file');
+ $type = $q->uploadInfo($filename)->{'Content-Type'};
+ unless ($type eq 'text/html') {
+ die "HTML FILES ONLY!";
+ }
+
+If you are using a machine that recognizes "text" and "binary" data
+modes, be sure to understand when and how to use them (see the Camel book).
+Otherwise you may find that binary files are corrupted during file
+uploads.
+
+=head3 Accessing the temp files directly
+
+When processing an uploaded file, CGI.pm creates a temporary file on your hard
+disk and passes you a file handle to that file. After you are finished with the
+file handle, CGI.pm unlinks (deletes) the temporary file. If you need to you
+can access the temporary file directly. You can access the temp file for a file
+upload by passing the file name to the tmpFileName() method:
+
+ $filename = $query->param('uploaded_file');
+ $tmpfilename = $query->tmpFileName($filename);
+
+The temporary file will be deleted automatically when your program exits unless
+you manually rename it. On some operating systems (such as Windows NT), you
+will need to close the temporary file's filehandle before your program exits.
+Otherwise the attempt to delete the temporary file will fail.
+
+=head3 Handling interrupted file uploads
+
+There are occasionally problems involving parsing the uploaded file.
+This usually happens when the user presses "Stop" before the upload is
+finished. In this case, CGI.pm will return undef for the name of the
+uploaded file and set I<cgi_error()> to the string "400 Bad request
+(malformed multipart POST)". This error message is designed so that
+you can incorporate it into a status code to be sent to the browser.
+Example:
+
+ $file = $q->upload('uploaded_file');
+ if (!$file && $q->cgi_error) {
+ print $q->header(-status=>$q->cgi_error);
+ exit 0;
+ }
+
+You are free to create a custom HTML page to complain about the error,
+if you wish.
+
+=head3 Progress bars for file uploads and avoiding temp files
+
+CGI.pm gives you low-level access to file upload management through
+a file upload hook. You can use this feature to completely turn off
+the temp file storage of file uploads, or potentially write your own
+file upload progress meter.
+
+This is much like the UPLOAD_HOOK facility available in L<Apache::Request>, with
+the exception that the first argument to the callback is an L<Apache::Upload>
+object, here it's the remote filename.
+
+ $q = CGI->new(\&hook [,$data [,$use_tempfile]]);
+
+ sub hook {
+ my ($filename, $buffer, $bytes_read, $data) = @_;
+ print "Read $bytes_read bytes of $filename\n";
+ }
+
+The C<< $data >> field is optional; it lets you pass configuration
+information (e.g. a database handle) to your hook callback.
+
+The C<< $use_tempfile >> field is a flag that lets you turn on and off
+CGI.pm's use of a temporary disk-based file during file upload. If you
+set this to a FALSE value (default true) then $q->param('uploaded_file')
+will no longer work, and the only way to get at the uploaded data is
+via the hook you provide.
+
+If using the function-oriented interface, call the CGI::upload_hook()
+method before calling param() or any other CGI functions:
+
+ CGI::upload_hook(\&hook [,$data [,$use_tempfile]]);
+
+This method is not exported by default. You will have to import it
+explicitly if you wish to use it without the CGI:: prefix.
+
+=head3 Troubleshooting file uploads on Windows
+
+If you are using CGI.pm on a Windows platform and find that binary
+files get slightly larger when uploaded but that text files remain the
+same, then you have forgotten to activate binary mode on the output
+filehandle. Be sure to call binmode() on any handle that you create
+to write the uploaded file to disk.
+
+=head3 Older ways to process file uploads
+
+( This section is here for completeness. if you are building a new application with CGI.pm, you can skip it. )
+
+The original way to process file uploads with CGI.pm was to use param(). The
+value it returns has a dual nature as both a file name and a lightweight
+filehandle. This dual nature is problematic if you following the recommended
+practice of having C<use strict> in your code. Perl will complain when you try
+to use a string as a filehandle. More seriously, it is possible for the remote
+user to type garbage into the upload field, in which case what you get from
+param() is not a filehandle at all, but a string.
+
+To solve this problem the upload() method was added, which always returns a
+lightweight filehandle. This generally works well, but will have trouble
+interoperating with some other modules because the file handle is not derived
+from L<IO::Handle>. So that brings us to current recommendation given above,
+which is to call the handle() method on the file handle returned by upload().
+That upgrades the handle to an IO::Handle. It's a big win for compatibility for
+a small penalty of loading IO::Handle the first time you call it.
+
+
+=head2 CREATING A POPUP MENU
+
+ print popup_menu('menu_name',
+ ['eenie','meenie','minie'],
+ 'meenie');
+
+ -or-
+
+ %labels = ('eenie'=>'your first choice',
+ 'meenie'=>'your second choice',
+ 'minie'=>'your third choice');
+ %attributes = ('eenie'=>{'class'=>'class of first choice'});
+ print popup_menu('menu_name',
+ ['eenie','meenie','minie'],
+ 'meenie',\%labels,\%attributes);
+
+ -or (named parameter style)-
+
+ print popup_menu(-name=>'menu_name',
+ -values=>['eenie','meenie','minie'],
+ -default=>['meenie','minie'],
+ -labels=>\%labels,
+ -attributes=>\%attributes);
+
+popup_menu() creates a menu.
+
+=over 4
+
+=item 1.
+
+The required first argument is the menu's name (-name).
+
+=item 2.
+
+The required second argument (-values) is an array B<reference>
+containing the list of menu items in the menu. You can pass the
+method an anonymous array, as shown in the example, or a reference to
+a named array, such as "\@foo".
+
+=item 3.
+
+The optional third parameter (-default) is the name of the default
+menu choice. If not specified, the first item will be the default.
+The values of the previous choice will be maintained across
+queries. Pass an array reference to select multiple defaults.
+
+=item 4.
+
+The optional fourth parameter (-labels) is provided for people who
+want to use different values for the user-visible label inside the
+popup menu and the value returned to your script. It's a pointer to an
+hash relating menu values to user-visible labels. If you
+leave this parameter blank, the menu values will be displayed by
+default. (You can also leave a label undefined if you want to).
+
+=item 5.
+
+The optional fifth parameter (-attributes) is provided to assign
+any of the common HTML attributes to an individual menu item. It's
+a pointer to a hash relating menu values to another
+hash with the attribute's name as the key and the
+attribute's value as the value.
+
+=back
+
+When the form is processed, the selected value of the popup menu can
+be retrieved using:
+
+ $popup_menu_value = param('menu_name');
+
+=head2 CREATING AN OPTION GROUP
+
+Named parameter style
+
+ print popup_menu(-name=>'menu_name',
+ -values=>[qw/eenie meenie minie/,
+ optgroup(-name=>'optgroup_name',
+ -values => ['moe','catch'],
+ -attributes=>{'catch'=>{'class'=>'red'}})],
+ -labels=>{'eenie'=>'one',
+ 'meenie'=>'two',
+ 'minie'=>'three'},
+ -default=>'meenie');
+
+ Old style
+ print popup_menu('menu_name',
+ ['eenie','meenie','minie',
+ optgroup('optgroup_name', ['moe', 'catch'],
+ {'catch'=>{'class'=>'red'}})],'meenie',
+ {'eenie'=>'one','meenie'=>'two','minie'=>'three'});
+
+optgroup() creates an option group within a popup menu.
+
+=over 4
+
+=item 1.
+
+The required first argument (B<-name>) is the label attribute of the
+optgroup and is B<not> inserted in the parameter list of the query.
+
+=item 2.
+
+The required second argument (B<-values>) is an array reference
+containing the list of menu items in the menu. You can pass the
+method an anonymous array, as shown in the example, or a reference
+to a named array, such as \@foo. If you pass a HASH reference,
+the keys will be used for the menu values, and the values will be
+used for the menu labels (see -labels below).
+
+=item 3.
+
+The optional third parameter (B<-labels>) allows you to pass a reference
+to a hash containing user-visible labels for one or more
+of the menu items. You can use this when you want the user to see one
+menu string, but have the browser return your program a different one.
+If you don't specify this, the value string will be used instead
+("eenie", "meenie" and "minie" in this example). This is equivalent
+to using a hash reference for the -values parameter.
+
+=item 4.
+
+An optional fourth parameter (B<-labeled>) can be set to a true value
+and indicates that the values should be used as the label attribute
+for each option element within the optgroup.
+
+=item 5.
+
+An optional fifth parameter (-novals) can be set to a true value and
+indicates to suppress the val attribute in each option element within
+the optgroup.
+
+See the discussion on optgroup at W3C
+(http://www.w3.org/TR/REC-html40/interact/forms.html#edef-OPTGROUP)
+for details.
+
+=item 6.
+
+An optional sixth parameter (-attributes) is provided to assign
+any of the common HTML attributes to an individual menu item. It's
+a pointer to a hash relating menu values to another
+hash with the attribute's name as the key and the
+attribute's value as the value.
+
+=back
+
+=head2 CREATING A SCROLLING LIST
+
+ print scrolling_list('list_name',
+ ['eenie','meenie','minie','moe'],
+ ['eenie','moe'],5,'true',{'moe'=>{'class'=>'red'}});
+ -or-
+
+ print scrolling_list('list_name',
+ ['eenie','meenie','minie','moe'],
+ ['eenie','moe'],5,'true',
+ \%labels,%attributes);
+
+ -or-
+
+ print scrolling_list(-name=>'list_name',
+ -values=>['eenie','meenie','minie','moe'],
+ -default=>['eenie','moe'],
+ -size=>5,
+ -multiple=>'true',
+ -labels=>\%labels,
+ -attributes=>\%attributes);
+
+scrolling_list() creates a scrolling list.
+
+=over 4
+
+=item B<Parameters:>
+
+=item 1.
+
+The first and second arguments are the list name (-name) and values
+(-values). As in the popup menu, the second argument should be an
+array reference.
+
+=item 2.
+
+The optional third argument (-default) can be either a reference to a
+list containing the values to be selected by default, or can be a
+single value to select. If this argument is missing or undefined,
+then nothing is selected when the list first appears. In the named
+parameter version, you can use the synonym "-defaults" for this
+parameter.
+
+=item 3.
+
+The optional fourth argument is the size of the list (-size).
+
+=item 4.
+
+The optional fifth argument can be set to true to allow multiple
+simultaneous selections (-multiple). Otherwise only one selection
+will be allowed at a time.
+
+=item 5.
+
+The optional sixth argument is a pointer to a hash
+containing long user-visible labels for the list items (-labels).
+If not provided, the values will be displayed.
+
+=item 6.
+
+The optional sixth parameter (-attributes) is provided to assign
+any of the common HTML attributes to an individual menu item. It's
+a pointer to a hash relating menu values to another
+hash with the attribute's name as the key and the
+attribute's value as the value.
+
+When this form is processed, all selected list items will be returned as
+a list under the parameter name 'list_name'. The values of the
+selected items can be retrieved with:
+
+ @selected = param('list_name');
+
+=back
+
+=head2 CREATING A GROUP OF RELATED CHECKBOXES
+
+ print checkbox_group(-name=>'group_name',
+ -values=>['eenie','meenie','minie','moe'],
+ -default=>['eenie','moe'],
+ -linebreak=>'true',
+ -disabled => ['moe'],
+ -labels=>\%labels,
+ -attributes=>\%attributes);
+
+ print checkbox_group('group_name',
+ ['eenie','meenie','minie','moe'],
+ ['eenie','moe'],'true',\%labels,
+ {'moe'=>{'class'=>'red'}});
+
+ HTML3-COMPATIBLE BROWSERS ONLY:
+
+ print checkbox_group(-name=>'group_name',
+ -values=>['eenie','meenie','minie','moe'],
+ -rows=2,-columns=>2);
+
+
+checkbox_group() creates a list of checkboxes that are related
+by the same name.
+
+=over 4
+
+=item B<Parameters:>
+
+=item 1.
+
+The first and second arguments are the checkbox name and values,
+respectively (-name and -values). As in the popup menu, the second
+argument should be an array reference. These values are used for the
+user-readable labels printed next to the checkboxes as well as for the
+values passed to your script in the query string.
+
+=item 2.
+
+The optional third argument (-default) can be either a reference to a
+list containing the values to be checked by default, or can be a
+single value to checked. If this argument is missing or undefined,
+then nothing is selected when the list first appears.
+
+=item 3.
+
+The optional fourth argument (-linebreak) can be set to true to place
+line breaks between the checkboxes so that they appear as a vertical
+list. Otherwise, they will be strung together on a horizontal line.
+
+=back
+
+
+The optional B<-labels> argument is a pointer to a hash
+relating the checkbox values to the user-visible labels that will be
+printed next to them. If not provided, the values will be used as the
+default.
+
+
+The optional parameters B<-rows>, and B<-columns> cause
+checkbox_group() to return an HTML3 compatible table containing the
+checkbox group formatted with the specified number of rows and
+columns. You can provide just the -columns parameter if you wish;
+checkbox_group will calculate the correct number of rows for you.
+
+The option B<-disabled> takes an array of checkbox values and disables
+them by greying them out (this may not be supported by all browsers).
+
+The optional B<-attributes> argument is provided to assign any of the
+common HTML attributes to an individual menu item. It's a pointer to
+a hash relating menu values to another hash
+with the attribute's name as the key and the attribute's value as the
+value.
+
+The optional B<-tabindex> argument can be used to control the order in which
+radio buttons receive focus when the user presses the tab button. If
+passed a scalar numeric value, the first element in the group will
+receive this tab index and subsequent elements will be incremented by
+one. If given a reference to an array of radio button values, then
+the indexes will be jiggered so that the order specified in the array
+will correspond to the tab order. You can also pass a reference to a
+hash in which the hash keys are the radio button values and the values
+are the tab indexes of each button. Examples:
+
+ -tabindex => 100 # this group starts at index 100 and counts up
+ -tabindex => ['moe','minie','eenie','meenie'] # tab in this order
+ -tabindex => {meenie=>100,moe=>101,minie=>102,eenie=>200} # tab in this order
+
+The optional B<-labelattributes> argument will contain attributes
+attached to the <label> element that surrounds each button.
+
+When the form is processed, all checked boxes will be returned as
+a list under the parameter name 'group_name'. The values of the
+"on" checkboxes can be retrieved with:
+
+ @turned_on = param('group_name');
+
+The value returned by checkbox_group() is actually an array of button
+elements. You can capture them and use them within tables, lists,
+or in other creative ways:
+
+ @h = checkbox_group(-name=>'group_name',-values=>\@values);
+ &use_in_creative_way(@h);
+
+=head2 CREATING A STANDALONE CHECKBOX
+
+ print checkbox(-name=>'checkbox_name',
+ -checked=>1,
+ -value=>'ON',
+ -label=>'CLICK ME');
+
+ -or-
+
+ print checkbox('checkbox_name','checked','ON','CLICK ME');
+
+checkbox() is used to create an isolated checkbox that isn't logically
+related to any others.
+
+=over 4
+
+=item B<Parameters:>
+
+=item 1.
+
+The first parameter is the required name for the checkbox (-name). It
+will also be used for the user-readable label printed next to the
+checkbox.
+
+=item 2.
+
+The optional second parameter (-checked) specifies that the checkbox
+is turned on by default. Synonyms are -selected and -on.
+
+=item 3.
+
+The optional third parameter (-value) specifies the value of the
+checkbox when it is checked. If not provided, the word "on" is
+assumed.
+
+=item 4.
+
+The optional fourth parameter (-label) is the user-readable label to
+be attached to the checkbox. If not provided, the checkbox name is
+used.
+
+=back
+
+The value of the checkbox can be retrieved using:
+
+ $turned_on = param('checkbox_name');
+
+=head2 CREATING A RADIO BUTTON GROUP
+
+ print radio_group(-name=>'group_name',
+ -values=>['eenie','meenie','minie'],
+ -default=>'meenie',
+ -linebreak=>'true',
+ -labels=>\%labels,
+ -attributes=>\%attributes);
+
+ -or-
+
+ print radio_group('group_name',['eenie','meenie','minie'],
+ 'meenie','true',\%labels,\%attributes);
+
+
+ HTML3-COMPATIBLE BROWSERS ONLY:
+
+ print radio_group(-name=>'group_name',
+ -values=>['eenie','meenie','minie','moe'],
+ -rows=2,-columns=>2);
+
+radio_group() creates a set of logically-related radio buttons
+(turning one member of the group on turns the others off)
+
+=over 4
+
+=item B<Parameters:>
+
+=item 1.
+
+The first argument is the name of the group and is required (-name).
+
+=item 2.
+
+The second argument (-values) is the list of values for the radio
+buttons. The values and the labels that appear on the page are
+identical. Pass an array I<reference> in the second argument, either
+using an anonymous array, as shown, or by referencing a named array as
+in "\@foo".
+
+=item 3.
+
+The optional third parameter (-default) is the name of the default
+button to turn on. If not specified, the first item will be the
+default. You can provide a nonexistent button name, such as "-" to
+start up with no buttons selected.
+
+=item 4.
+
+The optional fourth parameter (-linebreak) can be set to 'true' to put
+line breaks between the buttons, creating a vertical list.
+
+=item 5.
+
+The optional fifth parameter (-labels) is a pointer to an associative
+array relating the radio button values to user-visible labels to be
+used in the display. If not provided, the values themselves are
+displayed.
+
+=back
+
+
+All modern browsers can take advantage of the optional parameters
+B<-rows>, and B<-columns>. These parameters cause radio_group() to
+return an HTML3 compatible table containing the radio group formatted
+with the specified number of rows and columns. You can provide just
+the -columns parameter if you wish; radio_group will calculate the
+correct number of rows for you.
+
+To include row and column headings in the returned table, you
+can use the B<-rowheaders> and B<-colheaders> parameters. Both
+of these accept a pointer to an array of headings to use.
+The headings are just decorative. They don't reorganize the
+interpretation of the radio buttons -- they're still a single named
+unit.
+
+The optional B<-tabindex> argument can be used to control the order in which
+radio buttons receive focus when the user presses the tab button. If
+passed a scalar numeric value, the first element in the group will
+receive this tab index and subsequent elements will be incremented by
+one. If given a reference to an array of radio button values, then
+the indexes will be jiggered so that the order specified in the array
+will correspond to the tab order. You can also pass a reference to a
+hash in which the hash keys are the radio button values and the values
+are the tab indexes of each button. Examples:
+
+ -tabindex => 100 # this group starts at index 100 and counts up
+ -tabindex => ['moe','minie','eenie','meenie'] # tab in this order
+ -tabindex => {meenie=>100,moe=>101,minie=>102,eenie=>200} # tab in this order
+
+
+The optional B<-attributes> argument is provided to assign any of the
+common HTML attributes to an individual menu item. It's a pointer to
+a hash relating menu values to another hash
+with the attribute's name as the key and the attribute's value as the
+value.
+
+The optional B<-labelattributes> argument will contain attributes
+attached to the <label> element that surrounds each button.
+
+When the form is processed, the selected radio button can
+be retrieved using:
+
+ $which_radio_button = param('group_name');
+
+The value returned by radio_group() is actually an array of button
+elements. You can capture them and use them within tables, lists,
+or in other creative ways:
+
+ @h = radio_group(-name=>'group_name',-values=>\@values);
+ &use_in_creative_way(@h);
+
+=head2 CREATING A SUBMIT BUTTON
+
+ print submit(-name=>'button_name',
+ -value=>'value');
+
+ -or-
+
+ print submit('button_name','value');
+
+submit() will create the query submission button. Every form
+should have one of these.
+
+=over 4
+
+=item B<Parameters:>
+
+=item 1.
+
+The first argument (-name) is optional. You can give the button a
+name if you have several submission buttons in your form and you want
+to distinguish between them.
+
+=item 2.
+
+The second argument (-value) is also optional. This gives the button
+a value that will be passed to your script in the query string. The
+name will also be used as the user-visible label.
+
+=item 3.
+
+You can use -label as an alias for -value. I always get confused
+about which of -name and -value changes the user-visible label on the
+button.
+
+=back
+
+You can figure out which button was pressed by using different
+values for each one:
+
+ $which_one = param('button_name');
+
+=head2 CREATING A RESET BUTTON
+
+ print reset
+
+reset() creates the "reset" button. Note that it restores the
+form to its value from the last time the script was called,
+NOT necessarily to the defaults.
+
+Note that this conflicts with the Perl reset() built-in. Use
+CORE::reset() to get the original reset function.
+
+=head2 CREATING A DEFAULT BUTTON
+
+ print defaults('button_label')
+
+defaults() creates a button that, when invoked, will cause the
+form to be completely reset to its defaults, wiping out all the
+changes the user ever made.
+
+=head2 CREATING A HIDDEN FIELD
+
+ print hidden(-name=>'hidden_name',
+ -default=>['value1','value2'...]);
+
+ -or-
+
+ print hidden('hidden_name','value1','value2'...);
+
+hidden() produces a text field that can't be seen by the user. It
+is useful for passing state variable information from one invocation
+of the script to the next.
+
+=over 4
+
+=item B<Parameters:>
+
+=item 1.
+
+The first argument is required and specifies the name of this
+field (-name).
+
+=item 2.
+
+The second argument is also required and specifies its value
+(-default). In the named parameter style of calling, you can provide
+a single value here or a reference to a whole list
+
+=back
+
+Fetch the value of a hidden field this way:
+
+ $hidden_value = param('hidden_name');
+
+Note, that just like all the other form elements, the value of a
+hidden field is "sticky". If you want to replace a hidden field with
+some other values after the script has been called once you'll have to
+do it manually:
+
+ param('hidden_name','new','values','here');
+
+=head2 CREATING A CLICKABLE IMAGE BUTTON
+
+ print image_button(-name=>'button_name',
+ -src=>'/source/URL',
+ -align=>'MIDDLE');
+
+ -or-
+
+ print image_button('button_name','/source/URL','MIDDLE');
+
+image_button() produces a clickable image. When it's clicked on the
+position of the click is returned to your script as "button_name.x"
+and "button_name.y", where "button_name" is the name you've assigned
+to it.
+
+=over 4
+
+=item B<Parameters:>
+
+=item 1.
+
+The first argument (-name) is required and specifies the name of this
+field.
+
+=item 2.
+
+The second argument (-src) is also required and specifies the URL
+
+=item 3.
+The third option (-align, optional) is an alignment type, and may be
+TOP, BOTTOM or MIDDLE
+
+=back
+
+Fetch the value of the button this way:
+ $x = param('button_name.x');
+ $y = param('button_name.y');
+
+=head2 CREATING A JAVASCRIPT ACTION BUTTON
+
+ print button(-name=>'button_name',
+ -value=>'user visible label',
+ -onClick=>"do_something()");
+
+ -or-
+
+ print button('button_name',"user visible value","do_something()");
+
+button() produces an C<< <input> >> tag with C<type="button">. When it's
+pressed the fragment of JavaScript code pointed to by the B<-onClick> parameter
+will be executed.
+
+=head1 HTTP COOKIES
+
+Browsers support a so-called "cookie" designed to help maintain state
+within a browser session. CGI.pm has several methods that support
+cookies.
+
+A cookie is a name=value pair much like the named parameters in a CGI
+query string. CGI scripts create one or more cookies and send
+them to the browser in the HTTP header. The browser maintains a list
+of cookies that belong to a particular Web server, and returns them
+to the CGI script during subsequent interactions.
+
+In addition to the required name=value pair, each cookie has several
+optional attributes:
+
+=over 4
+
+=item 1. an expiration time
+
+This is a time/date string (in a special GMT format) that indicates
+when a cookie expires. The cookie will be saved and returned to your
+script until this expiration date is reached if the user exits
+the browser and restarts it. If an expiration date isn't specified, the cookie
+will remain active until the user quits the browser.
+
+=item 2. a domain
+
+This is a partial or complete domain name for which the cookie is
+valid. The browser will return the cookie to any host that matches
+the partial domain name. For example, if you specify a domain name
+of ".capricorn.com", then the browser will return the cookie to
+Web servers running on any of the machines "www.capricorn.com",
+"www2.capricorn.com", "feckless.capricorn.com", etc. Domain names
+must contain at least two periods to prevent attempts to match
+on top level domains like ".edu". If no domain is specified, then
+the browser will only return the cookie to servers on the host the
+cookie originated from.
+
+=item 3. a path
+
+If you provide a cookie path attribute, the browser will check it
+against your script's URL before returning the cookie. For example,
+if you specify the path "/cgi-bin", then the cookie will be returned
+to each of the scripts "/cgi-bin/tally.pl", "/cgi-bin/order.pl",
+and "/cgi-bin/customer_service/complain.pl", but not to the script
+"/cgi-private/site_admin.pl". By default, path is set to "/", which
+causes the cookie to be sent to any CGI script on your site.
+
+=item 4. a "secure" flag
+
+If the "secure" attribute is set, the cookie will only be sent to your
+script if the CGI request is occurring on a secure channel, such as SSL.
+
+=back
+
+The interface to HTTP cookies is the B<cookie()> method:
+
+ $cookie = cookie(-name=>'sessionID',
+ -value=>'xyzzy',
+ -expires=>'+1h',
+ -path=>'/cgi-bin/database',
+ -domain=>'.capricorn.org',
+ -secure=>1);
+ print header(-cookie=>$cookie);
+
+B<cookie()> creates a new cookie. Its parameters include:
+
+=over 4
+
+=item B<-name>
+
+The name of the cookie (required). This can be any string at all.
+Although browsers limit their cookie names to non-whitespace
+alphanumeric characters, CGI.pm removes this restriction by escaping
+and unescaping cookies behind the scenes.
+
+=item B<-value>
+
+The value of the cookie. This can be any scalar value,
+array reference, or even hash reference. For example,
+you can store an entire hash into a cookie this way:
+
+ $cookie=cookie(-name=>'family information',
+ -value=>\%childrens_ages);
+
+=item B<-path>
+
+The optional partial path for which this cookie will be valid, as described
+above.
+
+=item B<-domain>
+
+The optional partial domain for which this cookie will be valid, as described
+above.
+
+=item B<-expires>
+
+The optional expiration date for this cookie. The format is as described
+in the section on the B<header()> method:
+
+ "+1h" one hour from now
+
+=item B<-secure>
+
+If set to true, this cookie will only be used within a secure
+SSL session.
+
+=back
+
+The cookie created by cookie() must be incorporated into the HTTP
+header within the string returned by the header() method:
+
+ use CGI ':standard';
+ print header(-cookie=>$my_cookie);
+
+To create multiple cookies, give header() an array reference:
+
+ $cookie1 = cookie(-name=>'riddle_name',
+ -value=>"The Sphynx's Question");
+ $cookie2 = cookie(-name=>'answers',
+ -value=>\%answers);
+ print header(-cookie=>[$cookie1,$cookie2]);
+
+To retrieve a cookie, request it by name by calling cookie() method
+without the B<-value> parameter. This example uses the object-oriented
+form:
+
+ use CGI;
+ $query = CGI->new;
+ $riddle = $query->cookie('riddle_name');
+ %answers = $query->cookie('answers');
+
+Cookies created with a single scalar value, such as the "riddle_name"
+cookie, will be returned in that form. Cookies with array and hash
+values can also be retrieved.
+
+The cookie and CGI namespaces are separate. If you have a parameter
+named 'answers' and a cookie named 'answers', the values retrieved by
+param() and cookie() are independent of each other. However, it's
+simple to turn a CGI parameter into a cookie, and vice-versa:
+
+ # turn a CGI parameter into a cookie
+ $c=cookie(-name=>'answers',-value=>[param('answers')]);
+ # vice-versa
+ param(-name=>'answers',-value=>[cookie('answers')]);
+
+If you call cookie() without any parameters, it will return a list of
+the names of all cookies passed to your script:
+
+ @cookies = cookie();
+
+See the B<cookie.cgi> example script for some ideas on how to use
+cookies effectively.
+
+=head1 WORKING WITH FRAMES
+
+It's possible for CGI.pm scripts to write into several browser panels
+and windows using the HTML 4 frame mechanism. There are three
+techniques for defining new frames programmatically:
+
+=over 4
+
+=item 1. Create a <Frameset> document
+
+After writing out the HTTP header, instead of creating a standard
+HTML document using the start_html() call, create a <frameset>
+document that defines the frames on the page. Specify your script(s)
+(with appropriate parameters) as the SRC for each of the frames.
+
+There is no specific support for creating <frameset> sections
+in CGI.pm, but the HTML is very simple to write.
+
+=item 2. Specify the destination for the document in the HTTP header
+
+You may provide a B<-target> parameter to the header() method:
+
+ print header(-target=>'ResultsWindow');
+
+This will tell the browser to load the output of your script into the
+frame named "ResultsWindow". If a frame of that name doesn't already
+exist, the browser will pop up a new window and load your script's
+document into that. There are a number of magic names that you can
+use for targets. See the HTML C<< <frame> >> documentation for details.
+
+=item 3. Specify the destination for the document in the <form> tag
+
+You can specify the frame to load in the FORM tag itself. With
+CGI.pm it looks like this:
+
+ print start_form(-target=>'ResultsWindow');
+
+When your script is reinvoked by the form, its output will be loaded
+into the frame named "ResultsWindow". If one doesn't already exist
+a new window will be created.
+
+=back
+
+The script "frameset.cgi" in the examples directory shows one way to
+create pages in which the fill-out form and the response live in
+side-by-side frames.
+
+=head1 SUPPORT FOR JAVASCRIPT
+
+The usual way to use JavaScript is to define a set of functions in a
+<SCRIPT> block inside the HTML header and then to register event
+handlers in the various elements of the page. Events include such
+things as the mouse passing over a form element, a button being
+clicked, the contents of a text field changing, or a form being
+submitted. When an event occurs that involves an element that has
+registered an event handler, its associated JavaScript code gets
+called.
+
+The elements that can register event handlers include the <BODY> of an
+HTML document, hypertext links, all the various elements of a fill-out
+form, and the form itself. There are a large number of events, and
+each applies only to the elements for which it is relevant. Here is a
+partial list:
+
+=over 4
+
+=item B<onLoad>
+
+The browser is loading the current document. Valid in:
+
+ + The HTML <BODY> section only.
+
+=item B<onUnload>
+
+The browser is closing the current page or frame. Valid for:
+
+ + The HTML <BODY> section only.
+
+=item B<onSubmit>
+
+The user has pressed the submit button of a form. This event happens
+just before the form is submitted, and your function can return a
+value of false in order to abort the submission. Valid for:
+
+ + Forms only.
+
+=item B<onClick>
+
+The mouse has clicked on an item in a fill-out form. Valid for:
+
+ + Buttons (including submit, reset, and image buttons)
+ + Checkboxes
+ + Radio buttons
+
+=item B<onChange>
+
+The user has changed the contents of a field. Valid for:
+
+ + Text fields
+ + Text areas
+ + Password fields
+ + File fields
+ + Popup Menus
+ + Scrolling lists
+
+=item B<onFocus>
+
+The user has selected a field to work with. Valid for:
+
+ + Text fields
+ + Text areas
+ + Password fields
+ + File fields
+ + Popup Menus
+ + Scrolling lists
+
+=item B<onBlur>
+
+The user has deselected a field (gone to work somewhere else). Valid
+for:
+
+ + Text fields
+ + Text areas
+ + Password fields
+ + File fields
+ + Popup Menus
+ + Scrolling lists
+
+=item B<onSelect>
+
+The user has changed the part of a text field that is selected. Valid
+for:
+
+ + Text fields
+ + Text areas
+ + Password fields
+ + File fields
+
+=item B<onMouseOver>
+
+The mouse has moved over an element.
+
+ + Text fields
+ + Text areas
+ + Password fields
+ + File fields
+ + Popup Menus
+ + Scrolling lists
+
+=item B<onMouseOut>
+
+The mouse has moved off an element.
+
+ + Text fields
+ + Text areas
+ + Password fields
+ + File fields
+ + Popup Menus
+ + Scrolling lists
+
+=back
+
+In order to register a JavaScript event handler with an HTML element,
+just use the event name as a parameter when you call the corresponding
+CGI method. For example, to have your validateAge() JavaScript code
+executed every time the textfield named "age" changes, generate the
+field like this:
+
+ print textfield(-name=>'age',-onChange=>"validateAge(this)");
+
+This example assumes that you've already declared the validateAge()
+function by incorporating it into a <SCRIPT> block. The CGI.pm
+start_html() method provides a convenient way to create this section.
+
+Similarly, you can create a form that checks itself over for
+consistency and alerts the user if some essential value is missing by
+creating it this way:
+ print start_form(-onSubmit=>"validateMe(this)");
+
+See the javascript.cgi script for a demonstration of how this all
+works.
+
+
+=head1 LIMITED SUPPORT FOR CASCADING STYLE SHEETS
+
+CGI.pm has limited support for HTML3's cascading style sheets (css).
+To incorporate a stylesheet into your document, pass the
+start_html() method a B<-style> parameter. The value of this
+parameter may be a scalar, in which case it is treated as the source
+URL for the stylesheet, or it may be a hash reference. In the latter
+case you should provide the hash with one or more of B<-src> or
+B<-code>. B<-src> points to a URL where an externally-defined
+stylesheet can be found. B<-code> points to a scalar value to be
+incorporated into a <style> section. Style definitions in B<-code>
+override similarly-named ones in B<-src>, hence the name "cascading."
+
+You may also specify the type of the stylesheet by adding the optional
+B<-type> parameter to the hash pointed to by B<-style>. If not
+specified, the style defaults to 'text/css'.
+
+To refer to a style within the body of your document, add the
+B<-class> parameter to any HTML element:
+
+ print h1({-class=>'Fancy'},'Welcome to the Party');
+
+Or define styles on the fly with the B<-style> parameter:
+
+ print h1({-style=>'Color: red;'},'Welcome to Hell');
+
+You may also use the new B<span()> element to apply a style to a
+section of text:
+
+ print span({-style=>'Color: red;'},
+ h1('Welcome to Hell'),
+ "Where did that handbasket get to?"
+ );
+
+Note that you must import the ":html3" definitions to have the
+B<span()> method available. Here's a quick and dirty example of using
+CSS's. See the CSS specification at
+http://www.w3.org/Style/CSS/ for more information.
+
+ use CGI qw/:standard :html3/;
+
+ #here's a stylesheet incorporated directly into the page
+ $newStyle=<<END;
+ <!--
+ P.Tip {
+ margin-right: 50pt;
+ margin-left: 50pt;
+ color: red;
+ }
+ P.Alert {
+ font-size: 30pt;
+ font-family: sans-serif;
+ color: red;
+ }
+ -->
+ END
+ print header();
+ print start_html( -title=>'CGI with Style',
+ -style=>{-src=>'http://www.capricorn.com/style/st1.css',
+ -code=>$newStyle}
+ );
+ print h1('CGI with Style'),
+ p({-class=>'Tip'},
+ "Better read the cascading style sheet spec before playing with this!"),
+ span({-style=>'color: magenta'},
+ "Look Mom, no hands!",
+ p(),
+ "Whooo wee!"
+ );
+ print end_html;
+
+Pass an array reference to B<-code> or B<-src> in order to incorporate
+multiple stylesheets into your document.
+
+Should you wish to incorporate a verbatim stylesheet that includes
+arbitrary formatting in the header, you may pass a -verbatim tag to
+the -style hash, as follows:
+
+print start_html (-style => {-verbatim => '@import url("/server-common/css/'.$cssFile.'");',
+ -src => '/server-common/css/core.css'});
+
+
+This will generate an HTML header that contains this:
+
+ <link rel="stylesheet" type="text/css" href="/server-common/css/core.css">
+ <style type="text/css">
+ @import url("/server-common/css/main.css");
+ </style>
+
+Any additional arguments passed in the -style value will be
+incorporated into the <link> tag. For example:
+
+ start_html(-style=>{-src=>['/styles/print.css','/styles/layout.css'],
+ -media => 'all'});
+
+This will give:
+
+ <link rel="stylesheet" type="text/css" href="/styles/print.css" media="all"/>
+ <link rel="stylesheet" type="text/css" href="/styles/layout.css" media="all"/>
+
+<p>
+
+To make more complicated <link> tags, use the Link() function
+and pass it to start_html() in the -head argument, as in:
+
+ @h = (Link({-rel=>'stylesheet',-type=>'text/css',-src=>'/ss/ss.css',-media=>'all'}),
+ Link({-rel=>'stylesheet',-type=>'text/css',-src=>'/ss/fred.css',-media=>'paper'}));
+ print start_html({-head=>\@h})
+
+To create primary and "alternate" stylesheet, use the B<-alternate> option:
+
+ start_html(-style=>{-src=>[
+ {-src=>'/styles/print.css'},
+ {-src=>'/styles/alt.css',-alternate=>1}
+ ]
+ });
+
+=head1 DEBUGGING
+
+If you are running the script from the command line or in the perl
+debugger, you can pass the script a list of keywords or
+parameter=value pairs on the command line or from standard input (you
+don't have to worry about tricking your script into reading from
+environment variables). You can pass keywords like this:
+
+ your_script.pl keyword1 keyword2 keyword3
+
+or this:
+
+ your_script.pl keyword1+keyword2+keyword3
+
+or this:
+
+ your_script.pl name1=value1 name2=value2
+
+or this:
+
+ your_script.pl name1=value1&name2=value2
+
+To turn off this feature, use the -no_debug pragma.
+
+To test the POST method, you may enable full debugging with the -debug
+pragma. This will allow you to feed newline-delimited name=value
+pairs to the script on standard input.
+
+When debugging, you can use quotes and backslashes to escape
+characters in the familiar shell manner, letting you place
+spaces and other funny characters in your parameter=value
+pairs:
+
+ your_script.pl "name1='I am a long value'" "name2=two\ words"
+
+Finally, you can set the path info for the script by prefixing the first
+name/value parameter with the path followed by a question mark (?):
+
+ your_script.pl /your/path/here?name1=value1&name2=value2
+
+=head2 DUMPING OUT ALL THE NAME/VALUE PAIRS
+
+The Dump() method produces a string consisting of all the query's
+name/value pairs formatted nicely as a nested list. This is useful
+for debugging purposes:
+
+ print Dump
+
+
+Produces something that looks like:
+
+ <ul>
+ <li>name1
+ <ul>
+ <li>value1
+ <li>value2
+ </ul>
+ <li>name2
+ <ul>
+ <li>value1
+ </ul>
+ </ul>
+
+As a shortcut, you can interpolate the entire CGI object into a string
+and it will be replaced with the a nice HTML dump shown above:
+
+ $query=CGI->new;
+ print "<h2>Current Values</h2> $query\n";
+
+=head1 FETCHING ENVIRONMENT VARIABLES
+
+Some of the more useful environment variables can be fetched
+through this interface. The methods are as follows:
+
+=over 4
+
+=item B<Accept()>
+
+Return a list of MIME types that the remote browser accepts. If you
+give this method a single argument corresponding to a MIME type, as in
+Accept('text/html'), it will return a floating point value
+corresponding to the browser's preference for this type from 0.0
+(don't want) to 1.0. Glob types (e.g. text/*) in the browser's accept
+list are handled correctly.
+
+Note that the capitalization changed between version 2.43 and 2.44 in
+order to avoid conflict with Perl's accept() function.
+
+=item B<raw_cookie()>
+
+Returns the HTTP_COOKIE variable. Cookies have a special format, and
+this method call just returns the raw form (?cookie dough). See
+cookie() for ways of setting and retrieving cooked cookies.
+
+Called with no parameters, raw_cookie() returns the packed cookie
+structure. You can separate it into individual cookies by splitting
+on the character sequence "; ". Called with the name of a cookie,
+retrieves the B<unescaped> form of the cookie. You can use the
+regular cookie() method to get the names, or use the raw_fetch()
+method from the CGI::Cookie module.
+
+=item B<user_agent()>
+
+Returns the HTTP_USER_AGENT variable. If you give
+this method a single argument, it will attempt to
+pattern match on it, allowing you to do something
+like user_agent(Mozilla);
+
+=item B<path_info()>
+
+Returns additional path information from the script URL.
+E.G. fetching /cgi-bin/your_script/additional/stuff will result in
+path_info() returning "/additional/stuff".
+
+NOTE: The Microsoft Internet Information Server
+is broken with respect to additional path information. If
+you use the Perl DLL library, the IIS server will attempt to
+execute the additional path information as a Perl script.
+If you use the ordinary file associations mapping, the
+path information will be present in the environment,
+but incorrect. The best thing to do is to avoid using additional
+path information in CGI scripts destined for use with IIS.
+
+=item B<path_translated()>
+
+As per path_info() but returns the additional
+path information translated into a physical path, e.g.
+"/usr/local/etc/httpd/htdocs/additional/stuff".
+
+The Microsoft IIS is broken with respect to the translated
+path as well.
+
+=item B<remote_host()>
+
+Returns either the remote host name or IP address.
+if the former is unavailable.
+
+=item B<remote_addr()>
+
+Returns the remote host IP address, or
+127.0.0.1 if the address is unavailable.
+
+=item B<script_name()>
+Return the script name as a partial URL, for self-referring
+scripts.
+
+=item B<referer()>
+
+Return the URL of the page the browser was viewing
+prior to fetching your script. Not available for all
+browsers.
+
+=item B<auth_type ()>
+
+Return the authorization/verification method in use for this
+script, if any.
+
+=item B<server_name ()>
+
+Returns the name of the server, usually the machine's host
+name.
+
+=item B<virtual_host ()>
+
+When using virtual hosts, returns the name of the host that
+the browser attempted to contact
+
+=item B<server_port ()>
+
+Return the port that the server is listening on.
+
+=item B<virtual_port ()>
+
+Like server_port() except that it takes virtual hosts into account.
+Use this when running with virtual hosts.
+
+=item B<server_software ()>
+
+Returns the server software and version number.
+
+=item B<remote_user ()>
+
+Return the authorization/verification name used for user
+verification, if this script is protected.
+
+=item B<user_name ()>
+
+Attempt to obtain the remote user's name, using a variety of different
+techniques. This only works with older browsers such as Mosaic.
+Newer browsers do not report the user name for privacy reasons!
+
+=item B<request_method()>
+
+Returns the method used to access your script, usually
+one of 'POST', 'GET' or 'HEAD'.
+
+=item B<content_type()>
+
+Returns the content_type of data submitted in a POST, generally
+multipart/form-data or application/x-www-form-urlencoded
+
+=item B<http()>
+
+Called with no arguments returns the list of HTTP environment
+variables, including such things as HTTP_USER_AGENT,
+HTTP_ACCEPT_LANGUAGE, and HTTP_ACCEPT_CHARSET, corresponding to the
+like-named HTTP header fields in the request. Called with the name of
+an HTTP header field, returns its value. Capitalization and the use
+of hyphens versus underscores are not significant.
+
+For example, all three of these examples are equivalent:
+
+ $requested_language = http('Accept-language');
+ $requested_language = http('Accept_language');
+ $requested_language = http('HTTP_ACCEPT_LANGUAGE');
+
+=item B<https()>
+
+The same as I<http()>, but operates on the HTTPS environment variables
+present when the SSL protocol is in effect. Can be used to determine
+whether SSL is turned on.
+
+=back
+
+=head1 USING NPH SCRIPTS
+
+NPH, or "no-parsed-header", scripts bypass the server completely by
+sending the complete HTTP header directly to the browser. This has
+slight performance benefits, but is of most use for taking advantage
+of HTTP extensions that are not directly supported by your server,
+such as server push and PICS headers.
+
+Servers use a variety of conventions for designating CGI scripts as
+NPH. Many Unix servers look at the beginning of the script's name for
+the prefix "nph-". The Macintosh WebSTAR server and Microsoft's
+Internet Information Server, in contrast, try to decide whether a
+program is an NPH script by examining the first line of script output.
+
+
+CGI.pm supports NPH scripts with a special NPH mode. When in this
+mode, CGI.pm will output the necessary extra header information when
+the header() and redirect() methods are
+called.
+
+The Microsoft Internet Information Server requires NPH mode. As of
+version 2.30, CGI.pm will automatically detect when the script is
+running under IIS and put itself into this mode. You do not need to
+do this manually, although it won't hurt anything if you do. However,
+note that if you have applied Service Pack 6, much of the
+functionality of NPH scripts, including the ability to redirect while
+setting a cookie, B<do not work at all> on IIS without a special patch
+from Microsoft. See
+http://web.archive.org/web/20010812012030/http://support.microsoft.com/support/kb/articles/Q280/3/41.ASP
+Non-Parsed Headers Stripped From CGI Applications That Have nph-
+Prefix in Name.
+
+=over 4
+
+=item In the B<use> statement
+
+Simply add the "-nph" pragma to the list of symbols to be imported into
+your script:
+
+ use CGI qw(:standard -nph)
+
+=item By calling the B<nph()> method:
+
+Call B<nph()> with a non-zero parameter at any point after using CGI.pm in your program.
+
+ CGI->nph(1)
+
+=item By using B<-nph> parameters
+
+in the B<header()> and B<redirect()> statements:
+
+ print header(-nph=>1);
+
+=back
+
+=head1 Server Push
+
+CGI.pm provides four simple functions for producing multipart
+documents of the type needed to implement server push. These
+functions were graciously provided by Ed Jordan <ed@fidalgo.net>. To
+import these into your namespace, you must import the ":push" set.
+You are also advised to put the script into NPH mode and to set $| to
+1 to avoid buffering problems.
+
+Here is a simple script that demonstrates server push:
+
+ #!/usr/local/bin/perl
+ use CGI qw/:push -nph/;
+ $| = 1;
+ print multipart_init(-boundary=>'----here we go!');
+ for (0 .. 4) {
+ print multipart_start(-type=>'text/plain'),
+ "The current time is ",scalar(localtime),"\n";
+ if ($_ < 4) {
+ print multipart_end;
+ } else {
+ print multipart_final;
+ }
+ sleep 1;
+ }
+
+This script initializes server push by calling B<multipart_init()>.
+It then enters a loop in which it begins a new multipart section by
+calling B<multipart_start()>, prints the current local time,
+and ends a multipart section with B<multipart_end()>. It then sleeps
+a second, and begins again. On the final iteration, it ends the
+multipart section with B<multipart_final()> rather than with
+B<multipart_end()>.
+
+=over 4
+
+=item multipart_init()
+
+ multipart_init(-boundary=>$boundary);
+
+Initialize the multipart system. The -boundary argument specifies
+what MIME boundary string to use to separate parts of the document.
+If not provided, CGI.pm chooses a reasonable boundary for you.
+
+=item multipart_start()
+
+ multipart_start(-type=>$type)
+
+Start a new part of the multipart document using the specified MIME
+type. If not specified, text/html is assumed.
+
+=item multipart_end()
+
+ multipart_end()
+
+End a part. You must remember to call multipart_end() once for each
+multipart_start(), except at the end of the last part of the multipart
+document when multipart_final() should be called instead of multipart_end().
+
+=item multipart_final()
+
+ multipart_final()
+
+End all parts. You should call multipart_final() rather than
+multipart_end() at the end of the last part of the multipart document.
+
+=back
+
+Users interested in server push applications should also have a look
+at the CGI::Push module.
+
+=head1 Avoiding Denial of Service Attacks
+
+A potential problem with CGI.pm is that, by default, it attempts to
+process form POSTings no matter how large they are. A wily hacker
+could attack your site by sending a CGI script a huge POST of many
+megabytes. CGI.pm will attempt to read the entire POST into a
+variable, growing hugely in size until it runs out of memory. While
+the script attempts to allocate the memory the system may slow down
+dramatically. This is a form of denial of service attack.
+
+Another possible attack is for the remote user to force CGI.pm to
+accept a huge file upload. CGI.pm will accept the upload and store it
+in a temporary directory even if your script doesn't expect to receive
+an uploaded file. CGI.pm will delete the file automatically when it
+terminates, but in the meantime the remote user may have filled up the
+server's disk space, causing problems for other programs.
+
+The best way to avoid denial of service attacks is to limit the amount
+of memory, CPU time and disk space that CGI scripts can use. Some Web
+servers come with built-in facilities to accomplish this. In other
+cases, you can use the shell I<limit> or I<ulimit>
+commands to put ceilings on CGI resource usage.
+
+
+CGI.pm also has some simple built-in protections against denial of
+service attacks, but you must activate them before you can use them.
+These take the form of two global variables in the CGI name space:
+
+=over 4
+
+=item B<$CGI::POST_MAX>
+
+If set to a non-negative integer, this variable puts a ceiling
+on the size of POSTings, in bytes. If CGI.pm detects a POST
+that is greater than the ceiling, it will immediately exit with an error
+message. This value will affect both ordinary POSTs and
+multipart POSTs, meaning that it limits the maximum size of file
+uploads as well. You should set this to a reasonably high
+value, such as 1 megabyte.
+
+=item B<$CGI::DISABLE_UPLOADS>
+
+If set to a non-zero value, this will disable file uploads
+completely. Other fill-out form values will work as usual.
+
+=back
+
+You can use these variables in either of two ways.
+
+=over 4
+
+=item B<1. On a script-by-script basis>
+
+Set the variable at the top of the script, right after the "use" statement:
+
+ use CGI qw/:standard/;
+ use CGI::Carp 'fatalsToBrowser';
+ $CGI::POST_MAX=1024 * 100; # max 100K posts
+ $CGI::DISABLE_UPLOADS = 1; # no uploads
+
+=item B<2. Globally for all scripts>
+
+Open up CGI.pm, find the definitions for $POST_MAX and
+$DISABLE_UPLOADS, and set them to the desired values. You'll
+find them towards the top of the file in a subroutine named
+initialize_globals().
+
+=back
+
+An attempt to send a POST larger than $POST_MAX bytes will cause
+I<param()> to return an empty CGI parameter list. You can test for
+this event by checking I<cgi_error()>, either after you create the CGI
+object or, if you are using the function-oriented interface, call
+<param()> for the first time. If the POST was intercepted, then
+cgi_error() will return the message "413 POST too large".
+
+This error message is actually defined by the HTTP protocol, and is
+designed to be returned to the browser as the CGI script's status
+ code. For example:
+
+ $uploaded_file = param('upload');
+ if (!$uploaded_file && cgi_error()) {
+ print header(-status=>cgi_error());
+ exit 0;
+ }
+
+However it isn't clear that any browser currently knows what to do
+with this status code. It might be better just to create an
+HTML page that warns the user of the problem.
+
+=head1 COMPATIBILITY WITH CGI-LIB.PL
+
+To make it easier to port existing programs that use cgi-lib.pl the
+compatibility routine "ReadParse" is provided. Porting is simple:
+
+OLD VERSION
+
+ require "cgi-lib.pl";
+ &ReadParse;
+ print "The value of the antique is $in{antique}.\n";
+
+NEW VERSION
+
+ use CGI;
+ CGI::ReadParse();
+ print "The value of the antique is $in{antique}.\n";
+
+CGI.pm's ReadParse() routine creates a tied variable named %in,
+which can be accessed to obtain the query variables. Like
+ReadParse, you can also provide your own variable. Infrequently
+used features of ReadParse, such as the creation of @in and $in
+variables, are not supported.
+
+Once you use ReadParse, you can retrieve the query object itself
+this way:
+
+ $q = $in{CGI};
+ print $q->textfield(-name=>'wow',
+ -value=>'does this really work?');
+
+This allows you to start using the more interesting features
+of CGI.pm without rewriting your old scripts from scratch.
+
+An even simpler way to mix cgi-lib calls with CGI.pm calls is to import both the
+C<:cgi-lib> and C<:standard> method:
+
+ use CGI qw(:cgi-lib :standard);
+ &ReadParse;
+ print "The price of your purchase is $in{price}.\n";
+ print textfield(-name=>'price', -default=>'$1.99');
+
+=head2 Cgi-lib functions that are available in CGI.pm
+
+In compatability mode, the following cgi-lib.pl functions are
+available for your use:
+
+ ReadParse()
+ PrintHeader()
+ HtmlTop()
+ HtmlBot()
+ SplitParam()
+ MethGet()
+ MethPost()
+
+=head2 Cgi-lib functions that are not available in CGI.pm
+
+ * Extended form of ReadParse()
+ The extended form of ReadParse() that provides for file upload
+ spooling, is not available.
+
+ * MyBaseURL()
+ This function is not available. Use CGI.pm's url() method instead.
+
+ * MyFullURL()
+ This function is not available. Use CGI.pm's self_url() method
+ instead.
+
+ * CgiError(), CgiDie()
+ These functions are not supported. Look at CGI::Carp for the way I
+ prefer to handle error messages.
+
+ * PrintVariables()
+ This function is not available. To achieve the same effect,
+ just print out the CGI object:
+
+ use CGI qw(:standard);
+ $q = CGI->new;
+ print h1("The Variables Are"),$q;
+
+ * PrintEnv()
+ This function is not available. You'll have to roll your own if you really need it.
+
+=head1 AUTHOR INFORMATION
+
+The CGI.pm distribution is copyright 1995-2007, Lincoln D. Stein. It is
+distributed under GPL and the Artistic License 2.0.
+
+Address bug reports and comments to: lstein@cshl.org. When sending
+bug reports, please provide the version of CGI.pm, the version of
+Perl, the name and version of your Web server, and the name and
+version of the operating system you are using. If the problem is even
+remotely browser dependent, please provide information about the
+affected browsers as well.
+
+=head1 CREDITS
+
+Thanks very much to:
+
+=over 4
+
+=item Matt Heffron (heffron@falstaff.css.beckman.com)
+
+=item James Taylor (james.taylor@srs.gov)
+
+=item Scott Anguish <sanguish@digifix.com>
+
+=item Mike Jewell (mlj3u@virginia.edu)
+
+=item Timothy Shimmin (tes@kbs.citri.edu.au)
+
+=item Joergen Haegg (jh@axis.se)
+
+=item Laurent Delfosse (delfosse@delfosse.com)
+
+=item Richard Resnick (applepi1@aol.com)
+
+=item Craig Bishop (csb@barwonwater.vic.gov.au)
+
+=item Tony Curtis (tc@vcpc.univie.ac.at)
+
+=item Tim Bunce (Tim.Bunce@ig.co.uk)
+
+=item Tom Christiansen (tchrist@convex.com)
+
+=item Andreas Koenig (k@franz.ww.TU-Berlin.DE)
+
+=item Tim MacKenzie (Tim.MacKenzie@fulcrum.com.au)
+
+=item Kevin B. Hendricks (kbhend@dogwood.tyler.wm.edu)
+
+=item Stephen Dahmen (joyfire@inxpress.net)
+
+=item Ed Jordan (ed@fidalgo.net)
+
+=item David Alan Pisoni (david@cnation.com)
+
+=item Doug MacEachern (dougm@opengroup.org)
+
+=item Robin Houston (robin@oneworld.org)
+
+=item ...and many many more...
+
+for suggestions and bug fixes.
+
+=back
+
+=head1 A COMPLETE EXAMPLE OF A SIMPLE FORM-BASED SCRIPT
+
+
+ #!/usr/local/bin/perl
+
+ use CGI ':standard';
+
+ print header;
+ print start_html("Example CGI.pm Form");
+ print "<h1> Example CGI.pm Form</h1>\n";
+ print_prompt();
+ do_work();
+ print_tail();
+ print end_html;
+
+ sub print_prompt {
+ print start_form;
+ print "<em>What's your name?</em><br>";
+ print textfield('name');
+ print checkbox('Not my real name');
+
+ print "<p><em>Where can you find English Sparrows?</em><br>";
+ print checkbox_group(
+ -name=>'Sparrow locations',
+ -values=>[England,France,Spain,Asia,Hoboken],
+ -linebreak=>'yes',
+ -defaults=>[England,Asia]);
+
+ print "<p><em>How far can they fly?</em><br>",
+ radio_group(
+ -name=>'how far',
+ -values=>['10 ft','1 mile','10 miles','real far'],
+ -default=>'1 mile');
+
+ print "<p><em>What's your favorite color?</em> ";
+ print popup_menu(-name=>'Color',
+ -values=>['black','brown','red','yellow'],
+ -default=>'red');
+
+ print hidden('Reference','Monty Python and the Holy Grail');
+
+ print "<p><em>What have you got there?</em><br>";
+ print scrolling_list(
+ -name=>'possessions',
+ -values=>['A Coconut','A Grail','An Icon',
+ 'A Sword','A Ticket'],
+ -size=>5,
+ -multiple=>'true');
+
+ print "<p><em>Any parting comments?</em><br>";
+ print textarea(-name=>'Comments',
+ -rows=>10,
+ -columns=>50);
+
+ print "<p>",reset;
+ print submit('Action','Shout');
+ print submit('Action','Scream');
+ print end_form;
+ print "<hr>\n";
+ }
+
+ sub do_work {
+
+ print "<h2>Here are the current settings in this form</h2>";
+
+ for my $key (param) {
+ print "<strong>$key</strong> -> ";
+ my @values = param($key);
+ print join(", ",@values),"<br>\n";
+ }
+ }
+
+ sub print_tail {
+ print <<END;
+ <hr>
+ <address>Lincoln D. Stein</address><br>
+ <a href="/">Home Page</a>
+ END
+ }
+
+=head1 BUGS
+
+Please report them.
+
+=head1 SEE ALSO
+
+L<CGI::Carp> - provides a L<Carp> implementation tailored to the CGI environment.
+
+L<CGI::Fast> - supports running CGI applications under FastCGI
+
+L<CGI::Pretty> - pretty prints HTML generated by CGI.pm (with a performance penalty)
+
+=cut
+
diff --git a/lib/CGI/Apache.pm b/lib/CGI/Apache.pm
new file mode 100644
index 000000000..e055e3055
--- /dev/null
+++ b/lib/CGI/Apache.pm
@@ -0,0 +1,27 @@
+package CGI::Apache;
+use CGI;
+
+$VERSION = '1.01';
+
+1;
+__END__
+
+=head1 NAME
+
+CGI::Apache - Backward compatibility module for CGI.pm
+
+=head1 SYNOPSIS
+
+Do not use this module. It is deprecated.
+
+=head1 ABSTRACT
+
+=head1 DESCRIPTION
+
+=head1 AUTHOR INFORMATION
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+=cut
diff --git a/lib/CGI/Carp.pm b/lib/CGI/Carp.pm
new file mode 100644
index 000000000..9d644d9c9
--- /dev/null
+++ b/lib/CGI/Carp.pm
@@ -0,0 +1,630 @@
+package CGI::Carp;
+
+=head1 NAME
+
+B<CGI::Carp> - CGI routines for writing to the HTTPD (or other) error log
+
+=head1 SYNOPSIS
+
+ use CGI::Carp;
+
+ croak "We're outta here!";
+ confess "It was my fault: $!";
+ carp "It was your fault!";
+ warn "I'm confused";
+ die "I'm dying.\n";
+
+ use CGI::Carp qw(cluck);
+ cluck "I wouldn't do that if I were you";
+
+ use CGI::Carp qw(fatalsToBrowser);
+ die "Fatal error messages are now sent to browser";
+
+=head1 DESCRIPTION
+
+CGI scripts have a nasty habit of leaving warning messages in the error
+logs that are neither time stamped nor fully identified. Tracking down
+the script that caused the error is a pain. This fixes that. Replace
+the usual
+
+ use Carp;
+
+with
+
+ use CGI::Carp
+
+And the standard warn(), die (), croak(), confess() and carp() calls
+will automagically be replaced with functions that write out nicely
+time-stamped messages to the HTTP server error log.
+
+For example:
+
+ [Fri Nov 17 21:40:43 1995] test.pl: I'm confused at test.pl line 3.
+ [Fri Nov 17 21:40:43 1995] test.pl: Got an error message: Permission denied.
+ [Fri Nov 17 21:40:43 1995] test.pl: I'm dying.
+
+=head1 REDIRECTING ERROR MESSAGES
+
+By default, error messages are sent to STDERR. Most HTTPD servers
+direct STDERR to the server's error log. Some applications may wish
+to keep private error logs, distinct from the server's error log, or
+they may wish to direct error messages to STDOUT so that the browser
+will receive them.
+
+The C<carpout()> function is provided for this purpose. Since
+carpout() is not exported by default, you must import it explicitly by
+saying
+
+ use CGI::Carp qw(carpout);
+
+The carpout() function requires one argument, which should be a
+reference to an open filehandle for writing errors. It should be
+called in a C<BEGIN> block at the top of the CGI application so that
+compiler errors will be caught. Example:
+
+ BEGIN {
+ use CGI::Carp qw(carpout);
+ open(LOG, ">>/usr/local/cgi-logs/mycgi-log") or
+ die("Unable to open mycgi-log: $!\n");
+ carpout(LOG);
+ }
+
+carpout() does not handle file locking on the log for you at this point.
+Also, note that carpout() does not work with in-memory file handles, although
+a patch would be welcome to address that.
+
+The real STDERR is not closed -- it is moved to CGI::Carp::SAVEERR. Some
+servers, when dealing with CGI scripts, close their connection to the
+browser when the script closes STDOUT and STDERR. CGI::Carp::SAVEERR is there to
+prevent this from happening prematurely.
+
+You can pass filehandles to carpout() in a variety of ways. The "correct"
+way according to Tom Christiansen is to pass a reference to a filehandle
+GLOB:
+
+ carpout(\*LOG);
+
+This looks weird to mere mortals however, so the following syntaxes are
+accepted as well:
+
+ carpout(LOG);
+ carpout(main::LOG);
+ carpout(main'LOG);
+ carpout(\LOG);
+ carpout(\'main::LOG');
+
+ ... and so on
+
+FileHandle and other objects work as well.
+
+Use of carpout() is not great for performance, so it is recommended
+for debugging purposes or for moderate-use applications. A future
+version of this module may delay redirecting STDERR until one of the
+CGI::Carp methods is called to prevent the performance hit.
+
+=head1 MAKING PERL ERRORS APPEAR IN THE BROWSER WINDOW
+
+If you want to send fatal (die, confess) errors to the browser, ask to
+import the special "fatalsToBrowser" subroutine:
+
+ use CGI::Carp qw(fatalsToBrowser);
+ die "Bad error here";
+
+Fatal errors will now be echoed to the browser as well as to the log. CGI::Carp
+arranges to send a minimal HTTP header to the browser so that even errors that
+occur in the early compile phase will be seen.
+Nonfatal errors will still be directed to the log file only (unless redirected
+with carpout).
+
+Note that fatalsToBrowser may B<not> work well with mod_perl version 2.0
+and higher.
+
+=head2 Changing the default message
+
+By default, the software error message is followed by a note to
+contact the Webmaster by e-mail with the time and date of the error.
+If this message is not to your liking, you can change it using the
+set_message() routine. This is not imported by default; you should
+import it on the use() line:
+
+ use CGI::Carp qw(fatalsToBrowser set_message);
+ set_message("It's not a bug, it's a feature!");
+
+You may also pass in a code reference in order to create a custom
+error message. At run time, your code will be called with the text
+of the error message that caused the script to die. Example:
+
+ use CGI::Carp qw(fatalsToBrowser set_message);
+ BEGIN {
+ sub handle_errors {
+ my $msg = shift;
+ print "<h1>Oh gosh</h1>";
+ print "<p>Got an error: $msg</p>";
+ }
+ set_message(\&handle_errors);
+ }
+
+In order to correctly intercept compile-time errors, you should call
+set_message() from within a BEGIN{} block.
+
+=head1 DOING MORE THAN PRINTING A MESSAGE IN THE EVENT OF PERL ERRORS
+
+If fatalsToBrowser in conjunction with set_message does not provide
+you with all of the functionality you need, you can go one step
+further by specifying a function to be executed any time a script
+calls "die", has a syntax error, or dies unexpectedly at runtime
+with a line like "undef->explode();".
+
+ use CGI::Carp qw(set_die_handler);
+ BEGIN {
+ sub handle_errors {
+ my $msg = shift;
+ print "content-type: text/html\n\n";
+ print "<h1>Oh gosh</h1>";
+ print "<p>Got an error: $msg</p>";
+
+ #proceed to send an email to a system administrator,
+ #write a detailed message to the browser and/or a log,
+ #etc....
+ }
+ set_die_handler(\&handle_errors);
+ }
+
+Notice that if you use set_die_handler(), you must handle sending
+HTML headers to the browser yourself if you are printing a message.
+
+If you use set_die_handler(), you will most likely interfere with
+the behavior of fatalsToBrowser, so you must use this or that, not
+both.
+
+Using set_die_handler() sets SIG{__DIE__} (as does fatalsToBrowser),
+and there is only one SIG{__DIE__}. This means that if you are
+attempting to set SIG{__DIE__} yourself, you may interfere with
+this module's functionality, or this module may interfere with
+your module's functionality.
+
+=head2 SUPPRESSING PERL ERRORS APPEARING IN THE BROWSER WINDOW
+
+A problem sometimes encountered when using fatalsToBrowser is
+when a C<die()> is done inside an C<eval> body or expression.
+Even though the
+fatalsToBrower support takes precautions to avoid this,
+you still may get the error message printed to STDOUT.
+This may have some undesireable effects when the purpose of doing the
+eval is to determine which of several algorithms is to be used.
+
+By setting C<$CGI::Carp::TO_BROWSER> to 0 you can suppress printing the C<die> messages
+but without all of the complexity of using C<set_die_handler>.
+You can localize this effect to inside C<eval> bodies if this is desireable:
+For example:
+
+ eval {
+ local $CGI::Carp::TO_BROWSER = 0;
+ die "Fatal error messages not sent browser"
+ }
+ # $@ will contain error message
+
+
+=head1 MAKING WARNINGS APPEAR AS HTML COMMENTS
+
+It is now also possible to make non-fatal errors appear as HTML
+comments embedded in the output of your program. To enable this
+feature, export the new "warningsToBrowser" subroutine. Since sending
+warnings to the browser before the HTTP headers have been sent would
+cause an error, any warnings are stored in an internal buffer until
+you call the warningsToBrowser() subroutine with a true argument:
+
+ use CGI::Carp qw(fatalsToBrowser warningsToBrowser);
+ use CGI qw(:standard);
+ print header();
+ warningsToBrowser(1);
+
+You may also give a false argument to warningsToBrowser() to prevent
+warnings from being sent to the browser while you are printing some
+content where HTML comments are not allowed:
+
+ warningsToBrowser(0); # disable warnings
+ print "<script type=\"text/javascript\"><!--\n";
+ print_some_javascript_code();
+ print "//--></script>\n";
+ warningsToBrowser(1); # re-enable warnings
+
+Note: In this respect warningsToBrowser() differs fundamentally from
+fatalsToBrowser(), which you should never call yourself!
+
+=head1 OVERRIDING THE NAME OF THE PROGRAM
+
+CGI::Carp includes the name of the program that generated the error or
+warning in the messages written to the log and the browser window.
+Sometimes, Perl can get confused about what the actual name of the
+executed program was. In these cases, you can override the program
+name that CGI::Carp will use for all messages.
+
+The quick way to do that is to tell CGI::Carp the name of the program
+in its use statement. You can do that by adding
+"name=cgi_carp_log_name" to your "use" statement. For example:
+
+ use CGI::Carp qw(name=cgi_carp_log_name);
+
+. If you want to change the program name partway through the program,
+you can use the C<set_progname()> function instead. It is not
+exported by default, you must import it explicitly by saying
+
+ use CGI::Carp qw(set_progname);
+
+Once you've done that, you can change the logged name of the program
+at any time by calling
+
+ set_progname(new_program_name);
+
+You can set the program back to the default by calling
+
+ set_progname(undef);
+
+Note that this override doesn't happen until after the program has
+compiled, so any compile-time errors will still show up with the
+non-overridden program name
+
+=head1 CHANGE LOG
+
+3.51 Added $CGI::Carp::TO_BROWSER
+
+1.29 Patch from Peter Whaite to fix the unfixable problem of CGI::Carp
+ not behaving correctly in an eval() context.
+
+1.05 carpout() added and minor corrections by Marc Hedlund
+ <hedlund@best.com> on 11/26/95.
+
+1.06 fatalsToBrowser() no longer aborts for fatal errors within
+ eval() statements.
+
+1.08 set_message() added and carpout() expanded to allow for FileHandle
+ objects.
+
+1.09 set_message() now allows users to pass a code REFERENCE for
+ really custom error messages. croak and carp are now
+ exported by default. Thanks to Gunther Birznieks for the
+ patches.
+
+1.10 Patch from Chris Dean (ctdean@cogit.com) to allow
+ module to run correctly under mod_perl.
+
+1.11 Changed order of &gt; and &lt; escapes.
+
+1.12 Changed die() on line 217 to CORE::die to avoid B<-w> warning.
+
+1.13 Added cluck() to make the module orthogonal with Carp.
+ More mod_perl related fixes.
+
+1.20 Patch from Ilmari Karonen (perl@itz.pp.sci.fi): Added
+ warningsToBrowser(). Replaced <CODE> tags with <PRE> in
+ fatalsToBrowser() output.
+
+1.23 ineval() now checks both $^S and inspects the message for the "eval" pattern
+ (hack alert!) in order to accommodate various combinations of Perl and
+ mod_perl.
+
+1.24 Patch from Scott Gifford (sgifford@suspectclass.com): Add support
+ for overriding program name.
+
+1.26 Replaced CORE::GLOBAL::die with the evil $SIG{__DIE__} because the
+ former isn't working in some people's hands. There is no such thing
+ as reliable exception handling in Perl.
+
+1.27 Replaced tell STDOUT with bytes=tell STDOUT.
+
+=head1 AUTHORS
+
+Copyright 1995-2002, Lincoln D. Stein. All rights reserved.
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+Address bug reports and comments to: lstein@cshl.org
+
+=head1 SEE ALSO
+
+Carp, CGI::Base, CGI::BasePlus, CGI::Request, CGI::MiniSvr, CGI::Form,
+CGI::Response
+
+=cut
+
+require 5.000;
+use Exporter;
+#use Carp;
+BEGIN {
+ require Carp;
+ *CORE::GLOBAL::die = \&CGI::Carp::die;
+}
+
+use File::Spec;
+
+@ISA = qw(Exporter);
+@EXPORT = qw(confess croak carp);
+@EXPORT_OK = qw(carpout fatalsToBrowser warningsToBrowser wrap set_message set_die_handler set_progname cluck ^name= die);
+
+$main::SIG{__WARN__}=\&CGI::Carp::warn;
+
+$CGI::Carp::VERSION = '3.51';
+$CGI::Carp::CUSTOM_MSG = undef;
+$CGI::Carp::DIE_HANDLER = undef;
+$CGI::Carp::TO_BROWSER = 1;
+
+
+# fancy import routine detects and handles 'errorWrap' specially.
+sub import {
+ my $pkg = shift;
+ my(%routines);
+ my(@name);
+ if (@name=grep(/^name=/,@_))
+ {
+ my($n) = (split(/=/,$name[0]))[1];
+ set_progname($n);
+ @_=grep(!/^name=/,@_);
+ }
+
+ grep($routines{$_}++,@_,@EXPORT);
+ $WRAP++ if $routines{'fatalsToBrowser'} || $routines{'wrap'};
+ $WARN++ if $routines{'warningsToBrowser'};
+ my($oldlevel) = $Exporter::ExportLevel;
+ $Exporter::ExportLevel = 1;
+ Exporter::import($pkg,keys %routines);
+ $Exporter::ExportLevel = $oldlevel;
+ $main::SIG{__DIE__} =\&CGI::Carp::die if $routines{'fatalsToBrowser'};
+# $pkg->export('CORE::GLOBAL','die');
+}
+
+# These are the originals
+sub realwarn { CORE::warn(@_); }
+sub realdie { CORE::die(@_); }
+
+sub id {
+ my $level = shift;
+ my($pack,$file,$line,$sub) = caller($level);
+ my($dev,$dirs,$id) = File::Spec->splitpath($file);
+ return ($file,$line,$id);
+}
+
+sub stamp {
+ my $time = scalar(localtime);
+ my $frame = 0;
+ my ($id,$pack,$file,$dev,$dirs);
+ if (defined($CGI::Carp::PROGNAME)) {
+ $id = $CGI::Carp::PROGNAME;
+ } else {
+ do {
+ $id = $file;
+ ($pack,$file) = caller($frame++);
+ } until !$file;
+ }
+ ($dev,$dirs,$id) = File::Spec->splitpath($id);
+ return "[$time] $id: ";
+}
+
+sub set_progname {
+ $CGI::Carp::PROGNAME = shift;
+ return $CGI::Carp::PROGNAME;
+}
+
+
+sub warn {
+ my $message = shift;
+ my($file,$line,$id) = id(1);
+ $message .= " at $file line $line.\n" unless $message=~/\n$/;
+ _warn($message) if $WARN;
+ my $stamp = stamp;
+ $message=~s/^/$stamp/gm;
+ realwarn $message;
+}
+
+sub _warn {
+ my $msg = shift;
+ if ($EMIT_WARNINGS) {
+ # We need to mangle the message a bit to make it a valid HTML
+ # comment. This is done by substituting similar-looking ISO
+ # 8859-1 characters for <, > and -. This is a hack.
+ $msg =~ tr/<>-/\253\273\255/;
+ chomp $msg;
+ print STDOUT "<!-- warning: $msg -->\n";
+ } else {
+ push @WARNINGS, $msg;
+ }
+}
+
+
+# The mod_perl package Apache::Registry loads CGI programs by calling
+# eval. These evals don't count when looking at the stack backtrace.
+sub _longmess {
+ my $message = Carp::longmess();
+ $message =~ s,eval[^\n]+(ModPerl|Apache)/(?:Registry|Dispatch)\w*\.pm.*,,s
+ if exists $ENV{MOD_PERL};
+ return $message;
+}
+
+sub ineval {
+ (exists $ENV{MOD_PERL} ? 0 : $^S) || _longmess() =~ /eval [\{\']/m
+}
+
+sub die {
+ # if no argument is passed, propagate $@ like
+ # the real die
+ my ($arg,@rest) = @_ ? @_
+ : $@ ? "$@\t...propagated"
+ : "Died"
+ ;
+
+ &$DIE_HANDLER($arg,@rest) if $DIE_HANDLER;
+
+ # the "$arg" is done on purpose!
+ # if called as die( $object, 'string' ),
+ # all is stringified, just like with
+ # the real 'die'
+ $arg = join '' => "$arg", @rest if @rest;
+
+ my($file,$line,$id) = id(1);
+
+ $arg .= " at $file line $line.\n" unless ref $arg or $arg=~/\n$/;
+
+ realdie $arg if ineval();
+ &fatalsToBrowser($arg) if ($WRAP and $CGI::Carp::TO_BROWSER);
+
+ $arg=~s/^/ stamp() /gme if $arg =~ /\n$/ or not exists $ENV{MOD_PERL};
+
+ $arg .= "\n" unless $arg =~ /\n$/;
+
+ realdie $arg;
+}
+
+sub set_message {
+ $CGI::Carp::CUSTOM_MSG = shift;
+ return $CGI::Carp::CUSTOM_MSG;
+}
+
+sub set_die_handler {
+
+ my ($handler) = shift;
+
+ #setting SIG{__DIE__} here is necessary to catch runtime
+ #errors which are not called by literally saying "die",
+ #such as the line "undef->explode();". however, doing this
+ #will interfere with fatalsToBrowser, which also sets
+ #SIG{__DIE__} in the import() function above (or the
+ #import() function above may interfere with this). for
+ #this reason, you should choose to either set the die
+ #handler here, or use fatalsToBrowser, not both.
+ $main::SIG{__DIE__} = $handler;
+
+ $CGI::Carp::DIE_HANDLER = $handler;
+
+ return $CGI::Carp::DIE_HANDLER;
+}
+
+sub confess { CGI::Carp::die Carp::longmess @_; }
+sub croak { CGI::Carp::die Carp::shortmess @_; }
+sub carp { CGI::Carp::warn Carp::shortmess @_; }
+sub cluck { CGI::Carp::warn Carp::longmess @_; }
+
+# We have to be ready to accept a filehandle as a reference
+# or a string.
+sub carpout {
+ my($in) = @_;
+ my($no) = fileno(to_filehandle($in));
+ realdie("Invalid filehandle $in\n") unless defined $no;
+
+ open(SAVEERR, ">&STDERR");
+ open(STDERR, ">&$no") or
+ ( print SAVEERR "Unable to redirect STDERR: $!\n" and exit(1) );
+}
+
+sub warningsToBrowser {
+ $EMIT_WARNINGS = @_ ? shift : 1;
+ _warn(shift @WARNINGS) while $EMIT_WARNINGS and @WARNINGS;
+}
+
+# headers
+sub fatalsToBrowser {
+ my $msg = shift;
+
+ $msg = "$msg" if ref $msg;
+
+ $msg=~s/&/&amp;/g;
+ $msg=~s/>/&gt;/g;
+ $msg=~s/</&lt;/g;
+ $msg=~s/"/&quot;/g;
+
+ my($wm) = $ENV{SERVER_ADMIN} ?
+ qq[the webmaster (<a href="mailto:$ENV{SERVER_ADMIN}">$ENV{SERVER_ADMIN}</a>)] :
+ "this site's webmaster";
+ my ($outer_message) = <<END;
+For help, please send mail to $wm, giving this error message
+and the time and date of the error.
+END
+ ;
+ my $mod_perl = exists $ENV{MOD_PERL};
+
+ if ($CUSTOM_MSG) {
+ if (ref($CUSTOM_MSG) eq 'CODE') {
+ print STDOUT "Content-type: text/html\n\n"
+ unless $mod_perl;
+ eval {
+ &$CUSTOM_MSG($msg); # nicer to perl 5.003 users
+ };
+ if ($@) { print STDERR q(error while executing the error handler: $@); }
+
+ return;
+ } else {
+ $outer_message = $CUSTOM_MSG;
+ }
+ }
+
+ my $mess = <<END;
+<h1>Software error:</h1>
+<pre>$msg</pre>
+<p>
+$outer_message
+</p>
+END
+ ;
+
+ if ($mod_perl) {
+ my $r;
+ if ($ENV{MOD_PERL_API_VERSION} && $ENV{MOD_PERL_API_VERSION} == 2) {
+ $mod_perl = 2;
+ require Apache2::RequestRec;
+ require Apache2::RequestIO;
+ require Apache2::RequestUtil;
+ require APR::Pool;
+ require ModPerl::Util;
+ require Apache2::Response;
+ $r = Apache2::RequestUtil->request;
+ }
+ else {
+ $r = Apache->request;
+ }
+ # If bytes have already been sent, then
+ # we print the message out directly.
+ # Otherwise we make a custom error
+ # handler to produce the doc for us.
+ if ($r->bytes_sent) {
+ $r->print($mess);
+ $mod_perl == 2 ? ModPerl::Util::exit(0) : $r->exit;
+ } else {
+ # MSIE won't display a custom 500 response unless it is >512 bytes!
+ if ($ENV{HTTP_USER_AGENT} =~ /MSIE/) {
+ $mess = "<!-- " . (' ' x 513) . " -->\n$mess";
+ }
+ $r->custom_response(500,$mess);
+ }
+ } else {
+ my $bytes_written = eval{tell STDOUT};
+ if (defined $bytes_written && $bytes_written > 0) {
+ print STDOUT $mess;
+ }
+ else {
+ print STDOUT "Status: 500\n";
+ print STDOUT "Content-type: text/html\n\n";
+ print STDOUT $mess;
+ }
+ }
+
+ warningsToBrowser(1); # emit warnings before dying
+}
+
+# Cut and paste from CGI.pm so that we don't have the overhead of
+# always loading the entire CGI module.
+sub to_filehandle {
+ my $thingy = shift;
+ return undef unless $thingy;
+ return $thingy if UNIVERSAL::isa($thingy,'GLOB');
+ return $thingy if UNIVERSAL::isa($thingy,'FileHandle');
+ if (!ref($thingy)) {
+ my $caller = 1;
+ while (my $package = caller($caller++)) {
+ my($tmp) = $thingy=~/[\':]/ ? $thingy : "$package\:\:$thingy";
+ return $tmp if defined(fileno($tmp));
+ }
+ }
+ return undef;
+}
+
+1;
diff --git a/lib/CGI/Cookie.pm b/lib/CGI/Cookie.pm
new file mode 100644
index 000000000..df344ff34
--- /dev/null
+++ b/lib/CGI/Cookie.pm
@@ -0,0 +1,540 @@
+package CGI::Cookie;
+
+use strict;
+use warnings;
+
+# See the bottom of this file for the POD documentation. Search for the
+# string '=head'.
+
+# You can run this file through either pod2man or pod2html to produce pretty
+# documentation in manual or html file format (these utilities are part of the
+# Perl 5 distribution).
+
+# Copyright 1995-1999, Lincoln D. Stein. All rights reserved.
+# It may be used and modified freely, but I do request that this copyright
+# notice remain attached to the file. You may modify this module as you
+# wish, but if you redistribute a modified version, please attach a note
+# listing the modifications you have made.
+
+our $VERSION='1.30';
+
+use CGI::Util qw(rearrange unescape escape);
+use overload '""' => \&as_string, 'cmp' => \&compare, 'fallback' => 1;
+
+my $PERLEX = 0;
+# Turn on special checking for ActiveState's PerlEx
+$PERLEX++ if defined($ENV{'GATEWAY_INTERFACE'}) && $ENV{'GATEWAY_INTERFACE'} =~ /^CGI-PerlEx/;
+
+# Turn on special checking for mod_perl
+# PerlEx::DBI tries to fool DBI by setting MOD_PERL
+my $MOD_PERL = 0;
+if (exists $ENV{MOD_PERL} && ! $PERLEX) {
+ if (exists $ENV{MOD_PERL_API_VERSION} && $ENV{MOD_PERL_API_VERSION} == 2) {
+ $MOD_PERL = 2;
+ require Apache2::RequestUtil;
+ require APR::Table;
+ } else {
+ $MOD_PERL = 1;
+ require Apache;
+ }
+}
+
+# fetch a list of cookies from the environment and
+# return as a hash. the cookies are parsed as normal
+# escaped URL data.
+sub fetch {
+ my $class = shift;
+ my $raw_cookie = get_raw_cookie(@_) or return;
+ return $class->parse($raw_cookie);
+}
+
+# Fetch a list of cookies from the environment or the incoming headers and
+# return as a hash. The cookie values are not unescaped or altered in any way.
+ sub raw_fetch {
+ my $class = shift;
+ my $raw_cookie = get_raw_cookie(@_) or return;
+ my %results;
+ my($key,$value);
+
+ my @pairs = split("[;,] ?",$raw_cookie);
+ for my $pair ( @pairs ) {
+ $pair =~ s/^\s+|\s+$//g; # trim leading trailing whitespace
+ my ( $key, $value ) = split "=", $pair;
+
+ $value = defined $value ? $value : '';
+ $results{$key} = $value;
+ }
+ return wantarray ? %results : \%results;
+}
+
+sub get_raw_cookie {
+ my $r = shift;
+ $r ||= eval { $MOD_PERL == 2 ?
+ Apache2::RequestUtil->request() :
+ Apache->request } if $MOD_PERL;
+
+ return $r->headers_in->{'Cookie'} if $r;
+
+ die "Run $r->subprocess_env; before calling fetch()"
+ if $MOD_PERL and !exists $ENV{REQUEST_METHOD};
+
+ return $ENV{HTTP_COOKIE} || $ENV{COOKIE};
+}
+
+
+sub parse {
+ my ($self,$raw_cookie) = @_;
+ return wantarray ? () : {} unless $raw_cookie;
+
+ my %results;
+
+ my @pairs = split("[;,] ?",$raw_cookie);
+ for (@pairs) {
+ s/^\s+//;
+ s/\s+$//;
+
+ my($key,$value) = split("=",$_,2);
+
+ # Some foreign cookies are not in name=value format, so ignore
+ # them.
+ next if !defined($value);
+ my @values = ();
+ if ($value ne '') {
+ @values = map unescape($_),split(/[&;]/,$value.'&dmy');
+ pop @values;
+ }
+ $key = unescape($key);
+ # A bug in Netscape can cause several cookies with same name to
+ # appear. The FIRST one in HTTP_COOKIE is the most recent version.
+ $results{$key} ||= $self->new(-name=>$key,-value=>\@values);
+ }
+ return wantarray ? %results : \%results;
+}
+
+sub new {
+ my ( $class, @params ) = @_;
+ $class = ref( $class ) || $class;
+ # Ignore mod_perl request object--compatibility with Apache::Cookie.
+ shift if ref $params[0]
+ && eval { $params[0]->isa('Apache::Request::Req') || $params[0]->isa('Apache') };
+ my ( $name, $value, $path, $domain, $secure, $expires, $max_age, $httponly )
+ = rearrange(
+ [
+ 'NAME', [ 'VALUE', 'VALUES' ],
+ 'PATH', 'DOMAIN',
+ 'SECURE', 'EXPIRES',
+ 'MAX-AGE','HTTPONLY'
+ ],
+ @params
+ );
+ return undef unless defined $name and defined $value;
+ my $self = {};
+ bless $self, $class;
+ $self->name( $name );
+ $self->value( $value );
+ $path ||= "/";
+ $self->path( $path ) if defined $path;
+ $self->domain( $domain ) if defined $domain;
+ $self->secure( $secure ) if defined $secure;
+ $self->expires( $expires ) if defined $expires;
+ $self->max_age($expires) if defined $max_age;
+ $self->httponly( $httponly ) if defined $httponly;
+ return $self;
+}
+
+sub as_string {
+ my $self = shift;
+ return "" unless $self->name;
+
+ no warnings; # some things may be undefined, that's OK.
+
+ my $name = escape( $self->name );
+ my $value = join "&", map { escape($_) } $self->value;
+ my @cookie = ( "$name=$value" );
+
+ push @cookie,"domain=".$self->domain if $self->domain;
+ push @cookie,"path=".$self->path if $self->path;
+ push @cookie,"expires=".$self->expires if $self->expires;
+ push @cookie,"max-age=".$self->max_age if $self->max_age;
+ push @cookie,"secure" if $self->secure;
+ push @cookie,"HttpOnly" if $self->httponly;
+
+ return join "; ", @cookie;
+}
+
+sub compare {
+ my ( $self, $value ) = @_;
+ return "$self" cmp $value;
+}
+
+sub bake {
+ my ($self, $r) = @_;
+
+ $r ||= eval {
+ $MOD_PERL == 2
+ ? Apache2::RequestUtil->request()
+ : Apache->request
+ } if $MOD_PERL;
+ if ($r) {
+ $r->headers_out->add('Set-Cookie' => $self->as_string);
+ } else {
+ require CGI;
+ print CGI::header(-cookie => $self);
+ }
+
+}
+
+# accessors
+sub name {
+ my ( $self, $name ) = @_;
+ $self->{'name'} = $name if defined $name;
+ return $self->{'name'};
+}
+
+sub value {
+ my ( $self, $value ) = @_;
+ if ( defined $value ) {
+ my @values
+ = ref $value eq 'ARRAY' ? @$value
+ : ref $value eq 'HASH' ? %$value
+ : ( $value );
+ $self->{'value'} = [@values];
+ }
+ return wantarray ? @{ $self->{'value'} } : $self->{'value'}->[0];
+}
+
+sub domain {
+ my ( $self, $domain ) = @_;
+ $self->{'domain'} = lc $domain if defined $domain;
+ return $self->{'domain'};
+}
+
+sub secure {
+ my ( $self, $secure ) = @_;
+ $self->{'secure'} = $secure if defined $secure;
+ return $self->{'secure'};
+}
+
+sub expires {
+ my ( $self, $expires ) = @_;
+ $self->{'expires'} = CGI::Util::expires($expires,'cookie') if defined $expires;
+ return $self->{'expires'};
+}
+
+sub max_age {
+ my ( $self, $max_age ) = @_;
+ $self->{'max-age'} = CGI::Util::expire_calc($max_age)-time() if defined $max_age;
+ return $self->{'max-age'};
+}
+
+sub path {
+ my ( $self, $path ) = @_;
+ $self->{'path'} = $path if defined $path;
+ return $self->{'path'};
+}
+
+
+sub httponly { # HttpOnly
+ my ( $self, $httponly ) = @_;
+ $self->{'httponly'} = $httponly if defined $httponly;
+ return $self->{'httponly'};
+}
+
+1;
+
+=head1 NAME
+
+CGI::Cookie - Interface to HTTP Cookies
+
+=head1 SYNOPSIS
+
+ use CGI qw/:standard/;
+ use CGI::Cookie;
+
+ # Create new cookies and send them
+ $cookie1 = CGI::Cookie->new(-name=>'ID',-value=>123456);
+ $cookie2 = CGI::Cookie->new(-name=>'preferences',
+ -value=>{ font => Helvetica,
+ size => 12 }
+ );
+ print header(-cookie=>[$cookie1,$cookie2]);
+
+ # fetch existing cookies
+ %cookies = CGI::Cookie->fetch;
+ $id = $cookies{'ID'}->value;
+
+ # create cookies returned from an external source
+ %cookies = CGI::Cookie->parse($ENV{COOKIE});
+
+=head1 DESCRIPTION
+
+CGI::Cookie is an interface to HTTP/1.1 cookies, an
+innovation that allows Web servers to store persistent information on
+the browser's side of the connection. Although CGI::Cookie is
+intended to be used in conjunction with CGI.pm (and is in fact used by
+it internally), you can use this module independently.
+
+For full information on cookies see
+
+ http://tools.ietf.org/html/rfc2109
+ http://tools.ietf.org/html/rfc2965
+ http://tools.ietf.org/html/draft-ietf-httpstate-cookie
+
+=head1 USING CGI::Cookie
+
+CGI::Cookie is object oriented. Each cookie object has a name and a
+value. The name is any scalar value. The value is any scalar or
+array value (associative arrays are also allowed). Cookies also have
+several optional attributes, including:
+
+=over 4
+
+=item B<1. expiration date>
+
+The expiration date tells the browser how long to hang on to the
+cookie. If the cookie specifies an expiration date in the future, the
+browser will store the cookie information in a disk file and return it
+to the server every time the user reconnects (until the expiration
+date is reached). If the cookie species an expiration date in the
+past, the browser will remove the cookie from the disk file. If the
+expiration date is not specified, the cookie will persist only until
+the user quits the browser.
+
+=item B<2. domain>
+
+This is a partial or complete domain name for which the cookie is
+valid. The browser will return the cookie to any host that matches
+the partial domain name. For example, if you specify a domain name
+of ".capricorn.com", then the browser will return the cookie to
+Web servers running on any of the machines "www.capricorn.com",
+"ftp.capricorn.com", "feckless.capricorn.com", etc. Domain names
+must contain at least two periods to prevent attempts to match
+on top level domains like ".edu". If no domain is specified, then
+the browser will only return the cookie to servers on the host the
+cookie originated from.
+
+=item B<3. path>
+
+If you provide a cookie path attribute, the browser will check it
+against your script's URL before returning the cookie. For example,
+if you specify the path "/cgi-bin", then the cookie will be returned
+to each of the scripts "/cgi-bin/tally.pl", "/cgi-bin/order.pl", and
+"/cgi-bin/customer_service/complain.pl", but not to the script
+"/cgi-private/site_admin.pl". By default, the path is set to "/", so
+that all scripts at your site will receive the cookie.
+
+=item B<4. secure flag>
+
+If the "secure" attribute is set, the cookie will only be sent to your
+script if the CGI request is occurring on a secure channel, such as SSL.
+
+=item B<5. httponly flag>
+
+If the "httponly" attribute is set, the cookie will only be accessible
+through HTTP Requests. This cookie will be inaccessible via JavaScript
+(to prevent XSS attacks).
+
+This feature is only supported by recent browsers like Internet Explorer
+6 Service Pack 1, Firefox 3.0 and Opera 9.5 (and later of course).
+
+See these URLs for more information:
+
+ http://msdn.microsoft.com/en-us/library/ms533046.aspx
+ http://www.owasp.org/index.php/HTTPOnly#Browsers_Supporting_HTTPOnly
+
+=back
+
+=head2 Creating New Cookies
+
+ my $c = CGI::Cookie->new(-name => 'foo',
+ -value => 'bar',
+ -expires => '+3M',
+ -domain => '.capricorn.com',
+ -path => '/cgi-bin/database',
+ -secure => 1
+ );
+
+Create cookies from scratch with the B<new> method. The B<-name> and
+B<-value> parameters are required. The name must be a scalar value.
+The value can be a scalar, an array reference, or a hash reference.
+(At some point in the future cookies will support one of the Perl
+object serialization protocols for full generality).
+
+B<-expires> accepts any of the relative or absolute date formats
+recognized by CGI.pm, for example "+3M" for three months in the
+future. See CGI.pm's documentation for details.
+
+B<-max-age> accepts the same data formats as B<< -expires >>, but sets a
+relative value instead of an absolute like B<< -expires >>. This is intended to be
+more secure since a clock could be changed to fake an absolute time. In
+practice, as of 2011, C<< -max-age >> still does not enjoy the widespread support
+that C<< -expires >> has. You can set both, and browsers that support
+C<< -max-age >> should ignore the C<< Expires >> header. The drawback
+to this approach is the bit of bandwidth for sending an extra header on each cookie.
+
+B<-domain> points to a domain name or to a fully qualified host name.
+If not specified, the cookie will be returned only to the Web server
+that created it.
+
+B<-path> points to a partial URL on the current server. The cookie
+will be returned to all URLs beginning with the specified path. If
+not specified, it defaults to '/', which returns the cookie to all
+pages at your site.
+
+B<-secure> if set to a true value instructs the browser to return the
+cookie only when a cryptographic protocol is in use.
+
+B<-httponly> if set to a true value, the cookie will not be accessible
+via JavaScript.
+
+For compatibility with Apache::Cookie, you may optionally pass in
+a mod_perl request object as the first argument to C<new()>. It will
+simply be ignored:
+
+ my $c = CGI::Cookie->new($r,
+ -name => 'foo',
+ -value => ['bar','baz']);
+
+=head2 Sending the Cookie to the Browser
+
+The simplest way to send a cookie to the browser is by calling the bake()
+method:
+
+ $c->bake;
+
+This will print the Set-Cookie HTTP header to STDOUT using CGI.pm. CGI.pm
+will be loaded for this purpose if it is not already. Otherwise CGI.pm is not
+required or used by this module.
+
+Under mod_perl, pass in an Apache request object:
+
+ $c->bake($r);
+
+If you want to set the cookie yourself, Within a CGI script you can send
+a cookie to the browser by creating one or more Set-Cookie: fields in the
+HTTP header. Here is a typical sequence:
+
+ my $c = CGI::Cookie->new(-name => 'foo',
+ -value => ['bar','baz'],
+ -expires => '+3M');
+
+ print "Set-Cookie: $c\n";
+ print "Content-Type: text/html\n\n";
+
+To send more than one cookie, create several Set-Cookie: fields.
+
+If you are using CGI.pm, you send cookies by providing a -cookie
+argument to the header() method:
+
+ print header(-cookie=>$c);
+
+Mod_perl users can set cookies using the request object's header_out()
+method:
+
+ $r->headers_out->set('Set-Cookie' => $c);
+
+Internally, Cookie overloads the "" operator to call its as_string()
+method when incorporated into the HTTP header. as_string() turns the
+Cookie's internal representation into an RFC-compliant text
+representation. You may call as_string() yourself if you prefer:
+
+ print "Set-Cookie: ",$c->as_string,"\n";
+
+=head2 Recovering Previous Cookies
+
+ %cookies = CGI::Cookie->fetch;
+
+B<fetch> returns an associative array consisting of all cookies
+returned by the browser. The keys of the array are the cookie names. You
+can iterate through the cookies this way:
+
+ %cookies = CGI::Cookie->fetch;
+ for (keys %cookies) {
+ do_something($cookies{$_});
+ }
+
+In a scalar context, fetch() returns a hash reference, which may be more
+efficient if you are manipulating multiple cookies.
+
+CGI.pm uses the URL escaping methods to save and restore reserved characters
+in its cookies. If you are trying to retrieve a cookie set by a foreign server,
+this escaping method may trip you up. Use raw_fetch() instead, which has the
+same semantics as fetch(), but performs no unescaping.
+
+You may also retrieve cookies that were stored in some external
+form using the parse() class method:
+
+ $COOKIES = `cat /usr/tmp/Cookie_stash`;
+ %cookies = CGI::Cookie->parse($COOKIES);
+
+If you are in a mod_perl environment, you can save some overhead by
+passing the request object to fetch() like this:
+
+ CGI::Cookie->fetch($r);
+
+If the value passed to parse() is undefined, an empty array will returned in list
+contact, and an empty hashref will be returned in scalar context.
+
+=head2 Manipulating Cookies
+
+Cookie objects have a series of accessor methods to get and set cookie
+attributes. Each accessor has a similar syntax. Called without
+arguments, the accessor returns the current value of the attribute.
+Called with an argument, the accessor changes the attribute and
+returns its new value.
+
+=over 4
+
+=item B<name()>
+
+Get or set the cookie's name. Example:
+
+ $name = $c->name;
+ $new_name = $c->name('fred');
+
+=item B<value()>
+
+Get or set the cookie's value. Example:
+
+ $value = $c->value;
+ @new_value = $c->value(['a','b','c','d']);
+
+B<value()> is context sensitive. In a list context it will return
+the current value of the cookie as an array. In a scalar context it
+will return the B<first> value of a multivalued cookie.
+
+=item B<domain()>
+
+Get or set the cookie's domain.
+
+=item B<path()>
+
+Get or set the cookie's path.
+
+=item B<expires()>
+
+Get or set the cookie's expiration time.
+
+=back
+
+
+=head1 AUTHOR INFORMATION
+
+Copyright 1997-1998, Lincoln D. Stein. All rights reserved.
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+Address bug reports and comments to: lstein@cshl.org
+
+=head1 BUGS
+
+This section intentionally left blank.
+
+=head1 SEE ALSO
+
+L<CGI::Carp>, L<CGI>
+
+L<RFC 2109|http://www.ietf.org/rfc/rfc2109.txt>, L<RFC 2695|http://www.ietf.org/rfc/rfc2965.txt>
+
+=cut
diff --git a/lib/CGI/Fast.pm b/lib/CGI/Fast.pm
new file mode 100644
index 000000000..e31dac3f5
--- /dev/null
+++ b/lib/CGI/Fast.pm
@@ -0,0 +1,224 @@
+package CGI::Fast;
+use strict;
+
+# A way to say "use warnings" that's compatible with even older perls.
+# making it local will not affect the code that loads this module
+# and since we're not in a BLOCK, warnings are enabled until the EOF
+local $^W = 1;
+
+# See the bottom of this file for the POD documentation. Search for the
+# string '=head'.
+
+# You can run this file through either pod2man or pod2html to produce pretty
+# documentation in manual or html file format (these utilities are part of the
+# Perl 5 distribution).
+
+# Copyright 1995,1996, Lincoln D. Stein. All rights reserved.
+# It may be used and modified freely, but I do request that this copyright
+# notice remain attached to the file. You may modify this module as you
+# wish, but if you redistribute a modified version, please attach a note
+# listing the modifications you have made.
+
+$CGI::Fast::VERSION='1.08';
+
+use CGI;
+use FCGI;
+# use vars works like "our", but is compatible with older Perls.
+use vars qw(
+ @ISA
+ $ignore
+);
+@ISA = ('CGI');
+
+# workaround for known bug in libfcgi
+while (($ignore) = each %ENV) { }
+
+# override the initialization behavior so that
+# state is NOT maintained between invocations
+sub save_request {
+ # no-op
+}
+
+# If ENV{FCGI_SOCKET_PATH} is specified, we maintain a FCGI Request handle
+# in this package variable.
+use vars qw($Ext_Request);
+BEGIN {
+ # If ENV{FCGI_SOCKET_PATH} is given, explicitly open the socket,
+ # and keep the request handle around from which to call Accept().
+ if ($ENV{FCGI_SOCKET_PATH}) {
+ my $path = $ENV{FCGI_SOCKET_PATH};
+ my $backlog = $ENV{FCGI_LISTEN_QUEUE} || 100;
+ my $socket = FCGI::OpenSocket( $path, $backlog );
+ $Ext_Request = FCGI::Request( \*STDIN, \*STDOUT, \*STDERR,
+ \%ENV, $socket, 1 );
+ }
+}
+
+# New is slightly different in that it calls FCGI's
+# accept() method.
+sub new {
+ my ($self, $initializer, @param) = @_;
+ unless (defined $initializer) {
+ if ($Ext_Request) {
+ return undef unless $Ext_Request->Accept() >= 0;
+ } else {
+ return undef unless FCGI::accept() >= 0;
+ }
+ }
+ CGI->_reset_globals;
+ $self->_setup_symbols(@CGI::SAVED_SYMBOLS) if @CGI::SAVED_SYMBOLS;
+ return $CGI::Q = $self->SUPER::new($initializer, @param);
+}
+
+1;
+
+=head1 NAME
+
+CGI::Fast - CGI Interface for Fast CGI
+
+=head1 SYNOPSIS
+
+ use CGI::Fast qw(:standard);
+ $COUNTER = 0;
+ while (new CGI::Fast) {
+ print header;
+ print start_html("Fast CGI Rocks");
+ print
+ h1("Fast CGI Rocks"),
+ "Invocation number ",b($COUNTER++),
+ " PID ",b($$),".",
+ hr;
+ print end_html;
+ }
+
+=head1 DESCRIPTION
+
+CGI::Fast is a subclass of the CGI object created by CGI.pm. It is
+specialized to work well FCGI module, which greatly speeds up CGI
+scripts by turning them into persistently running server processes.
+Scripts that perform time-consuming initialization processes, such as
+loading large modules or opening persistent database connections, will
+see large performance improvements.
+
+=head1 OTHER PIECES OF THE PUZZLE
+
+In order to use CGI::Fast you'll need the FCGI module. See
+http://www.cpan.org/ for details.
+
+=head1 WRITING FASTCGI PERL SCRIPTS
+
+FastCGI scripts are persistent: one or more copies of the script
+are started up when the server initializes, and stay around until
+the server exits or they die a natural death. After performing
+whatever one-time initialization it needs, the script enters a
+loop waiting for incoming connections, processing the request, and
+waiting some more.
+
+A typical FastCGI script will look like this:
+
+ #!/usr/bin/perl
+ use CGI::Fast;
+ &do_some_initialization();
+ while ($q = new CGI::Fast) {
+ &process_request($q);
+ }
+
+Each time there's a new request, CGI::Fast returns a
+CGI object to your loop. The rest of the time your script
+waits in the call to new(). When the server requests that
+your script be terminated, new() will return undef. You can
+of course exit earlier if you choose. A new version of the
+script will be respawned to take its place (this may be
+necessary in order to avoid Perl memory leaks in long-running
+scripts).
+
+CGI.pm's default CGI object mode also works. Just modify the loop
+this way:
+
+ while (new CGI::Fast) {
+ &process_request;
+ }
+
+Calls to header(), start_form(), etc. will all operate on the
+current request.
+
+=head1 INSTALLING FASTCGI SCRIPTS
+
+See the FastCGI developer's kit documentation for full details. On
+the Apache server, the following line must be added to srm.conf:
+
+ AddType application/x-httpd-fcgi .fcgi
+
+FastCGI scripts must end in the extension .fcgi. For each script you
+install, you must add something like the following to srm.conf:
+
+ FastCgiServer /usr/etc/httpd/fcgi-bin/file_upload.fcgi -processes 2
+
+This instructs Apache to launch two copies of file_upload.fcgi at
+startup time.
+
+=head1 USING FASTCGI SCRIPTS AS CGI SCRIPTS
+
+Any script that works correctly as a FastCGI script will also work
+correctly when installed as a vanilla CGI script. However it will
+not see any performance benefit.
+
+=head1 EXTERNAL FASTCGI SERVER INVOCATION
+
+FastCGI supports a TCP/IP transport mechanism which allows FastCGI scripts to run
+external to the webserver, perhaps on a remote machine. To configure the
+webserver to connect to an external FastCGI server, you would add the following
+to your srm.conf:
+
+ FastCgiExternalServer /usr/etc/httpd/fcgi-bin/file_upload.fcgi -host sputnik:8888
+
+Two environment variables affect how the C<CGI::Fast> object is created,
+allowing C<CGI::Fast> to be used as an external FastCGI server. (See C<FCGI>
+documentation for C<FCGI::OpenSocket> for more information.)
+
+=over
+
+=item FCGI_SOCKET_PATH
+
+The address (TCP/IP) or path (UNIX Domain) of the socket the external FastCGI
+script to which bind an listen for incoming connections from the web server.
+
+=item FCGI_LISTEN_QUEUE
+
+Maximum length of the queue of pending connections.
+
+=back
+
+For example:
+
+ #!/usr/local/bin/perl # must be a FastCGI version of perl!
+ use CGI::Fast;
+ &do_some_initialization();
+ $ENV{FCGI_SOCKET_PATH} = "sputnik:8888";
+ $ENV{FCGI_LISTEN_QUEUE} = 100;
+ while ($q = new CGI::Fast) {
+ &process_request($q);
+ }
+
+=head1 CAVEATS
+
+I haven't tested this very much.
+
+=head1 AUTHOR INFORMATION
+
+Copyright 1996-1998, Lincoln D. Stein. All rights reserved.
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+Address bug reports and comments to: lstein@cshl.org
+
+=head1 BUGS
+
+This section intentionally left blank.
+
+=head1 SEE ALSO
+
+L<CGI::Carp>, L<CGI>
+
+=cut
diff --git a/lib/CGI/Pretty.pm b/lib/CGI/Pretty.pm
new file mode 100644
index 000000000..869fe0cdf
--- /dev/null
+++ b/lib/CGI/Pretty.pm
@@ -0,0 +1,312 @@
+package CGI::Pretty;
+
+# See the bottom of this file for the POD documentation. Search for the
+# string '=head'.
+
+# You can run this file through either pod2man or pod2html to produce pretty
+# documentation in manual or html file format (these utilities are part of the
+# Perl 5 distribution).
+
+use strict;
+use CGI ();
+
+$CGI::Pretty::VERSION = '3.46';
+$CGI::DefaultClass = __PACKAGE__;
+$CGI::Pretty::AutoloadClass = 'CGI';
+@CGI::Pretty::ISA = qw( CGI );
+
+initialize_globals();
+
+sub _prettyPrint {
+ my $input = shift;
+ return if !$$input;
+ return if !$CGI::Pretty::LINEBREAK || !$CGI::Pretty::INDENT;
+
+# print STDERR "'", $$input, "'\n";
+
+ foreach my $i ( @CGI::Pretty::AS_IS ) {
+ if ( $$input =~ m{</$i>}si ) {
+ my ( $a, $b, $c ) = $$input =~ m{(.*)(<$i[\s/>].*?</$i>)(.*)}si;
+ next if !$b;
+ $a ||= "";
+ $c ||= "";
+
+ _prettyPrint( \$a ) if $a;
+ _prettyPrint( \$c ) if $c;
+
+ $b ||= "";
+ $$input = "$a$b$c";
+ return;
+ }
+ }
+ $$input =~ s/$CGI::Pretty::LINEBREAK/$CGI::Pretty::LINEBREAK$CGI::Pretty::INDENT/g;
+}
+
+sub comment {
+ my($self,@p) = CGI::self_or_CGI(@_);
+
+ my $s = "@p";
+ $s =~ s/$CGI::Pretty::LINEBREAK/$CGI::Pretty::LINEBREAK$CGI::Pretty::INDENT/g if $CGI::Pretty::LINEBREAK;
+
+ return $self->SUPER::comment( "$CGI::Pretty::LINEBREAK$CGI::Pretty::INDENT$s$CGI::Pretty::LINEBREAK" ) . $CGI::Pretty::LINEBREAK;
+}
+
+sub _make_tag_func {
+ my ($self,$tagname) = @_;
+
+ # As Lincoln as noted, the last else clause is VERY hairy, and it
+ # took me a while to figure out what I was trying to do.
+ # What it does is look for tags that shouldn't be indented (e.g. PRE)
+ # and makes sure that when we nest tags, those tags don't get
+ # indented.
+ # For an example, try print td( pre( "hello\nworld" ) );
+ # If we didn't care about stuff like that, the code would be
+ # MUCH simpler. BTW: I won't claim to be a regular expression
+ # guru, so if anybody wants to contribute something that would
+ # be quicker, easier to read, etc, I would be more than
+ # willing to put it in - Brian
+
+ my $func = qq"
+ sub $tagname {";
+
+ $func .= q'
+ shift if $_[0] &&
+ (ref($_[0]) &&
+ (substr(ref($_[0]),0,3) eq "CGI" ||
+ UNIVERSAL::isa($_[0],"CGI")));
+ my($attr) = "";
+ if (ref($_[0]) && ref($_[0]) eq "HASH") {
+ my(@attr) = make_attributes(shift()||undef,1);
+ $attr = " @attr" if @attr;
+ }';
+
+ if ($tagname=~/start_(\w+)/i) {
+ $func .= qq!
+ return "<\L$1\E\$attr>\$CGI::Pretty::LINEBREAK";} !;
+ } elsif ($tagname=~/end_(\w+)/i) {
+ $func .= qq!
+ return "<\L/$1\E>\$CGI::Pretty::LINEBREAK"; } !;
+ } else {
+ $func .= qq#
+ return ( \$CGI::XHTML ? "<\L$tagname\E\$attr />" : "<\L$tagname\E\$attr>" ) .
+ \$CGI::Pretty::LINEBREAK unless \@_;
+ my(\$tag,\$untag) = ("<\L$tagname\E\$attr>","</\L$tagname>\E");
+
+ my \%ASIS = map { lc("\$_") => 1 } \@CGI::Pretty::AS_IS;
+ my \@args;
+ if ( \$CGI::Pretty::LINEBREAK || \$CGI::Pretty::INDENT ) {
+ if(ref(\$_[0]) eq 'ARRAY') {
+ \@args = \@{\$_[0]}
+ } else {
+ foreach (\@_) {
+ \$args[0] .= \$_;
+ \$args[0] .= \$CGI::Pretty::LINEBREAK if \$args[0] !~ /\$CGI::Pretty::LINEBREAK\$/ && 0;
+ chomp \$args[0] if exists \$ASIS{ "\L$tagname\E" };
+
+ \$args[0] .= \$" if \$args[0] !~ /\$CGI::Pretty::LINEBREAK\$/ && 1;
+ }
+ chop \$args[0] unless \$" eq "";
+ }
+ }
+ else {
+ \@args = ref(\$_[0]) eq 'ARRAY' ? \@{\$_[0]} : "\@_";
+ }
+
+ my \@result;
+ if ( exists \$ASIS{ "\L$tagname\E" } ) {
+ \@result = map { "\$tag\$_\$untag" } \@args;
+ }
+ else {
+ \@result = map {
+ chomp;
+ my \$tmp = \$_;
+ CGI::Pretty::_prettyPrint( \\\$tmp );
+ \$tag . \$CGI::Pretty::LINEBREAK .
+ \$CGI::Pretty::INDENT . \$tmp . \$CGI::Pretty::LINEBREAK .
+ \$untag . \$CGI::Pretty::LINEBREAK
+ } \@args;
+ }
+ if (\$CGI::Pretty::LINEBREAK || \$CGI::Pretty::INDENT) {
+ return join ("", \@result);
+ } else {
+ return "\@result";
+ }
+ }#;
+ }
+
+ return $func;
+}
+
+sub start_html {
+ return CGI::start_html( @_ ) . $CGI::Pretty::LINEBREAK;
+}
+
+sub end_html {
+ return CGI::end_html( @_ ) . $CGI::Pretty::LINEBREAK;
+}
+
+sub new {
+ my $class = shift;
+ my $this = $class->SUPER::new( @_ );
+
+ if ($CGI::MOD_PERL) {
+ if ($CGI::MOD_PERL == 1) {
+ my $r = Apache->request;
+ $r->register_cleanup(\&CGI::Pretty::_reset_globals);
+ }
+ else {
+ my $r = Apache2::RequestUtil->request;
+ $r->pool->cleanup_register(\&CGI::Pretty::_reset_globals);
+ }
+ }
+ $class->_reset_globals if $CGI::PERLEX;
+
+ return bless $this, $class;
+}
+
+sub initialize_globals {
+ # This is the string used for indentation of tags
+ $CGI::Pretty::INDENT = "\t";
+
+ # This is the string used for seperation between tags
+ $CGI::Pretty::LINEBREAK = $/;
+
+ # These tags are not prettify'd.
+ # When this list is updated, also update the docs.
+ @CGI::Pretty::AS_IS = qw( a pre code script textarea td );
+
+ 1;
+}
+sub _reset_globals { initialize_globals(); }
+
+# ugly, but quick fix
+sub import {
+ my $self = shift;
+ no strict 'refs';
+ ${ "$self\::AutoloadClass" } = 'CGI';
+
+ # This causes modules to clash.
+ undef %CGI::EXPORT;
+ undef %CGI::EXPORT;
+
+ $self->_setup_symbols(@_);
+ my ($callpack, $callfile, $callline) = caller;
+
+ # To allow overriding, search through the packages
+ # Till we find one in which the correct subroutine is defined.
+ my @packages = ($self,@{"$self\:\:ISA"});
+ foreach my $sym (keys %CGI::EXPORT) {
+ my $pck;
+ my $def = ${"$self\:\:AutoloadClass"} || $CGI::DefaultClass;
+ foreach $pck (@packages) {
+ if (defined(&{"$pck\:\:$sym"})) {
+ $def = $pck;
+ last;
+ }
+ }
+ *{"${callpack}::$sym"} = \&{"$def\:\:$sym"};
+ }
+}
+
+1;
+
+=head1 NAME
+
+CGI::Pretty - module to produce nicely formatted HTML code
+
+=head1 SYNOPSIS
+
+ use CGI::Pretty qw( :html3 );
+
+ # Print a table with a single data element
+ print table( TR( td( "foo" ) ) );
+
+=head1 DESCRIPTION
+
+CGI::Pretty is a module that derives from CGI. It's sole function is to
+allow users of CGI to output nicely formatted HTML code.
+
+When using the CGI module, the following code:
+ print table( TR( td( "foo" ) ) );
+
+produces the following output:
+ <TABLE><TR><TD>foo</TD></TR></TABLE>
+
+If a user were to create a table consisting of many rows and many columns,
+the resultant HTML code would be quite difficult to read since it has no
+carriage returns or indentation.
+
+CGI::Pretty fixes this problem. What it does is add a carriage
+return and indentation to the HTML code so that one can easily read
+it.
+
+ print table( TR( td( "foo" ) ) );
+
+now produces the following output:
+ <TABLE>
+ <TR>
+ <TD>foo</TD>
+ </TR>
+ </TABLE>
+
+=head2 Recommendation for when to use CGI::Pretty
+
+CGI::Pretty is far slower than using CGI.pm directly. A benchmark showed that
+it could be about 10 times slower. Adding newlines and spaces may alter the
+rendered appearance of HTML. Also, the extra newlines and spaces also make the
+file size larger, making the files take longer to download.
+
+With all those considerations, it is recommended that CGI::Pretty be used
+primarily for debugging.
+
+=head2 Tags that won't be formatted
+
+The following tags are not formatted: <a>, <pre>, <code>, <script>, <textarea>, and <td>.
+If these tags were formatted, the
+user would see the extra indentation on the web browser causing the page to
+look different than what would be expected. If you wish to add more tags to
+the list of tags that are not to be touched, push them onto the C<@AS_IS> array:
+
+ push @CGI::Pretty::AS_IS,qw(XMP);
+
+=head2 Customizing the Indenting
+
+If you wish to have your own personal style of indenting, you can change the
+C<$INDENT> variable:
+
+ $CGI::Pretty::INDENT = "\t\t";
+
+would cause the indents to be two tabs.
+
+Similarly, if you wish to have more space between lines, you may change the
+C<$LINEBREAK> variable:
+
+ $CGI::Pretty::LINEBREAK = "\n\n";
+
+would create two carriage returns between lines.
+
+If you decide you want to use the regular CGI indenting, you can easily do
+the following:
+
+ $CGI::Pretty::INDENT = $CGI::Pretty::LINEBREAK = "";
+
+=head1 AUTHOR
+
+Brian Paulsen <Brian@ThePaulsens.com>, with minor modifications by
+Lincoln Stein <lstein@cshl.org> for incorporation into the CGI.pm
+distribution.
+
+Copyright 1999, Brian Paulsen. All rights reserved.
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+Bug reports and comments to Brian@ThePaulsens.com. You can also write
+to lstein@cshl.org, but this code looks pretty hairy to me and I'm not
+sure I understand it!
+
+=head1 SEE ALSO
+
+L<CGI>
+
+=cut
diff --git a/lib/CGI/Push.pm b/lib/CGI/Push.pm
new file mode 100644
index 000000000..2af7d794d
--- /dev/null
+++ b/lib/CGI/Push.pm
@@ -0,0 +1,325 @@
+package CGI::Push;
+
+# See the bottom of this file for the POD documentation. Search for the
+# string '=head'.
+
+# You can run this file through either pod2man or pod2html to produce pretty
+# documentation in manual or html file format (these utilities are part of the
+# Perl 5 distribution).
+
+# Copyright 1995-2000, Lincoln D. Stein. All rights reserved.
+# It may be used and modified freely, but I do request that this copyright
+# notice remain attached to the file. You may modify this module as you
+# wish, but if you redistribute a modified version, please attach a note
+# listing the modifications you have made.
+
+# The most recent version and complete docs are available at:
+# http://stein.cshl.org/WWW/software/CGI/
+
+$CGI::Push::VERSION='1.05';
+use CGI;
+use CGI::Util 'rearrange';
+@ISA = ('CGI');
+
+$CGI::DefaultClass = 'CGI::Push';
+$CGI::Push::AutoloadClass = 'CGI';
+
+# add do_push() and push_delay() to exported tags
+push(@{$CGI::EXPORT_TAGS{':standard'}},'do_push','push_delay');
+
+sub do_push {
+ my ($self,@p) = CGI::self_or_default(@_);
+
+ # unbuffer output
+ $| = 1;
+ srand;
+ my ($random) = sprintf("%08.0f",rand()*1E8);
+ my ($boundary) = "----=_NeXtPaRt$random";
+
+ my (@header);
+ my ($type,$callback,$delay,$last_page,$cookie,$target,$expires,$nph,@other) = rearrange([TYPE,NEXT_PAGE,DELAY,LAST_PAGE,[COOKIE,COOKIES],TARGET,EXPIRES,NPH],@p);
+ $type = 'text/html' unless $type;
+ $callback = \&simple_counter unless $callback && ref($callback) eq 'CODE';
+ $delay = 1 unless defined($delay);
+ $self->push_delay($delay);
+ $nph = 1 unless defined($nph);
+
+ my(@o);
+ foreach (@other) { push(@o,split("=")); }
+ push(@o,'-Target'=>$target) if defined($target);
+ push(@o,'-Cookie'=>$cookie) if defined($cookie);
+ push(@o,'-Type'=>"multipart/x-mixed-replace;boundary=\"$boundary\"");
+ push(@o,'-Server'=>"CGI.pm Push Module") if $nph;
+ push(@o,'-Status'=>'200 OK');
+ push(@o,'-nph'=>1) if $nph;
+ print $self->header(@o);
+
+ $boundary = "$CGI::CRLF--$boundary";
+
+ print "WARNING: YOUR BROWSER DOESN'T SUPPORT THIS SERVER-PUSH TECHNOLOGY.${boundary}$CGI::CRLF";
+
+ my (@contents) = &$callback($self,++$COUNTER);
+
+ # now we enter a little loop
+ while (1) {
+ print "Content-type: ${type}$CGI::CRLF$CGI::CRLF" unless $type =~ /^dynamic|heterogeneous$/i;
+ print @contents;
+ @contents = &$callback($self,++$COUNTER);
+ if ((@contents) && defined($contents[0])) {
+ print "${boundary}$CGI::CRLF";
+ do_sleep($self->push_delay()) if $self->push_delay();
+ } else {
+ if ($last_page && ref($last_page) eq 'CODE') {
+ print "${boundary}$CGI::CRLF";
+ do_sleep($self->push_delay()) if $self->push_delay();
+ print "Content-type: ${type}$CGI::CRLF$CGI::CRLF" unless $type =~ /^dynamic|heterogeneous$/i;
+ print &$last_page($self,$COUNTER);
+ }
+ print "${boundary}--$CGI::CRLF";
+ last;
+ }
+ }
+ print "WARNING: YOUR BROWSER DOESN'T SUPPORT THIS SERVER-PUSH TECHNOLOGY.$CGI::CRLF";
+}
+
+sub simple_counter {
+ my ($self,$count) = @_;
+ return $self->start_html("CGI::Push Default Counter"),
+ $self->h1("CGI::Push Default Counter"),
+ "This page has been updated ",$self->strong($count)," times.",
+ $self->hr(),
+ $self->a({'-href'=>'http://www.genome.wi.mit.edu/ftp/pub/software/WWW/cgi_docs.html'},'CGI.pm home page'),
+ $self->end_html;
+}
+
+sub do_sleep {
+ my $delay = shift;
+ if ( ($delay >= 1) && ($delay!~/\./) ){
+ sleep($delay);
+ } else {
+ select(undef,undef,undef,$delay);
+ }
+}
+
+sub push_delay {
+ my ($self,$delay) = CGI::self_or_default(@_);
+ return defined($delay) ? $self->{'.delay'} =
+ $delay : $self->{'.delay'};
+}
+
+1;
+
+=head1 NAME
+
+CGI::Push - Simple Interface to Server Push
+
+=head1 SYNOPSIS
+
+ use CGI::Push qw(:standard);
+
+ do_push(-next_page=>\&next_page,
+ -last_page=>\&last_page,
+ -delay=>0.5);
+
+ sub next_page {
+ my($q,$counter) = @_;
+ return undef if $counter >= 10;
+ return start_html('Test'),
+ h1('Visible'),"\n",
+ "This page has been called ", strong($counter)," times",
+ end_html();
+ }
+
+ sub last_page {
+ my($q,$counter) = @_;
+ return start_html('Done'),
+ h1('Finished'),
+ strong($counter - 1),' iterations.',
+ end_html;
+ }
+
+=head1 DESCRIPTION
+
+CGI::Push is a subclass of the CGI object created by CGI.pm. It is
+specialized for server push operations, which allow you to create
+animated pages whose content changes at regular intervals.
+
+You provide CGI::Push with a pointer to a subroutine that will draw
+one page. Every time your subroutine is called, it generates a new
+page. The contents of the page will be transmitted to the browser
+in such a way that it will replace what was there beforehand. The
+technique will work with HTML pages as well as with graphics files,
+allowing you to create animated GIFs.
+
+Only Netscape Navigator supports server push. Internet Explorer
+browsers do not.
+
+=head1 USING CGI::Push
+
+CGI::Push adds one new method to the standard CGI suite, do_push().
+When you call this method, you pass it a reference to a subroutine
+that is responsible for drawing each new page, an interval delay, and
+an optional subroutine for drawing the last page. Other optional
+parameters include most of those recognized by the CGI header()
+method.
+
+You may call do_push() in the object oriented manner or not, as you
+prefer:
+
+ use CGI::Push;
+ $q = new CGI::Push;
+ $q->do_push(-next_page=>\&draw_a_page);
+
+ -or-
+
+ use CGI::Push qw(:standard);
+ do_push(-next_page=>\&draw_a_page);
+
+Parameters are as follows:
+
+=over 4
+
+=item -next_page
+
+ do_push(-next_page=>\&my_draw_routine);
+
+This required parameter points to a reference to a subroutine responsible for
+drawing each new page. The subroutine should expect two parameters
+consisting of the CGI object and a counter indicating the number
+of times the subroutine has been called. It should return the
+contents of the page as an B<array> of one or more items to print.
+It can return a false value (or an empty array) in order to abort the
+redrawing loop and print out the final page (if any)
+
+ sub my_draw_routine {
+ my($q,$counter) = @_;
+ return undef if $counter > 100;
+ return start_html('testing'),
+ h1('testing'),
+ "This page called $counter times";
+ }
+
+You are of course free to refer to create and use global variables
+within your draw routine in order to achieve special effects.
+
+=item -last_page
+
+This optional parameter points to a reference to the subroutine
+responsible for drawing the last page of the series. It is called
+after the -next_page routine returns a false value. The subroutine
+itself should have exactly the same calling conventions as the
+-next_page routine.
+
+=item -type
+
+This optional parameter indicates the content type of each page. It
+defaults to "text/html". Normally the module assumes that each page
+is of a homogeneous MIME type. However if you provide either of the
+magic values "heterogeneous" or "dynamic" (the latter provided for the
+convenience of those who hate long parameter names), you can specify
+the MIME type -- and other header fields -- on a per-page basis. See
+"heterogeneous pages" for more details.
+
+=item -delay
+
+This indicates the delay, in seconds, between frames. Smaller delays
+refresh the page faster. Fractional values are allowed.
+
+B<If not specified, -delay will default to 1 second>
+
+=item -cookie, -target, -expires, -nph
+
+These have the same meaning as the like-named parameters in
+CGI::header().
+
+If not specified, -nph will default to 1 (as needed for many servers, see below).
+
+=back
+
+=head2 Heterogeneous Pages
+
+Ordinarily all pages displayed by CGI::Push share a common MIME type.
+However by providing a value of "heterogeneous" or "dynamic" in the
+do_push() -type parameter, you can specify the MIME type of each page
+on a case-by-case basis.
+
+If you use this option, you will be responsible for producing the
+HTTP header for each page. Simply modify your draw routine to
+look like this:
+
+ sub my_draw_routine {
+ my($q,$counter) = @_;
+ return header('text/html'), # note we're producing the header here
+ start_html('testing'),
+ h1('testing'),
+ "This page called $counter times";
+ }
+
+You can add any header fields that you like, but some (cookies and
+status fields included) may not be interpreted by the browser. One
+interesting effect is to display a series of pages, then, after the
+last page, to redirect the browser to a new URL. Because redirect()
+does b<not> work, the easiest way is with a -refresh header field,
+as shown below:
+
+ sub my_draw_routine {
+ my($q,$counter) = @_;
+ return undef if $counter > 10;
+ return header('text/html'), # note we're producing the header here
+ start_html('testing'),
+ h1('testing'),
+ "This page called $counter times";
+ }
+
+ sub my_last_page {
+ return header(-refresh=>'5; URL=http://somewhere.else/finished.html',
+ -type=>'text/html'),
+ start_html('Moved'),
+ h1('This is the last page'),
+ 'Goodbye!'
+ hr,
+ end_html;
+ }
+
+=head2 Changing the Page Delay on the Fly
+
+If you would like to control the delay between pages on a page-by-page
+basis, call push_delay() from within your draw routine. push_delay()
+takes a single numeric argument representing the number of seconds you
+wish to delay after the current page is displayed and before
+displaying the next one. The delay may be fractional. Without
+parameters, push_delay() just returns the current delay.
+
+=head1 INSTALLING CGI::Push SCRIPTS
+
+Server push scripts must be installed as no-parsed-header (NPH)
+scripts in order to work correctly on many servers. On Unix systems,
+this is most often accomplished by prefixing the script's name with "nph-".
+Recognition of NPH scripts happens automatically with WebSTAR and
+Microsoft IIS. Users of other servers should see their documentation
+for help.
+
+Apache web server from version 1.3b2 on does not need server
+push scripts installed as NPH scripts: the -nph parameter to do_push()
+may be set to a false value to disable the extra headers needed by an
+NPH script.
+
+=head1 AUTHOR INFORMATION
+
+Copyright 1995-1998, Lincoln D. Stein. All rights reserved.
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+Address bug reports and comments to: lstein@cshl.org
+
+=head1 BUGS
+
+This section intentionally left blank.
+
+=head1 SEE ALSO
+
+L<CGI::Carp>, L<CGI>
+
+=cut
+
diff --git a/lib/CGI/Switch.pm b/lib/CGI/Switch.pm
new file mode 100644
index 000000000..a311080e4
--- /dev/null
+++ b/lib/CGI/Switch.pm
@@ -0,0 +1,28 @@
+package CGI::Switch;
+use CGI;
+
+$VERSION = '1.01';
+
+1;
+
+__END__
+
+=head1 NAME
+
+CGI::Switch - Backward compatibility module for defunct CGI::Switch
+
+=head1 SYNOPSIS
+
+Do not use this module. It is deprecated.
+
+=head1 ABSTRACT
+
+=head1 DESCRIPTION
+
+=head1 AUTHOR INFORMATION
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+=cut
diff --git a/lib/CGI/Util.pm b/lib/CGI/Util.pm
new file mode 100644
index 000000000..ef95c9f01
--- /dev/null
+++ b/lib/CGI/Util.pm
@@ -0,0 +1,395 @@
+package CGI::Util;
+
+use strict;
+use vars qw($VERSION @EXPORT_OK @ISA @A2E @E2A);
+require Exporter;
+@ISA = qw(Exporter);
+@EXPORT_OK = qw(rearrange rearrange_header make_attributes unescape escape
+ expires ebcdic2ascii ascii2ebcdic);
+
+$VERSION = '3.51';
+
+use constant EBCDIC => "\t" ne "\011";
+
+# (ord('^') == 95) for codepage 1047 as on os390, vmesa
+@A2E = (
+ 0, 1, 2, 3, 55, 45, 46, 47, 22, 5, 21, 11, 12, 13, 14, 15,
+ 16, 17, 18, 19, 60, 61, 50, 38, 24, 25, 63, 39, 28, 29, 30, 31,
+ 64, 90,127,123, 91,108, 80,125, 77, 93, 92, 78,107, 96, 75, 97,
+ 240,241,242,243,244,245,246,247,248,249,122, 94, 76,126,110,111,
+ 124,193,194,195,196,197,198,199,200,201,209,210,211,212,213,214,
+ 215,216,217,226,227,228,229,230,231,232,233,173,224,189, 95,109,
+ 121,129,130,131,132,133,134,135,136,137,145,146,147,148,149,150,
+ 151,152,153,162,163,164,165,166,167,168,169,192, 79,208,161, 7,
+ 32, 33, 34, 35, 36, 37, 6, 23, 40, 41, 42, 43, 44, 9, 10, 27,
+ 48, 49, 26, 51, 52, 53, 54, 8, 56, 57, 58, 59, 4, 20, 62,255,
+ 65,170, 74,177,159,178,106,181,187,180,154,138,176,202,175,188,
+ 144,143,234,250,190,160,182,179,157,218,155,139,183,184,185,171,
+ 100,101, 98,102, 99,103,158,104,116,113,114,115,120,117,118,119,
+ 172,105,237,238,235,239,236,191,128,253,254,251,252,186,174, 89,
+ 68, 69, 66, 70, 67, 71,156, 72, 84, 81, 82, 83, 88, 85, 86, 87,
+ 140, 73,205,206,203,207,204,225,112,221,222,219,220,141,142,223
+ );
+@E2A = (
+ 0, 1, 2, 3,156, 9,134,127,151,141,142, 11, 12, 13, 14, 15,
+ 16, 17, 18, 19,157, 10, 8,135, 24, 25,146,143, 28, 29, 30, 31,
+ 128,129,130,131,132,133, 23, 27,136,137,138,139,140, 5, 6, 7,
+ 144,145, 22,147,148,149,150, 4,152,153,154,155, 20, 21,158, 26,
+ 32,160,226,228,224,225,227,229,231,241,162, 46, 60, 40, 43,124,
+ 38,233,234,235,232,237,238,239,236,223, 33, 36, 42, 41, 59, 94,
+ 45, 47,194,196,192,193,195,197,199,209,166, 44, 37, 95, 62, 63,
+ 248,201,202,203,200,205,206,207,204, 96, 58, 35, 64, 39, 61, 34,
+ 216, 97, 98, 99,100,101,102,103,104,105,171,187,240,253,254,177,
+ 176,106,107,108,109,110,111,112,113,114,170,186,230,184,198,164,
+ 181,126,115,116,117,118,119,120,121,122,161,191,208, 91,222,174,
+ 172,163,165,183,169,167,182,188,189,190,221,168,175, 93,180,215,
+ 123, 65, 66, 67, 68, 69, 70, 71, 72, 73,173,244,246,242,243,245,
+ 125, 74, 75, 76, 77, 78, 79, 80, 81, 82,185,251,252,249,250,255,
+ 92,247, 83, 84, 85, 86, 87, 88, 89, 90,178,212,214,210,211,213,
+ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57,179,219,220,217,218,159
+ );
+
+if (EBCDIC && ord('^') == 106) { # as in the BS2000 posix-bc coded character set
+ $A2E[91] = 187; $A2E[92] = 188; $A2E[94] = 106; $A2E[96] = 74;
+ $A2E[123] = 251; $A2E[125] = 253; $A2E[126] = 255; $A2E[159] = 95;
+ $A2E[162] = 176; $A2E[166] = 208; $A2E[168] = 121; $A2E[172] = 186;
+ $A2E[175] = 161; $A2E[217] = 224; $A2E[219] = 221; $A2E[221] = 173;
+ $A2E[249] = 192;
+
+ $E2A[74] = 96; $E2A[95] = 159; $E2A[106] = 94; $E2A[121] = 168;
+ $E2A[161] = 175; $E2A[173] = 221; $E2A[176] = 162; $E2A[186] = 172;
+ $E2A[187] = 91; $E2A[188] = 92; $E2A[192] = 249; $E2A[208] = 166;
+ $E2A[221] = 219; $E2A[224] = 217; $E2A[251] = 123; $E2A[253] = 125;
+ $E2A[255] = 126;
+ }
+elsif (EBCDIC && ord('^') == 176) { # as in codepage 037 on os400
+ $A2E[10] = 37; $A2E[91] = 186; $A2E[93] = 187; $A2E[94] = 176;
+ $A2E[133] = 21; $A2E[168] = 189; $A2E[172] = 95; $A2E[221] = 173;
+
+ $E2A[21] = 133; $E2A[37] = 10; $E2A[95] = 172; $E2A[173] = 221;
+ $E2A[176] = 94; $E2A[186] = 91; $E2A[187] = 93; $E2A[189] = 168;
+}
+
+# Smart rearrangement of parameters to allow named parameter
+# calling. We do the rearrangement if:
+# the first parameter begins with a -
+
+sub rearrange {
+ my ($order,@param) = @_;
+ my ($result, $leftover) = _rearrange_params( $order, @param );
+ push @$result, make_attributes( $leftover, defined $CGI::Q ? $CGI::Q->{escape} : 1 )
+ if keys %$leftover;
+ @$result;
+}
+
+sub rearrange_header {
+ my ($order,@param) = @_;
+
+ my ($result,$leftover) = _rearrange_params( $order, @param );
+ push @$result, make_attributes( $leftover, 0, 1 ) if keys %$leftover;
+
+ @$result;
+}
+
+sub _rearrange_params {
+ my($order,@param) = @_;
+ return [] unless @param;
+
+ if (ref($param[0]) eq 'HASH') {
+ @param = %{$param[0]};
+ } else {
+ return \@param
+ unless (defined($param[0]) && substr($param[0],0,1) eq '-');
+ }
+
+ # map parameters into positional indices
+ my ($i,%pos);
+ $i = 0;
+ foreach (@$order) {
+ foreach (ref($_) eq 'ARRAY' ? @$_ : $_) { $pos{lc($_)} = $i; }
+ $i++;
+ }
+
+ my (@result,%leftover);
+ $#result = $#$order; # preextend
+ while (@param) {
+ my $key = lc(shift(@param));
+ $key =~ s/^\-//;
+ if (exists $pos{$key}) {
+ $result[$pos{$key}] = shift(@param);
+ } else {
+ $leftover{$key} = shift(@param);
+ }
+ }
+
+ return \@result, \%leftover;
+}
+
+sub make_attributes {
+ my $attr = shift;
+ return () unless $attr && ref($attr) && ref($attr) eq 'HASH';
+ my $escape = shift || 0;
+ my $do_not_quote = shift;
+
+ my $quote = $do_not_quote ? '' : '"';
+
+ my(@att);
+ foreach (keys %{$attr}) {
+ my($key) = $_;
+ $key=~s/^\-//; # get rid of initial - if present
+
+ # old way: breaks EBCDIC!
+ # $key=~tr/A-Z_/a-z-/; # parameters are lower case, use dashes
+
+ ($key="\L$key") =~ tr/_/-/; # parameters are lower case, use dashes
+
+ my $value = $escape ? simple_escape($attr->{$_}) : $attr->{$_};
+ push(@att,defined($attr->{$_}) ? qq/$key=$quote$value$quote/ : qq/$key/);
+ }
+ return @att;
+}
+
+sub simple_escape {
+ return unless defined(my $toencode = shift);
+ $toencode =~ s{&}{&amp;}gso;
+ $toencode =~ s{<}{&lt;}gso;
+ $toencode =~ s{>}{&gt;}gso;
+ $toencode =~ s{\"}{&quot;}gso;
+# Doesn't work. Can't work. forget it.
+# $toencode =~ s{\x8b}{&#139;}gso;
+# $toencode =~ s{\x9b}{&#155;}gso;
+ $toencode;
+}
+
+sub utf8_chr {
+ my $c = shift(@_);
+ if ($] >= 5.006){
+ require utf8;
+ my $u = chr($c);
+ utf8::encode($u); # drop utf8 flag
+ return $u;
+ }
+ if ($c < 0x80) {
+ return sprintf("%c", $c);
+ } elsif ($c < 0x800) {
+ return sprintf("%c%c", 0xc0 | ($c >> 6), 0x80 | ($c & 0x3f));
+ } elsif ($c < 0x10000) {
+ return sprintf("%c%c%c",
+ 0xe0 | ($c >> 12),
+ 0x80 | (($c >> 6) & 0x3f),
+ 0x80 | ( $c & 0x3f));
+ } elsif ($c < 0x200000) {
+ return sprintf("%c%c%c%c",
+ 0xf0 | ($c >> 18),
+ 0x80 | (($c >> 12) & 0x3f),
+ 0x80 | (($c >> 6) & 0x3f),
+ 0x80 | ( $c & 0x3f));
+ } elsif ($c < 0x4000000) {
+ return sprintf("%c%c%c%c%c",
+ 0xf8 | ($c >> 24),
+ 0x80 | (($c >> 18) & 0x3f),
+ 0x80 | (($c >> 12) & 0x3f),
+ 0x80 | (($c >> 6) & 0x3f),
+ 0x80 | ( $c & 0x3f));
+
+ } elsif ($c < 0x80000000) {
+ return sprintf("%c%c%c%c%c%c",
+ 0xfc | ($c >> 30),
+ 0x80 | (($c >> 24) & 0x3f),
+ 0x80 | (($c >> 18) & 0x3f),
+ 0x80 | (($c >> 12) & 0x3f),
+ 0x80 | (($c >> 6) & 0x3f),
+ 0x80 | ( $c & 0x3f));
+ } else {
+ return utf8_chr(0xfffd);
+ }
+}
+
+# unescape URL-encoded data
+sub unescape {
+ shift() if @_ > 0 and (ref($_[0]) || (defined $_[1] && $_[0] eq $CGI::DefaultClass));
+ my $todecode = shift;
+ return undef unless defined($todecode);
+ $todecode =~ tr/+/ /; # pluses become spaces
+ if (EBCDIC) {
+ $todecode =~ s/%([0-9a-fA-F]{2})/chr $A2E[hex($1)]/ge;
+ } else {
+ # handle surrogate pairs first -- dankogai. Ref: http://unicode.org/faq/utf_bom.html#utf16-2
+ $todecode =~ s{
+ %u([Dd][89a-bA-B][0-9a-fA-F]{2}) # hi
+ %u([Dd][c-fC-F][0-9a-fA-F]{2}) # lo
+ }{
+ utf8_chr(
+ 0x10000
+ + (hex($1) - 0xD800) * 0x400
+ + (hex($2) - 0xDC00)
+ )
+ }gex;
+ $todecode =~ s/%(?:([0-9a-fA-F]{2})|u([0-9a-fA-F]{4}))/
+ defined($1)? chr hex($1) : utf8_chr(hex($2))/ge;
+ }
+ return $todecode;
+}
+
+# URL-encode data
+#
+# We cannot use the %u escapes, they were rejected by W3C, so the official
+# way is %XX-escaped utf-8 encoding.
+# Naturally, Unicode strings have to be converted to their utf-8 byte
+# representation. (No action is required on 5.6.)
+# Byte strings were traditionally used directly as a sequence of octets.
+# This worked if they actually represented binary data (i.e. in CGI::Compress).
+# This also worked if these byte strings were actually utf-8 encoded; e.g.,
+# when the source file used utf-8 without the apropriate "use utf8;".
+# This fails if the byte string is actually a Latin 1 encoded string, but it
+# was always so and cannot be fixed without breaking the binary data case.
+# -- Stepan Kasal <skasal@redhat.com>
+#
+if ($] == 5.008) {
+ package utf8;
+
+ no warnings 'redefine'; # needed for Perl 5.8.1+
+
+ my $is_utf8_redefinition = <<'EOR';
+ sub is_utf8 {
+ my ($text) = @_;
+
+ my $ctext = pack q{C0a*}, $text;
+
+ return ($text ne $ctext) && ($ctext =~ m/^(
+ [\x09\x0A\x0D\x20-\x7E]
+ | [\xC2-\xDF][\x80-\xBF]
+ | \xE0[\xA0-\xBF][\x80-\xBF]
+ | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}
+ | \xED[\x80-\x9F][\x80-\xBF]
+ | \xF0[\x90-\xBF][\x80-\xBF]{2}
+ | [\xF1-\xF3][\x80-\xBF]{3}
+ | \xF4[\x80-\x8F][\x80-\xBF]{2}
+ )*$/xo);
+ }
+EOR
+
+ eval $is_utf8_redefinition;
+}
+
+sub escape {
+ # If we being called in an OO-context, discard the first argument.
+ shift() if @_ > 1 and ( ref($_[0]) || (defined $_[1] && $_[0] eq $CGI::DefaultClass));
+ my $toencode = shift;
+ return undef unless defined($toencode);
+ utf8::encode($toencode) if ($] >= 5.008 && utf8::is_utf8($toencode));
+ if (EBCDIC) {
+ $toencode=~s/([^a-zA-Z0-9_.~-])/uc sprintf("%%%02x",$E2A[ord($1)])/eg;
+ } else {
+ $toencode=~s/([^a-zA-Z0-9_.~-])/uc sprintf("%%%02x",ord($1))/eg;
+ }
+ return $toencode;
+}
+
+# This internal routine creates date strings suitable for use in
+# cookies and HTTP headers. (They differ, unfortunately.)
+# Thanks to Mark Fisher for this.
+sub expires {
+ my($time,$format) = @_;
+ $format ||= 'http';
+
+ my(@MON)=qw/Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec/;
+ my(@WDAY) = qw/Sun Mon Tue Wed Thu Fri Sat/;
+
+ # pass through preformatted dates for the sake of expire_calc()
+ $time = expire_calc($time);
+ return $time unless $time =~ /^\d+$/;
+
+ # make HTTP/cookie date string from GMT'ed time
+ # (cookies use '-' as date separator, HTTP uses ' ')
+ my($sc) = ' ';
+ $sc = '-' if $format eq "cookie";
+ my($sec,$min,$hour,$mday,$mon,$year,$wday) = gmtime($time);
+ $year += 1900;
+ return sprintf("%s, %02d$sc%s$sc%04d %02d:%02d:%02d GMT",
+ $WDAY[$wday],$mday,$MON[$mon],$year,$hour,$min,$sec);
+}
+
+# This internal routine creates an expires time exactly some number of
+# hours from the current time. It incorporates modifications from
+# Mark Fisher.
+sub expire_calc {
+ my($time) = @_;
+ my(%mult) = ('s'=>1,
+ 'm'=>60,
+ 'h'=>60*60,
+ 'd'=>60*60*24,
+ 'M'=>60*60*24*30,
+ 'y'=>60*60*24*365);
+ # format for time can be in any of the forms...
+ # "now" -- expire immediately
+ # "+180s" -- in 180 seconds
+ # "+2m" -- in 2 minutes
+ # "+12h" -- in 12 hours
+ # "+1d" -- in 1 day
+ # "+3M" -- in 3 months
+ # "+2y" -- in 2 years
+ # "-3m" -- 3 minutes ago(!)
+ # If you don't supply one of these forms, we assume you are
+ # specifying the date yourself
+ my($offset);
+ if (!$time || (lc($time) eq 'now')) {
+ $offset = 0;
+ } elsif ($time=~/^\d+/) {
+ return $time;
+ } elsif ($time=~/^([+-]?(?:\d+|\d*\.\d*))([smhdMy])/) {
+ $offset = ($mult{$2} || 1)*$1;
+ } else {
+ return $time;
+ }
+ my $cur_time = time;
+ return ($cur_time+$offset);
+}
+
+sub ebcdic2ascii {
+ my $data = shift;
+ $data =~ s/(.)/chr $E2A[ord($1)]/ge;
+ $data;
+}
+
+sub ascii2ebcdic {
+ my $data = shift;
+ $data =~ s/(.)/chr $A2E[ord($1)]/ge;
+ $data;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+CGI::Util - Internal utilities used by CGI module
+
+=head1 SYNOPSIS
+
+none
+
+=head1 DESCRIPTION
+
+no public subroutines
+
+=head1 AUTHOR INFORMATION
+
+Copyright 1995-1998, Lincoln D. Stein. All rights reserved.
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+Address bug reports and comments to: lstein@cshl.org. When sending
+bug reports, please provide the version of CGI.pm, the version of
+Perl, the name and version of your Web server, and the name and
+version of the operating system you are using. If the problem is even
+remotely browser dependent, please provide information about the
+affected browsers as well.
+
+=head1 SEE ALSO
+
+L<CGI>
+
+=cut
diff --git a/lib/README b/lib/README
new file mode 100644
index 000000000..5778a9a3f
--- /dev/null
+++ b/lib/README
@@ -0,0 +1,4 @@
+This directory contains the Perl modules that Bugzilla requires to run.
+
+If you would rather have Bugzilla use the Perl modules installed on your
+system, you can delete everything in this directory.
diff --git a/long_list.cgi b/long_list.cgi
new file mode 100755
index 000000000..7e1f69534
--- /dev/null
+++ b/long_list.cgi
@@ -0,0 +1,36 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Gervase Markham <gerv@gerv.net>
+
+use strict;
+use lib qw(. lib);
+use Bugzilla;
+
+my $cgi = Bugzilla->cgi;
+
+# Convert comma/space separated elements into separate params
+my $buglist = $cgi->param('buglist') || $cgi->param('bug_id') || $cgi->param('id');
+my @ids = split (/[\s,]+/, $buglist);
+
+my $ids = join('', map { $_ = "&id=" . $_ } @ids);
+
+print $cgi->redirect("show_bug.cgi?format=multiple$ids");
diff --git a/migrate.pl b/migrate.pl
new file mode 100755
index 000000000..df6b833a0
--- /dev/null
+++ b/migrate.pl
@@ -0,0 +1,110 @@
+#!/usr/bin/perl -w
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is The Bugzilla Migration Tool.
+#
+# The Initial Developer of the Original Code is Lambda Research
+# Corporation. Portions created by the Initial Developer are Copyright
+# (C) 2009 the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+use strict;
+use File::Basename;
+BEGIN { chdir dirname($0); }
+use lib qw(. lib);
+use Bugzilla;
+use Bugzilla::Migrate;
+
+use Getopt::Long;
+use Pod::Usage;
+
+my %switch;
+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});
+}
+
+my $migrator = Bugzilla::Migrate->load($switch{'from'});
+$migrator->verbose($switch{'verbose'});
+$migrator->dry_run($switch{'dry-run'});
+$migrator->check_requirements();
+$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();
+ }
+}
+
+__END__
+
+=head1 NAME
+
+migrate.pl - A script to migrate from other bug-trackers to Bugzilla.
+
+=head1 SYNOPSIS
+
+ ./migrate.pl --from=<tracker> [--verbose] [--dry-run]
+
+ Migrates from another bug-tracker to Bugzilla. If you want
+ to upgrade Bugzilla, use checksetup.pl instead.
+
+ Always test this on a backup copy of your database before
+ running it on your live Bugzilla.
+
+=head1 OPTIONS
+
+=over
+
+=item B<--from=tracker>
+
+Specifies what bug-tracker you're migrating from. To see what values
+are valid, see the contents of the F<Bugzilla/Migrate/> directory.
+
+=item B<--dry-run>
+
+Don't modify the Bugzilla database at all, just test the import.
+Note that this could cause significant slowdown and other strange effects
+on a live Bugzilla, so only use it on a test instance.
+
+=item B<--verbose>
+
+If specified, this script will output extra debugging information
+to STDERR. Specify multiple times (up to three) for more information.
+
+=back
+
+=head1 DESCRIPTION
+
+This script copies data from another bug-tracker into Bugzilla. It migrates
+users, products, and bugs from the other bug-tracker into this Bugzilla,
+without removing any of the data currently in this Bugzilla.
+
+Note that you will need enough space in your temporary directory to hold
+the size of all attachments in your current bug-tracker.
+
+You may also need to increase the number of file handles a process is allowed
+to hold open (as the migrator will create a file handle for each attachment
+in your database). On Linux and simliar systems, you can do this as root
+by typing C<ulimit -n 65535> before running your script. \ No newline at end of file
diff --git a/mod_perl.pl b/mod_perl.pl
new file mode 100644
index 000000000..f96156724
--- /dev/null
+++ b/mod_perl.pl
@@ -0,0 +1,159 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::ModPerl;
+
+use strict;
+use warnings;
+
+# This sets up our libpath without having to specify it in the mod_perl
+# configuration.
+use File::Basename;
+use lib dirname(__FILE__);
+use Bugzilla::Constants ();
+use lib Bugzilla::Constants::bz_locations()->{'ext_libpath'};
+
+# If you have an Apache2::Status handler in your Apache configuration,
+# you need to load Apache2::Status *here*, so that any later-loaded modules
+# can report information to Apache2::Status.
+#use Apache2::Status ();
+
+# We don't want to import anything into the global scope during
+# startup, so we always specify () after using any module in this
+# file.
+
+use Apache2::ServerUtil;
+use ModPerl::RegistryLoader ();
+use CGI ();
+CGI->compile(qw(:cgi -no_xhtml -oldstyle_urls :private_tempfiles
+ :unique_headers SERVER_PUSH :push));
+use File::Basename ();
+use Template::Config ();
+Template::Config->preload();
+
+# For PerlChildInitHandler
+eval { require Math::Random::Secure };
+
+use Bugzilla ();
+use Bugzilla::CGI ();
+use Bugzilla::Extension ();
+use Bugzilla::Install::Requirements ();
+use Bugzilla::Mailer ();
+use Bugzilla::Template ();
+use Bugzilla::Util ();
+
+use Apache2::SizeLimit;
+# This means that every httpd child will die after processing
+# a CGI if it is taking up more than 70MB of RAM all by itself.
+Apache2::SizeLimit->set_max_unshared_size(70_000);
+
+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;
+# Make sure each httpd child receives a different random seed (bug 476622).
+# Math::Random::Secure has one srand that needs to be called for
+# every process, and Perl has another. (Various Perl modules still use
+# the built-in rand(), even though we only use Math::Random::Secure in
+# Bugzilla itself, so we need to srand() both of them.) However,
+# Math::Random::Secure may not be installed, so we call its srand in an
+# eval.
+PerlChildInitHandler "sub { eval { Math::Random::Secure::srand() }; srand(); }"
+<Directory "$cgi_path">
+ AddHandler perl-script .cgi
+ # No need to PerlModule these because they're already defined in mod_perl.pl
+ PerlResponseHandler Bugzilla::ModPerl::ResponseHandler
+ PerlCleanupHandler Apache2::SizeLimit Bugzilla::ModPerl::CleanupHandler
+ PerlOptions +ParseHeaders
+ Options +ExecCGI
+ AllowOverride Limit FileInfo Indexes
+ DirectoryIndex index.cgi index.html
+</Directory>
+EOT
+
+$server->add_config([split("\n", $conf)]);
+
+# Pre-load all extensions
+$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
+# Bugzilla/ModPerl/ResponseHandler.pm
+$rl->{package} = 'Bugzilla::ModPerl::ResponseHandler';
+my $feature_files = Bugzilla::Install::Requirements::map_files_to_features();
+
+# Prevent "use lib" from doing anything when the .cgi files are compiled.
+# 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 {};
+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);
+}
+
+package Bugzilla::ModPerl::ResponseHandler;
+use strict;
+use base qw(ModPerl::Registry);
+use Bugzilla;
+
+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();
+ return $class->SUPER::handler(@_);
+}
+
+
+package Bugzilla::ModPerl::CleanupHandler;
+use strict;
+use Apache2::Const -compile => qw(OK);
+
+sub handler {
+ my $r = shift;
+
+ 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};
+ }
+
+ return Apache2::Const::OK;
+}
+
+1;
diff --git a/page.cgi b/page.cgi
new file mode 100755
index 000000000..a6a198d8b
--- /dev/null
+++ b/page.cgi
@@ -0,0 +1,98 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Gervase Markham <gerv@gerv.net>
+#
+
+###############################################################################
+# This CGI is a general template display engine. To display templates using it,
+# put them in the "pages" subdirectory of en/default, call them
+# "foo.<ctype>.tmpl" and use the URL page.cgi?id=foo.<ctype> , where <ctype> is
+# a content-type, e.g. html.
+###############################################################################
+
+use strict;
+
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Error;
+use Bugzilla::Hook;
+use Bugzilla::Search::Quicksearch;
+
+###############
+# Subroutines #
+###############
+
+# 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;
+}
+
+###############
+# Main Script #
+###############
+
+Bugzilla->login();
+
+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
+ ThrowCodeError("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
+ ThrowCodeError("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");
+}
diff --git a/post_bug.cgi b/post_bug.cgi
new file mode 100755
index 000000000..a0cfaf29d
--- /dev/null
+++ b/post_bug.cgi
@@ -0,0 +1,265 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Dan Mosedale <dmose@mozilla.org>
+# Joe Robins <jmrobins@tgix.com>
+# Gervase Markham <gerv@gerv.net>
+# Marc Schumann <wurblzap@gmail.com>
+
+use strict;
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Attachment;
+use Bugzilla::BugMail;
+use Bugzilla::Constants;
+use Bugzilla::Util;
+use Bugzilla::Error;
+use Bugzilla::Bug;
+use Bugzilla::User;
+use Bugzilla::Field;
+use Bugzilla::Hook;
+use Bugzilla::Product;
+use Bugzilla::Component;
+use Bugzilla::Keyword;
+use Bugzilla::Token;
+use Bugzilla::Flag;
+
+my $user = Bugzilla->login(LOGIN_REQUIRED);
+
+my $cgi = Bugzilla->cgi;
+my $dbh = Bugzilla->dbh;
+my $template = Bugzilla->template;
+my $vars = {};
+
+######################################################################
+# Main Script
+######################################################################
+
+# redirect to enter_bug if no field is passed.
+print $cgi->redirect(correct_urlbase() . 'enter_bug.cgi') unless $cgi->param();
+
+# Detect if the user already used the same form to submit a bug
+my $token = trim($cgi->param('token'));
+if ($token) {
+ my ($creator_id, $date, $old_bug_id) = Bugzilla::Token::GetTokenData($token);
+ unless ($creator_id
+ && ($creator_id == $user->id)
+ && ($old_bug_id =~ "^createbug:"))
+ {
+ # The token is invalid.
+ ThrowUserError('token_does_not_exist');
+ }
+
+ $old_bug_id =~ s/^createbug://;
+
+ if ($old_bug_id && (!$cgi->param('ignore_token')
+ || ($cgi->param('ignore_token') != $old_bug_id)))
+ {
+ $vars->{'bugid'} = $old_bug_id;
+ $vars->{'allow_override'} = defined $cgi->param('ignore_token') ? 0 : 1;
+
+ print $cgi->header();
+ $template->process("bug/create/confirm-create-dupe.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
+ }
+}
+
+# do a match on the fields if applicable
+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;
+}
+
+umask 0;
+
+# 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");
+$template->process($format->{'template'}, $vars, \$comment)
+ || 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;
+
+# 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(
+ product
+ component
+
+ assigned_to
+ qa_contact
+
+ alias
+ blocked
+ comment_is_private
+ bug_file_loc
+ bug_severity
+ bug_status
+ dependson
+ keywords
+ short_desc
+ op_sys
+ priority
+ rep_platform
+ version
+ target_milestone
+ status_whiteboard
+
+ estimated_time
+ deadline
+));
+my %bug_params;
+foreach my $field (@bug_fields) {
+ $bug_params{$field} = $cgi->param($field);
+}
+$bug_params{'cc'} = [$cgi->param('cc')];
+$bug_params{'groups'} = [$cgi->param('groups')];
+$bug_params{'comment'} = $comment;
+
+my @multi_selects = grep {$_->type == FIELD_TYPE_MULTI_SELECT && $_->enter_bug}
+ Bugzilla->active_custom_fields;
+
+foreach my $field (@multi_selects) {
+ $bug_params{$field->name} = [$cgi->param($field->name)];
+}
+
+my $bug = Bugzilla::Bug->create(\%bug_params);
+
+# Get the bug ID back.
+my $id = $bug->bug_id;
+# 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);
+
+# 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");
+}
+
+# We don't have to check if the user can see the bug, because a user filing
+# a bug can always see it. You can't change reporter_accessible until
+# after the bug is filed.
+
+# Add an attachment if requested.
+if (defined($cgi->upload('data')) || $cgi->param('attachurl')) {
+ $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 => scalar $cgi->param('attachurl') || $cgi->upload('data'),
+ description => scalar $cgi->param('description'),
+ filename => $cgi->param('attachurl') ? '' : scalar $cgi->upload('data'),
+ ispatch => scalar $cgi->param('ispatch'),
+ isprivate => scalar $cgi->param('isprivate'),
+ isurl => scalar $cgi->param('attachurl'),
+ mimetype => $content_type,
+ store_in_file => scalar $cgi->param('bigfile'),
+ });
+ };
+ 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);
+$bug->set_flags($flags, $new_flags);
+$bug->update($timestamp);
+
+$vars->{'id'} = $id;
+$vars->{'bug'} = $bug;
+
+Bugzilla::Hook::process('post_bug_after_creation', { vars => $vars });
+
+ThrowCodeError("bug_error", { bug => $bug }) if $bug->error;
+
+if ($token) {
+ trick_taint($token);
+ $dbh->do('UPDATE tokens SET eventdata = ? WHERE token = ?', undef,
+ ("createbug:$id", $token));
+}
+
+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);
+}
+$vars->{sentmail} = \@all_mail_results;
+
+print $cgi->header();
+$template->process("bug/create/created.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+
+1;
diff --git a/process_bug.cgi b/process_bug.cgi
new file mode 100755
index 000000000..0d57bfcfe
--- /dev/null
+++ b/process_bug.cgi
@@ -0,0 +1,414 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Dan Mosedale <dmose@mozilla.org>
+# Dave Miller <justdave@syndicomm.com>
+# Christopher Aillon <christopher@aillon.com>
+# Myk Melez <myk@mozilla.org>
+# Jeff Hedlund <jeff.hedlund@matrixsi.com>
+# Frédéric Buclin <LpSolit@gmail.com>
+# Lance Larsh <lance.larsh@oracle.com>
+# Akamai Technologies <bugzilla-dev@akamai.com>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+# Implementation notes for this file:
+#
+# 1) the 'id' form parameter is validated early on, and if it is not a valid
+# bugid an error will be reported, so it is OK for later code to simply check
+# for a defined form 'id' value, and it can assume a valid bugid.
+#
+# 2) If the 'id' form parameter is not defined (after the initial validation),
+# then we are processing multiple bugs, and @idlist will contain the ids.
+#
+# 3) If we are processing just the one id, then it is stored in @idlist for
+# later processing.
+
+use strict;
+
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Bug;
+use Bugzilla::BugMail;
+use Bugzilla::Mailer;
+use Bugzilla::User;
+use Bugzilla::Util;
+use Bugzilla::Error;
+use Bugzilla::Field;
+use Bugzilla::Product;
+use Bugzilla::Component;
+use Bugzilla::Keyword;
+use Bugzilla::Flag;
+use Bugzilla::Status;
+use Bugzilla::Token;
+
+use List::MoreUtils qw(firstidx);
+use Storable qw(dclone);
+
+my $user = Bugzilla->login(LOGIN_REQUIRED);
+
+my $cgi = Bugzilla->cgi;
+my $dbh = Bugzilla->dbh;
+my $template = Bugzilla->template;
+my $vars = {};
+
+######################################################################
+# Subroutines
+######################################################################
+
+# 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;
+}
+
+######################################################################
+# Begin Data/Security Validation
+######################################################################
+
+# Create a list of objects for all bugs being modified in this request.
+my @bug_objects;
+if (defined $cgi->param('id')) {
+ my $bug = Bugzilla::Bug->check(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($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.
+
+# 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");
+ }
+ }
+}
+
+# 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' },
+});
+
+print $cgi->header() unless Bugzilla->usage_mode == USAGE_MODE_EMAIL;
+
+# Check for a mid-air collision. Currently this only works when updating
+# an individual bug.
+if (defined $cgi->param('delta_ts'))
+{
+ my $delta_ts_z = datetime_from($cgi->param('delta_ts'));
+ my $first_delta_tz_z = datetime_from($first_bug->delta_ts);
+ if ($first_delta_tz_z ne $delta_ts_z) {
+ ($vars->{'operations'}) =
+ Bugzilla::Bug::GetBugActivity($first_bug->id, undef,
+ scalar $cgi->param('delta_ts'));
+
+ $vars->{'title_tag'} = "mid_air";
+
+ ThrowCodeError('undefined_field', { field => 'longdesclength' })
+ if !defined $cgi->param('longdesclength');
+
+ $vars->{'start_at'} = $cgi->param('longdesclength');
+ # Always sort midair collision comments oldest to newest,
+ # regardless of the user's personal preference.
+ $vars->{'comments'} = $first_bug->comments({ order => "oldest_to_newest" });
+ $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
+# and display the mid-air collision page if delta_ts changed.
+# If we do a mass-change, we use session tokens.
+my $token = $cgi->param('token');
+
+if ($cgi->param('id')) {
+ check_hash_token($token, [$first_bug->id, $first_bug->delta_ts]);
+}
+else {
+ check_token_data($token, 'buglist_mass_change', 'query.cgi');
+}
+
+######################################################################
+# End Data/Security Validation
+######################################################################
+
+$vars->{'title_tag'} = "bug_processed";
+
+my $action;
+if (defined $cgi->param('id')) {
+ $action = $user->settings->{'post_bug_submit_action'}->{'value'};
+
+ 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';
+}
+
+# For each bug, we have to check if the user can edit the bug the product
+# is currently in, before we allow them to change anything.
+foreach my $bug (@bug_objects) {
+ if (!Bugzilla->user->can_edit_product($bug->product_obj->id) ) {
+ ThrowUserError("product_edit_denied",
+ { product => $bug->product });
+ }
+}
+
+# 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);
+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',
+);
+
+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('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'),
+ };
+}
+if (should_set('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')];
+}
+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);
+ }
+ }
+}
+# 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'))
+{
+ 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, Bugzilla->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 };
+}
+
+# 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 (Bugzilla->params->{"usebugaliases"} && defined $cgi->param('alias')) {
+ $set_all_fields{alias} = $cgi->param('alias');
+ }
+}
+
+my %is_private;
+foreach my $field (grep(/^defined_isprivate/, $cgi->param())) {
+ $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 ($removed_groups) = diff_arrays(\@check_groups, \@set_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)];
+ }
+}
+
+foreach my $b (@bug_objects) {
+ $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);
+}
+
+##############################
+# 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 Bugzilla->user->is_timetracker;
+ }
+ }
+
+ $bug->send_changes($changes, $vars);
+}
+
+if (Bugzilla->usage_mode == USAGE_MODE_EMAIL) {
+ # 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;
+ }
+ $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());
+}
+
+1;
diff --git a/query.cgi b/query.cgi
new file mode 100755
index 000000000..6c1456512
--- /dev/null
+++ b/query.cgi
@@ -0,0 +1,355 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# David Gardiner <david.gardiner@unisa.edu.au>
+# Matthias Radestock <matthias@sorted.org>
+# Gervase Markham <gerv@gerv.net>
+# Byron Jones <bugzilla@glob.com.au>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+use strict;
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Bug;
+use Bugzilla::Constants;
+use Bugzilla::Search;
+use Bugzilla::User;
+use Bugzilla::Util;
+use Bugzilla::Error;
+use Bugzilla::Product;
+use Bugzilla::Keyword;
+use Bugzilla::Field;
+use Bugzilla::Install::Util qw(vers_cmp);
+
+my $cgi = Bugzilla->cgi;
+my $dbh = Bugzilla->dbh;
+my $template = Bugzilla->template;
+my $vars = {};
+my $buffer = $cgi->query_string();
+
+my $user = Bugzilla->login();
+my $userid = $user->id;
+
+if ($cgi->param('nukedefaultquery')) {
+ if ($userid) {
+ $dbh->do("DELETE FROM namedqueries" .
+ " WHERE userid = ? AND name = ?",
+ undef, ($userid, DEFAULT_QUERY_NAME));
+ }
+ $buffer = "";
+}
+
+# We are done with changes committed to the DB.
+$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));
+}
+
+local our %default;
+
+# We pass the defaults as a hash of references to arrays. For those
+# 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;
+
+ # Nothing must be undef, otherwise the template complains.
+ my @list = ("bug_status", "resolution", "assigned_to",
+ "rep_platform", "priority", "bug_severity",
+ "classification", "product", "reporter", "op_sys",
+ "component", "version", "chfield", "chfieldfrom",
+ "chfieldto", "chfieldvalue", "target_milestone",
+ "email", "emailtype", "emailreporter",
+ "emailassigned_to", "emailcc", "emailqa_contact",
+ "emaillongdesc", "content",
+ "changedin", "short_desc", "short_desc_type",
+ "longdesc", "longdesc_type", "bug_file_loc",
+ "bug_file_loc_type", "status_whiteboard",
+ "status_whiteboard_type", "bug_id",
+ "bug_id_type", "keywords", "keywords_type",
+ "deadlinefrom", "deadlineto",
+ "x_axis_field", "y_axis_field", "z_axis_field",
+ "chart_format", "cumulate", "x_labels_vertical",
+ "category", "subcategory", "name", "newcategory",
+ "newsubcategory", "public", "frequency");
+ # These fields can also have default values. And because there are
+ # hooks in the advanced search page which let you add fields as
+ # discrete forms, we also need to retain the operators.
+ my @custom_fields = Bugzilla->active_custom_fields;
+ push(@list, map { $_->name } @custom_fields);
+ push(@list, map { $_->name . '_type'} @custom_fields);
+
+ foreach my $name (@list) {
+ $default{$name} = [];
+ }
+
+ # we won't prefill the boolean chart data from this query if
+ # there are any being submitted via params
+ my $prefillcharts = (grep(/^field-/, $cgi->param)) ? 0 : 1;
+
+ # Iterate over the URL parameters
+ foreach my $name ($buf->param()) {
+ my @values = $buf->param($name);
+
+ # If the name begins with the string 'field', 'type', 'value', or
+ # 'negate', then it is part of the boolean charts. Because
+ # these are built different than the rest of the form, we need
+ # to store these as parameters. We also need to indicate that
+ # we found something so the default query isn't added in if
+ # all we have are boolean chart items.
+ if ($name =~ m/^(?:field|type|value|negate)/) {
+ $cgi->param(-name => $name, -value => $values[0]) if ($prefillcharts);
+ $foundone = 1;
+ }
+ # 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 =~ m/^(.+)(\d)$/ && defined($default{$1})) {
+ $foundone = 1;
+ $default{$1}->[$2] = $values[0];
+ }
+ elsif (exists $default{$name}) {
+ $foundone = 1;
+ push (@{$default{$name}}, @values);
+ }
+ }
+ 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"});
+ }
+}
+
+if (!scalar(@{$default{'chfieldto'}}) || $default{'chfieldto'}->[0] eq "") {
+ $default{'chfieldto'} = ["Now"];
+}
+
+# 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};
+Bugzilla::Product::preload(\@selectable_products);
+
+# Create the component, version and milestone lists.
+my %components;
+my %versions;
+my %milestones;
+
+foreach my $product (@selectable_products) {
+ $components{$_->name} = 1 foreach (@{$product->components});
+ $versions{$_->name} = 1 foreach (@{$product->versions});
+ $milestones{$_->name} = 1 foreach (@{$product->milestones});
+}
+
+my @components = sort(keys %components);
+my @versions = sort { vers_cmp (lc($a), lc($b)) } keys %versions;
+my @milestones = sort(keys %milestones);
+
+$vars->{'product'} = \@selectable_products;
+
+# Create data structures representing each classification
+if (Bugzilla->params->{'useclassification'}) {
+ $vars->{'classification'} = $user->get_selectable_classifications;
+}
+
+# We use 'component_' because 'component' is a Template Toolkit reserved word.
+$vars->{'component_'} = \@components;
+
+$vars->{'version'} = \@versions;
+
+if (Bugzilla->params->{'usetargetmilestone'}) {
+ $vars->{'target_milestone'} = \@milestones;
+}
+
+my @chfields;
+
+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 (Bugzilla->user->is_timetracker) {
+ push @chfields, "work_time";
+} else {
+ @chfields = grep($_ ne "estimated_time", @chfields);
+ @chfields = grep($_ ne "remaining_time", @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->{'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;
+
+# Boolean charts
+my @fields = Bugzilla->get_fields({ obsolete => 0 });
+
+# If we're not in the time-tracking group, exclude time-tracking fields.
+if (!Bugzilla->user->is_timetracker) {
+ foreach my $tt_field (TIMETRACKING_FIELDS) {
+ @fields = grep($_->name ne $tt_field, @fields);
+ }
+}
+
+@fields = sort {lc($a->description) cmp lc($b->description)} @fields;
+unshift(@fields, { name => "noop", description => "---" });
+$vars->{'fields'} = \@fields;
+
+# Creating new charts - if the cmd-add value is there, we define the field
+# value so the code sees it and creates the chart. It will attempt to select
+# "xyzzy" as the default, and fail. This is the correct behaviour.
+foreach my $cmd (grep(/^cmd-/, $cgi->param)) {
+ if ($cmd =~ /^cmd-add(\d+)-(\d+)-(\d+)$/) {
+ $cgi->param(-name => "field$1-$2-$3", -value => "xyzzy");
+ }
+}
+
+if (!$cgi->param('field0-0-0')) {
+ $cgi->param(-name => 'field0-0-0', -value => "xyzzy");
+}
+
+# Create data structure of boolean chart info. It's an array of arrays of
+# arrays - with the inner arrays having three members - field, type and
+# value.
+my @charts;
+for (my $chart = 0; $cgi->param("field$chart-0-0"); $chart++) {
+ my @rows;
+ for (my $row = 0; $cgi->param("field$chart-$row-0"); $row++) {
+ my @cols;
+ for (my $col = 0; $cgi->param("field$chart-$row-$col"); $col++) {
+ my $value = $cgi->param("value$chart-$row-$col");
+ if (!defined($value)) {
+ $value = '';
+ }
+ push(@cols, { field => $cgi->param("field$chart-$row-$col"),
+ type => $cgi->param("type$chart-$row-$col") || 'noop',
+ value => $value });
+ }
+ push(@rows, \@cols);
+ }
+ push(@charts, {'rows' => \@rows, 'negate' => scalar($cgi->param("negate$chart")) });
+}
+
+$default{'charts'} = \@charts;
+
+# 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));
+}
+
+# Sort order
+my $deforder;
+my @orders = ('Bug Number', 'Importance', 'Assignee', 'Last Changed');
+
+if ($cgi->cookie('LASTORDER')) {
+ $deforder = "Reuse same sort as last time";
+ unshift(@orders, $deforder);
+}
+
+if ($cgi->param('order')) { $deforder = $cgi->param('order') }
+
+$vars->{'userdefaultquery'} = $userdefaultquery;
+$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('format') && $cgi->param('format') =~ /^report-(table|graph)$/) {
+ # Get legal custom fields for tabular and graphical reports.
+ my @custom_fields_for_reports =
+ grep { $_->type == FIELD_TYPE_SINGLE_SELECT } Bugzilla->active_custom_fields;
+ $vars->{'custom_fields'} = \@custom_fields_for_reports;
+}
+
+$vars->{'known_name'} = $cgi->param('known_name');
+$vars->{'columnlist'} = $cgi->param('columnlist');
+
+
+# Add in the defaults.
+$vars->{'default'} = \%default;
+
+$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';
+ }
+}
+
+# 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");
+}
+
+# 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'));
+
+print $cgi->header($format->{'ctype'});
+
+$template->process($format->{'template'}, $vars)
+ || ThrowTemplateError($template->error());
diff --git a/quips.cgi b/quips.cgi
new file mode 100755
index 000000000..97993d488
--- /dev/null
+++ b/quips.cgi
@@ -0,0 +1,151 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Owen Taylor <otaylor@redhat.com>
+# Gervase Markham <gerv@gerv.net>
+# David Fallon <davef@tetsubo.com>
+# Tobias Burnus <burnus@net-b.de>
+
+use strict;
+
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Util;
+use Bugzilla::Error;
+use Bugzilla::User;
+use Bugzilla::Token;
+
+my $user = Bugzilla->login(LOGIN_REQUIRED);
+
+my $cgi = Bugzilla->cgi;
+my $dbh = Bugzilla->dbh;
+my $template = Bugzilla->template;
+my $vars = {};
+
+my $action = $cgi->param('action') || "";
+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");
+
+ 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;
+}
+
+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")
+ || Bugzilla->user->in_group('admin') || 0;
+ my $comment = $cgi->param("quip");
+ $comment || ThrowUserError("need_quip");
+ 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;
+}
+
+if ($action eq 'approve') {
+ $user->in_group('admin')
+ || ThrowUserError("auth_failure", {group => "admin",
+ 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;
+}
+
+if ($action eq "delete") {
+ Bugzilla->user->in_group("admin")
+ || ThrowUserError("auth_failure", {group => "admin",
+ action => "delete",
+ object => "quips"});
+ my $quipid = $cgi->param("quipid");
+ ThrowCodeError("need_quipid") unless $quipid =~ /(\d+)/;
+ $quipid = $1;
+ 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);
+}
+
+print $cgi->header();
+$template->process("list/quips.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
diff --git a/relogin.cgi b/relogin.cgi
new file mode 100755
index 000000000..0e04b1bdc
--- /dev/null
+++ b/relogin.cgi
@@ -0,0 +1,203 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Gervase Markham <gerv@gerv.net>
+# A. Karl Kornel <karl@kornel.name>
+
+use strict;
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Mailer;
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Token;
+use Bugzilla::User;
+use Bugzilla::Util;
+use Date::Format;
+
+my $template = Bugzilla->template;
+my $cgi = Bugzilla->cgi;
+
+my $action = $cgi->param('action') || '';
+
+my $vars = {};
+my $target;
+
+if (!$action) {
+ # redirect to index.cgi if no action is defined.
+ print $cgi->redirect(correct_urlbase() . 'index.cgi');
+}
+# 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');
+
+ # 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);
+
+ # 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 => $cgi->param('target_login'),
+ reason => $cgi->param('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 => scalar $cgi->param('target_login'),
+ reason => scalar $cgi->param('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 => $cgi->param('target_login') });
+ unless (defined($target_user)
+ && $target_user->id
+ && $user->can_see_user($target_user))
+ {
+ ThrowUserError('user_match_failed',
+ { 'name' => $cgi->param('target_login') }
+ );
+ }
+ if ($target_user->in_group('bz_sudo_protect')) {
+ ThrowUserError('sudo_protected', { login => $target_user->login });
+ }
+
+ # If we have a reason passed in, keep it under 200 characters
+ my $reason = $cgi->param('reason') || '';
+ $reason = substr($reason, $[, 200);
+
+ # 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);
+ $cgi->send_cookie('-name' => 'sudo',
+ '-expires' => $time_string,
+ '-value' => $token
+ );
+
+ # 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.
+
+ # Go ahead and send out the message now
+ my $message;
+ my $mail_template = Bugzilla->template_inner($target_user->settings->{'lang'}->{'value'});
+ $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';
+}
+# 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';
+}
+# No valid action found
+else {
+ Bugzilla->login(LOGIN_OPTIONAL);
+ ThrowUserError('unknown_action', {action => $action});
+}
+
+# Display the template
+print $cgi->header();
+$template->process($target, $vars)
+ || ThrowTemplateError($template->error());
diff --git a/report.cgi b/report.cgi
new file mode 100755
index 000000000..9695d54da
--- /dev/null
+++ b/report.cgi
@@ -0,0 +1,349 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Gervase Markham <gerv@gerv.net>
+# <rdean@cambianetworks.com>
+
+use strict;
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Util;
+use Bugzilla::Error;
+use Bugzilla::Field;
+use List::MoreUtils qw(uniq);
+
+my $cgi = Bugzilla->cgi;
+my $template = Bugzilla->template;
+my $vars = {};
+my $buffer = $cgi->query_string();
+
+# 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;
+}
+
+use Bugzilla::Search;
+
+Bugzilla->login();
+
+my $dbh = Bugzilla->switch_to_shadow_db();
+
+my $action = $cgi->param('action') || 'menu';
+
+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;
+}
+
+my $col_field = $cgi->param('x_axis_field') || '';
+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");
+}
+
+my $width = $cgi->param('width');
+my $height = $cgi->param('height');
+
+if (defined($width)) {
+ (detaint_natural($width) && $width > 0)
+ || ThrowCodeError("invalid_dimensions");
+ $width <= 2000 || ThrowUserError("chart_too_large");
+}
+
+if (defined($height)) {
+ (detaint_natural($height) && $height > 0)
+ || ThrowCodeError("invalid_dimensions");
+ $height <= 2000 || ThrowUserError("chart_too_large");
+}
+
+# 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 (defined $cgi->param('format') && $cgi->param('format') eq "table") {
+ 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')) {
+ ThrowCodeError('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 = '';
+ }
+}
+
+# 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
+ || ($valid_columns->{$row_field} && trick_taint($row_field))
+ || ThrowCodeError("report_axis_invalid", {fld => "x", val => $row_field});
+!$col_field
+ || ($valid_columns->{$col_field} && trick_taint($col_field))
+ || ThrowCodeError("report_axis_invalid", {fld => "y", val => $col_field});
+!$tbl_field
+ || ($valid_columns->{$tbl_field} && trick_taint($tbl_field))
+ || ThrowCodeError("report_axis_invalid", {fld => "z", val => $tbl_field});
+
+my @axis_fields = ($row_field || EMPTY_COLUMN,
+ $col_field || EMPTY_COLUMN,
+ $tbl_field || EMPTY_COLUMN);
+
+# 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' => $params);
+my $query = $search->getSQL();
+
+$::SIG{TERM} = 'DEFAULT';
+$::SIG{PIPE} = 'DEFAULT';
+
+my $results = $dbh->selectall_arrayref($query);
+
+# We have a hash of hashes for the data itself, and a hash to hold the
+# row/col/table names.
+my %data;
+my %names;
+
+# Read the bug data and count the bugs for each possible value of row, column
+# and table.
+#
+# We detect a numerical field, and sort appropriately, if all the values are
+# numeric.
+my $col_isnumeric = 1;
+my $row_isnumeric = 1;
+my $tbl_isnumeric = 1;
+
+foreach my $result (@$results) {
+ my ($row, $col, $tbl) = @$result;
+
+ # handle empty dimension member names
+ $row = ' ' if ($row eq '');
+ $col = ' ' if ($col eq '');
+ $tbl = ' ' if ($tbl eq '');
+
+ $row = "" if ($row eq EMPTY_COLUMN);
+ $col = "" if ($col eq EMPTY_COLUMN);
+ $tbl = "" if ($tbl eq EMPTY_COLUMN);
+
+ $data{$tbl}{$col}{$row}++;
+ $names{"col"}{$col}++;
+ $names{"row"}{$row}++;
+ $names{"tbl"}{$tbl}++;
+
+ $col_isnumeric &&= ($col =~ /^-?\d+(\.\d+)?$/o);
+ $row_isnumeric &&= ($row =~ /^-?\d+(\.\d+)?$/o);
+ $tbl_isnumeric &&= ($tbl =~ /^-?\d+(\.\d+)?$/o);
+}
+
+my @col_names = @{get_names($names{"col"}, $col_isnumeric, $col_field)};
+my @row_names = @{get_names($names{"row"}, $row_isnumeric, $row_field)};
+my @tbl_names = @{get_names($names{"tbl"}, $tbl_isnumeric, $tbl_field)};
+
+# The GD::Graph package requires a particular format of data, so once we've
+# 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);
+ }
+
+ 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->{'col_names'} = \@col_names;
+$vars->{'row_names'} = \@row_names;
+$vars->{'tbl_names'} = \@tbl_names;
+
+# Below a certain width, we don't see any bars, so there needs to be a minimum.
+if ($width && $cgi->param('format') eq "bar") {
+ my $min_width = (scalar(@col_names) || 1) * 20;
+
+ if (!$cgi->param('cumulate')) {
+ $min_width *= (scalar(@row_names) || 1);
+ }
+
+ $vars->{'min_width'} = $min_width;
+}
+
+$vars->{'width'} = $width if $width;
+$vars->{'height'} = $height if $height;
+
+$vars->{'query'} = $query;
+$vars->{'debug'} = $cgi->param('debug');
+
+my $formatparam = $cgi->param('format');
+
+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;
+ trick_taint($formatparam);
+ $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'} = join("&", $buffer =~ /[&?]($col_field=[^&]+)/g);
+ $vars->{'row_vals'} = join("&", $buffer =~ /[&?]($row_field=[^&]+)/g);
+ $vars->{'tbl_vals'} = join("&", $buffer =~ /[&?]($tbl_field=[^&]+)/g);
+
+ # 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;
+}
+else {
+ ThrowUserError('unknown_action', {action => $action});
+}
+
+my $format = $template->get_format("reports/report", $formatparam,
+ 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
+# set debug=1 to always get an HTML content-type, and view the error.
+$format->{'ctype'} = "text/html" if $cgi->param('debug');
+
+my @time = localtime(time());
+my $date = sprintf "%04d-%02d-%02d", 1900+$time[5],$time[4]+1,$time[3];
+my $filename = "report-$date.$format->{extension}";
+print $cgi->header(-type => $format->{'ctype'},
+ -content_disposition => "inline; filename=$filename");
+
+# 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;
+ print "<pre>data hash:\n";
+ print Data::Dumper::Dumper(%data) . "\n\n";
+ print "data array:\n";
+ print Data::Dumper::Dumper(@image_data) . "\n\n</pre>";
+}
+
+# All formats point to the same section of the documentation.
+$vars->{'doc_section'} = 'reporting.html#reports';
+
+disable_utf8() if ($format->{'ctype'} =~ /^image\//);
+
+$template->process("$format->{'template'}", $vars)
+ || ThrowTemplateError($template->error());
+
+exit;
+
+
+sub get_names {
+ my ($names, $isnumeric, $field) = @_;
+
+ # These are all the fields we want to preserve the order of in reports.
+ my %fields;
+ my @select_fields = Bugzilla->get_fields({ is_select => 1 });
+ foreach my $field (@select_fields) {
+ my @names = map($_->name, @{$field->legal_values});
+ unshift @names, ' ' if $field->name eq 'resolution';
+ $fields{$field->name} = [ uniq @names ];
+ }
+ my $field_list = $fields{$field};
+ my @sorted;
+
+ if ($field_list) {
+ my @unsorted = keys %{$names};
+
+ # Extract the used fields from the field_list, in the order they
+ # appear in the field_list. This lets us keep e.g. severities in
+ # the normal order.
+ #
+ # This is O(n^2) but it shouldn't matter for short lists.
+ foreach my $item (@$field_list) {
+ push(@sorted, $item) if grep { $_ eq $item } @unsorted;
+ }
+ }
+ elsif ($isnumeric) {
+ # It's not a field we are preserving the order of, so sort it
+ # numerically...
+ sub numerically { $a <=> $b }
+ @sorted = sort numerically keys(%{$names});
+ } else {
+ # ...or alphabetically, as appropriate.
+ @sorted = sort(keys(%{$names}));
+ }
+
+ return \@sorted;
+}
diff --git a/reports.cgi b/reports.cgi
new file mode 100755
index 000000000..2eacc6127
--- /dev/null
+++ b/reports.cgi
@@ -0,0 +1,264 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Harrison Page <harrison@netscape.com>,
+# Terry Weissman <terry@mozilla.org>,
+# Dawn Endico <endico@mozilla.org>
+# Bryce Nesbitt <bryce@nextbus.COM>,
+# Joe Robins <jmrobins@tgix.com>,
+# Gervase Markham <gerv@gerv.net> and Adam Spiers <adam@spiers.net>
+# Added ability to chart any combination of resolutions/statuses.
+# Derive the choice of resolutions/statuses from the -All- data file
+# Removed hardcoded order of resolutions/statuses when reading from
+# daily stats file, so now works independently of collectstats.pl
+# version
+# Added image caching by date and datasets
+# Myk Melez <myk@mozilla.org>:
+# Implemented form field validation and reorganized code.
+# Frédéric Buclin <LpSolit@gmail.com>:
+# Templatization.
+
+use strict;
+
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Util;
+use Bugzilla::Error;
+use Bugzilla::Status;
+
+use File::Basename;
+use Digest::MD5 qw(md5_hex);
+
+# 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 $template = Bugzilla->template;
+my $vars = {};
+
+if (!Bugzilla->feature('old_charts')) {
+ ThrowCodeError('feature_disabled', { feature => 'old_charts' });
+}
+
+my $dir = bz_locations()->{'datadir'} . "/mining";
+my $graph_dir = bz_locations()->{'graphsdir'};
+my $graph_url = basename($graph_dir);
+my $product_name = $cgi->param('product') || '';
+
+Bugzilla->switch_to_shadow_db();
+
+if (!$product_name) {
+ # Can we do bug charts?
+ (-d $dir && -d $graph_dir)
+ || ThrowCodeError('chart_dir_nonexistent',
+ {dir => $dir, graph_dir => $graph_dir});
+
+ my %default_sel = map { $_ => 1 } BUG_STATE_OPEN;
+
+ 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);
+ }
+
+ # We only want those products that the user has permissions for.
+ my @myproducts = ('-All-');
+ # Extract product names from objects and add them to the list.
+ push( @myproducts, map { $_->name } @{$user->get_selectable_products} );
+
+ $vars->{'datasets'} = \@datasets;
+ $vars->{'products'} = \@myproducts;
+
+ print $cgi->header();
+}
+else {
+ # For security and correctness, validate the value of the "product" form variable.
+ # Valid values are those products for which the user has permissions which appear
+ # in the "product" drop-down menu on the report generation form.
+ my ($product) = grep { $_->name eq $product_name } @{$user->get_selectable_products};
+ ($product || $product_name eq '-All-')
+ || ThrowUserError('invalid_product_name', {product => $product_name});
+
+ # Product names can change over time. Their ID cannot; so use the ID
+ # to generate the filename.
+ my $prod_id = $product ? $product->id : 0;
+
+ # 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 names.
+ my $key = Bugzilla->localconfig->{'site_wide_secret'};
+ my $project = bz_locations()->{'project'} || '';
+ my $image_file = join(':', ($key, $project, $prod_id, @datasets));
+ # Wide characters cause md5_hex() to die.
+ if (Bugzilla->params->{'utf8'}) {
+ utf8::encode($image_file) if utf8::is_utf8($image_file);
+ }
+ my $type = chart_image_type();
+ $image_file = md5_hex($image_file) . ".$type";
+ trick_taint($image_file);
+
+ if (! -e "$graph_dir/$image_file") {
+ generate_chart($dir, "$graph_dir/$image_file", $type, $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)
+ || ThrowTemplateError($template->error());
+
+#####################
+# Subroutines #
+#####################
+
+sub get_data {
+ my $dir = shift;
+
+ my @datasets;
+ open(DATA, '<', "$dir/-All-")
+ || ThrowCodeError('chart_file_open_fail', {filename => "$dir/-All-"});
+
+ while (<DATA>) {
+ if (/^# fields?: (.+)\s*$/) {
+ @datasets = grep ! /date/i, (split /\|/, $1);
+ last;
+ }
+ }
+ close(DATA);
+ return @datasets;
+}
+
+sub chart_image_type {
+ # what chart type should we be generating?
+ my $testimg = Chart::Lines->new(2,2);
+ my $type = $testimg->can('gif') ? "gif" : "png";
+
+ undef $testimg;
+ return $type;
+}
+
+sub generate_chart {
+ my ($dir, $image_file, $type, $product, $datasets) = @_;
+ $product = $product ? $product->name : '-All-';
+ my $data_file = $product;
+ $data_file =~ s/\//-/gs;
+ $data_file = $dir . '/' . $data_file;
+
+ if (! open FILE, $data_file) {
+ if ($product eq '-All-') {
+ $product = '';
+ }
+ ThrowCodeError('chart_data_not_generated', {'product' => $product});
+ }
+
+ 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];
+ }
+ }
+ }
+
+ 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",
+ "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->$type($image_file, [ @data{('DATE', @labels)} ]);
+}
diff --git a/request.cgi b/request.cgi
new file mode 100755
index 000000000..16d7662e8
--- /dev/null
+++ b/request.cgi
@@ -0,0 +1,360 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Myk Melez <myk@mozilla.org>
+# Frédéric Buclin <LpSolit@gmail.com>
+
+################################################################################
+# Script Initialization
+################################################################################
+
+# Make it harder for us to do dangerous things in Perl.
+use strict;
+
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Util;
+use Bugzilla::Error;
+use Bugzilla::Flag;
+use Bugzilla::FlagType;
+use Bugzilla::User;
+use Bugzilla::Product;
+use Bugzilla::Component;
+
+# Make sure the user is logged in.
+my $user = Bugzilla->login();
+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') || '';
+
+print $cgi->header();
+
+################################################################################
+# Main Body Execution
+################################################################################
+
+my $fields;
+$fields->{'requester'}->{'type'} = 'single';
+# If the user doesn't restrict his 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';
+}
+
+Bugzilla::User::match_field($fields);
+
+if ($action eq 'queue') {
+ queue();
+}
+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;
+ }
+ }
+ $vars->{'components'} = [ sort { $a cmp $b } keys %components ];
+
+ $template->process('request/queue.html.tmpl', $vars)
+ || ThrowTemplateError($template->error());
+}
+exit;
+
+################################################################################
+# Functions
+################################################################################
+
+sub queue {
+ 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,
+ 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, 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
+ # so we can display product and component names, and the bug_group_map
+ # table to help us weed out secure bugs to which the user should not have
+ # access.
+ "
+ FROM flags
+ LEFT JOIN attachments
+ ON flags.attach_id = attachments.attach_id
+ INNER JOIN flagtypes
+ ON flags.type_id = flagtypes.id
+ INNER JOIN profiles AS requesters
+ ON flags.setter_id = requesters.userid
+ LEFT JOIN profiles AS requestees
+ ON flags.requestee_id = requestees.userid
+ INNER JOIN bugs
+ ON flags.bug_id = bugs.bug_id
+ INNER JOIN products
+ ON bugs.product_id = products.id
+ INNER JOIN components
+ ON bugs.component_id = components.id
+ LEFT JOIN bug_group_map AS bgmap
+ ON bgmap.bug_id = bugs.bug_id
+ AND bgmap.group_id NOT IN (" .
+ $user->groups_as_string . ")
+ LEFT JOIN bug_group_map AS privs
+ ON privs.bug_id = bugs.bug_id
+ LEFT JOIN cc AS ccmap
+ ON ccmap.who = $userid
+ AND ccmap.bug_id = bugs.bug_id
+ " .
+
+ # Weed out bug the user does not have access to
+ " WHERE ((bgmap.group_id IS NULL) OR
+ (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
+ 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 = ();
+
+ # 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') unless $cgi->param('do_union');
+ }
+ elsif ($status ne "all") {
+ push(@criteria, "flags.status = '$status'");
+ push(@excluded_columns, 'status') unless $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 $cgi->param('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));
+ }
+ else { push(@criteria, "flags.requestee_id IS NULL") }
+ push(@excluded_columns, 'requestee') unless $cgi->param('do_union');
+ }
+
+ # 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') unless $cgi->param('do_union');
+ 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') unless $cgi->param('do_union');
+ }
+ }
+
+ # 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') unless $cgi->param('do_union');
+ }
+
+ # Add the criteria to the query. We do an intersection by default
+ # but do a union if the "do_union" URL parameter (for which there is no UI
+ # because it's an advanced feature that people won't usually want) is true.
+ my $and_or = $cgi->param('do_union') ? " OR " : " AND ";
+ $query .= " AND (" . join($and_or, @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,
+ 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;
+
+ 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 ];
+
+ # Generate and return the UI (HTML page) from the appropriate template.
+ $template->process("request/queue.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+}
+
+################################################################################
+# Data Validation / Security Authorization
+################################################################################
+
+sub validateStatus {
+ my $status = shift;
+ return if !defined $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;
+
+ 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
+ FROM flagtypes
+ WHERE flagtypes.id IN
+ (SELECT DISTINCT type_id FROM flags)
+ ORDER BY name');
+ return $flag_types;
+}
diff --git a/robots.txt b/robots.txt
new file mode 100644
index 000000000..0f823cb24
--- /dev/null
+++ b/robots.txt
@@ -0,0 +1,3 @@
+User-agent: *
+Allow: /index.cgi
+Disallow: /
diff --git a/runtests.pl b/runtests.pl
new file mode 100755
index 000000000..092d8069b
--- /dev/null
+++ b/runtests.pl
@@ -0,0 +1,43 @@
+#!/usr/bin/perl -w
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Mike Norton.
+# Portions created by the Initial Developer are Copyright (C) 2002
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#
+
+# Make it harder for us to do dangerous things in Perl.
+use diagnostics;
+use strict;
+use lib qw(lib);
+
+use Test::Harness qw(&runtests $verbose);
+
+$verbose = 0;
+my $onlytest = "";
+
+foreach (@ARGV) {
+ if (/^(?:-v|--verbose)$/) {
+ $verbose = 1;
+ }
+ else {
+ $onlytest = sprintf("%0.3d",$_);
+ }
+}
+
+runtests(glob("t/$onlytest*.t"));
+
diff --git a/sanitycheck.cgi b/sanitycheck.cgi
new file mode 100755
index 000000000..bd022df73
--- /dev/null
+++ b/sanitycheck.cgi
@@ -0,0 +1,923 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Matthew Tuck <matty@chariot.net.au>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+# Marc Schumann <wurblzap@gmail.com>
+# Frédéric Buclin <LpSolit@gmail.com>
+
+use strict;
+
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Bug;
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Hook;
+use Bugzilla::Util;
+use Bugzilla::Status;
+use Bugzilla::Token;
+
+###########################################################################
+# General subs
+###########################################################################
+
+sub get_string {
+ 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>';
+ print $start_tag . get_string($san_tag, $vars) . "</p>\n";
+ }
+}
+
+###########################################################################
+# Start
+###########################################################################
+
+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->settings->{'lang'}->{'value'});
+}
+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']);
+ }
+}
+my $vars = {};
+
+print $cgi->header() unless Bugzilla->usage_mode == USAGE_MODE_CMDLINE;
+
+# Make sure the user is authorized to access sanitycheck.cgi.
+# 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"});
+
+unless (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) {
+ $template->process('admin/sanitycheck/list.html.tmpl', $vars)
+ || ThrowTemplateError($template->error());
+}
+
+###########################################################################
+# Create missing group_control_map entries
+###########################################################################
+
+if ($cgi->param('createmissinggroupcontrolmapentries')) {
+ Status('group_control_map_entries_creation');
+
+ 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)});
+
+ 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,
+ bgm.group_id,
+ gcm.membercontrol,
+ groups.name,
+ products.name
+ FROM bugs
+ INNER JOIN bug_group_map AS bgm
+ ON bugs.bug_id = bgm.bug_id
+ INNER JOIN groups
+ ON bgm.group_id = groups.id
+ INNER JOIN products
+ ON bugs.product_id = products.id
+ LEFT JOIN group_control_map AS gcm
+ 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);
+ }
+ }
+
+ Status('group_control_map_entries_repaired', {counter => $counter});
+}
+
+###########################################################################
+# Fix missing creation date
+###########################################################################
+
+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)});
+}
+
+###########################################################################
+# Fix everconfirmed
+###########################################################################
+
+if ($cgi->param('repair_everconfirmed')) {
+ Status('everconfirmed_start');
+
+ 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)");
+
+ Status('everconfirmed_end');
+}
+
+###########################################################################
+# Fix entries in Bugs full_text
+###########################################################################
+
+if ($cgi->param('repair_bugs_fulltext')) {
+ Status('bugs_fulltext_start');
+
+ 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');
+
+ foreach my $bugid (@$bug_ids) {
+ Bugzilla::Bug->new($bugid)->_sync_fulltext('new_bug');
+ }
+
+ Status('bugs_fulltext_fixed', {bug_count => scalar(@$bug_ids)});
+}
+
+###########################################################################
+# Send unsent mail
+###########################################################################
+
+if ($cgi->param('rescanallBugMail')) {
+ require Bugzilla::BugMail;
+
+ Status('send_bugmail_start');
+ my $time = $dbh->sql_interval(30, 'MINUTE');
+
+ my $list = $dbh->selectcol_arrayref(qq{
+ SELECT bug_id
+ FROM bugs
+ WHERE (lastdiffed IS NULL
+ OR lastdiffed < delta_ts)
+ AND delta_ts < now() - $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'} = Bugzilla->user;
+
+ foreach my $bugid (@$list) {
+ Bugzilla::BugMail::Send($bugid, $vars);
+ }
+
+ Status('send_bugmail_end') if scalar(@$list);
+
+ unless (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) {
+ $template->process('global/footer.html.tmpl', $vars)
+ || ThrowTemplateError($template->error());
+ }
+ exit;
+}
+
+###########################################################################
+# Remove all references to deleted bugs
+###########################################################################
+
+if ($cgi->param('remove_invalid_bug_references')) {
+ Status('bug_reference_deletion_start');
+
+ $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/') {
+
+ my ($table, $field) = split('/', $pair);
+ $field ||= "bug_id";
+
+ 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");
+
+ if (scalar(@$bug_ids)) {
+ $dbh->do("DELETE FROM $table WHERE $field IN (" . join(',', @$bug_ids) . ")");
+ }
+ }
+
+ $dbh->bz_commit_transaction();
+ Status('bug_reference_deletion_end');
+}
+
+###########################################################################
+# Remove all references to deleted attachments
+###########################################################################
+
+if ($cgi->param('remove_invalid_attach_references')) {
+ Status('attachment_reference_deletion_start');
+
+ $dbh->bz_start_transaction();
+
+ 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');
+
+ 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');
+}
+
+###########################################################################
+# Remove all references to deleted users or groups from whines
+###########################################################################
+
+if ($cgi->param('remove_old_whine_targets')) {
+ Status('whines_obsolete_target_deletion_start');
+
+ $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
+ 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) . ")");
+ }
+ }
+ $dbh->bz_commit_transaction();
+ Status('whines_obsolete_target_deletion_end');
+}
+
+###########################################################################
+# Repair hook
+###########################################################################
+
+Bugzilla::Hook::process('sanitycheck_repair', { status => \&Status });
+
+###########################################################################
+# Checks
+###########################################################################
+Status('checks_start');
+
+###########################################################################
+# Perform referential (cross) checks
+###########################################################################
+
+# This checks that a simple foreign key has a valid primary key value. NULL
+# references are acceptable and cause no problem.
+#
+# The first parameter is the primary key table name.
+# The second parameter is the primary key field name.
+# Each successive parameter represents a foreign key, it must be a list
+# reference, where the list has:
+# the first value is the foreign key table name.
+# the second value is the foreign key field name.
+# the third value is optional and represents a field on the foreign key
+# table to display when the check fails.
+# the fourth value is optional and is a list reference to values that
+# are excluded from checking.
+#
+# FIXME: The excluded values parameter should go away - the QA contact
+# fields should use NULL instead - see bug #109474.
+# The same goes for series; no bug for that yet.
+
+sub CrossCheck {
+ my $table = shift @_;
+ my $field = shift @_;
+ my $dbh = Bugzilla->dbh;
+
+ Status('cross_check_to', {table => $table, field => $field});
+
+ while (@_) {
+ my $ref = shift @_;
+ my ($refertable, $referfield, $keyname, $exceptions) = @$ref;
+
+ $exceptions ||= [];
+ my %exceptions = map { $_ => 1 } @$exceptions;
+
+ Status('cross_check_from', {table => $refertable, field => $referfield});
+
+ 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');
+ }
+ }
+}
+
+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("resolution", "value",
+ ["bugs", "resolution", "bug_id"]);
+
+CrossCheck("bug_severity", "value",
+ ["bugs", "bug_severity", "bug_id"]);
+
+CrossCheck("op_sys", "value",
+ ["bugs", "op_sys", "bug_id"]);
+
+CrossCheck("priority", "value",
+ ["bugs", "priority", "bug_id"]);
+
+CrossCheck("rep_platform", "value",
+ ["bugs", "rep_platform", "bug_id"]);
+
+CrossCheck('series', 'series_id',
+ ['series_data', 'series_id']);
+
+CrossCheck('series_categories', 'id',
+ ['series', 'category'],
+ ["category_group_map", "category_id"],
+ ["series", "subcategory"]);
+
+CrossCheck('whine_events', 'id',
+ ['whine_queries', 'eventid'],
+ ['whine_schedules', 'eventid']);
+
+CrossCheck('attachments', 'attach_id',
+ ['attach_data', 'id'],
+ ['bugs_activity', 'attach_id']);
+
+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.
+#
+# The first parameter is the primary key table name.
+# The second parameter is the primary key first field name.
+# The third parameter is the primary key second field name.
+# Each successive parameter represents a foreign key, it must be a list
+# reference, where the list has:
+# the first value is the foreign key table name
+# the second value is the foreign key first field name.
+# the third value is the foreign key second field name.
+# the fourth value is optional and represents a field on the foreign key
+# table to display when the check fails
+
+sub DoubleCrossCheck {
+ my $table = shift @_;
+ my $field1 = shift @_;
+ my $field2 = shift @_;
+ my $dbh = Bugzilla->dbh;
+
+ Status('double_cross_check_to',
+ {table => $table, field1 => $field1, field2 => $field2});
+
+ while (@_) {
+ my $ref = shift @_;
+ my ($refertable, $referfield1, $referfield2, $keyname) = @$ref;
+
+ Status('double_cross_check_from',
+ {table => $refertable, field1 => $referfield1, field2 =>$referfield2});
+
+ my $d_cross_check = $dbh->selectall_arrayref(qq{
+ SELECT DISTINCT $refertable.$referfield1,
+ $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');
+ }
+ }
+}
+
+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"]);
+
+###########################################################################
+# Perform login checks
+###########################################################################
+
+Status('profile_login_start');
+
+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');
+}
+
+###########################################################################
+# Perform keyword checks
+###########################################################################
+
+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 $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;
+ }
+}
+
+###########################################################################
+# Check for flags being in incorrect products and components
+###########################################################################
+
+Status('flag_check_start');
+
+my $invalid_flags = $dbh->selectall_arrayref(
+ 'SELECT DISTINCT flags.id, flags.bug_id, flags.attach_id
+ FROM flags
+ INNER JOIN bugs
+ ON flags.bug_id = bugs.bug_id
+ LEFT JOIN flaginclusions AS i
+ 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');
+
+my @invalid_flags = @$invalid_flags;
+
+$invalid_flags = $dbh->selectall_arrayref(
+ '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)');
+
+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');
+ }
+ 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');
+ }
+}
+
+###########################################################################
+# General bug checks
+###########################################################################
+
+sub BugCheck {
+ 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)});
+ }
+ }
+}
+
+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');
+
+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');
+
+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 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');
+
+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');
+BugCheck("bugs WHERE bug_status NOT IN ($open_states) AND resolution = ''",
+ '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');
+
+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');
+
+###########################################################################
+# Control Values
+###########################################################################
+
+# 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 $query = qq{
+ SELECT COUNT(product_id)
+ FROM group_control_map
+ WHERE membercontrol NOT IN( $groups )
+ OR othercontrol NOT IN( $groups )
+ OR ((membercontrol != othercontrol)
+ AND (membercontrol != } . CONTROLMAPSHOWN . q{)
+ AND ((membercontrol != } . CONTROLMAPDEFAULT . q{)
+ 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_violation');
+BugCheck("bugs
+ INNER JOIN bug_group_map
+ ON bugs.bug_id = bug_group_map.bug_id
+ LEFT JOIN group_control_map
+ ON bugs.product_id = group_control_map.product_id
+ 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');
+
+BugCheck("bugs
+ INNER JOIN group_control_map
+ ON bugs.product_id = group_control_map.product_id
+ INNER JOIN groups
+ ON group_control_map.group_id = groups.id
+ LEFT JOIN bug_group_map
+ ON bugs.bug_id = bug_group_map.bug_id
+ 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');
+
+###########################################################################
+# Unsent mail
+###########################################################################
+
+Status('unsent_bugmail_check');
+
+my $time = $dbh->sql_interval(30, 'MINUTE');
+my $badbugs = $dbh->selectcol_arrayref(qq{
+ SELECT bug_id
+ FROM bugs
+ WHERE (lastdiffed IS NULL OR lastdiffed < delta_ts)
+ AND delta_ts < now() - $time
+ ORDER BY bug_id});
+
+
+if (scalar(@$badbugs > 0)) {
+ Status('unsent_bugmail_alert', {badbugs => $badbugs}, 'alert');
+ Status('unsent_bugmail_fix');
+}
+
+###########################################################################
+# Whines
+###########################################################################
+
+Status('whines_obsolete_target_start');
+
+my $display_repair_whines_link = 0;
+foreach my $target (['groups', 'id', MAILTO_GROUP],
+ ['profiles', 'userid', MAILTO_USER])
+{
+ 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;
+ }
+}
+Status('whines_obsolete_target_fix') if $display_repair_whines_link;
+
+###########################################################################
+# Check hook
+###########################################################################
+
+Bugzilla::Hook::process('sanitycheck_check', { status => \&Status });
+
+###########################################################################
+# End
+###########################################################################
+
+Status('checks_completed');
+
+unless (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) {
+ $template->process('global/footer.html.tmpl', $vars)
+ || ThrowTemplateError($template->error());
+}
diff --git a/sanitycheck.pl b/sanitycheck.pl
new file mode 100755
index 000000000..e0128b58a
--- /dev/null
+++ b/sanitycheck.pl
@@ -0,0 +1,114 @@
+#!/usr/bin/perl -w
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Frédéric Buclin.
+# Portions created by Frédéric Buclin are Copyright (C) 2007
+# Frédéric Buclin. All Rights Reserved.
+#
+# Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
+
+use strict;
+
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::User;
+use Bugzilla::Mailer;
+
+use Getopt::Long;
+use Pod::Usage;
+
+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 $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 $cgi = Bugzilla->cgi;
+my $template = Bugzilla->template;
+
+# Authenticate using this user account.
+Bugzilla->set_user($user);
+
+# Pass this param to sanitycheck.cgi.
+$cgi->param('verbose', $verbose);
+
+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;
+
+ $template->process('email/sanitycheck.txt.tmpl', $vars, \$message)
+ || ThrowTemplateError($template->error());
+
+ MessageToMTA($message);
+}
+
+
+__END__
+
+=head1 NAME
+
+sanitycheck.pl - Perl script to perform a sanity check at the command line
+
+=head1 SYNOPSIS
+
+ ./sanitycheck.pl [--help]
+ ./sanitycheck.pl [--verbose] --login <user@domain.com>
+
+=head1 OPTIONS
+
+=over
+
+=item B<--help>
+
+Displays this help text
+
+=item B<--verbose>
+
+Causes this script to be more verbose in its output. Without this option,
+the script will return only errors. With the option, the script will append
+all output to the email.
+
+=item B<--login>
+
+This should be passed the email address of a user that is capable of
+running the Sanity Check process, a user with the editcomponents priv. This
+user will receive an email with the results of the script run.
+
+=back
+
+=head1 DESCRIPTION
+
+This script provides a way of running a 'Sanity Check' on the database
+via either a CLI or cron. It is equivalent to calling sanitycheck.cgi
+via a web broswer.
diff --git a/search_plugin.cgi b/search_plugin.cgi
new file mode 100755
index 000000000..4dfe8fa9f
--- /dev/null
+++ b/search_plugin.cgi
@@ -0,0 +1,43 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
+
+use strict;
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Error;
+use Bugzilla::Constants;
+
+Bugzilla->login();
+
+my $cgi = Bugzilla->cgi;
+my $template = Bugzilla->template;
+my $vars = {};
+
+# Return the appropriate HTTP response headers.
+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;
+}
+$template->process("search/search-plugin.xml.tmpl", $vars)
+ || ThrowTemplateError($template->error());
diff --git a/show_activity.cgi b/show_activity.cgi
new file mode 100755
index 000000000..27096018f
--- /dev/null
+++ b/show_activity.cgi
@@ -0,0 +1,66 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Myk Melez <myk@mozilla.org>
+# Gervase Markham <gerv@gerv.net>
+
+use strict;
+
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Error;
+use Bugzilla::Bug;
+
+my $cgi = Bugzilla->cgi;
+my $template = Bugzilla->template;
+my $vars = {};
+
+###############################################################################
+# Begin Data/Security Validation
+###############################################################################
+
+# 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 $bug = Bugzilla::Bug->check($id);
+
+###############################################################################
+# End Data/Security Validation
+###############################################################################
+
+# Run queries against the shadow DB. In the worst case, new changes are not
+# visible immediately due to replication lag.
+Bugzilla->switch_to_shadow_db;
+
+($vars->{'operations'}, $vars->{'incomplete_data'}) =
+ Bugzilla::Bug::GetBugActivity($bug->id);
+
+$vars->{'bug'} = $bug;
+
+print $cgi->header();
+
+$template->process("bug/activity/show.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+
diff --git a/show_bug.cgi b/show_bug.cgi
new file mode 100755
index 000000000..64d2e875f
--- /dev/null
+++ b/show_bug.cgi
@@ -0,0 +1,129 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+
+use strict;
+
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::User;
+use Bugzilla::Keyword;
+use Bugzilla::Bug;
+
+my $cgi = Bugzilla->cgi;
+my $template = Bugzilla->template;
+my $vars = {};
+
+my $user = Bugzilla->login();
+
+# Editable, 'single' HTML bugs are treated slightly specially in a few places
+my $single = !$cgi->param('format')
+ && (!$cgi->param('ctype') || $cgi->param('ctype') 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 Bugzilla->cgi->header();
+ $template->process("bug/choose.html.tmpl", $vars) ||
+ ThrowTemplateError($template->error());
+ exit;
+}
+
+my $format = $template->get_format("bug/show", scalar $cgi->param('format'),
+ scalar $cgi->param('ctype'));
+
+my @bugs = ();
+my %marks;
+
+# If the user isn't logged in, we use data from the shadow DB. If he plans
+# to edit the bug(s), he will have to log in first, meaning that the data
+# will be reloaded anyway, from the main DB.
+Bugzilla->switch_to_shadow_db unless $user->id;
+
+if ($single) {
+ my $id = $cgi->param('id');
+ push @bugs, Bugzilla::Bug->check($id);
+ 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 {
+ foreach my $id ($cgi->param('id')) {
+ # Be kind enough and accept URLs of the form: id=1,2,3.
+ my @ids = split(/,/, $id);
+ foreach (@ids) {
+ my $bug = new Bugzilla::Bug($_);
+ # This is basically a backwards-compatibility hack from when
+ # Bugzilla::Bug->new used to set 'NotPermitted' if you couldn't
+ # see the bug.
+ if (!$bug->{error} && !$user->can_see_bug($bug->bug_id)) {
+ $bug->{error} = 'NotPermitted';
+ }
+ push(@bugs, $bug);
+ }
+ }
+}
+
+$vars->{'bugs'} = \@bugs;
+$vars->{'marks'} = \%marks;
+
+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
+# attachments" without listing almost all the fields.
+my @fieldlist = (Bugzilla::Bug->fields, 'flag', 'group', 'long_desc',
+ 'attachment', 'attachmentdata', 'token');
+my %displayfields;
+
+if ($cgi->param("field")) {
+ @fieldlist = $cgi->param("field");
+}
+
+unless (Bugzilla->user->is_timetracker) {
+ @fieldlist = grep($_ !~ /(^deadline|_time)$/, @fieldlist);
+}
+
+foreach (@fieldlist) {
+ $displayfields{$_} = 1;
+}
+
+foreach ($cgi->param("excludefield")) {
+ $displayfields{$_} = undef;
+}
+
+$vars->{'displayfields'} = \%displayfields;
+
+print $cgi->header($format->{'ctype'});
+
+$template->process("$format->{'template'}", $vars)
+ || ThrowTemplateError($template->error());
diff --git a/showattachment.cgi b/showattachment.cgi
new file mode 100755
index 000000000..e90a01533
--- /dev/null
+++ b/showattachment.cgi
@@ -0,0 +1,40 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Jacob Steenhagen <jake@bugzilla.org>
+
+use strict;
+
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Util;
+
+my $cgi = Bugzilla->cgi;
+
+my $id = $cgi->param('attach_id');
+detaint_natural($id) if defined $id;
+$id ||= "";
+
+print $cgi->redirect(-location=>"attachment.cgi?id=$id",
+ -status=>'301 Permanent Redirect');
+
+exit;
diff --git a/showdependencygraph.cgi b/showdependencygraph.cgi
new file mode 100755
index 000000000..162dd2afb
--- /dev/null
+++ b/showdependencygraph.cgi
@@ -0,0 +1,342 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Gervase Markham <gerv@gerv.net>
+
+use strict;
+
+use lib qw(. lib);
+
+use File::Temp;
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Install::Filesystem;
+use Bugzilla::Util;
+use Bugzilla::Error;
+use Bugzilla::Bug;
+use Bugzilla::Status;
+
+Bugzilla->login();
+
+my $cgi = Bugzilla->cgi;
+my $template = Bugzilla->template;
+my $vars = {};
+# Connect to the shadow database if this installation is using one to improve
+# performance.
+my $dbh = Bugzilla->switch_to_shadow_db();
+
+local our (%seen, %edgesdone, %bugtitles);
+
+# 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
+# pull possible HTML titles from the %bugtitles hash (filled elsewhere
+# in the code)
+
+# The dot mapdata lines have the following format (\nsummary is optional):
+# 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 \((.*),(.*)\) \((.*),(.*)\) (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.
+ my $bugtitle = html_quote(clean_text($bugtitles{$bugid}));
+ $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;
+
+ $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";
+ $seen{$blocked} = 1;
+ $seen{$dependson} = 1;
+ }
+}
+
+# The list of valid directions. Some are not proposed in the dropdrown
+# menu despite the fact that they are valid.
+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';
+}
+
+my $display = $cgi->param('display') || 'tree';
+my $webdotdir = bz_locations()->{'webdotdir'};
+
+if (!defined $cgi->param('id') && $display ne 'doall') {
+ ThrowCodeError("missing_bug_id");
+}
+
+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 => $! });
+
+my $urlbase = Bugzilla->params->{'urlbase'};
+
+print $fh "digraph G {";
+print $fh qq{
+graph [URL="${urlbase}query.cgi", rankdir=$rankdir]
+node [URL="${urlbase}show_bug.cgi?id=\\N", style=filled, color=lightgrey]
+};
+
+my %baselist;
+
+if ($display eq 'doall') {
+ my $dependencies = $dbh->selectall_arrayref(
+ "SELECT blocked, dependson FROM dependencies");
+
+ foreach my $dependency (@$dependencies) {
+ my ($blocked, $dependson) = @$dependency;
+ AddLink($blocked, $dependson, $fh);
+ }
+} else {
+ foreach my $i (split('[\s,]+', $cgi->param('id'))) {
+ 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
+ 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);
+ }
+ }
+ }
+ # 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 @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;
+ }
+}
+
+my $sth = $dbh->prepare(
+ q{SELECT bug_status, resolution, short_desc
+ FROM bugs
+ WHERE bugs.bug_id = ?});
+foreach my $k (keys(%seen)) {
+ # Retrieve bug information from the database
+ my ($stat, $resolution, $summary) = $dbh->selectrow_array($sth, undef, $k);
+
+ # Resolution and summary are shown only if user can see the bug
+ if (!Bugzilla->user->can_see_bug($k)) {
+ $resolution = $summary = '';
+ }
+
+ $vars->{'short_desc'} = $summary if ($k eq $cgi->param('id'));
+
+ 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;
+ 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.
+ $bugtitles{$k} = trim("$stat $resolution");
+
+ # 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";
+ }
+}
+
+
+print $fh "}\n";
+close $fh;
+
+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);
+}
+
+# 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);
+ if (file_mod_time($f) < $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->{'multiple_bugs'} = ($cgi->param('id') =~ /[ ,]/);
+$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();
+$template->process("bug/dependency-graph.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
diff --git a/showdependencytree.cgi b/showdependencytree.cgi
new file mode 100755
index 000000000..8683c190c
--- /dev/null
+++ b/showdependencytree.cgi
@@ -0,0 +1,141 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Andreas Franke <afranke@mathweb.org>
+# Christian Reis <kiko@async.com.br>
+# Myk Melez <myk@mozilla.org>
+# Frédéric Buclin <LpSolit@gmail.com>
+
+use strict;
+
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Error;
+use Bugzilla::Bug;
+
+use List::Util qw(max);
+
+my $user = Bugzilla->login();
+
+my $cgi = Bugzilla->cgi;
+my $template = Bugzilla->template;
+my $vars = {};
+# Connect to the shadow database if this installation is using one to improve
+# performance.
+my $dbh = Bugzilla->switch_to_shadow_db();
+
+################################################################################
+# Data/Security Validation #
+################################################################################
+
+# 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;
+
+local our $hide_resolved = $cgi->param('hide_resolved') ? 1 : 0;
+
+local our $maxdepth = $cgi->param('maxdepth') || 0;
+if ($maxdepth !~ /^\d+$/) { $maxdepth = 0 };
+
+################################################################################
+# Main Section #
+################################################################################
+
+# Stores the greatest depth to which either tree goes.
+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 = {};
+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 = {};
+GenerateTree($id, "blocked", 1, $blocked_tree, $blocked_ids);
+$vars->{'blocked_tree'} = $blocked_tree;
+$vars->{'blocked_ids'} = [keys(%$blocked_ids)];
+
+$vars->{'realdepth'} = $realdepth;
+
+$vars->{'bugid'} = $id;
+$vars->{'maxdepth'} = $maxdepth;
+$vars->{'hide_resolved'} = $hide_resolved;
+
+print $cgi->header();
+$template->process("bug/dependency-tree.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+
+################################################################################
+# Recursive Tree Generation Function #
+################################################################################
+
+sub GenerateTree {
+ # Generates a dependency tree for a given bug. Calls itself recursively
+ # to generate sub-trees for the bug's dependencies.
+ my ($bug_id, $relationship, $depth, $bugs, $ids) = @_;
+
+ my @dependencies;
+ if ($relationship eq 'dependson') {
+ @dependencies = @{$bugs->{$bug_id}->dependson};
+ }
+ else {
+ @dependencies = @{$bugs->{$bug_id}->blocked};
+ }
+
+ # Don't do anything if this bug doesn't have any dependencies.
+ return unless scalar(@dependencies);
+
+ # Record this depth in the global $realdepth variable if it's farther
+ # than we've gone before.
+ $realdepth = max($realdepth, $depth);
+
+ foreach my $dep_id (@dependencies) {
+ # Get this dependency's record from the database and generate
+ # its sub-tree if we haven't already done so (which happens
+ # when bugs appear in dependency trees multiple times).
+ if (!$bugs->{$dep_id}) {
+ $bugs->{$dep_id} = new Bugzilla::Bug($dep_id);
+ GenerateTree($dep_id, $relationship, $depth+1, $bugs, $ids);
+ }
+
+ # Add this dependency to the list of this bug's dependencies
+ # if it exists, if we haven't exceeded the maximum depth the user
+ # wants the tree to go, and if the dependency isn't resolved
+ # (if we're ignoring resolved dependencies).
+ if (!$bugs->{$dep_id}->{'error'}
+ && Bugzilla->user->can_see_bug($dep_id)
+ && (!$maxdepth || $depth <= $maxdepth)
+ && ($bugs->{$dep_id}->isopened || !$hide_resolved))
+ {
+ # Due to AUTOLOAD in Bug.pm, we cannot add 'dependencies'
+ # as a bug object attribute from here.
+ push(@{$bugs->{'dependencies'}->{$bug_id}}, $dep_id);
+ $ids->{$dep_id} = 1;
+ }
+ }
+}
diff --git a/sidebar.cgi b/sidebar.cgi
new file mode 100755
index 000000000..35c4e64ad
--- /dev/null
+++ b/sidebar.cgi
@@ -0,0 +1,49 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s): Jacob Steenhagen <jake@bugzilla.org>
+
+use strict;
+
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Error;
+
+Bugzilla->login();
+my $cgi = Bugzilla->cgi;
+my $template = Bugzilla->template;
+
+###############################################################################
+# Main Body Execution
+###############################################################################
+
+# This sidebar is currently for use with Mozilla based web browsers.
+# Internet Explorer 6 is supposed to have a similar feature, but it
+# most likely won't support XUL ;) When that does come out, this
+# can be expanded to output normal HTML for IE. Until then, I like
+# the way Scott's sidebar looks so I'm using that as the base for
+# this file.
+# http://bugzilla.mozilla.org/show_bug.cgi?id=37339
+
+my $useragent = $ENV{HTTP_USER_AGENT};
+if ($useragent =~ m:Mozilla/([1-9][0-9]*):i && $1 >= 5 && $useragent !~ m/compatible/i) {
+ print $cgi->header("application/vnd.mozilla.xul+xml");
+ # Generate and return the XUL from the appropriate template.
+ $template->process("sidebar.xul.tmpl")
+ || ThrowTemplateError($template->error());
+} else {
+ ThrowUserError("sidebar_supports_mozilla_only");
+}
diff --git a/skins/README b/skins/README
new file mode 100644
index 000000000..111c00f03
--- /dev/null
+++ b/skins/README
@@ -0,0 +1,20 @@
+There are three directories here, standard/, custom/, and contrib/.
+
+standard/ holds the standard stylesheets. These are used no matter
+what skin the user selects. If the user selects the "Classic" skin,
+then *only* the standard/ stylesheets are used.
+
+contrib/ holds "skins" that the user can select in their preferences.
+skins are in directories, and they contain files with the same names
+as the files in skins/standard/. Simply putting a new directory
+into the contrib/ directory adds a new skin as an option in users'
+preferences.
+
+custom/ allows you to locally override the standard/ and contrib/ CSS.
+If you put files into the custom/ directory with the same names as the CSS
+files in skins/standard/, you can override the standard/ and contrib/
+CSS. For example, if you want to override some CSS in
+skins/standard/global.css, then you should create a file called "global.css"
+in custom/ and put some CSS in it. The CSS you put into files in custom/ will
+be used *in addition* to the CSS in skins/standard/ or the CSS in
+skins/contrib/. It will apply to every skin.
diff --git a/skins/contrib/Dusk/buglist.css b/skins/contrib/Dusk/buglist.css
new file mode 100644
index 000000000..2e14368b1
--- /dev/null
+++ b/skins/contrib/Dusk/buglist.css
@@ -0,0 +1,24 @@
+/* The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is the Bugzilla Bug Tracking System.
+ *
+ * The Initial Developer of the Original Code is Mike Schrag.
+ * Portions created by Marc Schumann are Copyright (c) 2007 Mike Schrag.
+ * All rights reserved.
+ *
+ * Contributor(s): Mike Schrag <mschrag@pobox.com>
+ * Byron Jones <bugzilla@glob.com.au>
+ * Marc Schumann <wurblzap@gmail.com>
+ */
+
+tr.bz_bugitem:hover {
+ background-color: #ccccff;
+}
diff --git a/skins/contrib/Dusk/global.css b/skins/contrib/Dusk/global.css
new file mode 100644
index 000000000..3a18e401e
--- /dev/null
+++ b/skins/contrib/Dusk/global.css
@@ -0,0 +1,253 @@
+/* The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is the Bugzilla Bug Tracking System.
+ *
+ * The Initial Developer of the Original Code is Mike Schrag.
+ * Portions created by Marc Schumann are Copyright (c) 2007 Mike Schrag.
+ * All rights reserved.
+ *
+ * Contributor(s): Mike Schrag <mschrag@pobox.com>
+ * Byron Jones <bugzilla@glob.com.au>
+ * Marc Schumann <wurblzap@gmail.com>
+ * Frédéric Buclin <LpSolit@gmail.com>
+ */
+
+body {
+ background: #c8c8c8;
+ font-family: Helvetica, Arial, Geneva;
+ padding-left: 1em;
+ padding-right: 1em;
+}
+
+/* page title */
+
+#titles {
+ -moz-border-radius-topleft: 5px;
+ -moz-border-radius-topright: 5px;
+}
+
+#header .links, #footer {
+ background-color: #929bb1;
+ color: #ddd;
+}
+
+#header {
+ -moz-border-radius-bottomleft: 5px;
+ -moz-border-radius-bottomright: 5px;
+ border: none;
+}
+
+#header a, #footer a {
+ color: white;
+ text-decoration: none;
+}
+#header a:hover, #footer a:hover {
+ text-decoration: underline;
+}
+
+/* body */
+
+#bugzilla-body {
+ background: #f0f0f0;
+ color: black;
+ border: 1px solid #747e93;
+ padding: 10px;
+ font-size: 10pt;
+ -moz-border-radius: 5px;
+}
+
+a {
+ color: #6070cf;
+}
+a:hover {
+ color: #8090ef;
+}
+
+hr {
+ border-color: #969696;
+ border-style: dashed;
+ border-width: 1px;
+ margin-top: 10px;
+}
+
+/* edit */
+
+#bugzilla-body th {
+ font-weight: bold;
+ vertical-align: top;
+ white-space: nowrap;
+}
+
+#bug-form td {
+ padding-top: 2px;
+}
+
+/* attachments */
+
+#attachment-list {
+ border: 2px solid #c8c8ba;
+ font-size: 9pt;
+}
+
+#attachment-list th {
+ background-color: #e6e6d8;
+ border: none;
+ border-bottom: 1px solid #c8c8ba;
+ text-align: left;
+}
+
+#attachment-list th a {
+ color: #646456;
+}
+
+#attachment-list td {
+ border: none;
+}
+
+#attachment-list-actions td {
+ border-top: 1px solid #c8c8ba;
+}
+
+/************/
+/* Comments */
+/************/
+
+#comments th {
+ font-size: 9pt;
+ font-weight: bold;
+ padding-top: 5px;
+ padding-right: 5px;
+ padding-bottom: 10px;
+ text-align: right;
+ vertical-align: top;
+ white-space: nowrap;
+}
+
+#comments td {
+ padding-top: 2px;
+}
+
+.reply-button a {
+ padding-left: 2px;
+ padding-right: 2px;
+}
+
+.bz_comment {
+ background-color: #e8e8e8;
+ margin: 1px 1px 10px 1px;
+ border-width: 1px;
+ border-style: solid;
+ border-color: #c8c8ba;
+ padding: 5px;
+ font-size: 9pt;
+}
+
+.bz_comment_head, .bz_first_comment_head {
+ margin: 0; padding: 0;
+ background-color: transparent;
+ font-weight: bold;
+}
+
+.bz_comment_user {
+ margin-left: 0;
+}
+
+.bz_comment.bz_private {
+ background-color: #f0e8e8;
+ border-color: #f8c8ba;
+}
+
+.comment_rule {
+ display: none;
+}
+
+/* footer */
+
+#footer {
+ border: 1px solid #747e93;
+ width: 100%;
+ -moz-border-radius: 5px;
+}
+
+#footer #links-actions,
+#footer #links-edit,
+#footer #links-saved,
+#footer #links-special {
+ margin-top: 2ex;
+}
+
+#footer .links {
+ border-spacing: 30px;
+ margin-bottom: 2ex;
+}
+
+.separator {
+ color: #cccccc;
+}
+
+/* tabs */
+
+.tabbed .tabbody {
+ background: #f8f8f8;
+ padding: 1em;
+ border-style: solid;
+ border-color: #000000;
+ border-width: 0 3px 3px 1px;
+}
+
+.tabs {
+ margin: 0;
+ padding: 0;
+ border-collapse: collapse;
+}
+
+.tabs td {
+ background: #c8c8c8;
+ border-width: 1px;
+}
+
+.tabs td.selected {
+ background: #f8f8f8;
+ border-width: 1px 3px 0 1px;
+}
+
+.tabs td.spacer {
+ background: transparent;
+ border-top: none;
+ border-left: none;
+ border-right: none;
+}
+
+/* other */
+
+.bz_row_odd {
+ background-color: #f0f0f0;
+}
+
+/* Rules specific for printing */
+@media print {
+ #header,
+ #footer,
+ .navigation {
+ display: none;
+ }
+
+ body {
+ background-image: none;
+ background-color: #ffffff;
+ }
+
+ #bugzilla-body {
+ border: none;
+ margin: 0;
+ padding: 0;
+ }
+}
diff --git a/skins/contrib/Dusk/index.css b/skins/contrib/Dusk/index.css
new file mode 100644
index 000000000..c9c2d1705
--- /dev/null
+++ b/skins/contrib/Dusk/index.css
@@ -0,0 +1,9 @@
+/*
+ * Custom rules for index.css.
+ * The rules you put here override rules in that stylesheet.
+ */
+
+ div#page-index .outro
+ {
+ clear:both;
+ }
diff --git a/skins/standard/IE-fixes.css b/skins/standard/IE-fixes.css
new file mode 100644
index 000000000..fc2225398
--- /dev/null
+++ b/skins/standard/IE-fixes.css
@@ -0,0 +1,56 @@
+/* The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is the Bugzilla Bug Tracking System.
+ *
+ * Contributor(s): Marc Schumann <wurblzap@gmail.com>
+ */
+
+.bz_comment_text, .uneditable_textarea {
+ white-space: pre;
+ word-wrap: break-word;
+}
+
+.component_table {
+ margin-top: .5em;
+}
+
+form#Create #comp_desc {
+ margin: .5em 1em;
+}
+
+#footer #useful-links li {
+ padding-bottom: 0.8ex;
+}
+
+#footer .label {
+ display: block;
+ float: left;
+ width: 8.2em;
+ padding-bottom: 0.1ex;
+}
+
+#footer #links-actions .label {
+ padding-top: 0.35em;
+}
+
+#footer .links {
+ display: inline;
+}
+
+#bug_id_container, .search_field_grid,
+.search_email_fields, ul.bug_changes li {
+ zoom: 1;
+ display: inline;
+}
+
+#keyword_container .yui-ac-content {
+ _height: 30em; /* ie6 */
+}
diff --git a/skins/standard/admin.css b/skins/standard/admin.css
new file mode 100644
index 000000000..6b330ef6d
--- /dev/null
+++ b/skins/standard/admin.css
@@ -0,0 +1,127 @@
+/* The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is the Bugzilla Bug Tracking System.
+ *
+ * Contributor(s): Marc Schumann <wurblzap@gmail.com>
+ */
+
+.warningmessages, .criticalmessages {
+ background-color: white;
+ border-style: solid;
+ border-width: 1px;
+ padding: 1ex 1ex 1ex 4ex;
+ margin: 1ex;
+}
+
+.warningmessages {
+ border-color: yellow;
+}
+
+.criticalmessages {
+ border-color: red;
+}
+
+.alert {
+ color: red;
+ background-color: inherit;
+}
+
+p.areyoureallyreallysure {
+ color: red;
+ font-size: 120%;
+ font-weight: bold;
+}
+
+tr.param_disabled {
+ background-color: lightgrey;
+}
+
+td.admin_links {
+ width: 50%;
+ padding: 1em;
+ vertical-align: top;
+}
+
+td.admin_links dt {
+ margin-top: 1em;
+}
+
+td.admin_links dt.forbidden, td.admin_links dd.forbidden {
+ font-size: smaller;
+ font-style: italic;
+ color: #aaa;
+}
+
+td.admin_links dt.forbidden a, td.admin_links dd.forbidden a {
+ text-decoration: none;
+ color: inherit;
+ cursor: default;
+}
+
+.col-header {
+ width: 8em;
+}
+
+.checkbox-cell {
+ border: 1px black solid;
+}
+
+/* Grey-green color */
+.open-status {
+ color: #286;
+}
+
+/* Brown-red color */
+.closed-status {
+ color: #a63;
+}
+
+/* Dark green color */
+.checked {
+ background-color: #5b4;
+}
+
+/* Dark red color */
+td.forbidden {
+ background-color: #811;
+}
+
+/* Light green color */
+td.set {
+ background-color: #efe;
+}
+
+/* Light red color */
+td.unset {
+ background-color: #fee;
+}
+
+tr.highlight:hover {
+ background-color: yellow;
+}
+
+th.title {
+ font-size: larger;
+ text-align: center;
+ vertical-align: middle;
+}
+
+#edit_custom_field tr {
+ vertical-align: top;
+}
+
+#edit_custom_field th {
+ text-align: right;
+}
+
+#edit_custom_field th.narrow_label {
+ white-space: normal;
+}
diff --git a/skins/standard/attachment.css b/skins/standard/attachment.css
new file mode 100644
index 000000000..aa0af37f3
--- /dev/null
+++ b/skins/standard/attachment.css
@@ -0,0 +1,242 @@
+ /* The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is the Bugzilla Bug Tracking System.
+ *
+ * Contributor(s): Myk Melez <myk@mozilla.org>
+ * Joel Peshkin <bugreport@peshkin.net>
+ * Erik Stambaugh <erik@dasbistro.com>
+ * Marc Schumann <wurblzap@gmail.com>
+ * Guy Pyrzak <guy.pyrzak@gmail.com>
+ */
+
+table.attachment_entry th {
+ text-align: right;
+ vertical-align: baseline;
+ white-space: nowrap;
+}
+
+table.attachment_entry td {
+ text-align: left;
+ vertical-align: baseline;
+ padding-bottom: 5px;
+}
+
+table#flags th,
+table#flags td {
+ text-align: left;
+ vertical-align: baseline;
+ font-size: small;
+}
+
+/* Rules used to view patches in diff mode. */
+
+.file_head {
+ font-weight: bold;
+ font-size: 1em;
+ background-color: #c3c3c3;
+ border: 1px solid black;
+}
+
+.file_head a {
+ text-decoration: none;
+ font-family: monospace;
+ font-size: 1.1em;
+}
+
+.file_collapse {
+ display: none;
+}
+
+.section_head {
+ background-color: #f0f0f0;
+ border: 1px solid black;
+ text-align: left;
+}
+
+table.file_table {
+ table-layout: fixed;
+ width: 100%;
+ empty-cells: show;
+ border-spacing: 0px;
+ border-collapse: collapse;
+ /* draw border below last open context section in listing */
+ border-bottom: 1px solid black;
+}
+
+tbody.file pre {
+ display: inline;
+ white-space: pre-wrap; /* CSS 3 & CSS 2.1 */
+ white-space: -moz-pre-wrap; /* Gecko < 1.9.1 */
+ white-space: -o-pre-wrap; /* Opera 7 */
+ font-size: 0.9em;
+}
+
+tbody.file pre:empty {
+ display: block;
+}
+
+.changed {
+ background-color: lightblue;
+}
+
+.added {
+ background-color: lightgreen;
+}
+
+.removed {
+ background-color: #FFCC99;
+}
+
+.num {
+ background-color: #ffe9ae;
+ text-align:right;
+ padding: 0 0.3em;
+ width: 3em;
+}
+
+.warning {
+ color: red
+}
+
+table.attachment_info th {
+ text-align: right;
+ vertical-align: top;
+}
+
+table.attachment_info td {
+ text-align: left;
+ vertical-align: top;
+}
+
+/* Text displayed when the attachment is not viewable by the web browser */
+#noview {
+ text-align: left;
+ vertical-align: middle;
+}
+
+#attachment_attributes div {
+ padding-bottom: 0.4em;
+}
+
+#attachment_attributes label,
+#attachment_attributes span.label,
+#attachment_actions span.label
+{
+ font-weight: bold;
+}
+
+#attachment_attributes .block {
+ display: block;
+}
+
+#smallCommentFrame, #attachment_flags {
+ float: left;
+}
+
+#smallCommentFrame {
+ margin-right: 1.5em;
+}
+
+#attachment_comments_and_flags, #attachment_actions {
+ clear: both;
+ margin-bottom: 1ex;
+}
+
+#attachment_information_read_only .title {
+ font-weight: bold;
+ font-size: 1.5em;
+ padding: 0;
+ margin: 0;
+}
+
+#attachment_information_read_only .title #bz_edit {
+ font-size: 0.7em;
+}
+
+#attachment_information_read_only .details {
+ font-size: 90%;
+}
+
+#attachment_info.read #attachment_information_edit {
+ display: none;
+}
+
+#attachment_info.edit #attachment_information_read_only {
+ display: none;
+}
+
+#attachment_info.edit #attachment_view_window {
+ float: left;
+ width: 80%;
+}
+
+#attachment_info.edit #attachment_information_edit {
+ width: 20%;
+}
+
+#attachment_info.edit #attachment_information_edit input.text,
+#attachment_info.edit #attachment_information_edit textarea {
+ width: 90%;
+}
+
+#attachment_isobsolete {
+ padding-right: 1em;
+}
+
+#attachment_information_edit {
+ float: left;
+}
+
+#smallCommentFrame textarea {
+ display: block;
+}
+
+textarea.bz_private {
+ border: 1px solid #F8C8BA;
+}
+
+#update {
+ clear: both;
+ display: block;
+}
+
+textarea {
+ font-family: monospace;
+}
+
+div#update_container {
+ clear: both;
+ padding: 1.5em 0;
+}
+
+#attachment_flags {
+ margin-bottom: 1em;
+}
+
+#attachment_flags p {
+ padding-bottom: 0;
+ margin-bottom: 0;
+}
+
+#editFrame, #viewDiffFrame, #viewFrame {
+ height: 400px;
+ width: 95%;
+ margin-left: 2%;
+}
+
+.details span.bz_private{
+ border-left: 1px solid darkred;
+ padding-left: 0.5em;
+}
+
+.no_javascript .bz_hide, .no_javascript .bz_edit {
+ display: none;
+}
diff --git a/skins/standard/buglist.css b/skins/standard/buglist.css
new file mode 100644
index 000000000..46c5f608a
--- /dev/null
+++ b/skins/standard/buglist.css
@@ -0,0 +1,117 @@
+/* The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is the Bugzilla Bug Tracking System.
+ *
+ * The Initial Developer of the Original Code is Netscape Communications
+ * Corporation. Portions created by Netscape are
+ * Copyright (C) 1998 Netscape Communications Corporation. All
+ * Rights Reserved.
+ *
+ * Contributor(s): Myk Melez <myk@mozilla.org>
+ */
+
+.search_description {
+ margin: .5em 0;
+ padding: 0;
+}
+.search_description li {
+ list-style-type: none;
+ display: inline;
+ margin-right: 2em;
+}
+
+.zero_results, .zero_result_links {
+ font-size: 120%;
+ font-weight: bold;
+}
+
+.bz_buglist_header th {
+ text-align: left;
+}
+
+.bz_sort_order_primary,
+.bz_sort_order_secondary {
+ display: inline-block;
+ padding-left: .2em;
+ text-decoration: none;
+}
+.bz_sort_order_primary { color: black; }
+.bz_sort_order_secondary { color: #777; }
+
+
+.bz_id_column {
+}
+
+.bz_row_odd {
+ background-color: #F7F7F7;
+ color: #000000;
+}
+
+.bz_row_even {
+ background-color: #FFFFFF;
+ color: #000000;
+}
+
+/* Style bug rows according to severity. */
+.bz_blocker { color: red; font-weight: bold; }
+.bz_critical { color: red; }
+.bz_enhancement { color: #666; background-color: white; }
+
+/* Align columns in the "change multiple bugs" form to the right. */
+table#form tr th { text-align: right; }
+
+table.bz_buglist td, table.bz_buglist th {
+}
+
+/* we use a first-child class and not the pseudo-class because IE
+ * doesn't support it :-( */
+tr.bz_secure td.first-child, a.bz_secure {
+ background-image: url("../../images/padlock.png");
+ background-position: center left;
+ background-repeat: no-repeat;
+ background-color: inherit;
+}
+
+th.first-child, td.first-child, a.bz_secure {
+ padding-left: 20px;
+}
+
+tr.bz_secure_mode_implied td.first-child {
+}
+
+tr.bz_secure_mode_manual td.first-child {
+}
+
+td.bz_estimated_time_column,
+td.bz_remaining_time_column,
+td.bz_actual_time_column,
+td.bz_percentage_complete_column {
+ text-align: right;
+}
+
+td.bz_total_label {
+ font-weight: bold;
+}
+
+td.bz_total {
+ border-top-style: solid;
+ border-top-color: #929bb1;
+ border-top-width: 3px;
+ text-align: right;
+}
+
+#commit, #action {
+ margin-top: .25em;
+}
+
+.bz_query_explain {
+ text-align: left;
+}
diff --git a/skins/standard/dependency-tree.css b/skins/standard/dependency-tree.css
new file mode 100644
index 000000000..cab167171
--- /dev/null
+++ b/skins/standard/dependency-tree.css
@@ -0,0 +1,94 @@
+/* The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is the Bugzilla Bug Tracking System.
+ *
+ * The Initial Developer of the Original Code is Netscape
+ * Communications
+ * Corporation. Portions created by Netscape are
+ * Copyright (C) 1998 Netscape Communications Corporation. All
+ * Rights Reserved.
+ *
+ * Contributor(s): Christian Reis <kiko@async.com.br>
+ * Andr Batosti <batosti@async.com.br>
+ */
+
+ul.tree {
+ padding-left: 0em;
+ margin-left: 1em;
+ display: block;
+}
+
+ul.tree ul {
+ padding-top: 3px;
+ display: block;
+}
+
+ul.tree li {
+ /* see http://www.kryogenix.org/code/browser/aqlists/ for idea */
+ padding-top: 3px;
+ text-indent: -1.2em;
+ padding-left: 0.5em;
+ padding-bottom: 3px;
+ list-style-type: none;
+ background: url("dependency-tree/bug-item.png") no-repeat;
+}
+
+ul.tree li a.b {
+ padding-left: 30px;
+ margin-right: -14px;
+ text-decoration: none;
+}
+
+ul.tree li a.b_open {
+ background: url("dependency-tree/tree-open.png") center no-repeat;
+ cursor: pointer;
+}
+
+ul.tree li a.b_closed {
+ background: url("dependency-tree/tree-closed.png") center no-repeat;
+ cursor: pointer;
+}
+
+ul.tree a.tree_link img {
+ border: 0;
+}
+
+.summ_info {
+ /* change to inline if you would like to see the full bug details
+ * displayed in the list */
+ display: none;
+ font-size: 75%;
+}
+
+.hint {
+ font-size: 90%;
+ margin: 0.2em;
+ padding: 0.1em;
+}
+
+.hint h3, .hint ul {
+ margin-top: 0.1em;
+ margin-bottom: 0.1em;
+}
+
+.summ A, .summ_deep A {
+ text-decoration: none;
+ color: darkblue;
+}
+
+.summ_deep {
+}
+
+.summ_h A {
+ background-color: #ffffaa;
+ color: #333;
+ font-weight: bold;
+}
diff --git a/skins/standard/dependency-tree/bug-item.png b/skins/standard/dependency-tree/bug-item.png
new file mode 100644
index 000000000..051025d74
--- /dev/null
+++ b/skins/standard/dependency-tree/bug-item.png
Binary files differ
diff --git a/skins/standard/dependency-tree/tree-closed.png b/skins/standard/dependency-tree/tree-closed.png
new file mode 100644
index 000000000..80f1e2b4d
--- /dev/null
+++ b/skins/standard/dependency-tree/tree-closed.png
Binary files differ
diff --git a/skins/standard/dependency-tree/tree-open.png b/skins/standard/dependency-tree/tree-open.png
new file mode 100644
index 000000000..eeb3e6bc1
--- /dev/null
+++ b/skins/standard/dependency-tree/tree-open.png
Binary files differ
diff --git a/skins/standard/dependency-tree/tree.png b/skins/standard/dependency-tree/tree.png
new file mode 100644
index 000000000..884d34732
--- /dev/null
+++ b/skins/standard/dependency-tree/tree.png
Binary files differ
diff --git a/skins/standard/duplicates.css b/skins/standard/duplicates.css
new file mode 100644
index 000000000..e89b72c59
--- /dev/null
+++ b/skins/standard/duplicates.css
@@ -0,0 +1,49 @@
+/* The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is the Bugzilla Bug Tracking System.
+ *
+ * The Initial Developer of the Original Code is Everything Solved, Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Max Kanat-Alexander <mkanat@bugzilla.org>
+ */
+
+#duplicates_table {
+ border-collapse: collapse;
+}
+
+#duplicates_table .resolved {
+ background-color: #d9d9d9;
+ color: black;
+}
+
+#duplicates_table thead tr {
+ background-color: #ccc;
+ color: black;
+}
+
+#duplicates_table thead tr th {
+ vertical-align: middle;
+}
+
+#duplicates_table td, #duplicates_table th {
+ border: 1px solid black;
+ padding: .1em .25em;
+}
+
+#duplicates_table tbody td {
+ text-align: center;
+}
+#duplicates_table tbody td.short_desc {
+ text-align: left;
+}
diff --git a/skins/standard/editusers.css b/skins/standard/editusers.css
new file mode 100644
index 000000000..770d602c8
--- /dev/null
+++ b/skins/standard/editusers.css
@@ -0,0 +1,71 @@
+/* The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is the Bugzilla Bug Tracking System.
+ *
+ * Contributor(s): Marc Schumann <wurblzap@gmail.com>
+ */
+
+table.main {
+ border-spacing: 1em;
+}
+table.main tr {
+ vertical-align: top;
+ border-top: solid thin black;
+}
+table.main th {
+ text-align: right;
+ white-space: nowrap;
+}
+table.main th,
+table.main td {
+ padding: 0;
+}
+table.main ul {
+ list-style-type: none;
+ padding-left: 0
+}
+
+table.groups {
+ border-spacing: 1px;
+}
+table.groups tr.indirect {
+ background-color: #cccccc;
+}
+table.groups th {
+ text-align: left;
+ padding: 0 0 0 1ex;
+}
+table.groups td {
+ padding: 2px;
+}
+table.groups td.checkbox {
+ text-align: center;
+ white-space: nowrap;
+}
+
+table#user_responsibilities th {
+ text-align: center;
+ padding: 0 1em 1em;
+}
+
+table#user_responsibilities th.product {
+ text-align: left;
+ padding: 1em 0 0;
+}
+
+table#user_responsibilities td.center {
+ text-align: center;
+}
+
+.missing {
+ color: red;
+ border-color: inherit;
+}
diff --git a/skins/standard/enter_bug.css b/skins/standard/enter_bug.css
new file mode 100644
index 000000000..bb8b05034
--- /dev/null
+++ b/skins/standard/enter_bug.css
@@ -0,0 +1,70 @@
+/* The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is the Bugzilla Bug Tracking System.
+ *
+ * The Initial Developer of the Original Code is Netscape Communications
+ * Corporation. Portions created by Netscape are Copyright (C) 1998
+ * Netscape Communications Corporation. All Rights Reserved.
+ *
+ * Contributor(s): Byron Jones <bugzilla@glob.com.au>
+ * Christian Reis <kiko@async.com.br>
+ * Vitaly Harisov <vitaly@rathedg.com>
+ * Svetlana Harisova <light@rathedg.com>
+ * Marc Schumann <wurblzap@gmail.com>
+ * Pascal Held <paheld@gmail.com>
+ * Max Kanat-Alexander <mkanat@bugzilla.org>
+ */
+
+/* These are specified using the class instead of the id so that they
+ don't override the YUI CSS. */
+.enter_bug_form table {
+ border-spacing: 0;
+ border-width: 0;
+}
+.enter_bug_form td, .enter_bug_form th { padding: .25em; }
+.enter_bug_form th { text-align: right; }
+
+/* This makes the "component" column as small as possible (since it
+ * contains only fixed-width content) and the Reporter column
+ * as large as possible, which makes the form not jump around
+ * when the Component Description changes size. This works
+ * pretty well on all browsers except IE 8.
+ */
+#Create #field_container_component { width: 1px; }
+#Create #field_container_reporter { width: 100%; }
+
+#Create .comment {
+ vertical-align: top;
+ overflow: auto;
+ color: green;
+}
+#Create #comp_desc_container td { padding: 0; }
+#Create #comp_desc { height: 11ex; }
+#Create #os_guess_note {
+ padding-top: 0;
+}
+#Create #os_guess_note div {
+ max-width: 35em;
+}
+
+/* Text inputs need to be a little shorter on enter_bug
+ * than the 100% that they are on show_bug.
+ */
+#Create .field_value .text_input { max-width: 50em; }
+
+/* The Possible Duplicates table on enter_bug. */
+#possible_duplicates th {
+ text-align: center;
+ background: none;
+ border-collapse: collapse;
+}
+/* Make the Add Me to CC button never wrap. */
+#possible_duplicates .yui-dt-col-update_token { white-space: nowrap; }
diff --git a/skins/standard/global.css b/skins/standard/global.css
new file mode 100644
index 000000000..394715e4a
--- /dev/null
+++ b/skins/standard/global.css
@@ -0,0 +1,574 @@
+/* The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is the Bugzilla Bug Tracking System.
+ *
+ * The Initial Developer of the Original Code is Netscape Communications
+ * Corporation. Portions created by Netscape are
+ * Copyright (C) 1998 Netscape Communications Corporation. All
+ * Rights Reserved.
+ *
+ * Contributor(s): Byron Jones <bugzilla@glob.com.au>
+ * Christian Reis <kiko@async.com.br>
+ * Vitaly Harisov <vitaly@rathedg.com>
+ * Svetlana Harisova <light@rathedg.com>
+ * Marc Schumann <wurblzap@gmail.com>
+ * Pascal Held <paheld@gmail.com>
+ */
+
+/* global (begin) */
+ body {
+ font-family: sans-serif;
+ color: #000;
+ background: #fff url("global/body-back.gif") repeat-x;
+ }
+ body, td, th, input {
+ font-family: Verdana, sans-serif;
+ font-size: small;
+ }
+ /* monospace is much smaller than Verdana by default, so we make it a bit bigger. */
+ pre, code, kbd {
+ font-size: medium;
+ }
+/* global (end) */
+
+/* header (begin) */
+ #header {
+ margin-bottom: 1em;
+ }
+
+ #header form, #header form input,
+ #footer form, #footer form input
+ {
+ font-size: 95%;
+ display: inline;
+ }
+
+ #header .links {
+ border-left: 1px solid #747E93;
+ border-right: 1px solid #747E93;
+ border-bottom: 1px solid #747E93;
+ -moz-border-radius-bottomleft: 5px;
+ -moz-border-radius-bottomright: 5px;
+ padding: 0.5em;
+ }
+
+ #lang_links_container {
+ float: right;
+ }
+ #lang_links_container .links {
+ border: none;
+ padding: .5em;
+ }
+
+ .lang_current {
+ font-weight: bold;
+ }
+
+ #message {
+ border: 1px solid red;
+ margin: 0.3em 0em;
+ padding: 0.3em;
+ color: green;
+ }
+
+ form.mini_login input.bz_login {
+ width: 10em;
+ }
+ form.mini_login input.bz_password {
+ width: 6em;
+ }
+ form.mini_login input.bz_remember {
+ margin: 0;
+ }
+ .bz_mini_login_help {
+ color: #777;
+ }
+
+/* header (end) */
+
+/* banner (begin) */
+ #banner {
+ }
+
+/* banner (end) */
+
+/* titles (begin) */
+ #titles {
+ width: 100%;
+ background-color: #404D6C;
+ color: #fff;
+ -moz-border-radius-topleft: 5px;
+ -moz-border-radius-topright: 5px;
+ font-size: 110%;
+ margin: 0;
+ padding: 0.5em;
+ vertical-align: bottom;
+ }
+
+ #titles a {
+ color: #fff;
+ }
+
+ #titles p {
+ margin: 0;
+ padding: 0;
+ }
+
+ #titles #title {
+ font-weight: bold;
+ white-space: nowrap;
+ }
+
+ #titles #subtitle {
+ font-weight: normal;
+ width: 100%;
+ text-align: center;
+ }
+
+ #titles #information {
+ font-weight: normal;
+ text-align: right;
+ font-size: 90%;
+ white-space: nowrap;
+ }
+
+/* titles (end) */
+
+/* footer (begin)
+ * See also the "header" section for styles that apply
+ * to both the header and footer.
+ */
+ #footer {
+ clear: both;
+ margin-top: 1em;
+ width: 100%;
+ background: #edf2f2;
+ border-top: 1px solid #ddd;
+ border-bottom: 1px solid #ddd;
+ }
+
+ #footer #useful-links {
+ padding-left: 1ex;
+ padding-right: 1ex;
+ }
+
+ #footer ul {
+ list-style-type: none;
+ }
+ #links-saved ul {
+ display: inline;
+ }
+ #links-saved th {
+ vertical-align: top;
+ }
+
+ #footer .label {
+ white-space: nowrap;
+ vertical-align: top;
+ }
+
+ #footer .links {
+ vertical-align: top;
+ }
+/* footer (end) */
+
+/* link lists (begin) */
+ ul.links {
+ margin: 0;
+ padding: 0;
+ list-style-type: none;
+ }
+
+ ul.links li {
+ display: inline;
+ white-space: nowrap;
+ }
+/* link lists (end) */
+
+/* tabs (begin) */
+ .tabs td {
+ background: #eee;
+ text-align: center;
+ border-style: solid;
+ border-color: black;
+ border-width: 0px 0px 2px 0px;
+ }
+
+ .tabs td.selected {
+ background: white;
+ border-width: 2px 2px 0px 2px;
+ }
+
+ .tabs td.spacer {
+ background: white;
+ }
+/* tabs (end) */
+
+/* generic (begin) */
+ a {
+ color: #039;
+ }
+
+ a:visited {
+ color: #636;
+ }
+
+ a:hover {
+ color: #333;
+ }
+
+ a:active {
+ color: #000;
+ }
+
+ .clickable_area {
+ cursor: pointer;
+ }
+/* generic (end) */
+
+/* Links that control whether or not something is visible. */
+a.controller {
+ font-size: 115%;
+}
+
+div#docslinks {
+ float: right;
+ border: 1px solid black;
+ padding: 1ex;
+ font-size: 80%;
+}
+
+#docslinks h2 {
+ margin: 0;
+}
+
+.bz_obsolete {
+ text-decoration: line-through;
+}
+.bz_inactive {
+ text-decoration: line-through;
+}
+.bz_closed,
+.bz_CLOSED td {
+ text-decoration: line-through;
+}
+.bz_private {
+ color: darkred;
+ background: #f3eeee;
+}
+.bz_disabled {
+ color: #a0a0a0;
+}
+
+/************/
+/* Comments */
+/************/
+
+.bz_comment_table td {
+ vertical-align: top;
+}
+
+.bz_comment {
+ margin-bottom: 2em;
+}
+
+/* The rules for these classes make international text wrap correctly,
+ even for languages like Japanese that have no spaces. */
+.bz_comment_text, .uneditable_textarea {
+ font-family: monospace;
+ /* Note that these must all be on separate lines or they stop
+ working in Konqueror. */
+ white-space: pre-wrap; /* CSS 3 & 2.1 */
+ white-space: -moz-pre-wrap; /* Gecko */
+ white-space: -pre-wrap; /* Opera 4-6 */
+ white-space: -o-pre-wrap; /* Opera 7 */
+}
+
+.bz_comment_text {
+ width: 50em;
+}
+
+.bz_comment_user, .bz_comment_time, .bz_comment_number,
+.bz_private_checkbox, .bz_comment_actions
+{
+ margin: 0 .5em;
+}
+
+.bz_comment_actions, .bz_comment_number, .bz_private_checkbox {
+ float: right;
+}
+
+.bz_collapse_expand_comments {
+ padding: 0;
+ margin: 0 0 0 1em;
+ list-style-type: none;
+}
+.bz_collapse_expand_comments li {
+ margin-bottom: .5em;
+}
+.bz_collapse_comment {
+ text-decoration: none;
+}
+
+.bz_private_checkbox input {
+ margin: 0;
+ vertical-align: middle;
+}
+
+.bz_comment_head, .bz_first_comment_head {
+ padding-top: .1em;
+ padding-bottom: .1em;
+ padding-left: .5em;
+ background-color: #e0e0e0;
+}
+
+.bz_comment_user_images img {
+ vertical-align: bottom;
+}
+
+.bz_comment_hilite pre {
+ background-color: lightgreen;
+ margin: 0;
+ padding: 1em 0;
+}
+
+/** End Comments **/
+
+.bz_default_hidden, .bz_tui_hidden, .bz_hidden_field, .bz_hidden_option {
+ /* We have !important because we want elements with these classes to always
+ * be hidden, even if there is some CSS that overrides it (we use these
+ * classes inside JavaScript to hide things). */
+ display: none !important;
+}
+
+.bz_comment_text span.quote {
+ color: #65379c;
+ /* Make quoted text not wrap. */
+ white-space: pre;
+}
+
+table#flags th,
+table#flags td {
+ vertical-align: middle;
+ text-align: left;
+}
+
+.flag_select {
+ min-width: 3em;
+}
+
+input.requestee {
+ width: 15em;
+}
+
+#error_msg {
+ font-size: x-large;
+}
+
+.throw_error {
+ background-color: #ff0000;
+ color: black;
+ font-size: 120%;
+ margin: 1em;
+ padding: 0.5em 1em;
+}
+
+dt {
+ font-weight: bold;
+}
+body > dl > dt {
+ border-top: dotted gray thin;
+}
+dl dl > dt {
+ border-top: none;
+}
+
+#admin_table th {
+ white-space: normal !important;
+}
+
+/* Style of the attachment table and time tracking table */
+#attachment_table {
+ border-collapse: collapse;
+ border: 1px solid #333333;
+}
+
+#attachment_table th, .bz_attach_footer, .bz_time_tracking_table th {
+ background-color: #E0E0E0;
+ color: black;
+}
+
+#attachment_table td, .bz_time_tracking_table th, .bz_time_tracking_table td {
+ border: 1px solid #333333;
+}
+
+.bz_attach_extra_info {
+ font-size: smaller;
+}
+
+.bz_attach_flags, .bz_attach_footer {
+ white-space: nowrap;
+}
+
+.bz_attach_view_hide {
+ float: right;
+ padding-left: 1em;
+}
+
+div.user_match {
+ margin-bottom: 1em;
+}
+
+.box {
+ border: 1px solid black;
+ color: black;
+ background-color: #ffc;
+ margin: 1em;
+ padding: 0.5em 1em;
+}
+
+.collapsed {
+ display: none;
+}
+
+/* Rules specific for printing */
+@media print {
+ #header, #footer {
+ display: none;
+ }
+
+ div.bz_query_buttons {
+ display: none;
+ }
+
+ body {
+ background-image: none;
+ background-color: #fff;
+ }
+}
+
+/**************/
+/* Bug Fields */
+/**************/
+
+.field_label {
+ text-align: right;
+ vertical-align: top;
+ font-weight: bold;
+}
+.field_help_link {
+ cursor: help;
+}
+.field_value, form#Create th, form#Create td {
+ vertical-align: top;
+}
+.field_value .text_input {
+ width: 100%;
+ min-width: 25em;
+}
+
+.uneditable_textarea {
+ width: 30em;
+ font-size: medium;
+}
+
+th.required:before {
+ content: "* ";
+}
+th.required:before, span.required_star {
+ color: red;
+}
+input.required, select.required, span.required_explanation {
+ background-color: #fff7cd;
+}
+
+.calendar_button {
+ background: transparent url("global/calendar.png") no-repeat;
+ width: 20px;
+ height: 20px;
+ vertical-align: middle;
+}
+.calendar_button span { display: none }
+/* These classes are set by YUI. */
+.yui-calcontainer {
+ display: none;
+ background-color: white;
+ padding: 10px;
+ border: 1px solid #404D6C;
+}
+
+.bug_urls {
+ margin: 0 0 1em 0;
+ padding: 0;
+ list-style-type: none;
+}
+
+/* custom styles for inline instances of autocomplete input fields */
+.yui-skin-sam .yui-ac-input { position:static !important;
+ vertical-align:middle !important; }
+.yui-skin-sam .yui-ac-container { left:0px !important; }
+.yui-skin-sam .yui-ac { display: inline-block; }
+#bugzilla-body .yui-ac-content {
+ max-height: 19em;
+ overflow: auto;
+ overflow-x: hidden;
+}
+
+#keyword_container {
+ padding-top: .2em;
+}
+
+
+#keyword_container .yui-ac-content {
+ margin-left: -1px;
+}
+
+/*******************/
+/* Form Validation */
+/*******************/
+
+.validation_error_text {
+ font-size: 120%;
+ color: #B70000;
+ font-weight: bold;
+}
+
+.validation_error_field, input.validation_error_field {
+ border: 2px solid #B70000;
+ background-color: #FFEBEB;
+}
+
+/*****************/
+/* colchange.cgi */
+/*****************/
+
+.image_button {
+ background-repeat: no-repeat;
+ background-position: center center;
+ width: 30px;
+ height: 20px;
+}
+
+#select_button {
+ background-image: url(global/right.png);
+}
+
+#deselect_button {
+ background-image: url(global/left.png);
+}
+
+#up_button {
+ background-image: url(global/up.png);
+}
+
+#down_button {
+ background-image: url(global/down.png);
+}
diff --git a/skins/standard/global/body-back.gif b/skins/standard/global/body-back.gif
new file mode 100644
index 000000000..72a0d3ee6
--- /dev/null
+++ b/skins/standard/global/body-back.gif
Binary files differ
diff --git a/skins/standard/global/calendar.png b/skins/standard/global/calendar.png
new file mode 100644
index 000000000..0eb9ca78e
--- /dev/null
+++ b/skins/standard/global/calendar.png
Binary files differ
diff --git a/skins/standard/global/down.png b/skins/standard/global/down.png
new file mode 100644
index 000000000..78a9e631a
--- /dev/null
+++ b/skins/standard/global/down.png
Binary files differ
diff --git a/skins/standard/global/header.png b/skins/standard/global/header.png
new file mode 100644
index 000000000..eca3f35ec
--- /dev/null
+++ b/skins/standard/global/header.png
Binary files differ
diff --git a/skins/standard/global/left.png b/skins/standard/global/left.png
new file mode 100644
index 000000000..f8cb2b2dd
--- /dev/null
+++ b/skins/standard/global/left.png
Binary files differ
diff --git a/skins/standard/global/right.png b/skins/standard/global/right.png
new file mode 100644
index 000000000..d02b707a6
--- /dev/null
+++ b/skins/standard/global/right.png
Binary files differ
diff --git a/skins/standard/global/up.png b/skins/standard/global/up.png
new file mode 100644
index 000000000..240d483df
--- /dev/null
+++ b/skins/standard/global/up.png
Binary files differ
diff --git a/skins/standard/index.css b/skins/standard/index.css
new file mode 100644
index 000000000..1b28dabd8
--- /dev/null
+++ b/skins/standard/index.css
@@ -0,0 +1,136 @@
+ /* The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is the Bugzilla Bug Tracking System.
+ *
+ * Contributor(s): Vitaly Harisov <vitaly@rathedg.com>
+ * Guy Pyrzak <guy.pyrzak@gmail.com>
+ */
+
+/* index page (begin) */
+
+ #page-index
+ {
+ padding: 0.2em 0.2em 0.15em 0.2em;
+ max-width: 1000px;
+ }
+
+ /* By default these contain nothing, but these CSS rules make things
+ easier on customizers. */
+ .intro, .outro {
+ text-align: center;
+ width: 20em;
+ }
+
+ /* Hide from NN4 */
+
+ #new_release
+ {
+ border: 2px solid red;
+ padding: 0.5em 1em;
+ margin: 1em;
+ font-weight: bold;
+ }
+
+ #new_release .notice
+ {
+ font-size: 80%;
+ font-weight: normal;
+ }
+
+ #welcome-admin a
+ {
+ font-weight: bold;
+ }
+
+ .bz_common_actions {
+ display: block;
+ height: 170px;
+ width: 145px;
+ float: left;
+ margin: 0 2ex 2em 0;
+ text-align: center;
+ }
+ .bz_common_actions span {
+ position: relative;
+ top: 95%;
+ font-weight: bold;
+ }
+ .bz_common_actions,
+ .bz_common_actions:visited,
+ .bz_common_actions:hover
+ {
+ text-decoration: none;
+ }
+ #enter_bug { background: url(index/file-a-bug.png) no-repeat; }
+ #query { background: url(index/search.png) no-repeat; }
+ #account {
+ background: url(index/new-account.png) no-repeat;
+ margin-right: 0;
+ }
+
+ #quicksearchForm
+ {
+ clear: both;
+ text-align: center;
+ margin-bottom: 2em;
+ }
+
+ #quicksearchForm #quicksearch_main
+ {
+ width: 27em;
+ }
+
+ #quicksearchForm
+ {
+ margin: 0;
+ padding: 0;
+ }
+
+ #page-index table{
+ border-collapse: collapse;
+ margin: auto;
+ }
+
+ #welcome
+ {
+ font-size: x-large;
+ font-weight: bold;
+ text-align: center;
+ margin: 0 0 0.8em 0;
+ padding: 0;
+ }
+
+ ul.additional_links
+ {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ }
+
+ ul#quicksearch_links{
+ margin-bottom: 1em;
+ }
+
+ ul.additional_links li
+ {
+ display: inline;
+ }
+
+ ul.additional_links li.bz_default_hidden
+ {
+ display: none;
+ }
+
+ input.quicksearch_help_text
+ {
+ color: #ccc;
+ }
+/* index page (end) */
diff --git a/skins/standard/index/file-a-bug.png b/skins/standard/index/file-a-bug.png
new file mode 100644
index 000000000..cf4c941b6
--- /dev/null
+++ b/skins/standard/index/file-a-bug.png
Binary files differ
diff --git a/skins/standard/index/help.png b/skins/standard/index/help.png
new file mode 100644
index 000000000..6f9035b64
--- /dev/null
+++ b/skins/standard/index/help.png
Binary files differ
diff --git a/skins/standard/index/new-account.png b/skins/standard/index/new-account.png
new file mode 100644
index 000000000..4ad9ff203
--- /dev/null
+++ b/skins/standard/index/new-account.png
Binary files differ
diff --git a/skins/standard/index/search.png b/skins/standard/index/search.png
new file mode 100644
index 000000000..8d33ebd1e
--- /dev/null
+++ b/skins/standard/index/search.png
Binary files differ
diff --git a/skins/standard/page.css b/skins/standard/page.css
new file mode 100644
index 000000000..da0c3be8d
--- /dev/null
+++ b/skins/standard/page.css
@@ -0,0 +1,103 @@
+/* The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Initial Developer of the Original Code is Everything Solved.
+ * Portions created by Everything Solved are Copyright (C) 2006
+ * Everything Solved. All Rights Reserved.
+ *
+ * The Original Code is the Bugzilla Bug Tracking System.
+ *
+ * Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+ */
+
+/* This CSS is used by various informational pages in the
+ template/en/default/pages/ directory. */
+
+#bugzilla-body {
+ padding: 0 1em;
+}
+
+#bugzilla-body > * {
+ /* People have an easier time reading narrower columns of text. */
+ max-width: 45em;
+}
+
+/*****************/
+/* Release Notes */
+/*****************/
+
+.req_new {
+ color: red;
+}
+
+.req_table {
+ border-collapse: collapse;
+}
+
+.req_table td, .req_table th {
+ border: 1px solid black;
+ padding: .25em;
+}
+
+/********************/
+/* QuickSearch Help */
+/********************/
+
+.qs_help li {
+ margin-top: 1ex;
+}
+
+.qs_fields th {
+ padding: 0 .25em;
+}
+.qs_fields th.field_nickname {
+ text-align: left;
+}
+.qs_fields td {
+ padding: .25em;
+ border-top: 1px solid gray;
+}
+.qs_fields .field_name {
+ width: 10em;
+}
+
+/***************/
+/* fields.html */
+/***************/
+
+table.field_value_explanation {
+ table-layout: fixed;
+ border-collapse: collapse;
+}
+
+.field_value_explanation thead h2 {
+ margin: 0;
+}
+
+.field_value_explanation .header_row td {
+ text-align: center;
+ font-size: 120%;
+ font-weight: bold;
+}
+
+.field_value_explanation tbody td {
+ border: 1px solid black;
+ padding: 1em;
+}
+
+.field_value_explanation dt,
+.field_descriptions dt
+{
+ margin-top: 1em;
+}
+
+.field_descriptions dt {
+ font-size: 120%;
+}
diff --git a/skins/standard/panel.css b/skins/standard/panel.css
new file mode 100644
index 000000000..23b52705e
--- /dev/null
+++ b/skins/standard/panel.css
@@ -0,0 +1,37 @@
+body
+ {
+ font-family: sans-serif;
+ font-size: 10pt;
+ background-color: white;
+ }
+
+ul
+ {
+ padding-left: 12px;
+ }
+
+radio
+ {
+ -moz-user-select: ignore;
+ }
+
+.text-link
+ {
+ margin-left: 3px;
+ }
+
+.text-link:hover
+ {
+ text-decoration: underline;
+ cursor: pointer;
+ }
+
+.descriptive-content
+ {
+ color: #AAAAAA;
+ }
+
+.descriptive-content[focused=true]
+ {
+ color: black;
+ }
diff --git a/skins/standard/params.css b/skins/standard/params.css
new file mode 100644
index 000000000..4eec75261
--- /dev/null
+++ b/skins/standard/params.css
@@ -0,0 +1,61 @@
+#menu {
+ width: 10em;
+ margin-top: 1em;
+ margin-right: 0.5em;
+ border: solid thin;
+ border-spacing: 0px;
+ border-collapse: collapse;
+ text-align: center;
+ color: black;
+ background-color: #edf2f2;
+ font-weight: normal;
+}
+
+#menu a:link, #menu a:visited {
+ color: #039;
+ background-color: transparent;
+}
+
+#menu a:hover, #menu a:active {
+ color: red;
+ background-color: transparent;
+}
+
+#menu td {
+ border: solid thin;
+ padding: 0.2em 0.5em;
+}
+
+table td {
+ vertical-align: top;
+}
+
+td.selected_section {
+ color: #090;
+ background-color: white;
+}
+
+td.index {
+ color: black;
+ background-color: #edf;
+}
+
+dt {
+ font-weight: bold;
+}
+
+dd {
+ margin-bottom: 1.5em;
+}
+
+.sortlist_separator {
+ font-weight: bold;
+ font-size: 80%;
+ background-color: #dddddd;
+}
+
+.contribute {
+ border: 1px dotted black;
+ padding: .5em;
+ font-size: small;
+}
diff --git a/skins/standard/reports.css b/skins/standard/reports.css
new file mode 100644
index 000000000..1856bd96e
--- /dev/null
+++ b/skins/standard/reports.css
@@ -0,0 +1,89 @@
+/* The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is the Bugzilla Bug Tracking System.
+ *
+ * The Initial Developer of the Original Code is Everything Solved,
+ * Inc. Portions created by the Initial Developer are Copyright (C)
+ * 2009 the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Max Kanat-Alexander <mkanat@bugzilla.org>
+ */
+
+/* describecomponents.cgi */
+
+#components_header_table {
+ margin-bottom: 1em;
+}
+
+.product_container {
+ width: 65%;
+}
+
+.product_name {
+ margin: 0;
+}
+
+.product_desc {
+ /* This is padding instead of margin because it looks better
+ * with the scrollbar. */
+ padding: 0 2em;
+ font-style: italic;
+ max-height: 5em;
+ overflow: auto;
+}
+
+.instructions {
+ font-weight: bold;
+ font-size: 105%;
+ padding-right: 1em;
+}
+
+.components_header {
+ margin: 0;
+ font-size: 140%;
+}
+
+.component_table {
+ margin-top: -1em;
+ margin-left: 2em;
+}
+
+.component_table thead th {
+ padding-right: 1em;
+ vertical-align: bottom;
+ text-align: left;
+}
+
+.component_table td {
+ border-bottom: 1px dotted gray;
+}
+
+.component_table td.component_assignee,
+.component_table td.component_qa_contact
+{
+ border: none;
+ padding-top: .5em;
+}
+
+.component_name {
+ font-size: 115%;
+ font-weight: bold;
+ padding-right: 1em;
+ vertical-align: middle;
+ min-width: 8em;
+}
+
+.component_description {
+ padding-bottom: .5em;
+ color: #333;
+}
+
diff --git a/skins/standard/search_form.css b/skins/standard/search_form.css
new file mode 100644
index 000000000..7258b880b
--- /dev/null
+++ b/skins/standard/search_form.css
@@ -0,0 +1,202 @@
+/* The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is the Bugzilla Bug Tracking System.
+ *
+ * The Initial Developer of the Original Code is Guy Pyrzak
+ * Portions created by the Initial Developer are Copyright (C) 2010 the
+ * Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Guy Pyrzak <guy.pyrzak@gmail.com>
+ */
+
+#summary_field {
+ padding: 1em;
+ margin: 1em;
+ border: 1px solid black;
+ background-color: #eee;
+ white-space: nowrap;
+}
+
+#bug_id_container {
+ display: inline-block;
+ vertical-align: middle;
+ padding-bottom: 1ex;
+}
+
+#bug_id_container input {
+ width: 9em;
+}
+
+.container_date_from,
+.container_date_to {
+ width: 14em;
+ padding-bottom: 1ex;
+}
+.container_date_from input,
+.container_date_to input {
+ width: 8em;
+}
+
+#bug_id_container input {
+ width: 9em;
+}
+
+#bug_id_type{
+ width: inherit;
+}
+
+.search_field_grid {
+ margin-top: 1em;
+ display: inline-block;
+}
+
+.search_field_grid .field_help_link,
+.history_query .field_help_link
+{
+ display: block;
+ text-align: left;
+}
+
+#chart .section_help {
+ font-size: 0.8em;
+ font-weight: normal
+}
+
+#bug_id_container .field_help {
+ font-size: 0.75em
+}
+
+.search_field_row {
+ white-space: nowrap;
+ margin-bottom: 0.5em;
+}
+
+.search_field_row .container_date_from, .search_field_row .container_date_to{
+ display: inline;
+}
+
+#summary_field.search_field_row {
+ display: block;
+}
+
+#summary_field.search_field_row input,
+#summary_field.search_field_row select,
+{
+ display: inline;
+ padding-bottom: 0;
+ vertical-align: middle;
+}
+
+.search_field_row .field_label, #field_label_short_desc {
+ width: 14em;
+ display: inline-block;
+ line-height: 2em;
+ margin-right: 0.8em;
+}
+
+#field_label_short_desc {
+ text-align: right;
+}
+
+#summary_field.search_field_row {
+ width: inherit;
+}
+
+#keyword_container {
+ padding-bottom: 0;
+}
+
+.search_field_grid .field_label,
+.search_field_grid .field_label
+ {
+ display: block;
+ padding-bottom: 1ex;
+}
+
+.search_field_grid select {
+ width: 17em;
+ height: 15ex;
+}
+
+.search_field_grid, .search_field_row {
+ padding-left: 1.5em;
+}
+
+.search_email_fields {
+ display: inline-block;
+ width: 14.5em;
+ padding-left: 1.5em;
+}
+
+ul.bug_changes {
+ margin: 0;
+ padding: 0;
+}
+
+ul.bug_changes li {
+ display: inline-block;
+ width: 14.5em;
+ vertical-align: top;
+ padding-left: 1.5em;
+}
+
+ul.bug_changes select {
+ width: 15em;
+}
+
+ul.bug_changes li label {
+ display: block;
+}
+
+div.bz_section_title {
+ display: block;
+ margin-top: 2em;
+ font-size: 1.2em;
+}
+
+div.bz_section_title a {
+ font-weight: bold;
+}
+
+div.bz_section_title span {
+ font-size: 0.75em;
+ margin-left: 1em;
+}
+
+#summary_field label {
+ font-weight: bold;
+}
+
+#queryform, #reportform {
+ margin-bottom: 2em;
+}
+
+#knob {
+ margin-top: 2em;
+}
+
+.hide_people_filter #people_filter_section,
+.hide_history_filter #history_filter_section,
+.hide_detailed_information #detailed_information_section
+{
+ display: none;
+}
+
+.arrow {
+ display: inline;
+ width: 16px;
+ height: 16px;
+}
+
+.bz_search_section, ul.bz_search_section {
+ margin-top: 1em;
+} \ No newline at end of file
diff --git a/skins/standard/show_bug.css b/skins/standard/show_bug.css
new file mode 100644
index 000000000..99c0b405e
--- /dev/null
+++ b/skins/standard/show_bug.css
@@ -0,0 +1,118 @@
+.bz_alias_short_desc_container {
+ margin: 8px 0;
+ padding: 0.3em;
+ background-color: rgb(208, 208, 208);
+ -moz-border-radius: 0.5em;
+ font-size: 125%;
+ font-weight: bold;
+}
+
+.bz_bug .edit_form {
+ width: 100%;
+}
+.bz_bug .edit_form table {
+ width: 100%;
+}
+.bz_bug #alias {
+ min-width: 0;
+ width: 10em;
+}
+
+.flags_label {
+ text-align: left;
+}
+table#flags {
+ width: auto;
+}
+
+.bz_column_spacer {
+ width: 0.5em;
+}
+
+.related_actions {
+ font-size: 0.85em;
+ float: right;
+ list-style-type: none;
+ white-space: nowrap;
+ margin: 0;
+ padding: 0;
+}
+
+.related_actions li {
+ display: inline;
+}
+
+.bz_show_bug_column {
+ vertical-align: top;
+}
+
+.bz_section_spacer {
+ height: 1em;
+}
+
+#duplicate_settings {
+ white-space: nowrap;
+}
+
+#bz_big_form_parts td {
+ vertical-align: top;
+}
+
+.bz_group_visibility_section {
+ margin-left: 1em;
+}
+
+.bz_group_visibility_section .instructions {
+ font-style: italic;
+}
+
+#bz_restrict_group_visibility_help .instructions {
+ margin-top: 0;
+}
+
+#bz_enable_role_visibility_help {
+ margin-top: 1em;
+}
+
+.bz_time_tracking_table {
+ border-collapse: collapse;
+}
+
+.bz_time_tracking_table th {
+ text-align: center;
+}
+
+.bz_time_tracking_table td {
+ text-align: center;
+}
+
+.bz_time_tracking_table th,
+.bz_time_tracking_table td {
+ padding: 4px;
+}
+
+.bz_time_tracking_table .bz_summarize_time {
+ text-align: right;
+}
+.bz_time_tracking_table #deadline {
+ width: 7em;
+}
+
+#summary tr td {
+ vertical-align:top;
+}
+
+#status {
+ margin-bottom: 3ex;
+}
+
+.knob-buttons {
+ float: right;
+}
+
+.text_input, .bz_userfield, #keyword_container {
+ width: 100%;
+}
+.bz_bug .bz_alias_short_desc_container {
+ width: inherit;
+}
diff --git a/skins/standard/show_multiple.css b/skins/standard/show_multiple.css
new file mode 100644
index 000000000..50875382d
--- /dev/null
+++ b/skins/standard/show_multiple.css
@@ -0,0 +1,52 @@
+hr {margin: 20px auto 40px}
+
+.bz_private { color:darkred }
+
+h1 {
+ font-size: 2em;
+ margin-bottom: 10px;
+}
+
+/* bugfields is table of all fields and values */
+.bugfields {
+ font-size: small;
+ background: #eee;
+ padding: 5px;
+ border: 1px solid silver;
+ width: 100%;
+}
+
+.bugfields tr {
+ vertical-align: top;
+}
+
+.bugfields th {
+ width: 10em;
+ text-align: left;
+ font-weight: normal;
+ line-height: 150%;
+}
+
+.bugfields td {
+ font-weight: bold;
+ line-height: 150%;
+}
+
+.bugfields .rightcell {
+ padding-left: 10px;
+}
+
+/* set line-height to normal for nested tables of bugfields table */
+.bugfields table th, .bugfields table td {
+ line-height: 100%;
+ width: auto;
+}
+
+.bugfields table.timetracking th, .bugfields table.timetracking td {
+ width: 10em;
+}
+
+.error {
+ color: red;
+ font-weight: bold;
+}
diff --git a/skins/standard/summarize-time.css b/skins/standard/summarize-time.css
new file mode 100644
index 000000000..d3f121290
--- /dev/null
+++ b/skins/standard/summarize-time.css
@@ -0,0 +1,46 @@
+/* The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is the Bugzilla Bug Tracking System.
+ *
+ * Contributor(s): Christian Reis <kiko@async.com.br>
+ */
+
+td { vertical-align: top }
+
+table.zeroitems, table.realitems {
+ margin-left: 2.0em;
+ margin-top: 2px;
+ border: 1px solid black;
+ border: 1px solid black;
+}
+
+tr.section_total {
+ background: #000000;
+ color: #ffffff;
+}
+
+td.subtotal {
+ background: #B0C0D9;
+}
+
+.zeroitems .bug_header { background: #d0e0f0 }
+.zeroitems .bug_header2 { background: #f9f9f9 }
+
+/* the fixed headers -- .number uses bug_header so hack it here */
+.number .bug_header, .number .bug_header2 { background: #d0e0f0 }
+.owner_header { background: #d0e0f0 }
+
+
+/* the details headers */
+.number .owner_header, .owner .bug_header { background: #ffffff }
+.number .owner_header2, .owner .bug_header2 { background: #EFEFEF }
+
+
diff --git a/summarize_time.cgi b/summarize_time.cgi
new file mode 100755
index 000000000..6f9580ac2
--- /dev/null
+++ b/summarize_time.cgi
@@ -0,0 +1,374 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s): Christian Reis <kiko@async.com.br>
+# Shane H. W. Travis <travis@sedsystems.ca>
+# Frédéric Buclin <LpSolit@gmail.com>
+
+use strict;
+
+use lib qw(. lib);
+
+use Date::Parse; # strptime
+
+use Bugzilla;
+use Bugzilla::Constants; # LOGIN_*
+use Bugzilla::Bug; # EmitDependList
+use Bugzilla::Util; # trim
+use Bugzilla::Error;
+
+#
+# Date handling
+#
+
+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;
+ }
+ }
+
+ 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) = @_;
+
+ if ($day > 31) {
+ $month += 1;
+ $day = 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 == 4 || $month == 6 || $month == 9 || $month == 11) &&
+ ($day == 31) )
+ {
+ $month += 1;
+ $day = 1;
+ }
+
+ 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);
+
+ # 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;
+}
+
+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);
+}
+
+# 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;
+}
+
+# Return a hashref whose key is chosen by the user (bug ID or commenter)
+# and value is a hash of the form {bug ID, commenter, time spent}.
+# 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 ($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
+ 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;
+}
+
+# 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 $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);
+
+ 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 ($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;
+}
+
+#
+# Template code starts here
+#
+
+Bugzilla->login(LOGIN_REQUIRED);
+
+my $cgi = Bugzilla->cgi;
+my $user = Bugzilla->user;
+my $template = Bugzilla->template;
+my $vars = {};
+
+Bugzilla->switch_to_shadow_db();
+
+$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 => 'view'});
+
+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 ($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 = grep { $user->can_see_bug($_) } @bugs;
+ }
+
+ $start_date = trim $cgi->param('start_date');
+ $end_date = trim $cgi->param('end_date');
+
+ # 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);
+ }
+ foreach my $date ($start_date, $end_date) {
+ next unless $date;
+ validate_date($date)
+ || ThrowUserError('illegal_date', {date => $date, format => 'YYYY-MM-DD'});
+ }
+
+ # 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]);
+ }
+
+ # 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');
+}
+
+$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->{'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'});
+$template->process("$format->{'template'}", $vars)
+ || ThrowTemplateError($template->error());
diff --git a/t/001compile.t b/t/001compile.t
new file mode 100644
index 000000000..9e63da0b4
--- /dev/null
+++ b/t/001compile.t
@@ -0,0 +1,106 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code are the Bugzilla Tests.
+#
+# The Initial Developer of the Original Code is Zach Lipton
+# Portions created by Zach Lipton are Copyright (C) 2001 Zach Lipton.
+# All Rights Reserved.
+#
+# Contributor(s): Zach Lipton <zach@zachlipton.com>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+
+#################
+#Bugzilla Test 1#
+###Compilation###
+
+use strict;
+use 5.008001;
+use lib qw(. lib t);
+use Config;
+use Support::Files;
+use Test::More tests => scalar(@Support::Files::testitems);
+
+BEGIN {
+ use_ok('Bugzilla::Constants');
+ use_ok('Bugzilla::Install::Requirements');
+ use_ok('Bugzilla');
+}
+
+sub compile_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;
+
+ if ($file =~ s/\.pm$//) {
+ $file =~ s{/}{::}g;
+ use_ok($file);
+ return;
+ }
+
+ open(my $fh, $file);
+ my $bang = <$fh>;
+ close $fh;
+
+ 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 -wc$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;
+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);
+ }
+}
diff --git a/t/002goodperl.t b/t/002goodperl.t
new file mode 100644
index 000000000..e9726cb8c
--- /dev/null
+++ b/t/002goodperl.t
@@ -0,0 +1,129 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code are the Bugzilla Tests.
+#
+# The Initial Developer of the Original Code is Zach Lipton
+# Portions created by Zach Lipton are
+# Copyright (C) 2001 Zach Lipton. All
+# Rights Reserved.
+#
+# Contributor(s): Zach Lipton <zach@zachlipton.com>
+# Jacob Steenhagen <jake@bugzilla.org>
+# David D. Kilzer <ddkilzer@theracingworld.com>
+
+
+#################
+#Bugzilla Test 2#
+####GoodPerl#####
+
+use strict;
+
+use lib 't';
+
+use Support::Files;
+
+use Test::More tests => (scalar(@Support::Files::testitems) * 3);
+
+my @testitems = @Support::Files::testitems; # get the files to test.
+
+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");
+ }
+ 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
+ $flags = "w";
+ } 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 = "wT";
+ } else {
+ ok(0, "$file has shebang but unknown extension");
+ next;
+ }
+
+ if ($file_line1 =~ m#^\#\!/usr/bin/perl\s#) {
+ if ($file_line1 =~ m#\s-$flags#) {
+ ok(1,"$file uses standard perl location and -$flags");
+ } else {
+ ok(0,"$file is MISSING -$flags --WARNING");
+ }
+ } else {
+ ok(0,"$file uses non-standard perl location");
+ }
+ }
+}
+
+foreach my $file (@testitems) {
+ my $found_use_strict = 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>) {
+ if ($file_line =~ m/^\s*use strict/) {
+ $found_use_strict = 1;
+ last;
+ }
+ }
+ close (FILE);
+ if ($found_use_strict) {
+ ok(1,"$file uses strict");
+ } else {
+ ok(0,"$file DOES NOT use strict --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;
+ }
+ 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);
+}
+exit 0;
diff --git a/t/003safesys.t b/t/003safesys.t
new file mode 100644
index 000000000..b4f41f61c
--- /dev/null
+++ b/t/003safesys.t
@@ -0,0 +1,65 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code are the Bugzilla Tests.
+#
+# The Initial Developer of the Original Code is Zach Lipton
+# Portions created by Zach Lipton are
+# Copyright (C) 2001 Zach Lipton. All
+# Rights Reserved.
+#
+# Contributor(s): Zach Lipton <zach@zachlipton.com>
+
+
+#################
+#Bugzilla Test 3#
+###Safesystem####
+
+use strict;
+
+use lib 't';
+
+use Support::Files;
+
+use Test::More tests => scalar(@Support::Files::testitems);
+
+# Capture the TESTOUT from Test::More or Test::Builder for printing errors.
+# This will handle verbosity for us automatically.
+my $fh;
+{
+ local $^W = 0; # 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\"";
+
+foreach my $file (@testitems) {
+ $file =~ s/\s.*$//; # nuke everything after the first space (#comment)
+ next if (!$file); # skip null entries
+ my $command = "$perlapp -c -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
new file mode 100644
index 000000000..31ce7927c
--- /dev/null
+++ b/t/004template.t
@@ -0,0 +1,137 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code are the Bugzilla tests.
+#
+# The Initial Developer of the Original Code is Jacob Steenhagen.
+# Portions created by Jacob Steenhagen are
+# Copyright (C) 2001 Jacob Steenhagen. All
+# Rights Reserved.
+#
+# Contributor(s): Jacob Steenhagen <jake@bugzilla.org>
+# Zach Lipton <zach@zachlipton.com>
+# David D. Kilzer <ddkilzer@kilzer.net>
+# Tobias Burnus <burnus@net-b.de>
+#
+
+#################
+#Bugzilla Test 4#
+####Templates####
+
+use strict;
+
+use lib 't';
+
+use Support::Templates;
+
+# Bug 137589 - Disable command-line input of CGI.pm when testing
+use CGI qw(-no_debug);
+
+use File::Spec;
+use Template;
+use Test::More tests => ( scalar(@referenced_files) * scalar(@languages)
+ + $num_actual_files );
+
+# Capture the TESTOUT from Test::More or Test::Builder for printing errors.
+# This will handle verbosity for us automatically.
+my $fh;
+{
+ local $^W = 0; # 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;
+ }
+}
+
+# Checks whether one of the passed files exists
+sub existOnce {
+ foreach my $file (@_) {
+ return $file if -e $file;
+ }
+ return 0;
+}
+
+# Check to make sure all templates that are referenced in
+# Bugzilla exist in the proper place.
+
+foreach my $lang (@languages) {
+ foreach my $file (@referenced_files) {
+ my @path = map(File::Spec->catfile($_, $file),
+ split(':', $include_path{$lang} . ":" . $include_path{"en"}));
+ if (my $path = existOnce(@path)) {
+ ok(1, "$path exists");
+ } else {
+ ok(0, "$file cannot be located --ERROR");
+ print $fh "Looked in:\n " . join("\n ", @path) . "\n";
+ }
+ }
+}
+
+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/initialize.none.tmpl",
+
+ FILTERS =>
+ {
+ html_linebreak => sub { return $_; },
+ no_break => 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);
+ if (-e $path) {
+ my ($data, $err) = $provider->fetch($file);
+
+ if (!$err) {
+ ok(1, "$file syntax ok");
+ }
+ else {
+ ok(0, "$file has bad syntax --ERROR");
+ print $fh $data . "\n";
+ }
+ }
+ else {
+ ok(1, "$path doesn't exist, skipping test");
+ }
+ }
+}
+
+exit 0;
diff --git a/t/005whitespace.t b/t/005whitespace.t
new file mode 100644
index 000000000..edba8b274
--- /dev/null
+++ b/t/005whitespace.t
@@ -0,0 +1,82 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code are the Bugzilla tests.
+#
+# The Initial Developer of the Original Code is Jacob Steenhagen.
+# Portions created by Jacob Steenhagen are
+# Copyright (C) 2001 Jacob Steenhagen. All
+# Rights Reserved.
+#
+# Contributor(s): Jacob Steenhagen <jake@bugzilla.org>
+# David D. Kilzer <ddkilzer@kilzer.net>
+# Colin Ogilvie <mozilla@colinogilvie.co.uk>
+# Marc Schumann <wurblzap@gmail.com>
+#
+
+#################
+#Bugzilla Test 5#
+#####no_tabs#####
+
+use strict;
+
+use lib 't';
+
+use Support::Files;
+use Support::Templates;
+
+use File::Spec;
+use Test::More tests => ( scalar(@Support::Files::testitems)
+ + $Support::Templates::num_actual_files) * 3;
+
+my @testitems = @Support::Files::testitems;
+for my $path (@Support::Templates::include_paths) {
+ 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);
+}
+
+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);
+}
+
+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);
+}
+
+exit 0;
diff --git a/t/006spellcheck.t b/t/006spellcheck.t
new file mode 100644
index 000000000..fe631e389
--- /dev/null
+++ b/t/006spellcheck.t
@@ -0,0 +1,100 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code are the Bugzilla Tests.
+#
+# The Initial Developer of the Original Code is Zach Lipton
+# Portions created by Zach Lipton are
+# Copyright (C) 2002 Zach Lipton. All
+# Rights Reserved.
+#
+# Contributor(s): Zach Lipton <zach@zachlipton.com>
+
+
+#################
+#Bugzilla Test 6#
+####Spelling#####
+
+use lib 't';
+use Support::Files;
+
+BEGIN { # yes the indenting is off, deal with it
+#add the words to check here:
+@evilwords = qw(
+anyways
+appearence
+arbitary
+cancelled
+critera
+databasa
+dependan
+existance
+existant
+paramater
+refered
+repsentation
+suported
+varsion
+);
+
+$testcount = scalar(@Support::Files::testitems);
+}
+
+use Test::More tests => $testcount;
+
+# Capture the TESTOUT from Test::More or Test::Builder for printing errors.
+# This will handle verbosity for us automatically.
+my $fh;
+{
+ local $^W = 0; # 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;
+
+# at last, here we actually run the test...
+my $evilwordsregexp = join('|', @evilwords);
+
+foreach my $file (@testitems) {
+ $file =~ s/\s.*$//; # nuke everything after the first space (#comment)
+ next if (!$file); # skip null entries
+
+ 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
new file mode 100644
index 000000000..742c2b2d2
--- /dev/null
+++ b/t/007util.t
@@ -0,0 +1,74 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code are the Bugzilla Tests.
+#
+# The Initial Developer of the Original Code is Zach Lipton
+# Portions created by Zach Lipton are Copyright (C) 2002 Zach Lipton.
+# All Rights Reserved.
+#
+# Contributor(s): Zach Lipton <zach@zachlipton.com>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+
+#################
+#Bugzilla Test 7#
+#####Util.pm#####
+
+use lib 't';
+use Support::Files;
+use Test::More tests => 13;
+
+BEGIN {
+ use_ok(Bugzilla);
+ use_ok(Bugzilla::Util);
+}
+
+# We need to override user preferences so we can get an expected value when
+# Bugzilla::Util::format_time() calls ask for the 'timezone' user preference.
+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));
+
+# 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');
+
+#url_quote():
+is(url_quote("<lala&>gaa\"'[]{\\"),"%3Clala%26%3Egaa%22%27%5B%5D%7B%5C",'url_quote');
+
+#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)');
+
+# 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',
+);
+foreach my $input (keys %email_strings) {
+ is(Bugzilla::Util::email_filter($input), $email_strings{$input},
+ "email_filter('$input')");
+}
diff --git a/t/008filter.t b/t/008filter.t
new file mode 100644
index 000000000..6f95943ab
--- /dev/null
+++ b/t/008filter.t
@@ -0,0 +1,236 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code are the Bugzilla tests.
+#
+# The Initial Developer of the Original Code is Jacob Steenhagen.
+# Portions created by Jacob Steenhagen are
+# Copyright (C) 2001 Jacob Steenhagen. All
+# Rights Reserved.
+#
+# Contributor(s): Gervase Markham <gerv@gerv.net>
+
+#################
+#Bugzilla Test 8#
+#####filter######
+
+# 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.
+
+# Sample exploit code: '>"><script>alert('Oh dear...')</script>
+
+use strict;
+use lib qw(. lib t);
+
+use vars qw(%safe);
+
+use Bugzilla::Constants;
+use Support::Templates;
+use File::Spec;
+use Test::More tests => $Support::Templates::num_actual_files;
+use Cwd;
+
+# Undefine the record separator so we can read in whole files at once
+my $oldrecsep = $/;
+my $topdir = cwd;
+$/ = undef;
+
+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") {
+ ok(0, "$path has templates but no filterexceptions.pl file. --ERROR");
+ next;
+ }
+ else {
+ 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) {
+ 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|png)\.tmpl$/) {
+ ok(1, "($lang/$flavor) $file is filter-safe");
+ next;
+ }
+
+ # Read the entire file into a string
+ open (FILE, "<$file") || die "Can't open $file: $!\n";
+ my $slurp = <FILE>;
+ close (FILE);
+
+ my @unfiltered;
+
+ # /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 @lineno = ($` =~ m/\n/gs);
+ my $lineno = scalar(@lineno) + 1;
+
+ if (!directive_ok($file, $directive)) {
+
+ # 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) = @_;
+
+ # Comments
+ return 1 if $directive =~ /^#/;
+
+ # 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 '';
+
+ # 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;
+ };
+
+ # Directives
+ return 1 if $directive =~ /^(IF|END|UNLESS|FOREACH|PROCESS|INCLUDE|
+ BLOCK|USE|ELSE|NEXT|LAST|DEFAULT|FLUSH|
+ 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);
+ }
+
+ # + - * /
+ 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|url_quote|css_class_quote|
+ ics|quoteUrls|time|uri|xml|lower|html_light|
+ obsolete|inactive|closed|unitconvert|
+ txt|html_linebreak|none)\b/x;
+
+ return 0;
+}
+
+$/ = $oldrecsep;
+
+exit 0;
diff --git a/t/009bugwords.t b/t/009bugwords.t
new file mode 100644
index 000000000..242ac478d
--- /dev/null
+++ b/t/009bugwords.t
@@ -0,0 +1,101 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code are the Bugzilla tests.
+#
+# The Initial Developer of the Original Code is Jacob Steenhagen.
+# Portions created by Jacob Steenhagen are
+# Copyright (C) 2001 Jacob Steenhagen. All
+# Rights Reserved.
+#
+# Contributor(s): Gervase Markham <gerv@gerv.net>
+#
+
+#################
+#Bugzilla Test 9#
+####bugwords#####
+
+# 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
+# "[% terms.bug %]". This test makes sure the relevant words aren't used
+# bare.
+
+use strict;
+
+use lib 't';
+
+use Support::Files;
+use Support::Templates;
+use Bugzilla::Util;
+
+use File::Spec;
+
+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)));
+}
+
+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;
+ }
+ }
+
+ # "Bugzilla"
+ if (grep /(?<!X\-)Bugzilla(?!_|::|-&gt|\.pm)/, $text) {
+ # Exclude JS comments, hyperlinks, USE, and variable assignment.
+ unless (grep /(\/\/.*|org.*>|api\/|USE |= )Bugzilla/, $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";
+ }
+ }
+ else {
+ ok(1, "$file has no invalid barewords");
+ }
+}
+
+exit 0;
diff --git a/t/010dependencies.t b/t/010dependencies.t
new file mode 100644
index 000000000..3289d098e
--- /dev/null
+++ b/t/010dependencies.t
@@ -0,0 +1,118 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code are the Bugzilla Tests.
+#
+# Contributor(s): David Miller <justdave@bugzilla.org>
+# Frédéric Buclin <LpSolit@gmail.com>
+
+
+##################
+#Bugzilla Test 10#
+## dependencies ##
+
+use strict;
+use lib qw(. lib t);
+
+use Support::Files;
+use Test::More qw(no_plan);
+
+my %mods;
+my %deps;
+
+use constant MODULE_REGEX => qr/
+ (?:(?:^\s*use)
+ |
+ (?:^require)
+ )\s+
+ ['"]?
+ ([\w:\.\\]+)
+/x;
+use constant BASE_REGEX => qr/^use base qw\(([^\)]+)/;
+
+# Extract all Perl modules.
+foreach my $file (@Support::Files::testitems) {
+ if ($file =~ /^(.*)\.pm$/) {
+ my $module = $1;
+ $module =~ s#/#::#g;
+ $mods{$module} = $file;
+ }
+}
+
+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" 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 {
+ my ($module, $used_module) = @_;
+ my @list = ($used_module);
+ my %seen;
+ while (my $next = shift @list) {
+ if ($module eq $next) {
+ ok(0, "Dependency on $used_module from $module causes loop. --ERROR");
+ return;
+ }
+ if (!$seen{$next}) {
+ push(@list, @{$deps{$next}}) if defined $deps{$next};
+ }
+ $seen{$next} = 1;
+ }
+ ok(1, "No dependency loop between $module and $used_module");
+}
+
+
+foreach my $module (keys %deps) {
+ foreach my $used_module (@{$deps{$module}}) {
+ creates_loop($module, $used_module);
+ }
+}
+
+exit 0;
diff --git a/t/011pod.t b/t/011pod.t
new file mode 100644
index 000000000..517ca03ad
--- /dev/null
+++ b/t/011pod.t
@@ -0,0 +1,60 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code are the Bugzilla Tests.
+#
+# Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
+
+
+##################
+#Bugzilla Test 11#
+##POD validation##
+
+use strict;
+
+use lib 't';
+
+use Support::Files;
+use Pod::Checker;
+
+use Test::More tests => scalar(@Support::Files::testitems);
+
+# Capture the TESTOUT from Test::More or Test::Builder for printing errors.
+# This will handle verbosity for us automatically.
+my $fh;
+{
+ local $^W = 0; # 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");
+ }
+}
+
+exit 0;
diff --git a/t/012throwables.t b/t/012throwables.t
new file mode 100644
index 000000000..3738ad524
--- /dev/null
+++ b/t/012throwables.t
@@ -0,0 +1,243 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+# vim: ts=4 sw=4 et tw=80
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code are the Bugzilla Tests.
+#
+# The Initial Developer of the Original Code is Zach Lipton
+# Portions created by Zach Lipton are
+# Copyright (C) 2001 Zach Lipton. All
+# Rights Reserved.
+#
+# Contributor(s): Dennis Melentyev <dennis.melentyev@infopulse.com.ua>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+
+
+##################
+#Bugzilla Test 12#
+######Errors######
+
+use strict;
+use lib qw(. lib t);
+
+use Bugzilla::Constants;
+use Bugzilla::WebService::Constants;
+
+use File::Spec;
+use Support::Files;
+use Support::Templates;
+use Test::More;
+
+my %Errors = ();
+
+# Just a workaround for template errors handling. Define it as used.
+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 = ();
+
+# Find all modules
+foreach my $module (@Support::Files::testitems) {
+ $test_modules{$module} = ();
+}
+
+# Find all error templates
+# Process all files since otherwise handling template hooks would became too
+# 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#;
+ }
+}
+
+# Count the tests. The +1 is for checking the WS_ERROR_CODE errors.
+my $tests = (scalar keys %test_modules) + (scalar keys %test_templates) + 1;
+exit 0 if !$tests;
+
+# Set requested tests counter.
+plan tests => $tests;
+
+# Collect all errors defined in templates
+foreach my $file (keys %test_templates) {
+ $file =~ m|template/([^/]+).*/global/([^/]+)-error\.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;
+ }
+ }
+ }
+ 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;
+ }
+
+ 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);
+}
+
+# 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");
+ }
+ 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);
+ }
+ }
+ }
+ }
+}
+
+# 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.");
+ }
+}
+
+# Now report modules results
+foreach my $file (sort keys %test_modules) {
+ Report($file, @{$test_modules{$file}});
+}
+
+# And report WS_ERROR_CODE results
+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}});
+}
+
+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});
+}
+
+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));
+ }
+ }
+ 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");
+ }
+}
+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);
+ }
+}
+
+exit 0;
diff --git a/t/Support/Files.pm b/t/Support/Files.pm
new file mode 100644
index 000000000..6c6e0ee57
--- /dev/null
+++ b/t/Support/Files.pm
@@ -0,0 +1,55 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code are the Bugzilla Tests.
+#
+# The Initial Developer of the Original Code is Zach Lipton
+# Portions created by Zach Lipton are
+# Copyright (C) 2001 Zach Lipton. All
+# Rights Reserved.
+#
+# Contributor(s): Zach Lipton <zach@zachlipton.com>
+# Joel Peshkin <bugreport@peshkin.net>
+
+
+package Support::Files;
+
+use File::Find;
+
+@additional_files = ();
+
+@files = glob('*');
+find(sub { push(@files, $File::Find::name) if $_ =~ /\.pm$/;}, 'Bugzilla');
+push(@files, 'extensions/create.pl');
+
+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;
+}
+
+foreach $currentfile (@files) {
+ if (isTestingFile($currentfile)) {
+ push(@testitems,$currentfile);
+ }
+}
+
+
+1;
diff --git a/t/Support/Systemexec.pm b/t/Support/Systemexec.pm
new file mode 100644
index 000000000..676ee02a4
--- /dev/null
+++ b/t/Support/Systemexec.pm
@@ -0,0 +1,14 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+
+package Support::Systemexec;
+require Exporter;
+@ISA = qw(Exporter);
+@EXPORT = qw(system exec);
+@EXPORT_OK = qw();
+sub system($$@) {
+ 1;
+}
+sub exec($$@) {
+ 1;
+}
+1;
diff --git a/t/Support/Templates.pm b/t/Support/Templates.pm
new file mode 100644
index 000000000..40e16f105
--- /dev/null
+++ b/t/Support/Templates.pm
@@ -0,0 +1,150 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code are the Bugzilla tests.
+#
+# The Initial Developer of the Original Code is Jacob Steenhagen.
+# Portions created by Jacob Steenhagen are
+# Copyright (C) 2001 Jacob Steenhagen. All
+# Rights Reserved.
+#
+# Contributor(s): Jacob Steenhagen <jake@bugzilla.org>
+# David D. Kilzer <ddkilzer@kilzer.net>
+# Tobias Burnus <burnus@net-b.de>
+#
+
+package Support::Templates;
+
+use strict;
+
+use lib 't';
+use base qw(Exporter);
+@Support::Templates::EXPORT =
+ qw(@languages @include_paths %include_path @referenced_files
+ %actual_files $num_actual_files);
+use vars qw(@languages @include_paths %include_path @referenced_files
+ %actual_files $num_actual_files);
+
+use Support::Files;
+
+use File::Find;
+use File::Spec;
+
+# The available template languages
+@languages = ();
+
+# The colon separated includepath per language
+%include_path = ();
+
+# All include paths
+@include_paths = ();
+
+# Files which are referenced in the cgi files
+@referenced_files = ();
+
+# All files sorted by include_path
+%actual_files = ();
+
+# total number of actual_files
+$num_actual_files = 0;
+
+# Scan for the template available languages and include paths
+{
+ opendir(DIR, "template") || die "Can't open 'template': $!";
+ my @files = grep { /^[a-z-]+$/i } readdir(DIR);
+ closedir DIR;
+
+ foreach my $langdir (@files) {
+ next if($langdir =~ /^CVS$/i);
+
+ my $path = File::Spec->catdir('template', $langdir, 'custom');
+ my @dirs = ();
+ push(@dirs, $path) if(-d $path);
+ $path = File::Spec->catdir('template', $langdir, 'extension');
+ push(@dirs, $path) if(-d $path);
+ $path = File::Spec->catdir('template', $langdir, 'default');
+ push(@dirs, $path) if(-d $path);
+
+ next if(scalar(@dirs) == 0);
+ push(@languages, $langdir);
+ push(@include_paths, @dirs);
+ $include_path{$langdir} = join(":",@dirs);
+ }
+}
+
+
+my @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);
+
+ # File::Spec 3.13 and newer return "." instead of "" if both
+ # arguments of abs2rel() are identical.
+ $local_dir = "" if ($local_dir eq ".");
+
+ if ($local_dir) {
+ $filename = File::Spec->catfile($local_dir, $_);
+ } else {
+ $filename = $_;
+ }
+
+ push(@files, $filename);
+ }
+}
+
+# Scan the given template include path for templates
+sub find_actual_files {
+ my $include_path = $_[0];
+ @files = ();
+ find(\&find_templates, $include_path);
+ return @files;
+}
+
+
+foreach my $include_path (@include_paths) {
+ $actual_files{$include_path} = [ find_actual_files($include_path) ];
+ $num_actual_files += scalar(@{$actual_files{$include_path}});
+}
+
+# Scan Bugzilla's perl code looking for templates used and put them
+# in the @referenced_files array to be used by the 004template.t test.
+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;
+ }
+ }
+}
+
+1;
diff --git a/template/en/default/account/auth/login-small.html.tmpl b/template/en/default/account/auth/login-small.html.tmpl
new file mode 100644
index 000000000..a7e72eaf1
--- /dev/null
+++ b/template/en/default/account/auth/login-small.html.tmpl
@@ -0,0 +1,121 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Jacob Steenhagen <jake@bugzilla.org>
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[%# Use the current script name. If an empty name is returned,
+ # then we are accessing the home page. %]
+
+[% login_target = cgi.url("-relative" => 1, "-query" => 1) %]
+[% IF !login_target OR login_target.match("^token.cgi") %]
+ [% login_target = "index.cgi" %]
+[% END %]
+
+[% login_target = urlbase _ login_target %]
+
+<li id="mini_login_container[% qs_suffix %]">
+ <span class="separator">| </span>
+ [% connector = "?" %]
+ [% IF cgi.request_method == "GET" AND cgi.query_string %]
+ [% connector = "&" %]
+ [% END %]
+ [% script_name = login_target _ connector _ "GoAheadAndLogIn=1" %]
+ <a id="login_link[% qs_suffix %]" href="[% script_name FILTER html %]"
+ onclick="return show_mini_login_form('[% qs_suffix %]')">Log In</a>
+ <form action="[% login_target FILTER html %]" method="POST"
+ class="mini_login bz_default_hidden"
+ id="mini_login[% qs_suffix FILTER html %]"
+ onsubmit="return check_mini_login_fields( '[% qs_suffix FILTER html %]' );"
+ >
+ <input id="Bugzilla_login[% qs_suffix FILTER html %]"
+ class="bz_login"
+ name="Bugzilla_login"
+ onfocus="mini_login_on_focus('[% qs_suffix FILTER js %]')"
+ >
+ <input class="bz_password"
+ id="Bugzilla_password[% qs_suffix FILTER html %]"
+ name="Bugzilla_password"
+ type="password"
+ >
+ <input class="bz_password bz_default_hidden bz_mini_login_help" type="text"
+ id="Bugzilla_password_dummy[% qs_suffix %]" value="password"
+ onfocus="mini_login_on_focus('[% qs_suffix FILTER js %]')"
+ >
+ [% IF Param('rememberlogin') == 'defaulton' ||
+ Param('rememberlogin') == 'defaultoff'
+ %]
+ <input type="checkbox" id="Bugzilla_remember[% qs_suffix %]"
+ name="Bugzilla_remember" value="on" class="bz_remember"
+ [%+ "checked" IF Param('rememberlogin') == "defaulton" %]>
+ <label for="Bugzilla_remember[% qs_suffix %]">Remember</label>
+ [% END %]
+ <input type="submit" name="GoAheadAndLogIn" value="Log in"
+ id="log_in[% qs_suffix %]">
+ <script type="text/javascript">
+ mini_login_constants = {
+ "login" : "login",
+ "warning" : "You must set the login and password before logging in."
+ };
+ [%# We need this event to fire after autocomplete, because it does
+ # something different depending on whether or not there's already
+ # data in the login and password box.
+ # However, autocomplete happens at all sorts of different times in
+ # different browsers (before or after onDOMReady, before or after
+ # window.onload, in almost all combinations you can imagine).
+ # The only good solution I found is to time the event 200
+ # milliseconds after window.onload for WebKit (doing it immediately
+ # at onload works in Chrome but not in Safari, but I can't detect
+ # them separately using YUI), and right after onDOMReady in Gecko.
+ # The WebKit solution is also fairly guaranteed to work on any
+ # browser (it's just strange, since the fields only populate 200 ms
+ # after the page loads), so it's the default. IE doesn't even
+ # recognize our forms as login forms, so I made it use the Gecko
+ # method also (since it's nicer visually). Opera never autocompletes
+ # forms without user interaction, so it also uses the Gecko method.
+ #%]
+ if (YAHOO.env.ua.gecko || YAHOO.env.ua.ie || YAHOO.env.ua.opera) {
+ YAHOO.util.Event.onDOMReady(function() {
+ init_mini_login_form('[% qs_suffix FILTER html %]');
+ });
+ }
+ else {
+ YAHOO.util.Event.on(window, 'load', function () {
+ window.setTimeout(function() {
+ init_mini_login_form('[% qs_suffix FILTER html %]');
+ }, 200);
+ });
+ }
+ </script>
+ <a href="#" onclick="return hide_mini_login_form('[% qs_suffix %]')">[x]</a>
+ </form>
+</li>
+<li id="forgot_container[% qs_suffix %]">
+ <span class="separator">| </span>
+ <a id="forgot_link[% qs_suffix %]" href="[% script_name FILTER html %]#forgot"
+ onclick="return show_forgot_form('[% qs_suffix %]')">Forgot Password</a>
+ <form action="token.cgi" method="post" id="forgot_form[% qs_suffix %]"
+ class="mini_forgot bz_default_hidden">
+ <label>Login: <input type="text" name="loginname" size="20"></label>
+ <input id="forgot_button[% qs_suffix %]" value="Reset Password"
+ type="submit">
+ <input type="hidden" name="a" value="reqpw">
+ <a href="#" onclick="return hide_forgot_form('[% qs_suffix %]')">[x]</a>
+ </form>
+</li>
diff --git a/template/en/default/account/auth/login.html.tmpl b/template/en/default/account/auth/login.html.tmpl
new file mode 100644
index 000000000..80dd12153
--- /dev/null
+++ b/template/en/default/account/auth/login.html.tmpl
@@ -0,0 +1,120 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ # Toms Baugis <toms@myrealbox.com>
+ #%]
+
+[%# INTERFACE:
+ # target: string. URL to go to after login.
+ #%]
+
+[% IF !target %]
+ [% target = "index.cgi" %]
+[% END %]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% PROCESS global/header.html.tmpl
+ title = "Log in to $terms.Bugzilla",
+ onload = "document.forms['login'].Bugzilla_login.focus()"
+%]
+
+[% USE Bugzilla %]
+
+<p>
+ I need a legitimate login and password to continue.
+</p>
+
+<form name="login" action="[% target FILTER html %]" method="POST"
+[%- IF Bugzilla.cgi.param("data") %] enctype="multipart/form-data"[% END %]>
+ <table>
+ <tr>
+ <th align="right"><label for="Bugzilla_login">Login:</label></th>
+ <td>
+ <input size="35" id="Bugzilla_login" name="Bugzilla_login">
+ [% Param('emailsuffix') FILTER html %]
+ </td>
+ </tr>
+ <tr>
+ <th align="right"><label for="Bugzilla_password">Password:</label></th>
+ <td>
+ <input type="password" size="35" id="Bugzilla_password" name="Bugzilla_password">
+ </td>
+ </tr>
+
+ [% IF Param('rememberlogin') == 'defaulton' ||
+ Param('rememberlogin') == 'defaultoff' %]
+ <tr>
+ <th>&nbsp;</th>
+ <td>
+ <input type="checkbox" id="Bugzilla_remember" name="Bugzilla_remember" value="on"
+ [%+ "checked" IF Param('rememberlogin') == "defaulton" %]>
+ <label for="Bugzilla_remember">Remember my Login</label>
+ </td>
+ </tr>
+ [% END %]
+
+ <tr>
+ <th>&nbsp;</th>
+ <td>
+ <input type="checkbox" id="Bugzilla_restrictlogin" name="Bugzilla_restrictlogin"
+ checked="checked">
+ <label for="Bugzilla_restrictlogin">Restrict this session to this IP address
+ (using this option improves security)</label>
+ </td>
+ </tr>
+ </table>
+
+ [% PROCESS "global/hidden-fields.html.tmpl"
+ exclude="^Bugzilla_(login|password|restrictlogin)$" %]
+
+ <input type="submit" name="GoAheadAndLogIn" value="Log in" id="log_in">
+
+ <p>
+ (Note: you should make sure cookies are enabled for this site.
+ Otherwise, you will be required to log in frequently.)
+ </p>
+</form>
+
+[%# Allow the user to create a new account, or request a token to change
+ # their password, assuming that our auth method allows that.
+ #%]
+
+ [% IF Param("createemailregexp") && user.authorizer.user_can_create_account %]
+ <hr>
+
+ <p>
+ If you don't have a [% terms.Bugzilla %] account, you can
+ <a href="createaccount.cgi">create a new account</a>.
+ </p>
+ [% END %]
+
+ [% IF user.authorizer.can_change_password %]
+ <hr>
+
+ <form id="forgot" method="get" action="token.cgi">
+ <input type="hidden" name="a" value="reqpw">
+ If you have an account, but have forgotten your password,
+ enter your login name below and submit a request
+ to change your password.<br>
+ <input size="35" name="loginname">
+ <input type="submit" id="request" value="Reset Password">
+ </form>
+ [% END %]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/account/cancel-token.txt.tmpl b/template/en/default/account/cancel-token.txt.tmpl
new file mode 100644
index 000000000..6619dedd3
--- /dev/null
+++ b/template/en/default/account/cancel-token.txt.tmpl
@@ -0,0 +1,106 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): John Vandenberg <zeroj@null.net>
+ # Tobias Burnus <burnus@net-b.de>
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+From: [% Param('mailfrom') %]
+To: [% emailaddress %]
+Subject: [% PROCESS subject %]
+X-Bugzilla-Type: admin
+
+A request was canceled from [% remoteaddress %].
+
+If you did not request this, it could be either an honest
+mistake or someone attempting to break into your [% terms.Bugzilla %] account.
+
+Take a look at the information below and forward this email
+to [% Param('maintainer') %] if you suspect foul play.
+
+ Token: [% token %]
+ Token Type: [% tokentype %]
+ User: [% emailaddress %]
+ Issue Date: [% issuedate FILTER time("%Y-%m-%d %H:%M:%S %Z", timezone) %]
+ Event Data: [% eventdata %]
+Canceled Because: [% PROCESS cancelactionmessage %]
+
+[% BLOCK subject %]
+ [% IF tokentype == 'new_account' %]
+ User account creation request canceled
+ [% ELSIF tokentype == 'password' %]
+ Password change request canceled
+ [% ELSIF tokentype == 'emailnew' OR tokentype == 'emailold' %]
+ Email change request canceled
+ [% ELSE %]
+ [% tokentype %] token canceled
+ [% END %]
+[% END %]
+
+[% BLOCK cancelactionmessage %]
+ [% IF cancelaction == 'account_exists' %]
+ Account [% email %] already exists.
+
+ [% ELSIF cancelaction == 'email_change_canceled' %]
+ The request to change the email address for
+ the [% old_email %] account to [% new_email %] has
+ been canceled.
+
+ [% ELSIF cancelaction == 'email_change_canceled_reinstated' %]
+ The request to change the email address for your account
+ to [% new_email %] has been canceled. Your old account
+ settings have been reinstated.
+
+ [% ELSIF cancelaction == 'emailold_change_canceled' %]
+ The request to change the email address for your account
+ to [% new_email %] has been canceled.
+
+ [% ELSIF cancelaction == 'password_change_canceled' %]
+ You have requested cancellation.
+
+ [% ELSIF cancelaction == 'account_creation_canceled' %]
+ The creation of the user account [% emailaddress %]
+ has been canceled.
+
+ [% ELSIF cancelaction == 'user_logged_in' %]
+ You have logged in.
+
+ [% ELSIF cancelaction == 'wrong_token_for_changing_passwd' %]
+ You have tried to use the token to change the password.
+
+ [% ELSIF cancelaction == 'wrong_token_for_cancelling_email_change' %]
+ You have tried to use the token to cancel the email address change.
+
+ [% ELSIF cancelaction == 'wrong_token_for_confirming_email_change' %]
+ You have tried to use the token to confirm the email address change.
+
+ [% ELSIF cancelaction == 'wrong_token_for_creating_account' %]
+ You have tried to use the token to create a user account.
+
+ [% ELSE %]
+ [%# Give sensible error if the cancel-token function is used incorrectly.
+ #%]
+ You are using [% terms.Bugzilla %]'s cancel-token function incorrectly. You
+ passed in the string '[% cancelaction %]'. The correct use is to pass
+ in a tag, and define that tag in the file cancel-token.txt.tmpl.
+
+ If you are a [% terms.Bugzilla %] end-user seeing this message, please forward this
+ email to [% Param('maintainer') %].
+ [% END %]
+[% END %]
diff --git a/template/en/default/account/create.html.tmpl b/template/en/default/account/create.html.tmpl
new file mode 100644
index 000000000..5b8220193
--- /dev/null
+++ b/template/en/default/account/create.html.tmpl
@@ -0,0 +1,79 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ #%]
+
+[%# INTERFACE
+ # none
+ #
+ # Param("maintainer") is used to display the maintainer's email.
+ # Param("emailsuffix") is used to pre-fill the email field.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% title = BLOCK %]
+ Create a new [% terms.Bugzilla %] account
+[% END %]
+
+[% PROCESS global/header.html.tmpl
+ title = title
+ onload = "document.forms['account_creation_form'].login.focus();" %]
+
+<p>
+ To create a [% terms.Bugzilla %] account, all you need to do is to enter
+[% IF Param('emailsuffix') == '' %]
+ a legitimate email address.
+[% ELSE %]
+ an account name which when combined with [% Param('emailsuffix') %]
+ corresponds to an address where you receive email.
+[% END %]
+ You will receive an email at this address to confirm the creation of your
+ account. <b>You will not be able to log in until you receive the email.</b>
+ If it doesn't arrive within a reasonable amount of time, you may contact
+ the maintainer of this [% terms.Bugzilla %] installation
+ at <a href="mailto:[% Param("maintainer") %]">[% Param("maintainer") %]</a>.
+</p>
+
+[% IF Param('createemailregexp') == '.*' && Param('emailsuffix') == '' %]
+<p>
+ <b>PRIVACY NOTICE:</b> [% terms.Bugzilla %] is an open [% terms.bug %]
+ tracking system. Activity on most [% terms.bugs %], including email
+ addresses, will be visible to the public. We <b>recommend</b> using a
+ secondary account or free web email service (such as Gmail, Yahoo,
+ Hotmail, or similar) to avoid receiving spam at your primary email address.
+</p>
+[% END %]
+
+<form id="account_creation_form" method="get" action="createaccount.cgi">
+ <table>
+ <tr>
+ <td align="right">
+ <b>Email address:</b>
+ </td>
+ <td>
+ <input size="35" id="login" name="login">
+ [% Param('emailsuffix') FILTER html %]
+ </td>
+ </tr>
+ </table>
+ <br>
+ <input type="submit" id="send" value="Send">
+</form>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/account/created.html.tmpl b/template/en/default/account/created.html.tmpl
new file mode 100644
index 000000000..d794198bc
--- /dev/null
+++ b/template/en/default/account/created.html.tmpl
@@ -0,0 +1,40 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+
+[%# INTERFACE:
+ # login: string. The user's Bugzilla login email address.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% title = BLOCK %]
+ Request for new user account '[% login FILTER html %]' submitted
+[% END %]
+
+[% PROCESS global/header.html.tmpl title = title %]
+
+<p>
+ A confirmation email has been sent containing a link to continue
+ creating an account. The link will expire if an account is not
+ created within [% constants.MAX_TOKEN_AGE FILTER html %] days.
+</p>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/account/email/change-new.txt.tmpl b/template/en/default/account/email/change-new.txt.tmpl
new file mode 100644
index 000000000..5803b0274
--- /dev/null
+++ b/template/en/default/account/email/change-new.txt.tmpl
@@ -0,0 +1,41 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): John Vandenberg <zeroj@null.net>
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+From: [% Param('mailfrom') %]
+To: [% emailaddress %]
+Subject: [% terms.Bugzilla %] Change Email Address Request
+X-Bugzilla-Type: admin
+
+[%+ terms.Bugzilla %] has received a request to change the email address
+for the account [% oldemailaddress %] to your address.
+
+To confirm the change, visit the following link:
+
+[%+ urlbase %]token.cgi?t=[% token FILTER url_quote %]&a=cfmem
+
+If you are not the person who made this request, or you wish to cancel
+this request, visit the following link:
+
+[%+ urlbase %]token.cgi?t=[% token FILTER url_quote %]&a=cxlem
+
+If you do nothing, the request will lapse after [% constants.MAX_TOKEN_AGE %] days
+(on [% expiration_ts FILTER time("%B %e, %Y at %H:%M %Z") %]).
diff --git a/template/en/default/account/email/change-old.txt.tmpl b/template/en/default/account/email/change-old.txt.tmpl
new file mode 100644
index 000000000..6b7774420
--- /dev/null
+++ b/template/en/default/account/email/change-old.txt.tmpl
@@ -0,0 +1,46 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): John Vandenberg <zeroj@null.net>
+ #%]
+[%# INTERFACE:
+ # emailaddress: string. The user's old Bugzilla login email address.
+ # newemailaddress: string. The user's new Bugzilla login email address.
+ # token: string. The token associated with this change.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+From: [% Param('mailfrom') %]
+To: [% emailaddress %]
+Subject: [% terms.Bugzilla %] Change Email Address Request
+Importance: High
+X-MSMail-Priority: High
+X-Priority: 1
+X-Bugzilla-Type: admin
+
+[%+ terms.Bugzilla %] has received a request to change the email address
+for your account to [%+ newemailaddress %].
+
+If you are not the person who made this request, or you wish to cancel
+this request, visit the following link:
+
+[%+ urlbase %]token.cgi?t=[% token FILTER url_quote %]&a=cxlem
+
+If you do nothing, and [%+ newemailaddress %] confirms this request,
+the change will be made permanent after [% constants.MAX_TOKEN_AGE %] days
+(on [% expiration_ts FILTER time("%B %e, %Y at %H:%M %Z") %]).
diff --git a/template/en/default/account/email/confirm-new.html.tmpl b/template/en/default/account/email/confirm-new.html.tmpl
new file mode 100644
index 000000000..a677db796
--- /dev/null
+++ b/template/en/default/account/email/confirm-new.html.tmpl
@@ -0,0 +1,79 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+
+[%# INTERFACE:
+ # token: string. The token to be used in the user account creation.
+ # email: email address of the user account.
+ # expiration_ts: expiration date of the token.
+ #%]
+
+[% title = BLOCK %]Create a new user account for '[% email FILTER html %]'[% END %]
+[% PROCESS "global/header.html.tmpl"
+ title = title
+ onload = "document.forms['confirm_account_form'].realname.focus();" %]
+
+<p>
+ To create your account, you must enter a password in the form below.
+ Your email address and Real Name (if provided) will be shown with
+ changes you make.
+</p>
+
+<form id="confirm_account_form" method="post" action="token.cgi">
+ <input type="hidden" name="t" value="[% token FILTER html %]">
+ <input type="hidden" name="a" value="confirm_new_account">
+ <table>
+ <tr>
+ <th align="right">Email Address:</th>
+ <td>[% email FILTER html %]</td>
+ </tr>
+ <tr>
+ <th align="right"><small><i>(OPTIONAL)</i></small> <label for="realname">Real Name</label>:</th>
+ <td><input type="text" id="realname" name="realname" value=""></td>
+ </tr>
+ <tr>
+ <th align="right"><label for="passwd1">Type your password</label>:</th>
+ <td>
+ <input type="password" id="passwd1" name="passwd1" value="">
+ (minimum [% constants.USER_PASSWORD_MIN_LENGTH FILTER none %] characters)
+ </td>
+ </tr>
+ <tr>
+ <th align="right"><label for="passwd2">Confirm your password</label>:</th>
+ <td><input type="password" id="passwd2" name="passwd2" value=""></td>
+ </tr>
+ <tr>
+ <th align="right">&nbsp;</th>
+ <td><input type="submit" id="confirm" value="Create"></td>
+ </tr>
+ </table>
+</form>
+
+<p>
+ This account will not be created if this form is not completed by
+ <u>[% expiration_ts FILTER time("%B %e, %Y at %H:%M %Z") %]</u>.
+</p>
+
+<p>
+ If you do not wish to create an account with this email click the
+ cancel account button below and your details will be forgotten.
+</p>
+
+<form id="cancel_account_form" method="post" action="token.cgi">
+ <input type="hidden" name="t" value="[% token FILTER html %]">
+ <input type="hidden" name="a" value="cancel_new_account">
+ <input type="submit" id="confirm" value="Cancel Account">
+</form>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/account/email/confirm.html.tmpl b/template/en/default/account/email/confirm.html.tmpl
new file mode 100644
index 000000000..39add3238
--- /dev/null
+++ b/template/en/default/account/email/confirm.html.tmpl
@@ -0,0 +1,47 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): John Vandenberg <zeroj@null.net>
+ #%]
+
+[%# INTERFACE:
+ # token: string. The token to be used in this address change.
+ #%]
+
+[% title = "Confirm Change Email" %]
+[% PROCESS global/header.html.tmpl %]
+
+<p>
+ To change your email address, please enter the old email address:
+</p>
+
+<form method="post" action="token.cgi">
+ <input type="hidden" name="t" value="[% token FILTER html %]">
+ <input type="hidden" name="a" value="chgem">
+ <table>
+ <tr>
+ <th align="right">Old Email Address:</th>
+ <td><input type="text" name="email" size="36"></td>
+ </tr>
+ <tr>
+ <th align="right">&nbsp;</th>
+ <td><input type="submit" id="confirm" value="Submit"></td>
+ </tr>
+ </table>
+</form>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/account/email/request-new.txt.tmpl b/template/en/default/account/email/request-new.txt.tmpl
new file mode 100644
index 000000000..c56054b94
--- /dev/null
+++ b/template/en/default/account/email/request-new.txt.tmpl
@@ -0,0 +1,56 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+
+[%# INTERFACE:
+ # token: random string used to authenticate the transaction.
+ # expiration_ts: expiration date of the token.
+ # email: email address of the new account.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+From: [% Param('mailfrom') %]
+To: [% email %]
+Subject: [% terms.Bugzilla %]: confirm account creation
+X-Bugzilla-Type: admin
+
+[%+ terms.Bugzilla %] has received a request to create a user account
+using your email address ([% email %]).
+
+To continue creating an account using this email address, visit the
+following link by [% expiration_ts FILTER time("%B %e, %Y at %H:%M %Z") %]:
+
+[%+ urlbase %]token.cgi?t=[% token FILTER url_quote %]&a=request_new_account
+
+If you did not receive this email before [% expiration_ts FILTER time("%B %e, %Y at %H:%M %Z") %] or
+you wish to create an account using a different email address you can begin
+again by going to:
+
+[%+ urlbase %]createaccount.cgi
+
+[% IF Param('createemailregexp') == '.*' && Param('emailsuffix') == '' %]
+PRIVACY NOTICE: [% terms.Bugzilla %] is an open [% terms.bug %] tracking system. Activity on most
+[%+ terms.bugs %], including email addresses, will be visible to the public. We recommend
+using a secondary account or free web email service (such as Gmail, Yahoo,
+Hotmail, or similar) to avoid receiving spam at your primary email address.
+[% END %]
+
+If you do not wish to create an account, or if this request was made in
+error you can do nothing or visit the following link:
+
+[%+ urlbase %]token.cgi?t=[% token FILTER url_quote %]&a=cancel_new_account
+
+If the above links do not work, or you have any other issues regarding
+your account, please contact administration at [% Param('maintainer') %].
diff --git a/template/en/default/account/password/forgotten-password.txt.tmpl b/template/en/default/account/password/forgotten-password.txt.tmpl
new file mode 100644
index 000000000..574975c85
--- /dev/null
+++ b/template/en/default/account/password/forgotten-password.txt.tmpl
@@ -0,0 +1,40 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): John Vandenberg <zeroj@null.net>
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+From: [% Param('mailfrom') %]
+To: [% emailaddress %]
+Subject: [% terms.Bugzilla %] Change Password Request
+X-Bugzilla-Type: admin
+
+You have (or someone impersonating you has) requested to change your
+[%+ terms.Bugzilla %] password. To complete the change, visit the following link:
+
+[%+ urlbase %]token.cgi?t=[% token FILTER url_quote %]&a=cfmpw
+
+If you are not the person who made this request, or you wish to cancel
+this request, visit the following link:
+
+[%+ urlbase %]token.cgi?t=[% token FILTER url_quote %]&a=cxlpw
+
+If you do nothing, the request will lapse after [% constants.MAX_TOKEN_AGE %] days
+(on [% expiration_ts FILTER time("%B %e, %Y at %H:%M %Z", timezone) %]) or when you
+log in successfully.
diff --git a/template/en/default/account/password/set-forgotten-password.html.tmpl b/template/en/default/account/password/set-forgotten-password.html.tmpl
new file mode 100644
index 000000000..a2ae517c8
--- /dev/null
+++ b/template/en/default/account/password/set-forgotten-password.html.tmpl
@@ -0,0 +1,56 @@
+ [%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ #%]
+
+[% title = "Change Password" %]
+[% PROCESS global/header.html.tmpl %]
+
+<p>
+ To change your password, enter a new password twice:
+</p>
+
+<form method="post" action="token.cgi">
+ <input type="hidden" name="t" value="[% token FILTER html %]">
+ <input type="hidden" name="a" value="chgpw">
+ <table>
+ <tr>
+ <th align="right">New Password:</th>
+ <td>
+ <input type="password" name="password">
+ (minimum [% constants.USER_PASSWORD_MIN_LENGTH FILTER none %] characters)
+ </td>
+ </tr>
+
+ <tr>
+ <th align="right">New Password Again:</th>
+ <td>
+ <input type="password" name="matchpassword">
+ </td>
+ </tr>
+
+ <tr>
+ <th align="right">&nbsp;</th>
+ <td>
+ <input type="submit" id="update" value="Submit">
+ </td>
+ </tr>
+ </table>
+</form>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/account/prefs/account.html.tmpl b/template/en/default/account/prefs/account.html.tmpl
new file mode 100644
index 000000000..0457ff892
--- /dev/null
+++ b/template/en/default/account/prefs/account.html.tmpl
@@ -0,0 +1,99 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ #%]
+
+[%# INTERFACE:
+ # realname: string. The user's real name, if any.
+ # login_change_date: string. The date the email change will be complete. (optional)
+ # new_login_name: string. The user's new Bugzilla login whilst not confirmed. (optional)
+ #%]
+
+<table>
+ <tr>
+ <td colspan="3">
+ Please enter your existing password to confirm account changes.
+ </td>
+ </tr>
+ <tr>
+ <th align="right">Password:</th>
+ <td>
+ <input type="hidden" name="old_login" value="[% user.login FILTER html %]">
+ <input type="password" name="old_password">
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2"><hr></td>
+ </tr>
+ [% IF user.authorizer.can_change_password %]
+ <tr>
+ <th align="right">New password:</th>
+ <td>
+ <input type="password" name="new_password1">
+ </td>
+ </tr>
+
+ <tr>
+ <th align="right">Confirm new password:</th>
+ <td>
+ <input type="password" name="new_password2">
+ </td>
+ </tr>
+ [% END %]
+
+ <tr>
+ <th align="right">Your real name (optional, but encouraged):</th>
+ <td>
+ <input size="35" name="realname" value="[% realname FILTER html %]">
+ </td>
+ </tr>
+
+ [% IF user.authorizer.can_change_email && Param('allowemailchange') %]
+ [% IF login_change_date %]
+ [% IF new_login_name %]
+ <tr>
+ <th align="right">Pending email address:</th>
+ <td>[% new_login_name FILTER html %]</td>
+ </tr>
+ <tr>
+ <th align="right">Change request expires:</th>
+ <td>[% login_change_date FILTER time %]</td>
+ </tr>
+ [% ELSE %]
+ <tr>
+ <th align="right">Confirmed email address:</th>
+ <td>[% user.login FILTER html %]</td>
+ </tr>
+ <tr>
+ <th align="right">Completion date:</th>
+ <td>[% login_change_date FILTER time %]</td>
+ </tr>
+ [% END %]
+ [% ELSE %]
+ <tr>
+ <th align="right">New email address:</th>
+ <td>
+ <input size="35" name="new_login_name">
+ </td>
+ </tr>
+ [% END %]
+ [% END %]
+
+ [% Hook.process('field') %]
+
+</table>
diff --git a/template/en/default/account/prefs/email.html.tmpl b/template/en/default/account/prefs/email.html.tmpl
new file mode 100644
index 000000000..96a111bae
--- /dev/null
+++ b/template/en/default/account/prefs/email.html.tmpl
@@ -0,0 +1,289 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ # Myk Melez <myk@mozilla.org>
+ # Shane H. W. Travis <travis@sedsystems.ca>
+ #%]
+
+[%# INTERFACE:
+ # watchedusers: string.
+ # Comma-separated list of email addresses this user watches.
+ # watchers: array.
+ # Array of users watching this user's account.
+ # excludeself: boolean.
+ # True if user is not receiving self-generated mail.
+ # <rolename>: Multiple hashes, one for each rolename (e.g. assignee; see
+ # below), keyed by reasonname (e.g. comments; again, see
+ # below). The value is a boolean - true if the user is
+ # receiving mail for that reason when in that role.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+<p>
+ If you don't like getting a notification for "trivial"
+ changes to [% terms.bugs %], you can use the settings below to
+ filter some or all notifications.
+</p>
+
+<script type="text/javascript">
+<!--
+function SetCheckboxes(setting) {
+ for (var count = 0; count < document.userprefsform.elements.length; count++) {
+ var theinput = document.userprefsform.elements[count];
+ if (theinput.type == "checkbox" && !theinput.disabled) {
+ if (theinput.name.match("neg")) {
+ theinput.checked = false;
+ }
+ else {
+ theinput.checked = setting;
+ }
+ }
+ }
+}
+
+document.write('<input type="button" value="Enable All Mail" onclick="SetCheckboxes(true); return false;">\n');
+document.write('<input type="button" value="Disable All Mail" onclick="SetCheckboxes(false); return false;">\n');
+// -->
+</script>
+
+<hr>
+
+<table>
+ <tr>
+ <td colspan="2">
+ <b>Global options:</b>
+ </td>
+ </tr>
+
+ <tr>
+ <td width="150"></td>
+ <td>
+ [% prefname = "email-$constants.REL_ANY-$constants.EVT_FLAG_REQUESTED" %]
+ <input type="checkbox" name="[% prefname %]" id="[% prefname %]"
+ value="1"
+ [% " checked"
+ IF user.mail_settings.${constants.REL_ANY}.${constants.EVT_FLAG_REQUESTED} %]>
+ <label for="[% prefname %]">Email me when someone asks me to set a flag</label>
+ <br>
+ </td>
+ </tr>
+ <tr>
+ <td width="150"></td>
+ <td>
+ [% prefname = "email-$constants.REL_ANY-$constants.EVT_REQUESTED_FLAG" %]
+ <input type="checkbox" name="[% prefname %]" id="[% prefname %]"
+ value="1"
+ [% " checked"
+ IF user.mail_settings.${constants.REL_ANY}.${constants.EVT_REQUESTED_FLAG} %]>
+ <label for="[% prefname %]">Email me when someone sets a flag I asked for</label>
+ <br>
+ </td>
+ </tr>
+[% IF user.is_global_watcher %]
+ <tr>
+ <td width="150"></td>
+ <td>
+ You are watching all [% terms.bugs %]. To be removed from this role,
+ contact
+ <a href="mailto:[% Param("maintainer") %]">[% Param("maintainer") %]</a>.
+ </td>
+ </tr>
+[% END %]
+</table>
+
+<hr>
+<b>Field/recipient specific options:</b>
+<br>
+<br>
+
+[% events = [
+ { id = constants.EVT_ADDED_REMOVED,
+ description = "I'm added to or removed from this capacity" },
+ { id = constants.EVT_BUG_CREATED,
+ description = "A new $terms.bug is created" },
+ { id = constants.EVT_OPENED_CLOSED,
+ description = "The $terms.bug is resolved or reopened" },
+ { id = constants.EVT_PROJ_MANAGEMENT,
+ description = "The priority, status, severity, or milestone changes" },
+ { id = constants.EVT_COMMENT,
+ description = "New comments are added" },
+ { id = constants.EVT_ATTACHMENT,
+ description = "New attachments are added" },
+ { id = constants.EVT_ATTACHMENT_DATA,
+ description = "Some attachment data changes" },
+ { id = constants.EVT_KEYWORD,
+ description = "The keywords field changes" },
+ { id = constants.EVT_CC,
+ description = "The CC field changes" },
+ { id = constants.EVT_DEPEND_BLOCK,
+ description = "The dependency tree changes" },
+ { id = constants.EVT_OTHER,
+ description = "Any field not mentioned above changes" },
+] %]
+
+[% neg_events = [
+ { id = constants.EVT_UNCONFIRMED,
+ description = "The $terms.bug is in the UNCONFIRMED state" },
+ { id = constants.EVT_CHANGED_BY_ME,
+ description = "The change was made by me" },
+] %]
+
+[% relationships = [
+ { id = constants.REL_ASSIGNEE,
+ description = "Assignee" },
+ { id = constants.REL_REPORTER,
+ description = "Reporter" },
+ { id = constants.REL_CC,
+ description = "CCed" },
+] %]
+
+[% IF Param('useqacontact') %]
+ [% relationships.push({ id = constants.REL_QA,
+ description = "QA Contact" }) %]
+[% END %]
+
+
+[%# This is up here so that the "relationships" hook can modify it. %]
+[% no_added_removed = [constants.REL_REPORTER] %]
+
+[% Hook.process('relationships') %]
+
+[% num_columns = relationships.size %]
+
+<table class="bz_emailprefs" border="1">
+ <tr>
+ <td colspan="[% num_columns FILTER html %]" align="center" width="50%">
+ <b>When my relationship to this [% terms.bug %] is:</b>
+ </td>
+ <td rowspan="2" width="40%">
+ <b>I want to receive mail when:</b>
+ </td>
+ </tr>
+
+ <tr>
+ [% FOREACH relationship = relationships %]
+ <th align="center" width="9%">
+ [% relationship.description FILTER html %]
+ </th>
+ [% END %]
+ </tr>
+
+ [% FOREACH event = events %]
+ [% count = loop.count() %]
+ <tr class="bz_row_[% count % 2 == 1 ? "odd" : "even" %]">
+ [% FOREACH relationship = relationships %]
+ <td align="center">
+ <input type="checkbox"
+ name="email-[% relationship.id %]-[% event.id %]"
+ value="1"
+ [%# The combinations don't always make sense; disable a couple %]
+ [% IF event.id == constants.EVT_ADDED_REMOVED AND
+ no_added_removed.contains(relationship.id)
+ %]
+ disabled
+ [% ELSIF user.mail_settings.${relationship.id}.${event.id} %]
+ checked
+ [% END %]>
+ </td>
+ [% END %]
+ <td>
+ [% event.description FILTER html %]
+ </td>
+ </tr>
+ [% END %]
+
+ <tr>
+ <td colspan="[% num_columns FILTER html %]"
+ align="center" width="50%">
+ &nbsp;
+ </td>
+ <td width="40%">
+ <b>but not when (overrides above):</b>
+ </td>
+ </tr>
+
+ [% FOREACH event = neg_events %]
+ [% count = loop.count() %]
+ <tr class="bz_row_[% count % 2 == 1 ? "odd" : "even" %]">
+ [% FOREACH relationship = relationships %]
+ <td align="center">
+ <input type="checkbox"
+ name="neg-email-[% relationship.id %]-[% event.id %]"
+ value="1"
+ [% " checked" IF NOT user.mail_settings.${relationship.id}.${event.id} %]>
+ </td>
+ [% END %]
+ <td>
+ [% event.description FILTER html %]
+ </td>
+ </tr>
+ [% END %]
+
+</table>
+
+<hr>
+<b>User Watching</b>
+
+<p>
+If you watch a user, it is as if you are standing in their shoes for the
+purposes of getting email. Email is sent or not according to <u>your</u>
+preferences for <u>their</u> relationship to the [% terms.bug %]
+(e.g. Assignee).
+</p>
+
+<p>
+[% IF watchedusers.size %]
+You are watching everyone in the following list:
+ </p>
+ <p>
+ <select id="watched_by_you" name="watched_by_you" multiple="multiple" size="5">
+ [% FOREACH w = watchedusers %]
+ <option value="[% w FILTER html %]">[% w FILTER html %]</option>
+ [% END %]
+ </select> <br />
+ <input type="checkbox" id="remove_watched_users" name="remove_watched_users">
+ <label for="remove_watched_users">Remove selected users from my watch list</label>
+[% ELSE %]
+You are currently not watching any users.
+[% END %]
+</p>
+
+<p id="new_watched_by_you">Add users to my watch list (comma separated list):
+ [% INCLUDE global/userselect.html.tmpl
+ id => "new_watchedusers"
+ name => "new_watchedusers"
+ value => ""
+ size => 60
+ multiple => 5
+ %]
+</p>
+
+<p id="watching_you">Users watching you:<br>
+ [% IF watchers.size %]
+ [% FOREACH watcher = watchers %]
+ [% watcher FILTER html %] <br>
+ [% END %]
+ [% ELSE %]
+ <i>No one</i>
+ [% END %]
+</p>
+
+<hr>
+
+<br>
diff --git a/template/en/default/account/prefs/permissions.html.tmpl b/template/en/default/account/prefs/permissions.html.tmpl
new file mode 100644
index 000000000..5e8dc9ca2
--- /dev/null
+++ b/template/en/default/account/prefs/permissions.html.tmpl
@@ -0,0 +1,93 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ #%]
+
+[%# INTERFACE:
+ # has_bits: array of hashes. May be empty.
+ # name => Names of the permissions the user has.
+ # desc => Descriptions of the permissions the user has.
+ # set_bits: array of hashes. May be empty.
+ # name => Names of the permissions the user can set for
+ # other people.
+ # desc => Descriptions of the permissions the user can set for
+ # other people.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+<table align="center">
+ <tr>
+ <td>
+ [% IF has_bits.size %]
+ You have the following permission bits set on your account:
+ <table align="center">
+ [% FOREACH bit_description = has_bits %]
+ <tr>
+ <td>[% bit_description.name FILTER html %]</td>
+ <td>[% bit_description.desc FILTER html_light %]</td>
+ </tr>
+ [% END %]
+ </table>
+
+ [% FOREACH privs = ["editcomponents", "canconfirm", "editbugs"] %]
+ [% SET products = ${"local_$privs"} %]
+ [% IF products && products.size %]
+ <br>
+ <p>
+ You also have local '[% privs FILTER html %]' privileges
+ for the following products:
+ </p>
+ <p>
+ [% FOREACH product = products %]
+ [% product.name FILTER html %]<br>
+ [% END %]
+ </p>
+ [% END %]
+ [% END %]
+
+ [% ELSE %]
+ There are no permission bits set on your account.
+ [% END %]
+
+ [% IF user.in_group('editusers') %]
+ <br>
+ You have editusers privileges. You can turn on and off
+ all permissions for all users.
+ [% ELSIF set_bits.size %]
+ <br>
+ And you can turn on or off the following bits for
+ <a href="editusers.cgi">other users</a>:
+ <table align="center">
+ [% FOREACH bit_description = set_bits %]
+ <tr>
+ <td>[% bit_description.name FILTER html %]</td>
+ <td>[% bit_description.desc FILTER html_light %]</td>
+ </tr>
+ [% END %]
+ </table>
+ [% END %]
+
+ [% IF user.in_group('bz_sudoers') %]
+ <br>
+ You are a member of the <b>bz_sudoers</b> group, so you can
+ <a href="relogin.cgi?action=prepare-sudo">impersonate someone else</a>.
+ [% END %]
+ </td>
+ </tr>
+</table>
diff --git a/template/en/default/account/prefs/prefs.html.tmpl b/template/en/default/account/prefs/prefs.html.tmpl
new file mode 100644
index 000000000..2e7d98c07
--- /dev/null
+++ b/template/en/default/account/prefs/prefs.html.tmpl
@@ -0,0 +1,114 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+
+[%# INTERFACE:
+ # tabs: List of hashes. May not be empty. Each hash has three members:
+ # name: string. Name of the tab (used internally.)
+ # description: string. Description of the tab (used in tab title).
+ # saveable: boolean. True if tab has a form which can be submitted.
+ # True if user is not receiving self-generated mail.
+ # Note: For each tab name, a template "prefs/${tab.name}.tmpl" must exist,
+ # and its interface must be fulfilled.
+ # current_tab: A direct reference to one of the hashes in the tabs list.
+ # This tab will be displayed.
+ # changes_saved: boolean/string. True if the CGI processed form data before
+ # displaying anything, and can contain an optional custom
+ # message if required (which Perl still evaluates as True).
+ # dont_show_button: boolean. Prevent the display of the "Submit Changes" button.
+ #%]
+
+[% filtered_login = user.login FILTER html %]
+[% PROCESS global/header.html.tmpl
+ title = "User Preferences"
+ subheader = filtered_login
+ style_urls = ['skins/standard/admin.css']
+ javascript_urls = ['js/util.js']
+ doc_section = "userpreferences.html"
+ %]
+
+[% tabs = [{ name => "settings", label => "General Preferences",
+ link => "userprefs.cgi?tab=settings", saveable => "1" },
+ { name => "email", label => "Email Preferences",
+ link => "userprefs.cgi?tab=email", saveable => "1" },
+ { name => "saved-searches", label => "Saved Searches",
+ link => "userprefs.cgi?tab=saved-searches", saveable => "1" },
+ { name => "account", label => "Account Information",
+ link => "userprefs.cgi?tab=account", saveable => "1" },
+ { name => "permissions", label => "Permissions",
+ link => "userprefs.cgi?tab=permissions", saveable => "0" } ] %]
+
+[% Hook.process('tabs') %]
+
+[% FOREACH tab IN tabs %]
+ [% IF tab.name == current_tab_name %]
+ [% current_tab = tab %]
+ [% LAST %]
+ [% END %]
+[% END %]
+
+[% WRAPPER global/tabs.html.tmpl
+ tabs = tabs
+ current_tab = current_tab
+%]
+
+[% IF changes_saved %]
+ <div id="message">
+ The changes to your [% current_tab.label FILTER lower %] have been saved.
+
+ [% IF email_changes_saved %]
+ <p>
+ An email has been sent to both old and new email
+ addresses to confirm the change of email address.
+ </p>
+ [% END %]
+ </div>
+[% END %]
+
+<h3>[% current_tab.label %]</h3>
+
+[% IF current_tab.saveable %]
+ <form name="userprefsform" method="post" action="userprefs.cgi">
+ <input type="hidden" name="tab" value="[% current_tab.name %]">
+ <input type="hidden" name="token" value="[% token FILTER html %]">
+[% END %]
+
+[% PROCESS "account/prefs/${current_tab.name}.html.tmpl"
+ IF current_tab.name.defined %]
+
+[% IF current_tab.saveable %]
+ <input type="hidden" name="dosave" value="1">
+
+ [% UNLESS dont_show_button %]
+ <table>
+ <tr>
+ <td width="150">&nbsp;</td>
+ <td>
+ <input type="submit" id="update" value="Submit Changes">
+ </td>
+ </tr>
+ </table>
+ [% END %]
+ </form>
+[% END %]
+
+[% END %]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/account/prefs/saved-searches.html.tmpl b/template/en/default/account/prefs/saved-searches.html.tmpl
new file mode 100644
index 000000000..6d87ade76
--- /dev/null
+++ b/template/en/default/account/prefs/saved-searches.html.tmpl
@@ -0,0 +1,209 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ #%]
+
+[%# INTERFACE:
+ # queryshare_groups: list of groups the user may share queries with
+ # (id, name).
+ # bless_group_ids: list of group ids the user may bless.
+ #%]
+
+[% IF user.can_bless %]
+ <script type="text/javascript"><!--
+ function update_checkbox(group) {
+ var bless_groups = [[% bless_group_ids.join(",") FILTER js %]];
+ var checkbox = document.getElementById(group.name.replace(/share_(\d+)/, "force_$1"));
+
+ if (bz_isValueInArray(bless_groups, group.value)) {
+ YAHOO.util.Dom.removeClass(checkbox.parentNode, "bz_default_hidden");
+ } else {
+ YAHOO.util.Dom.addClass(checkbox.parentNode, "bz_default_hidden");
+ checkbox.checked = false;
+ }
+ } //-->
+ </script>
+[% END %]
+
+<p>Your saved searches are as follows:</p>
+
+<blockquote>
+ <table border="1" cellpadding="3">
+ <tr>
+ <th>
+ Search
+ </th>
+ <th>
+ Run
+ </th>
+ <th>
+ Edit
+ </th>
+ <th>
+ Forget
+ </th>
+ <th>
+ Show in
+ Footer
+ </th>
+ [% may_share = user.in_group(Param('querysharegroup')) && queryshare_groups.size %]
+ [% IF may_share %]
+ <th>
+ Share With a Group
+ </th>
+ [% END %]
+ </tr>
+ <tr>
+ <td>My [% terms.Bugs %]</td>
+ <td>
+ [% filtered_username = user.login FILTER url_quote %]
+ <a href="[% Param('mybugstemplate').replace('%userid%', filtered_username) %]">Run</a>
+ </td>
+ <td>
+ &nbsp;
+ </td>
+ <td>
+ &nbsp;
+ </td>
+ <td align="center">
+ <input type="checkbox"
+ name="showmybugslink"
+ value="1"
+ [% " checked" IF user.showmybugslink %]>
+ </td>
+ [% IF may_share %]
+ <td>
+ &mdash;
+ </td>
+ [% END %]
+ </tr>
+ [% FOREACH q = user.queries %]
+ <tr>
+ <td>[% q.name FILTER html %]</td>
+ <td>
+ <a href="buglist.cgi?cmdtype=dorem&amp;remaction=run&amp;namedcmd=[% q.name FILTER url_quote %]
+ [% IF q.shared_with_group.id %]&amp;sharer_id=[% user.id FILTER url_quote %][% END %]">Run</a>
+ </td>
+ <td>
+ <a href="query.cgi?[% q.edit_link FILTER html %]&amp;known_name=
+ [% q.name FILTER url_quote %]">Edit</a>
+ </td>
+ <td>
+ [% IF q.used_in_whine %]
+ Remove from <a href="editwhines.cgi">whining</a> first
+ [% ELSE %]
+ <a href="buglist.cgi?cmdtype=dorem&amp;remaction=forget&amp;namedcmd=
+ [% q.name FILTER url_quote %]&amp;token=
+ [% issue_hash_token([q.id, q.name]) FILTER url_quote %]">Forget</a>
+ [% END %]
+ </td>
+ <td align="center">
+ <input type="checkbox"
+ name="link_in_footer_[% q.id FILTER html %]"
+ value="1"
+ alt="[% q.name FILTER html %]"
+ [% " checked" IF q.link_in_footer %]>
+ </td>
+ [% IF may_share %]
+ <td>
+ <select name="share_[% q.id FILTER html %]"
+ [% IF user.can_bless %] onchange="update_checkbox(this);"[% END %]>
+ <option value="">Don't share</option>
+ [% FOREACH group = queryshare_groups %]
+ <option value="[% group.id %]"
+ [% ' selected="selected"'
+ IF q.shared_with_group.id == group.id %]
+ >[% group.name FILTER html %]</option>
+ [% END %]
+ </select>
+ [% IF user.can_bless %]
+ <span [% IF !bless_group_ids.grep("^$q.shared_with_group.id\$").0
+ %]class="bz_default_hidden"[% END %]>
+ <input type="checkbox" id="force_[% q.id FILTER html %]"
+ name="force_[% q.id FILTER html %]" value="1">
+ <label for="force_[% q.id FILTER html %]">Add to footer</label>
+ </span>
+ [% END %]
+ [% IF q.shared_with_users %]
+ (shared with [% q.shared_with_users FILTER html %]
+ [%+ q.shared_with_users > 1 ? "users" : "user" %])
+ [% END %]
+ </td>
+ [% END %]
+ </tr>
+ [% END %]
+ </table>
+[% IF user.can_bless %]
+ <p>Note that for every search that has the "Add to footer" selected, a
+ link to the shared search is added to the footer of every user that is
+ a direct member of the group at the time you click Submit Changes.</p>
+[% END %]
+</blockquote>
+
+[% IF user.queries_available.size %]
+ <p>You may use these searches saved and shared by others:</p>
+
+ <table border="1" cellpadding="3">
+ <tr>
+ <th>
+ Search
+ </th>
+ <th>
+ Shared By
+ </th>
+ <th>
+ Shared To
+ </th>
+ <th>
+ Run
+ </th>
+ <th>
+ Edit
+ </th>
+ <th>
+ Show in
+ Footer
+ </th>
+ </tr>
+ [% FOREACH q = user.queries_available %]
+ <tr>
+ <td>[% q.name FILTER html %]</td>
+ <td>[% q.user.identity FILTER html %]</td>
+ <td>[% q.shared_with_group.name FILTER html %]</td>
+ <td>
+ <a href="buglist.cgi?cmdtype=dorem&amp;remaction=run&amp;namedcmd=
+ [% q.name FILTER url_quote %]&amp;sharer_id=
+ [% q.user.id FILTER url_quote %]">Run</a>
+ </td>
+ <td>
+ <a href="query.cgi?[% q.edit_link FILTER html %]&amp;known_name=
+ [% q.name FILTER url_quote %]">Edit</a>
+ </td>
+ <td align="center">
+ <input type="checkbox"
+ name="link_in_footer_[% q.id FILTER html %]"
+ value="1"
+ alt="[% q.name FILTER html %]"
+ [% " checked" IF q.link_in_footer %]>
+ </td>
+ </tr>
+ [% END %]
+ </table>
+[% ELSE %]
+ <p>No searches are shared with you by other users.</p>
+[% END %]
diff --git a/template/en/default/account/prefs/settings.html.tmpl b/template/en/default/account/prefs/settings.html.tmpl
new file mode 100644
index 000000000..f8b6ba487
--- /dev/null
+++ b/template/en/default/account/prefs/settings.html.tmpl
@@ -0,0 +1,77 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Shane H. W. Travis <travis@sedsystems.ca>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ #
+ #%]
+
+[%# INTERFACE:
+ # setting_names: an array of strings
+ # settings: a hash of hashes, keyed by setting_name.
+ # Each hash contains:
+ # is_enabled - boolean
+ # default_value - string (global default for this setting)
+ # value - string (user-defined preference)
+ # is_default - boolean (true if user has no preference)
+ # has_settings_enabled : boolean; is true if there is at least one user pref
+ # enabled by the maintainer.
+ #%]
+
+[% PROCESS "global/setting-descs.none.tmpl" %]
+
+[% IF settings.size %]
+ [% UNLESS has_settings_enabled %]
+ <p class="criticalmessages">
+ All user preferences have been disabled by the
+ <a href="mailto:[% Param("maintainer") %]">maintainer</a>
+ of this installation, and so you cannot customize any.
+ </p>
+ [% END %]
+
+ <table border="0" cellpadding="8">
+ [% FOREACH name = setting_names %]
+ [% default_name = name _ '-isdefault' %]
+ [% default_val = settings.${name}.default_value %]
+ <tr>
+ <td align="right">
+ [% setting_descs.$name OR name FILTER html %]
+ </td>
+ <td>
+ [% IF settings.${name}.is_enabled %]
+ <select name="[% name FILTER html %]" id="[% name FILTER html %]">
+ <option value="[% default_name FILTER html %]"
+ [% ' selected="selected"' IF settings.${name}.is_default %]>
+ Site Default ([% setting_descs.${default_val} OR default_val FILTER html %])
+ </option>
+ [% FOREACH x = settings.${name}.legal_values %]
+ <option value="[% x FILTER html %]"
+ [% ' selected="selected"'
+ IF x == settings.${name}.value
+ AND NOT settings.${name}.is_default %]>
+ [% setting_descs.${x} OR x FILTER html %]
+ </option>
+ [% END %]
+ </select>
+ [% ELSE %]
+ <select name="[% name FILTER html %]" id="[% name FILTER html %]" disabled="disabled">
+ <option value="[% default_name FILTER html %]">
+ Site Default ([% setting_descs.${default_val} OR default_val FILTER html %])
+ </option>
+ </select>
+ [% END %]
+ </td>
+ </tr>
+ [% END %]
+ </table>
+[% END %]
+<br>
diff --git a/template/en/default/account/profile-activity.html.tmpl b/template/en/default/account/profile-activity.html.tmpl
new file mode 100644
index 000000000..c6fd45c65
--- /dev/null
+++ b/template/en/default/account/profile-activity.html.tmpl
@@ -0,0 +1,86 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Vlad Dascalu <jocuri@softhome.net>
+ # Gavin Shelley <bugzilla@chimpychompy.org>
+ #%]
+
+[%# INTERFACE:
+ # otheruser: Bugzilla User Object; The user whose profile activity
+ # we are viewing.
+ #
+ # listselectionvalues: selection values to recreate the current user list.
+ #
+ # profile_changes: An array of hashes containing the following fields:
+ #
+ # who: string; login name of who made the change
+ # activity_when: string; when the change was made
+ # what: string; the description of the field which was changed
+ # removed: string; the removed value (maybe empty string)
+ # added: string; the added value (maybe empty string)
+ #%]
+
+[% title = BLOCK %]
+ Account History for '[% otheruser.login FILTER html %]'
+[% END %]
+
+
+[% PROCESS global/header.html.tmpl
+ title = title
+%]
+
+[% PROCESS admin/users/listselectvars.html.tmpl
+ listselectionvalues = listselectionvalues
+%]
+
+[% columns =
+ [{name => 'who'
+ heading => 'Who'
+ }
+ {name => 'activity_when'
+ heading => 'When'
+ }
+ {name => 'what'
+ heading => 'What'
+ content_use_field => 1
+ }
+ {name => 'removed'
+ heading => 'Removed'
+ }
+ {name => 'added'
+ heading => 'Added'
+ }
+ ]
+%]
+
+[% PROCESS admin/table.html.tmpl
+ columns = columns
+ data = profile_changes
+%]
+
+<p><a href="editusers.cgi?action=edit&amp;userid=
+ [%- otheruser.id FILTER url_quote %]"
+ title="Edit user '[% otheruser.login FILTER html %]'">Edit this user</a> or
+ <a title="Search For Users" href="editusers.cgi">search for other accounts</a>
+ [% IF listselectionvalues.matchtype != 'exact' %]
+ or go <a title="Return to the user list"
+ href="editusers.cgi?action=list[% INCLUDE listselectionurlparams %]">back
+ to the user list</a>
+ [% END %]
+</p>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/admin.html.tmpl b/template/en/default/admin/admin.html.tmpl
new file mode 100644
index 000000000..145360bfa
--- /dev/null
+++ b/template/en/default/admin/admin.html.tmpl
@@ -0,0 +1,135 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% title = BLOCK %]
+ Administer your installation ([% terms.Bugzilla %]
+ [%+ constants.BUGZILLA_VERSION FILTER html %])
+[% END %]
+
+[% PROCESS global/header.html.tmpl title = title
+ style_urls = ['skins/standard/admin.css']
+ doc_section = "administration.html"
+%]
+
+<div>
+ This page is only accessible to empowered users. You can access administrative pages
+ from here (based on your privileges), letting you configure different aspects of
+ this installation. Note: some sections may not be accessible to you and are marked
+ using a lighter color.
+</div>
+
+<table>
+ <tr>
+ <td class="admin_links">
+ <dl>
+ [% class = user.in_group('tweakparams') ? "" : "forbidden" %]
+ <dt id="parameters" class="[% class %]"><a href="editparams.cgi">Parameters</a></dt>
+ <dd class="[% class %]">Set core parameters of the installation. That's the
+ place where you specify the URL to access this installation, determine how
+ users authenticate, choose which [% terms.bug %] fields to display, select
+ the mail transfer agent to send email notifications, choose which group of
+ users can use charts and share queries, and much more.</dd>
+
+ <dt id="preferences" class="[% class %]"><a href="editsettings.cgi">Default Preferences</a></dt>
+ <dd class="[% class %]">Set the default user preferences. These are the values
+ which will be used by default for all users. Users will be able to edit their
+ own preferences from the <a href="userprefs.cgi?tab=settings">Preferences</a>.</dd>
+
+ [% class = user.in_group('editcomponents') ? "" : "forbidden" %]
+ <dt id="sanitycheck" class="[% class %]"><a href="sanitycheck.cgi">Sanity Check</a></dt>
+ <dd class="[% class %]">Run sanity checks to locate problems in your database.
+ This may take several tens of minutes depending on the size of your installation.
+ You can also automate this check by running <tt>sanitycheck.pl</tt> from a cron job.
+ A notification will be sent per email to the specified user if errors are detected.</dd>
+
+ [% class = (user.in_group('editusers') || user.can_bless) ? "" : "forbidden" %]
+ <dt id="users" class="[% class %]"><a href="editusers.cgi">Users</a></dt>
+ <dd class="[% class %]">Create new user accounts or edit existing ones. You can
+ also add and remove users from groups (also known as "user privileges").</dd>
+
+ [% class = (Param('useclassification') && user.in_group('editclassifications')) ? "" : "forbidden" %]
+ <dt id="classifications" class="[% class %]"><a href="editclassifications.cgi">Classifications</a></dt>
+ <dd class="[% class %]">If your installation has to manage many products at once,
+ it's a good idea to group these products into distinct categories. This lets users
+ find information more easily when doing searches or when filing new [% terms.bugs %].</dd>
+
+ [% class = (user.in_group('editcomponents')
+ || user.get_products_by_permission("editcomponents").size) ? "" : "forbidden" %]
+ <dt id="products" class="[% class %]"><a href="editproducts.cgi">Products</a></dt>
+ <dd class="[% class %]">Edit all aspects of products, including group restrictions
+ which let you define who can access [% terms.bugs %] being in these products. You
+ can also edit some specific attributes of products such as
+ <a href="editcomponents.cgi">components</a>, <a href="editversions.cgi">versions</a>
+ and <a href="editmilestones.cgi">milestones</a> directly.</dd>
+
+ [% class = user.in_group('editcomponents') ? "" : "forbidden" %]
+ <dt id="flags" class="[% class %]"><a href="editflagtypes.cgi">Flags</a></dt>
+ <dd class="[% class %]">A flag is a custom 4-states attribute of [% terms.bugs %]
+ and/or attachments. These states are: granted, denied, requested and undefined.
+ You can set as many flags as desired per [% terms.bug %], and define which users
+ are allowed to edit them.</dd>
+
+ [% Hook.process('end_links_left') %]
+ </dl>
+ </td>
+
+ <td class="admin_links">
+ <dl>
+ [% class = user.in_group('admin') ? "" : "forbidden" %]
+ <dt id="custom_fields" class="[% class %]"><a href="editfields.cgi">Custom Fields</a></dt>
+ <dd class="[% class %]">[% terms.Bugzilla %] lets you define fields which are
+ not implemented by default, based on your local and specific requirements.
+ These fields can then be used as any other field, meaning that you can set
+ them in [% terms.bugs %] and run any search involving them.<br>
+ Before creating new fields, keep in mind that too many fields may make the user
+ interface more complex and harder to use. Be sure you have investigated other ways
+ to satisfy your needs before doing this.</dd>
+
+ <dt id="field_values" class="[% class %]"><a href="editvalues.cgi">Field Values</a></dt>
+ <dd class="[% class %]">Define legal values for fields whose values must belong
+ to some given list. This is also the place where you define legal values for some
+ types of custom fields.</dd>
+
+ <dt id="status_workflow" class="[% class %]"><a href="editworkflow.cgi">[%terms.Bug %] Status Workflow</a></dt>
+ <dd class="[% class %]">Customize your workflow and choose initial [% terms.bug %]
+ statuses available on [% terms.bug %] creation and allowed [% terms.bug %] status
+ transitions when editing existing [% terms.bugs %].</dd>
+
+ [% class = user.in_group('creategroups') ? "" : "forbidden" %]
+ <dt id="groups" class="[% class %]"><a href="editgroups.cgi">Groups</a></dt>
+ <dd class="[% class %]">Define groups which will be used in the installation.
+ They can either be used to define new user privileges or to restrict the access
+ to some [% terms.bugs %].</dd>
+
+ [% class = user.in_group('editkeywords') ? "" : "forbidden" %]
+ <dt id="keywords" class="[% class %]"><a href="editkeywords.cgi">Keywords</a></dt>
+ <dd class="[% class %]">Set keywords to be used with [% terms.bugs %]. Keywords
+ are an easy way to "tag" [% terms.bugs %] to let you find them more easily later.</dd>
+
+ [% class = user.in_group('bz_canusewhines') ? "" : "forbidden" %]
+ <dt id="whining" class="[% class %]"><a href="editwhines.cgi">Whining</a></dt>
+ <dd class="[% class %]">Set queries which will be run at some specified date
+ and time, and get the result of these queries directly per email. This is a
+ good way to create reminders and to keep track of the activity in your installation.</dd>
+
+ [% Hook.process('end_links_right') %]
+ </dl>
+ </td>
+ </tr>
+</table>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/classifications/add.html.tmpl b/template/en/default/admin/classifications/add.html.tmpl
new file mode 100644
index 000000000..1a6941f67
--- /dev/null
+++ b/template/en/default/admin/classifications/add.html.tmpl
@@ -0,0 +1,39 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Albert Ting <alt@sonic.net>
+ #%]
+
+[% PROCESS global/header.html.tmpl
+ title = "Add new classification"
+%]
+
+<form method=post action="editclassifications.cgi">
+ <table border=0 cellpadding=4 cellspacing=0>
+
+ [% PROCESS "admin/classifications/edit-common.html.tmpl" %]
+
+ </table>
+ <hr>
+ <input type=submit value="Add">
+ <input type=hidden name="action" value="new">
+ <input type="hidden" name="token" value="[% token FILTER html %]">
+</FORM>
+
+[% PROCESS admin/classifications/footer.html.tmpl %]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/classifications/del.html.tmpl b/template/en/default/admin/classifications/del.html.tmpl
new file mode 100644
index 000000000..5a3800f7a
--- /dev/null
+++ b/template/en/default/admin/classifications/del.html.tmpl
@@ -0,0 +1,63 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Albert Ting <alt@sonic.net>
+ #%]
+
+[% PROCESS global/header.html.tmpl
+ title = "Delete classification"
+%]
+
+<table border=1 cellpadding=4 cellspacing=0>
+<tr bgcolor="#6666ff">
+ <th valign="top" align="left">Part</th>
+ <th valign="top" align="left">Value</th>
+
+</tr><tr>
+ <td valign="top">Classification:</td>
+ <td valign="top">[% classification.name FILTER html %]</td>
+
+</tr><tr>
+ <td valign="top">Description:</td>
+ <td valign="top">
+ [% IF classification.description %]
+ [% classification.description FILTER html_light %]
+ [% ELSE %]
+ <font color="red">description missing</font>
+ [% END %]
+ </td>
+
+</tr><tr>
+ <td valign="top">Sortkey:</td>
+ <td valign="top">[% classification.sortkey FILTER html %]</td>
+
+</tr>
+</table>
+
+<h2>Confirmation</h2>
+
+<p>Do you really want to delete this classification?</p>
+<form method=post action="editclassifications.cgi">
+ <input type=submit value="Yes, delete">
+ <input type=hidden name="action" value="delete">
+ <input type=hidden name="classification" value="[% classification.name FILTER html %]">
+ <input type="hidden" name="token" value="[% token FILTER html %]">
+</form>
+
+[% PROCESS admin/classifications/footer.html.tmpl %]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/classifications/edit-common.html.tmpl b/template/en/default/admin/classifications/edit-common.html.tmpl
new file mode 100644
index 000000000..e0db0089f
--- /dev/null
+++ b/template/en/default/admin/classifications/edit-common.html.tmpl
@@ -0,0 +1,47 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Tiago Rodrigues de Mello <timello@linux.vnet.ibm.com>
+ #%]
+
+[%# INTERFACE:
+ # classification: Bugzilla::Classifiation object.
+ #%]
+
+<tr>
+ <th align="right">Classification:</th>
+ <td><input size=64 maxlength=64 name="classification"
+ value="[% classification.name FILTER html %]"></td>
+</tr>
+<tr>
+ <th align="right">Description:</th>
+ <td>
+ [% INCLUDE global/textarea.html.tmpl
+ name = 'description'
+ minrows = 4
+ cols = 64
+ defaultcontent = classification.description
+ %]
+ </td>
+</tr>
+<tr>
+ <th align="right"><label for="sortkey">Sortkey:</label></th>
+ <td><input id="sortkey" size="20" maxlength="20" name="sortkey"
+ value="[%- classification.sortkey FILTER html %]"></td>
+</tr>
+
+[% Hook.process('rows') %]
diff --git a/template/en/default/admin/classifications/edit.html.tmpl b/template/en/default/admin/classifications/edit.html.tmpl
new file mode 100644
index 000000000..2ef1725f3
--- /dev/null
+++ b/template/en/default/admin/classifications/edit.html.tmpl
@@ -0,0 +1,67 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Albert Ting <alt@sonic.net>
+ #%]
+
+[% PROCESS global/header.html.tmpl
+ title = "Edit classification"
+%]
+
+<form method=post action="editclassifications.cgi">
+ <table border=0 cellpadding=4 cellspacing=0>
+
+ [% PROCESS "admin/classifications/edit-common.html.tmpl" %]
+
+ <tr valign=top>
+ <th align="right">
+ <a href="editproducts.cgi?classification=[% classification.name FILTER url_quote %]">
+ Edit Products</a>:
+ </th>
+ <td>
+ [% IF classification.products.size > 0 %]
+ <table>
+ [% FOREACH product = classification.products %]
+ <tr>
+ <th align=right valign=top>[% product.name FILTER html %]</th>
+ <td valign=top>
+ [% IF product.description %]
+ [% product.description FILTER html_light %]
+ [% ELSE %]
+ <font color="red">description missing</font>
+ [% END %]
+ </td>
+ </tr>
+ [% END %]
+ </table>
+ [% ELSE %]
+ <font color="red">none</font>
+ [% END %]
+ </td>
+ </tr>
+ </table>
+
+ <input type=hidden name="classificationold"
+ value="[% classification.name FILTER html %]">
+ <input type=hidden name="action" value="update">
+ <input type="hidden" name="token" value="[% token FILTER html %]">
+ <input type=submit value="Update">
+</form>
+
+[% PROCESS admin/classifications/footer.html.tmpl %]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/classifications/footer.html.tmpl b/template/en/default/admin/classifications/footer.html.tmpl
new file mode 100644
index 000000000..db983aa74
--- /dev/null
+++ b/template/en/default/admin/classifications/footer.html.tmpl
@@ -0,0 +1,24 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Nitish Bezzala <nbezzala@yahoo.com>
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+<p>Back to the <a href="./">main [% terms.bugs %] page</a>
+or <a href="editclassifications.cgi"> edit</a> more classifications.</p>
diff --git a/template/en/default/admin/classifications/reclassify.html.tmpl b/template/en/default/admin/classifications/reclassify.html.tmpl
new file mode 100644
index 000000000..146a1acc6
--- /dev/null
+++ b/template/en/default/admin/classifications/reclassify.html.tmpl
@@ -0,0 +1,90 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Albert Ting <alt@sonic.net>
+ #%]
+
+[% PROCESS global/header.html.tmpl
+ title = "Reclassify products"
+%]
+
+<form method=post action="editclassifications.cgi">
+ <table border=0 cellpadding=4 cellspacing=0>
+ <tr>
+ <td valign="top">Classification:</td>
+ <td valign="top" colspan=3>[% classification.name FILTER html %]</td>
+
+ </tr><tr>
+ <td valign="top">Description:</td>
+ <td valign="top" colspan=3>
+ [% IF classification.description %]
+ [% classification.description FILTER html_light %]
+ [% ELSE %]
+ <font color="red">description missing</font>
+ [% END %]
+ </td>
+
+ </tr><tr>
+ <td valign="top">Sortkey:</td>
+ <td valign="top" colspan=3>[% classification.sortkey FILTER html %]</td>
+
+ </tr><tr>
+ <td valign="top">Products:</td>
+ <td valign="top">Other Classifications</td>
+ <td></td>
+ <td valign="top">This Classification</td>
+
+ </tr><tr>
+ <td></td>
+ <td valign="top">
+ <select name="prodlist" id="prodlist" multiple="multiple" size="20">
+ [% FOREACH class = classifications %]
+ [% IF class.id != classification.id %]
+ [% FOREACH product = class.products %]
+ <option value="[% product.name FILTER html %]">
+ [[% class.name FILTER html %]]&nbsp;[% product.name FILTER html %]
+ </option>
+ [% END %]
+ [% END %]
+ [% END %]
+ </select></td>
+
+ <td align="center">
+ <input type=submit value=" Add &gt;&gt; " name="add_products"><br><br>
+ <input type=submit value="&lt;&lt; Remove" name="remove_products">
+ </td>
+
+ <td valign="middle" rowspan=2>
+ <select name="myprodlist" id="myprodlist" multiple="multiple" size="20">
+ [% FOREACH product = classification.products %]
+ <option value="[% product.name FILTER html %]">
+ [% product.name FILTER html %]
+ </option>
+ [% END %]
+ </select></td>
+ </tr>
+ </table>
+
+ <input type=hidden name="action" value="reclassify">
+ <input type=hidden name="classification" value="[% classification.name FILTER html %]">
+ <input type="hidden" name="token" value="[% token FILTER html %]">
+</form>
+
+[% PROCESS admin/classifications/footer.html.tmpl %]
+
+[% PROCESS global/footer.html.tmpl %]
+
diff --git a/template/en/default/admin/classifications/select.html.tmpl b/template/en/default/admin/classifications/select.html.tmpl
new file mode 100644
index 000000000..d6b352d02
--- /dev/null
+++ b/template/en/default/admin/classifications/select.html.tmpl
@@ -0,0 +1,66 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Albert Ting <alt@sonic.net>
+ #%]
+
+[% PROCESS global/header.html.tmpl
+ title = "Select classification"
+%]
+
+<table border=1 cellpadding=4 cellspacing=0>
+ <tr bgcolor="#6666ff">
+ <th align="left">Edit Classification ...</th>
+ <th align="left">Description</th>
+ <th align="left">Sortkey</th>
+ <th align="left">Products</th>
+ <th align="left">Action</th>
+ </tr>
+
+ [% FOREACH cl = classifications %]
+ <tr>
+ <td valign="top"><a href="editclassifications.cgi?action=edit&amp;classification=[% cl.name FILTER url_quote %]"><b>[% cl.name FILTER html %]</b></a></td>
+ <td valign="top">
+ [% IF cl.description %]
+ [% cl.description FILTER html_light %]
+ [% ELSE %]
+ <font color="red">none</font>
+ [% END %]
+ </td>
+ <td valign="top">[% cl.sortkey FILTER html %]</td>
+ [% IF (cl.id == 1) %]
+ <td valign="top">[% cl.product_count FILTER html %]</td>
+ [% ELSE %]
+ <td valign="top"><a href="editclassifications.cgi?action=reclassify&amp;classification=[% cl.name FILTER url_quote %]">reclassify ([% cl.product_count FILTER html %])</a></td>
+ [% END %]
+
+ [%# don't allow user to delete the default id. %]
+ [% IF (cl.id == 1) %]
+ <td valign="top">&nbsp;</td>
+ [% ELSE %]
+ <td valign="top"><a href="editclassifications.cgi?action=del&amp;classification=[% cl.name FILTER url_quote %]">delete</a></td>
+ [% END %]
+ </tr>
+ [% END %]
+
+ <tr>
+ <td valign="top" colspan=4>Add a new classification</td>
+ <td valign="top" align="center"><a href="editclassifications.cgi?action=add">Add</a></td>
+ </tr>
+</table>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/components/confirm-delete.html.tmpl b/template/en/default/admin/components/confirm-delete.html.tmpl
new file mode 100644
index 000000000..d0a1385f1
--- /dev/null
+++ b/template/en/default/admin/components/confirm-delete.html.tmpl
@@ -0,0 +1,160 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gavin Shelley <bugzilla@chimpychompy.org>
+ #%]
+
+[%# INTERFACE:
+ # comp: object; Bugzilla::Component object representing the component the
+ # user wants to delete.
+ # product: object; Bugzilla::Product object representing the product to
+ # which the component belongs.
+ #%]
+
+[% title = BLOCK %]Delete component '[% comp.name FILTER html %]'
+from '[% product.name FILTER html %]' product
+ [% END %]
+
+[% PROCESS global/header.html.tmpl
+ title = title
+%]
+
+<table border="1" cellpadding="4" cellspacing="0">
+<tr bgcolor="#6666FF">
+ <th valign="top" align="left">Field</th>
+ <th valign="top" align="left">Value</th>
+</tr>
+<tr>
+ <td valign="top">Component:</td>
+ <td valign="top">[% comp.name FILTER html %]</td>
+</tr>
+<tr>
+ <td valign="top">Component Description:</td>
+ <td valign="top">[% comp.description FILTER html_light %]</td>
+</tr>
+<tr>
+ <td valign="top">Default assignee:</td>
+ <td valign="top">[% comp.default_assignee.login FILTER html %]</td>
+
+[% IF Param('useqacontact') %]
+</tr>
+<tr>
+ <td valign="top">Default QA contact:</td>
+ <td valign="top">[% comp.default_qa_contact.login FILTER html %]</td>
+[% END %]
+
+</tr>
+<tr>
+ <td valign="top">Component of Product:</td>
+ <td valign="top">[% product.name FILTER html %]</td>
+
+[% IF product.description %]
+</tr>
+<tr>
+ <td valign="top">Product Description:</td>
+ <td valign="top">[% product.description FILTER html_light %]</td>
+[% END %]
+
+[% IF Param('usetargetmilestone') %]
+</tr>
+<tr>
+ <td valign="top">Product Milestone URL:</td>
+ <td valign="top">
+ <a href="[% product.milestone_url FILTER html %]">
+ [% product.milestone_url FILTER html %]
+ </a>
+ </td>
+[% END %]
+
+</tr>
+<tr>
+ <TD VALIGN="top">Open for [% terms.bugs %]:</TD>
+ <TD VALIGN="top">[% IF product.is_active %]Yes[% ELSE %]No[% END %]</td>
+</tr>
+<tr>
+ <td valign="top">[% terms.Bugs %]:</td>
+ <td valign="top">
+[% IF comp.bug_count %]
+ <a title="List of [% terms.bugs %] for component '[% comp.name FILTER html %]'"
+ href="buglist.cgi?component=[% comp.name FILTER url_quote %]&amp;product=
+ [%- product.name FILTER url_quote %]">[% comp.bug_count %]</a>
+[% ELSE %]
+ None
+[% END %]
+ </td>
+</tr>
+</table>
+
+<h2>Confirmation</h2>
+
+[% IF comp.bug_count %]
+
+ [% IF !Param("allowbugdeletion") %]
+ <p>
+ Sorry, there
+
+ [% IF comp.bug_count > 1 %]
+ are [% comp.bug_count %] [%+ terms.bugs %]
+ [% ELSE %]
+ is [% comp.bug_count %] [%+ terms.bug %]
+ [% END %]
+
+ outstanding for this component. You must reassign
+
+ [% IF comp.bug_count > 1 %]
+ those [% terms.bugs %]
+ [% ELSE %]
+ that [% terms.bug %]
+ [% END %]
+
+ to another component before you can delete this one.
+ </p>
+ [% ELSE %]
+
+ <table border="0" cellpadding="20" width="70%" bgcolor="red"><tr><td>
+
+ There [% IF comp.bug_count > 1 %]
+ are [% comp.bug_count %] [%+ terms.bugs %]
+ [% ELSE %]
+ is 1 [% terms.bug %]
+ [% END %]
+ entered for this component! When you delete this
+ component, <b><blink>ALL</blink></b> stored [% terms.bugs %] and
+ their history will be deleted too.
+ </td></tr></table>
+
+ [% END %]
+
+[% END %]
+
+[% IF comp.bug_count == 0 || Param('allowbugdeletion') %]
+
+ <p>Do you really want to delete this component?</p>
+
+ <form method="post" action="editcomponents.cgi">
+ <input type="submit" id="delete" value="Yes, delete">
+ <input type="hidden" name="action" value="delete">
+ <input type="hidden" name="product" value="[% product.name FILTER html %]">
+ <input type="hidden" name="component" value="[% comp.name FILTER html %]">
+ <input type="hidden" name="token" value="[% token FILTER html %]">
+ </form>
+
+[% END %]
+
+[% PROCESS admin/components/footer.html.tmpl %]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/components/create.html.tmpl b/template/en/default/admin/components/create.html.tmpl
new file mode 100644
index 000000000..c3b691d83
--- /dev/null
+++ b/template/en/default/admin/components/create.html.tmpl
@@ -0,0 +1,49 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gavin Shelley <bugzilla@chimpychompy.org>
+ # Akamai Technologies <bugzilla-dev@akamai.com>
+ #%]
+
+[%# INTERFACE:
+ # product: object; Bugzilla::Product object representing the product to
+ # which the component belongs.
+ #%]
+
+[% title = BLOCK %]Add component to the [% product.name FILTER html %] product[% END %]
+[% PROCESS global/header.html.tmpl
+ yui = [ 'autocomplete' ]
+ javascript_urls = [ "js/field.js" ]
+ title = title
+%]
+
+<form method="post" action="editcomponents.cgi">
+ <table border="0" cellpadding="4" cellspacing="0">
+
+ [% PROCESS "admin/components/edit-common.html.tmpl" %]
+
+ </table>
+ <hr>
+ <input type="submit" id="create" value="Add">
+ <input type="hidden" name="action" value="new">
+ <input type="hidden" name='product' value="[% product.name FILTER html %]">
+ <input type="hidden" name="token" value="[% token FILTER html %]">
+</form>
+
+[% PROCESS admin/components/footer.html.tmpl %]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/components/edit-common.html.tmpl b/template/en/default/admin/components/edit-common.html.tmpl
new file mode 100644
index 000000000..069b56cfd
--- /dev/null
+++ b/template/en/default/admin/components/edit-common.html.tmpl
@@ -0,0 +1,86 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Tiago Rodrigues de Mello <timello@linux.vnet.ibm.com>
+ #%]
+
+[%# INTERFACE:
+ # comp: object; Bugzilla::Component object.
+ #%]
+
+<tr>
+ <td valign="top">Component:</td>
+ <td><input size="64" maxlength="64" name="component"
+ value="[%- comp.name FILTER html %]"></td>
+</tr>
+<tr>
+ <td valign="top">Component Description:</td>
+ <td>
+ [% INCLUDE global/textarea.html.tmpl
+ name = 'description'
+ minrows = 4
+ cols = 64
+ wrap = 'virtual'
+ defaultcontent = comp.description
+ %]
+ </td>
+</tr>
+<tr>
+ <td valign="top"><label for="initialowner">Default Assignee:</label></td>
+ <td>
+ [% INCLUDE global/userselect.html.tmpl
+ name => "initialowner"
+ id => "initialowner"
+ value => comp.default_assignee.login
+ size => 64
+ %]
+ </td>
+</tr>
+[% IF Param('useqacontact') %]
+ <tr>
+ <td valign="top"><label for="initialqacontact">Default QA contact:</label></td>
+ <td>
+ [% INCLUDE global/userselect.html.tmpl
+ name => "initialqacontact"
+ id => "initialqacontact"
+ value => comp.default_qa_contact.login
+ size => 64
+ emptyok => 1
+ %]
+ </td>
+ </tr>
+[% END %]
+<tr>
+ <td valign="top">
+ <label for="initialcc">Default CC List:</label>
+ </td>
+ <td>
+ [% INCLUDE global/userselect.html.tmpl
+ name => "initialcc"
+ id => "initialcc"
+ value => initial_cc_names
+ size => 64
+ multiple => 5
+ %]
+ <br>
+ [% IF !Param("usemenuforusers") %]
+ <em>Enter user names for the CC list as a comma-separated list.</em>
+ [% END %]
+ </td>
+</tr>
+
+[% Hook.process('rows') %]
diff --git a/template/en/default/admin/components/edit.html.tmpl b/template/en/default/admin/components/edit.html.tmpl
new file mode 100644
index 000000000..be14be054
--- /dev/null
+++ b/template/en/default/admin/components/edit.html.tmpl
@@ -0,0 +1,76 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gavin Shelley <bugzilla@chimpychompy.org>
+ # Akamai Technologies <bugzilla-dev@akamai.com>
+ #%]
+
+[%# INTERFACE:
+ # comp: object; Bugzilla::Component object representing the component the
+ # user wants to edit.
+ # product: object; Bugzilla::Product object representing the product to
+ # which the component belongs.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% title = BLOCK %]
+ Edit component '[% comp.name FILTER html %]' of product '[% product.name FILTER html %]'
+[% END %]
+[% PROCESS global/header.html.tmpl
+ title = title
+ yui = [ 'autocomplete' ]
+ javascript_urls = [ "js/field.js" ]
+%]
+
+<form method="post" action="editcomponents.cgi">
+ <table border="0" cellpadding="4" cellspacing="0">
+
+ [% PROCESS "admin/components/edit-common.html.tmpl" %]
+
+ <tr>
+ <td>[% terms.Bugs %]:</td>
+ <td>
+[% IF comp.bug_count > 0 %]
+ <a title="[% terms.Bugs %] in component '[% comp.name FILTER html %]'"
+ href="buglist.cgi?component=
+ [%- comp.name FILTER url_quote %]&amp;product=
+ [%- product.name FILTER url_quote %]">[% comp.bug_count %]</a>
+[% ELSE %]
+ None
+[% END %]
+ </td>
+ </tr>
+
+ </table>
+
+ <input type="hidden" name="action" value="update">
+ <input type="hidden" name="componentold" value="[% comp.name FILTER html %]">
+ <input type="hidden" name="product" value="[% product.name FILTER html %]">
+ <input type="hidden" name="token" value="[% token FILTER html %]">
+ <input type="submit" value="Save Changes" id="update"> or <a
+ href="editcomponents.cgi?action=del&amp;product=
+ [%- product.name FILTER url_quote %]&amp;component=
+ [%- comp.name FILTER url_quote %]">Delete</a> this component.
+
+</form>
+
+[% PROCESS admin/components/footer.html.tmpl
+ no_edit_component_link = 1
+%]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/components/footer.html.tmpl b/template/en/default/admin/components/footer.html.tmpl
new file mode 100644
index 000000000..b2e105eb3
--- /dev/null
+++ b/template/en/default/admin/components/footer.html.tmpl
@@ -0,0 +1,54 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gavin Shelley <bugzilla@chimpychompy.org>
+ #%]
+
+[%# INTERFACE:
+ # comp: object; Bugzilla::Component object representing the component
+ # product: object; Bugzilla::Product object representing the product to
+ # which the component belongs.
+ #%]
+
+<hr>
+
+<p>
+Edit
+
+[% IF comp && !no_edit_component_link %]
+ component <a
+ title="Edit Component '[% comp.name FILTER html %]'"
+ href="editcomponents.cgi?action=edit&amp;product=
+ [%- product.name FILTER url_quote %]&amp;component=[% comp.name FILTER url_quote %]">
+ '[% comp.name FILTER html %]'</a>
+ or edit
+[% END %]
+
+[% IF !no_edit_other_components_link %]
+other components of product <a
+ title="Choose a component from product '[% product.name FILTER html %]' to edit"
+ href="editcomponents.cgi?product=
+ [%- product.name FILTER url_quote %]">'[% product.name FILTER html %]'</a>,
+ or edit
+[% END %]
+
+product <a
+ title="Edit Product '[% product.name FILTER html %]'"
+ href="editproducts.cgi?action=edit&amp;product=
+ [%- product.name FILTER url_quote %]">'[% product.name FILTER html %]'</a>.
+
+</p>
diff --git a/template/en/default/admin/components/list.html.tmpl b/template/en/default/admin/components/list.html.tmpl
new file mode 100644
index 000000000..632d47e6e
--- /dev/null
+++ b/template/en/default/admin/components/list.html.tmpl
@@ -0,0 +1,128 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gavin Shelley <bugzilla@chimpychompy.org>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+
+[%# INTERFACE:
+ # showbugcounts: if defined, then bug counts should be included in the table
+ # product: object; Bugzilla::Product object representing the product to
+ # which the component belongs.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% title = BLOCK %]Select component of product
+ '[% product.name FILTER html %]'[% END %]
+[% PROCESS global/header.html.tmpl
+ title = title
+%]
+
+[% edit_contentlink = BLOCK %]editcomponents.cgi?action=edit&amp;product=
+ [%- product.name FILTER url_quote %]&amp;component=%%name%%[% END %]
+[% delete_contentlink = BLOCK %]editcomponents.cgi?action=del&amp;product=
+ [%- product.name FILTER url_quote %]&amp;component=%%name%%[% END %]
+[% bug_count_contentlink = BLOCK %]buglist.cgi?component=%%name%%&amp;product=
+ [%- product.name FILTER url_quote %][% END %]
+
+
+[% columns = [
+ {
+ name => "name"
+ heading => "Edit component..."
+ contentlink => edit_contentlink
+ },
+ {
+ name => "description"
+ heading => "Description"
+ allow_html_content => 1
+ },
+ {
+ name => "initialowner"
+ heading => "Default Assignee"
+ },
+ ]
+%]
+
+[% IF Param('useqacontact') %]
+
+ [% columns.push({
+ name => 'initialqacontact'
+ heading => 'QA Contact'
+ }) %]
+
+[% END %]
+
+[% IF showbugcounts %]
+
+ [% columns.push({
+ name => 'bug_count'
+ heading => "$terms.Bugs"
+ align => "right"
+ contentlink => bug_count_contentlink
+ }) %]
+
+[% END %]
+
+[% columns.push({
+ heading => "Action"
+ content => "Delete"
+ contentlink => delete_contentlink
+ }) %]
+
+[%# Overrides the initialowner and the initialqacontact with right values %]
+[% overrides.initialowner = {} %]
+[% overrides.initialqacontact = {} %]
+
+[%# "component" is a reserved word in Template Toolkit. %]
+[% FOREACH my_component = product.components %]
+ [% overrides.initialowner.name.${my_component.name} = {
+ override_content => 1
+ content => my_component.default_assignee.login
+ }
+ %]
+ [% overrides.initialqacontact.name.${my_component.name} = {
+ override_content => 1
+ content => my_component.default_qa_contact.login
+ }
+ %]
+[% END %]
+
+[% Hook.process('before_table') %]
+
+[% PROCESS admin/table.html.tmpl
+ columns = columns
+ data = product.components
+ overrides = overrides
+%]
+
+<p><a href="editcomponents.cgi?action=add&amp;product=[% product.name FILTER url_quote %]">Add</a>
+ a new component to product '[% product.name FILTER html %]'</p>
+
+[% IF ! showbugcounts %]
+
+ <p><a href="editcomponents.cgi?product=[% product.name FILTER url_quote %]&amp;showbugcounts=1">
+ Redisplay table with [% terms.bug %] counts (slower)</a></p>
+
+[% END %]
+
+[% PROCESS admin/components/footer.html.tmpl
+ no_edit_other_components_link = 1
+ %]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/components/select-product.html.tmpl b/template/en/default/admin/components/select-product.html.tmpl
new file mode 100644
index 000000000..0910f9802
--- /dev/null
+++ b/template/en/default/admin/components/select-product.html.tmpl
@@ -0,0 +1,70 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gavin Shelley <bugzilla@chimpychompy.org>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ #
+ #%]
+
+[%# INTERFACE:
+ # products: array of product objects
+ # showbugcounts: if defined, then bug counts should be included in the table
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% PROCESS global/header.html.tmpl
+ title = "Edit components for which product?"
+%]
+
+[% columns = [
+ {
+ name => "name"
+ heading => "Edit components of..."
+ contentlink => "editcomponents.cgi?product=%%name%%"
+ },
+ {
+ name => "description"
+ heading => "Description"
+ allow_html_content => 1
+ }
+ ]
+%]
+
+[% IF showbugcounts %]
+
+ [% columns.push({
+ name => 'bug_count'
+ heading => "$terms.Bugs"
+ align => "right"
+ contentlink => "buglist.cgi?product=%%name%%"
+ })
+ %]
+
+[% END %]
+
+[% PROCESS admin/table.html.tmpl
+ columns = columns
+ data = products
+%]
+
+[% IF !showbugcounts %]
+ <p><a href="editcomponents.cgi?showbugcounts=1">
+ Redisplay table with [% terms.bug %] counts (slower)</a></p>
+[% END %]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/confirm-action.html.tmpl b/template/en/default/admin/confirm-action.html.tmpl
new file mode 100644
index 000000000..521d2d157
--- /dev/null
+++ b/template/en/default/admin/confirm-action.html.tmpl
@@ -0,0 +1,98 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Frédéric Buclin.
+ #
+ # Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+
+[%# INTERFACE:
+ # abuser: identity of the user who created the (invalid?) token.
+ # token_action: the action the token was supposed to serve.
+ # expected_action: the action the user was going to do.
+ # script_name: the script generating this warning.
+ # alternate_script: the suggested script to redirect the user to
+ # if he declines submission.
+ #%]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+[% PROCESS global/header.html.tmpl title = "Suspicious Action"
+ style_urls = ['skins/standard/global.css'] %]
+
+[% IF abuser %]
+ <div class="throw_error">
+ <p>When you view an administrative form in [% terms.Bugzilla %], a token string
+ is randomly generated and stored both in the database and in the form you loaded,
+ to make sure that the requested changes are being made as a result of submitting
+ a form generated by [% terms.Bugzilla %]. Unfortunately, the token used right now
+ is incorrect, meaning that it looks like you didn't come from the right page.
+ The following token has been used :</p>
+
+ <table border="0" cellpadding="5" cellspacing="0">
+ [% IF token_action != expected_action %]
+ <tr>
+ <th>Action&nbsp;stored:</th>
+ <td>[% token_action FILTER html %]</td>
+ </tr>
+ <tr>
+ <th>&nbsp;</th>
+ <td>
+ This action doesn't match the one expected ([% expected_action FILTER html %]).
+ </td>
+ </tr>
+ [% END %]
+
+ [% IF abuser != user.identity %]
+ <tr>
+ <th>Generated&nbsp;by:</th>
+ <td>[% abuser FILTER html %]</td>
+ </tr>
+ <tr>
+ <th>&nbsp;</th>
+ <td>
+ This token has not been generated by you. It is possible that someone
+ tried to trick you!
+ </td>
+ </tr>
+ [% END %]
+ </table>
+
+ <p>Please report this problem to [%+ Param("maintainer") FILTER html %].</p>
+ </div>
+[% ELSE %]
+ <div class="throw_error">
+ It looks like you didn't come from the right page (you have no valid token for
+ the <em>[% expected_action FILTER html %]</em> action while processing the
+ '[% script_name FILTER html%]' script). The reason could be one of:<br>
+ <ul>
+ <li>You clicked the "Back" button of your web browser after having successfully
+ submitted changes, which is generally not a good idea (but harmless).</li>
+ <li>You entered the URL in the address bar of your web browser directly,
+ which should be safe.</li>
+ <li>You clicked on a URL which redirected you here <b>without your consent</b>,
+ in which case this action is much more critical.</li>
+ </ul>
+ Are you sure you want to commit these changes anyway? This may result in
+ unexpected and undesired results.
+ </div>
+
+ <form name="check" id="check" method="post" action="[% script_name FILTER html %]">
+ [% PROCESS "global/hidden-fields.html.tmpl"
+ exclude="^(Bugzilla_login|Bugzilla_password)$" %]
+ <input type="submit" id="confirm" value="Confirm Changes">
+ </form>
+ <p>Or throw away these changes and go back to <a href="[% alternate_script FILTER html %]">
+ [%- alternate_script FILTER html %]</a>.</p>
+[% END %]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/custom_fields/cf-js.js.tmpl b/template/en/default/admin/custom_fields/cf-js.js.tmpl
new file mode 100644
index 000000000..528b88b2d
--- /dev/null
+++ b/template/en/default/admin/custom_fields/cf-js.js.tmpl
@@ -0,0 +1,77 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is NASA.
+ # Portions created by NASA are Copyright (C) 2008
+ # San Jose State University Foundation. All Rights Reserved.
+ #
+ # Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+// Disable a checkbox based on the state of another one.
+function toggleCheckbox(this_checkbox, other_checkbox_id) {
+ var other_checkbox = document.getElementById(other_checkbox_id);
+ other_checkbox.disabled = !this_checkbox.checked;
+}
+
+var select_values = new Array();
+[% USE Bugzilla %]
+[% FOREACH sel_field = Bugzilla.get_fields({ is_select => 1 }) %]
+ select_values[[% sel_field.id FILTER js %]] = [
+ [% FOREACH legal_value = sel_field.legal_values %]
+ [%# Prefix components with the name of their product so that admins
+ know which component we're talking about. #%]
+ [% IF sel_field.name == 'component' %]
+ [% SET value_name = display_value('product', legal_value.product.name) _ ': '
+ _ display_value(sel_field.name, legal_value.name) %]
+ [% ELSE %]
+ [% SET value_name = display_value(sel_field.name, legal_value.name) %]
+ [% END %]
+ [[% legal_value.id FILTER js %], '[% value_name FILTER js %]'][% ',' UNLESS loop.last %]
+ [% END %]
+ ];
+[% END %]
+
+function onChangeType(type_field) {
+ var value_field = document.getElementById('value_field_id');
+ if (type_field.value == [% constants.FIELD_TYPE_SINGLE_SELECT %]
+ || type_field.value == [% constants.FIELD_TYPE_MULTI_SELECT %])
+ {
+ value_field.disabled = false;
+ }
+ else {
+ value_field.disabled = true;
+ }
+
+ var reverse_desc = document.getElementById('reverse_desc');
+ if (type_field.value == [% constants.FIELD_TYPE_BUG_ID %])
+ {
+ reverse_desc.disabled = false;
+ }
+ else {
+ reverse_desc.disabled = true;
+ reverse_desc.value = '';
+ }
+}
+
+function onChangeVisibilityField() {
+ var vis_field = document.getElementById('visibility_field_id');
+ var vis_value = document.getElementById('visibility_value_id');
+
+ if (vis_field.value) {
+ var values = select_values[vis_field.value];
+ bz_populateSelectFromArray(vis_value, values);
+ }
+ else {
+ bz_clearOptions(vis_value);
+ }
+}
diff --git a/template/en/default/admin/custom_fields/confirm-delete.html.tmpl b/template/en/default/admin/custom_fields/confirm-delete.html.tmpl
new file mode 100644
index 000000000..c93683614
--- /dev/null
+++ b/template/en/default/admin/custom_fields/confirm-delete.html.tmpl
@@ -0,0 +1,66 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Alexander Eiser <alexe@ed.ca>
+ #%]
+
+[%# INTERFACE:
+ # field: object; the field object that you are trying to delete.
+ # token: string; the delete_field token required to complete deletion.
+ #%]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+[% title = BLOCK %]
+ Delete the Custom Field '[% field.name FILTER html %]' ([% field.description FILTER html %])
+[% END %]
+
+[% PROCESS global/header.html.tmpl
+ title = title
+ doc_section = "custom-fields.html#delete-custom-fields"
+%]
+
+<table border="1" cellpadding="4" cellspacing="0">
+<tr bgcolor="#6666FF">
+ <th valign="top" align="left">Field</th>
+ <th valign="top" align="left">Value</th>
+</tr>
+<tr>
+ <td valign="top">Custom Field:</td>
+ <td valign="top">[% field.name FILTER html %]</td>
+</tr>
+<tr>
+ <td valign="top">Description:</td>
+ <td valign="top">[% field.description FILTER html %]</td>
+</tr>
+<tr>
+ <td valign="top">Type:</td>
+ <td valign="top">[% field_types.${field.type} FILTER html %]</td>
+</tr>
+</table>
+
+<h2>Confirmation</h2>
+
+<p>
+ Are you sure you want to remove this field from the database?<br>
+ <em>This action will only be successful if the field is obsolete,
+ and has never been used in [% terms.abug FILTER html %].</em>
+</p>
+
+<a href="editfields.cgi?action=delete&amp;name=[% field.name FILTER html %]&amp;token=[% token FILTER html %]">
+ Delete field '[% field.description FILTER html %]'</a>
+
+<p>
+ <a href="editfields.cgi">Back to the list of existing custom fields</a>
+</p>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/custom_fields/create.html.tmpl b/template/en/default/admin/custom_fields/create.html.tmpl
new file mode 100644
index 000000000..fcdf73bc7
--- /dev/null
+++ b/template/en/default/admin/custom_fields/create.html.tmpl
@@ -0,0 +1,173 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+
+[%# INTERFACE:
+ # none
+ #%]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+[% javascript = BLOCK %]
+ [% INCLUDE "admin/custom_fields/cf-js.js.tmpl" %]
+[% END %]
+
+[% PROCESS global/header.html.tmpl
+ title = "Add a new Custom Field"
+ onload = "document.getElementById('new_bugmail').disabled = true;"
+ javascript_urls = [ 'js/util.js' ]
+ doc_section = "custom-fields.html#add-custom-fields"
+ style_urls = ['skins/standard/admin.css']
+%]
+
+[%# set initial editability of fields such as Reverse Relationship Description %]
+<script type="text/javascript">
+YAHOO.util.Event.onDOMReady(function() {onChangeType(document.getElementById('type'))});
+</script>
+
+<p>
+ Adding custom fields can make the interface of [% terms.Bugzilla %] very
+ complicated. Many admins who are new to [% terms.Bugzilla %] start off
+ adding many custom fields, and then their users complain that the interface
+ is "too complex". Please think carefully before adding any custom fields.
+ It may be the case that [% terms.Bugzilla %] already does what you need,
+ and you just haven't enabled the correct feature yet.
+</p>
+
+<ul>
+ <li>Custom field names must begin with "cf_" to distinguish them from
+ standard fields. If you omit "cf_" from the beginning of the name, it
+ will be added for you.</li>
+ <li>Descriptions are a very short string describing the field and will be
+ used as the label for this field in the user interface.</li>
+</ul>
+
+<form id="add_field" action="editfields.cgi" method="GET">
+ <table border="0" cellspacing="0" cellpadding="5" id="edit_custom_field">
+ <tr>
+ <th class="narrow_label"><label for="name">Name:</label></th>
+ <td>
+ <input type="text" id="name" name="name" value="cf_" size="40" maxlength="64">
+ </td>
+
+ <th>
+ <label for="enter_bug">Can be set on [% terms.bug %] creation:</label>
+ </th>
+ <td>
+ <input type="checkbox" id="enter_bug" name="enter_bug" value="1"
+ onchange="toggleCheckbox(this, 'new_bugmail');">
+ </td>
+ </tr>
+ <tr>
+ <th class="narrow_label"><label for="desc">Description:</label></th>
+ <td><input type="text" id="desc" name="desc" value="" size="40"></td>
+
+ <th>
+ <label for="new_bugmail">Displayed in [% terms.bug %]mail for new [% terms.bugs %]:</label>
+ </th>
+ <td><input type="checkbox" id="new_bugmail" name="new_bugmail" value="1"></td>
+ </tr>
+ <tr>
+ <th class="narrow_label"><label for="type">Type:</label></th>
+ <td>
+ <select id="type" name="type" onchange="onChangeType(this)">
+ [% FOREACH type = field_types.keys %]
+ [% NEXT IF type == constants.FIELD_TYPE_UNKNOWN %]
+ <option value="[% type FILTER html %]">[% field_types.$type FILTER html %]</option>
+ [% END %]
+ </select>
+ </td>
+
+ <th><label for="obsolete">Is obsolete:</label></th>
+ <td><input type="checkbox" id="obsolete" name="obsolete" value="1"></td>
+ </tr>
+ <tr>
+ <th class="narrow_label"><label for="sortkey">Sortkey:</label></th>
+ <td>
+ <input type="text" id="sortkey" name="sortkey" size="6" maxlength="6">
+ </td>
+
+ <th align="right"><label for="is_mandatory">Is mandatory:</label></th>
+ <td><input type="checkbox" id="is_mandatory" name="is_mandatory" value="1"></td>
+ </tr>
+
+ <tr>
+ <th class="narrow_label">
+ <label for="reverse_desc">Reverse Relationship Description:</label>
+ </th>
+ <td>
+ <input type="text" id="reverse_desc" name="reverse_desc" value="" size="40" disabled="disabled">
+ <br/>
+ Use this label for the list of [% terms.bugs %] that link to
+ [%+ terms.abug %] with this
+ [%+ field_types.${constants.FIELD_TYPE_BUG_ID} FILTER html %]
+ field. For example, if the description is "Is a duplicate of",
+ the reverse description would be "Duplicates of this [% terms.bug %]".
+ Leave blank to disable the list for this field.
+ </td>
+ <th>
+ <label for="visibility_field_id">Field only appears when:</label>
+ </th>
+ <td>
+ <select name="visibility_field_id" id="visibility_field_id"
+ onchange="onChangeVisibilityField()">
+ <option></option>
+ [% FOREACH sel_field = Bugzilla.get_fields({ is_select => 1 }) %]
+ <option value="[% sel_field.id FILTER html %]">
+ [% sel_field.description FILTER html %]
+ ([% sel_field.name FILTER html %])
+ </option>
+ [% END %]
+ </select>
+ <label for="visibility_value_id"><strong>is set to:</strong></label>
+ <select name="visibility_value_id" id="visibility_value_id">
+ <option value=""></option>
+ </select>
+ </td>
+ </tr>
+
+ <tr>
+ <td colspan="2">&nbsp;</td>
+ <th>
+ <label for="value_field_id">
+ Field that controls the values<br>
+ that appear in this field:
+ </label>
+ </th>
+
+ <td>
+ <select disabled="disabled" name="value_field_id" id="value_field_id">
+ <option></option>
+ [% FOREACH sel_field = Bugzilla.get_fields({ is_select => 1 }) %]
+ <option value="[% sel_field.id FILTER html %]">
+ [% sel_field.description FILTER html %]
+ ([% sel_field.name FILTER html %])
+ </option>
+ [% END %]
+ </select>
+ </td>
+ </tr>
+ </table>
+ <p>
+ <input type="hidden" name="action" value="new">
+ <input type="hidden" name="token" value="[% token FILTER html %]">
+ <input type="submit" id="create" value="Create">
+ </p>
+</form>
+
+<p>
+ <a href="editfields.cgi">Back to the list of existing custom fields</a>
+</p>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/custom_fields/edit.html.tmpl b/template/en/default/admin/custom_fields/edit.html.tmpl
new file mode 100644
index 000000000..755c3642c
--- /dev/null
+++ b/template/en/default/admin/custom_fields/edit.html.tmpl
@@ -0,0 +1,188 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+
+[%# INTERFACE:
+ # field: Bugzila::Field; the current field being edited
+ #%]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+[% title = BLOCK %]
+ Edit the Custom Field '[% field.name FILTER html %]' ([% field.description FILTER html %])
+[% END %]
+
+[% javascript = BLOCK %]
+ [% INCLUDE "admin/custom_fields/cf-js.js.tmpl" %]
+[% END %]
+
+[% PROCESS global/header.html.tmpl
+ title = title
+ onload = "toggleCheckbox(document.getElementById('enter_bug'), 'new_bugmail');"
+ javascript_urls = [ 'js/util.js' ]
+ doc_section = "custom-fields.html#edit-custom-fields"
+ style_urls = ['skins/standard/admin.css']
+%]
+
+<p>
+ Descriptions are a very short string describing the field and will be used as
+ the label for this field in the user interface.
+</p>
+
+<form id="edit_field" action="editfields.cgi" method="GET">
+ <table border="0" cellspacing="0" cellpadding="5" id="edit_custom_field">
+ <tr>
+ <th class="narrow_label">Name:</th>
+ <td>[% field.name FILTER html %]</td>
+
+ <th>
+ <label for="enter_bug">Can be set on [% terms.bug %] creation:</label>
+ </th>
+ <td><input type="checkbox" id="enter_bug" name="enter_bug" value="1"
+ [%- " checked" IF field.enter_bug %]
+ onchange="toggleCheckbox(this, 'new_bugmail');"></td>
+ </tr>
+ <tr>
+ <th class="narrow_label"><label for="desc">Description:</label></th>
+ <td><input type="text" id="desc" name="desc" size="40"
+ value="[% field.description FILTER html %]"></td>
+
+ <th>
+ <label for="new_bugmail">Displayed in [% terms.bug %]mail for new [% terms.bugs %]:</label>
+ </th>
+ <td><input type="checkbox" id="new_bugmail" name="new_bugmail" value="1"
+ [%- " checked" IF field.mailhead %]></td>
+ </tr>
+ <tr>
+ <th class="narrow_label">Type:</th>
+ <td>[% field_types.${field.type} FILTER html %]</td>
+
+ <th><label for="obsolete">Is obsolete:</label></th>
+ <td><input type="checkbox" id="obsolete" name="obsolete" value="1"
+ [%- " checked" IF field.obsolete %]></td>
+ </tr>
+ <tr>
+ <th class="narrow_label"><label for="sortkey">Sortkey:</label></th>
+ <td>
+ <input type="text" id="sortkey" name="sortkey" size="6" maxlength="6"
+ value="[% field.sortkey FILTER html %]">
+ </td>
+ <th align="right"><label for="is_mandatory">Is mandatory:</label></th>
+ <td><input type="checkbox" id="is_mandatory" name="is_mandatory" value="1"
+ [%- ' checked="checked"' IF field.is_mandatory %]></td>
+ </tr>
+ <tr>
+ [% IF field.type == constants.FIELD_TYPE_BUG_ID %]
+ <th class="narrow_label">
+ <label for="reverse_desc">Reverse Relationship Description:</label>
+ </th>
+ <td>
+ <input type="text" id="reverse_desc" name="reverse_desc" size="40"
+ value="[% field.reverse_desc FILTER html %]">
+ <br/>
+ Use this label for the list of [% terms.bugs %] that link to
+ [%+ terms.abug %] with this
+ [%+ field_types.${constants.FIELD_TYPE_BUG_ID} FILTER html %] field.
+ For example, if the description is "Is a duplicate of",
+ the reverse description would be "Duplicates of this [% terms.bug %]".
+ Leave blank to disable the list for this field.
+ </td>
+ [% ELSE %]
+ <td colspan="2">&nbsp;</td>
+ [% END %]
+ <th>
+ <label for="visibility_field_id">Field only appears when:</label>
+ </th>
+ <td>
+ <select name="visibility_field_id" id="visibility_field_id"
+ onchange="onChangeVisibilityField()">
+ <option></option>
+ [% FOREACH sel_field = Bugzilla.get_fields({ is_select => 1 }) %]
+ [% NEXT IF sel_field.id == field.id %]
+ <option value="[% sel_field.id FILTER html %]"
+ [% ' selected="selected"'
+ IF sel_field.id == field.visibility_field.id %]>
+ [% sel_field.description FILTER html %]
+ ([% sel_field.name FILTER html %])
+ </option>
+ [% END %]
+ </select>
+ <label for="visibility_value_id"><strong>is set to:</strong></label>
+ <select name="visibility_value_id" id="visibility_value_id">
+ [% FOREACH value = field.visibility_field.legal_values %]
+ <option value="[% value.id FILTER html %]"
+ [% ' selected="selected"'
+ IF field.visibility_value.id == value.id %]>
+ [% IF field.visibility_field.name == 'component' %]
+ [% display_value('product', value.product.name) FILTER html %]:
+ [% END %]
+ [%+ display_value(field.visibility_field.name, value.name) FILTER html %]
+ </option>
+ [% END %]
+ </select>
+ </td>
+ </tr>
+ [% IF field.is_select %]
+ <tr>
+ <th>&nbsp;</th>
+ <td>
+ <a href="editvalues.cgi?field=[% field.name FILTER url_quote %]">Edit
+ legal values for this field</a>.
+ </td>
+
+ <th>
+ <label for="value_field_id">
+ Field that controls the values<br>
+ that appear in this field:
+ </label>
+ </th>
+
+ <td>
+ <select name="value_field_id" id="value_field_id">
+ <option></option>
+ [% FOREACH sel_field = Bugzilla.get_fields({ is_select => 1 }) %]
+ [% NEXT IF sel_field.id == field.id %]
+ <option value="[% sel_field.id FILTER html %]"
+ [% ' selected="selected"'
+ IF sel_field.id == field.value_field.id %]>
+ [% sel_field.description FILTER html %]
+ ([% sel_field.name FILTER html %])
+ </option>
+ [% END %]
+ </select>
+ </td>
+ </tr>
+ [% END %]
+ </table>
+ <br>
+ <input type="hidden" name="action" value="update">
+ <input type="hidden" name="name" value="[% field.name FILTER html %]">
+ <input type="hidden" name="token" value="[% token FILTER html %]">
+ <input type="submit" id="edit" value="Submit">
+</form>
+
+[% IF field.obsolete %]
+<p>
+ <a href="editfields.cgi?action=del&amp;name=[% field.name FILTER html %]">Remove
+ this custom field from the database.</a><br>
+ This action will only be successful if the custom field has never been used
+ in [% terms.abug %].<br>
+</p>
+[% END %]
+
+<p>
+ <a href="editfields.cgi">Back to the list of existing custom fields</a>
+</p>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/custom_fields/list.html.tmpl b/template/en/default/admin/custom_fields/list.html.tmpl
new file mode 100644
index 000000000..385650a63
--- /dev/null
+++ b/template/en/default/admin/custom_fields/list.html.tmpl
@@ -0,0 +1,106 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+
+[%# INTERFACE:
+ # custom_fields: a list of Bugzilla::Field objects, representing custom fields.
+ #%]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+[% PROCESS global/header.html.tmpl
+ title = "Custom Fields"
+ doc_section = "custom-fields.html"
+%]
+
+[% delete_contentlink = BLOCK %]editfields.cgi?action=del&amp;name=%%name%%[% END %]
+
+[% columns = [
+ {
+ name => "name"
+ heading => "Edit custom field..."
+ contentlink => "editfields.cgi?action=edit&amp;name=%%name%%"
+ },
+ {
+ name => "description"
+ heading => "Description"
+ },
+ {
+ name => "sortkey"
+ heading => "Sortkey"
+ },
+ {
+ name => "type"
+ heading => "Type"
+ },
+ {
+ name => "enter_bug"
+ heading => "Editable on $terms.Bug Creation"
+ },
+ {
+ name => "mailhead"
+ heading => "In ${terms.Bug}mail on $terms.Bug Creation"
+ },
+ {
+ name => "obsolete"
+ heading => "Is Obsolete"
+ },
+ {
+ name => "is_mandatory"
+ heading => "Is Mandatory"
+ },
+ {
+ name => "action"
+ heading => "Action"
+ content => ""
+ }
+ ]
+%]
+
+[% USE Bugzilla %]
+[% custom_fields = Bugzilla.get_fields({ custom => 1 }) %]
+
+[%# We want to display the type name of fields, not their type ID. %]
+[% overrides.type = {} %]
+
+[% FOREACH field_type = field_types.keys %]
+ [% overrides.type.type.$field_type = {
+ override_content => 1
+ content => field_types.$field_type
+ }
+ %]
+[% END %]
+
+
+[% overrides.action.obsolete = {
+ "1" => {
+ override_content => 1
+ content => "Delete"
+ override_contentlink => 1
+ contentlink => delete_contentlink
+ }
+ }
+%]
+
+[% PROCESS admin/table.html.tmpl
+ columns = columns
+ overrides = overrides
+ data = custom_fields
+%]
+
+<p>
+ <a href="editfields.cgi?action=add">Add a new custom field</a>
+</p>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/fieldvalues/confirm-delete.html.tmpl b/template/en/default/admin/fieldvalues/confirm-delete.html.tmpl
new file mode 100644
index 000000000..2ea7c2fb0
--- /dev/null
+++ b/template/en/default/admin/fieldvalues/confirm-delete.html.tmpl
@@ -0,0 +1,161 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[%# INTERFACE:
+ # value: Bugzilla::Field::Choice; The field value being deleted.
+ # value_count: number; The number of values available for this field.
+ # field: object; the field the value is being deleted from.
+ #%]
+
+[% title = BLOCK %]
+ Delete Value '[% value.name FILTER html %]' from the
+ '[% field.description FILTER html %]' ([% field.name FILTER html %]) field
+[% END %]
+
+[% PROCESS global/header.html.tmpl
+ title = title
+ style_urls = ['skins/standard/admin.css']
+%]
+
+<table border="1" cellpadding="4" cellspacing="0">
+<tr bgcolor="#6666FF">
+ <th valign="top" align="left">Field</th>
+ <th valign="top" align="left">Value</th>
+</tr>
+<tr>
+ <td valign="top">Field Name:</td>
+ <td valign="top">[% field.description FILTER html %]</td>
+</tr>
+<tr>
+ <td valign="top">Field Value:</td>
+ <td valign="top">[% value.name FILTER html %]</td>
+</tr>
+<tr>
+ <td valign="top">[% terms.Bugs %]:</td>
+ <td valign="top">
+[% IF value.bug_count %]
+ <a title="List of [% terms.bugs %] where '
+ [%- field.description FILTER html %]' is '
+ [%- value.name FILTER html %]'"
+ href="buglist.cgi?[% field.name FILTER url_quote %]=
+ [%- value.name FILTER url_quote %]">
+ [%- value.bug_count FILTER html %]</a>
+[% ELSE %]
+ None
+[% END %]
+ </td>
+</tr>
+</table>
+
+<h2>Confirmation</h2>
+
+[% IF value.is_default || value.bug_count || (value_count == 1)
+ || value.controls_visibility_of_fields.size
+ || value.controlled_values_array.size
+%]
+
+ <p>Sorry, but the '[% value.name FILTER html %]' value cannot be deleted
+ from the '[% field.description FILTER html %]' field for the following
+ reason(s):</p>
+
+ <ul class="warningmessages">
+ [% IF value.is_default %]
+ <li>'[% value.name FILTER html %]' is the default value for
+ the '[% field.description FILTER html %]' field.
+ [% IF user.in_group('tweakparams') %]
+ You first have to <a href="editparams.cgi?section=bugfields">change
+ the default value</a> for this field before you can delete
+ this value.
+ [% END %]
+ </li>
+ [% END %]
+
+ [% IF value.bug_count %]
+ <li>
+ [% IF value.bug_count > 1 %]
+ There are [% value.bug_count FILTER html %] [%+ terms.bugs %]
+ with this field value.
+ [% ELSE %]
+ There is 1 [% terms.bug %] with this field value.
+ [% END %]
+ You must change the field value on
+ <a title="List of [% terms.bugs %] where '
+ [%- field.description FILTER html %]' is '
+ [%- value.name FILTER html %]'"
+ href="buglist.cgi?[% field.name FILTER url_quote %]=
+ [%- value.name FILTER url_quote %]">
+ [% IF value.bug_count > 1 %]
+ those [% terms.bugs %]
+ [% ELSE %]
+ that [% terms.bug %]
+ [% END %]
+ </a>
+ to another value before you can delete this value.
+ </li>
+ [% END %]
+
+ [% IF value_count == 1 %]
+ <li>'[% value.name FILTER html %]' is the last value for
+ '[%- field.description FILTER html %]', and so it can not be deleted.
+ </li>
+ [% END %]
+
+ [% IF value.controls_visibility_of_fields.size %]
+ <li>This value controls the visibility of the following fields:<br>
+ [% FOREACH field = value.controls_visibility_of_fields %]
+ <a href="editfields.cgi?action=edit&name=
+ [%- field.name FILTER url_quote %]">
+ [%- field.description FILTER html %]
+ ([% field.name FILTER html %])</a><br>
+ [% END %]
+ </li>
+ [% END %]
+
+ [% IF value.controlled_values_array.size %]
+ <li>This value controls the visibility of the following values in
+ other fields:<br>
+ [% FOREACH field_name = value.controlled_values.keys %]
+ [% FOREACH controlled = value.controlled_values.${field_name} %]
+ <a href="editvalues.cgi?action=edit&field=
+ [%- controlled.field.name FILTER url_quote %]&value=
+ [%- controlled.name FILTER url_quote %]">
+ [% controlled.field.description FILTER html %]
+ ([% controlled.field.name FILTER html %]):
+ [%+ controlled.name FILTER html %]</a><br>
+ [% END %]
+ [% END %]
+ </li>
+ [% END %]
+ </ul>
+
+[% ELSE %]
+
+ <p>Do you really want to delete this value?</p>
+
+ <form method="post" action="editvalues.cgi">
+ <input type="submit" value="Yes, delete" id="delete">
+ <input type="hidden" name="action" value="delete">
+ <input type="hidden" name="field" value="[% field.name FILTER html %]">
+ <input type="hidden" name="value" value="[% value.name FILTER html %]">
+ <input type="hidden" name="token" value="[% token FILTER html %]">
+ </form>
+
+[% END %]
+
+[% PROCESS admin/fieldvalues/footer.html.tmpl
+ no_edit_link = 1
+ +%]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/fieldvalues/create.html.tmpl b/template/en/default/admin/fieldvalues/create.html.tmpl
new file mode 100644
index 000000000..019831489
--- /dev/null
+++ b/template/en/default/admin/fieldvalues/create.html.tmpl
@@ -0,0 +1,100 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+
+[%# INTERFACE:
+ # field: object; the field the value is being created for
+ #%]
+
+[% title = BLOCK %]
+ Add Value for the '[% field.description FILTER html %]' ([% field.name FILTER html %]) field
+[% END %]
+[% PROCESS global/header.html.tmpl
+ title = title
+%]
+
+<p>
+ This page allows you to add a new value for the
+ '[% field.description FILTER html %]' field.
+</p>
+
+<form method="post" action="editvalues.cgi">
+ <table border="0" cellpadding="4" cellspacing="0">
+ <tr>
+ <th align="right"><label for="value">Value:</label></th>
+ <td>
+ <input id="value" name="value" size="30"
+ maxlength="[% constants.MAX_FIELD_VALUE_SIZE FILTER none %]">
+ </td>
+ </tr>
+ <tr>
+ <th align="right"><label for="sortkey">Sortkey:</label></th>
+ <td><input id="sortkey" name="sortkey" size="6" maxlength="6"></td>
+ </tr>
+ [% IF field.name == "bug_status" %]
+ <tr>
+ <th align="right"><label for="is_open">Status Type:</label></th>
+ <td>
+ <input type="radio" id="open_status" name="is_open" value="1"
+ checked="checked">
+ <label for="open_status">Open</label><br>
+ <input type="radio" id="closed_status" name="is_open" value="0">
+ <label for="closed_status">Closed (requires a Resolution)</label>
+ </td>
+ </tr>
+ <tr>
+ <th>&nbsp;</th>
+ <td>
+ Note: The open/close attribute can only be set now, when you create
+ the status. It cannot be edited later.
+ </td>
+ </tr>
+ [% END %]
+ [% IF field.value_field %]
+ <tr>
+ <th align="right">
+ <label for="visibility_value_id">Only appears when
+ [%+ field.value_field.description FILTER html %] is set to:
+ </label>
+ </th>
+ <td>
+ <select name="visibility_value_id" id="visibility_value_id">
+ <option></option>
+ [% FOREACH field_value = field.value_field.legal_values %]
+ [% NEXT IF field_value.name == '' %]
+ <option value="[% field_value.id FILTER none %]">
+ [% IF field.value_field.name == 'component' %]
+ [% field_value.product.name FILTER html %]:
+ [% END %]
+ [%- field_value.name FILTER html -%]
+ </option>
+ [% END %]
+ </select>
+ <small>(Leave unset to have this value always appear.)</small>
+ </td>
+ </tr>
+ [% END %]
+ </table>
+ <input type="submit" id="create" value="Add">
+ <input type="hidden" name="action" value="new">
+ <input type="hidden" name='field' value="[% field.name FILTER html %]">
+ <input type="hidden" name="token" value="[% token FILTER html %]">
+</form>
+
+[% PROCESS admin/fieldvalues/footer.html.tmpl
+ no_add_link = 1
+ %]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/fieldvalues/edit.html.tmpl b/template/en/default/admin/fieldvalues/edit.html.tmpl
new file mode 100644
index 000000000..9c42ce639
--- /dev/null
+++ b/template/en/default/admin/fieldvalues/edit.html.tmpl
@@ -0,0 +1,114 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[%# INTERFACE:
+ # value: Bugzilla::Field::Choice; The field value we are editing.
+ # field: Bugzilla::Field; The field this value belongs to.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% title = BLOCK %]
+ Edit Value '[% value.name FILTER html %]' for the
+ '[% field.description FILTER html %]' ([% field.name FILTER html %]) field
+[% END %]
+[% PROCESS global/header.html.tmpl
+ title = title
+%]
+
+<form method="post" action="editvalues.cgi">
+ <table border="0" cellpadding="4" cellspacing="0">
+
+ <tr>
+ <th valign="top" align="right">
+ <label for="value_new">Field Value:</label>
+ </th>
+ <td>
+ [% IF value.is_static %]
+ <input type="hidden" name="value_new" id="value_new"
+ value="[% value.name FILTER html %]">
+ [%- value.name FILTER html %]
+ [% ELSE %]
+ <input id="value_new" name="value_new" size="20"
+ maxlength="[% constants.MAX_FIELD_VALUE_SIZE FILTER none %]"
+ value="[% value.name FILTER html %]">
+ [% END %]
+ </td>
+ </tr>
+ <tr>
+ <th align="right"><label for="sortkey">Sortkey:</label></th>
+ <td><input id="sortkey" size="6" maxlength="6" name="sortkey"
+ value="[%- value.sortkey FILTER html %]"></td>
+ </tr>
+ [% IF field.name == "bug_status" %]
+ <tr>
+ <th align="right"><label for="is_open">Status Type:</label></th>
+ <td>[% IF value.is_open %]Open[% ELSE %]Closed[% END %]</td>
+ </tr>
+ [% END %]
+ [% IF field.value_field %]
+ <tr>
+ <th align="right">
+ <label for="visibility_value_id">Only appears when
+ [%+ field.value_field.description FILTER html %] is set to:
+ </label>
+ </th>
+ <td>
+ <select name="visibility_value_id" id="visibility_value_id">
+ <option></option>
+ [% FOREACH field_value = field.value_field.legal_values %]
+ [% NEXT IF field_value.name == '' %]
+ <option value="[% field_value.id FILTER none %]"
+ [% ' selected="selected"'
+ IF field_value.id == value.visibility_value.id %]>
+ [% IF field.value_field.name == 'component' %]
+ [% field_value.product.name FILTER html %]:
+ [% END %]
+ [% field_value.name FILTER html -%]
+ </option>
+ [% END %]
+ </select>
+ <small>(Leave unset to have this value always appear.)</small>
+ </td>
+ </tr>
+ [% END %]
+ <tr>
+ <th align="right"><label for="is_active">Enabled for [% terms.bugs %]:</label></th>
+ <td><input id="is_active" name="is_active" type="checkbox" value="1"
+ [%+ 'checked="checked"' IF value.is_active %]
+ [%+ 'disabled="disabled"' IF value.is_default OR value.is_static %]>
+ [% IF value.is_default %]
+ This value is selected as default in the parameters for this field. It cannot be disabled.
+ [% ELSIF value.is_static %]
+ This value is non-deletable and cannot be disabled.
+ [% END %]
+ [% IF !(value.is_default OR value.is_static) %]
+ <input id="defined_is_active" name="defined_is_active"
+ type="hidden" value="1">
+ [% END %]
+ </td>
+ </tr>
+ </table>
+ <input type="hidden" name="value" value="[% value.name FILTER html %]">
+ <input type="hidden" name="action" value="update">
+ <input type="hidden" name="field" value="[% field.name FILTER html %]">
+ <input type="hidden" name="token" value="[% token FILTER html %]">
+ <input type="submit" id="update" value="Save Changes">
+</form>
+
+[% PROCESS admin/fieldvalues/footer.html.tmpl
+ no_edit_link = 1 %]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/fieldvalues/footer.html.tmpl b/template/en/default/admin/fieldvalues/footer.html.tmpl
new file mode 100644
index 000000000..288612d4c
--- /dev/null
+++ b/template/en/default/admin/fieldvalues/footer.html.tmpl
@@ -0,0 +1,55 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[%# INTERFACE:
+ # value: string; the value being inserted/edited.
+ # field: object; the field which the value belongs/belonged to.
+ #
+ # no_XXX_link: boolean; if defined, then don't show the corresponding
+ # link. Supported parameters are:
+ #
+ # no_edit_link
+ # no_edit_other_link
+ # no_add_link
+ #%]
+
+<hr>
+
+<p>
+
+[% UNLESS no_add_link %]
+ <a title="Add a value for the '[% field.description FILTER html %]' field."
+ href="editvalues.cgi?action=add&amp;field=
+ [%- field.name FILTER url_quote %]">Add</a> a value.
+[% END %]
+
+[% IF value.defined && !no_edit_link %]
+ Edit value <a
+ title="Edit value '[% value.name FILTER html %]' for the '
+ [%- field.name FILTER html %]' field"
+ href="editvalues.cgi?action=edit&amp;field=
+ [%- field.name FILTER url_quote %]&amp;value=
+ [%- value.name FILTER url_quote %]">
+ '[% value.name FILTER html %]'</a>.
+[% END %]
+
+[% UNLESS no_edit_other_link %]
+ Edit other values for the <a
+ href="editvalues.cgi?field=
+ [%- field.name FILTER url_quote %]">'[% field.description FILTER html %]'</a> field.
+
+[% END %]
+
+</p>
diff --git a/template/en/default/admin/fieldvalues/list.html.tmpl b/template/en/default/admin/fieldvalues/list.html.tmpl
new file mode 100644
index 000000000..3f750ebca
--- /dev/null
+++ b/template/en/default/admin/fieldvalues/list.html.tmpl
@@ -0,0 +1,99 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[%# INTERFACE:
+ # values: array of hashes having the following properties:
+ # - name: string; The value.
+ # - sortkey: number; The sortkey used to order the value when
+ # displayed to the user in a list.
+ #
+ # field: object; the field we are editing values for.
+ # static: array; list of values which cannot be renamed nor deleted.
+ #%]
+
+[% USE Bugzilla %]
+[% cgi = Bugzilla.cgi %]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% title = BLOCK %]Select value for the '[% field.description FILTER html %]'
+ ([% field.name FILTER html %]) field[% END %]
+[% PROCESS global/header.html.tmpl
+ title = title
+%]
+
+[% edit_contentlink = BLOCK %]editvalues.cgi?action=edit&amp;field=
+ [%- field.name FILTER url_quote %]&amp;value=%%name%%[% END %]
+[% delete_contentlink = BLOCK %]editvalues.cgi?action=del&amp;field=
+ [%- field.name FILTER url_quote %]&amp;value=%%name%%[% END %]
+
+
+[% columns = [
+ {
+ name => "name"
+ heading => "Edit field value..."
+ contentlink => edit_contentlink
+ },
+ {
+ name => "sortkey"
+ heading => "Sortkey"
+ },
+ {
+ name => "isactive"
+ heading => "Enabled for $terms.bugs"
+ yesno_field => 1
+ },
+ {
+ name => "action"
+ heading => "Action"
+ content => "Delete"
+ contentlink => delete_contentlink
+ } ]
+%]
+
+
+[% SET overrides.action = {} %]
+[% FOREACH check_value = values %]
+ [% IF check_value.is_static %]
+ [% overrides.action.name.${check_value.name} = {
+ override_content => 1
+ content => "(Non-deletable value)"
+ override_contentlink => 1
+ contentlink => undef
+ }
+ %]
+ [% ELSIF check_value.is_default %]
+ [% overrides.action.name.${check_value.name} = {
+ override_content => 1
+ content => "(Default value)"
+ override_contentlink => 1
+ contentlink => undef
+ }
+ %]
+ [% END %]
+
+[% END %]
+
+[% PROCESS admin/table.html.tmpl
+ columns = columns
+ data = values
+ overrides = overrides
+%]
+
+[% PROCESS admin/fieldvalues/footer.html.tmpl
+ no_edit_other_link = 1
+ %]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/fieldvalues/select-field.html.tmpl b/template/en/default/admin/fieldvalues/select-field.html.tmpl
new file mode 100644
index 000000000..3704d42aa
--- /dev/null
+++ b/template/en/default/admin/fieldvalues/select-field.html.tmpl
@@ -0,0 +1,47 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+ #
+ #%]
+
+[%# INTERFACE:
+ # fields: Array of hashes. Each hash contains only one key, "name."
+ # The names are the same as the keys from field_descs
+ # (see global/field-descs.html.tmpl).
+ #%]
+
+[% USE Bugzilla %]
+[% cgi = Bugzilla.cgi %]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% PROCESS global/header.html.tmpl
+ title = "Edit values for which field?"
+%]
+
+[% columns = [
+ {
+ name => "name"
+ heading => "Edit field values for..."
+ content_use_field = 1
+ contentlink => "editvalues.cgi?field=%%name%%"
+ }
+ ]
+%]
+
+[% PROCESS admin/table.html.tmpl
+ columns = columns
+ data = fields
+%]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/flag-type/confirm-delete.html.tmpl b/template/en/default/admin/flag-type/confirm-delete.html.tmpl
new file mode 100644
index 000000000..ed909417d
--- /dev/null
+++ b/template/en/default/admin/flag-type/confirm-delete.html.tmpl
@@ -0,0 +1,63 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Myk Melez <myk@mozilla.org>
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% title = BLOCK %]Confirm Deletion of Flag Type '[% flag_type.name FILTER html %]'[% END %]
+
+[% PROCESS global/header.html.tmpl
+ title = title
+ doc_section = "flags-overview.html#flags-delete"
+%]
+
+<p>
+ [% IF flag_type.flag_count %]
+ There are [% flag_type.flag_count %] flags of type [% flag_type.name FILTER html %].
+ If you delete this type, those flags will also be deleted.
+ [% END %]
+
+ Note that instead of deleting the type you can
+ <a href="editflagtypes.cgi?action=deactivate&amp;id=[% flag_type.id %]&amp;token=
+ [%- token FILTER html %]">deactivate it</a>,
+ in which case the type [% IF flag_type.flag_count %] and its flags [% END %] will remain
+ in the database but will not appear in the [% terms.Bugzilla %] UI.
+</p>
+
+<table>
+ <tr>
+ <td colspan="2">
+ Do you really want to delete this type?
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <a href="editflagtypes.cgi?action=delete&amp;id=[% flag_type.id %]&amp;token=
+ [%- token FILTER html %]">Yes, delete
+ </a>
+ </td>
+ <td align="right">
+ <a href="editflagtypes.cgi">
+ No, don't delete
+ </a>
+ </td>
+ </tr>
+</table>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/flag-type/edit.html.tmpl b/template/en/default/admin/flag-type/edit.html.tmpl
new file mode 100644
index 000000000..ebebf5082
--- /dev/null
+++ b/template/en/default/admin/flag-type/edit.html.tmpl
@@ -0,0 +1,253 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Myk Melez <myk@mozilla.org>
+ # Mark Bickford <markhb@maine.rr.com>
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% PROCESS "global/js-products.html.tmpl" %]
+
+[% IF type.target_type == "bug" %]
+ [% title = BLOCK %]Create Flag Type for [% terms.Bugs %][% END %]
+ [% typeLabelLowerPlural = BLOCK %][% terms.bugs %][% END %]
+ [% typeLabelLowerSingular = BLOCK %][% terms.bug %][% END %]
+[% ELSE %]
+ [% title = "Create Flag Type for Attachments" %]
+ [% typeLabelLowerPlural = BLOCK %]attachments[% END %]
+ [% typeLabelLowerSingular = BLOCK %]attachment[% END %]
+[% END %]
+
+[% doc_section = "flags-overview.html#flags-create" %]
+[% IF last_action == "copy" %]
+ [% title = BLOCK %]Create Flag Type Based on [% type.name FILTER html %][% END %]
+[% ELSIF last_action == "edit" %]
+ [% title = BLOCK %]Edit Flag Type [% type.name FILTER html %][% END %]
+ [% doc_section = "flags-overview.html#flags-edit" %]
+[% END %]
+
+[% PROCESS global/header.html.tmpl
+ title = title
+ style = "
+ table#form th { text-align: right; vertical-align: baseline; white-space: nowrap; }
+ table#form td { text-align: left; vertical-align: baseline; }
+ "
+ onload="var f = document.forms[0]; selectProduct(f.product, f.component, null, null, '__Any__');"
+ javascript_urls=["js/productform.js"]
+ doc_section = doc_section
+%]
+
+<form method="post" action="editflagtypes.cgi">
+ <input type="hidden" name="action" value="[% action %]">
+ <input type="hidden" name="id" value="[% type.id %]">
+ <input type="hidden" name="token" value="[% token FILTER html %]">
+ <input type="hidden" name="target_type" value="[% type.target_type %]">
+ [% FOREACH category = type.inclusions %]
+ <input type="hidden" name="inclusions" value="[% category.value FILTER html %]">
+ [% END %]
+ [% FOREACH category = type.exclusions %]
+ <input type="hidden" name="exclusions" value="[% category.value FILTER html %]">
+ [% END %]
+
+ [%# Add a hidden button at the top of the form so that the user pressing "return"
+ # really submit the form, as expected. %]
+ <input type="submit" id="commit" value="Submit" style="display: none;">
+
+ <table id="form" cellspacing="0" cellpadding="4" border="0">
+ <tr>
+ <th>Name:</th>
+ <td>
+ a short name identifying this type<br>
+ <input type="text" name="name" value="[% type.name FILTER html %]"
+ size="50" maxlength="50">
+ </td>
+ </tr>
+
+ <tr>
+ <th>Description:</th>
+ <td>
+ a comprehensive description of this type<br>
+ [% INCLUDE global/textarea.html.tmpl
+ name = 'description'
+ minrows = 4
+ cols = 80
+ defaultcontent = type.description
+ %]
+ </td>
+ </tr>
+
+ <tr>
+ <th>Category:</th>
+
+ <td>
+ the products/components to which [% typeLabelLowerPlural %] must
+ (inclusions) or must not (exclusions) belong in order for users
+ to be able to set flags of this type for them
+ <table>
+ <tr>
+ <td style="vertical-align: top;">
+ <b>Product/Component:</b><br>
+ <select name="product" onchange="selectProduct(this, this.form.component, null, null, '__Any__');">
+ <option value="">__Any__</option>
+ [% FOREACH prod = products %]
+ <option value="[% prod.name FILTER html %]"
+ [% "selected" IF type.product.name == prod.name %]>
+ [% prod.name FILTER html %]</option>
+ [% END %]
+ </select><br>
+ <select name="component">
+ <option value="">__Any__</option>
+ [% FOREACH comp = components %]
+ <option value="[% comp FILTER html %]"
+ [% "selected" IF type.component.name == comp %]>
+ [% comp FILTER html %]</option>
+ [% END %]
+ </select><br>
+ <input type="submit" name="categoryAction-include" value="Include">
+ <input type="submit" name="categoryAction-exclude" value="Exclude">
+ </td>
+ <td style="vertical-align: top;">
+ <b>Inclusions:</b><br>
+ [% PROCESS "global/select-menu.html.tmpl" name="inclusion_to_remove" multiple="1" size="7" options=type.inclusions %]<br>
+ <input type="submit" name="categoryAction-removeInclusion" value="Remove Inclusion">
+ </td>
+ <td style="vertical-align: top;">
+ <b>Exclusions:</b><br>
+ [% PROCESS "global/select-menu.html.tmpl" name="exclusion_to_remove" multiple="1" size="7" options=type.exclusions %]<br>
+ <input type="submit" name="categoryAction-removeExclusion" value="Remove Exclusion">
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+
+ <tr>
+ <th>Sort Key:</th>
+ <td>
+ a number between 1 and 32767 by which this type will be sorted
+ when displayed to users in a list; ignore if you don't care
+ what order the types appear in or if you want them to appear
+ in alphabetical order<br>
+ <input type="text" name="sortkey" value="[% type.sortkey || 1 %]" size="5" maxlength="5">
+ </td>
+ </tr>
+
+ <tr>
+ <th>&nbsp;</th>
+ <td>
+ <input type="checkbox" id="is_active" name="is_active"
+ [% " checked" IF type.is_active || !type.is_active.defined %]>
+ <label for="is_active">active (flags of this type appear in the UI and can be set)</label>
+ </td>
+ </tr>
+
+ <tr>
+ <th>&nbsp;</th>
+ <td>
+ <input type="checkbox" id="is_requestable" name="is_requestable"
+ [% " checked" IF type.is_requestable || !type.is_requestable.defined %]>
+ <label for="is_requestable">requestable (users can ask for flags of this type to be set)</label>
+ </td>
+ </tr>
+
+ <tr>
+ <th>CC List:</th>
+ <td>
+ if requestable, who should get carbon copied on email notification of requests.
+ This is a comma-separated list of full e-mail addresses which do not
+ need to be [% terms.Bugzilla %] logins.
+ [% IF Param('emailsuffix') %]
+ Note that the configured emailsuffix
+ <kbd>[% Param('emailsuffix') %]</kbd> will <em>not</em> be appended
+ to these addresses, so you should add it explicitly if so desired.
+ [% END %]<br>
+ <input type="text" name="cc_list" value="[% type.cc_list FILTER html %]" size="80" maxlength="200">
+ </td>
+ </tr>
+
+ <tr>
+ <th>&nbsp;</th>
+ <td>
+ <input type="checkbox" id="is_requesteeble" name="is_requesteeble"
+ [% " checked" IF type.is_requesteeble || !type.is_requesteeble.defined %]>
+ <label for="is_requesteeble">specifically requestable (users can ask specific other users
+ to set flags of this type as opposed to just asking the wind)</label>
+ </td>
+ </tr>
+
+ <tr>
+ <th>&nbsp;</th>
+ <td>
+ <input type="checkbox" id="is_multiplicable" name="is_multiplicable"
+ [% " checked" IF type.is_multiplicable || !type.is_multiplicable.defined %]>
+ <label for="is_multiplicable">multiplicable (multiple flags of this type can be set on
+ the same [% typeLabelLowerSingular %])</label>
+ </td>
+ </tr>
+
+ <tr>
+ <th>Grant Group:</th>
+ <td>
+ the group allowed to grant/deny flags of this type
+ (to allow all users to grant/deny these flags, select no group)<br>
+ [% PROCESS select selname = "grant_group" %]
+ </td>
+ </tr>
+
+ <tr>
+ <th>Request Group:</th>
+ <td>
+ if flags of this type are requestable, the group allowed to request them
+ (to allow all users to request these flags, select no group)<br>
+ Note that the request group alone has no effect if the grant group is not defined!<br>
+ [% PROCESS select selname = "request_group" %]
+ </td>
+ </tr>
+
+ <tr>
+ <th></th>
+ <td>
+ <input type="submit" id="save" value="
+ [%- IF (last_action == "enter" || last_action == "copy") %]Create
+ [%- ELSE %]Save Changes
+ [%- END %]">
+ </td>
+ </tr>
+
+ </table>
+
+</form>
+
+[% PROCESS global/footer.html.tmpl %]
+
+
+[%############################################################################%]
+[%# Block for SELECT fields #%]
+[%############################################################################%]
+
+[% BLOCK select %]
+ <select name="[% selname %]" id="[% selname %]">
+ <option value="">(no group)</option>
+ [% FOREACH group = groups %]
+ <option value="[% group.name FILTER html %]"
+ [% " selected" IF (type.${selname} && type.${selname}.name == group.name) %]>
+ [%- group.name FILTER html %]
+ </option>
+ [% END %]
+ </select>
+[% END %]
diff --git a/template/en/default/admin/flag-type/list.html.tmpl b/template/en/default/admin/flag-type/list.html.tmpl
new file mode 100644
index 000000000..220db8900
--- /dev/null
+++ b/template/en/default/admin/flag-type/list.html.tmpl
@@ -0,0 +1,174 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Myk Melez <myk@mozilla.org>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% PROCESS "global/js-products.html.tmpl" %]
+
+[% PROCESS global/header.html.tmpl
+ title = 'Administer Flag Types'
+ style = "
+ table#flag_types_bugs tr th,
+ table#flag_types_attachments tr th { text-align: left; }
+ .inactive { color: #787878; }
+ .multiplicable { display: block; }
+ "
+ onload="var f = document.flagtype_form; selectProduct(f.product, f.component, null, null, '__All__');"
+ javascript_urls=["js/productform.js"]
+ doc_section = "flags-overview.html#flag-types"
+%]
+
+<p>
+ Flags are markers that identify whether [% terms.abug %] or attachment has been granted
+ or denied some status. Flags appear in the UI as a name and a status symbol
+ ("+" for granted, "-" for denied, and "?" for statuses requested by users).
+</p>
+
+<p>
+ For example, you might define a "review" status for users to request review
+ for their patches. When a patch writer requests review, the string "review?"
+ will appear in the attachment. When a patch reviewer reviews the patch,
+ either the string "review+" or the string "review-" will appear in the patch,
+ depending on whether the patch passed or failed review.
+</p>
+
+<p>
+ You can restrict the list of flag types to those available for a given product
+ and component. If a product is selected with no component, only flag types
+ which are available to at least one component of the product are shown.
+</p>
+
+<form id="flagtype_form" name="flagtype_form" action="editflagtypes.cgi" method="get">
+ <table>
+ <tr>
+ <th><label for="product">Product:</label></th>
+ <td>
+ <select name="product" onchange="selectProduct(this, this.form.component, null, null, '__Any__');">
+ <option value="">__Any__</option>
+ [% FOREACH prod = products %]
+ <option value="[% prod.name FILTER html %]"
+ [% " selected" IF selected_product == prod.name %]>
+ [% prod.name FILTER html %]</option>
+ [% END %]
+ </select>
+ </td>
+ <th><label for="component">Component:</label></th>
+ <td>
+ <select name="component">
+ <option value="">__Any__</option>
+ [% FOREACH comp = components %]
+ <option value="[% comp FILTER html %]"
+ [% " selected" IF selected_component == comp %]>
+ [% comp FILTER html %]</option>
+ [% END %]
+ </select>
+ </td>
+ <td>
+ <input type="checkbox" id="show_flag_counts" name="show_flag_counts" value="1"
+ [%+ 'checked="checked"' IF show_flag_counts %]>
+ <label for="show_flag_counts">Show flag counts</label>
+ </td>
+ <td><input type="submit" id="submit" value="Filter"></td>
+ </tr>
+ </table>
+</form>
+
+<h3>Flag Types for [% terms.Bugs %]</h3>
+
+[% PROCESS display_flag_types types=bug_types types_id='bugs' %]
+
+<p>
+ <a href="editflagtypes.cgi?action=enter&amp;target_type=bug">Create Flag Type for [% terms.Bugs %]</a>
+</p>
+
+<h3>Flag Types for Attachments</h3>
+
+[% PROCESS display_flag_types types=attachment_types types_id='attachments' %]
+
+<p>
+ <a href="editflagtypes.cgi?action=enter&amp;target_type=attachment">Create Flag Type For Attachments</a>
+</p>
+
+[% PROCESS global/footer.html.tmpl %]
+
+
+[% BLOCK display_flag_types %]
+ <table id="flag_types_[% types_id FILTER html %]" cellspacing="0" cellpadding="4" border="1">
+
+ <tr>
+ <th>Edit name ...</th>
+ <th>Description</th>
+ <th>Sortkey</th>
+ <th>Properties</th>
+ <th>Grant group</th>
+ <th>Request group</th>
+ [% IF show_flag_counts %]
+ <th>Flags</th>
+ [%# Note to translators: translate the strings in quotes only. %]
+ [% state_desc = {granted = 'granted' denied = 'denied' pending = 'pending'} %]
+ [% END %]
+ <th>Actions</th>
+ </tr>
+
+ [% FOREACH type = types %]
+
+ <tr class="[% IF type.is_active %]active[% ELSE %]inactive[% END %]">
+ <td><a href="editflagtypes.cgi?action=edit&amp;id=[% type.id %]">[% type.name FILTER html FILTER no_break %]</a></td>
+ <td>[% type.description FILTER html %]</td>
+ <td align="right">[% type.sortkey FILTER html %]</td>
+ <td>
+ [% IF type.is_requestable %]
+ <span class="requestable">requestable</span>
+ [% END %]
+ [% IF type.is_requestable && type.is_requesteeble %]
+ <span class="requesteeble">(specifically)</span>
+ [% END %]
+ [% IF type.is_multiplicable %]
+ <span class="multiplicable">multiplicable</span>
+ [% END %]
+ </td>
+ <td>[% IF type.grant_group %][% type.grant_group.name FILTER html %][% END %]</td>
+ <td>[% IF type.request_group %][% type.request_group.name FILTER html %][% END %]</td>
+ [% IF show_flag_counts %]
+ <td>
+ [% FOREACH state = ['granted', 'pending', 'denied'] %]
+ [% bug_list = bug_lists.${type.id}.$state || [] %]
+ [% IF bug_list.size %]
+ <a href="buglist.cgi?bug_id=[% bug_list.unique.nsort.join(",") FILTER html %]">
+ [% bug_list.size FILTER html %] [%+ state_desc.$state FILTER html %]
+ </a>
+ <br>
+ [% ELSE %]
+ 0 [% state_desc.$state FILTER html %]<br>
+ [% END %]
+ [% END %]
+ </td>
+ [% END %]
+ <td>
+ <a href="editflagtypes.cgi?action=copy&amp;id=[% type.id %]">Copy</a>
+ | <a href="editflagtypes.cgi?action=confirmdelete&amp;id=[% type.id %]">Delete</a>
+ </td>
+ </tr>
+
+ [% END %]
+
+ </table>
+[% END %]
diff --git a/template/en/default/admin/groups/confirm-remove.html.tmpl b/template/en/default/admin/groups/confirm-remove.html.tmpl
new file mode 100644
index 000000000..cdb070d33
--- /dev/null
+++ b/template/en/default/admin/groups/confirm-remove.html.tmpl
@@ -0,0 +1,66 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Dave Miller <justdave@syndicomm.com>
+ # Joel Peshkin <bugreport@peshkin.net>
+ # Jacob Steenhagen <jake@bugzilla.org>
+ # Vlad Dascalu <jocuri@softhome.net>
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[%# INTERFACE:
+ # group: The Bugzilla::Group being changed.
+ # regexp: the regexp according to which the update is performed.
+ #%]
+
+[% IF regexp %]
+ [% title = "Confirm: Remove Explicit Members in the Regular Expression?" %]
+[% ELSE %]
+ [% title = "Confirm: Remove All Explicit Members?" %]
+[% END %]
+
+[% PROCESS global/header.html.tmpl
+ title = title
+ doc_section = "groups.html"
+%]
+
+[% IF regexp %]
+ <p>This option will remove all users from '[% group.name FILTER html %]'
+ whose login names match the regular expression:
+ '[% regexp FILTER html %]'</p>
+[% ELSE %]
+ <p>This option will remove all explicitly defined users
+ from '[% group.name FILTER html %].'</p>
+[% END %]
+
+<p>Generally, you will only need to do this when upgrading groups
+ created with [% terms.Bugzilla %] versions 2.16 and earlier. Use
+ this option with <b>extreme care</b> and consult the documentation
+ for further information.
+</p>
+
+<form method="post" action="editgroups.cgi">
+ <input type="hidden" name="group_id" value="[% group.id FILTER html %]">
+ <input type="hidden" name="regexp" value="[% regexp FILTER html %]">
+ <input type="hidden" name="action" value="remove_regexp">
+
+ <input name="token" type="hidden" value="[% token FILTER html %]">
+ <input name="confirm" type="submit" value="Confirm">
+ <p>Or <a href="editgroups.cgi">return to the Edit Groups page</a>.</p>
+</form>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/groups/create.html.tmpl b/template/en/default/admin/groups/create.html.tmpl
new file mode 100644
index 000000000..b3ac72372
--- /dev/null
+++ b/template/en/default/admin/groups/create.html.tmpl
@@ -0,0 +1,102 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Dave Miller <justdave@syndicomm.com>
+ # Joel Peshkin <bugreport@peshkin.net>
+ # Jacob Steenhagen <jake@bugzilla.org>
+ # Vlad Dascalu <jocuri@softhome.net>
+ #%]
+
+[%# INTERFACE:
+ # none
+ #%]
+
+[% PROCESS global/header.html.tmpl
+ title = "Add group"
+ subheader = "This page allows you to define a new user group."
+ doc_section = "groups.html#create-groups"
+%]
+
+<form method="post" action="editgroups.cgi">
+ <table border="1" cellpadding="4" cellspacing="0"><tr>
+ <th>New Name</th>
+ <th>New Description</th>
+ <th>New User RegExp</th>
+ <th>Use For [% terms.Bugs %]</th>
+ </tr><tr>
+ <td><input size="20" name="name"></td>
+ <td><input size="40" name="desc"></td>
+ <td><input size="30" name="regexp"></td>
+ <td><input type="checkbox" name="isactive" value="1" checked></td>
+ </tr>
+ <tr>
+ <th>Icon URL:</th>
+ <td colspan="3"><input type="text" size="70" maxlength="255" id="icon_url" name="icon_url"></td>
+ </tr>
+ [% Hook.process('field') %]
+ </table>
+
+ <hr>
+
+ <input type="checkbox" id="insertnew" name="insertnew" value="1"
+ [% IF Param("makeproductgroups") %] checked[% END %]>
+ <label for="insertnew">Insert new group into all existing products.</label>
+ <p>
+ <input type="submit" id="create" value="Add">
+ <input type="hidden" name="action" value="new">
+ <input type="hidden" name="token" value="[% token FILTER html %]">
+</form>
+
+<p><b>Name</b> is what is used with the B<!-- blah -->ugzilla->user->in_group()
+function in any customized cgi files you write that use a given group.
+It can also be used by people submitting [% terms.bugs %] by email to
+limit [% terms.abug %] to a certain set of groups.</p>
+
+<p><b>Description</b> is what will be shown in the [% terms.bug %] reports
+to members of the group where they can choose whether
+the [% terms.bug %] will be restricted to others in the same group.</p>
+
+<p>The <b>Use For [% terms.Bugs %]</b> flag determines whether or not the
+group is eligible to be used for [% terms.bugs %]. If you clear this, it will
+no longer be possible for users to add [% terms.bugs %] to this group,
+although [% terms.bugs %] already in the group will remain in the group.
+Doing so is a much less drastic way to stop a group from growing
+than deleting the group would be. <b>Note: If you are creating
+a group, you probably want it to be usable for [% terms.bugs %], in which
+case you should leave this checked.</b></p>
+
+<p><b>User RegExp</b> is optional, and if filled in, will
+automatically grant membership to this group to anyone with an
+email address that matches this regular expression.</p>
+
+<p>
+ <b>Icon URL</b> is optional, and is the URL pointing to the icon
+ used to identify the group. It may be either a relative URL to the base URL
+ of this installation or an absolute URL. This icon will be displayed
+ in comments in [% terms.bugs %] besides the name of the author of comments.
+</p>
+
+<p>By default, the new group will be associated with existing
+products. Unchecking the "Insert new group into all existing
+products" option will prevent this and make the group become
+visible only when its controls have been added to a product.</p>
+
+<p>Back to the <a href="./">main [% terms.bugs %] page</a>
+
+or to the <a href="editgroups.cgi">group list</a>.
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/groups/delete.html.tmpl b/template/en/default/admin/groups/delete.html.tmpl
new file mode 100644
index 000000000..9d32da4de
--- /dev/null
+++ b/template/en/default/admin/groups/delete.html.tmpl
@@ -0,0 +1,187 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Dave Miller <justdave@syndicomm.com>
+ # Joel Peshkin <bugreport@peshkin.net>
+ # Jacob Steenhagen <jake@bugzilla.org>
+ # Vlad Dascalu <jocuri@softhome.net>
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[%# INTERFACE:
+ # group: A Bugzilla::Group object representing the group that is
+ # about to be deleted.
+ # shared_queries: int; The number of queries being shared with this
+ # group.
+ #%]
+
+
+[% PROCESS global/header.html.tmpl
+ title = "Delete group"
+ doc_section = "groups.html"
+%]
+
+<table border="1">
+ <tr>
+ <th>Id</th>
+ <th>Name</th>
+ <th>Description</th>
+ </tr>
+ <tr>
+ <td>[% group.id FILTER html %]</td>
+ <td>[% group.name FILTER html %]</td>
+ <td>[% group.description FILTER html_light %]</td>
+ </tr>
+</table>
+
+<form method="post" action="editgroups.cgi">
+ [% IF group.members_non_inherited.size %]
+ <p><b>[% group.members_non_inherited.size FILTER html %] users belong
+ directly to this group. You cannot delete this group while there are
+ users in it.</b>
+
+ <br><a href="editusers.cgi?action=list&amp;groupid=
+ [%- group.id FILTER url_quote %]&amp;grouprestrict=1">Show
+ me which users</a> - <label><input type="checkbox" name="removeusers">Remove
+ all users from this group for me.</label></p>
+ [% END %]
+
+ [% IF group.granted_by_direct(constants.GROUP_MEMBERSHIP).size %]
+ <p><b>Members of this group inherit membership in the following groups:</b></p>
+ <ul>
+ [% FOREACH grantor = group.granted_by_direct(constants.GROUP_MEMBERSHIP) %]
+ <li>[% grantor.name FILTER html %]</li>
+ [% END %]
+ </ul>
+ [% END %]
+
+ [% IF group.bugs.size %]
+ <p><b>[% group.bugs.size FILTER html %] [%+ terms.bug %] reports are
+ visible only to this group. You cannot delete this group while any
+ [%+ terms.bugs %] are using it.</b>
+
+ <br><a href="buglist.cgi?field0-0-0=bug_group&amp;type0-0-0=equals&amp;value0-0-0=
+ [%- group.name FILTER url_quote %]">Show me
+ which [% terms.bugs %]</a> -
+ <label><input type="checkbox" name="removebugs">Remove
+ all [% terms.bugs %] from this group restriction for me.</label></p>
+
+ <p><b>NOTE:</b> It's quite possible to make confidential [% terms.bugs %]
+ public by checking this box. It is <B>strongly</B> suggested
+ that you review the [% terms.bugs %] in this group before checking
+ the box.</p>
+ [% END %]
+
+ [% IF group.products.size %]
+ <p><b>This group is tied to the following products:</b></p>
+ [% SET any_hidden = 0 %]
+ <ul>
+ [% FOREACH data = group.products %]
+
+ [% SET active = [] %]
+ [% FOREACH control = data.controls.keys.sort %]
+ [% NEXT IF !data.controls.$control %]
+ [% IF control == 'othercontrol' OR control == 'membercontrol' %]
+ [% SWITCH data.controls.$control %]
+ [% CASE constants.CONTROLMAPMANDATORY %]
+ [% SET type = "Mandatory" %]
+ [% CASE constants.CONTROLMAPSHOWN %]
+ [% SET type = "Shown" %]
+ [% CASE constants.CONTROLMAPDEFAULT %]
+ [% SET type = "Default" %]
+ [% END %]
+ [% active.push("$control: $type") %]
+ [% ELSE %]
+ [% active.push(control) %]
+ [% END %]
+ [% END %]
+
+ [% SET hidden = 0 %]
+ [% IF data.controls.othercontrol == constants.CONTROLMAPMANDATORY
+ AND data.controls.membercontrol == constants.CONTROLMAPMANDATORY
+ AND data.controls.entry
+ %]
+ [% SET hidden = 1 %]
+ [% END %]
+
+ <li><a href="editproducts.cgi?action=editgroupcontrols&amp;product=
+ [%- data.product.name FILTER url_quote %]">
+ [%- data.product.name FILTER html %]</a>
+ ([% active.join(', ') FILTER html %])
+ [% IF hidden %]
+ <strong>WARNING: This product is currently hidden.
+ Deleting this group will make this product publicly visible.
+ </strong>
+ [% END %]</li>
+ [% END %]
+ </ul>
+
+ <p><label><input type="checkbox" name="unbind">Delete this group anyway,
+ and remove these controls.</label></p>
+ [% END %]
+
+ [% IF group.flag_types.size %]
+ <p><b>This group restricts who can make changes to flags of certain types.
+ You cannot delete this group while there are flag types using it.</b>
+
+ <br><a href="editflagtypes.cgi?action=list&amp;group=
+ [%- group.id FILTER url_quote %]">Show
+ me which types</a> -
+ <label><input type="checkbox" name="removeflags">Remove all
+ flag types from this group for me.</label></p>
+ [% END %]
+
+ [% IF shared_queries %]
+ <p>
+ <b>There
+ [% IF shared_queries > 1 %]
+ are [% shared_queries %] saved searches
+ [% ELSE %]
+ is a saved search
+ [% END %]
+ being shared with this group.</b>
+ If you delete this group,
+ [% IF shared_queries > 1 %]
+ these saved searches
+ [% ELSE %]
+ this saved search
+ [% END %]
+ will fall back to being private again.
+ </p>
+ [% END %]
+
+ <h2>Confirmation</h2>
+
+ <p>Do you really want to delete this group?</p>
+ [% IF group.users.size || group.bugs.size || group.products.size
+ || group.flags.size
+ %]
+ <p><b>You must check all of the above boxes or correct the
+ indicated problems first before you can proceed.</b></p>
+ [% END %]
+
+ <p>
+ <input type="submit" id="delete" value="Yes, delete">
+ <input type="hidden" name="action" value="delete">
+ <input type="hidden" name="group" value="[% group.id FILTER html %]">
+ <input type="hidden" name="token" value="[% token FILTER html %]">
+ </p>
+</form>
+
+Go back to the <a href="editgroups.cgi">group list</a>.
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/groups/edit.html.tmpl b/template/en/default/admin/groups/edit.html.tmpl
new file mode 100644
index 000000000..f7fd3081b
--- /dev/null
+++ b/template/en/default/admin/groups/edit.html.tmpl
@@ -0,0 +1,249 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Dave Miller <justdave@syndicomm.com>
+ # Joel Peshkin <bugreport@peshkin.net>
+ # Jacob Steenhagen <jake@bugzilla.org>
+ # Vlad Dascalu <jocuri@softhome.net>
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[%# INTERFACE:
+ # group - A Bugzilla::Group representing the group being edited.
+ # *_current - Arrays of Bugzilla::Group objects that show the current
+ # values for this group, as far as grants.
+ # *_available - Arrays of Bugzilla::Group objects that show the current
+ # available values for each grant.
+ #%]
+
+[% title = BLOCK %]Change Group: [% group.name FILTER html %][% END %]
+
+[% PROCESS global/header.html.tmpl
+ title = title
+ doc_section = "groups.html#edit-groups"
+ style = "
+ .grant_table { border-collapse: collapse; }
+ .grant_table td, .grant_table th {
+ padding-left: .5em;
+ }
+ .grant_table td.one, .grant_table th.one {
+ border-right: 1px solid black;
+ padding-right: .5em;
+ }
+ "
+%]
+
+<form method="post" action="editgroups.cgi">
+ <input type="hidden" name="action" value="postchanges">
+ <input type="hidden" name="group_id" value="[% group.id FILTER html %]">
+
+ <table border="1" cellpadding="4">
+ <tr>
+ <th>Group:</th>
+ <td>
+ [% IF group.is_bug_group %]
+ <input type="text" name="name" size="60"
+ value="[% group.name FILTER html %]">
+ [% ELSE %]
+ [% group.name FILTER html %]
+ [% END %]
+ </td>
+ </tr>
+
+ <tr>
+ <th>Description:</th>
+ <td>
+ [% IF group.is_bug_group %]
+ <input type="text" name="desc" size="70"
+ value="[% group.description FILTER html %]">
+ [% ELSE %]
+ [% group.description FILTER html %]
+ [% END %]
+ </td>
+ </tr>
+
+ <tr>
+ <th>User Regexp:</th>
+ <td>
+ <input type="text" name="regexp" size="40"
+ value="[% group.user_regexp FILTER html %]">
+ </td>
+ </tr>
+
+ <tr>
+ <th>
+ Icon URL:
+ [% IF group.icon_url %]
+ <img src="[% group.icon_url FILTER html %]" alt="[% group.name FILTER html %]">
+ [% END %]
+ </th>
+ <td>
+ <input type="text" name="icon_url" size="70" maxlength="255"
+ value="[% group.icon_url FILTER html %]">
+ </td>
+ </tr>
+
+ [% IF group.is_bug_group %]
+ <tr>
+ <th>Use For [% terms.Bugs %]:</th>
+ <td>
+ <input type="checkbox" name="isactive"
+ value="1" [% 'checked="checked"' IF group.is_active %]>
+ </td>
+ </tr>
+ [% END %]
+ [% Hook.process('field') %]
+ </table>
+
+ <h4>Group Permissions</h4>
+
+ <table class="grant_table">
+ <tr>
+ <th class="one">Groups That Are a Member of This Group<br>
+ (&quot;Users in <var>X</var> are automatically in
+ [%+ group.name FILTER html %]&quot;)</th>
+ <th>Groups That This Group Is a Member Of<br>
+ (&quot;If you are in [% group.name FILTER html %], you are
+ automatically also in...&quot;)</th>
+ </tr>
+ <tr>
+ <td class="one">
+ [% PROCESS select_pair name = "members" size = 10
+ items_available = members_available
+ items_current = members_current %]
+ </td>
+
+ <td>[% PROCESS select_pair name = "member_of" size = 10
+ items_available = member_of_available
+ items_current = member_of_current %]</td>
+ </tr>
+ </table>
+
+ <table class="grant_table">
+ <tr>
+ <th class="one">
+ Groups That Can Grant Membership in This Group<br>
+ (&quot;Users in <var>X</var> can add other users to
+ [%+ group.name FILTER html %]&quot;)
+
+ </th>
+ <th>Groups That This Group Can Grant Membership In<br>
+ (&quot;Users in [% group.name FILTER html %] can add users to...&quot;)
+ </th>
+ </tr>
+ <tr>
+ <td class="one">
+ [% PROCESS select_pair name = "bless_from" size = 10
+ items_available = bless_from_available
+ items_current = bless_from_current %]
+ </td>
+ <td>[% PROCESS select_pair name = "bless_to" size = 10
+ items_available = bless_to_available
+ items_current = bless_to_current %]
+ </td>
+ </tr>
+ </table>
+
+ [% IF Param('usevisibilitygroups') %]
+ <table class="grant_table">
+ <tr>
+ <th class="one">
+ Groups That Can See This Group<br>
+ (&quot;Users in <var>X</var> can see users in
+ [%+ group.name FILTER html %]&quot;)
+ </th>
+ <th>Groups That This Group Can See<br>
+ (&quot;Users in [% group.name FILTER html %] can see users in...&quot;)
+ </th>
+ </tr>
+ <tr>
+ <td class="one">
+ [% PROCESS select_pair name = "visible_from" size = 10
+ items_available = visible_from_available
+ items_current = visible_from_current %]
+ </td>
+ <td>[% PROCESS select_pair name = "visible_to_me" size = 10
+ items_available = visible_to_me_available
+ items_current = visible_to_me_current %]
+ </td>
+ </tr>
+ </table>
+ [% END %]
+
+ <input type="submit" value="Update Group">
+ <input type="hidden" name="token" value="[% token FILTER html %]">
+</form>
+
+<h4>Mass Remove</h4>
+
+<p>You can use this form to do mass-removal of users from groups.
+ This is often very useful if you upgraded from [% terms.Bugzilla %]
+ 2.16.</p>
+
+<table><tr><td>
+<form method="post" action="editgroups.cgi">
+ <fieldset>
+ <legend>Remove all explicit memberships from users whose login names
+ match the following regular expression:</legend>
+ <input type="text" size="20" name="regexp">
+ <input type="submit" value="Remove Memberships">
+
+ <p>If you leave the field blank, all explicit memberships in
+ this group will be removed.</p>
+
+ <input type="hidden" name="action" value="confirm_remove">
+ <input type="hidden" name="group_id" value="[% group.id FILTER html %]">
+ </fieldset>
+</form>
+</td></tr></table>
+
+<p>Back to the <a href="editgroups.cgi">group list</a>.</p>
+
+[% PROCESS global/footer.html.tmpl %]
+
+[% BLOCK select_pair %]
+ <table class="select_pair">
+ <tr>
+ <th><label for="[% "${name}_add" FILTER html %]">Add<br>
+ (select to add)</label></th>
+ <th><label for="[% "${name}_remove" FILTER html %]">Current<br>
+ (select to remove)</label></th>
+ </tr>
+ <tr>
+ <td>
+ <select multiple="multiple" size="[% size FILTER html %]"
+ name="[% "${name}_add" FILTER html %]"
+ id="[% "${name}_add" FILTER html %]">
+ [% FOREACH item = items_available %]
+ <option value="[% item.id FILTER html %]">
+ [% item.name FILTER html %]</option>
+ [% END %]
+ </select>
+ </td>
+ <td>
+ <select multiple="multiple" size="[% size FILTER html %]"
+ name="[% "${name}_remove" FILTER html %]"
+ id="[% "${name}_remove" FILTER html %]">
+ [% FOREACH item = items_current %]
+ <option value="[% item.id FILTER html %]">
+ [% item.name FILTER html %]</option>
+ [% END %]
+ </select>
+ </td>
+ </tr>
+ </table>
+[% END %]
diff --git a/template/en/default/admin/groups/list.html.tmpl b/template/en/default/admin/groups/list.html.tmpl
new file mode 100644
index 000000000..1d137dc5f
--- /dev/null
+++ b/template/en/default/admin/groups/list.html.tmpl
@@ -0,0 +1,168 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Dave Miller <justdave@syndicomm.com>
+ # Joel Peshkin <bugreport@peshkin.net>
+ # Jacob Steenhagen <jake@bugzilla.org>
+ # Vlad Dascalu <jocuri@softhome.net>
+ #%]
+
+[%# INTERFACE:
+ # groups: array with group objects having the properties:
+ # - id: number. The ID of the group.
+ # - name: string. The name of the group.
+ # - description: string. The description of the group.
+ # - userregexp: string. The user regexp for the given group.
+ # - isactive: boolean int. Specifies if the group is active or not.
+ # - isbuggroup: boolean int. Specifies if it can be used for bugs.
+ #%]
+
+[% PROCESS global/header.html.tmpl
+ title = "Edit Groups"
+ subheader = "This lets you edit the groups available to put users in."
+ doc_section = "groups.html"
+%]
+
+[% edit_contentlink = "editgroups.cgi?action=changeform&amp;group=%%id%%" %]
+[% del_contentlink = "editgroups.cgi?action=del&amp;group=%%id%%" %]
+
+[% columns =
+ [{name => 'name'
+ heading => 'Name'
+ contentlink => edit_contentlink
+ }
+ {name => 'description'
+ heading => 'Description'
+ allow_html_content => 1
+ }
+ {name => 'userregexp'
+ heading => 'User RegExp'
+ }
+ {name => 'is_active_bug_group'
+ heading => "Use For $terms.Bugs"
+ align => 'center'
+ }
+ {name => 'type'
+ heading => 'Type'
+ align => 'center'
+ }
+ {name => 'action'
+ heading => 'Action'
+ }
+ ]
+%]
+
+[% overrides.is_active_bug_group = {
+ 'is_active_bug_group' => {
+ "0" => {
+ override_content => 1
+ content => "&nbsp;"
+ }
+ "1" => {
+ override_content => 1
+ content => "X"
+ }
+ }
+ }
+
+ overrides.userregexp = {
+ 'userregexp' => {
+ "" => {
+ override_content => 1
+ content => "&nbsp;"
+ }
+ }
+ }
+%]
+
+[% FOREACH group IN ["chartgroup", "insidergroup", "timetrackinggroup", "querysharegroup"] %]
+ [% special_group = Param(group) %]
+
+ [% IF special_group %]
+ [% overrides.action.name.$special_group = {
+ override_content => 1
+ content => "(used as the '$group')"
+ }
+ %]
+ [% END %]
+[% END %]
+
+[% overrides.action.isbuggroup = {
+ "1" => {
+ override_content => 1
+ content => "Delete"
+ override_contentlink => 1
+ contentlink => del_contentlink
+ }
+ }
+
+ overrides.type.isbuggroup = {
+ "0" => {
+ override_content => 1
+ content => "system"
+ }
+ "1" => {
+ override_content => 1
+ content => "user"
+ }
+ }
+%]
+
+[% PROCESS admin/table.html.tmpl
+ columns = columns
+ data = groups
+ overrides = overrides
+%]
+
+<p><a href="editgroups.cgi?action=add">Add Group</a></p>
+
+<p>
+ <b>Name</b> is what is used with the B<!-- blah -->ugzilla->user->in_group()
+function in any customized cgi files you write that use a given group.
+It can also be used by people submitting [% terms.bugs %] by email to
+limit [% terms.abug %] to a certain set of groups.
+</p>
+
+<p>
+ <b>Description</b> is what will be shown in the [% terms.bug %] reports
+to members of the group where they can choose whether the [% terms.bug %]
+will be restricted to others in the same group.
+</p>
+
+<p>
+ <b>User RegExp</b> is optional, and if filled in, will automatically
+grant membership to this group to anyone with an email address
+that matches this perl regular expression. Do not forget
+the trailing '$'. Example '@mycompany\.com$'
+</p>
+
+<p>
+ The <b>Use For [% terms.Bugs %]</b> flag determines whether or not
+the group is eligible to be used for [% terms.bugs %]. If you remove
+this flag, it will no longer be possible for users to add [% terms.bugs %]
+to this group, although [% terms.bugs %] already in the group will remain
+in the group. Doing so is a much less drastic way to stop a group
+from growing than deleting the group as well as a way to maintain
+lists of users without cluttering the lists of groups used
+for [% terms.bug %] restrictions.
+</p>
+
+<p>
+ The <b>Type</b> field identifies system groups.
+</p>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/keywords/confirm-delete.html.tmpl b/template/en/default/admin/keywords/confirm-delete.html.tmpl
new file mode 100644
index 000000000..20a6deee7
--- /dev/null
+++ b/template/en/default/admin/keywords/confirm-delete.html.tmpl
@@ -0,0 +1,53 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Terry Weissman <terry@mozilla.org>
+ # Vlad Dascalu <jocuri@softhome.net>
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[%# INTERFACE:
+ # keyword: A Bugzilla::Keyword object.
+ #%]
+
+[% PROCESS global/header.html.tmpl
+ title = "Delete Keyword"
+%]
+
+<p>
+ [% IF keyword.bug_count == 1 %]
+ There is one [% terms.bug %] with this keyword set.
+ [% ELSIF keyword.bug_count > 1 %]
+ There are [% keyword.bug_count FILTER html %] [%+ terms.bugs %] with
+ this keyword set.
+ [% END %]
+
+ Are you <b>sure</b> you want to delete
+ the <code>[% keyword.name FILTER html %]</code> keyword?
+</p>
+
+<form method="post" action="editkeywords.cgi">
+ <input type="hidden" name="id" value="[% keyword.id FILTER html %]">
+ <input type="hidden" name="action" value="delete">
+ <input type="hidden" name="token" value="[% token FILTER html %]">
+ <input type="submit" id="delete"
+ value="Yes, really delete the keyword">
+</form>
+
+<p><a href="editkeywords.cgi">Edit other keywords</a>.</p>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/keywords/create.html.tmpl b/template/en/default/admin/keywords/create.html.tmpl
new file mode 100644
index 000000000..e5d6aa0df
--- /dev/null
+++ b/template/en/default/admin/keywords/create.html.tmpl
@@ -0,0 +1,58 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Terry Weissman <terry@mozilla.org>
+ # Vlad Dascalu <jocuri@softhome.net>
+ #%]
+
+[%# INTERFACE:
+ # none
+ #%]
+
+[% PROCESS global/header.html.tmpl
+ title = "Add keyword"
+ subheader = "This page allows you to add a new keyword."
+%]
+
+<form method="post" action="editkeywords.cgi">
+ <table border="0" cellpadding="4" cellspacing="0">
+ <tr>
+ <th align="right">Name:</th>
+ <td><input size="64" maxlength="64" name="name" value=""></td>
+ </tr>
+ <tr>
+ <th align="right">Description:</th>
+ <td>
+ [% INCLUDE global/textarea.html.tmpl
+ name = 'description'
+ minrows = 4
+ cols = 64
+ wrap = 'virtual'
+ %]
+ </td>
+ </tr>
+ </table>
+ <hr>
+ <input type="hidden" name="id" value="-1">
+ <input type="submit" id="create" value="Add">
+ <input type="hidden" name="action" value="new">
+ <input type="hidden" name="token" value="[% token FILTER html %]">
+</form>
+
+<p><a href="editkeywords.cgi">Edit other keywords</a>.</p>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/keywords/edit.html.tmpl b/template/en/default/admin/keywords/edit.html.tmpl
new file mode 100644
index 000000000..c4b9a64d7
--- /dev/null
+++ b/template/en/default/admin/keywords/edit.html.tmpl
@@ -0,0 +1,73 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Terry Weissman <terry@mozilla.org>
+ # Vlad Dascalu <jocuri@softhome.net>
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[%# INTERFACE:
+ # keyword: A Bugzilla::Keyword object.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% PROCESS global/header.html.tmpl
+ title = "Edit keyword"
+%]
+
+<form method="post" action="editkeywords.cgi">
+ <table border="0" cellpadding="4" cellspacing="0">
+ <tr>
+ <th align="right">Name:</th>
+ <td><input size="64" maxlength="64" name="name"
+ value="[% keyword.name FILTER html %]"></td>
+ </tr>
+ <tr>
+ <th align="right">Description:</th>
+ <td>
+ [% INCLUDE global/textarea.html.tmpl
+ name = 'description'
+ minrows = 4
+ cols = 64
+ wrap = 'virtual'
+ defaultcontent = keyword.description
+ %]
+ </td>
+ </tr>
+ <tr>
+ <th align="right">[% terms.Bugs %]:</th>
+ <td>
+ [% IF keyword.bug_count > 0 %]
+ <a href="buglist.cgi?keywords=[% keyword.name FILTER url_quote %]">
+ [% keyword.bug_count FILTER html %]</a>
+ [% ELSE %]
+ none
+ [% END %]
+ </td>
+ </tr>
+ </table>
+
+ <input type="submit" id="update" value="Save Changes">
+ <input type="hidden" name="action" value="update">
+ <input type="hidden" name="id" value="[% keyword.id FILTER html %]">
+ <input type="hidden" name="token" value="[% token FILTER html %]">
+</form>
+
+<p><a href="editkeywords.cgi">Edit other keywords</a>.</p>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/keywords/list.html.tmpl b/template/en/default/admin/keywords/list.html.tmpl
new file mode 100644
index 000000000..c400a2362
--- /dev/null
+++ b/template/en/default/admin/keywords/list.html.tmpl
@@ -0,0 +1,70 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Terry Weissman <terry@mozilla.org>
+ # Vlad Dascalu <jocuri@softhome.net>
+ # Jouni Heikniemi <jouni@heikniemi.net>
+ #%]
+
+[%# INTERFACE:
+ # keywords: array keyword objects having the properties:
+ # - id: number. The ID of the keyword.
+ # - name: string. The name of the keyword.
+ # - description: string. The description of the keyword.
+ # - bug_count: number. The number of bugs with the keyword.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% PROCESS global/header.html.tmpl
+ title = "Select keyword"
+%]
+
+[% columns = [
+ {
+ name => "name"
+ heading => "Edit keyword..."
+ contentlink => "editkeywords.cgi?action=edit&amp;id=%%id%%"
+ },
+ {
+ name => "description"
+ heading => "Description"
+ allow_html_content => 1
+ },
+ {
+ name => "bug_count"
+ heading => "$terms.Bugs"
+ align => "right"
+ contentlink => "buglist.cgi?keywords=%%name%%"
+ },
+ {
+ heading => "Action"
+ content => "Delete"
+ contentlink => "editkeywords.cgi?action=del&amp;id=%%id%%"
+ }
+ ]
+%]
+
+[% PROCESS admin/table.html.tmpl
+ columns = columns
+ data = keywords
+ footer = footer_row
+%]
+
+<p><a href="editkeywords.cgi?action=add">Add a new keyword</a></p>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/milestones/confirm-delete.html.tmpl b/template/en/default/admin/milestones/confirm-delete.html.tmpl
new file mode 100644
index 000000000..ea89b8021
--- /dev/null
+++ b/template/en/default/admin/milestones/confirm-delete.html.tmpl
@@ -0,0 +1,98 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gavin Shelley <bugzilla@chimpychompy.org>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+
+[%# INTERFACE:
+ # product: object; Bugzilla::Product object representing the product to
+ # which the milestone belongs.
+ # milestone: object; Bugzilla::Milestone object representing the
+ # milestone the user wants to delete.
+ #%]
+
+[% title = BLOCK %]Delete Milestone of Product '[% product.name FILTER html %]'
+ [% END %]
+
+[% PROCESS global/header.html.tmpl
+ title = title
+%]
+
+<table border="1" cellpadding="4" cellspacing="0">
+<tr bgcolor="#6666FF">
+ <th valign="top" align="left">Field</th>
+ <th valign="top" align="left">Value</th>
+</tr>
+<tr>
+ <td valign="top">Milestone:</td>
+ <td valign="top">[% milestone.name FILTER html %]</td>
+</tr>
+<tr>
+ <td valign="top">Milestone of Product:</td>
+ <td valign="top">[% product.name FILTER html %]</td>
+</tr>
+<tr>
+ <td valign="top">[% terms.Bugs %]:</td>
+ <td valign="top">
+[% IF milestone.bug_count %]
+ <a title="List of [% terms.bugs %] targetted at milestone '
+ [% milestone.name FILTER html %]'"
+ href="buglist.cgi?target_milestone=[% milestone.name FILTER url_quote %]&amp;product=
+ [%- product.name FILTER url_quote %]">
+ [% milestone.bug_count FILTER none %]</a>
+[% ELSE %]
+ None
+[% END %]
+ </td>
+</tr>
+</table>
+
+<h2>Confirmation</h2>
+
+[% IF milestone.bug_count %]
+
+ <table border="0" cellpadding="20" width="70%" bgcolor="red">
+ <tr><td>
+ There
+ [% IF milestone.bug_count > 1 %]
+ are [% milestone.bug_count FILTER none %] [%+ terms.bugs %]
+ [% ELSE %]
+ is 1 [% terms.bug %]
+ [% END %]
+ entered for this milestone! When you delete this milestone,
+ <b><blink>ALL</blink></b> of these [% terms.bugs %] will be retargeted
+ to [% product.default_milestone FILTER html %], the default milestone for
+ the [% product.name FILTER html %] product.
+ </td></tr>
+ </table>
+
+[% END %]
+
+<p>Do you really want to delete this milestone?<p>
+
+<form method="post" action="editmilestones.cgi">
+ <input type="submit" id="delete" value="Yes, delete">
+ <input type="hidden" name="action" value="delete">
+ <input type="hidden" name="product" value="[% product.name FILTER html %]">
+ <input type="hidden" name="milestone" value="[% milestone.name FILTER html %]">
+ <input type="hidden" name="token" value="[% token FILTER html %]">
+</form>
+
+[% PROCESS admin/milestones/footer.html.tmpl %]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/milestones/create.html.tmpl b/template/en/default/admin/milestones/create.html.tmpl
new file mode 100644
index 000000000..d7c2f580f
--- /dev/null
+++ b/template/en/default/admin/milestones/create.html.tmpl
@@ -0,0 +1,58 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gavin Shelley <bugzilla@chimpychompy.org>
+ #%]
+
+[%# INTERFACE:
+ # product: object; Bugzilla::Product object representing the product to
+ # which the milestone belongs.
+ #%]
+
+[% title = BLOCK %]Add Milestone to Product '[% product.name FILTER html %]'[% END %]
+[% subheader = BLOCK %]This page allows you to add a new milestone to product
+ '[% product.name FILTER html %]'.[% END %]
+[% PROCESS global/header.html.tmpl
+ title = title
+ subheader = subheader
+ onload = "document.forms['f'].milestone.focus()"
+%]
+
+<form name="f" method="post" action="editmilestones.cgi">
+ <table border="0" cellpadding="4" cellspacing="0">
+ <tr>
+ <th align="right"><label for="milestone">Milestone:</label></th>
+ <td><input id="milestone" size="20" maxlength="20" name="milestone"
+ value=""></td>
+ </tr>
+ <tr>
+ <th align="right"><label for="sortkey">Sortkey:</label></th>
+ <td><input id="sortkey" size="20" maxlength="20" name="sortkey"
+ value=""></td>
+ </tr>
+ </table>
+ <input type="submit" id="create" value="Add">
+ <input type="hidden" name="action" value="new">
+ <input type="hidden" name='product' value="[% product.name FILTER html %]">
+ <input type="hidden" name="token" value="[% token FILTER html %]">
+</form>
+
+[% PROCESS admin/milestones/footer.html.tmpl
+ no_add_milestone_link = 1
+ %]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/milestones/edit.html.tmpl b/template/en/default/admin/milestones/edit.html.tmpl
new file mode 100644
index 000000000..dfe9d1bd8
--- /dev/null
+++ b/template/en/default/admin/milestones/edit.html.tmpl
@@ -0,0 +1,63 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gavin Shelley <bugzilla@chimpychompy.org>
+ #%]
+
+[%# INTERFACE:
+ # product: object; Bugzilla::Product object representing the product to
+ # which the milestone belongs.
+ # milestone: object; Bugzilla::Milestone object representing the
+ # milestone the user wants to edit.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% title = BLOCK %]Edit Milestone '[% milestone.name FILTER html %]' of product '
+ [%- product.name FILTER html %]'[% END %]
+[% PROCESS global/header.html.tmpl
+ title = title
+ onload = "document.forms['f'].milestone.select()"
+%]
+
+<form name="f" method="post" action="editmilestones.cgi">
+ <table border="0" cellpadding="4" cellspacing="0">
+
+ <tr>
+ <th valign="top"><label for="milestone">Milestone:</label></th>
+ <td><input id="milestone" size="20" maxlength="20" name="milestone" value="
+ [%- milestone.name FILTER html %]"></td>
+ </tr>
+ <tr>
+ <th align="right"><label for="sortkey">Sortkey:</label></th>
+ <td><input id="sortkey" size="20" maxlength="20" name="sortkey" value="
+ [%- milestone.sortkey FILTER html %]"></td>
+ </tr>
+
+ </table>
+
+ <input type="hidden" name="milestoneold" value="[% milestone.name FILTER html %]">
+ <input type="hidden" name="action" value="update">
+ <input type="hidden" name="product" value="[% product.name FILTER html %]">
+ <input type="submit" id="update" value="Save Changes">
+ <input type="hidden" name="token" value="[% token FILTER html %]">
+</form>
+
+[% PROCESS admin/milestones/footer.html.tmpl
+ no_edit_milestone_link = 1 %]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/milestones/footer.html.tmpl b/template/en/default/admin/milestones/footer.html.tmpl
new file mode 100644
index 000000000..e91e5f9ad
--- /dev/null
+++ b/template/en/default/admin/milestones/footer.html.tmpl
@@ -0,0 +1,67 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gavin Shelley <bugzilla@chimpychompy.org>
+ #%]
+
+[%# INTERFACE:
+ # product: object; Bugzilla::Product object representing the product to
+ # which the milestone belongs.
+ #
+ # milestone: object; Bugzilla::Milestone object representing the
+ # milestone.
+ #
+ # no_XXX_link: boolean; if defined, then don't show the corresponding
+ # link. Supported parameters are:
+ #
+ # no_edit_milestone_link
+ # no_edit_other_milestones_link
+ # no_add_milestone_link
+ #%]
+
+<hr>
+
+<p>
+
+[% UNLESS no_add_milestone_link %]
+ <a title="Add a milestone to product '[% product.name FILTER html %]'"
+ href="editmilestones.cgi?action=add&amp;product=
+ [%- product.name FILTER url_quote %]">Add</a> a milestone.
+[% END %]
+
+[% IF milestone.name && !no_edit_milestone_link %]
+ Edit milestone <a
+ title="Edit Milestone '[% milestone.name FILTER html %]' of product '
+ [%- product.name FILTER html %]'"
+ href="editmilestones.cgi?action=edit&amp;product=
+ [%- product.name FILTER url_quote %]&amp;milestone=
+ [%- milestone.name FILTER url_quote %]">
+ '[% milestone.name FILTER html %]'</a>.
+[% END %]
+
+[% UNLESS no_edit_other_milestones_link %]
+ Edit other milestones of product <a
+ href="editmilestones.cgi?product=
+ [%- product.name FILTER url_quote %]">'[% product.name FILTER html %]'</a>.
+
+[% END %]
+
+ Edit product <a
+ href="editproducts.cgi?action=edit&amp;product=
+ [%- product.name FILTER url_quote %]">'[% product.name FILTER html %]'</a>.
+
+</p>
diff --git a/template/en/default/admin/milestones/list.html.tmpl b/template/en/default/admin/milestones/list.html.tmpl
new file mode 100644
index 000000000..9422855ac
--- /dev/null
+++ b/template/en/default/admin/milestones/list.html.tmpl
@@ -0,0 +1,108 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gavin Shelley <bugzilla@chimpychompy.org>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+
+[%# INTERFACE:
+ # product: object; Bugzilla::Product object representing the product to
+ # which the milestones belongs.
+ # showbugcounts: if defined, then bug counts should be included in the table
+ #%]
+
+[% USE Bugzilla %]
+[% cgi = Bugzilla.cgi %]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% title = BLOCK %]Select milestone of product
+ '[% product.name FILTER html %]'[% END %]
+[% PROCESS global/header.html.tmpl
+ title = title
+%]
+
+[% edit_contentlink = BLOCK %]editmilestones.cgi?action=edit&amp;product=
+ [%- product.name FILTER url_quote %]&amp;milestone=%%name%%[% END %]
+[% delete_contentlink = BLOCK %]editmilestones.cgi?action=del&amp;product=
+ [%- product.name FILTER url_quote %]&amp;milestone=%%name%%[% END %]
+[% bug_count_contentlink = BLOCK %]buglist.cgi?target_milestone=%%name%%&amp;product=
+ [%- product.name FILTER url_quote %][% END %]
+
+
+[% columns = [
+ {
+ name => "name"
+ heading => "Edit milestone..."
+ contentlink => edit_contentlink
+ },
+ {
+ name => "sortkey"
+ heading => "Sortkey"
+ }
+ ]
+%]
+
+[% IF showbugcounts %]
+
+ [% columns.push({
+ name => "bug_count"
+ heading => "$terms.Bugs"
+ align => "right"
+ contentlink => bug_count_contentlink
+ })
+ %]
+
+[% END %]
+
+[% columns.push({
+ name => "action"
+ heading => "Action"
+ content => "Delete"
+ contentlink => delete_contentlink
+ })
+%]
+
+[%# We want to override the usual 'Delete' link for the default milestone %]
+[% overrides.action.name.${product.default_milestone} = {
+ override_content => 1
+ content => "(Default milestone)"
+ override_contentlink => 1
+ contentlink => undef
+ }
+%]
+
+[% Hook.process('before_table') %]
+
+[% PROCESS admin/table.html.tmpl
+ columns = columns
+ data = product.milestones
+ overrides = overrides
+%]
+
+[% IF ! showbugcounts %]
+
+ <p><a href="editmilestones.cgi?product=[% product.name FILTER url_quote %]&amp;showbugcounts=1">
+ Redisplay table with [% terms.bug %] counts (slower)</a></p>
+
+[% END %]
+
+[% PROCESS admin/milestones/footer.html.tmpl
+ no_edit_other_milestones_link = 1
+%]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/milestones/select-product.html.tmpl b/template/en/default/admin/milestones/select-product.html.tmpl
new file mode 100644
index 000000000..587db6d65
--- /dev/null
+++ b/template/en/default/admin/milestones/select-product.html.tmpl
@@ -0,0 +1,70 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gavin Shelley <bugzilla@chimpychompy.org>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ #
+ #%]
+
+[%# INTERFACE:
+ # products: array of product objects
+ # showbugcounts: if defined, then bug counts should be included in the table
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% PROCESS global/header.html.tmpl
+ title = "Edit milestones for which product?"
+%]
+
+[% columns = [
+ {
+ name => "name"
+ heading => "Edit milestones of..."
+ contentlink => "editmilestones.cgi?product=%%name%%"
+ },
+ {
+ name => "description"
+ heading => "Description"
+ allow_html_content => 1
+ }
+ ]
+%]
+
+[% IF showbugcounts %]
+
+ [% columns.push({
+ name => 'bug_count'
+ heading => "$terms.Bugs"
+ align => "right"
+ contentlink => "buglist.cgi?product=%%name%%"
+ })
+ %]
+
+[% END %]
+
+[% PROCESS admin/table.html.tmpl
+ columns = columns
+ data = products
+%]
+
+[% IF !showbugcounts %]
+ <p><a href="editmilestones.cgi?showbugcounts=1">
+ Redisplay table with [% terms.bug %] counts (slower)</a></p>
+[% END %]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/params/admin.html.tmpl b/template/en/default/admin/params/admin.html.tmpl
new file mode 100644
index 000000000..dd83ebb2e
--- /dev/null
+++ b/template/en/default/admin/params/admin.html.tmpl
@@ -0,0 +1,41 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Dave Miller <justdave@bugzilla.org>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+[%
+ title = "Administrative Policies"
+ desc = "Set up account policies"
+%]
+
+[% param_descs = {
+ allowbugdeletion => "The pages to edit products and components can delete all " _
+ "associated $terms.bugs when you delete a product (or component). " _
+ "Since that is a pretty scary idea, you have to turn on " _
+ "this option before any such deletions will ever happen.",
+
+ allowemailchange => "Users can change their own email address through the preferences. " _
+ "Note that the change is validated by emailing both addresses, so " _
+ "switching this option on will not let users use an invalid address.",
+
+ allowuserdeletion => "The user editing pages are capable of letting you delete user accounts. " _
+ "$terms.Bugzilla will issue a warning in case you'd run into inconsistencies " _
+ "when you're about to do so, but such deletions remain kinda scary. " _
+ "So, you have to turn on this option before any such deletions " _
+ "will ever happen." }
+%] \ No newline at end of file
diff --git a/template/en/default/admin/params/advanced.html.tmpl b/template/en/default/admin/params/advanced.html.tmpl
new file mode 100644
index 000000000..a8e8a297b
--- /dev/null
+++ b/template/en/default/admin/params/advanced.html.tmpl
@@ -0,0 +1,81 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Dave Miller <justdave@bugzilla.org>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+
+[%
+ title = "Advanced"
+ desc = "Settings for advanced configurations."
+%]
+
+[% sts_desc = BLOCK %]
+ Enables the sending of the
+ <a href="http://en.wikipedia.org/wiki/Strict_Transport_Security">Strict-Transport-Security</a>
+ header along with HTTP responses on SSL connections. This adds greater
+ security to your SSL connections by forcing the browser to always
+ access your domain over SSL and never accept an invalid certificate.
+ However, it should only be used if you have the <code>ssl_redirect</code>
+ parameter turned on, [% terms.Bugzilla %] is the only thing running
+ on its domain (i.e., your <code>urlbase</code> is something like
+ <code>http://bugzilla.example.com/</code>), and you never plan to disable
+ the <code>ssl_redirect</code> parameter.
+ <ul>
+ <li>
+ off - Don't send the Strict-Transport-Security header with requests.
+ </li>
+ <li>
+ this_domain_only - Send the Strict-Transport-Security header with all
+ requests, but only support it for the current domain.
+ </li>
+ <li>
+ include_subdomains - Send the Strict-Transport-Security header along
+ with the <code>includeSubDomains</code> flag, which will apply the
+ security change to all subdomains. This is especially useful when
+ combined with an <code>attachment_base</code> that exists as (a)
+ subdomain(s) under the main [% terms.Bugzilla %] domain.
+ </li>
+ </ul>
+[% END %]
+
+[% param_descs = {
+ cookiedomain =>
+ "If your website is at 'www.foo.com', setting this to"
+ _ " '.foo.com' will also allow 'bar.foo.com' to access"
+ _ " $terms.Bugzilla cookies. This is useful if you have more than"
+ _ " one hostname pointing at the same web server, and you"
+ _ " want them to share the $terms.Bugzilla cookie.",
+
+ inbound_proxies =>
+ "When inbound traffic to $terms.Bugzilla goes through a proxy,"
+ _ " $terms.Bugzilla thinks that the IP address of every single"
+ _ " user is the IP address of the proxy. If you enter a comma-separated"
+ _ " list of IPs in this parameter, then $terms.Bugzilla will trust any"
+ _ " <code>X-Forwarded-For</code> header sent from those IPs,"
+ _ " and use the value of that header as the end user's IP address.",
+
+ proxy_url =>
+ "$terms.Bugzilla may have to access the web to get notifications about"
+ _ " new releases (see the <tt>upgrade_notification</tt> parameter)."
+ _ " If your $terms.Bugzilla server is behind a proxy, it may be"
+ _ " necessary to enter its URL if the web server cannot access the"
+ _ " HTTP_PROXY environment variable. If you have to authenticate,"
+ _ " use the <code>http://user:pass@proxy_url/</code> syntax.",
+
+ strict_transport_security => sts_desc,
+} %]
diff --git a/template/en/default/admin/params/attachment.html.tmpl b/template/en/default/admin/params/attachment.html.tmpl
new file mode 100644
index 000000000..12fd491fc
--- /dev/null
+++ b/template/en/default/admin/params/attachment.html.tmpl
@@ -0,0 +1,76 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Dave Miller <justdave@bugzilla.org>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+[%
+ title = "Attachments"
+ desc = "Set up attachment options"
+%]
+
+[% param_descs = {
+ allow_attachment_display =>
+ "If this option is on, users will be able to view attachments from"
+ _ " their browser, if their browser supports the attachment's MIME type."
+ _ " If this option is off, users are forced to download attachments,"
+ _ " even if the browser is able to display them."
+ _ "<p>This is a security restriction for installations where untrusted"
+ _ " users may upload attachments that could be potentially damaging if"
+ _ " viewed directly in the browser.</p>"
+ _ "<p>It is highly recommended that you set the <tt>attachment_base</tt>"
+ _ " parameter if you turn this parameter on.",
+
+ attachment_base =>
+ "When the <tt>allow_attachment_display</tt> parameter is on, it is "
+ _ " possible for a malicious attachment to steal your cookies or"
+ _ " perform an attack on $terms.Bugzilla using your credentials."
+ _ "<p>If you would like additional security on attachments to avoid"
+ _ " this, set this parameter to an alternate URL for your $terms.Bugzilla"
+ _ " that is not the same as <tt>urlbase</tt> or <tt>sslbase</tt>."
+ _ " That is, a different domain name that resolves to this exact"
+ _ " same $terms.Bugzilla installation.</p>"
+ _ "<p>Note that if you have set the"
+ _ " <a href=\"editparams.cgi?section=advanced#cookiedomain_desc\"><tt>cookiedomain</tt>"
+ _" parameter</a>, you should set <tt>attachment_base</tt> to use a"
+ _ " domain that would <em>not</em> be matched by"
+ _ " <tt>cookiedomain</tt>.</p>"
+ _ "<p>For added security, you can insert <tt>%bugid%</tt> into the URL,"
+ _ " which will be replaced with the ID of the current $terms.bug that"
+ _ " the attachment is on, when you access an attachment. This will limit"
+ _ " attachments to accessing only other attachments on the same"
+ _ " ${terms.bug}. Remember, though, that all those possible domain names "
+ _ " (such as <tt>1234.your.domain.com</tt>) must point to this same"
+ _ " $terms.Bugzilla instance.",
+
+ allow_attachment_deletion => "If this option is on, administrators will be able to delete " _
+ "the content of attachments.",
+
+ allow_attach_url => "If this option is on, it will be possible to " _
+ "specify a URL when creating an attachment and " _
+ "treat the URL itself as if it were an attachment.",
+
+ maxattachmentsize => "The maximum size (in kilobytes) of attachments. " _
+ "$terms.Bugzilla will not accept attachments greater than this number " _
+ "of kilobytes in size. Setting this parameter to 0 will prevent " _
+ "attaching files to ${terms.bugs}.",
+
+ maxlocalattachment => "The maximum size (in megabytes) of attachments identified by " _
+ "the user as 'Big Files' to be stored locally on the webserver. " _
+ "If set to zero, attachments will never be kept on the local " _
+ "filesystem." }
+%]
diff --git a/template/en/default/admin/params/auth.html.tmpl b/template/en/default/admin/params/auth.html.tmpl
new file mode 100644
index 000000000..35bddf1af
--- /dev/null
+++ b/template/en/default/admin/params/auth.html.tmpl
@@ -0,0 +1,129 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Dave Miller <justdave@bugzilla.org>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ # Marc Schumann <wurblzap@gmail.com>
+ #%]
+[%
+ title = "User Authentication"
+ desc = "Set up your authentication policies"
+%]
+
+[% param_descs = {
+ auth_env_id => "Environment variable used by external authentication system " _
+ "to store a unique identifier for each user. Leave it blank " _
+ "if there isn't one or if this method of authentication " _
+ "is not being used.",
+
+ auth_env_email => "Environment variable used by external authentication system " _
+ "to store each user's email address. This is a required " _
+ "field for environmental authentication. Leave it blank " _
+ "if you are not going to use this feature.",
+
+ auth_env_realname => "Environment variable used by external authentication system " _
+ "to store the user's real name. Leave it blank if there " _
+ "isn't one or if this method of authentication is not being " _
+ "used.",
+
+ user_info_class => "Mechanism(s) to be used for gathering a user's login information.
+ More than one may be selected. If the first one returns nothing,
+ the second is tried, and so on.<br>
+ The types are:
+ <dl>
+ <dt>CGI</dt>
+ <dd>
+ Asks for username and password via CGI form interface.
+ </dd>
+ <dt>Env</dt>
+ <dd>
+ Info for a pre-authenticated user is passed in system
+ environment variables.
+ </dd>
+ </dl>",
+
+ user_verify_class => "Mechanism(s) to be used for verifying (authenticating) information
+ gathered by user_info_class.
+ More than one may be selected. If the first one cannot find the
+ user, the second is tried, and so on.<br>
+ The types are:
+ <dl>
+ <dt>DB</dt>
+ <dd>
+ ${terms.Bugzilla}'s built-in authentication. This is the most common
+ choice.
+ </dd>
+ <dt>RADIUS</dt>
+ <dd>
+ RADIUS authentication using a RADIUS server.
+ Please see the $terms.Bugzilla documentation for
+ more information.
+ Using this method requires
+ <a href=\"?section=radius\">additional
+ parameters</a> to be set.
+ </dd>
+ <dt>LDAP</dt>
+ <dd>
+ LDAP authentication using an LDAP server.
+ Please see the $terms.Bugzilla documentation
+ for more information. Using this method requires
+ <a href=\"?section=ldap\">additional
+ parameters</a> to be set.
+ </dd>
+ </dl>",
+
+ rememberlogin => "Controls management of session cookies
+ <ul>
+ <li>
+ on - Session cookies never expire (the user has to login only
+ once per browser).
+ </li>
+ <li>
+ off - Session cookies last until the users session ends (the user
+ will have to login in each new browser session).
+ </li>
+ <li>
+ defaulton/defaultoff - Default behavior as described
+ above, but user can choose whether $terms.Bugzilla will remember his
+ login or not.
+ </li>
+ </ul>",
+
+ requirelogin => "If this option is set, all access to the system beyond the " _
+ "front page will require a login. No anonymous users will " _
+ "be permitted.",
+
+ emailregexp => "This defines the regexp to use for legal email addresses. The " _
+ "default tries to match fully qualified email addresses. Another " _
+ "popular value to put here is <tt>^[^@]+$</tt>, which means " _
+ "'local usernames, no @ allowed.'",
+
+ emailregexpdesc => "This describes in English words what kinds of legal addresses " _
+ "are allowed by the <tt>emailregexp</tt> param.",
+
+ emailsuffix => "This is a string to append to any email addresses when actually " _
+ "sending mail to that address. It is useful if you have changed " _
+ "the <tt>emailregexp</tt> param to only allow local usernames, " _
+ "but you want the mail to be delivered to username@my.local.hostname.",
+
+ createemailregexp => "This defines the regexp to use for email addresses that are " _
+ "permitted to self-register using a 'New Account' feature. The " _
+ "default (.*) permits any account matching the emailregexp " _
+ "to be created. If this parameter is left blank, no users " _
+ "will be permitted to create their own accounts and all accounts " _
+ "will have to be created by an administrator." }
+%]
diff --git a/template/en/default/admin/params/bugchange.html.tmpl b/template/en/default/admin/params/bugchange.html.tmpl
new file mode 100644
index 000000000..15d4f1e29
--- /dev/null
+++ b/template/en/default/admin/params/bugchange.html.tmpl
@@ -0,0 +1,57 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Dave Miller <justdave@bugzilla.org>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+[%
+ title = "$terms.Bug Change Policies"
+ desc = "Set up $terms.bug change policies"
+%]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+[% accept_status = display_value('bug_status', 'IN_PROGRESS') FILTER html %]
+
+[% param_descs = {
+ duplicate_or_move_bug_status => "When $terms.abug is marked as a duplicate of another one " _
+ "or is moved to another installation, use this $terms.bug status."
+
+ letsubmitterchoosepriority => "If this is on, then people submitting $terms.bugs can " _
+ "choose an initial priority for that ${terms.bug}. " _
+ "If off, then all $terms.bugs initially have the default " _
+ "priority selected below.",
+
+ letsubmitterchoosemilestone => "If this is on, then people submitting $terms.bugs can " _
+ "choose the Target Milestone for that ${terms.bug}. " _
+ "If off, then all $terms.bugs initially have the default " _
+ "milestone for the product being filed in.",
+
+ musthavemilestoneonaccept =>
+ "If you are using ${field_descs.target_milestone}, do you want to require"
+ _ " that the milestone be set in order for a user to set"
+ _ " ${terms.abug}'s status to ${accept_status}?",
+
+ commentonchange_resolution => "If this option is on, the user needs to enter a short " _
+ "comment if the resolution of the $terms.bug changes.",
+
+ commentonduplicate => "If this option is on, the user needs to enter a short comment " _
+ "if the $terms.bug is marked as duplicate.",
+
+ noresolveonopenblockers => "Don\'t allow $terms.bugs to be resolved as fixed " _
+ "if they have unresolved dependencies." }
+%]
diff --git a/template/en/default/admin/params/bugfields.html.tmpl b/template/en/default/admin/params/bugfields.html.tmpl
new file mode 100644
index 000000000..58b08f615
--- /dev/null
+++ b/template/en/default/admin/params/bugfields.html.tmpl
@@ -0,0 +1,61 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Dave Miller <justdave@bugzilla.org>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+[%
+ title = "$terms.Bug Fields"
+ desc = "Choose fields you want to display"
+%]
+
+[% param_descs = {
+ useclassification => "If this is on, $terms.Bugzilla will associate each product with a " _
+ "specific classification. But you must have 'editclassification' " _
+ "permissions enabled in order to edit classifications.",
+
+ usetargetmilestone => "Do you wish to use the Target Milestone field?",
+
+ useqacontact => "Do you wish to use the QA Contact field?",
+
+ usestatuswhiteboard => "Do you wish to use the Status Whiteboard field?",
+
+ usebugaliases => "Do you wish to use $terms.bug aliases, which allow you to assign " _
+ "$terms.bugs an easy-to-remember name by which you can refer to them?",
+
+ use_see_also =>
+ "Do you wish to use the See Also field? It allows you refer to"
+ _ " $terms.bugs in other installations. Even if you disable this field,"
+ _ " $terms.bug relationships (URLs) already set on $terms.bugs will"
+ _ " still appear and can be removed.",
+
+ defaultpriority => "This is the priority that newly entered $terms.bugs are set to.",
+
+ defaultseverity => "This is the severity that newly entered $terms.bugs are set to.",
+
+ defaultplatform => "This is the platform that is preselected on the $terms.bug " _
+ "entry form.<br> " _
+ "You can leave this empty: " _
+ "$terms.Bugzilla will then use the platform that the browser " _
+ "reports to be running on as the default.",
+
+ defaultopsys => "This is the operating system that is preselected on the $terms.bug " _
+ "entry form.<br> " _
+ "You can leave this empty: " _
+ "$terms.Bugzilla will then use the operating system that the browser " _
+ "reports to be running on as the default." }
+%]
diff --git a/template/en/default/admin/params/common.html.tmpl b/template/en/default/admin/params/common.html.tmpl
new file mode 100644
index 000000000..3ec38ca56
--- /dev/null
+++ b/template/en/default/admin/params/common.html.tmpl
@@ -0,0 +1,150 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
+ # Marc Schumann <wurblzap@gmail.com>
+ #%]
+[%# INTERFACE:
+ # panel: hash representing the current panel.
+ #%]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+[% sortlist_separator = '---' %]
+
+<dl>
+ [% FOREACH param = panel.param_list %]
+ <dt id="[% param.name FILTER html %]_desc">[% param.name FILTER html %]</dt>
+ <dd>[% panel.param_descs.${param.name} FILTER none %]
+ <p>
+ [% IF param.type == "t" %]
+ <input type="text" size="80" name="[% param.name FILTER html %]"
+ id="[% param.name FILTER html %]" value="[% Param(param.name) FILTER html %]">
+ [% ELSIF param.type == "p" %]
+ <input type="password" size="80" name="[% param.name FILTER html %]"
+ id="[% param.name FILTER html %]" value="[% Param(param.name) FILTER html %]"
+ autocomplete="off">
+ [% ELSIF param.type == "l" %]
+ <textarea name="[% param.name FILTER html %]" id="[% param.name FILTER html %]"
+ rows="10" cols="80">[% Param(param.name) FILTER html %]</textarea>
+ [% ELSIF param.type == "b" %]
+ <input type="radio" name="[% param.name FILTER html %]" id="[% param.name FILTER html %]-on"
+ value=1 [% "checked=\"checked\"" IF Param(param.name) %]>
+ <label for="[% param.name FILTER html %]-on">On</label>
+ <input type="radio" name="[% param.name FILTER html %]" id="[% param.name FILTER html %]-off"
+ value=0 [% "checked=\"checked\"" IF !Param(param.name) %]>
+ <label for="[% param.name FILTER html %]-off">Off</label>
+ [% ELSIF param.type == "m" %]
+ [% boxSize = 5 %]
+ [% boxSize = param.choices.size IF param.choices.size < 5 %]
+
+ <select multiple="multiple" size="[% boxSize FILTER html %]"
+ name="[% param.name FILTER html %]" id="[% param.name FILTER html %]">
+ [% FOREACH item = param.choices %]
+ <option value="[% item FILTER html %]"
+ [% " selected=\"selected\"" IF lsearch(Param(param.name), item) != -1 %]>
+ [% item FILTER html %]
+ </option>
+ [% END %]
+ </select>
+ [% ELSIF param.type == "o" %]
+ <script type="text/javascript"><!--
+ document.write("<span style=\"display: none\">");
+ // -->
+ </script>
+ <input id="input_[% param.name FILTER html %]" size="80"
+ name="[% param.name FILTER html %]"
+ value="[% Param(param.name) FILTER html %]"><br>
+ <script type="text/javascript"><!--
+ document.write("<\/span>");
+ // -->
+ </script>
+ [% boxSize = 7 %]
+ [% boxSize = 3 + param.choices.size IF param.choices.size < 7 %]
+ [% plist = Param(param.name).split(',') %]
+
+ <script type="text/javascript"><!--
+ document.write(
+ '<table>' +
+ ' <tr>' +
+ ' <td rowspan="2">' +
+ ' <select id="select_[% param.name FILTER html %]"' +
+ ' size="[% boxSize FILTER html %]"' +
+ ' name="select_[% param.name FILTER html %]">' +
+ [% FOREACH item = plist %]
+ ' <option value="[% item FILTER html %]">[% item FILTER html %]<\/option>' +
+ [% END %]
+ ' <option class="sortlist_separator"' +
+ ' disabled="disabled"' +
+ ' value="[% sortlist_separator %]">active&uarr;&nbsp;&darr;inactive<\/option>' +
+ [% FOREACH item = param.choices %]
+ [% IF lsearch(plist, item) == -1 %]
+ ' <option value="[% item FILTER html %]">[% item FILTER html %]<\/option>' +
+ [% END %]
+ [% END %]
+ ' <\/select>' +
+ ' <\/td>' +
+ ' <td style="vertical-align: bottom">' +
+ ' <button type="button"' +
+ ' onClick="sortedList_moveItem(\'[% param.name FILTER html %]\', -1, \'[% sortlist_separator %]\');">&uarr;<\/button>' +
+ ' <\/td>' +
+ ' <\/tr>' +
+ ' <tr>' +
+ ' <td style="vertical-align: top">' +
+ ' <button type="button"' +
+ ' onClick="sortedList_moveItem(\'[% param.name FILTER html %]\', +1, \'[% sortlist_separator %]\');">&darr;<\/button>' +
+ ' <\/td>' +
+ ' <\/tr>' +
+ '<\/table>');
+ // -->
+ </script>
+ [% ELSIF param.type == "s" %]
+ <select name="[% param.name FILTER html %]" id="[% param.name FILTER html %]">
+ [% FOREACH item = param.choices %]
+ <option value="[% item FILTER html %]"
+ [% " selected=\"selected\"" IF item == Param(param.name) %]>
+ [% IF param.name == "defaultseverity" %]
+ [% display_value("bug_severity", item) FILTER html %]
+ [% ELSIF param.name == "defaultplatform" %]
+ [% display_value("rep_platform", item) FILTER html %]
+ [% ELSIF param.name == "defaultopsys" %]
+ [% display_value("op_sys", item) FILTER html %]
+ [% ELSIF param.name == "duplicate_or_move_bug_status" %]
+ [% display_value("bug_status", item) FILTER html %]
+ [% ELSE %]
+ [% item FILTER html %]
+ [% END %]
+ </option>
+ [% END %]
+ </select>
+ [% ELSE %]
+ <font color="red">
+ <blink>Unknown param type [% param.type FILTER html %]!!!</blink>
+ </font>
+ [% END %]
+ </p>
+ [% UNLESS param.no_reset %]
+ <p>
+ <input type="checkbox" name="reset-[% param.name FILTER html %]"
+ id="reset-[% param.name FILTER html %]">
+ <label for="reset-[% param.name FILTER html %]">Reset</label>
+ </p>
+ [% END %]
+ <hr>
+ </dd>
+ [% END %]
+</dl>
diff --git a/template/en/default/admin/params/core.html.tmpl b/template/en/default/admin/params/core.html.tmpl
new file mode 100644
index 000000000..b1578f422
--- /dev/null
+++ b/template/en/default/admin/params/core.html.tmpl
@@ -0,0 +1,48 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Dave Miller <justdave@bugzilla.org>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+
+[%
+ title = "Required Settings"
+ desc = "Settings that are required for proper operation of $terms.Bugzilla"
+%]
+
+[% param_descs = {
+ urlbase => "The URL that is the common initial leading part of all $terms.Bugzilla " _
+ "URLs.",
+
+ sslbase => "The URL that is the common initial leading part of all HTTPS " _
+ "(SSL) $terms.Bugzilla URLs.",
+
+ ssl_redirect =>
+ "When this is enabled, $terms.Bugzilla will ensure that every page is"
+ _ " accessed over SSL, by redirecting any plain HTTP requests to HTTPS"
+ _ " using the <tt>sslbase</tt> parameter. Also, when this is enabled,"
+ _ " $terms.Bugzilla will send out links using <tt>sslbase</tt> in emails"
+ _ " instead of <tt>urlbase</tt>.",
+
+ cookiepath => "Path, relative to your web document root, to which to restrict " _
+ "$terms.Bugzilla cookies. Normally this is the URI portion of your URL " _
+ "base. Begin with a / (single slash mark). For instance, if " _
+ "$terms.Bugzilla serves from 'http://www.somedomain.com/bugzilla/', set " _
+ "this parameter to /bugzilla/. Setting it to / will allow " _
+ "all sites served by this web server or virtual host to read " _
+ "$terms.Bugzilla cookies.",
+} %]
diff --git a/template/en/default/admin/params/dependencygraph.html.tmpl b/template/en/default/admin/params/dependencygraph.html.tmpl
new file mode 100644
index 000000000..4cf22d508
--- /dev/null
+++ b/template/en/default/admin/params/dependencygraph.html.tmpl
@@ -0,0 +1,49 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Dave Miller <justdave@bugzilla.org>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+[%
+ title = "Dependency Graphs"
+ desc = "Optional setup for dependency graphing"
+%]
+
+[% param_descs = {
+ webdotbase => "It is possible to show graphs of dependent ${terms.bugs}. You may set
+ this parameter to any of the following:
+ <ul>
+ <li>
+ A complete file path to 'dot' (part of
+ <a href=\"http://www.graphviz.org\">GraphViz</a>) will
+ generate the graphs locally.
+ </li>
+ <li>
+ A URL prefix pointing to an installation of the
+ <a href=\"http://www.research.att.com/~north/cgi-bin/webdot.cgi\">webdot
+ package</a> will generate the graphs remotely.
+ </li>
+ <li>
+ A blank value will disable dependency graphing.
+ </li>
+ </ul>
+ The default value is a publicly-accessible webdot server. If you change
+ this value, make certain that the webdot server can read files from your
+ webdot directory. On Apache you do this by editing the .htaccess file,
+ for other systems the needed measures may vary. You can run <kbd>checksetup.pl</kbd>
+ to recreate the .htaccess file if it has been lost."}
+%]
diff --git a/template/en/default/admin/params/editparams.html.tmpl b/template/en/default/admin/params/editparams.html.tmpl
new file mode 100644
index 000000000..21fa9fa41
--- /dev/null
+++ b/template/en/default/admin/params/editparams.html.tmpl
@@ -0,0 +1,121 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Dave Miller <justdave@bugzilla.org>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+[%# INTERFACE:
+ # panels: array of hashes representing the panels available.
+ # param_changed: array of parameters which have been changed.
+ # shutdown_is_active: boolean; is true when 'shutdownhtml' has been turned on.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% curpanel = -1 %]
+[% panels = panels.nsort('sortkey') %]
+
+[% FOREACH panel = panels %]
+ [% PROCESS "admin/params/${panel.name}.html.tmpl"
+ params = panel.param_list %]
+ [% panel.title = title %]
+ [% panel.desc = desc %]
+ [% panel.param_descs = param_descs %]
+ [% IF panel.current %][% curpanel = loop.index %][% END %]
+[% END %]
+
+[% current_panel = panels.$curpanel %]
+
+[%# We cannot call header.html.tmpl earlier as we have to know
+ which panel is active first, in order to get its title %]
+
+[% title = BLOCK %]
+ [% IF curpanel == -1 %]
+ Parameters: Index
+ [% ELSE %]
+ Configuration:
+ [%+ current_panel.title FILTER html %]
+ [% END %]
+[% END %]
+
+[% PROCESS global/header.html.tmpl
+ title = title
+ message = message
+ style_urls = ['skins/standard/params.css']
+ javascript_urls = ['js/params.js']
+ doc_section = "parameters.html"
+%]
+
+<table border="0" width="100%">
+ <tr>
+ <td>
+ [%# NAVIGATION BAR %]
+ <table id="menu">
+ <tr>
+ <td class="index">
+ <a href="editparams.cgi?section=index" title="Show all parameters">Index</a>
+ </td>
+ </tr>
+ [% FOREACH panel = panels %]
+ <tr>
+ [% IF panel.current %]
+ <td class="selected_section">
+ <span title="[% panel.desc FILTER html %]">[% panel.title FILTER html %]</span>
+ </td>
+ [% ELSE %]
+ <td>
+ <a href="editparams.cgi?section=[% panel.name FILTER url_quote %]"
+ title="[% panel.desc FILTER html %]">[% panel.title FILTER html %]</a>
+ </td>
+ [% END %]
+ </tr>
+ [% END %]
+ </table>
+ </td>
+ <td>
+ [% IF curpanel == -1 %]
+ [% PROCESS admin/params/index.html.tmpl panels = panels %]
+ [% ELSE %]
+
+ <div class="contribute"><strong>Note:</strong>
+ [%+ terms.Bugzilla %] is developed entirely by volunteers. The
+ best way to give back to the [% terms.Bugzilla %] project is
+ to <a href="http://www.bugzilla.org/contribute/">contribute</a>
+ yourself! You don't have to be a programmer to contribute, there are
+ lots of things that we need.
+ </div>
+
+ <p>
+ This lets you edit the basic operating parameters of [% terms.Bugzilla %].
+ Be careful!<br>
+ Any item you check "Reset" on will get reset to its default value.
+ </p>
+
+ [%# CONTENT PANEL %]
+ <form method="post" action="editparams.cgi">
+ [% PROCESS admin/params/common.html.tmpl panel = current_panel %]
+ <input type="hidden" name="section" value="[% current_panel.name FILTER html %]">
+ <input type="hidden" name="action" value="save">
+ <input type="hidden" name="token" value="[% token FILTER html %]">
+ <input type="submit" name="action" value="Save Changes">
+ </form>
+ [% END %]
+ </td>
+ </tr>
+</table>
+
+[% INCLUDE global/footer.html.tmpl %]
diff --git a/template/en/default/admin/params/general.html.tmpl b/template/en/default/admin/params/general.html.tmpl
new file mode 100644
index 000000000..c19cf1407
--- /dev/null
+++ b/template/en/default/admin/params/general.html.tmpl
@@ -0,0 +1,86 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Dave Miller <justdave@bugzilla.org>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+
+[%
+ title = "General"
+ desc = "Miscellaneous general settings that are not required."
+%]
+
+[% param_descs = {
+ maintainer =>
+ "The email address of the person who maintains this installation "
+ _ " of ${terms.Bugzilla}.",
+
+ docs_urlbase =>
+ "The URL that is the common initial leading part of all"
+ _ " $terms.Bugzilla documentation URLs. It may be an absolute URL,"
+ _ " or a URL relative to the <tt>urlbase</tt> parameter. Leave this"
+ _ " empty to suppress links to the documentation."
+ _ "'%lang%' will be replaced by user's preferred language (if"
+ _ " documentation is available in that language).",
+
+ utf8 =>
+ "Use UTF-8 (Unicode) encoding for all text in ${terms.Bugzilla}. New"
+ _ " installations should set this to true to avoid character encoding"
+ _ " problems. <strong>Existing databases should set this to true"
+ _ " only after the data has been converted from existing legacy"
+ _ " character encodings to UTF-8, using the <kbd>contrib/recode.pl</kbd>"
+ _ " script</strong>."
+ _ " <p>Note that if you turn this parameter from &quot;off&quot; to"
+ _ " &quot;on&quot;, you must re-run <kbd>checksetup.pl</kbd> immediately"
+ _ " afterward.</p>",
+
+ shutdownhtml =>
+ "If this field is non-empty, then $terms.Bugzilla will be completely"
+ _ " disabled and this text will be displayed instead of all the"
+ _ " $terms.Bugzilla pages.",
+
+ announcehtml =>
+ "If this field is non-empty, then $terms.Bugzilla will"
+ _ " display whatever is in this field at the top of every"
+ _ " HTML page. The HTML you put in this field is not wrapped or"
+ _ " enclosed in anything. You might want to wrap it inside a"
+ _ "<tt>&lt;div&gt;</tt>. Give the div <em>id=\"message\"</em> to get"
+ _ " green text inside a red box, or <em>class=\"bz_private\"</em> for"
+ _ " dark red on a red background. Anything defined in "
+ _ " <tt>skins/standard/global.css</tt> or <tt>skins/custom/global.css</tt>"
+ _ " will work. To get centered text, use <em>style=\"text-align: "
+ _ " center;\"</em>.",
+
+ upgrade_notification =>
+ "$terms.Bugzilla can inform you when a new release is available."
+ _ " The notification will appear on the $terms.Bugzilla homepage,"
+ _ " for administrators only."
+ _ " <ul><li>'development_snapshot' notifies you about the development "
+ _ " snapshot that has been released.</li>"
+ _ " <li>'latest_stable_release' notifies you about the most recent"
+ _ " release available on the most recent stable branch. This branch"
+ _ " may be different from the branch your installation is based on.</li>"
+ _ " <li>'stable_branch_release' notifies you only about new releases"
+ _ " corresponding to the branch your installation is based on."
+ _ " If you are running a release candidate, you will get a notification"
+ _ " for newer release candidates too.</li>"
+ _ " <li>'disabled' will never notify you about new releases and no"
+ _ " connection will be established to a remote server.</li></ul>"
+ _ " <p>Note that if your $terms.Bugzilla server requires a proxy to"
+ _ " access the Internet, you may also need to set the <tt>proxy_url</tt>"
+ _ " parameter in the Advanced section.</p>",
+} %]
diff --git a/template/en/default/admin/params/groupsecurity.html.tmpl b/template/en/default/admin/params/groupsecurity.html.tmpl
new file mode 100644
index 000000000..ab39a9149
--- /dev/null
+++ b/template/en/default/admin/params/groupsecurity.html.tmpl
@@ -0,0 +1,56 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Dave Miller <justdave@bugzilla.org>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+[%
+ title = "Group Security"
+ desc = "Decide how you will use Security Groups"
+%]
+
+[% param_descs = {
+ makeproductgroups => "If this is on, $terms.Bugzilla will associate $terms.abug group " _
+ "with each product in the database, and use it for querying ${terms.bugs}.",
+
+ chartgroup => "The name of the group of users who can use the 'New Charts' " _
+ "feature. Administrators should ensure that the public categories " _
+ "and series definitions do not divulge confidential information " _
+ "before enabling this for an untrusted population. If left blank, " _
+ "no users will be able to use New Charts.",
+
+ insidergroup => "The name of the group of users who can see/change private " _
+ "comments and attachments.",
+
+ timetrackinggroup => "The name of the group of users who can see/change time tracking " _
+ "information.",
+
+ querysharegroup => "The name of the group of users who can share their " _
+ "saved searches with others.",
+
+ usevisibilitygroups => "Do you wish to restrict visibility of users to members of " _
+ "specific groups?",
+
+ strict_isolation => "Don't allow users to be assigned to, " _
+ "be qa-contacts on, " _
+ "be added to CC list, " _
+ "or make or remove dependencies " _
+ "involving any bug that is in a product on which that " _
+ "user is forbidden to edit.",
+
+ }
+%]
diff --git a/template/en/default/admin/params/index.html.tmpl b/template/en/default/admin/params/index.html.tmpl
new file mode 100644
index 000000000..9f8024528
--- /dev/null
+++ b/template/en/default/admin/params/index.html.tmpl
@@ -0,0 +1,51 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
+ # Nitish Bezzala <nbezzala@yahoo.com>
+ #%]
+
+<p>
+ All parameters are displayed below, per section.
+ If you cannot find one from here, then the parameter does not exist.
+</p>
+
+<div align="center">
+ <table>
+ <tr>
+ <th>Parameter</th>
+ <th>Section</th>
+ </tr>
+ [% FOREACH panel = panels %]
+ [% FOREACH param = panel.param_list.sort('name') %]
+ <tr>
+ <td>
+ <a href="editparams.cgi?section=
+ [%- panel.name FILTER url_quote %]#[% param.name FILTER url_quote %]_desc">
+ [% param.name FILTER html %]</a>
+ </td>
+ <td>
+ [% panel.title FILTER html %]
+ </td>
+ </tr>
+ [% END %]
+ <tr>
+ <td>&nbsp;</td><td>&nbsp;</td>
+ </tr>
+ [% END %]
+ </table>
+</div>
diff --git a/template/en/default/admin/params/ldap.html.tmpl b/template/en/default/admin/params/ldap.html.tmpl
new file mode 100644
index 000000000..cdc585d74
--- /dev/null
+++ b/template/en/default/admin/params/ldap.html.tmpl
@@ -0,0 +1,58 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Dave Miller <justdave@bugzilla.org>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+[%
+ title = "LDAP"
+ desc = "Configure this first before choosing LDAP as an authentication method"
+%]
+
+[% param_descs = {
+ LDAPserver => "The name (and optionally port) of your LDAP server " _
+ "(e.g. ldap.company.com, or ldap.company.com:portnum). " _
+ "URI syntax can also be used, such as "_
+ "ldaps://ldap.company.com (for a secure connection) or " _
+ "ldapi://%2fvar%2flib%2fldap_sock (for a socket-based " _
+ "local connection. Multiple hostnames or URIs can be comma " _
+ "separated; each will be tried in turn until a connection is " _
+ "established.",
+
+ LDAPstarttls => "Whether to require encrypted communication once a normal " _
+ "LDAP connection is achieved with the server.",
+
+ LDAPbinddn => "If your LDAP server requires that you use a binddn and password " _
+ "instead of binding anonymously, enter it here " _
+ "(e.g. cn=default,cn=user:password). " _
+ "Leave this empty for the normal case of an anonymous bind.",
+
+ LDAPBaseDN => "The BaseDN for authenticating users against " _
+ "(e.g. ou=People,o=Company).",
+
+ LDAPuidattribute => "The name of the attribute containing the user's login name.",
+
+ LDAPmailattribute => "The name of the attribute of a user in your " _
+ "directory that contains the email address, to be " _
+ "used as $terms.Bugzilla username. If this parameter " _
+ "is empty, $terms.Bugzilla will use the LDAP username"_
+ " as the $terms.Bugzilla username. You may also want" _
+ " to set the \"emailsuffix\" parameter, in this case.",
+
+ LDAPfilter => "LDAP filter to AND with the <tt>LDAPuidattribute</tt> for " _
+ "filtering the list of valid users." }
+%]
diff --git a/template/en/default/admin/params/mta.html.tmpl b/template/en/default/admin/params/mta.html.tmpl
new file mode 100644
index 000000000..05c44853e
--- /dev/null
+++ b/template/en/default/admin/params/mta.html.tmpl
@@ -0,0 +1,78 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Dave Miller <justdave@bugzilla.org>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+[%
+ title = "Email"
+ desc = "How will outgoing mail be delivered?"
+%]
+
+[% param_descs = {
+ mail_delivery_method => "Defines how email is sent, or if it is sent at all.<br>
+ <ul>
+ <li>
+ 'Sendmail', 'SMTP' and 'Qmail' are all MTAs.
+ You need to install a third-party sendmail replacement if
+ you want to use sendmail on Windows.
+ </li>
+ <li>
+ 'Test' is useful for debugging: all email is stored
+ in 'data/mailer.testfile' instead of being sent.
+ </li>
+ <li>
+ 'none' will completely disable email. $terms.Bugzilla continues
+ to act as though it is sending mail, but nothing is sent or
+ stored.
+ </li>
+ </ul>",
+
+ mailfrom => "The email address of the $terms.Bugzilla mail daemon. Some email systems " _
+ "require this to be a valid email address.",
+
+ use_mailer_queue => "In a large $terms.Bugzilla installation, updating"
+ _ " $terms.bugs can be very slow, because $terms.Bugzilla sends all"
+ _ " email at once. If you enable this parameter, $terms.Bugzilla will"
+ _ " queue all mail and then send it in the background. This requires"
+ _ " that you have installed certain Perl modules (as listed by"
+ _ " <kbd>checksetup.pl</kbd> for this feature), and that you are"
+ _ " running the <code>jobqueue.pl</code> daemon (otherwise your mail"
+ _ " won't get sent). This affects all mail sent by $terms.Bugzilla,"
+ _ " not just $terms.bug updates.",
+
+ smtpserver => "The SMTP server address (if using SMTP for mail delivery).",
+
+ smtp_username => "The username to pass to the SMTP server for SMTP authentication. " _
+ "Leave this field empty if your SMTP server doesn't require authentication.",
+
+ smtp_password => "The password to pass to the SMTP server for SMTP authentication. " _
+ "This field has no effect if the smtp_username parameter is left empty.",
+
+ smtp_debug => "If enabled, this will print detailed information to your" _
+ " web server's error log about the communication between" _
+ " $terms.Bugzilla and your SMTP server. You can use this to" _
+ " troubleshoot email problems.",
+
+ whinedays => "The number of days that we'll let a $terms.bug sit untouched in a CONFIRMED " _
+ "state before our cronjob will whine at the owner.<br> " _
+ "Set to 0 to disable whining.",
+
+ globalwatchers => "A comma-separated list of users who should receive a " _
+ "copy of every notification mail the system sends." }
+
+%]
diff --git a/template/en/default/admin/params/patchviewer.html.tmpl b/template/en/default/admin/params/patchviewer.html.tmpl
new file mode 100644
index 000000000..389acc1b5
--- /dev/null
+++ b/template/en/default/admin/params/patchviewer.html.tmpl
@@ -0,0 +1,64 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Dave Miller <justdave@bugzilla.org>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+[%
+ title = "Patch Viewer"
+ desc = "Set up third-party applications to run with PatchViewer"
+%]
+
+[% param_descs = {
+ cvsroot => "The <a href=\"http://www.cvshome.org\">CVS</a> root that most " _
+ "users of your system will be using for 'cvs diff'. Used in " _
+ "Patch Viewer ('Diff' option on patches) to figure out where " _
+ "patches are rooted even if users did the 'cvs diff' from " _
+ "different places in the directory structure. (NOTE: if your " _
+ "CVS repository is remote and requires a password, you must " _
+ "either ensure the $terms.Bugzilla user has done a 'cvs login' or " _
+ "specify the password " _
+ "<a href=\"http://www.cvshome.org/docs/manual/cvs_2.html#SEC26\">as " _
+ "part of the CVS root</a>.) Leave this blank if you have no " _
+ "CVS repository.",
+
+ cvsroot_get => "The CVS root $terms.Bugzilla will be using to get patches from. " _
+ "Some installations may want to mirror their CVS repository on " _
+ "the $terms.Bugzilla server or even have it on that same server, and " _
+ "thus the repository can be the local file system (and much " _
+ "faster). Make this the same as cvsroot if you don't " _
+ "understand what this is (if cvsroot is blank, make this blank too).",
+
+ bonsai_url => "The URL to a <a href=\"http://www.mozilla.org/bonsai.html\">Bonsai</a> " _
+ "server containing information about your CVS repository. " _
+ "Patch Viewer will use this information to create links to " _
+ "bonsai's blame for each section of a patch (it will append " _
+ "'/cvsblame.cgi?...' to this url). Leave this blank if you " _
+ "don't understand what this is.",
+
+ lxr_url => "The URL to an <a href=\"http://sourceforge.net/projects/lxr\">LXR</a> server " _
+ "that indexes your CVS repository. Patch Viewer will use this " _
+ "information to create links to LXR for each file in a patch. " _
+ "Leave this blank if you don't understand what this is.",
+
+ lxr_root => "Some LXR installations do not index the CVS repository from the root -- " _
+ "<a href=\"http://lxr.mozilla.org/mozilla\">Mozilla's</a>, for " _
+ "example, starts indexing under <code>mozilla/</code>. This " _
+ "means URLs are relative to that extra path under the root. " _
+ "Enter this if you have a similar situation. Leave it blank " _
+ "if you don't know what this is." }
+%]
diff --git a/template/en/default/admin/params/query.html.tmpl b/template/en/default/admin/params/query.html.tmpl
new file mode 100644
index 000000000..34ea04381
--- /dev/null
+++ b/template/en/default/admin/params/query.html.tmpl
@@ -0,0 +1,58 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Dave Miller <justdave@bugzilla.org>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+[%
+ title = "Query Defaults"
+ desc = "Default options for query and $terms.bug lists"
+%]
+
+[% param_descs = {
+ quip_list_entry_control => "Controls how easily users can add entries to the quip list.
+ <ul>
+ <li>
+ open - Users may freely add to the quip list, and
+ their entries will immediately be available for viewing.
+ </li>
+ <li>
+ moderated - quips can be entered, but need to be approved
+ by an admin before they will be shown.
+ </li>
+ <li>
+ closed - no new additions to the quips list are allowed.
+ </li>
+ </ul>",
+
+ mostfreqthreshold => "The minimum number of duplicates $terms.abug needs to show up on the " _
+ "<a href=\"duplicates.cgi\">most frequently reported $terms.bugs page</a>. " _
+ "If you have a large database and this page takes a long time to " _
+ "load, try increasing this number.",
+
+ mybugstemplate => "This is the URL to use to bring up a simple 'all of my $terms.bugs' " _
+ "list for a user. %userid% will get replaced with the login name of a user.",
+
+ defaultquery => "This is the default query that initially comes up when you " _
+ "access the advanced query page. It's in URL parameter " _
+ "format, which makes it hard to read. Sorry!",
+
+ specific_search_allow_empty_words =>
+ "Whether to allow a search on the 'Simple Search' page with an empty"
+ _ " 'Words' field.",
+
+} %]
diff --git a/template/en/default/admin/params/radius.html.tmpl b/template/en/default/admin/params/radius.html.tmpl
new file mode 100644
index 000000000..f12e581b7
--- /dev/null
+++ b/template/en/default/admin/params/radius.html.tmpl
@@ -0,0 +1,54 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Marc Schumann.
+ # Portions created by Marc Schumann are Copyright (c) 2007 Marc Schumann.
+ # All rights reserved.
+ #
+ # Contributor(s): Marc Schumann <wurblzap@gmail.com>
+ #%]
+[%
+ title = "RADIUS"
+ desc = "Configure this first before choosing RADIUS as an authentication method"
+%]
+
+[% param_descs = {
+ RADIUS_server => "The name (and optionally port) of your RADIUS server " _
+ "(e.g. <code>radius.company.com</code>, or " _
+ "<code>radius.company.com:portnum</code>).<br>" _
+ "Required only if " _
+ "<a href=\"?section=auth#user_verify_class_desc\">the " _
+ "<code>user_verify_class</code> parameter</a> contains " _
+ "<code>RADIUS</code>.",
+
+ RADIUS_secret => "Your RADIUS server's secret.<br>" _
+ "Required only if " _
+ "<a href=\"?section=auth#user_verify_class_desc\">the " _
+ "<code>user_verify_class</code> parameter</a> contains " _
+ "<code>RADIUS</code>.",
+
+ RADIUS_NAS_IP => "The NAS-IP-Address attribute to be used when exchanging " _
+ "data with your RADIUS server. " _
+ "If unspecified, <code>127.0.0.1</code> will be used.<br>" _
+ "Useful only if " _
+ "<a href=\"?section=auth#user_verify_class_desc\">the " _
+ "<code>user_verify_class</code> parameter</a> " _
+ "contains <code>RADIUS</code>.",
+
+ RADIUS_email_suffix => "Suffix to append to a RADIUS user name to form an " _
+ "e-mail address.<br>" _
+ "Useful only if " _
+ "<a href=\"?section=auth#user_verify_class_desc\">the " _
+ "<code>user_verify_class</code> parameter</a> " _
+ "contains <code>RADIUS</code>.",
+ }
+%]
diff --git a/template/en/default/admin/params/shadowdb.html.tmpl b/template/en/default/admin/params/shadowdb.html.tmpl
new file mode 100644
index 000000000..c8122844e
--- /dev/null
+++ b/template/en/default/admin/params/shadowdb.html.tmpl
@@ -0,0 +1,49 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Dave Miller <justdave@bugzilla.org>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+[%
+ title = "Shadow Database"
+ desc = "An optional hack to increase database performance"
+%]
+
+[% param_descs = {
+ shadowdbhost => "The host the shadow database is on.",
+
+ shadowdbport => "The port the shadow database is on. Ignored if " _
+ "<tt>shadowdbhost</tt> is blank. Note: if the host is the local " _
+ "machine, then MySQL will ignore this setting, and you must " _
+ "specify a socket below.",
+
+ shadowdbsock => "The socket used to connect to the shadow database, if the host " _
+ "is the local machine. This setting is required because MySQL " _
+ "ignores the port specified by the client and connects using " _
+ "its compiled-in socket path (on unix machines) when connecting " _
+ "from a client to a local server. If you leave this blank, and " _
+ "have the database on localhost, then the <tt>shadowdbport</tt> " _
+ "will be ignored.",
+
+ shadowdb => "If non-empty, then this is the name of another database in " _
+ "which $terms.Bugzilla will use as a read-only copy of everything. " _
+ "This is done so that long slow read-only operations can be used " _
+ "against this db, and not lock up things for everyone else. This " _
+ "database is on the <tt>shadowdbhost</tt>, and must exist. " _
+ "$terms.Bugzilla does not update it, if you use this parameter, then " _
+ "you need to set up replication for your database." }
+%]
diff --git a/template/en/default/admin/params/usermatch.html.tmpl b/template/en/default/admin/params/usermatch.html.tmpl
new file mode 100644
index 000000000..54f150900
--- /dev/null
+++ b/template/en/default/admin/params/usermatch.html.tmpl
@@ -0,0 +1,39 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Dave Miller <justdave@bugzilla.org>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+[%
+ title = "User Matching"
+ desc = "Set up your user matching policies"
+%]
+
+[% param_descs = {
+ usemenuforusers => "If this option is set, $terms.Bugzilla will offer you a list " _
+ "to select from (instead of a text entry field) where a user " _
+ "needs to be selected. This option should not be enabled on " _
+ "sites where there are a large number of users.",
+
+ maxusermatches => "Search for no more than this many matches.<br> " _
+ "If set to '1', no users will be displayed on ambiguous matches. " _
+ "This is useful for user privacy purposes.<br> " _
+ "A value of zero means no limit.",
+
+ confirmuniqueusermatch => "Whether a confirmation screen should be displayed when only " _
+ "one user matches a search entry." }
+%]
diff --git a/template/en/default/admin/products/confirm-delete.html.tmpl b/template/en/default/admin/products/confirm-delete.html.tmpl
new file mode 100644
index 000000000..f4a04b86f
--- /dev/null
+++ b/template/en/default/admin/products/confirm-delete.html.tmpl
@@ -0,0 +1,266 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Gavin Shelley <bugzilla@chimpychompy.org>
+ #%]
+
+[%# INTERFACE:
+ # product: Bugzilla::Product object; The product
+ #
+ # (classification fields available if Param('useclassification') is enabled:)
+ #
+ # classification: Bugzilla::Classification object; The classification
+ # the product is in
+ #
+ #%]
+
+[% title = BLOCK %]Delete Product '[% product.name FILTER html %]'
+[% END %]
+
+[% PROCESS global/header.html.tmpl
+ title = title
+ style_urls = ['skins/standard/admin.css']
+%]
+
+<table border="1" cellpadding="4" cellspacing="0">
+ <tr bgcolor="#6666FF">
+ <th valign="top" align="left">Field</th>
+ <th valign="top" align="left">Value</th>
+ </tr>
+
+ [% IF Param('useclassification') %]
+ <tr>
+ <td>Classification:</td>
+ <td>[% classification.name FILTER html %]</td>
+ </tr>
+ <tr>
+ <td>Classification Description:</td>
+ [%# descriptions are intentionally not filtered to allow html content %]
+ <td>
+ [% IF classification.description %]
+ [% classification.description FILTER html_light %]
+ [% ELSE %]
+ <span style="color: red">missing</span>
+ [% END %]
+ </td>
+ </tr>
+ [% END %]
+
+ <tr>
+ <td valign="top">Product:</td>
+ <td valign="top">
+ <a href="editproducts.cgi?product=[% product.name FILTER url_quote %]">
+ [% product.name FILTER html %]
+ </a>
+ </td>
+ </tr>
+ <tr>
+ <td valign="top">Description:</td>
+ [%# descriptions are intentionally not filtered to allow html content %]
+ <td valign="top">
+ [% IF product.description %]
+ [% product.description FILTER html_light %]
+ [% ELSE %]
+ <span style="color: red">missing</span>
+ [% END %]
+ </td>
+ </tr>
+
+ [% IF Param('usetargetmilestone') %]
+ <tr>
+ <td>Milestone URL:</td>
+ <td>
+ [% IF product.milestone_url %]
+ <a href="[% product.milestone_url FILTER html %]">
+ [%- product.milestone_url FILTER html %]
+ </a>
+ [% ELSE %]
+ none
+ [% END %]
+ </td>
+ </tr>
+ [% END %]
+
+ <tr>
+ <td>Closed for [% terms.bugs %]:</td>
+ <td>
+ [% IF product.is_active %]
+ open
+ [% ELSE %]
+ closed
+ [% END %]
+ </td>
+ </tr>
+
+ <tr>
+ <td>
+ [% IF product.components.size > 0 %]
+ <a href="editcomponents.cgi?product=[% product.name FILTER url_quote %]"
+ title="Edit components for product '[% product.name FILTER html %]'">
+ Components:
+ </a>
+ [% ELSE %]
+ Components:
+ [% END %]
+ </td>
+ <td>
+ [% IF product.components.size > 0 %]
+ <table>
+ [% FOREACH c = product.components %]
+ <tr>
+ <th align="right">[% c.name FILTER html %]:</th>
+ [%# descriptions are intentionally not filtered to allow html content %]
+ <td>
+ [% IF c.description %]
+ [% c.description FILTER html_light %]
+ [% ELSE %]
+ <span style="color: red">missing</span>
+ [% END %]
+ </td>
+ </tr>
+ [% END %]
+ </table>
+ [% ELSE %]
+ none
+ [% END %]
+ </td>
+ </tr>
+
+ <tr>
+ <td>
+ [% IF product.versions.size > 0 %]
+ <a href="editversions.cgi?product=[%- product.name FILTER url_quote %]">
+ Versions:
+ </a>
+ [% ELSE %]
+ Versions:
+ [% END %]
+ </td>
+ <td>
+ [% IF product.versions.size > 0 %]
+ [% FOREACH v = product.versions %]
+ [% v.name FILTER html %]<br>
+ [% END %]
+ [% ELSE %]
+ none
+ [% END %]
+ </td>
+ </tr>
+
+
+ [% IF Param('usetargetmilestone') %]
+ <tr>
+ <td valign="top">
+ [% IF product.milestones.size > 0 %]
+ <a href="editmilestones.cgi?product=[%- product.name FILTER url_quote %]">
+ Milestones:
+ </a>
+ [% ELSE %]
+ Milestones:
+ [% END %]
+ </td>
+ <td>
+ [% IF product.milestones.size > 0 %]
+ [% FOREACH m = product.milestones %]
+ [% m.name FILTER html %]<br>
+ [% END %]
+ [% ELSE %]
+ none
+ [% END %]
+ </td>
+ </tr>
+ [% END %]
+
+ <tr>
+ <td>[% terms.Bugs %]:</td>
+ <td>
+ [% IF product.bug_count %]
+ <a href="buglist.cgi?product=[% product.name FILTER url_quote %]"
+ title="List of [% terms.bugs %] for product '[% product.name FILTER html %]'">
+ [% product.bug_count FILTER html %]
+ </a>
+ [% ELSE %]
+ none
+ [% END %]
+ </td>
+ </tr>
+</table>
+
+<h2>Confirmation</h2>
+
+[% IF product.bug_count %]
+
+ [% IF !Param("allowbugdeletion") %]
+
+ <p>Sorry, there
+
+ [% IF product.bug_count > 1 %]
+ are [% product.bug_count FILTER html %] [%+ terms.bugs %]
+ [% ELSE %]
+ is 1 [% terms.bug %]
+ [% END %]
+
+ outstanding for this product. You must reassign
+
+ [% IF product.bug_count > 1 %]
+ those [% terms.bugs %]
+ [% ELSE %]
+ that [% terms.bug %]
+ [% END %]
+
+ to another product before you can delete this one.</p>
+
+ [% ELSE %]
+
+ <table border="0" cellpadding="20" width="70%" bgcolor="red">
+ <tr>
+ <td>
+ There
+ [% IF product.bug_count > 1 %]
+ are [% product.bug_count FILTER html %] [%+ terms.bugs %]
+ [% ELSE %]
+ is 1 [% terms.bug %]
+ [% END %]
+ entered for this product! When you delete this
+ product, <b><blink>ALL</blink></b> stored [% terms.bugs %] and
+ their history will be deleted, too.
+ </td>
+ </tr>
+ </table>
+
+ [% END %]
+
+[% END %]
+
+[% Hook.process("confirmation") %]
+
+[% IF product.bug_count == 0 || Param('allowbugdeletion') %]
+
+ <p>Do you really want to delete this product?</p>
+
+ <form method="post" action="editproducts.cgi">
+ <input type="checkbox" id="delete_series" name="delete_series" value=1>
+ <label for="delete_series">
+ Delete all related series (you can also delete them later, by visiting
+ the <a href="chart.cgi?category=[% product.name FILTER html %]">New Charts page</a>.)
+ </label><p>
+ <input type="submit" id="delete" value="Yes, delete">
+ <input type="hidden" name="action" value="delete">
+ <input type="hidden" name="product" value="[% product.name FILTER html %]">
+ <input type="hidden" name="token" value="[% token FILTER html %]">
+ </form>
+
+[% END %]
+
+[% PROCESS admin/products/footer.html.tmpl %]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/products/create.html.tmpl b/template/en/default/admin/products/create.html.tmpl
new file mode 100644
index 000000000..1b50661cf
--- /dev/null
+++ b/template/en/default/admin/products/create.html.tmpl
@@ -0,0 +1,68 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Gabriel S. Oliveira <gabriel@async.com.br>
+ #%]
+
+[%# INTERFACE:
+ # classification: Bugzilla::Classification object; If classifications
+ # are enabled, then this is
+ # the currently selected classification
+ #
+ #%]
+
+[% title = BLOCK %]Add Product[% END %]
+
+[% PROCESS global/header.html.tmpl
+ title = title
+ style_urls = ['skins/standard/admin.css']
+ javascript_urls = ['js/util.js']
+%]
+
+[% DEFAULT
+ product.is_active = 1,
+ version = "unspecified",
+ product.defaultmilestone = constants.DEFAULT_MILESTONE
+ product.allows_unconfirmed = 1
+%]
+
+<form method="post" action="editproducts.cgi">
+ <table border="0" cellpadding="4" cellspacing="0">
+
+ [% PROCESS "admin/products/edit-common.html.tmpl" %]
+
+ <tr>
+ <th align="right">Version:</th>
+ <td><input size="64" maxlength="255" name="version"
+ value="[% version FILTER html %]">
+ </td>
+ </tr>
+ <tr>
+ <th align="right">Create chart datasets for this product:</th>
+ <td>
+ <input type="checkbox" name="createseries" value="1" checked="checked">
+ </td>
+ </tr>
+ </table>
+
+ <input type="submit" value="Add">
+ <input type="hidden" name="action" value="new">
+ <input type="hidden" name="token" value="[% token FILTER html %]">
+ <input type="hidden" name="classification"
+ value="[% classification.name FILTER html %]">
+</form>
+
+[% PROCESS "admin/products/footer.html.tmpl"
+ no_add_product_link = 1
+ no_edit_product_link = 1 %]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/products/edit-common.html.tmpl b/template/en/default/admin/products/edit-common.html.tmpl
new file mode 100644
index 000000000..4812707cd
--- /dev/null
+++ b/template/en/default/admin/products/edit-common.html.tmpl
@@ -0,0 +1,83 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Jack Nerad <jnerad@bellsouth.net>
+ # Tiago R. Mello <tiago@async.com.br>
+ # Gabriel S. Oliveira <gabriel@async.com.br>
+ #%]
+
+[%# INTERFACE:
+ # product: Bugzilla::Product object; The product
+ #
+ # classification: Bugzilla::Classifiation object; classification product is in.
+ #%]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+[% IF Param('useclassification') %]
+ <tr>
+ <th align="right"><b>Classification:</b></th>
+ <td><b>[% classification.name FILTER html %]</b></td>
+ </tr>
+[% END %]
+
+<tr>
+ <th align="right">Product:</th>
+ <td><input size="64" maxlength="64" name="product"
+ value="[% product.name FILTER html %]">
+ </td>
+</tr>
+<tr>
+ <th align="right">Description:</th>
+ <td><textarea rows="4" cols="64" wrap="virtual" name="description">
+ [% product.description FILTER html %]</textarea>
+ </td>
+</tr>
+
+[% IF Param('usetargetmilestone') -%]
+ <tr>
+ <th align="right">Default milestone:</th>
+ <td>
+ [% IF product.milestones.size %]
+ <select name="defaultmilestone">
+ [% FOREACH m = product.milestones %]
+ <option value="[% m.name FILTER html %]"
+ [% " selected=\"selected\"" IF m.name == product.defaultmilestone %]>
+ [%- m.name FILTER html -%]</option>
+ [% END %]
+ </select>
+ [% ELSE %]
+ <input type="text" size="20" maxlength="20" name="defaultmilestone"
+ value="[% product.defaultmilestone FILTER html %]">
+ [% END %]
+ </td>
+ </tr>
+[% END %]
+
+<tr>
+ <th align="right">Open for [% terms.bug %] entry:</th>
+ <td><input type="checkbox" name="is_active" value="1"
+ [% ' checked="checked"' IF product.is_active %]>
+ </td>
+</tr>
+<tr>
+ <th align="right">
+ <label for="allows_unconfirmed">Enable the
+ [%+ display_value('bug_status', 'UNCONFIRMED') FILTER html %] status
+ in this product:</label>
+ </th>
+ <td><input type="checkbox" id="allows_unconfirmed" name="allows_unconfirmed"
+ [% ' checked="checked"' IF product.allows_unconfirmed %]>
+ </td>
+</tr>
+
+[% Hook.process('rows') %]
diff --git a/template/en/default/admin/products/edit.html.tmpl b/template/en/default/admin/products/edit.html.tmpl
new file mode 100644
index 000000000..976739f78
--- /dev/null
+++ b/template/en/default/admin/products/edit.html.tmpl
@@ -0,0 +1,149 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Jack Nerad <jnerad@bellsouth.net>
+ # Tiago R. Mello <tiago@async.com.br>
+ # Gabriel S. Oliveira <gabriel@async.com.br>
+ #%]
+
+[%# INTERFACE:
+ # product: Bugzilla::Product object; The product
+ #
+ # (classification fields available if Param('useclassification') is enabled:)
+ #
+ # classification: Bugzilla::Classification object; The classification
+ # the product is in
+ #%]
+
+[% title = BLOCK %]Edit Product '[% product.name FILTER html %]'[% END %]
+
+[% PROCESS global/header.html.tmpl
+ title = title
+ style_urls = ['skins/standard/admin.css']
+ javascript_urls = ['js/util.js']
+%]
+
+[% group_control = {${constants.CONTROLMAPNA} => 'NA',
+ ${constants.CONTROLMAPSHOWN} => 'Shown',
+ ${constants.CONTROLMAPDEFAULT} => 'Default',
+ ${constants.CONTROLMAPMANDATORY} => 'Mandatory'}
+ %]
+
+<form method="post" action="editproducts.cgi">
+ <table border="0" cellpadding="4" cellspacing="0">
+
+ [% PROCESS "admin/products/edit-common.html.tmpl" %]
+
+ <tr>
+ <th align="right" valign="top">
+ <a href="editcomponents.cgi?product=[% product.name FILTER url_quote %]">
+ Edit components:
+ </a>
+ </th>
+ <td>
+ [% IF product.components.size -%]
+ [% FOREACH component = product.components %]
+ <b>[% component.name FILTER html %]:</b>&nbsp;
+ [% IF component.description %]
+ [% component.description FILTER html_light %]
+ [% ELSE %]
+ <font color="red">description missing</font>
+ [% END %]
+ <br>
+ [% END %]
+ [% ELSE %]
+ <font color="red">missing</font>
+ [% END %]
+ </td>
+ </tr>
+ <tr>
+ <th align="right" valign="top">
+ <a href="editversions.cgi?product=[% product.name FILTER url_quote %]">Edit
+versions:</a>
+ </th>
+ <td>
+ [%- IF product.versions.size -%]
+ [% FOREACH v = product.versions %]
+ [% v.name FILTER html %]
+ <br>
+ [% END %]
+ [% ELSE %]
+ <font color="red">missing</font>
+ [% END %]
+ </td>
+ </tr>
+ [% IF Param('usetargetmilestone') %]
+ <tr>
+ <th align="right" valign="top">
+ <a href="editmilestones.cgi?product=[% product.name FILTER url_quote %]">
+ Edit milestones:</a>
+ </th>
+ <td>
+ [%- IF product.milestones.size -%]
+ [%- FOREACH m = product.milestones -%]
+ [% m.name FILTER html %]
+ <br>
+ [% END %]
+ [% ELSE %]
+ <font color="red">missing</font>
+ [% END %]
+ </td>
+ </tr>
+ [% END %]
+ <tr>
+ <th align="right" valign="top">
+ <a href="editproducts.cgi?action=editgroupcontrols&product=
+ [%- product.name FILTER url_quote %]">
+ Edit Group Access Controls:
+ </a>
+ </th>
+ <td>
+ [% IF product.group_controls.size %]
+ [% FOREACH g = product.group_controls.values.sort("name") %]
+ <b>[% g.group.name FILTER html %]:</b>&nbsp;
+ [% IF g.group.isactive %]
+ [% group_control.${g.membercontrol} FILTER html %]/
+ [% group_control.${g.othercontrol} FILTER html %]
+ [% IF g.entry %], ENTRY[% END %]
+ [% IF g.canedit %], CANEDIT[% END %]
+ [% IF g.editcomponents %], editcomponents[% END %]
+ [% IF g.canconfirm %], canconfirm[% END %]
+ [% IF g.editbugs %], editbugs[% END %]
+ [% ELSE %]
+ DISABLED
+ [% END %]
+ <br>
+ [% END %]
+ [% ELSE %]
+ no groups
+ [% END %]
+ </td>
+ </tr>
+ <tr>
+ <th align="right">[% terms.Bugs %]:</th>
+ <td><a href="buglist.cgi?product=[% product.name FILTER url_quote %]">
+ [% product.bug_count FILTER html %]</a></td>
+ </tr>
+ </table>
+
+ <input type="hidden" name="product_old_name"
+ value="[% product.name FILTER html %]">
+ <input type="hidden" name="action" value="update">
+ <input type="hidden" name="token" value="[% token FILTER html %]">
+ <input type="submit" name="submit" value="Save Changes">
+</form>
+
+[% PROCESS "admin/products/footer.html.tmpl"
+ no_add_product_link = 1
+ no_edit_product_link = 1 %]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/products/footer.html.tmpl b/template/en/default/admin/products/footer.html.tmpl
new file mode 100644
index 000000000..661829b7c
--- /dev/null
+++ b/template/en/default/admin/products/footer.html.tmpl
@@ -0,0 +1,86 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Gavin Shelley <bugzilla@chimpychompy.org>
+ #%]
+
+[%# INTERFACE:
+ # product: Bugzilla::Product Object; the product
+ #
+ # classification: Bugzilla::Classification object ; If classifications
+ # are enabled, then this is the currently
+ # selected classification
+ #
+ # no_XXX_link: boolean; if defined, then don't show the corresponding
+ # link. Supported parameters are:
+ #
+ # no_edit_product_link
+ # no_edit_other_products_link
+ # no_add_product_link
+ #%]
+
+[% IF Param('useclassification') && classification %]
+ [% classification_url_part = BLOCK %]&amp;classification=
+ [%- classification.name FILTER url_quote %]
+ [% END %]
+ [% classification_url_part_start = BLOCK %]classification=
+ [%- classification.name FILTER url_quote %]
+ [% END %]
+ [% classification_text = BLOCK %]
+ of classification '[% classification.name FILTER html %]'
+ [% END %]
+[% END %]
+
+<hr>
+
+<p>
+[% UNLESS no_add_product_link || !user.in_group("editcomponents") %]
+ <a title="Add a product"
+ href="editproducts.cgi?action=add">Add</a> a product[% -%]
+[%# Strictly speaking, we should not have to check for a
+ classification if they are enabled, but I'm just being paranoid %]
+ [% IF Param('useclassification') && classification %]
+ (<a title="Add a product to classification '
+ [%- classification.name FILTER html %]'"
+ href="editproducts.cgi?action=add
+ [%- classification_url_part %]">to
+ classification '[% classification.name FILTER html %]'</a>)
+ [% END %].
+[% END %]
+
+[% IF product && !no_edit_product_link %]
+ Edit product <a
+ title="Edit Product '[% product.name FILTER html %]'
+ [%- classification_text %]"
+ href="editproducts.cgi?action=edit&amp;product=[% product.name FILTER url_quote %]">
+ '[% product.name FILTER html %]'</a>.
+[% END %]
+
+
+[%# Edit other products (in a classification if specified): %]
+[% UNLESS no_edit_other_products_link %]
+ Edit <a
+ href="editproducts.cgi?
+ [%- classification_url_part_start FILTER none %]">other products
+ [% classification_text %]</a>.
+
+[% END %]
+
+[% IF Param('useclassification') && classification
+ && user.in_group('editclassifications') %]
+ Edit classification <a href="editclassifications.cgi?action=edit
+ [%- classification_url_part %]">'
+ [%- classification.name FILTER html %]'</a>.
+
+[% END %]
+
+</p>
diff --git a/template/en/default/admin/products/groupcontrol/confirm-edit.html.tmpl b/template/en/default/admin/products/groupcontrol/confirm-edit.html.tmpl
new file mode 100644
index 000000000..1fc92c928
--- /dev/null
+++ b/template/en/default/admin/products/groupcontrol/confirm-edit.html.tmpl
@@ -0,0 +1,58 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Joel Peshkin <bugreport@peshkin.net>
+ #
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+[% title = BLOCK %]
+ Confirm Group Control Change for product '[%- product.name FILTER html %]'
+[% END %]
+
+[% PROCESS global/header.html.tmpl title = title %]
+[% FOREACH group = mandatory_groups %]
+<P>
+group '[% group.name FILTER html %]' impacts [% group.count %]
+[%+ terms.bugs %] for
+which the group is newly mandatory and will be added.
+[% END %]
+
+[% FOREACH group = na_groups %]
+<P>
+group '[% group.name FILTER html %]' impacts [% group.count %]&nbsp;
+[% terms.bugs %] for which the group is no longer applicable and will
+be removed.[% END %]
+<form action="editproducts.cgi" method="post" >
+
+ [% PROCESS "global/hidden-fields.html.tmpl" exclude="^Bugzilla_(login|password)$" %]
+
+ <br>
+ Click "Continue" to proceed with the change including the changes
+ indicated above. If you do not want these changes, use "back" to
+ return to the previous page.
+ <p>
+ <input type="hidden" name="confirmed" value="confirmed">
+ <input type="submit" id="update" value="Continue">
+ </p>
+
+</form>
+
+
+[% PROCESS global/footer.html.tmpl %]
+
+
diff --git a/template/en/default/admin/products/groupcontrol/edit.html.tmpl b/template/en/default/admin/products/groupcontrol/edit.html.tmpl
new file mode 100644
index 000000000..8c634ebfe
--- /dev/null
+++ b/template/en/default/admin/products/groupcontrol/edit.html.tmpl
@@ -0,0 +1,325 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Joel Peshkin <bugreport@peshkin.net>
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% title = BLOCK %]
+ Edit Group Controls for [% product.name FILTER html %]
+[% END %]
+[% PROCESS global/header.html.tmpl
+ title = title
+%]
+
+<form method="post" action="editproducts.cgi">
+ <input type="hidden" name="action" value="updategroupcontrols">
+ <input type="hidden" name="product" value="[% product.name FILTER html %]">
+ <input type="hidden" name="token" value="[% token FILTER html %]">
+
+ <table id="form" cellspacing="0" cellpadding="4" border="1">
+ <tr bgcolor="#6666ff">
+ <th>Group</th>
+ <th>Entry</th>
+ <th>MemberControl</th>
+ <th>OtherControl</th>
+ <th>Canedit</th>
+ <th>editcomponents</th>
+ <th>canconfirm</th>
+ <th>editbugs</th>
+ <th>[% terms.Bugs %]</th>
+ </tr>
+ [% FOREACH group = product.group_controls(1).values.sort("name") %]
+ [% IF !group.group.isactive AND group.bug_count %]
+ <tr bgcolor="#bbbbbb">
+ <td>
+ [% group.group.name FILTER html %]
+ </td>
+ <td align="center" colspan=7>
+ Disabled
+ </td>
+ <td>
+ [% group.bug_count FILTER html %]
+ </td>
+ <tr>
+ [% ELSIF group.group.is_active %]
+ <tr>
+ <td>
+ [% group.group.name FILTER html %]
+ </td>
+ <td>
+ <input type=checkbox value=1 name=entry_[% group.id %]
+ [% " checked=\"checked\"" IF group.entry %]>
+ </td>
+ <td>
+ <select name="membercontrol_[% group.id %]">
+ <option value=[% constants.CONTROLMAPNA %]
+ [% " selected=\"selected\""
+ IF group.membercontrol == constants.CONTROLMAPNA %]
+ >NA
+ </option>
+ <option value=[% constants.CONTROLMAPSHOWN %]
+ [% " selected=\"selected\""
+ IF group.membercontrol == constants.CONTROLMAPSHOWN %]
+ >Shown
+ </option>
+ <option value=[% constants.CONTROLMAPDEFAULT %]
+ [% " selected=\"selected\""
+ IF group.membercontrol == constants.CONTROLMAPDEFAULT %]
+ >Default
+ </option>
+ <option value=[% constants.CONTROLMAPMANDATORY %]
+ [% " selected=\"selected\""
+ IF group.membercontrol == constants.CONTROLMAPMANDATORY %]
+ >Mandatory
+ </option>
+ </select>
+ </td>
+ <td>
+ <select name="othercontrol_[% group.id %]">
+ <option value=[% constants.CONTROLMAPNA %]
+ [% " selected=\"selected\""
+ IF group.othercontrol == constants.CONTROLMAPNA %]
+ >NA
+ </option>
+ <option value=[% constants.CONTROLMAPSHOWN %]
+ [% " selected=\"selected\""
+ IF group.othercontrol == constants.CONTROLMAPSHOWN %]
+ >Shown
+ </option>
+ <option value=[% constants.CONTROLMAPDEFAULT %]
+ [% " selected=\"selected\""
+ IF group.othercontrol == constants.CONTROLMAPDEFAULT %]
+ >Default
+ </option>
+ <option value=[% constants.CONTROLMAPMANDATORY %]
+ [% " selected=\"selected\""
+ IF group.othercontrol == constants.CONTROLMAPMANDATORY %]
+ >Mandatory
+ </option>
+ </select>
+ </td>
+ <td>
+ <input type=checkbox value=1 name=canedit_[% group.id %]
+ [% " checked=\"checked\"" IF group.canedit %]>
+ </td>
+ <td>
+ <input type=checkbox value=1 name=editcomponents_[% group.id %]
+ [% " checked=\"checked\"" IF group.editcomponents %]>
+ </td>
+ <td>
+ <input type=checkbox value=1 name=canconfirm_[% group.id %]
+ [% " checked=\"checked\"" IF group.canconfirm %]>
+ </td>
+ <td>
+ <input type=checkbox value=1 name=editbugs_[% group.id %]
+ [% " checked=\"checked\"" IF group.editbugs %]>
+ </td>
+ <td>
+ [% group.bug_count || 0 FILTER html %]
+ </td>
+ </tr>
+ [% END %]
+ [% END %]
+
+ </table>
+ <br>
+ <input type=submit name="submit" value="submit">
+ <br>
+</form>
+
+
+<p>
+These settings control the relationship of the groups to this
+product.
+</p>
+<p>
+If any group has <b>Entry</b> selected, then this product will
+restrict [% terms.bug %] entry to only those users who are members of all the
+groups with entry selected.
+</p>
+<p>
+If any group has <b>Canedit</b> selected, then this product
+will be read-only for any users who are not members of all of
+the groups with Canedit selected. ONLY users who are members of
+all the canedit groups will be able to edit. This is an additional
+restriction that further restricts what can be edited by a user.
+</p>
+<p>
+The following settings control let you choose privileges on a <b>per-product basis</b>.
+This is a convenient way to give privileges to some users for some products
+only, without having to give them global privileges which would affect all
+products:
+</p>
+<p>
+Any group having <b>editcomponents</b> selected allows users who are
+in this group to edit all aspects of this product, including components,
+milestones and versions.
+</p>
+<p>
+Any group having <b>canconfirm</b> selected allows users who are
+in this group to confirm [% terms.bugs %] in this product.
+</p>
+<p>
+Any group having <b>editbugs</b> selected allows users who are
+in this group to edit all fields of [% terms.bugs %] in this product.
+</p>
+<p>
+The <b>MemberControl</b> and <b>OtherControl</b> fields
+indicate which [% terms.bugs %] will be placed in
+this group according to the following definitions.
+</p>
+
+<table border=1>
+ <tr>
+ <th>
+ MemberControl
+ </th>
+ <th>
+ OtherControl
+ </th>
+ <th>
+ Interpretation
+ </th>
+ </tr>
+ <tr>
+ <td>
+ NA
+ </td>
+ <td>
+ NA
+ </td>
+ <td>
+ [% terms.Bugs %] in this product are never associated with this group.
+ </td>
+ </tr>
+ <tr>
+ <td>
+ Shown
+ </td>
+ <td>
+ NA
+ </td>
+ <td>
+ [% terms.Bugs %] in this product are permitted to be restricted to this
+ group. Users who are members of this group will be able to place [% terms.bugs %] in
+ this group.
+ </td>
+ </tr>
+ <tr>
+ <td>
+ Shown
+ </td>
+ <td>
+ Shown
+ </td>
+ <td>
+ [% terms.Bugs %] in this product can be placed in this group by anyone
+ with permission to edit the [% terms.bug %] even if they are not a member
+ of this group.
+ </td>
+ </tr>
+ <tr>
+ <td>
+ Shown
+ </td>
+ <td>
+ Default
+ </td>
+ <td>
+ [% terms.Bugs %] in this product can be placed in this group by anyone
+ with permission to edit the [% terms.bug %] even if they are not a member
+ of this group. Non-members place [% terms.bugs %] in this group by default.
+ </td>
+ </tr>
+ <tr>
+ <td>
+ Shown
+ </td>
+ <td>
+ Mandatory
+ </td>
+ <td>
+ [% terms.Bugs %] in this product are permitted to be restricted to this
+ group. Users who are members of this group will be able to place [% terms.bugs %]
+ in this group. Non-members will be forced to restrict [% terms.bugs %] to
+ this group when they initially enter [% terms.abug %] in this product.
+ </td>
+ </tr>
+ <tr>
+ <td>
+ Default
+ </td>
+ <td>
+ NA
+ </td>
+ <td>
+ [% terms.Bugs %] in this product are permitted to be restricted to this
+ group and are placed in this group by default. Users who are members of this
+ group will be able to place [% terms.bugs %] in this group.
+ </td>
+ </tr>
+ <tr>
+ <td>
+ Default
+ </td>
+ <td>
+ Default
+ </td>
+ <td>
+ [% terms.Bugs %] in this product are permitted to be restricted to this
+ group and are placed in this group by default. Users who are members of this group
+ will be able to place [% terms.bugs %] in this group. Non-members will be
+ able to restrict [% terms.bugs %] to this group on entry and will do so by default.
+ </td>
+ </tr>
+ <tr>
+ <td>
+ Default
+ </td>
+ <td>
+ Mandatory
+ </td>
+ <td>
+ [% terms.Bugs %] in this product are permitted to be restricted to this
+ group and are placed in this group by default. Users who are members of this group
+ will be able to place [% terms.bugs %] in this group. Non-members will be forced
+ to place [% terms.bugs %] in this group on entry.
+ </td>
+ </tr>
+ <tr>
+ <td>
+ Mandatory
+ </td>
+ <td>
+ Mandatory
+ </td>
+ <td>
+ [% terms.Bugs %] in this product are required to be restricted to this
+ group. Users are not given any option.
+ </td>
+ </tr>
+</table>
+<p>
+Please note that the above table delineates the only allowable combinations
+for the <b>MemberControl</b> and <b>OtherControl</b> field settings.
+Attempting to submit a combination not listed there (e.g. Mandatory/NA,
+Default/Shown, etc.) will produce an error message.
+</p>
+[% PROCESS global/footer.html.tmpl %]
+
diff --git a/template/en/default/admin/products/groupcontrol/updated.html.tmpl b/template/en/default/admin/products/groupcontrol/updated.html.tmpl
new file mode 100644
index 000000000..2f59cae68
--- /dev/null
+++ b/template/en/default/admin/products/groupcontrol/updated.html.tmpl
@@ -0,0 +1,50 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): André Batosti <batosti@async.com.br>
+ #
+ #%]
+
+[%# INTERFACE:
+ # product: Bugzilla::Product object; the product.
+ # changes: Hashref with changes made to the product group controls.
+ #%]
+
+[% title = BLOCK %]
+ Update group access controls for [% product.name FILTER html %]
+[% END %]
+
+[% PROCESS global/header.html.tmpl
+ title = title
+%]
+<p>
+[% IF changes.group_controls.now_na.size %]
+ [% FOREACH g = changes.group_controls.now_na %]
+ Removing [% terms.bugs %] from group '[% g.name FILTER html %]' which
+ no longer applies to this product<p>
+ [% g.bug_count FILTER html %] [%+ terms.bugs %] removed<p>
+ [% END %]
+[% END %]
+
+[% IF changes.group_controls.now_mandatory.size %]
+ [% FOREACH g = changes.group_controls.now_mandatory %]
+ Adding [% terms.bugs %] to group '[% g.name FILTER html %]' which is
+ mandatory for this product<p>
+ [% g.bug_count FILTER html %] [%+ terms.bugs %] added<p>
+ [% END %]
+[% END %]
+
+Group control updates done<p>
+
+[% PROCESS admin/products/footer.html.tmpl %]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/products/list-classifications.html.tmpl b/template/en/default/admin/products/list-classifications.html.tmpl
new file mode 100644
index 000000000..77634e127
--- /dev/null
+++ b/template/en/default/admin/products/list-classifications.html.tmpl
@@ -0,0 +1,65 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Gavin Shelley <bugzilla@chimpychompy.org>
+ #%]
+
+[%# INTERFACE:
+ # classifications: array of hashes having the following properties:
+ # - name: string; The name of the classification
+ # - description: string; The classification description (html allowed)
+ # - product_count: number; The number of products in this classification
+ #
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% PROCESS global/header.html.tmpl
+ title = "Select Classification"
+%]
+
+[% columns = [
+ {
+ name => "name"
+ heading => "Edit products of..."
+ contentlink => 'editproducts.cgi?classification=%%name%%'
+ },
+ {
+ name => "description"
+ heading => "Description"
+ allow_html_content => 1
+ },
+ {
+ name => "product_count"
+ align => "right"
+ heading => "Product Count"
+ }
+ ]
+%]
+
+[% IF user.in_group('editcomponents') %]
+ [% columns.push({
+ heading => "Action..."
+ content => "Add product"
+ contentlink => 'editproducts.cgi?action=add&amp;classification=%%name%%' })
+ %]
+[% END %]
+
+[% PROCESS admin/table.html.tmpl
+ columns = columns
+ data = classifications
+%]
+
+[%# No need for the standard edit products footer, as we have an 'add'
+ link in the table %]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/products/list.html.tmpl b/template/en/default/admin/products/list.html.tmpl
new file mode 100644
index 000000000..93467df0e
--- /dev/null
+++ b/template/en/default/admin/products/list.html.tmpl
@@ -0,0 +1,95 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Gavin Shelley <bugzilla@chimpychompy.org>
+ #%]
+
+[%# INTERFACE:
+ # products: array of Bugzilla::Product objects
+ #
+ # classification: Bugzilla::Classification object; If classifications
+ # are enabled, then this is
+ # the currently selected classification
+ # showbugcounts: boolean; true if bug counts should be included in the table
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% IF classification %]
+ [% classification_url_part = BLOCK %]&amp;classification=
+ [%- classification.name FILTER url_quote %]
+ [%- END %]
+ [% classification_title = BLOCK %]
+ in classification '[% classification.name FILTER html %]'
+ [% END %]
+[% END %]
+
+[% PROCESS global/header.html.tmpl
+ title = "Select product $classification_title"
+%]
+
+[% columns = [
+ {
+ name => "name"
+ heading => "Edit product..."
+ contentlink => 'editproducts.cgi?action=edit&amp;product=%%name%%'
+ },
+ {
+ name => "description"
+ heading => "Description"
+ allow_html_content => 1
+ },
+ {
+ name => "is_active"
+ heading => "Open For New $terms.Bugs"
+ yesno_field => 1
+ },
+] %]
+
+[% IF showbugcounts %]
+
+ [% columns.push({
+ name => "bug_count"
+ heading => "$terms.Bug Count"
+ align => 'right'
+ contentlink => 'buglist.cgi?product=%%name%%'
+ })
+ %]
+
+[% END %]
+
+[% columns.push({
+ heading => "Action"
+ content => "Delete"
+ contentlink => 'editproducts.cgi?action=del&amp;product=%%name%%'
+ })
+%]
+
+[% Hook.process('before_table') %]
+
+[% PROCESS admin/table.html.tmpl
+ columns = columns
+ data = products
+%]
+
+[% IF !showbugcounts %]
+
+ <p><a href="editproducts.cgi?showbugcounts=1[% classification_url_part %]">
+ Redisplay table with [% terms.bug %] counts (slower)</a></p>
+
+[% END %]
+
+[% PROCESS admin/products/footer.html.tmpl
+ no_edit_other_products_link = 1
+ %]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/products/updated.html.tmpl b/template/en/default/admin/products/updated.html.tmpl
new file mode 100644
index 000000000..4140bab62
--- /dev/null
+++ b/template/en/default/admin/products/updated.html.tmpl
@@ -0,0 +1,105 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gavin Shelley <bugzilla@chimpychompy.org>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+
+[%# INTERFACE:
+ # product : Bugzilla::Product Object; new product.
+ # classification: Bugzilla::Classification Object; The product classification (may be empty or missing)
+ # changes: hashref with all changes made to the product. Each key is an edited field,
+ # and its value is an arrayref of the form [old values, new values].
+ #%]
+
+[% IF classification %]
+ [% classification_text = BLOCK %]
+ of classification '[% classification.name FILTER html %]'
+ [% END %]
+[% END %]
+
+[% title = BLOCK %]Updating Product '[% product.name FILTER html %]'
+ [% classification_text FILTER none %][% END %]
+[% PROCESS global/header.html.tmpl
+ title = title
+ style_urls = ['skins/standard/admin.css']
+%]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+[% IF changes.name.defined %]
+ <p>
+ Updated product name from '[% changes.name.0 FILTER html %]' to
+ '<a href="editproducts.cgi?action=edit&amp;product=
+ [%- product.name FILTER url_quote %]">[% product.name FILTER html %]</a>'.
+ </p>
+[% END %]
+
+
+[% IF changes.description.defined %]
+ <p>
+ Updated description to:
+ </p>
+ <p style="margin: 1em 3em 1em 3em">[% product.description FILTER html_light %]</p>
+[% END %]
+
+[% IF changes.isactive.defined %]
+ <p>
+ Product is now
+ [% IF product.is_active %]
+ open for
+ [% ELSE %]
+ closed to
+ [% END %]
+ new [% terms.bugs %].
+ </p>
+[% END %]
+
+[% IF changes.defaultmilestone.defined %]
+ <p>
+ Updated default milestone from '[% changes.defaultmilestone.0 FILTER html %]' to
+ '[% product.default_milestone FILTER html %]'.
+ </p>
+[% END %]
+
+[% IF changes.allows_unconfirmed.defined %]
+ <p>
+ [% IF product.allows_unconfirmed %]
+ The product now allows the
+ [%+ display_value('bug_status', 'UNCONFIRMED') FILTER html %] status.
+ [% ELSE %]
+ The product no longer allows the
+ [%+ display_value('bug_status', 'UNCONFIRMED') FILTER html %] status.
+ Note that any
+ <a href="buglist.cgi?product=
+ [%- product.name FILTER url_quote %]&amp;bug_status=UNCONFIRMED">
+ [%- terms.bugs %] that currently have the
+ [%+ display_value('bug_status', 'UNCONFIRMED') FILTER html %] status</a>
+ will remain in that status until they are edited.
+ [% END %]
+ </p>
+[% END %]
+
+[% Hook.process('changes') %]
+
+[% IF !changes.keys.size %]
+ <p>Nothing changed for product '[% product.name FILTER html %]'.</p>
+[% END %]
+
+[% PROCESS admin/products/footer.html.tmpl %]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/sanitycheck/list.html.tmpl b/template/en/default/admin/sanitycheck/list.html.tmpl
new file mode 100644
index 000000000..464297262
--- /dev/null
+++ b/template/en/default/admin/sanitycheck/list.html.tmpl
@@ -0,0 +1,37 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Frédéric Buclin.
+ #
+ # Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% PROCESS global/header.html.tmpl title = "Sanity Check"
+ style_urls = ['skins/standard/admin.css'] %]
+
+<div>
+ <p>
+ [% terms.Bugzilla %] is checking the referential integrity of your database.
+ This may take several minutes to complete.
+ </p>
+
+ <p>
+ Errors, if any, will be <span class="alert">emphasized like this</span>.
+ Depending on the errors found, some links will be displayed allowing you
+ to easily fix them. Fixing these errors will automatically run this script
+ again (so be aware that it may take an even longer time than the first run).
+ </p>
+</div>
+
+<hr>
diff --git a/template/en/default/admin/sanitycheck/messages.html.tmpl b/template/en/default/admin/sanitycheck/messages.html.tmpl
new file mode 100644
index 000000000..8d8cd3583
--- /dev/null
+++ b/template/en/default/admin/sanitycheck/messages.html.tmpl
@@ -0,0 +1,316 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Frédéric Buclin.
+ #
+ # Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% san_message = BLOCK %]
+ [% IF san_tag == "checks_start" %]
+ OK, now running sanity checks.
+
+ [% ELSIF san_tag == "checks_completed" %]
+ Sanity check completed.
+
+ [% ELSIF san_tag == "attachment_reference_deletion_start" %]
+ OK, now removing all references to deleted attachments.
+
+ [% ELSIF san_tag == "attachment_reference_deletion_end" %]
+ All references to deleted attachments have been removed.
+
+ [% ELSIF san_tag == "bug_check_alert" %]
+ [% errortext FILTER html %]: [% INCLUDE bug_list badbugs = badbugs %]
+
+ [% ELSIF san_tag == "bug_check_repair" %]
+ <a href="sanitycheck.cgi?[% param FILTER url_quote %]=1&amp;token=
+ [%- issue_hash_token(['sanitycheck']) FILTER url_quote %]">[% text FILTER html %]</a>.
+
+ [% ELSIF san_tag == "bug_check_creation_date" %]
+ Checking for [% terms.bugs %] with no creation date (which makes them invisible).
+
+ [% ELSIF san_tag == "bug_check_creation_date_error_text" %]
+ [% terms.Bugs %] with no creation date
+
+ [% ELSIF san_tag == "bug_check_creation_date_repair_text" %]
+ Repair missing creation date for these [% terms.bugs %]
+
+ [% ELSIF san_tag == "bug_check_bugs_fulltext" %]
+ Checking for [% terms.bugs %] with no entry for full text searching.
+
+ [% ELSIF san_tag == "bug_check_bugs_fulltext_error_text" %]
+ [% terms.Bugs %] with no entry for full text searching
+
+ [% ELSIF san_tag == "bug_check_bugs_fulltext_repair_text" %]
+ Repair missing full text search entries for these [% terms.bugs %]
+
+ [% ELSIF san_tag == "bug_check_res_dupl" %]
+ Checking resolution/duplicates
+
+ [% ELSIF san_tag == "bug_check_res_dupl_error_text" %]
+ [% terms.Bugs %] found on duplicates table that are not marked duplicate
+
+ [% ELSIF san_tag == "bug_check_res_dupl_error_text2" %]
+ [% terms.Bugs %] found marked resolved duplicate and not on duplicates table
+
+ [% ELSIF san_tag == "bug_check_status_res" %]
+ Checking statuses/resolutions
+
+ [% ELSIF san_tag == "bug_check_status_res_error_text" %]
+ [% terms.Bugs %] with open status and a resolution
+
+ [% ELSIF san_tag == "bug_check_status_res_error_text2" %]
+ [% terms.Bugs %] with non-open status and no resolution
+
+ [% ELSIF san_tag == "bug_check_status_everconfirmed" %]
+ Checking statuses/everconfirmed
+
+ [% ELSIF san_tag == "bug_check_status_everconfirmed_error_text" %]
+ [% terms.Bugs %] that are UNCONFIRMED but have everconfirmed set
+
+ [% ELSIF san_tag == "bug_check_status_everconfirmed_error_text2" %]
+ [% terms.Bugs %] with confirmed status but don't have everconfirmed set
+
+ [% ELSIF san_tag == "bug_check_control_values" %]
+ Checking for bad values in group_control_map
+
+ [% ELSIF san_tag == "bug_check_control_values_alert" %]
+ Found [% entries FILTER html %] bad group_control_map entries
+
+ [% ELSIF san_tag == "bug_check_control_values_violation" %]
+ Checking for [% terms.bugs %] with groups violating their product's group controls
+
+ [% ELSIF san_tag == "bug_check_control_values_error_text" %]
+ Have groups not permitted for their products
+
+ [% ELSIF san_tag == "bug_check_control_values_repair_text" %]
+ Permit the missing groups for the affected products
+ (set member control to <code>SHOWN</code>)
+
+ [% ELSIF san_tag == "bug_check_control_values_error_text2" %]
+ Are missing groups required for their products
+
+ [% ELSIF san_tag == "bug_creation_date_start" %]
+ OK, now fixing missing [% terms.bug %] creation dates.
+
+ [% ELSIF san_tag == "bug_creation_date_fixed" %]
+ [% bug_count FILTER html %] [%+ terms.bugs %] have been fixed.
+
+ [% ELSIF san_tag == "bugs_fulltext_start" %]
+ OK, now fixing [% terms.bug %] entries for full text searching.
+
+ [% ELSIF san_tag == "bugs_fulltext_fixed" %]
+ [% bug_count FILTER html %] [%+ terms.bugs %] have been fixed.
+
+ [% ELSIF san_tag == "bug_reference_deletion_start" %]
+ OK, now removing all references to deleted [% terms.bugs %].
+
+ [% ELSIF san_tag == "bug_reference_deletion_end" %]
+ All references to deleted [% terms.bugs %] have been removed.
+
+ [% ELSIF san_tag == "cross_check_to" %]
+ Checking references to [% table FILTER html %].[% field FILTER html %]...
+
+ [% ELSIF san_tag == "cross_check_from" %]
+ ... from [% table FILTER html %].[% field FILTER html %].
+
+ [% ELSIF san_tag == "cross_check_alert" %]
+ Bad value '[% value FILTER html %]' found in
+ [%+ table FILTER html %].[% field FILTER html %]
+ [% IF keyname %]
+ [% IF keyname == "bug_id" %]
+ ([% PROCESS bug_link bug_id = key %])
+ [% ELSE %]
+ ([% keyname FILTER html %] == '[% key FILTER html %]')
+ [% END %]
+ [% END %]
+
+ [% ELSIF san_tag == "cross_check_attachment_has_references" %]
+ <a href="sanitycheck.cgi?remove_invalid_attach_references=1&amp;token=
+ [%- issue_hash_token(['sanitycheck']) FILTER url_quote %]">Remove
+ invalid references to non existent attachments.</a>
+
+ [% ELSIF san_tag == "cross_check_bug_has_references" %]
+ <a href="sanitycheck.cgi?remove_invalid_bug_references=1&amp;token=
+ [%- issue_hash_token(['sanitycheck']) FILTER url_quote %]">Remove
+ invalid references to non existent [% terms.bugs %].</a>
+
+ [% ELSIF san_tag == "double_cross_check_to" %]
+ Checking references to [% table FILTER html %].[% field1 FILTER html %] /
+ [%+ table FILTER html %].[% field2 FILTER html %]...
+
+ [% ELSIF san_tag == "double_cross_check_from" %]
+ ... from [% table FILTER html %].[% field1 FILTER html %] /
+ [%+ table FILTER html %].[% field2 FILTER html %].
+
+ [% ELSIF san_tag == "double_cross_check_alert" %]
+ Bad values '[% value1 FILTER html %]', '[% value2 FILTER html %]' found
+ in [% table FILTER html %].[% field1 FILTER html %] /
+ [%+ table FILTER html %].[% field2 FILTER html %].
+ [% IF keyname %]
+ [% IF keyname == "bug_id" %]
+ ([% PROCESS bug_link bug_id = key %])
+ [% ELSE %]
+ ([% keyname FILTER html %] == '[% key FILTER html %]')
+ [% END %]
+ [% END %]
+
+ [% ELSIF san_tag == "everconfirmed_start" %]
+ OK, now fixing everconfirmed.
+
+ [% ELSIF san_tag == "everconfirmed_end" %]
+ everconfirmed fixed.
+
+ [% ELSIF san_tag == "flag_check_start" %]
+ Checking for flags being in the wrong product/component.
+
+ [% ELSIF san_tag == "flag_deletion_start" %]
+ OK, now deleting invalid flags.
+
+ [% ELSIF san_tag == "flag_deletion_end" %]
+ Invalid flags deleted.
+
+ [% ELSIF san_tag == "flag_alert" %]
+ Invalid flag [% flag_id FILTER html %] for
+ [% IF attach_id %]
+ attachment [% attach_id FILTER html %] in
+ [% END %]
+ [%+ PROCESS bug_link bug_id = bug_id %].
+
+ [% ELSIF san_tag == "flag_fix" %]
+ <a href="sanitycheck.cgi?remove_invalid_flags=1&amp;token=
+ [%- issue_hash_token(['sanitycheck']) FILTER url_quote %]">Click
+ here to delete invalid flags</a>
+
+ [% ELSIF san_tag == "group_control_map_entries_creation" %]
+ OK, now creating <code>SHOWN</code> member control entries
+ for product/group combinations lacking one.
+
+ [% ELSIF san_tag == "group_control_map_entries_update" %]
+ Updating <code>NA/<em>xxx</em></code> group control setting
+ for group <em>[% group_name FILTER html %]</em> to
+ <code>SHOWN/<em>xxx</em></code> in product
+ <em>[% product_name FILTER html %]</em>.
+
+ [% ELSIF san_tag == "group_control_map_entries_generation" %]
+ Generating <code>SHOWN/NA</code> group control setting
+ for group <em>[% group_name FILTER html %]</em> in product
+ <em>[% product_name FILTER html %]</em>.
+
+ [% ELSIF san_tag == "group_control_map_entries_repaired" %]
+ Repaired [% counter FILTER html %] defective group control settings.
+
+ [% ELSIF san_tag == "keyword_check_start" %]
+ Checking keywords table.
+
+ [% ELSIF san_tag == "keyword_check_alert" %]
+ Duplicate entry in keyworddefs for id [% id FILTER html %].
+
+ [% ELSIF san_tag == "keyword_check_invalid_name" %]
+ Bogus name in keyworddefs for id [% id FILTER html %].
+
+ [% ELSIF san_tag == "keyword_check_invalid_id" %]
+ Bogus keywordids [% id FILTER html %] found in keywords table.
+
+ [% ELSIF san_tag == "keyword_check_duplicated_ids" %]
+ Duplicate keyword IDs found in [% PROCESS bug_link bug_id = id %].
+
+ [% ELSIF san_tag == "profile_login_start" %]
+ Checking profile logins.
+
+ [% ELSIF san_tag == "profile_login_alert" %]
+ Bad profile email address, id=[% id FILTER html %],
+ &lt;[% email FILTER html %]&gt;.
+
+ [% ELSIF san_tag == "repair_bugs" %]
+ Repair these [% terms.bugs %].
+
+ [% ELSIF san_tag == "send_bugmail_start" %]
+ OK, now attempting to send unsent mail.
+
+ [% ELSIF san_tag == "send_bugmail_status" %]
+ [% bug_count FILTER html %] [%+ terms.bugs %] found with
+ possibly unsent mail.
+
+ [% ELSIF san_tag == "send_bugmail_end" %]
+ Unsent mail has been sent.
+
+ [% ELSIF san_tag == "unsent_bugmail_check" %]
+ Checking for unsent mail
+
+ [% ELSIF san_tag == "unsent_bugmail_alert" %]
+ [% terms.Bugs %] that have changes but no mail sent for at least
+ half an hour: [% INCLUDE bug_list badbugs = badbugs %]
+
+ [% ELSIF san_tag == "unsent_bugmail_fix" %]
+ <a href="sanitycheck.cgi?rescanallBugMail=1&amp;token=
+ [%- issue_hash_token(['sanitycheck']) FILTER url_quote %]">Send these mails</a>.
+
+ [% ELSIF san_tag == "whines_obsolete_target_deletion_start" %]
+ OK, now removing non-existent users/groups from whines.
+
+ [% ELSIF san_tag == "whines_obsolete_target_deletion_end" %]
+ Non-existent users/groups have been removed from whines.
+
+ [% ELSIF san_tag == "whines_obsolete_target_start" %]
+ Checking for whines with non-existent users/groups.
+
+ [% ELSIF san_tag == "whines_obsolete_target_alert" %]
+ [% FOREACH schedule = schedules %]
+ Non-existent [% (type == constants.MAILTO_USER) ? "user" : "group" FILTER html %]
+ [%+ schedule.1 FILTER html %] for whine schedule [% schedule.0 FILTER html %]<br>
+ [% END %]
+
+ [% ELSIF san_tag == "whines_obsolete_target_fix" %]
+ <a href="sanitycheck.cgi?remove_old_whine_targets=1&amp;token=
+ [%- issue_hash_token(['sanitycheck']) FILTER url_quote %]">Click here to
+ remove old users/groups</a>
+
+ [% ELSE %]
+ [% message = Hook.process("statuses") %]
+
+ [% IF message %]
+ [% message FILTER none %]
+ [% ELSE %]
+ The status message string <code>[% san_tag FILTER html %]</code>
+ was not found. Please send email to [% Param("maintainer") %] describing
+ the steps taken to obtain this message.
+ [% END %]
+
+ [% END %]
+[% END %]
+
+[% USE Bugzilla %]
+[% IF Bugzilla.usage_mode == constants.USAGE_MODE_CMDLINE %]
+ [% san_message FILTER none %]
+[% ELSE %]
+ [%# Avoid the txt filter in message.txt.tmpl. %]
+ [% san_message FILTER html %]
+[% END %]
+
+[% BLOCK bug_list %]
+ [% FOREACH bug_id = badbugs %]
+ [%# Do not use FILTER bug_link() here, because bug_link() calls get_text()
+ # which itself calls this template again, generating a recursion error.
+ # I doubt having a tooltip with the bug status and summary is so
+ # important here anyway, as you can click the "(as buglist)" link. %]
+ <a href="show_bug.cgi?id=[% bug_id FILTER url_quote %]">[% bug_id FILTER html %]</a>
+ [% ", " IF !loop.last %]
+ [% END %]
+ (<a href="buglist.cgi?bug_id=[% badbugs.join(",") FILTER url_quote %]">as [% terms.bug %] list</a>).
+[% END %]
+
+[% BLOCK bug_link %]
+ <a href="show_bug.cgi?id=[% bug_id FILTER url_quote %]">[% terms.bug %] [%+ bug_id FILTER html %]</a>
+[% END %]
diff --git a/template/en/default/admin/settings/edit.html.tmpl b/template/en/default/admin/settings/edit.html.tmpl
new file mode 100644
index 000000000..7f95f883e
--- /dev/null
+++ b/template/en/default/admin/settings/edit.html.tmpl
@@ -0,0 +1,101 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Shane H. W. Travis <travis@sedsystems.ca>
+ #
+ #%]
+
+[%# INTERFACE:
+ # settings: a hash of hashes, keyed by setting name.
+ # Each hash contains:
+ # is_enabled - boolean
+ # default_value - string (global default for this setting)
+ # value - string (user-defined preference)
+ # is_default - boolean (true if user has no preference)
+ #%]
+
+[% PROCESS global/header.html.tmpl
+ title = "Default Preferences"
+ %]
+
+[% PROCESS "global/setting-descs.none.tmpl" %]
+
+<p>
+This lets you edit the default preferences values.
+</p>
+<p>
+The Default Value displayed for each preference will apply to all users who
+do not choose their own value, and to anyone who is not logged in.
+</p>
+<p>
+The 'Enabled' checkbox controls whether or not this preference is available
+to users.<br>
+If it is checked, users will see this preference on their User Preferences page,
+and will be allowed to choose their own value if they desire.<br>
+If it is not checked, this preference will not appear on the User Preference
+page, and the Default Value will automatically apply to everyone.
+</p>
+<hr>
+
+[% IF settings.size %]
+ <form name="adminsettingform" method="post" action="editsettings.cgi">
+ <table border="1" cellpadding="4">
+ <tr>
+ <th>Preference Text</th>
+ <th>Default Value</th>
+ <th>Enabled</th>
+ </tr>
+
+ [% FOREACH name = settings.keys %]
+ [% checkbox_name = name _ '-enabled' %]
+ <tr>
+ <td align="right">
+ [% setting_descs.$name OR name FILTER html %]
+ </td>
+ <td>
+ <select name="[% name FILTER html %]" id="[% name FILTER html %]">
+ [% FOREACH x = settings.${name}.legal_values %]
+ <option value="[% x FILTER html %]"
+ [% " selected=\"selected\"" IF x == settings.${name}.default_value %]>
+ [% setting_descs.${x} OR x FILTER html %]
+ </option>
+ [% END %]
+ </select>
+ </td>
+ <td align="center">
+ <input type="checkbox"
+ name="[% checkbox_name FILTER html %]"
+ id="[% checkbox_name FILTER html %]"
+ [% " checked=\"checked\"" IF settings.${name}.is_enabled %]>
+ <br>
+ </td>
+ </tr>
+ [% END %]
+ </table>
+
+ <input type="hidden" name="action" value="update">
+ <input type="hidden" name="token" value="[% token FILTER html %]">
+ <table>
+ <tr>
+ <td width="150"></td>
+ <td>
+ <input type="submit" id="update" value="Submit Changes">
+ </td>
+ </tr>
+ </table>
+
+ </form>
+[% ELSE %]
+ There are no preferences to edit.
+[% END %]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/sudo.html.tmpl b/template/en/default/admin/sudo.html.tmpl
new file mode 100644
index 000000000..283eebe15
--- /dev/null
+++ b/template/en/default/admin/sudo.html.tmpl
@@ -0,0 +1,104 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 2005 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): A. Karl Kornel <karl@kornel.name>
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% PROCESS global/header.html.tmpl
+ title = "Begin sudo session"
+ style_urls = ['skins/standard/admin.css']
+ doc_section = "useradmin.html#impersonatingusers"
+ %]
+
+[% DEFAULT target_login = "" %]
+
+<p>
+ The <b>sudo</b> feature of [% terms.Bugzilla %] allows you to impersonate a
+ user for a short time While an sudo session is in progress, every action you
+ perform will be taking place as if you had logged in as the user whom will be
+ impersonating.
+</p>
+
+<p class="areyoureallyreallysure">
+ This is a very powerful feature; you should be very careful while using it.
+ Your actions may be logged more carefully than normal.
+</p>
+
+<form action="relogin.cgi" method="POST">
+ <p>
+ To begin,
+ [% IF Param('usemenuforusers') %]
+ select
+ [% ELSE %]
+ enter the login of
+ [% END %]
+ <label for="target_login">the <u>u</u>ser to impersonate</label>:
+ [% INCLUDE global/userselect.html.tmpl
+ id => "target_login"
+ name => "target_login"
+ value => target_login_default
+ accesskey => "u"
+ size => 30
+ %]
+ </p>
+
+ [% IF !Param('usemenuforusers') %]
+ <p>
+ The username must be entered exactly. No matching will be performed.
+ </p>
+ [% END %]
+
+ <p>
+ Next, please take a moment to explain <label for="reason">why you are doing
+ this:<br>
+ <input type="text" id="reason" name="reason" size="80" maxlength="200"
+ value="[% reason_default FILTER html %]">
+ </p>
+
+ <p>
+ The message you enter here will be sent to the impersonated user by email.
+ You may leave this empty if you wish, but they will still know that you
+ are impersonating them.
+ </p>
+
+ [% IF user.authorizer.can_login %]
+ <p>
+ Finally, enter <label for="Bugzilla_password">your [% terms.Bugzilla %]
+ password</label>:
+ <input type="hidden" name="Bugzilla_login" value="
+ [%- user.login FILTER html %]">
+ <input type="password" id="Bugzilla_password" name="Bugzilla_password" size="20">
+ <br>
+ This is done for two reasons. First of all, it is done to reduce
+ the chances of someone doing large amounts of damage using your
+ already-logged-in account. Second, it is there to force you to take the
+ time to consider if you really need to use this feature.
+ </p>
+ [% END %]
+
+ <p>
+ Click the button to begin the session:
+ <input type="submit" value="Begin Session">
+ <input type="hidden" name="action" value="begin-sudo">
+ <input type="hidden" name="token" value="[% token FILTER html %]">
+ </p>
+
+</form>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/table.html.tmpl b/template/en/default/admin/table.html.tmpl
new file mode 100644
index 000000000..c7177a66f
--- /dev/null
+++ b/template/en/default/admin/table.html.tmpl
@@ -0,0 +1,194 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Jouni Heikniemi <jouni@heikniemi.net>
+ #
+ #%]
+
+[%# INTERFACE:
+ #
+ # columns:
+ # array of hashes representing the columns in the table.
+ # Each hash contains data for a single column. Hash keys:
+ # name: Name of the field in the data param
+ # heading: The text to print at the header cell
+ # contentlink: URI to which the content of a data cell shall be linked to.
+ # Expressions of format %%xxx%% are replaced with value
+ # with the key xxx in data hash of the current row.
+ # content: If specified, the content of this variable is used
+ # instead of the data pulled from the current row.
+ # NOTE: This value is only partially HTML filtered!
+ # content_use_field: If defined and true, then each value in the
+ # column corresponds with a key in the
+ # field_descs field, and that value from the
+ # field_descs hash will be used instead of "content."
+ # See fieldvalues/select-field for an example of use.
+ # This content WILL be HTML-filtered in this case.
+ # align: left/center/right. Controls the horizontal alignment of the
+ # text in the column.
+ # allow_html_content: if defined, then this column allows some html content
+ # and so it will be only partially filtered.
+ # yesno_field: Turn the data from 0/!0 into Yes/No
+ #
+ # data:
+ # array of hashes representing the data for the table.
+ # Each hash contains data for a single row of data. The
+ # keys are column names from columns subhashes name field.
+ #
+ # overrides:
+ # Example:
+ # overrides { # first hash
+ # column_name_to_be_overwriten => { # second hash
+ # name_of_row_to_match_against => { # third hash
+ # value_to_match_against => { # fourth hash
+ # content => "some contents"
+ # override_content => 1
+ # }
+ # }
+ # }
+ # }
+ #
+ # Provides a method for overriding individual table cells. This is a hash
+ # (1), whose key is the column name, so the column must be named for
+ # one of it's cells to be overwritten. The hash value is another hash
+ # (2). The keys of that second hash are the name of the row to match
+ # against. The second hash then again points to another hash. Within this
+ # third hash (3), the keys represent values to match against. The item
+ # contains a fourth hash (4) specifying overridden values.
+ #
+ # Each column value mentioned in the 'columns' documentation above
+ # can be overwritten (apart from name and heading). To override a
+ # table-cell value 'xxx', specify a new 'xxx' value, and specify a
+ # 'override_xxx' value as well. See
+ # admin/milestones/list.html.tmpl for example
+ #
+ #%]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+[%################### TABLE HEADER ######################%]
+
+<table id="admin_table" border="1" cellpadding="4" cellspacing="0">
+ <tr bgcolor="#6666FF">
+ [% FOREACH c = columns %]
+ [%# Default to align left for headers %]
+ <th align="[% (c.align || 'left') FILTER html %]">
+ [% c.heading FILTER html %]
+ </th>
+ [% END %]
+ </tr>
+
+
+[%################### TABLE CONTENT ######################%]
+
+[% FOREACH row = data %]
+
+ <tr>
+ [% FOREACH c = columns %]
+
+ [%# Copy to local variables, as we may update these %]
+ [% contentlink = c.contentlink
+ content = c.content
+ content_use_field = c.content_use_field
+ align = c.align
+ class = c.class
+ allow_html_content = c.allow_html_content
+ yesno_field = c.yesno_field
+ %]
+
+ [%# Get any specific "important" overrides for this c.name and row.name ? %]
+ [% SET important = overrides.${c.name}.name.${row.name} %]
+
+ [% IF important %]
+
+ [% FOREACH key IN important.keys %]
+ [% SET ${key} = important.${key} %]
+ [% END %]
+
+ [% ELSE %]
+
+ [%# Are there any specific overrides for this column? %]
+ [% FOREACH match_field = overrides.${c.name}.keys %]
+
+ [% override = overrides.${c.name}.${match_field}.${row.$match_field} %]
+ [% NEXT UNLESS override %]
+
+ [% FOREACH key IN override.keys %]
+ [% SET ${key} = override.${key} %]
+ [% END %]
+
+ [% LAST %]
+
+ [% END %]
+ [% END %]
+
+ <td [% IF align %] align="[% align FILTER html %]" [% END %]
+ [% IF class %] class="[% class FILTER html %]" [% END %]>
+
+ [% IF contentlink %]
+ [% link_uri = contentlink %]
+ [% WHILE link_uri.search('%%(.+?)%%')%]
+ [% FOREACH m = link_uri.match('%%(.+?)%%') %]
+ [% IF row.$m.defined %]
+ [% replacement_value = FILTER url_quote; row.$m; END %]
+ [% ELSE %]
+ [% replacement_value = "" %]
+ [% END %]
+ [% link_uri = link_uri.replace("%%$m%%", replacement_value) %]
+ [% END %]
+ [% END %]
+ <a href="[% link_uri %]">
+ [% END %]
+
+ [% IF content_use_field %]
+ [% colname = row.${c.name} %]
+ [% field_descs.${colname} FILTER html %]
+ [% ELSIF content %]
+ [% content FILTER html_light %]
+ [% ELSE %]
+ [% IF yesno_field %]
+ [% IF row.${c.name} %]
+ Yes
+ [% ELSE %]
+ No
+ [% END %]
+ [% ELSE %]
+ [% IF allow_html_content %]
+ [% row.${c.name} FILTER html_light %]
+ [% ELSE %]
+ [% row.${c.name} FILTER html %]
+ [% END %]
+ [% END %]
+ [% END %]
+
+ [% IF contentlink %]
+ </a>
+ [% END %]
+
+ </td>
+ [% END %]
+ </tr>
+[% END %]
+
+[% IF data.size == 0 %]
+ <tr><td colspan="[% columns.size %]" align="center"><i>&lt;none&gt;</i></td></tr>
+[% END %]
+
+
+[%################### TABLE FOOTER ######################%]
+
+</table>
diff --git a/template/en/default/admin/users/confirm-delete.html.tmpl b/template/en/default/admin/users/confirm-delete.html.tmpl
new file mode 100644
index 000000000..4711376b0
--- /dev/null
+++ b/template/en/default/admin/users/confirm-delete.html.tmpl
@@ -0,0 +1,469 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Marc Schumann <wurblzap@gmail.com>
+ #%]
+
+[%# INTERFACE:
+ #
+ # listselectionvalues: selection values to recreate the current user
+ # list.
+ # editusers: is viewing user member of editusers?
+ # otheruser: Bugzilla::User object of the viewed user.
+ # reporter: number of bugs reported by the user
+ # assignee_or_qa: number of bugs the user is either the assignee
+ # or the QA contact
+ # bugs_activity: number of bugs the viewed user has activity
+ # entries on
+ # cc number of bugs the viewed user is cc list member
+ # of
+ # flags.requestee: number of flags the viewed user is being asked for
+ # flags.setter: number of flags the viewed user has set
+ # longdescs: number of bug comments the viewed user has written
+ # namedqueries: number of named queries the user has created
+ # namedquery_group_map: number of named queries the user has shared
+ # profiles_activity: number of changes made to other users' profiles
+ # series: number of series the viewed user has created
+ # watch.watched: number of users the viewed user is being watched
+ # by
+ # watch.watcher: number of users the viewed user is watching
+ # whine_events: number of whine events the viewed user has created
+ # whine_schedules: number of whine schedules the viewed user has
+ # created
+ #%]
+
+[% title = BLOCK %]Confirm deletion of user [% otheruser.login FILTER html %][% END %]
+
+[% PROCESS global/header.html.tmpl
+ title = title
+ style_urls = ['skins/standard/admin.css',
+ 'skins/standard/editusers.css']
+ doc_section = "useradmin.html#user-account-deletion"
+%]
+
+[% PROCESS admin/users/listselectvars.html.tmpl
+ listselectionvalues = listselectionvalues
+%]
+
+<table class="main">
+ <tr>
+ <th>Login name:</th>
+ <td>[% otheruser.login FILTER html %]</td>
+ </tr>
+ <tr>
+ <th>Real name:</th>
+ <td>[% otheruser.name FILTER html %]</td>
+ </tr>
+ <tr>
+ <th>Group set:</th>
+ <td>
+ [% IF otheruser.groups.size %]
+ <ul>
+ [% FOREACH group = otheruser.groups %]
+ <li>[% group.name FILTER html %]</li>
+ [% END %]
+ </ul>
+ [% ELSE %]
+ None
+ [% END %]
+ </td>
+ </tr>
+ [% IF otheruser.product_responsibilities.size %]
+ <tr>
+ <th>Product responsibilities:</th>
+ <td>
+ [% PROCESS admin/users/responsibilities.html.tmpl otheruser = otheruser %]
+ </td>
+ </tr>
+ [% END %]
+</table>
+
+[% IF otheruser.product_responsibilities.size %]
+ <p>
+ You can't delete this user at this time because
+ [%+ otheruser.login FILTER html %] has got responsibilities for at least
+ one product.
+ </p>
+ <p>
+ [% IF user.in_group("editcomponents", component.product_id) %]
+ Change this by clicking the product editing links above,
+ [% ELSE %]
+ For now, you can
+ [% END %]
+[% ELSE %]
+ [% accept_deletion = 1 %]
+
+ [% IF attachments || reporter || bugs_activity || flags.setter || longdescs || profiles_activity %]
+ <div class="criticalmessages">
+ <p>The following deletions are <b>unsafe</b> and would generate referential
+ integrity inconsistencies!</p>
+
+ <ul>
+ [% IF attachments %]
+ <li>
+ [% otheruser.login FILTER html %]
+ <a href="buglist.cgi?field0-0-0=attachments.submitter&type0-0-0=equals&value0-0-0=
+ [%- otheruser.login FILTER url_quote %]">has submitted
+ [% IF attachments == 1 %]
+ one attachment
+ [% ELSE %]
+ [%+ attachments %] attachments
+ [% END %]</a>.
+ If you delete the user account, the database records will be
+ inconsistent, resulting in
+ [% IF attachments == 1 %]
+ this attachment
+ [% ELSE %]
+ these attachments
+ [% END %]
+ not appearing in [% terms.bugs %] any more.
+ </li>
+ [% END %]
+ [% IF reporter %]
+ <li>
+ [% otheruser.login FILTER html %]
+ <a href="buglist.cgi?emailreporter1=1&amp;emailtype1=exact&amp;email1=
+ [%- otheruser.login FILTER url_quote %]">has reported
+ [% IF reporter == 1 %]
+ one [% terms.bug %]
+ [% ELSE %]
+ [%+ reporter %] [%+ terms.bugs %]
+ [% END %]</a>.
+ If you delete the user account, the database records will be
+ inconsistent, resulting in
+ [% IF reporter == 1 %]
+ this [% terms.bug %]
+ [% ELSE %]
+ these [% terms.bugs %]
+ [% END %]
+ not appearing in [% terms.bug %] lists any more.
+ </li>
+ [% END %]
+ [% IF bugs_activity %]
+ <li>
+ [% otheruser.login FILTER html %] has made
+ [% IF bugs_activity == 1 %]
+ a change on [% terms.abug %]
+ [% ELSE %]
+ changes on [% terms.bugs %]
+ [% END %].
+ If you delete the user account, the [% terms.bugs %] activity table in
+ the database will be inconsistent, resulting in
+ [% IF bugs_activity == 1 %]
+ this change
+ [% ELSE %]
+ these changes
+ [% END %]
+ not showing up in [% terms.bug %] activity logs any more.
+ </li>
+ [% END %]
+ [% IF flags.setter %]
+ <li>
+ [% otheruser.login FILTER html %] has
+ <a href="buglist.cgi?field0-0-0=setters.login_name&amp;type0-0-0=equals&amp;value0-0-0=
+ [%- otheruser.login FILTER url_quote %]">set
+ or requested
+ [% IF flags.setter == 1 %]
+ a flag
+ [% ELSE %]
+ [%+ flags.setter %] flags
+ [% END %]</a>.
+ If you delete the user account, the flags table in the database
+ will be inconsistent, resulting in
+ [% IF flags.setter == 1 %]
+ this flag
+ [% ELSE %]
+ these flags
+ [% END %]
+ not displaying correctly any more.
+ </li>
+ [% END %]
+ [% IF longdescs %]
+ <li>
+ [% otheruser.login FILTER html %] has
+ <a href="buglist.cgi?emaillongdesc1=1&amp;emailtype1=exact&amp;email1=
+ [%- otheruser.login FILTER url_quote %]">commented
+ [% IF longdescs == 1 %]
+ once on [% terms.abug %]
+ [% ELSE %]
+ [%+ longdescs %] times on [% terms.bugs %]
+ [% END %]</a>.
+ If you delete the user account, the comments table in the database
+ will be inconsistent, resulting in
+ [% IF longdescs == 1 %]
+ this comment
+ [% ELSE %]
+ these comments
+ [% END %]
+ not being visible any more.
+ </li>
+ [% END %]
+ [% IF profiles_activity %]
+ <li>
+ [% otheruser.login FILTER html %] has made
+ [% IF bugs_activity == 1 %]
+ a change on a other user's profile
+ [% ELSE %]
+ changes on other users' profiles
+ [% END %].
+ If you delete the user account, the user profiles activity table in
+ the database will be inconsistent.
+ </li>
+ [% END %]
+ </ul>
+ </div>
+ [% accept_deletion = 0 %]
+ [% END %]
+
+ [% IF assignee_or_qa || cc || component_cc || email_setting || flags.requestee ||
+ namedqueries || profile_setting || quips || series || watch.watched ||
+ watch.watcher || whine_events || whine_schedules || other_safe %]
+ <div class="warningmessages">
+ <p>The following deletions are <b>safe</b> and will not generate
+ referential integrity inconsistencies.</p>
+
+ <ul>
+ [% IF assignee_or_qa %]
+ <li>
+ [% otheruser.login FILTER html %]
+ <a href="buglist.cgi?emailassigned_to1=1&amp;emailqa_contact1=1&amp;emailtype1=exact&amp;email1=
+ [%- otheruser.login FILTER url_quote %]">is
+ the assignee or the QA contact of
+ [% IF assignee_or_qa == 1 %]
+ one [% terms.bug %]
+ [% ELSE %]
+ [%+ assignee_or_qa %] [%+ terms.bugs %]
+ [% END %]</a>.
+ If you delete the user account, these roles will fall back to
+ the default assignee or default QA contact.
+ </li>
+ [% END %]
+ [% IF cc %]
+ <li>
+ [% otheruser.login FILTER html %]
+ <a href="buglist.cgi?emailcc1=1&amp;emailtype1=exact&amp;email1=
+ [%- otheruser.login FILTER url_quote %]">is
+ on the CC list of
+ [% IF cc == 1 %]
+ [%+ terms.abug %]
+ [% ELSE %]
+ [%+ cc %] [%+ terms.bugs %]
+ [% END %]</a>.
+ If you delete the user account, it will be removed from these CC lists.
+ </li>
+ [% END %]
+ [% IF component_cc %]
+ <li>
+ [% otheruser.login FILTER html %] is on the default CC list of
+ [% IF component_cc == 1 %]
+ one component
+ [% ELSE %]
+ [%+ component_cc %] components
+ [% END %].
+ If you delete the user account, it will be removed from these CC lists.
+ </li>
+ [% END %]
+ [% IF email_setting %]
+ <li>
+ The user's e-mail settings will be deleted along with the user
+ account.
+ </li>
+ [% END %]
+ [% IF flags.requestee %]
+ <li>
+ [% otheruser.login FILTER html %] has been
+ <a href="buglist.cgi?field0-0-0=requestees.login_name&amp;type0-0-0=equals&amp;value0-0-0=
+ [%- otheruser.login FILTER url_quote %]">asked
+ to set
+ [% IF flags.requestee == 1 %]
+ a flag
+ [% ELSE %]
+ [% flags.requestee %] flags
+ [% END %]</a>.
+ If you delete the user account,
+ [% IF flags.requestee == 1 %]
+ this flag
+ [% ELSE %]
+ these flags
+ [% END %]
+ will change to be unspecifically requested.
+ </li>
+ [% END %]
+ [% IF namedqueries %]
+ <li>
+ [% otheruser.login FILTER html %] has
+ [% IF namedqueries == 1 %]
+ a [% 'shared' IF namedquery_group_map %] named search
+ [% ELSE %]
+ [%+ namedqueries FILTER html %] named searches
+ [% END %].
+ [% IF namedqueries == 1 %]
+ This named search
+ [% ELSE %]
+ These named searches
+ [% END %]
+ will be deleted along with the user account.
+ [% IF namedquery_group_map %]
+ [% IF namedqueries > 1 %]
+ Of these,
+ [% IF namedquery_group_map > 1 %]
+ [%+ namedquery_group_map FILTER html %] are
+ [% ELSE %]
+ one is
+ [% END %]
+ shared.
+ [% END %]
+ Other users will not be able to use
+ [% IF namedquery_group_map > 1 %]
+ these shared named searches
+ [% ELSE %]
+ this shared named search
+ [% END %]
+ any more.
+ [% END %]
+ </li>
+ [% END %]
+ [% IF profile_setting %]
+ <li>
+ The user's preference settings will be deleted along with the user
+ account.
+ </li>
+ [% END %]
+ [% IF series %]
+ <li>
+ [% otheruser.login FILTER html %] has created
+ [% IF series == 1 %]
+ a series
+ [% ELSE %]
+ [%+ series %] series
+ [% END %].
+ [% IF series == 1 %]
+ This series
+ [% ELSE %]
+ These series
+ [% END %]
+ will be deleted along with the user account.
+ </li>
+ [% END %]
+ [% IF quips %]
+ <li>
+ [% otheruser.login FILTER html %] has submitted
+ [% IF quips == 1 %]
+ a quip
+ [% ELSE %]
+ [%+ quips %] quips
+ [% END %].
+ If you delete the user account,
+ [% IF quips == 1 %]
+ this quip
+ [% ELSE %]
+ these quips
+ [% END %]
+ will have no author anymore, but will remain available.
+ </li>
+ [% END %]
+ [% IF watch.watched || watch.watcher %]
+ <li>
+ [% otheruser.login FILTER html %]
+ [% IF watch.watched %]
+ is being watched by
+ [% IF watch.watched == 1 %]
+ a user
+ [% ELSE %]
+ [%+ watch.watched %] users
+ [% END %]
+ [% END %]
+ [% IF watch.watcher %]
+ [%+ 'and' IF watch.watched %]
+ watches
+ [% IF watch.watcher == 1 %]
+ a user
+ [% ELSE %]
+ [%+ watch.watcher %] users
+ [% END %]
+ [% END %].
+ [% IF watch.watched + watch.watcher == 1 %]
+ This watching
+ [% ELSE %]
+ These watchings
+ [% END %]
+ will cease along with the deletion of the user account.
+ </li>
+ [% END %]
+ [% IF whine_events %]
+ <li>
+ [% otheruser.login FILTER html %] has scheduled
+ [% IF whine_events == 1 %]
+ a whine
+ [% ELSE %]
+ [%+ whine_events %] whines
+ [% END %].
+ [% IF whine_events == 1 %]
+ This whine
+ [% ELSE %]
+ These whines
+ [% END %]
+ will be deleted along with the user account.
+ </li>
+ [% END %]
+ [% IF whine_schedules %]
+ <li>
+ [% otheruser.login FILTER html %] is on the receiving end of
+ [% IF whine_schedules == 1 %]
+ a whine
+ [% ELSE %]
+ [%+ whine_schedules %] whines
+ [% END %].
+ The corresponding schedules will be deleted along with the user account,
+ but the whines themselves will be left unaltered.
+ </li>
+ [% END %]
+ [% Hook.process('warn_safe') %]
+ </ul>
+ </div>
+
+ [% IF accept_deletion %]
+ <p class="areyoureallyreallysure">
+ Please be aware of the consequences of this before continuing.
+ </p>
+ <p>Do you really want to delete this user account?</p>
+
+ <form method="post" action="editusers.cgi">
+ <p>
+ <input type="submit" id="delete" value="Yes, delete"/>
+ <input type="hidden" name="action" value="delete" />
+ <input type="hidden" name="userid" value="[% otheruser.id %]" />
+ <input type="hidden" name="token" value="[% token FILTER html %]">
+ [% INCLUDE listselectionhiddenfields %]
+ </p>
+ </form>
+ <p>If you do not want to delete the user account at this time,
+ [% ELSE %]
+ <p><b>You cannot delete this user account</b> due to unsafe actions reported above. You can
+ [% END %]
+
+ [% END %]
+[% END %]
+
+ <a href="editusers.cgi?action=edit&amp;userid=[% otheruser.id %]
+ [% INCLUDE listselectionurlparams %]">edit the user</a>,
+ go
+ <a href="editusers.cgi?action=list[% INCLUDE listselectionurlparams %]">back
+ to the user list</a>,
+ [% IF editusers %]
+ <a href="editusers.cgi?action=add[% INCLUDE listselectionurlparams %]">add
+ a new user</a>,
+ [% END %]
+ or <a href="editusers.cgi">find other users</a>.
+</p>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/users/create.html.tmpl b/template/en/default/admin/users/create.html.tmpl
new file mode 100644
index 000000000..6fd5b67e7
--- /dev/null
+++ b/template/en/default/admin/users/create.html.tmpl
@@ -0,0 +1,58 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Marc Schumann <wurblzap@gmail.com>
+ #%]
+
+[%# INTERFACE:
+ #
+ # listselectionvalues: selection values to recreate the current user list.
+ # editusers: is viewing user member of editusers?
+ #%]
+
+[% PROCESS global/header.html.tmpl
+ title = "Add user"
+ style_urls = ['skins/standard/editusers.css']
+ onload = "document.forms['f'].login.focus()"
+ doc_section = "useradmin.html#createnewusers"
+%]
+
+[% PROCESS admin/users/listselectvars.html.tmpl
+ listselectionvalues = listselectionvalues
+%]
+
+<form name="f" method="post" action="editusers.cgi">
+<table class="main">
+ [% PROCESS admin/users/userdata.html.tmpl
+ editform = 0
+ editusers = editusers
+ otheruser = []
+ %]
+</table>
+<p>
+ <input type="submit" id="add" value="Add"/>
+ <input type="hidden" name="action" value="new" />
+ <input type="hidden" name="token" value="[% token FILTER html %]">
+ [% INCLUDE listselectionhiddenfields %]
+</p>
+</form>
+
+<p>
+ You can also <a href="editusers.cgi">find a user</a>
+ [% IF listselectionvalues %],
+ or
+ <a href="editusers.cgi?action=list[% INCLUDE listselectionurlparams %]">go
+ back to the user list</a>
+ [% END %].
+</p>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/users/edit.html.tmpl b/template/en/default/admin/users/edit.html.tmpl
new file mode 100644
index 000000000..3efa4b8bf
--- /dev/null
+++ b/template/en/default/admin/users/edit.html.tmpl
@@ -0,0 +1,168 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Marc Schumann <wurblzap@gmail.com>
+ #%]
+
+[%# INTERFACE:
+ #
+ # message: message tag specifying a global/messages.html.tmpl
+ # message
+ # listselectionvalues: selection values to recreate the current user list.
+ # editusers: is viewing user member of editusers?
+ # otheruser: Bugzilla::User object of viewed user.
+ # groups: array of group information (name, grant type,
+ # canbless) for viewed user.
+ #%]
+
+[% title = BLOCK %]Edit user [% otheruser.identity FILTER html %][% END %]
+
+[% PROCESS global/header.html.tmpl
+ title = title
+ message = message
+ style_urls = ['skins/standard/admin.css', 'skins/standard/editusers.css']
+ doc_section = "useradmin.html#modifyusers"
+%]
+
+[% PROCESS admin/users/listselectvars.html.tmpl
+ listselectionvalues = listselectionvalues
+%]
+
+<form method="post" action="editusers.cgi">
+<table class="main">
+ [% PROCESS admin/users/userdata.html.tmpl
+ editform = 1
+ editusers = editusers
+ otheruser = otheruser
+ %]
+ [% IF groups.size %]
+ <tr>
+ <th>Group access:</th>
+ <td>
+ <table class="groups">
+ <tr>
+ [% IF editusers %]
+ <th colspan="3">
+ Can turn these bits on for other users
+ </th>
+ [% END %]
+ </tr>
+ <tr>
+ [% IF editusers %]
+ <td style="text-align: center; font-weight: bold">|</td>
+ [% END %]
+ <th colspan="2">User is a member of these groups</th>
+ </tr>
+ [% FOREACH group = groups %]
+ [% perms = permissions.${group.id} %]
+ <tr class="[% 'in' IF perms.regexpmember || perms.derivedmember %]direct">
+ [% IF editusers %]
+ <td class="checkbox">
+ [% '[' IF perms.indirectbless %]
+ [% %]<input type="checkbox"
+ name="bless_[% group.id %]"
+ value="1"
+ [% ' checked="checked"' IF perms.directbless %] />
+ [% ']' IF perms.indirectbless %]</td>
+ [% END %]
+ <td class="checkbox">
+ [% '[' IF perms.derivedmember %]
+ [% '*' IF perms.regexpmember %]
+ [%%]<input type="checkbox"
+ id="group_[% group.id %]"
+ name="group_[% group.id %]"
+ value="1"
+ [% ' checked="checked"' IF perms.directmember %] />
+ [% '*' IF perms.regexpmember %]
+ [% ']' IF perms.derivedmember %]</td>
+ <td class="groupname">
+ <label for="group_[% group.id %]">
+ <strong>[% group.name FILTER html %]:</strong>
+ [%+ group.description FILTER html_light %]
+ </label>
+ </td>
+ </tr>
+ [% END %]
+ </table>
+ </td>
+ </tr>
+ [% END %]
+
+ <tr>
+ <th>Product responsibilities:</th>
+ <td>
+ [% IF otheruser.product_responsibilities.size %]
+ [% PROCESS admin/users/responsibilities.html.tmpl otheruser = otheruser %]
+ [% ELSE %]
+ <em>none</em>
+ [% END %]
+ </td>
+ </tr>
+</table>
+
+<p>
+ <input type="submit" id="update" value="Save Changes" />
+ <input type="hidden" name="userid" value="[% otheruser.id %]" />
+ <input type="hidden" name="action" value="update" />
+ <input type="hidden" name="token" value="[% token FILTER html %]">
+ [% INCLUDE listselectionhiddenfields %]
+
+ or <a href="editusers.cgi?action=activity&amp;userid=[% otheruser.id %]"
+ title="View Account History for '
+ [%- otheruser.login FILTER html %]'">View Account History</a>
+</p>
+</form>
+<p>
+ User is a member of any groups shown with a check or grey bar.
+ A grey bar indicates indirect membership, either derived from other
+ groups (marked with square brackets) or via regular expression
+ (marked with '*').
+</p>
+[% IF editusers %]
+ <p>
+ Square brackets around the bless checkbox indicate the ability
+ to bless users (grant them membership in the group) as a result
+ of membership in another group.
+ </p>
+[% END %]
+
+[% IF Param('allowuserdeletion') && editusers %]
+ <form method="post" action="editusers.cgi">
+ <p>
+ <input type="submit" id="delete" value="Delete User" />
+ <input type="hidden" name="action" value="del" />
+ <input type="hidden" name="userid" value="[% otheruser.id %]" />
+ [% INCLUDE listselectionhiddenfields %]
+ </p>
+ </form>
+[% END %]
+
+<p>
+ You can also
+ [% IF editusers %]
+ <a href="editusers.cgi?action=add[% INCLUDE listselectionurlparams %]">add
+ a new user</a>
+ [% IF listselectionvalues %],
+ [% END %]
+ [% END %]
+ [% IF listselectionvalues.matchtype != 'exact' %]
+ go
+ <a href="editusers.cgi?action=list[% INCLUDE listselectionurlparams %]">back
+ to the user list</a>,
+ [% END %]
+ [% IF editusers OR listselectionvalues %]
+ or
+ [% END %]
+ <a href="editusers.cgi">find other users</a>.
+</p>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/users/list.html.tmpl b/template/en/default/admin/users/list.html.tmpl
new file mode 100644
index 000000000..cb05e827b
--- /dev/null
+++ b/template/en/default/admin/users/list.html.tmpl
@@ -0,0 +1,115 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Marc Schumann <wurblzap@gmail.com>
+ #%]
+
+[%# INTERFACE:
+ #
+ # listselectionvalues: selection values to recreate the current user list.
+ # editusers: is viewing user member of editusers?
+ # users: list of user information (id, login_name, realname,
+ # disabledtext).
+ #%]
+
+[% PROCESS global/header.html.tmpl
+ title = "Select user"
+ style_urls = ['skins/standard/editusers.css']
+ doc_section = "useradmin.html"
+%]
+
+[% PROCESS admin/users/listselectvars.html.tmpl
+ listselectionvalues = listselectionvalues
+%]
+
+[% listselectionurlparams = INCLUDE listselectionurlparams %]
+
+[% columns =
+ [{name => 'login_name'
+ heading => 'Edit user...'
+ contentlink => 'editusers.cgi?action=edit&amp;userid=%%userid%%' _
+ listselectionurlparams
+ }
+ {name => 'realname'
+ heading => 'Real name'
+ }
+ {heading => 'Account History'
+ content => 'View'
+ contentlink => 'editusers.cgi?action=activity' _
+ '&amp;userid=%%userid%%' _
+ listselectionurlparams
+ }
+ ]
+%]
+
+[% IF Param('allowuserdeletion') && editusers %]
+ [% columns.push({heading => 'Action'
+ content => 'Delete'
+ contentlink => 'editusers.cgi?action=del' _
+ '&amp;userid=%%userid%%' _
+ listselectionurlparams
+ }
+ )
+ %]
+[% END %]
+
+[%# Disabled users are crossed out. Missing realnames are noticed in red. %]
+[% overrides.login_name = {} %]
+[% overrides.realname = {} %]
+
+[% FOREACH thisuser = users %]
+ [% IF !thisuser.realname %]
+ [%# We cannot pass one class now and one class later. %]
+ [% SET classes = (thisuser.disabledtext ? "bz_inactive missing" : "missing") %]
+ [% overrides.realname.login_name.${thisuser.login_name} = {
+ content => "missing"
+ override_content => 1
+ class => "$classes"
+ override_class => 1
+ }
+ %]
+ [% ELSIF thisuser.disabledtext %]
+ [% overrides.realname.login_name.${thisuser.login_name} = {
+ class => "bz_inactive"
+ override_class => 1
+ }
+ %]
+ [% END %]
+
+ [% IF thisuser.disabledtext %]
+ [% overrides.login_name.login_name.${thisuser.login_name} = {
+ class => "bz_inactive"
+ override_class => 1
+ }
+ %]
+ [% END %]
+[% END %]
+
+<p>[% users.size %] user[% "s" UNLESS users.size == 1 %] found.</p>
+
+[% PROCESS admin/table.html.tmpl
+ columns = columns
+ data = users
+ overrides = overrides
+%]
+
+<p>
+ If you do not wish to modify a user account at this time, you can
+ <a href="editusers.cgi">find other users</a>
+ [% IF editusers %]
+ or
+ <a href="editusers.cgi?action=add[% INCLUDE listselectionurlparams %]">add
+ a new user</a>
+ [% END %].
+</p>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/users/listselectvars.html.tmpl b/template/en/default/admin/users/listselectvars.html.tmpl
new file mode 100644
index 000000000..a6eae5791
--- /dev/null
+++ b/template/en/default/admin/users/listselectvars.html.tmpl
@@ -0,0 +1,33 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Marc Schumann <wurblzap@gmail.com>
+ #%]
+
+[%# INTERFACE:
+ #
+ # listselectionvalues: selection values to recreate the current user list.
+ #%]
+
+[% BLOCK listselectionurlparams %]
+ [% FOREACH field = listselectionvalues.keys %]&amp;
+ [% field FILTER url_quote %]=
+ [% listselectionvalues.$field FILTER url_quote %]
+ [% END %]
+[% END %]
+
+[% BLOCK listselectionhiddenfields %]
+ [% FOREACH field = listselectionvalues.keys %]
+ <input type="hidden" name="[% field FILTER html %]"
+ value="[% listselectionvalues.$field FILTER html %]" />
+ [% END %]
+[% END %]
diff --git a/template/en/default/admin/users/responsibilities.html.tmpl b/template/en/default/admin/users/responsibilities.html.tmpl
new file mode 100644
index 000000000..5c9c3f317
--- /dev/null
+++ b/template/en/default/admin/users/responsibilities.html.tmpl
@@ -0,0 +1,65 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Marc Schumann <wurblzap@gmail.com>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+
+[% hidden_products = 0 %]
+<table id="user_responsibilities" border="0">
+ [% FOREACH item = otheruser.product_responsibilities %]
+ [% IF !user.can_see_product(item.product.name) %]
+ [% hidden_products = 1 %]
+ [% NEXT %]
+ [% END %]
+ <tbody>
+ <tr>
+ <th colspan="3" class="product">Product: [% item.product.name FILTER html %]</th>
+ </tr>
+ <tr>
+ <th>Component</th>
+ <th>Default Assignee</th>
+ <th>Default QA Contact</th>
+ <th>Default CC</th>
+ </tr>
+ [% FOREACH component = item.components %]
+ <tr>
+ <td>
+ [% IF user.in_group("editcomponents", component.product_id) %]
+ <a href="editcomponents.cgi?action=edit&amp;product=
+ [% item.product.name FILTER url_quote %]&amp;component=
+ [% component.name FILTER url_quote %]">
+ [% END %]
+ [% component.name FILTER html %]
+ [% IF user.in_group("editcomponents", component.product_id) %]
+ </a>
+ [% END %]
+ </td>
+ [% FOREACH responsibility = ['default_assignee', 'default_qa_contact'] %]
+ <td class="center">
+ [% component.$responsibility.id == otheruser.id ? "X" : "&nbsp;" %]
+ </td>
+ [% END %]
+ <td class="center">
+ [% component.initial_cc.contains(otheruser) ? "X" : "&nbsp;" %]
+ </td>
+ </tr>
+ [% END %]
+ </tbody>
+ [% END %]
+</table>
+
+[% IF hidden_products %]
+ <p class="criticalmessages">The user is involved in at least one product which you cannot
+ see (and so is not listed above). You have to ask an administrator with enough
+ privileges to edit this user's roles for these products.</p>
+[% END %]
diff --git a/template/en/default/admin/users/search.html.tmpl b/template/en/default/admin/users/search.html.tmpl
new file mode 100644
index 000000000..82e0afda7
--- /dev/null
+++ b/template/en/default/admin/users/search.html.tmpl
@@ -0,0 +1,78 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Marc Schumann <wurblzap@gmail.com>
+ # David Lawrence <dkl@redhat.com>
+ #%]
+
+[%# INTERFACE:
+ #
+ # editusers: is viewing user member of editusers?
+ # restrictablegroups: list of groups visible to the user:
+ # id: group id
+ # name: group name
+ #%]
+
+
+[% PROCESS global/header.html.tmpl
+ title = "Search users"
+ style_urls = ['skins/standard/editusers.css']
+ onload = "document.forms['f'].matchstr.focus()"
+ doc_section = "useradmin.html#user-account-search"
+%]
+
+[% PROCESS admin/users/listselectvars.html.tmpl
+ listselectionvalues = listselectionvalues
+%]
+
+<form name="f" method="get" action="editusers.cgi">
+<input type="hidden" name="action" value="list" />
+<p><label for="matchvalue">List users with</label>
+<select id="matchvalue" name="matchvalue">
+ <option value="login_name">login name</option>
+ <option value="realname">real name</option>
+ <option value="userid">user id</option>
+</select>
+<label for="matchstr">matching</label>
+<input size="32" name="matchstr" id="matchstr" />
+<select name="matchtype">
+ <option value="substr" selected="selected">case-insensitive substring</option>
+ <option value="regexp">case-insensitive regexp</option>
+ <option value="notregexp">not (case-insensitive regexp)</option>
+ <option value="exact">exact (find this user)</option>
+</select>
+<input type="submit" id="search" value="Search" /></p>
+
+[% IF restrictablegroups.size %]
+ <p><input type="checkbox" name="grouprestrict" value="1" id="grouprestrict" />
+ <label for="grouprestrict">Restrict to users belonging to group</label>
+ <select name="groupid"
+ onchange="document.forms['f'].grouprestrict.checked=true">
+ [% FOREACH group = restrictablegroups %]
+ <option value="[% group.id FILTER html %]">[% group.name FILTER html %]</option>
+ [% END %]
+ </select></p>
+[% END %]
+</form>
+
+[% IF editusers %]
+ <p>
+ You can also <a href="editusers.cgi?action=add">add a new user</a>
+ [%- IF listselectionvalues %],
+ or
+ <a href="editusers.cgi?action=list[% INCLUDE listselectionurlparams %]">show
+ the user list again</a>
+ [%- END %].
+ </p>
+[% END %]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/users/userdata.html.tmpl b/template/en/default/admin/users/userdata.html.tmpl
new file mode 100644
index 000000000..9b182fa14
--- /dev/null
+++ b/template/en/default/admin/users/userdata.html.tmpl
@@ -0,0 +1,97 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Marc Schumann <wurblzap@gmail.com>
+ #%]
+
+[%# INTERFACE:
+ #
+ # editform: is this an edit form? (It's a create form otherwise)
+ # editusers: is viewing user member of editusers?
+ # otheruser: Bugzilla::User object of user to edit
+ #%]
+
+<tr>
+ <th><label for="login">Login name:</label></th>
+ <td>
+ [% IF editusers %]
+ <input size="64" maxlength="255" name="login"
+ id="login" value="[% otheruser.login FILTER html %]" />
+ [% IF editform %]
+ [% IF !otheruser.in_group('bz_sudo_protect') %]
+ <br />
+ <a href="relogin.cgi?action=prepare-sudo&amp;target_login=
+ [%- otheruser.login FILTER url_quote %]">Impersonate this user</a>
+ [% END %]
+ [% END %]
+ [% ELSE %]
+ [% otheruser.login FILTER html %]
+ [% END %]
+ </td>
+</tr>
+<tr>
+ <th><label for="name">Real name:</label></th>
+ <td>
+ [% IF editusers %]
+ <input size="64" maxlength="255" name="name"
+ autocomplete="off"
+ id="name" value="[% otheruser.name FILTER html %]" />
+ [% ELSE %]
+ [% otheruser.name FILTER html %]
+ [% END %]
+ </td>
+</tr>
+
+[%# XXX This condition (can_change_password) will cause a problem
+ # if we ever have a login system that can create accounts through
+ # createaccount.cgi but can't change passwords.
+ #%]
+
+[% IF editusers %]
+ [% IF user.authorizer.can_change_password %]
+ <tr>
+ <th><label for="password">Password:</label></th>
+ <td>
+ <input type="password" size="16" name="password" id="password"
+ value="" autocomplete="off" />
+ [% IF editform %]<br />
+ (Enter new password to change.)
+ [% END %]
+ </td>
+ </tr>
+ [% END %]
+ <tr>
+ <th><label for="disable_mail">[% terms.Bug %]mail Disabled:</label></th>
+ <td>
+ <input type="checkbox" name="disable_mail" id="disable_mail" value="1"
+ [% IF otheruser.email_disabled %] checked="checked" [% END %] />
+ (This affects [% terms.bug %]mail and whinemail, not password-reset or other
+ non-[% terms.bug %]-related emails)
+ </td>
+ </tr>
+ <tr>
+ <th><label for="disabledtext">Disable text:</label></th>
+ <td>
+ [% INCLUDE global/textarea.html.tmpl
+ name = 'disabledtext'
+ id = 'disabledtext'
+ minrows = 2
+ maxrows = 10
+ defaultrows = 10
+ cols = 60
+ defaultcontent = otheruser.disabledtext
+ %]<br>
+ (If non-empty, then the account will be disabled, and this text should
+ explain why.)
+ </td>
+ </tr>
+[% END %]
diff --git a/template/en/default/admin/versions/confirm-delete.html.tmpl b/template/en/default/admin/versions/confirm-delete.html.tmpl
new file mode 100644
index 000000000..88ffceb31
--- /dev/null
+++ b/template/en/default/admin/versions/confirm-delete.html.tmpl
@@ -0,0 +1,101 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gavin Shelley <bugzilla@chimpychompy.org>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+
+[%# INTERFACE:
+ # product: object; Bugzilla::Product object representing the product to
+ # which the version belongs.
+ # version: object; Bugzilla::Version object representing the
+ # version the user wants to delete.
+ #%]
+
+[% title = BLOCK %]Delete Version of Product '[% product.name FILTER html %]'
+ [% END %]
+
+[% PROCESS global/header.html.tmpl
+ title = title
+%]
+
+<table border="1" cellpadding="4" cellspacing="0">
+<tr bgcolor="#6666FF">
+ <th valign="top" align="left">Field</th>
+ <th valign="top" align="left">Value</th>
+</tr>
+<tr>
+ <td valign="top">Version:</td>
+ <td valign="top">[% version.name FILTER html %]</td>
+</tr>
+<tr>
+ <td valign="top">Version of Product:</td>
+ <td valign="top">[% product.name FILTER html %]</td>
+</tr>
+<tr>
+ <td valign="top">[% terms.Bugs %]:</td>
+ <td valign="top">
+[% IF version.bug_count %]
+ <a title="List of [% terms.bugs %] targetted at version '
+ [%- version.name FILTER html %]'"
+ href="buglist.cgi?version=[% version.name FILTER url_quote %]&amp;product=
+ [%- product.name FILTER url_quote %]">
+ [%- version.bug_count FILTER none %]</a>
+[% ELSE %]
+ None
+[% END %]
+ </td>
+</tr>
+</table>
+
+<h2>Confirmation</h2>
+
+[% IF version.bug_count %]
+ <p>
+ Sorry, there
+ [% IF version.bug_count > 1 %]
+ are [% version.bug_count FILTER none %] [%+ terms.bugs %]
+ [% ELSE %]
+ is [% version.bug_count FILTER none %] [%+ terms.bug %]
+ [% END %]
+
+ outstanding for this version. You must move
+
+ [% IF version.bug_count > 1 %]
+ those [% terms.bugs %]
+ [% ELSE %]
+ that [% terms.bug %]
+ [% END %]
+ to another version before you can delete this one.
+ </p>
+[% ELSE %]
+
+ <p>Do you really want to delete this version?</p>
+
+ <form method="post" action="editversions.cgi">
+ <input type="submit" id="delete" value="Yes, delete">
+ <input type="hidden" name="action" value="delete">
+ <input type="hidden" name="product" value="[% product.name FILTER html %]">
+ <input type="hidden" name="version" value="[% version.name FILTER html %]">
+ <input type="hidden" name="token" value="[% token FILTER html %]">
+ </form>
+
+[% END %]
+
+[% PROCESS admin/versions/footer.html.tmpl %]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/versions/create.html.tmpl b/template/en/default/admin/versions/create.html.tmpl
new file mode 100644
index 000000000..8b4ba6473
--- /dev/null
+++ b/template/en/default/admin/versions/create.html.tmpl
@@ -0,0 +1,52 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gavin Shelley <bugzilla@chimpychompy.org>
+ #%]
+
+[%# INTERFACE:
+ # product: object; Bugzilla::Product object representing the product to
+ # which the version is being created for
+ #%]
+
+[% title = BLOCK %]Add Version to Product '[% product.name FILTER html %]'[% END %]
+[% subheader = BLOCK %]This page allows you to add a new version to product
+ '[% product.name FILTER html %]'.[% END %]
+[% PROCESS global/header.html.tmpl
+ title = title
+ subheader = subheader
+%]
+
+<form method="post" action="editversions.cgi">
+ <table border="0" cellpadding="4" cellspacing="0">
+ <tr>
+ <th align="right"><label for="version">Version:</label></th>
+ <td><input id="version" size="64" maxlength="64" name="version"
+ value=""></td>
+ </tr>
+ </table>
+ <input type="submit" id="create" value="Add">
+ <input type="hidden" name="action" value="new">
+ <input type="hidden" name='product' value="[% product.name FILTER html %]">
+ <input type="hidden" name="token" value="[% token FILTER html %]">
+</form>
+
+[% PROCESS admin/versions/footer.html.tmpl
+ no_add_version_link = 1
+ %]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/versions/edit.html.tmpl b/template/en/default/admin/versions/edit.html.tmpl
new file mode 100644
index 000000000..2a7c78423
--- /dev/null
+++ b/template/en/default/admin/versions/edit.html.tmpl
@@ -0,0 +1,57 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gavin Shelley <bugzilla@chimpychompy.org>
+ #%]
+
+[%# INTERFACE:
+ # product: object; Bugzilla::Product object representing the product to
+ # which the version belongs.
+ # version: object; Bugzilla::Version object representing the
+ # version the user wants to edit.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% title = BLOCK %]Edit Version '[% version.name FILTER html %]' of product '
+ [%- product.name FILTER html %]'[% END %]
+[% PROCESS global/header.html.tmpl
+ title = title
+%]
+
+<form method="post" action="editversions.cgi">
+ <table border="0" cellpadding="4" cellspacing="0">
+
+ <tr>
+ <th valign="top"><label for="version">Version:</label></th>
+ <td><input id="version" size="64" maxlength="64" name="version" value="
+ [%- version.name FILTER html %]"></td>
+ </tr>
+
+ </table>
+
+ <input type="hidden" name="versionold" value="[% version.name FILTER html %]">
+ <input type="hidden" name="action" value="update">
+ <input type="hidden" name="product" value="[% product.name FILTER html %]">
+ <input type="hidden" name="token" value="[% token FILTER html %]">
+ <input type="submit" id="update" value="Save Changes">
+</form>
+
+[% PROCESS admin/versions/footer.html.tmpl
+ no_edit_version_link = 1 %]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/versions/footer.html.tmpl b/template/en/default/admin/versions/footer.html.tmpl
new file mode 100644
index 000000000..8d96a12e9
--- /dev/null
+++ b/template/en/default/admin/versions/footer.html.tmpl
@@ -0,0 +1,65 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gavin Shelley <bugzilla@chimpychompy.org>
+ #%]
+
+[%# INTERFACE:
+ # product: object; Bugzilla::Product object representing the product to
+ # which the version belongs.
+ # version: object; Bugzilla::Version object representing the version
+ #
+ # no_XXX_link: boolean; if defined, then don't show the corresponding
+ # link. Supported parameters are:
+ #
+ # no_edit_version_link
+ # no_edit_other_versions_link
+ # no_add_version_link
+ #%]
+
+<hr>
+
+<p>
+
+[% UNLESS no_add_version_link %]
+ <a title="Add a version to product '[% product.name FILTER html %]'"
+ href="editversions.cgi?action=add&amp;product=
+ [%- product.name FILTER url_quote %]">Add</a> a version.
+[% END %]
+
+[% IF version.name && !no_edit_version_link %]
+ Edit version <a
+ title="Edit Version '[% version.name FILTER html %]' of product '
+ [%- product.name FILTER html %]'"
+ href="editversions.cgi?action=edit&amp;product=
+ [%- product.name FILTER url_quote %]&amp;version=
+ [%- version.name FILTER url_quote %]">
+ '[% version.name FILTER html %]'</a>.
+[% END %]
+
+[% UNLESS no_edit_other_versions_link %]
+ Edit other versions of product <a
+ href="editversions.cgi?product=
+ [%- product.name FILTER url_quote %]">'[% product.name FILTER html %]'</a>.
+
+[% END %]
+
+ Edit product <a
+ href="editproducts.cgi?action=edit&amp;product=
+ [%- product.name FILTER url_quote %]">'[% product.name FILTER html %]'</a>.
+
+</p>
diff --git a/template/en/default/admin/versions/list.html.tmpl b/template/en/default/admin/versions/list.html.tmpl
new file mode 100644
index 000000000..401ee519b
--- /dev/null
+++ b/template/en/default/admin/versions/list.html.tmpl
@@ -0,0 +1,89 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gavin Shelley <bugzilla@chimpychompy.org>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+
+[%# INTERFACE:
+ # product: object; Bugzilla::Product object representing the product to
+ # which the versions belongs.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% title = BLOCK %]Select version of product
+ '[% product.name FILTER html %]'[% END %]
+[% PROCESS global/header.html.tmpl
+ title = title
+%]
+
+[% edit_contentlink = BLOCK %]editversions.cgi?action=edit&amp;product=
+ [%- product.name FILTER url_quote %]&amp;version=%%name%%[% END %]
+[% delete_contentlink = BLOCK %]editversions.cgi?action=del&amp;product=
+ [%- product.name FILTER url_quote %]&amp;version=%%name%%[% END %]
+[% bug_count_contentlink = BLOCK %]buglist.cgi?version=%%name%%&amp;product=
+ [%- product.name FILTER url_quote %][% END %]
+
+
+[% columns = [
+ {
+ name => "name"
+ heading => "Edit version..."
+ contentlink => edit_contentlink
+ }
+ ]
+%]
+
+[% IF showbugcounts %]
+
+ [% columns.push({
+ name => "bug_count"
+ heading => "$terms.Bugs"
+ align => "right"
+ contentlink => bug_count_contentlink
+ })
+ %]
+
+[% END %]
+
+[% columns.push({
+ heading => "Action"
+ content => "Delete"
+ contentlink => delete_contentlink
+ })
+%]
+
+[% Hook.process('before_table') %]
+
+[% PROCESS admin/table.html.tmpl
+ columns = columns
+ data = product.versions
+%]
+
+[% IF ! showbugcounts %]
+
+ <p><a href="editversions.cgi?product=[% product.name FILTER url_quote %]&amp;showbugcounts=1">
+ Redisplay table with [% terms.bug %] counts (slower)</a></p>
+
+[% END %]
+
+[% PROCESS admin/versions/footer.html.tmpl
+ no_edit_other_versions_link = 1
+ %]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/versions/select-product.html.tmpl b/template/en/default/admin/versions/select-product.html.tmpl
new file mode 100644
index 000000000..7fded475e
--- /dev/null
+++ b/template/en/default/admin/versions/select-product.html.tmpl
@@ -0,0 +1,70 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gavin Shelley <bugzilla@chimpychompy.org>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ #
+ #%]
+
+[%# INTERFACE:
+ # products: array of product objects
+ # showbugcounts: if defined, then bug counts should be included in the table
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% PROCESS global/header.html.tmpl
+ title = "Edit versions for which product?"
+%]
+
+[% columns = [
+ {
+ name => "name"
+ heading => "Edit versions of..."
+ contentlink => "editversions.cgi?product=%%name%%"
+ },
+ {
+ name => "description"
+ heading => "Description"
+ allow_html_content => 1
+ }
+ ]
+%]
+
+[% IF showbugcounts %]
+
+ [% columns.push({
+ name => 'bug_count'
+ heading => "$terms.Bugs"
+ align => "right"
+ contentlink => "buglist.cgi?product=%%name%%"
+ })
+ %]
+
+[% END %]
+
+[% PROCESS admin/table.html.tmpl
+ columns = columns
+ data = products
+%]
+
+[% IF !showbugcounts %]
+ <p><a href="editversions.cgi?showbugcounts=1">
+ Redisplay table with [% terms.bug %] counts (slower)</a></p>
+[% END %]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/workflow/comment.html.tmpl b/template/en/default/admin/workflow/comment.html.tmpl
new file mode 100644
index 000000000..a7a6a74c2
--- /dev/null
+++ b/template/en/default/admin/workflow/comment.html.tmpl
@@ -0,0 +1,92 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
+ # Gervase Markham <gerv@mozilla.org>
+ #%]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+[% INCLUDE global/header.html.tmpl
+ title = "Comments Required on Status Transitions"
+ style_urls = ['skins/standard/admin.css']
+%]
+
+<script type="text/javascript">
+<!--
+ function toggle_cell(cell) {
+ if (cell.checked)
+ cell.parentNode.className = "checkbox-cell checked";
+ else
+ cell.parentNode.className = "checkbox-cell";
+ }
+//-->
+</script>
+
+<p>
+ This page allows you to define which status transitions require a comment
+ by the user doing the change.
+</p>
+
+<form id="workflow_form" method="POST" action="editworkflow.cgi">
+<table>
+ <tr>
+ <th colspan="2">&nbsp;</th>
+ <th colspan="[% statuses.size FILTER html %]" class="title">To</th>
+ </tr>
+
+ <tr>
+ <th rowspan="[% statuses.size + 2 FILTER html %]" class="title">From</th>
+ <th>&nbsp;</th>
+ [% FOREACH status = statuses %]
+ <th class="col-header[% status.is_open ? " open-status" : " closed-status" %]">
+ [% display_value("bug_status", status.name) FILTER html %]
+ </th>
+ [% END %]
+ </tr>
+
+ [%# This defines the entry point in the workflow %]
+ [% p = [{id => 0, name => "{Start}", is_open => 1}] %]
+ [% FOREACH status = p.merge(statuses) %]
+ <tr class="highlight">
+ <th align="right" class="[% status.is_open ? "open-status" : "closed-status" %]">
+ [% display_value("bug_status", status.name) FILTER html %]
+ </th>
+
+ [% FOREACH new_status = statuses %]
+ [% IF workflow.${status.id}.${new_status.id}.defined %]
+ <td align="center" class="checkbox-cell
+ [% " checked" IF workflow.${status.id}.${new_status.id} %]"
+ title="From [% status.name FILTER html %] to [% new_status.name FILTER html %]">
+ <input type="checkbox" name="c_[% status.id %]_[% new_status.id %]"
+ id="c_[% status.id %]_[% new_status.id %]" onclick="toggle_cell(this)"
+ [% " checked='checked'" IF workflow.${status.id}.${new_status.id} %]>
+ </td>
+ [% ELSE %]
+ <td class="checkbox-cell forbidden">&nbsp;</td>
+ [% END %]
+ [% END %]
+ </tr>
+ [% END %]
+</table>
+
+<p align="center">
+ <input type="hidden" name="action" value="update_comment">
+ <input type="hidden" name="token" value="[% token FILTER html %]">
+ <input type="submit" value="Commit Changes"> -
+ <a href="editworkflow.cgi?action=edit_comment">Cancel Changes</a> -
+ <a href="editworkflow.cgi">View Current Workflow</a>
+</p>
+
+</form>
+
+[% INCLUDE global/footer.html.tmpl %]
diff --git a/template/en/default/admin/workflow/edit.html.tmpl b/template/en/default/admin/workflow/edit.html.tmpl
new file mode 100644
index 000000000..406c08a21
--- /dev/null
+++ b/template/en/default/admin/workflow/edit.html.tmpl
@@ -0,0 +1,110 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
+ # Gervase Markham <gerv@mozilla.org>
+ #%]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+[% INCLUDE global/header.html.tmpl
+ title = "Edit Workflow"
+ style_urls = ['skins/standard/admin.css']
+%]
+
+<script type="text/javascript">
+<!--
+ function toggle_cell(cell) {
+ if (cell.checked)
+ cell.parentNode.className = "checkbox-cell checked";
+ else
+ cell.parentNode.className = "checkbox-cell";
+ }
+//-->
+</script>
+
+<p>
+ This page allows you to define which status transitions are valid in your workflow.
+ For compatibility with older versions of [% terms.Bugzilla %], reopening [% terms.abug %]
+ will only display either [% display_value("bug_status", "UNCONFIRMED") FILTER html %] or
+ [%+ display_value("bug_status", "REOPENED") FILTER html %] (if allowed by your workflow) but not
+ both. The decision depends on whether the [% terms.bug %] has ever been confirmed or not.
+ So it is a good idea to allow both transitions and let [% terms.Bugzilla %] select the
+ correct one.
+</p>
+
+<form id="workflow_form" method="POST" action="editworkflow.cgi">
+<table>
+ <tr>
+ <th colspan="2">&nbsp;</th>
+ <th colspan="[% statuses.size FILTER html %]" class="title">To</th>
+ </tr>
+
+ <tr>
+ <th rowspan="[% statuses.size + 2 FILTER html %]" class="title">From</th>
+ <th>&nbsp;</th>
+ [% FOREACH status = statuses %]
+ <th class="col-header[% status.is_open ? " open-status" : " closed-status" %]">
+ [% display_value("bug_status", status.name) FILTER html %]
+ </th>
+ [% END %]
+ </tr>
+
+ [%# This defines the entry point in the workflow %]
+ [% p = [{id => 0, name => "{Start}", is_open => 1}] %]
+ [% FOREACH status = p.merge(statuses) %]
+ <tr class="highlight">
+ <th align="right" class="[% status.is_open ? "open-status" : "closed-status" %]">
+ [% display_value("bug_status", status.name) FILTER html %]
+ </th>
+
+ [% FOREACH new_status = statuses %]
+ [% IF status.id != new_status.id && (status.id || new_status.is_open) %]
+ [% checked = workflow.${status.id}.${new_status.id}.defined ? 1 : 0 %]
+ [% mandatory = (status.id && new_status.name == Param("duplicate_or_move_bug_status")) ? 1 : 0 %]
+ <td align="center" class="checkbox-cell[% " checked" IF checked || mandatory %]"
+ title="From [% status.name FILTER html %] to [% new_status.name FILTER html %]">
+ <input type="checkbox" name="w_[% status.id %]_[% new_status.id %]"
+ id="w_[% status.id %]_[% new_status.id %]" onclick="toggle_cell(this)"
+ [%+ "checked='checked'" IF checked || mandatory %]
+ [%+ "disabled='disabled'" IF mandatory %]>
+ </td>
+ [% ELSE %]
+ <td class="checkbox-cell forbidden">&nbsp;</td>
+ [% END %]
+ [% END %]
+ </tr>
+ [% END %]
+</table>
+
+<p>
+ When [% terms.abug %] is marked as a duplicate of another one or is moved
+ to another installation, the [% terms.bug %] status is automatically set to
+ <b>[% display_value("bug_status", Param("duplicate_or_move_bug_status")) FILTER html %]</b>. All transitions to
+ this [% terms.bug %] status must then be valid (this is the reason why you cannot edit
+ them above).<br>
+ Note: you can change this setting by visiting the
+ <a href="editparams.cgi?section=bugchange#duplicate_or_move_bug_status">Parameters</a>
+ page and editing the <i>duplicate_or_move_bug_status</i> parameter.
+</p>
+
+<p align="center">
+ <input type="hidden" name="action" value="update">
+ <input type="hidden" name="token" value="[% token FILTER html %]">
+ <input type="submit" value="Commit Changes"> -
+ <a href="editworkflow.cgi">Cancel Changes</a> -
+ <a href="editworkflow.cgi?action=edit_comment">View Comments Required on Status Transitions</a>
+</p>
+
+</form>
+
+[% INCLUDE global/footer.html.tmpl %]
diff --git a/template/en/default/attachment/cancel-create-dupe.html.tmpl b/template/en/default/attachment/cancel-create-dupe.html.tmpl
new file mode 100644
index 000000000..f838955bc
--- /dev/null
+++ b/template/en/default/attachment/cancel-create-dupe.html.tmpl
@@ -0,0 +1,48 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Olav Vitters.
+ #
+ # Contributor(s): Olav Vitters <olav@bkor.dhs.org>
+ # David Lawrence <dkl@redhat.com>
+ #%]
+
+[%# INTERFACE:
+ # bugid: integer. ID of the bug report that this attachment relates to.
+ # attachid: integer. ID of the previous attachment recently created.
+ #%]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+[% PROCESS global/header.html.tmpl
+ title = "Already filed attachment"
+%]
+
+[% USE Bugzilla %]
+
+<table cellpadding="20">
+ <tr>
+ <td bgcolor="#ff0000">
+ <font size="+2">
+ You already used the form to file
+ <a href="[% urlbase FILTER html %]attachment.cgi?id=[% attachid FILTER url_quote %]&action=edit">attachment [% attachid FILTER url_quote %]</a>.
+ </font>
+ </td>
+ </tr>
+</table>
+
+<p>
+ You can either <a href="[% urlbase FILTER html %]attachment.cgi?bugid=[% bugid FILTER url_quote %]&action=enter">
+ create a new attachment</a> or [% "go back to $terms.bug $bugid" FILTER bug_link(bugid) FILTER none %].
+<p>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/attachment/choose.html.tmpl b/template/en/default/attachment/choose.html.tmpl
new file mode 100644
index 000000000..700abb4e8
--- /dev/null
+++ b/template/en/default/attachment/choose.html.tmpl
@@ -0,0 +1,43 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gavin Shelley <bugzilla@chimpychompy.org>
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% PROCESS global/header.html.tmpl
+ title = "Locate attachment",
+ onload = "document.forms['choose-id'].id.focus()"
+ %]
+
+<form name="choose-id" method="get" action="attachment.cgi">
+ <p>Access an attachment by entering its ID into the form below:</p>
+ <p>Attachment ID: <input name="id" size="6">
+ <button name="action" value="edit" id="edit">Details</button>
+ <button name="action" value="view" id="view">View</button>
+ </p>
+</form>
+
+<form method="get" action="show_bug.cgi">
+ <p>Or, access it from the list of attachments in its associated [% terms.bug %] report:</p>
+ <p>[% terms.Bug %] ID: <input name="id" size="6">
+ <input type="submit" name="action" value="View" id="action">
+ </p>
+</form>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/attachment/confirm-delete.html.tmpl b/template/en/default/attachment/confirm-delete.html.tmpl
new file mode 100644
index 000000000..14c76c309
--- /dev/null
+++ b/template/en/default/attachment/confirm-delete.html.tmpl
@@ -0,0 +1,92 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+
+[%# INTERFACE:
+ # a: attachment object; attachment the user wants to delete.
+ # token: string; The token used to identify the session.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% title = BLOCK %]
+ Delete Attachment [% a.id FILTER html %] of [% terms.Bug %] [%+ a.bug_id FILTER html %]
+[% END %]
+
+[% PROCESS global/header.html.tmpl
+ title = title
+ doc_section = "attachments.html"
+%]
+
+<table border="1" cellpadding="4" cellspacing="0">
+ <tr bgcolor="#6666FF">
+ <th valign="top" align="left">Field</th>
+ <th valign="top" align="left">Value</th>
+ </tr>
+ <tr>
+ <td valign="top">Attachment ID:</td>
+ <td valign="top">
+ <a href="attachment.cgi?id=[% a.id FILTER html %]">[% a.id FILTER html %]</a>
+ </td>
+ </tr>
+ <tr>
+ <td valign="top">File name:</td>
+ <td valign="top">[% a.filename FILTER html %]</td>
+ </tr>
+ <tr>
+ <td valign="top">Description:</td>
+ <td valign="top">[% a.description FILTER html %]</td>
+ </tr>
+ <tr>
+ <td valign="top">Contained in [% terms.Bug %]:</td>
+ <td valign="top">[% a.bug_id FILTER bug_link(a.bug_id) FILTER none %]</td>
+ </tr>
+ <tr>
+ <td valign="top">Creator:</td>
+ <td valign="top">[% a.attacher.identity FILTER html %]</td>
+ </tr>
+ <tr>
+ <td valign="top">Creation Date:</td>
+ <td valign="top">[% a.attached FILTER time %]</td>
+ </tr>
+</table>
+
+<h2>Confirmation</h2>
+
+<table border="0" cellpadding="20" width="70%" bgcolor="red">
+ <tr>
+ <td>
+ The content of this attachment will be deleted in an <b>irreversible</b> way.
+ </td>
+ </tr>
+</table>
+
+<p>Do you really want to delete this attachment?</p>
+
+<form action="attachment.cgi" method="POST">
+ <label for="reason">Reason of the deletion:</label>
+ <input type="text" id="reason" name="reason" value="" size="80" maxlength="200">
+ <br>
+ <input type="submit" value="Yes, delete" id="delete">
+ <input type="hidden" name="action" value="delete">
+ <input type="hidden" name="id" value="[% a.id FILTER html %]">
+ <input type="hidden" name="token" value="[% token FILTER html %]">
+</form>
+
+<p>
+ No, cancel this deletion and return to
+ [%+ "$terms.bug " _ a.bug_id FILTER bug_link(a.bug_id) FILTER none %].
+</p>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/attachment/create.html.tmpl b/template/en/default/attachment/create.html.tmpl
new file mode 100644
index 000000000..acf9e23bb
--- /dev/null
+++ b/template/en/default/attachment/create.html.tmpl
@@ -0,0 +1,139 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Myk Melez <myk@mozilla.org>
+ # Joel Peshkin <bugreport@peshkin.net>
+ # Erik Stambaugh <erik@dasbistro.com>
+ # Marc Schumann <wurblzap@gmail.com>
+ #%]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+[%# Define strings that will serve as the title and header of this page %]
+[% title = BLOCK %]Create New Attachment for [% terms.Bug %] #[% bug.bug_id %][% END %]
+[% header = BLOCK %]Create New Attachment for
+ [%+ "$terms.Bug $bug.bug_id" FILTER bug_link(bug) FILTER none %][% END %]
+[% subheader = BLOCK %][% bug.short_desc FILTER html %][% END %]
+
+[% PROCESS global/header.html.tmpl
+ title = title
+ header = header
+ subheader = subheader
+ style_urls = [ 'skins/standard/attachment.css' ]
+ yui = [ 'autocomplete' ]
+ javascript_urls = [ "js/attachment.js", 'js/field.js', "js/util.js" ]
+ doc_section = "attachments.html"
+%]
+
+<form name="entryform" method="post" action="attachment.cgi"
+ enctype="multipart/form-data"
+ onsubmit="return validateAttachmentForm(this)">
+ <input type="hidden" name="bugid" value="[% bug.bug_id %]">
+ <input type="hidden" name="action" value="insert">
+ <input type="hidden" name="token" value="[% token FILTER html %]">
+
+ <table class="attachment_entry">
+ [% PROCESS attachment/createformcontents.html.tmpl %]
+
+ [%# Additional fields for attachments on existing bugs: %]
+ <tr>
+ <th>Obsoletes:</th>
+ <td>
+ <em>(optional) Check each existing attachment made obsolete by your new attachment.</em><br>
+ [% IF attachments.size %]
+ [% FOREACH attachment = attachments %]
+ [% IF ((attachment.isprivate == 0) || user.is_insider) %]
+ <input type="checkbox" id="[% attachment.id %]"
+ name="obsolete" value="[% attachment.id %]">
+ <a href="attachment.cgi?id=[% attachment.id %]&amp;action=edit">[% attachment.id %]: [% attachment.description FILTER html %]</a><br>
+ [% END %]
+ [% END %]
+ [% ELSE %]
+ [no attachments can be made obsolete]
+ [% END %]
+ </td>
+ </tr>
+
+ [% IF (user.id != bug.assigned_to.id) AND user.in_group("editbugs", bug.product_id) %]
+ <tr>
+ <th>Reassignment:</th>
+ <td>
+ <em>If you want to assign this [% terms.bug %] to yourself,
+ check the box below.</em><br>
+ <input type="checkbox" id="takebug" name="takebug" value="1">
+ <label for="takebug">take [% terms.bug %]</label>
+ [% bug_statuses = [] %]
+ [% FOREACH bug_status = bug.status.can_change_to %]
+ [% NEXT IF bug_status.name == "UNCONFIRMED"
+ && !bug.product_obj.allows_unconfirmed %]
+ [% bug_statuses.push(bug_status) IF bug_status.is_open %]
+ [% END %]
+ [% IF bug_statuses.size %]
+ <label for="takebug">and set the [% terms.bug %] status to</label>
+ <select id="bug_status" name="bug_status">
+ <option value="[% bug.status.name FILTER html %]">[% display_value("bug_status", bug.status.name) FILTER html %] (current)</option>
+ [% FOREACH bug_status = bug_statuses %]
+ [% NEXT IF bug_status.id == bug.status.id %]
+ <option value="[% bug_status.name FILTER html %]">[% display_value("bug_status", bug_status.name) FILTER html %]</option>
+ [% END %]
+ </select>
+ [% END %]
+ </td>
+ </tr>
+ [% END %]
+ <tr>
+ <th><label for="comment">Comment:</label></th>
+ <td>
+ <em>(optional) Add a comment about this attachment to the [% terms.bug %].</em><br>
+ [% INCLUDE global/textarea.html.tmpl
+ name = 'comment'
+ id = 'comment'
+ minrows = 6
+ maxrows = 15
+ cols = constants.COMMENT_COLS
+ wrap = 'soft'
+ %]
+ </td>
+ </tr>
+ [% IF user.is_insider %]
+ <tr>
+ <th>Privacy:</th>
+ <td>
+ <input type="checkbox" name="isprivate" id="isprivate"
+ value="1" onClick="updateCommentPrivacy(this)">
+ <label for="isprivate">
+ Make attachment and comment private (visible only to members of
+ the <strong>[% Param('insidergroup') FILTER html %]</strong>
+ group)
+ </label>
+ </td>
+ </tr>
+ [% END %]
+
+ [% Hook.process('form_before_submit') %]
+
+ <tr>
+ <th>&nbsp;</th>
+ <td><input type="submit" id="create" value="Submit"></td>
+ </tr>
+ </table>
+
+</form>
+
+[% Hook.process('end') %]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/attachment/created.html.tmpl b/template/en/default/attachment/created.html.tmpl
new file mode 100644
index 000000000..da2fec823
--- /dev/null
+++ b/template/en/default/attachment/created.html.tmpl
@@ -0,0 +1,67 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Myk Melez <myk@mozilla.org>
+ #%]
+
+[%# INTERFACE:
+ # attachment: object of the attachment just created.
+ # contenttypemethod: string. How we got the content type of the attachment.
+ # Possible values: autodetect, list, manual.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+[% bug = bugs.0 %]
+[% PROCESS "bug/show-header.html.tmpl" %]
+[% PROCESS global/header.html.tmpl
+ title = "Attachment $attachment.id added to $terms.Bug $attachment.bug_id"
+%]
+
+<dl>
+ <dt>
+ <a title="[% attachment.description FILTER html %]"
+ href="attachment.cgi?id=[% attachment.id %]&amp;action=edit">Attachment #[% attachment.id %]</a>
+ to [% "$terms.bug $attachment.bug_id" FILTER bug_link(attachment.bug_id) FILTER none %] created
+ </dt>
+ <dd>
+ [% PROCESS "bug/process/bugmail.html.tmpl" mailing_bugid = attachment.bug_id %]
+ [% IF convertedbmp %]
+ <p>
+ <b>Note:</b> [% terms.Bugzilla %] automatically converted your BMP image file to a
+ compressed PNG format.
+ </p>
+ [% END %]
+ [% IF contenttypemethod == 'autodetect' %]
+ <p>
+ <b>Note:</b> [% terms.Bugzilla %] automatically detected the content type
+ <em>[% attachment.contenttype FILTER html %]</em> for this attachment. If this is
+ incorrect, correct the value by editing the attachment's
+ <a href="attachment.cgi?id=[% attachment.id %]&amp;action=edit">details</a>.
+ </p>
+ [% END %]
+
+ [%# Links to more information about the changed bug. %]
+ [% Hook.process("links") %]
+ </dd>
+</dl>
+
+<p>
+<a href="attachment.cgi?bugid=[% attachment.bug_id %]&amp;action=enter">Create
+ Another Attachment to [% terms.Bug %] [%+ attachment.bug_id %]</a>
+</p>
+
+[% PROCESS bug/show.html.tmpl %]
diff --git a/template/en/default/attachment/createformcontents.html.tmpl b/template/en/default/attachment/createformcontents.html.tmpl
new file mode 100644
index 000000000..d0ca336fc
--- /dev/null
+++ b/template/en/default/attachment/createformcontents.html.tmpl
@@ -0,0 +1,122 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Myk Melez <myk@mozilla.org>
+ # Joel Peshkin <bugreport@peshkin.net>
+ # Erik Stambaugh <erik@dasbistro.com>
+ # Marc Schumann <wurblzap@gmail.com>
+ #%]
+
+<tr>
+ <th><label for="data">File</label>:</th>
+ <td>
+ <em>Enter the path to the file on your computer.</em><br>
+ <input type="file" id="data" name="data" size="50"
+ [% IF Param("allow_attach_url") %]
+ onchange="DataFieldHandler()"
+ [% END %]
+ >
+ </td>
+</tr>
+[% IF Param("maxlocalattachment") %]
+<tr class="expert_fields">
+ <th>BigFile:</th>
+ <td>
+ <input type="checkbox" id="bigfile"
+ name="bigfile" value="bigfile">
+ <label for="bigfile">
+ Big File - Stored locally and may be purged
+ </label>
+ </td>
+</tr>
+[% END %]
+[% IF Param("allow_attach_url") %]
+<tr class="expert_fields">
+ <th><label for="attachurl">AttachURL</label>:</th>
+ <td>
+ <em>URL to be attached instead.</em><br>
+ <input type="text" id="attachurl" name="attachurl" size="60"
+ maxlength="2000"
+ onkeyup="URLFieldHandler()" onblur="URLFieldHandler()">
+ </td>
+</tr>
+</tbody>
+[% END %]
+<tr>
+ <th><label for="description">Description</label>:</th>
+ <td>
+ <em>Describe the attachment briefly.</em><br>
+ <input type="text" id="description" name="description" size="60" maxlength="200">
+ </td>
+</tr>
+<tr class="expert_fields">
+ <th>Content Type:</th>
+ <td>
+ <em>If the attachment is a patch, check the box below.</em><br>
+ <input type="checkbox" id="ispatch" name="ispatch" value="1"
+ onchange="setContentTypeDisabledState(this.form);">
+ <label for="ispatch">patch</label><br><br>
+ [%# Reset this whenever the page loads so that the JS state is up to date %]
+ <script type="text/javascript">
+ YAHOO.util.Event.onDOMReady(function() {
+ bz_fireEvent(document.getElementById('ispatch'), 'change');
+ });
+ </script>
+
+ <em>Otherwise, choose a method for determining the content type.</em><br>
+ <input type="radio" id="autodetect"
+ name="contenttypemethod" value="autodetect" checked="checked">
+ <label for="autodetect">auto-detect</label><br>
+ <input type="radio" id="list"
+ name="contenttypemethod" value="list">
+ <label for="list">select from list</label>:
+ <select name="contenttypeselection" id="contenttypeselection"
+ onchange="this.form.contenttypemethod[1].checked = true;">
+ [% PROCESS content_types %]
+ </select><br>
+ <input type="radio" id="manual"
+ name="contenttypemethod" value="manual">
+ <label for="manual">enter manually</label>:
+ <input type="text" name="contenttypeentry" id="contenttypeentry"
+ size="30" maxlength="200"
+ onchange="if (this.value) this.form.contenttypemethod[2].checked = true;">
+ </td>
+</tr>
+<tr class="expert_fields">
+ <td> </td>
+ <td>
+ [% IF flag_types && flag_types.size > 0 %]
+ [% PROCESS "flag/list.html.tmpl" bug_id=bugid attach_id=attachid %]<br>
+ [% END %]
+ </td>
+</tr>
+
+[% BLOCK content_types %]
+ [% mimetypes = [{type => "text/plain", desc => "plain text"},
+ {type => "text/html", desc => "HTML source"},
+ {type => "application/xml", desc => "XML source"},
+ {type => "image/gif", desc => "GIF image"},
+ {type => "image/jpeg", desc => "JPEG image"},
+ {type => "image/png", desc => "PNG image"},
+ {type => "application/octet-stream", desc => "binary file"}]
+ %]
+ [% Hook.process("mimetypes", "attachment/createformcontents.html.tmpl") %]
+
+ [% FOREACH m = mimetypes %]
+ <option value="[% m.type FILTER html %]">[% m.desc FILTER html %] ([% m.type FILTER html %])</option>
+ [% END %]
+[% END %]
diff --git a/template/en/default/attachment/delete_reason.txt.tmpl b/template/en/default/attachment/delete_reason.txt.tmpl
new file mode 100644
index 000000000..e4a1fc41f
--- /dev/null
+++ b/template/en/default/attachment/delete_reason.txt.tmpl
@@ -0,0 +1,32 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+
+[%# INTERFACE:
+ # attachment: object of the attachment the user wants to delete.
+ # reason: string; The reason provided by the user.
+ # date: the date when the request to delete the attachment was made.
+ #%]
+
+The content of attachment [% attachment.id %] has been deleted by
+ [%+ user.identity %]
+[% IF reason %]
+who provided the following reason:
+
+[%+ reason %]
+[% ELSE %]
+without providing any reason.
+[% END %]
+
+The token used to delete this attachment was generated at [% date FILTER time %].
diff --git a/template/en/default/attachment/diff-file.html.tmpl b/template/en/default/attachment/diff-file.html.tmpl
new file mode 100644
index 000000000..a742a841e
--- /dev/null
+++ b/template/en/default/attachment/diff-file.html.tmpl
@@ -0,0 +1,177 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): John Keiser <jkeiser@netscape.com>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+
+[%# This line is really long for a reason: to get rid of any possible textnodes
+ # between the elements. This is necessary because DOM parent-child-sibling
+ # relations can change and screw up the javascript for restoring, collapsing
+ # and expanding. Do not change without testing all three of those.
+ # Also, the first empty row is required because 'table-layout: fixed' only
+ # considers the first row to determine column widths. If a colspan is found,
+ # it then share the width equally among all columns, which we don't want.
+ #%]
+<table class="file_table"><thead><tr><td class="num"></td><td></td><td class="num"></td><td></td></tr><tr><td class="file_head" colspan="4"><a href="#" onclick="return twisty_click(this)">[% collapsed ? '(+)' : '(-)' %]</a><input type="checkbox" name="[% file.filename FILTER html %]"[% collapsed ? '' : ' checked' %] style="display: none">
+ [% IF lxr_prefix && !file.is_add %]
+ <a href="[% lxr_prefix %]">[% file.filename FILTER html %]</a>
+ [% ELSE %]
+ [% file.filename FILTER html %]
+ [% END %]
+ [% IF file.plus_lines %]
+ [% IF file.minus_lines %]
+ (-[% file.minus_lines %]&nbsp;/&nbsp;+[% file.plus_lines %]&nbsp;lines)
+ [% ELSE %]
+ (+[% file.plus_lines %]&nbsp;lines)
+ [% END %]
+ [% ELSE %]
+ [% IF file.minus_lines %]
+ (-[% file.minus_lines %]&nbsp;lines)
+ [% END %]
+ [% END %]
+</td></tr></thead><tbody class="[% collapsed ? 'file_collapse' : 'file' %]">
+<script type="text/javascript">
+incremental_restore()
+</script>
+
+[% section_num = 0 %]
+[% FOREACH section = sections %]
+ [% section_num = section_num + 1 %]
+ <tr><th colspan="4" class="section_head">
+ <table id="[% file.filename FILTER html %]_sec[% section_num %]" cellpadding="0" cellspacing="0">
+ <tr><th width="95%" align="left">
+ [% IF file.is_add %]
+ Added
+ [% ELSIF file.is_remove %]
+ [% IF bonsai_prefix %]
+ <a href="[% bonsai_prefix %]">Removed</a>
+ [% ELSE %]
+ Removed
+ [% END %]
+ [% ELSE %]
+ [% IF bonsai_prefix %]
+ <a href="[% bonsai_prefix %]#[% section.old_start %]">
+ [% END %]
+ [% IF section.old_lines > 1 %]
+ &nbsp;Lines&nbsp;[% section.old_start %]-[% section.old_start +
+ section.old_lines - 1 %]
+ [% ELSE %]
+ Line&nbsp;[% section.old_start %]
+ [% END %]
+ [% IF bonsai_prefix %]
+ </a>
+ [% END %]
+ &nbsp;&nbsp;[% section.func_info FILTER html IF section.func_info %]
+ [% END %]
+ </th><th>
+ <a href="#[% file.filename FILTER html %]_sec[% section_num %]">Link&nbsp;Here</a>&nbsp;
+ </th></tr></table>
+ </th></tr>
+ [% current_line_old = section.old_start %]
+ [% current_line_new = section.new_start %]
+ [% FOREACH group = section.groups %]
+ [% IF group.context %]
+ [% FOREACH line = group.context %]
+ <tr>
+ <td class="num">[% current_line_old %]</td>
+ <td><pre>[% line FILTER html %]</pre></td>
+ <td class="num">[% current_line_new %]</td>
+ <td><pre>[% line FILTER html %]</pre></td>
+ </tr>
+ [% current_line_old = current_line_old + 1 %]
+ [% current_line_new = current_line_new + 1 %]
+ [% END %]
+ [% END %]
+ [% IF group.plus.size %]
+ [% IF group.minus.size %]
+ [% i = 0 %]
+ [% WHILE (i < group.plus.size || i < group.minus.size) %]
+ [%# WHILE cannot loop more than 1000 times by default, so we break it every 500 times. %]
+ [% currentloop = 0 %]
+ [% WHILE currentloop < 500 && (i < group.plus.size || i < group.minus.size) %]
+ <tr>
+ [% IF i < group.minus.size %]
+ <td class="num">[% current_line_old + i %]</td>
+ <td class="changed"><pre>[% group.minus.$i FILTER html %]</pre></td>
+ [% ELSIF i == group.minus.size %]
+ [% rowspan = group.plus.size - group.minus.size %]
+ <td class="num"[% IF rowspan > 1 %] rowspan="[% rowspan FILTER none %]"[% END %]></td>
+ <td class="changed"[% IF rowspan > 1 %] rowspan="[% rowspan FILTER none %]"[% END %]></td>
+ [% END %]
+
+ [% IF i < group.plus.size %]
+ <td class="num">[% current_line_new + i %]</td>
+ <td class="changed"><pre>[% group.plus.$i FILTER html %]</pre></td>
+ [% ELSIF i == group.plus.size %]
+ [% rowspan = group.minus.size - group.plus.size %]
+ <td class="num"[% IF rowspan > 1 %] rowspan="[% rowspan FILTER none %]"[% END %]></td>
+ <td class="changed"[% IF rowspan > 1 %] rowspan="[% rowspan FILTER none %]"[% END %]></td>
+ [% END %]
+ </tr>
+ [% currentloop = currentloop + 1 %]
+ [% i = i + 1 %]
+ [% END %]
+ [% END %]
+ [% current_line_old = current_line_old + group.minus.size %]
+ [% current_line_new = current_line_new + group.plus.size %]
+ [% ELSE %]
+ [% FOREACH line = group.plus %]
+ [% IF file.is_add %]
+ <tr>
+ <td class="num">[% current_line_new %]</td>
+ <td class="added" colspan="3"><pre>[% line FILTER html %]</pre></td>
+ </tr>
+ [% ELSE %]
+ <tr>
+ [% IF loop.first %]
+ <td class="num"[% IF group.plus.size > 1 %] rowspan="[% group.plus.size %]"[% END %]></td>
+ <td[% IF group.plus.size > 1 %] rowspan="[% group.plus.size %]"[% END %]></td>
+ [% END %]
+ <td class="num">[% current_line_new %]</td>
+ <td class="added"><pre>[% line FILTER html %]</pre></td>
+ </tr>
+ [% END %]
+ [% current_line_new = current_line_new + 1 %]
+ [% END %]
+ [% END %]
+ [% ELSE %]
+ [% IF group.minus.size %]
+ [% FOREACH line = group.minus %]
+ [% IF file.is_remove %]
+ <tr>
+ <td class="num">[% current_line_old %]</td>
+ <td class="removed" colspan="3"><pre>[% line FILTER html %]</pre></td>
+ </tr>
+ [% ELSE %]
+ <tr>
+ <td class="num">[% current_line_old %]</td>
+ <td class="removed"><pre>[% line FILTER html %]</pre></td>
+ [% IF loop.first %]
+ <td class="num"[% IF group.minus.size > 1 %] rowspan="[% group.minus.size %]"[% END %]></td>
+ <td[% IF group.minus.size > 1 %] rowspan="[% group.minus.size %]"[% END %]></td>
+ [% END %]
+ </tr>
+ [% END %]
+ [% current_line_old = current_line_old + 1 %]
+ [% END %]
+ [% END %]
+ [% END %]
+ [% END %]
+[% END %]
+
+</table>
diff --git a/template/en/default/attachment/diff-footer.html.tmpl b/template/en/default/attachment/diff-footer.html.tmpl
new file mode 100644
index 000000000..49c662a98
--- /dev/null
+++ b/template/en/default/attachment/diff-footer.html.tmpl
@@ -0,0 +1,35 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): John Keiser <jkeiser@netscape.com>
+ #%]
+
+</form>
+
+[% IF headers %]
+
+ <br>
+
+ [% PROCESS global/variables.none.tmpl %]
+ <span>Return to [% "$terms.bug $bugid" FILTER bug_link(bugid) FILTER none %]</span>
+
+ [% PROCESS global/footer.html.tmpl %]
+
+[% ELSE %]
+</body>
+</html>
+[% END %]
diff --git a/template/en/default/attachment/diff-header.html.tmpl b/template/en/default/attachment/diff-header.html.tmpl
new file mode 100644
index 000000000..22e101c64
--- /dev/null
+++ b/template/en/default/attachment/diff-header.html.tmpl
@@ -0,0 +1,155 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): John Keiser <jkeiser@netscape.com>
+ #%]
+
+[%# Define strings that will serve as the title and header of this page %]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% title = BLOCK %]
+ [% IF attachid %]
+Attachment #[% attachid %] for [% terms.bug %] #[% bugid %]
+ [% ELSE %]
+Interdiff of #[% oldid %] and #[% newid %] for [% terms.bug %] #[% bugid %]
+ [% END %]
+[% END %]
+
+[% onload = 'restore_all(); document.checkboxform.restore_indicator.checked = true' %]
+
+[% BLOCK viewurl %]attachment.cgi?id=[% id %][% END %]
+[% BLOCK editurl %][% PROCESS viewurl %]&amp;action=edit[% END %]
+[% BLOCK diffurl %][% PROCESS viewurl %]&amp;action=diff[% END %]
+
+[% IF headers %]
+ [% header = BLOCK %]
+ [% IF attachid %]
+ Attachment #[% attachid %]: [% description FILTER html %]
+ [% ELSE %]
+ Diff Between
+ #[% oldid %]: <a href="[% PROCESS diffurl id=oldid %]">[% old_desc FILTER html %]</a>
+ and
+ #[% newid %]: <a href="[% PROCESS diffurl id=newid %]">[% new_desc FILTER html %]</a>
+ [% END %]
+ for <a href="show_bug.cgi?id=[% bugid %]">[% terms.bug %] #[% bugid %]</a>
+ [% END %]
+ [% subheader = BLOCK %]
+ [% bugsummary FILTER html %]
+ [% END %]
+ [% PROCESS global/header.html.tmpl doc_section = "attachments.html#patchviewer"
+ javascript_urls = "js/attachment.js"
+ style_urls = ['skins/standard/attachment.css'] %]
+[% ELSE %]
+ <html>
+ <head>
+ <link href="[% 'skins/standard/attachment.css' FILTER mtime %]"
+ rel="stylesheet" type="text/css">
+ <script src="[% 'js/attachment.js' FILTER mtime %]"
+ type="text/javascript"></script>
+ </head>
+ <body onload="[% onload FILTER html %]">
+[% END %]
+
+[%# If we have attachid, we are in diff, otherwise we're in interdiff %]
+[% IF attachid %]
+ [%# HEADER %]
+ [% IF headers %]
+ <a href="[% PROCESS viewurl id=attachid %]">View</a>
+ | <a href="[% PROCESS editurl id=attachid %]">Details</a>
+ | <a href="[% PROCESS diffurl id=attachid %]&amp;context=[% context FILTER url_quote %]&amp;collapsed=[% collapsed FILTER url_quote %]&amp;headers=[% headers FILTER url_quote %]&amp;format=raw">Raw&nbsp;Unified</a>
+ | Return to [% "$terms.bug $bugid" FILTER bug_link(bugid) FILTER none %]
+ [% END %]
+ [% IF other_patches.size > 0 %]
+ [% IF headers %] |[%END%]
+ Differences between
+ <form style="display: inline" action="attachment.cgi">
+ <select name="oldid">
+ [% FOREACH patch = other_patches %]
+ <option value="[% patch.id %]"
+ [% IF patch.selected %] selected[% END %]
+ >[% patch.desc FILTER html %]</option>
+ [% END %]
+ </select>
+ and this patch
+ <input type="submit" id="diff" value="Diff">
+ <input type="hidden" name="action" value="interdiff">
+ <input type="hidden" name="newid" value="[% attachid %]">
+ <input type="hidden" name="headers" value="[% headers FILTER html %]">
+ </form>
+ [% END %]
+ <br>
+[% ELSE %]
+ [% IF headers %]
+ <a href="attachment.cgi?oldid=[% oldid %]&amp;newid=[% newid %]&amp;action=interdiff&amp;format=raw">Raw Unified</a>
+ | Return to [% "$terms.bug $bugid" FILTER bug_link(bugid) FILTER none %]
+ |
+ [% END %]
+[% END %]
+
+[%# Collapse / Expand %]
+<a href="#"
+ onmouseover="lastStatus = window.status; window.status='Collapse All'; return true"
+ onmouseout="window.status = lastStatus; return true"
+ onclick="return collapse_all()">Collapse All</a> |
+<a href="#"
+ onmouseover="lastStatus = window.status; window.status='Expand All'; return true"
+ onmouseout="window.status = lastStatus; return true"
+ onclick="return expand_all()">Expand All</a>
+
+[% IF do_context %]
+ [%# only happens for normal viewing, not interdiff %]
+ | <span style='font-weight: bold'>Context:</span>
+ [% IF context == "patch" %]
+ (<strong>Patch</strong> /
+ [% ELSE %]
+ (<a href="[% PROCESS diffurl id=attachid %]&amp;headers=[% headers FILTER url_quote %]">Patch</a> /
+ [% END %]
+ [% IF context == "file" %]
+ <strong>File</strong> /
+ [% ELSE %]
+ <a href="[% PROCESS diffurl id=attachid %]&amp;headers=[% headers FILTER url_quote %]&amp;context=file">File</a> /
+ [% END %]
+
+ [% IF context == "patch" || context == "file" %]
+ [% context = 3 %]
+ [% END %]
+ [%# textbox for context %]
+ <form style="display: inline" action="attachment.cgi"><input type="hidden" name="action" value="diff"><input type="hidden" name="id" value="[% attachid %]"><input type="hidden" name="collapsed" value="[% collapsed FILTER html %]"><input type="hidden" name="headers" value="[% headers FILTER html %]"><input type="text" name="context" value="[% context FILTER html %]" size="3"></form>)
+[% END %]
+
+[% IF warning %]
+<h2 class="warning">Warning:
+ [% IF warning == "interdiff1" %]
+ this difference between two patches may show things in the wrong places due
+ to a limitation in [% terms.Bugzilla %] when comparing patches with different
+ sets of files.
+ [% END %]
+ [% IF warning == "interdiff2" %]
+ this difference between two patches may be inaccurate due to a limitation in
+ [%+ terms.Bugzilla %] when comparing patches made against different revisions.
+ [% END %]
+</h2>
+[% ELSE %]
+ <br><br>
+[% END %]
+
+[%# Restore Stuff %]
+<form name="checkboxform" action="attachment.cgi">
+<input type="checkbox" name="restore_indicator" style="display: none">
+
+
diff --git a/template/en/default/attachment/edit.html.tmpl b/template/en/default/attachment/edit.html.tmpl
new file mode 100644
index 000000000..752bbecc4
--- /dev/null
+++ b/template/en/default/attachment/edit.html.tmpl
@@ -0,0 +1,325 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Myk Melez <myk@mozilla.org>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ # Guy Pyrzak <guy.pyrzak@gmail.com>
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[%# Define strings that will serve as the title and header of this page %]
+[% title = BLOCK %]
+ Attachment [% attachment.id %] Details for [% terms.Bug %] [%+ attachment.bug_id %]
+[% END %]
+[% header = BLOCK %]
+ Attachment [% attachment.id %] Details for
+ [%+ "$terms.Bug ${attachment.bug_id}" FILTER bug_link(attachment.bug_id) FILTER none %]
+[% END %]
+[% subheader = BLOCK %][% attachment.bug.short_desc FILTER html %][% END %]
+
+[% PROCESS global/header.html.tmpl
+ title = title
+ header = header
+ subheader = subheader
+ doc_section = "attachments.html"
+ javascript_urls = ['js/attachment.js', 'js/field.js']
+ style_urls = ['skins/standard/attachment.css']
+ yui = [ 'autocomplete' ]
+ bodyclasses = "no_javascript"
+%]
+
+[%# No need to display the Diff button and iframe if the attachment is not a patch. %]
+[% use_patchviewer = (feature_enabled('patch_viewer') && attachment.ispatch) %]
+[% can_edit = attachment.validate_can_edit %]
+[% editable_or_hide = can_edit ? "" : " bz_hidden_option" %]
+
+<form method="post" action="attachment.cgi" onsubmit="normalizeComments();">
+ <input type="hidden" name="id" value="[% attachment.id %]">
+ <input type="hidden" name="action" value="update">
+ <input type="hidden" name="contenttypemethod" value="manual">
+ <input type="hidden" name="delta_ts" value="[% attachment.modification_time FILTER html %]">
+ [% IF user.id %]
+ <input type="hidden" name="token" value="[% issue_hash_token([attachment.id, attachment.modification_time]) FILTER html %]">
+ [% END %]
+
+ <div id="attachment_info" class="attachment_info [% IF can_edit %] edit[% ELSE %] read[% END%]">
+ <div id="attachment_attributes">
+ <div id="attachment_information_read_only" class="[% "bz_private" IF attachment.isprivate %]">
+ <div class="title">
+ [% "[patch]" IF attachment.ispatch%]
+ <span class="[% "bz_obsolete" IF attachment.isobsolete %]" title="[% "obsolete" IF attachment.isobsolete %]">
+ [% attachment.description FILTER html %]
+ </span>
+ [% IF can_edit %]
+ <span class="bz_edit">(<a href="javascript:toggle_attachment_details_visibility()">edit details</a>)</span>
+ [% END %]
+ </div>
+ [% IF NOT attachment.isurl %]
+ <div class="details">
+ [% attachment.filename FILTER html %] ([% attachment.contenttype FILTER html %])
+ [% IF attachment.datasize %]
+ [%+ attachment.datasize FILTER unitconvert %]
+ [% ELSE %]
+ <em>deleted</em>
+ [% END %], created by [%+ INCLUDE global/user.html.tmpl who = attachment.attacher %]
+ [% IF attachment.isprivate %]
+ <span class="bz_private">Only visible to <strong>[% Param('insidergroup') FILTER html %]</strong></span>
+ [% END %]
+ </div>
+ [% END %]
+ </div>
+ <div id="attachment_information_edit">
+ <span class="bz_hide">
+ (<a href="javascript:toggle_attachment_details_visibility();">hide</a>)
+ </span>
+ <div id="attachment_description">
+ <label for="description">Description:</label>&nbsp;
+ [% INCLUDE global/textarea.html.tmpl
+ id = 'description'
+ name = 'description'
+ minrows = 3
+ cols = 25
+ wrap = 'soft'
+ classes = 'block' _ editable_or_hide
+ defaultcontent = attachment.description
+ %]
+ </div>
+
+ [% IF attachment.isurl %]
+ <input type="hidden" name="filename"
+ value="[% attachment.filename FILTER html %]">
+ <input type="hidden" name="contenttypeentry"
+ value="[% attachment.contenttype FILTER html %]">
+ [% ELSE %]
+ <div id="attachment_filename">
+ <label for="filename">Filename:</label>
+ <input type="text" size="20" class="text block[% editable_or_hide %]"
+ id="filename" name="filename"
+ value="[% attachment.filename FILTER html %]">
+ </div>
+
+ <div id="attachment_mimetype">
+ <label for="contenttypeentry">MIME Type:</label>
+ <input type="text" size="20" class="text block[% editable_or_hide %]"
+ id="contenttypeentry" name="contenttypeentry"
+ value="[% attachment.contenttype FILTER html %]">
+ </div>
+
+ <div id="attachment_creator">
+ <span class="label">Creator:</span>
+ [%+ INCLUDE global/user.html.tmpl who = attachment.attacher %]
+ </div>
+
+ <div id="attachment_size">
+ <span class="label">Size:</span>
+ [% IF attachment.datasize %]
+ [%+ attachment.datasize FILTER unitconvert %]
+ [% ELSE %]
+ <em>deleted</em>
+ [% END %]
+ </div>
+
+ <div id="attachment_ispatch">
+ <input type="checkbox" id="ispatch" name="ispatch" value="1"
+ [%+ 'checked="checked"' IF attachment.ispatch %]>
+ <label for="ispatch">patch</label>
+ </div>
+ [% END %]
+ <div class="readonly">
+ <div class="checkboxes">
+ <div id="attachment_isobsolete">
+ <input type="checkbox" id="isobsolete" name="isobsolete" value="1"
+ [%+ 'checked="checked"' IF attachment.isobsolete %]>
+ <label for="isobsolete">obsolete</label>
+ </div>
+
+ [% IF user.is_insider %]
+ <div id="attachment_isprivate">
+ <input type="checkbox" id="isprivate" name="isprivate" value="1"
+ [%+ 'checked="checked"' IF attachment.isprivate %]>
+ [% IF can_edit %]
+ <label for="isprivate">private (only visible to
+ <strong>[% Param('insidergroup') FILTER html %]</strong>)
+ </label>
+ [% ELSE %]
+ <span class="label">Is Private:</span>
+ [%+ attachment.isprivate ? "yes" : "no" %]
+ [% END %]
+ </div>
+ [% END %]
+ </div>
+ </div>
+ </div>
+
+ <div id="attachment_view_window">
+ [% IF !attachment.datasize %]
+ <div><b>The content of this attachment has been deleted.</b></div>
+ [% ELSIF attachment.isurl %]
+ <div>
+ <a href="[% attachment.data FILTER html %]">
+ [% IF attachment.datasize < 120 %]
+ [% attachment.data FILTER html %]
+ [% ELSE %]
+ [% attachment.data FILTER truncate(80) FILTER html %]
+ &nbsp;...
+ [% attachment.data.match(".*(.{20})$").0 FILTER html %]
+ [% END %]
+ </a>
+ </div>
+ [% ELSIF !Param("allow_attachment_display") %]
+ <div id="view_disabled">
+ <p><b>
+ The attachment is not viewable in your browser due to security
+ restrictions enabled by your [% terms.Bugzilla %] administrator.
+ </b></p>
+ <p><b>
+ In order to view the attachment, you first have to
+ <a href="attachment.cgi?id=[% attachment.id %]">download it</a>.
+ </b></p>
+ </div>
+ [% ELSIF attachment.is_viewable %]
+ <div >
+ [% INCLUDE global/textarea.html.tmpl
+ id = 'editFrame'
+ name = 'comment'
+ classes = 'bz_default_hidden'
+ minrows = 10
+ cols = 80
+ wrap = 'soft'
+ disabled = 'disabled'
+ defaultcontent = (attachment.contenttype.match('^text\/')) ?
+ attachment.data.replace('(.*\n|.+)', '>$1') : undef
+ %]
+ <iframe id="viewFrame" src="attachment.cgi?id=[% attachment.id %]">
+ <b>You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
+ <a href="attachment.cgi?id=[% attachment.id %]">View the attachment on a separate page</a>.</b>
+ </iframe>
+ <script type="text/javascript">
+ <!--
+ var patchviewerinstalled = 0;
+ var attachment_id = [% attachment.id %];
+ if (typeof document.getElementById == "function") {
+ [% IF use_patchviewer %]
+ var patchviewerinstalled = 1;
+ document.write('<iframe id="viewDiffFrame" class="bz_default_hidden"><\/iframe>');
+ [% END %]
+ [% IF user.id %]
+ document.write('<button type="button" id="editButton" onclick="editAsComment(patchviewerinstalled);">Edit Attachment As Comment<\/button>');
+ document.write('<button type="button" id="undoEditButton" onclick="undoEditAsComment(patchviewerinstalled);" class="bz_default_hidden">Undo Edit As Comment<\/button>');
+ document.write('<button type="button" id="redoEditButton" onclick="redoEditAsComment(patchviewerinstalled);" class="bz_default_hidden">Redo Edit As Comment<\/button>');
+ var editFrame = document.getElementById('editFrame');
+ if (editFrame) {
+ editFrame.disabled = false;
+ }
+ [% END %]
+ [% IF use_patchviewer %]
+ document.write('<button type="button" id="viewDiffButton" onclick="viewDiff(attachment_id, patchviewerinstalled);">View Attachment As Diff<\/button>');
+ [% END %]
+ document.write('<button type="button" id="viewRawButton" onclick="viewRaw(patchviewerinstalled);" class="bz_default_hidden">View Attachment As Raw<\/button>');
+ }
+ //-->
+ </script>
+ </div>
+ [% ELSE %]
+ <div id="noview">
+ <p><b>
+ Attachment is not viewable in your browser because its MIME type
+ ([% attachment.contenttype FILTER html %]) is not one that your browser is
+ able to display.
+ </b></p>
+ <p><b>
+ <a href="attachment.cgi?id=[% attachment.id %]">Download the attachment</a>.
+ </b></p>
+ </div>
+ [% END %]
+ </div>
+ <div id="attachment_comments_and_flags">
+ [% IF user.id %]
+ <div id="smallCommentFrame" >
+ <label for="comment">Comment (on the [% terms.bug %]):</label>
+ [% classNames = 'block' %]
+ [% classNames = "$classes bz_private" IF attachment.isprivate %]
+ [% INCLUDE global/textarea.html.tmpl
+ id = 'comment'
+ name = 'comment'
+ minrows = 10
+ cols = 80
+ wrap = 'soft'
+ classes = classNames
+ %]
+ </div>
+ [% END %]
+ <div id="attachment_flags">
+ [% IF attachment.flag_types.size > 0 %]
+
+ [% PROCESS "flag/list.html.tmpl" bug_id = attachment.bug_id
+ attach_id = attachment.id
+ flag_types = attachment.flag_types
+ read_only_flags = !can_edit
+ %]
+
+ [% END %]
+ </div>
+
+ [% Hook.process('form_before_submit') %]
+
+ [% IF user.id %]
+ <div id="update_container">
+ <input type="submit" value="Submit" id="update">
+ </div>
+ [% END %]
+ </div>
+ </div>
+ </div>
+</form>
+
+<div id="attachment_actions">
+ <span class="label">Actions:</span>
+ <a href="attachment.cgi?id=[% attachment.id %]">View</a>
+ [% IF use_patchviewer %]
+ | <a href="attachment.cgi?id=[% attachment.id %]&amp;action=diff">Diff</a>
+ [% END %]
+ [% IF Param("allow_attachment_deletion")
+ && user.in_group('admin')
+ && attachment.datasize > 0 %]
+ | <a href="attachment.cgi?id=[% attachment.id %]&amp;action=delete">Delete</a>
+ [% END %]
+</div>
+
+<div id="attachment_list">
+ Attachments on [% "$terms.bug ${attachment.bug_id}" FILTER bug_link(attachment.bug_id) FILTER none %]:
+ [% FOREACH a = attachments %]
+ [% IF a == attachment.id %]
+ [%+ a %]
+ [% ELSE %]
+ <a href="attachment.cgi?id=[% a %]&amp;action=edit">[% a %]</a>
+ [% END %]
+ [% " |" UNLESS loop.last() %]
+ [% END %]
+</div>
+[% IF can_edit %]
+ <script type="text/javascript">
+ <!--
+ YAHOO.util.Dom.removeClass( document.body, "no_javascript" );
+ toggle_attachment_details_visibility( );
+ -->
+ </script>
+[% END %]
+[% Hook.process('end') %]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/attachment/list.html.tmpl b/template/en/default/attachment/list.html.tmpl
new file mode 100644
index 000000000..89eef1836
--- /dev/null
+++ b/template/en/default/attachment/list.html.tmpl
@@ -0,0 +1,167 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Myk Melez <myk@mozilla.org>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+
+[% RETURN UNLESS attachments.size || Param("maxattachmentsize") %]
+
+<script type="text/javascript">
+<!--
+function toggle_display(link) {
+ var table = document.getElementById("attachment_table");
+ // Store current height for scrolling later
+ var originalHeight = table.offsetHeight;
+ var rows = YAHOO.util.Dom.getElementsByClassName(
+ 'bz_tr_obsolete', 'tr', table);
+
+ for (var i = 0; i < rows.length; i++) {
+ bz_toggleClass(rows[i], 'bz_default_hidden');
+ }
+
+ if (YAHOO.util.Dom.hasClass(rows[0], 'bz_default_hidden')) {
+ link.innerHTML = "Show Obsolete";
+ }
+ else {
+ link.innerHTML = "Hide Obsolete";
+ }
+
+ var newHeight = table.offsetHeight;
+ // This scrolling makes the window appear to not move at all.
+ window.scrollBy(0, newHeight - originalHeight);
+
+ return false;
+}
+//-->
+</script>
+
+<br>
+<table id="attachment_table" cellspacing="0" cellpadding="4">
+ <tr id="a0">
+ <th colspan="[% show_attachment_flags ? 3 : 2 %]" align="left">
+ Attachments
+ </th>
+ </tr>
+
+ [% count = 0 %]
+ [% obsolete_attachments = 0 %]
+
+ [% FOREACH attachment = attachments %]
+ [% count = count + 1 %]
+ [% IF !attachment.isprivate || user.is_insider || attachment.attacher.id == user.id %]
+ [% IF attachment.isobsolete %]
+ [% obsolete_attachments = obsolete_attachments + 1 %]
+ [% END %]
+ <tr id="a[% count %]" class="[% "bz_contenttype_" _ attachment.contenttype
+ FILTER css_class_quote UNLESS attachment.isurl %]
+ [% " bz_patch" IF attachment.ispatch %]
+ [% " bz_url" IF attachment.isurl %]
+ [% " bz_private" IF attachment.isprivate %]
+ [% " bz_tr_obsolete bz_default_hidden"
+ IF attachment.isobsolete %]">
+ <td valign="top">
+ [% IF attachment.datasize %]
+ <a href="attachment.cgi?id=[% attachment.id %]"
+ title="View the content of the attachment">
+ [% END %]
+ <b>[% attachment.description FILTER html FILTER obsolete(attachment.isobsolete) %]</b>
+ [% "</a>" IF attachment.datasize %]
+
+ <span class="bz_attach_extra_info">
+ [% IF attachment.datasize %]
+ ([% attachment.datasize FILTER unitconvert %],
+ [% IF attachment.ispatch %]
+ patch)
+ [% ELSIF attachment.isurl %]
+ url)
+ [% ELSE %]
+ [%+ attachment.contenttype FILTER html %])
+ [% END %]
+ [% ELSE %]
+ (<em>deleted</em>)
+ [% END %]
+
+ <br>
+ <a href="#attach_[% attachment.id %]"
+ title="Go to the comment associated with the attachment">
+ [%- attachment.attached FILTER time %]</a>,
+
+ [% INCLUDE global/user.html.tmpl who = attachment.attacher %]
+ </span>
+ </td>
+
+ [% IF show_attachment_flags %]
+ <td class="bz_attach_flags" valign="top">
+ [% IF attachment.flags.size == 0 %]
+ <i>no flags</i>
+ [% ELSE %]
+ [% FOREACH flag = attachment.flags %]
+ [% IF user.id %]
+ <span title="[% flag.setter.identity FILTER html %]">[% flag.setter.nick FILTER html %]</span>:
+ [% ELSIF flag.setter.name %]
+ <span title="[% flag.setter.name FILTER html %]">[% flag.setter.nick FILTER html %]</span>:
+ [% ELSE %]
+ [% flag.setter.nick FILTER html %]:
+ [% END %]
+ [%+ flag.type.name FILTER html FILTER no_break %][% flag.status %]
+ [%+ IF flag.status == "?" && flag.requestee %]
+ [% IF user.id %]
+ (<span title="[% flag.requestee.identity FILTER html %]">[% flag.requestee.nick FILTER html %]</span>)
+ [% ELSIF flag.requestee.name %]
+ (<span title="[% flag.requestee.name FILTER html %]">[% flag.requestee.nick FILTER html %]</span>)
+ [% ELSE %]
+ ([% flag.requestee.nick FILTER html %])
+ [% END %]
+ [% END %]<br>
+ [% END %]
+ [% END %]
+ </td>
+ [% END %]
+
+ <td valign="top">
+ <a href="attachment.cgi?id=[% attachment.id %]&amp;action=edit">Details</a>
+ [% IF attachment.ispatch && feature_enabled('patch_viewer') %]
+ | <a href="attachment.cgi?id=[% attachment.id %]&amp;action=diff">Diff</a>
+ [% END %]
+ [% Hook.process("action") %]
+ </td>
+ </tr>
+ [% END %]
+ [% END %]
+
+ <tr class="bz_attach_footer">
+ <td colspan="[% show_attachment_flags ? 3 : 2 %]">
+ [% IF attachments.size %]
+ <span class="bz_attach_view_hide">
+ [% IF obsolete_attachments %]
+ <a href="#a0" onclick="return toggle_display(this);">Show
+ Obsolete</a> ([% obsolete_attachments %])
+ [% END %]
+ [% IF Param("allow_attachment_display") %]
+ <a href="attachment.cgi?bugid=[% bugid %]&amp;action=viewall">View All</a>
+ [% END %]
+ </span>
+ [% END %]
+ [% IF Param("maxattachmentsize") %]
+ <a href="attachment.cgi?bugid=[% bugid %]&amp;action=enter">Add an attachment</a>
+ (proposed patch, testcase, etc.)
+ [% END %]
+ </td>
+ </tr>
+</table>
+<br>
diff --git a/template/en/default/attachment/midair.html.tmpl b/template/en/default/attachment/midair.html.tmpl
new file mode 100644
index 000000000..f0883b55b
--- /dev/null
+++ b/template/en/default/attachment/midair.html.tmpl
@@ -0,0 +1,78 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Myk Melez <myk@mozilla.org>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+
+[%# INTERFACE:
+ # operations: array; bug activity since the user last displayed the attachment form,
+ # used by bug/activity/table.html.tmpl to display recent changes that will
+ # be overwritten if the user submits these changes. See that template
+ # for further documentation.
+ # attachment: object; the attachment being changed.
+ #%]
+
+[%# The global Bugzilla->cgi object is used to obtain form variable values. %]
+[% USE Bugzilla %]
+[% cgi = Bugzilla.cgi %]
+
+[% PROCESS global/variables.none.tmpl %]
+[% PROCESS global/header.html.tmpl title = "Mid-air collision!" %]
+
+<h1>Mid-air collision detected!</h1>
+
+<p>
+ Someone else has made changes to
+ <a href="attachment.cgi?id=[% attachment.id %]&amp;action=edit">attachment [% attachment.id %]</a>
+ of [% "$terms.bug $attachment.bug_id" FILTER bug_link(attachment.bug_id) FILTER none %]
+ at the same time you were trying to. The changes made were:
+</p>
+
+<p>
+ [% PROCESS "bug/activity/table.html.tmpl" incomplete_data=0 %]
+</p>
+
+[% IF cgi.param("comment") %]
+<p>
+ Your comment was:<br>
+ <blockquote><pre class="bz_comment_text">
+ [% cgi.param("comment") FILTER wrap_comment FILTER html %]
+ </pre></blockquote>
+</p>
+[% END %]
+
+<p>
+You have the following choices:
+</p>
+
+<ul>
+ <li>
+ <form method="post" action="attachment.cgi">
+ [% PROCESS "global/hidden-fields.html.tmpl" exclude="^Bugzilla_(login|password)$" %]
+ <input type="submit" id="process" value="Submit my changes anyway">
+ This will cause all of the above changes to be overwritten.
+ </form>
+ </li>
+ <li>
+ Throw away my changes, and
+ <a href="attachment.cgi?id=[% attachment.id %]&amp;action=edit">revisit
+ attachment [% attachment.id %]</a>
+ </li>
+</ul>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/attachment/show-multiple.html.tmpl b/template/en/default/attachment/show-multiple.html.tmpl
new file mode 100644
index 000000000..15e20e894
--- /dev/null
+++ b/template/en/default/attachment/show-multiple.html.tmpl
@@ -0,0 +1,102 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Myk Melez <myk@mozilla.org>
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+[% filtered_summary = bugsummary FILTER html %]
+[% header = BLOCK %]View All Attachments for
+ [%+ "$terms.Bug $bug.id" FILTER bug_link(bug) FILTER none %][% END %]
+
+[% title = BLOCK %]
+ View All Attachments for [% terms.Bug %] [%+ bug.bug_id FILTER html %]
+[% END %]
+
+[% PROCESS global/header.html.tmpl
+ title = title
+ header = header
+ subheader = filtered_summary
+ style_urls = ['skins/standard/attachment.css']
+%]
+
+<br>
+
+[% FOREACH a = attachments %]
+
+ <div align="center">
+ <table class="attachment_info" cellspacing="0" cellpadding="4" border="1" width="75%">
+ <tr>
+ <td valign="top" bgcolor="#cccccc" colspan="6">
+ <big><b>Attachment #[% a.id %]</b></big>
+ </td>
+ </tr>
+ <tr>
+ <td valign="top">
+ [% a.description FILTER html FILTER obsolete(a.isobsolete) %]
+ </td>
+
+ <td valign="top">
+ [% IF a.ispatch %]
+ <i>patch</i>
+ [% ELSE %]
+ [% a.contenttype FILTER html %]
+ [% END %]
+ </td>
+
+ <td valign="top">[% a.attached FILTER time %]</td>
+ <td valign="top">[% a.datasize FILTER unitconvert %]</td>
+
+ <td valign="top">
+ [% IF a.flags.size == 0 %]
+ <i>no flags</i>
+ [% ELSE %]
+ [% FOREACH flag = a.flags %]
+ [% flag.setter.nick FILTER html %]:
+ [%+ flag.type.name FILTER html %][% flag.status %]
+ [% IF flag.status == "?" && flag.requestee %]
+ ([% flag.requestee.nick FILTER html %])
+ [% END %]
+ [% ", " IF !loop.last %]
+ [% END %]
+ [% END %]
+ </td>
+
+ <td valign="top">
+ <a href="attachment.cgi?id=[% a.id %]&amp;action=edit">Details</a>
+ </td>
+ </tr>
+ </table>
+
+ [% IF a.is_viewable %]
+ <iframe src="attachment.cgi?id=[% a.id %]" width="75%" height="350">
+ <b>You cannot view the attachment on this page because your browser does not support IFRAMEs.
+ <a href="attachment.cgi?id=[% a.id %]">View the attachment on a separate page</a>.</b>
+ </iframe>
+ [% ELSE %]
+ <p><b>
+ Attachment cannot be viewed because its MIME type is not text/*, image/*, or application/vnd.mozilla.*.
+ <a href="attachment.cgi?id=[% a.id %]">Download the attachment instead</a>.
+ </b></p>
+ [% END %]
+ </div>
+
+ <br><br>
+
+[% END %]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/attachment/updated.html.tmpl b/template/en/default/attachment/updated.html.tmpl
new file mode 100644
index 000000000..9a74f5c98
--- /dev/null
+++ b/template/en/default/attachment/updated.html.tmpl
@@ -0,0 +1,46 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Myk Melez <myk@mozilla.org>
+ # Gervase Markham <gerv@gerv.net>
+ #%]
+
+[%# INTERFACE:
+ # attachment: object of the attachment we just attached.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+[% bug = bugs.0 %]
+
+[% PROCESS "bug/show-header.html.tmpl" %]
+[% PROCESS global/header.html.tmpl
+ title = "Changes Submitted to Attachment $attachment.id of $terms.Bug $attachment.bug_id"
+%]
+
+<dl>
+ <dt>Changes to
+ <a href="attachment.cgi?id=[% attachment.id %]&amp;action=edit">attachment [% attachment.id %]</a>
+ of [% "$terms.bug $attachment.bug_id" FILTER bug_link(attachment.bug_id) FILTER none %] submitted
+ </dt>
+ <dd>
+ [% PROCESS "bug/process/bugmail.html.tmpl" mailing_bugid = attachment.bug_id %]
+ [%# Links to more information about the changed bug. %]
+ [% Hook.process("links") %]
+ </dd>
+</dl>
+
+[% PROCESS bug/show.html.tmpl %]
diff --git a/template/en/default/bug/activity/show.html.tmpl b/template/en/default/bug/activity/show.html.tmpl
new file mode 100644
index 000000000..67ac689ca
--- /dev/null
+++ b/template/en/default/bug/activity/show.html.tmpl
@@ -0,0 +1,49 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ #%]
+
+[%# INTERFACE:
+ # bug: object. The bug whose activity is being displayed.
+ # operations: array of hashes, see activity/table.html.tmpl.
+ #
+ # This template also needs to be called with the interface to the
+ # activity/table.html.tmpl template fulfilled.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% filtered_desc = bug.short_desc FILTER html %]
+[% PROCESS global/header.html.tmpl
+ title = "Changes made to $terms.bug $bug.bug_id"
+ header = "Activity log for $terms.bug $bug.bug_id: $filtered_desc"
+ %]
+
+<p>
+ [% "Back to $terms.bug $bug.bug_id" FILTER bug_link(bug) FILTER none %]
+</p>
+
+[% PROCESS bug/activity/table.html.tmpl %]
+
+[% IF operations.size > 0 %]
+ <p>
+ [% "Back to $terms.bug $bug.bug_id" FILTER bug_link(bug) FILTER none %]
+ </p>
+[% END %]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/bug/activity/table.html.tmpl b/template/en/default/bug/activity/table.html.tmpl
new file mode 100644
index 000000000..a9aca0a64
--- /dev/null
+++ b/template/en/default/bug/activity/table.html.tmpl
@@ -0,0 +1,119 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ # David D. Kilzer <ddkilzer@kilzer.net>
+ # Reed Loden <reed@reedloden.com>
+ #%]
+
+[%# INTERFACE:
+ # operations: array of hashes. May be empty. Each has has three members:
+ # who: string. who performed the operation
+ # when: string. when they performed it
+ # changes: hash. Details of what they changed. This hash has three
+ # compulsory and one optional member:
+ # field: string. The name of the field
+ # removed: string. What was removed from the field
+ # added: string. What was added to the field
+ # attach_id: integer. If the change was adding an attachment, its id.
+ # incomplete_data: boolean. True if some of the data is incomplete (because
+ # it was affected by an old Bugzilla bug.)
+ #%]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+[% PROCESS bug/time.html.tmpl %]
+
+[% IF incomplete_data %]
+ <p>
+ There used to be an issue in <a href="http://www.bugzilla.org/">Bugzilla</a>
+ which caused activity data to be lost if there were a large number of cc's
+ or dependencies. That has been fixed, but some data was already lost in
+ your activity table that could not be regenerated. The changes that the
+ script could not reliably determine are prefixed by '?'.
+ </p>
+[% END %]
+
+[% IF operations.size > 0 %]
+ <table border cellpadding="4">
+ <tr>
+ <th>Who</th>
+ <th>When</th>
+ <th>What</th>
+ <th>Removed</th>
+ <th>Added</th>
+ </tr>
+
+ [% FOREACH operation = operations %]
+ <tr>
+ <td rowspan="[% operation.changes.size %]" valign="top">
+ [% operation.who FILTER email FILTER html %]
+ </td>
+ <td rowspan="[% operation.changes.size %]" valign="top">
+ [% operation.when FILTER time %]
+ </td>
+ [% FOREACH change = operation.changes %]
+ [% "</tr><tr>" IF loop.index > 0 %]
+ <td>
+ [% IF change.attachid %]
+ <a href="attachment.cgi?id=[% change.attachid %]">
+ Attachment #[% change.attachid %]</a>
+ [% END %]
+ [% IF change.comment.defined %]
+ [% comment_desc = field_descs.${change.fieldname} %]
+ [% comment_num = "Comment $change.comment.count" FILTER bug_link(bug.bug_id, comment_num => change.comment.count) %]
+ [% comment_desc.replace('^(Comment )?', "$comment_num ") FILTER none %]
+ [% ELSE %]
+ [%+ field_descs.${change.fieldname} FILTER html %]
+ [% END %]
+ </td>
+ [% PROCESS change_column change_type = change.removed %]
+ [% PROCESS change_column change_type = change.added %]
+ [% END %]
+ </tr>
+ [% END %]
+ </table>
+[% ELSE %]
+ <p>
+ No changes have been made to this [% terms.bug %] yet.
+ </p>
+[% END %]
+
+[% BLOCK change_column %]
+ <td>
+ [% IF change_type.defined %]
+ [% IF change.fieldname == 'estimated_time' ||
+ change.fieldname == 'remaining_time' ||
+ change.fieldname == 'work_time' %]
+ [% PROCESS formattimeunit time_unit=change_type %]
+ [% ELSIF change.fieldname == 'blocked' ||
+ change.fieldname == 'dependson' %]
+ [% change_type FILTER bug_list_link FILTER none %]
+ [% ELSIF change.fieldname == 'assigned_to' ||
+ change.fieldname == 'reporter' ||
+ change.fieldname == 'qa_contact' ||
+ change.fieldname == 'cc' ||
+ change.fieldname == 'flagtypes.name' %]
+ [% display_value(change.fieldname, change_type) FILTER email FILTER html %]
+ [% ELSE %]
+ [% display_value(change.fieldname, change_type) FILTER html %]
+ [% END %]
+ [% ELSE %]
+ &nbsp;
+ [% END %]
+ </td>
+[% END %]
diff --git a/template/en/default/bug/choose.html.tmpl b/template/en/default/bug/choose.html.tmpl
new file mode 100644
index 000000000..9009d3873
--- /dev/null
+++ b/template/en/default/bug/choose.html.tmpl
@@ -0,0 +1,35 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% PROCESS global/header.html.tmpl
+ title = "Search by $terms.bug number"
+ %]
+
+<form method="get" action="show_bug.cgi">
+ <p>
+ You may find a single [% terms.bug %] by entering its [% terms.bug %] id here:
+ <input name="id" size="6">
+ <input type="submit" id="show" value="Show Me This [% terms.Bug %]">
+ </p>
+</form>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/bug/comments.html.tmpl b/template/en/default/bug/comments.html.tmpl
new file mode 100644
index 000000000..580ba6b5e
--- /dev/null
+++ b/template/en/default/bug/comments.html.tmpl
@@ -0,0 +1,182 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ # Shane H. W. Travis <travis@sedsystems.ca>
+ #%]
+
+[% PROCESS bug/time.html.tmpl %]
+
+<script src="[% 'js/comments.js' FILTER mtime %]" type="text/javascript">
+</script>
+
+[% DEFAULT start_at = 0 mode = "show" %]
+[% sort_order = user.settings.comment_sort_order.value %]
+
+[%# NOTE: (start_at > 0) means we came here from a midair collision,
+ # in which case we don't care what the user's preference is.
+ %]
+[% IF (start_at > 0) %]
+ [% sort_order = "oldest_to_newest" %]
+[% END %]
+
+
+[%# Set up the variables as needed, depending on the sort order %]
+[% IF sort_order == "oldest_to_newest" %]
+ [% count = 0 %]
+ [% description = 0 %]
+ [% increment = 1 %]
+[% ELSE %]
+ [% increment = -1 %]
+ [% IF sort_order == "newest_to_oldest" %]
+ [% count = comments.size - 1 %]
+ [% description = 0 %]
+ [% ELSIF sort_order == "newest_to_oldest_desc_first" %]
+ [% count = comments.size %]
+ [% description = comments.size %]
+ [% END %]
+[% END %]
+
+<!-- This auto-sizes the comments and positions the collapse/expand links
+ to the right. -->
+<table class="bz_comment_table" cellpadding="0" cellspacing="0"><tr>
+<td>
+
+[% FOREACH comment = comments %]
+ [% IF count >= start_at %]
+ [% PROCESS a_comment %]
+ [% END %]
+
+ [% count = count + increment %]
+[% END %]
+
+[% IF user.settings.comment_box_position.value == "before_comments" && user.id %]
+ <div class="bz_add_comment">
+ <a href="#"
+ onclick="return goto_add_comments();">
+ Add Comment</a>
+ </div>
+[% END %]
+
+[%# Note: this template is used in multiple places; if you use this hook,
+ # make sure you are aware of this fact.
+ #%]
+[% Hook.process("aftercomments") %]
+
+</td>
+<td>
+ [% IF mode == "edit" %]
+ <ul class="bz_collapse_expand_comments">
+ <li><a href="#" onclick="toggle_all_comments('collapse');
+ return false;">Collapse All Comments</a></li>
+ <li><a href="#" onclick="toggle_all_comments('expand');
+ return false;">Expand All Comments</a></li>
+ [% IF user.settings.comment_box_position.value == "after_comments" && user.id %]
+ <li class="bz_add_comment"><a href="#"
+ onclick="return goto_add_comments('bug_status_bottom');">
+ Add Comment</a></li>
+ [% END %]
+ </ul>
+ [% END %]
+</td>
+</tr></table>
+
+[%############################################################################%]
+[%# Block for individual comments #%]
+[%############################################################################%]
+
+[% BLOCK a_comment %]
+ [% RETURN IF comment.is_private AND ! user.is_insider %]
+ [% comment_text = comment.body_full({ wrap => 1 }) %]
+ [% RETURN IF comment_text == '' AND (comment.work_time - 0) != 0 AND !user.is_timetracker %]
+
+ <div id="c[% count %]" class="bz_comment[% " bz_private" IF comment.is_private %]
+ [% " bz_comment_hilite" IF marks.$count %]
+ [% " bz_first_comment" IF count == description %]">
+ [% IF count == description %]
+ [% class_name = "bz_first_comment_head" %]
+ [% comment_label = "Description" %]
+ [% ELSE %]
+ [% class_name = "bz_comment_head" %]
+ [% comment_label = "Comment " _ count %]
+ [% END %]
+
+ <div class="[% class_name FILTER html %]">
+
+ [% IF mode == "edit" %]
+ <span class="bz_comment_actions">
+ <script type="text/javascript"><!--
+ addReplyLink([% count %], [% comment.id %]);
+ addCollapseLink([% count %]); // -->
+ </script>
+ </span>
+ [% END %]
+
+ [% IF mode == "edit" && user.is_insider %]
+ <div class="bz_private_checkbox">
+ <input type="hidden" value="1"
+ name="defined_isprivate_[% comment.id %]">
+ <input type="checkbox"
+ name="isprivate_[% comment.id %]" value="1"
+ id="isprivate_[% comment.id %]"
+ onClick="updateCommentPrivacy(this, [% count %])"
+ [% " checked=\"checked\"" IF comment.is_private %]>
+ <label for="isprivate_[% comment.id %]">Private</label>
+ </div>
+ [% END %]
+
+ <span class="bz_comment_number">
+ <a
+ href="show_bug.cgi?id=[% bug.bug_id %]#c[% count %]">
+ [%- comment_label FILTER html %]</a>
+ </span>
+
+ <span class="bz_comment_user">
+ [% INCLUDE global/user.html.tmpl who = comment.author %]
+ </span>
+
+ <span class="bz_comment_user_images">
+ [% FOREACH group = comment.author.direct_group_membership %]
+ [% NEXT UNLESS group.icon_url %]
+ <img src="[% group.icon_url FILTER html %]"
+ alt="[% group.name FILTER html %]"
+ title="[% group.name FILTER html %] - [% group.description FILTER html %]">
+ [% END %]
+ </span>
+
+ <span class="bz_comment_time">
+ [%+ comment.creation_ts FILTER time %]
+ </span>
+ </div>
+
+ [% IF user.is_timetracker &&
+ (comment.work_time > 0 || comment.work_time < 0) %]
+ <br>
+ Additional hours worked:
+ [% PROCESS formattimeunit time_unit=comment.work_time %]
+ [% END %]
+
+[%# Don't indent the <pre> block, since then the spaces are displayed in the
+ # generated HTML
+ #%]
+<pre class="bz_comment_text"
+ [% ' id="comment_text_' _ count _ '"' IF mode == "edit" %]>
+ [%- comment_text FILTER quoteUrls(bug, comment) -%]
+</pre>
+ </div>
+[% END %]
diff --git a/template/en/default/bug/create/comment-guided.txt.tmpl b/template/en/default/bug/create/comment-guided.txt.tmpl
new file mode 100644
index 000000000..df04d8fb5
--- /dev/null
+++ b/template/en/default/bug/create/comment-guided.txt.tmpl
@@ -0,0 +1,54 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ #%]
+[%# INTERFACE:
+ # This template has no interface.
+ #
+ # Form variables from a bug submission (i.e. the fields on a template from
+ # enter_bug.cgi) can be access via Bugzilla.cgi.param. It can be used to
+ # pull out various custom fields and format an initial Description entry
+ # from them.
+ #%]
+[% USE Bugzilla %]
+[% cgi = Bugzilla.cgi %]
+User-Agent: [%+ cgi.user_agent() %]
+Build Identifier: [%+ cgi.param("buildid") %]
+
+[%+ cgi.param("comment") IF cgi.param("comment") %]
+
+[%+ IF cgi.param("reproducible") != "Choose one..." -%]
+Reproducible: [%+ cgi.param("reproducible") %]
+[% END %]
+
+[% IF !(cgi.param("reproduce_steps").match('^1\.\s*2\.\s*3\.\s*$') || cgi.param("reproduce_steps").match('^\s*$')) %]
+Steps to Reproduce:
+[%+ cgi.param("reproduce_steps") %]
+[% END %]
+
+[% IF cgi.param("actual_results") -%]
+Actual Results:
+[%+ cgi.param("actual_results") %]
+[% END %]
+
+[% IF cgi.param("expected_results") %]
+Expected Results:
+[%+ cgi.param("expected_results") %]
+[% END %]
+
+[%+ cgi.param("additional_info") %]
diff --git a/template/en/default/bug/create/comment.txt.tmpl b/template/en/default/bug/create/comment.txt.tmpl
new file mode 100644
index 000000000..e7339d37a
--- /dev/null
+++ b/template/en/default/bug/create/comment.txt.tmpl
@@ -0,0 +1,32 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ #%]
+[%# INTERFACE:
+ # This template has no interface.
+ #
+ # Form variables from a bug submission (i.e. the fields on a template from
+ # enter_bug.cgi) can be access via Bugzilla.cgi.param. It can be used to
+ # pull out various custom fields and format an initial Description entry
+ # from them.
+ #%]
+[% USE Bugzilla %]
+[% Hook.process("form") %]
+
+
+[% Bugzilla.cgi.param("comment") %]
diff --git a/template/en/default/bug/create/confirm-create-dupe.html.tmpl b/template/en/default/bug/create/confirm-create-dupe.html.tmpl
new file mode 100644
index 000000000..b0a5cddda
--- /dev/null
+++ b/template/en/default/bug/create/confirm-create-dupe.html.tmpl
@@ -0,0 +1,57 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Olav Vitters.
+ #
+ # Contributor(s): Olav Vitters <olav@bkor.dhs.org>
+ #%]
+
+[%# INTERFACE:
+ # bugid: integer. ID of the bug previously used to create a bug.
+ # allow_override: boolean int. Is 1 if the user may submit the bug again.
+ #%]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+[% PROCESS global/header.html.tmpl
+ title = "Already filed $terms.bug"
+%]
+
+[% USE Bugzilla %]
+
+<table cellpadding="20">
+ <tr>
+ <td bgcolor="#ff0000">
+ <font size="+2">
+ You already used the form to file [% "$terms.bug $bugid" FILTER bug_link(bugid) FILTER none %].
+ </font>
+ </td>
+ </tr>
+</table>
+
+<p><font size="big">You are highly encouraged to visit [% "$terms.bug $bugid"
+FILTER bug_link(bugid) FILTER none %].</font></p>
+
+[% IF allow_override %]
+ <p>If you are sure you used the same form to submit a new [% terms.bug %],
+ click 'File [% terms.bug %] again'.<p>
+
+ <form name="create" id="create" method="post" action="post_bug.cgi"
+ [%- IF Bugzilla.cgi.param("data") %] enctype="multipart/form-data"[% END %]>
+ [% PROCESS "global/hidden-fields.html.tmpl"
+ exclude="^(Bugzilla_login|Bugzilla_password|ignore_token)$" %]
+ <input type="hidden" name="ignore_token" value="[% bugid FILTER html %]">
+ <input type="submit" value="File [% terms.bug %] again" id="file_bug_again">
+ </form>
+[% END %]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/bug/create/create-guided.html.tmpl b/template/en/default/bug/create/create-guided.html.tmpl
new file mode 100644
index 000000000..8be35f817
--- /dev/null
+++ b/template/en/default/bug/create/create-guided.html.tmpl
@@ -0,0 +1,522 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ # Christine Begle <cbegle@mozilla.org>
+ #%]
+
+[%# INTERFACE:
+ # This template has the same interface as create.html.tmpl
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% USE Bugzilla %]
+[% cgi = Bugzilla.cgi %]
+
+[% PROCESS global/header.html.tmpl
+ title = "Enter $terms.ABug"
+ onload = "PutDescription()"
+ style = "#somebugs { width: 100%; height: 500px }"
+ %]
+
+<p>
+ <font color="red">
+ This is a template used on mozilla.org. This template, and the
+ comment-guided.txt.tmpl template that formats the data submitted via
+ the form in this template, are included as a demo of what it's
+ possible to do with custom templates in general, and custom [% terms.bug %]
+ entry templates in particular. As much of the text will not apply,
+ you should alter it
+ if you want to use this form on your [% terms.Bugzilla %] installation.
+ </font>
+</p>
+
+[% tablecolour = "#FFFFCC" %]
+
+[%# This script displays the descriptions for selected components. %]
+<script type="text/javascript">
+var descriptions = [
+[% FOREACH c = product.components %]
+ '[% c.description FILTER js %]',
+[% END %]
+];
+
+function PutDescription() {
+ if ((document.getElementById) && (document.body.innerHTML)) {
+ var componentIndex = document.getElementById('component').selectedIndex;
+ if (componentIndex != -1) {
+ var description = document.getElementById('description');
+ description.innerHTML = descriptions[componentIndex];
+ }
+ }
+}
+</script>
+
+<h3 id="step1">Step 1 of 3 - has your [% terms.bug %] already been reported?</h3>
+
+<p>
+ <font color="red">Please don't skip this step - half of all
+ [% terms.bugs %] filed are
+ reported already.</font>
+ Check the two lists of frequently-reported [% terms.bugs %]:
+</p>
+
+[%# Include other products if sensible %]
+[% IF product.name == "Firefox" %]
+ [% productstring = "product=Mozilla%20Application%20Suite&amp;product=Firefox" %]
+[% ELSIF product.name == "Thunderbird" %]
+ [% productstring = "product=Mozilla%20Application%20Suite&amp;product=Thunderbird" %]
+[% ELSE %]
+ [% productstring = BLOCK %]product=[% product.name FILTER url_quote %][% END %]
+[% END %]
+
+<p>
+ <a href="duplicates.cgi?[% productstring %]&amp;format=simple" target="somebugs">All-time Top 100</a> (loaded initially) |
+ <a href="duplicates.cgi?[% productstring %]&amp;format=simple&amp;sortby=delta&amp;reverse=1&amp;maxrows=100&amp;changedsince=14" target="somebugs">Hot in the last two weeks</a>
+</p>
+
+<iframe name="somebugs" id="somebugs"
+ style="border: 2px black solid"
+ src="duplicates.cgi?[% productstring %]&amp;format=simple">
+</iframe>
+
+<p>
+ If your [% terms.bug %] isn't there, search [% terms.Bugzilla %] by entering
+ a few key words having to do with your [% terms.bug %] in this box.
+ For example: <tt><b>pop3 mail</b></tt> or <tt><b>copy paste</b></tt>.
+ The results will appear above.
+ </p>
+
+[%# All bugs opened inside the past six months %]
+ <form action="buglist.cgi" method="get" target="somebugs">
+ <input type="hidden" name="format" value="simple">
+ <input type="hidden" name="order" value="relevance desc">
+ <input type="hidden" name="bug_status" value="__all__">
+ <input type="hidden" name="product" value="[% product.name FILTER html %]">
+ [% IF product.name == "Firefox" OR
+ product.name == "Thunderbird" OR
+ product.name == "Mozilla Application Suite" OR
+ product.name == "Camino" %]
+ <input type="hidden" name="product" value="Core">
+ <input type="hidden" name="product" value="Toolkit">
+ <input type="hidden" name="product" value="PSM">
+ <input type="hidden" name="product" value="NSPR">
+ <input type="hidden" name="product" value="NSS">
+ [% END %]
+ <input type="hidden" name="chfieldfrom" value="-6m">
+ <input type="hidden" name="chfieldto" value="Now">
+ <input type="hidden" name="chfield" value="[Bug creation]">
+ <input type="text" name="content" size="40">
+ <input type="submit" id="search" value="Search">
+ </form>
+
+<p>
+ Look through the search results. If you get the
+ <tt><b>[% terms.zeroSearchResults %]</b></tt> message, [% terms.Bugzilla %]
+ found no [% terms.bugs %] that
+ match. Check for typing mistakes, or try fewer or different keywords.
+ If you find [% terms.abug %] that looks the same as yours, please add
+ any useful extra information you have to it, rather than opening a new one.
+</p>
+
+
+<h3 id="step2">Step 2 of 3 - give information</h3>
+
+<p>
+ If you've tried a few searches and your [% terms.bug %] really isn't in
+ there, tell us all about it.
+</p>
+
+<form method="post" action="post_bug.cgi">
+ <input type="hidden" name="format" value="guided">
+ <input type="hidden" name="assigned_to" value="">
+ <input type="hidden" name="priority"
+ value="[% default.priority FILTER html %]">
+ <input type="hidden" name="version"
+ value="[% default.version FILTER html %]">
+ <input type="hidden" name="token" value="[% token FILTER html %]">
+
+<table valign="top" cellpadding="5" cellspacing="5" border="0">
+
+ <tr bgcolor="[% tablecolour %]">
+ <td align="right" valign="top">
+ <b>Product</b>
+ </td>
+ <td valign="top">
+ <input type="hidden" name="product" value="[% product.name FILTER html %]">
+ [% product.name FILTER html %]
+ </td>
+ </tr>
+
+ <tr>
+ <td align="right" valign="top">
+ <b>Component</b>
+ </td>
+ <td valign="top">
+ <table border="0" cellpadding="0" cellspacing="0">
+ <tr>
+ <td valign="top">
+ <select name="component" id="component"
+ size="5" onchange="PutDescription()">
+ [% IF NOT default.component_ %]
+ [%# Various b.m.o. products have a "General" component,
+ which is a useful default. %]
+ [% default.component_ = "General" %]
+ [% END %]
+ [% FOREACH c = product.components %]
+ <option value="[% c.name FILTER html %]"
+ [% " selected=\"selected\"" IF c.name == default.component_ %]>
+ [% c.name FILTER html %]
+ </option>
+ [% END %]
+ </select>
+ </td>
+ <td valign="top" width="100%">
+ <div id="description" style="color: green; margin-left: 10px;
+ height: 5em; overflow: auto;">
+ <script type="text/javascript">
+ if ((document.getElementById) && (document.body.innerHTML)) {
+ document.write("\
+ Select a component to see its description here.");
+ }
+ </script>
+ </div>
+ </td>
+ </tr>
+ </table>
+
+ <p>
+ The area where the problem occurs.
+ To pick the right component, you could use the same one as
+ similar [% terms.bugs %] you found in your search, or read the full list of
+ <a target="_blank" href="describecomponents.cgi?product=
+ [% product.name FILTER url_quote %]">component
+ descriptions</a> (opens in new window) if you need more help.
+ </p>
+ </td>
+ </tr>
+
+ [%# We override rep_platform and op_sys for simplicity. The values chosen
+ are based on which are most common in the b.m.o database %]
+ [% rep_platform = [ "PC", "Macintosh", "All", "Other" ] %]
+
+ <tr bgcolor="[% tablecolour %]">
+ <td align="right" valign="top">
+ <b>Hardware Platform</b>
+ </td>
+ <td valign="top">
+ [% PROCESS select sel = 'rep_platform' %]
+ </td>
+ </tr>
+
+ [% op_sys = [ "Windows 2000", "Windows XP", "Windows Vista", "Windows 7",
+ "Mac OS X", "Linux", "All", "Other" ] %]
+
+ <tr>
+ <td align="right" valign="top">
+ <b>Operating System</b>
+ </td>
+ <td valign="top">
+ [% PROCESS select sel = 'op_sys' %]
+ </td>
+ </tr>
+
+ [% IF product.name.match("Firefox|Camino|Mozilla Application Suite") %]
+ [% matches = cgi.user_agent('Gecko/(\d+)') %]
+ [% buildid = cgi.user_agent() IF matches %]
+ [% END %]
+
+ [%# Accept URL parameter build ID for non-browser products %]
+ [% IF cgi.param("buildid") %]
+ [% buildid = cgi.param("buildid") %]
+ [% END %]
+
+ <tr bgcolor="[% tablecolour %]">
+ <td align="right" valign="top">
+ <b>Build Identifier</b>
+ </td>
+ <td valign="top">
+ <input type="text" size="80" name="buildid" value="[% buildid FILTER html %]">
+ <p>
+ This should identify the exact version of the product you were using.
+ If the above field is blank or you know it is incorrect, copy the
+ version text from the product's Help |
+ About menu (for browsers this will begin with "Mozilla/5.0...").
+ If the product won't start, instead paste the complete URL you downloaded
+ it from.
+ </p>
+ </td>
+ </tr>
+
+ <tr>
+ <td align="right" valign="top">
+ <b>URL</b>
+ </td>
+ <td valign="top">
+ <input type="text" size="80" name="bug_file_loc" value="http://">
+ <p>
+ URL that demonstrates the problem you are seeing (optional).<br>
+ <b>IMPORTANT</b>: if the problem is with a broken web page, you need
+ to report it
+ <a href="https://bugzilla.mozilla.org/page.cgi?id=broken-website.html">a different way</a>.
+ </p>
+ </td>
+ </tr>
+
+ <tr bgcolor="[% tablecolour %]">
+ <td align="right" valign="top">
+ <b>Summary</b>
+ </td>
+ <td valign="top">
+ <input type="text" size="80" name="short_desc" id="short_desc"
+ maxlength="255" spellcheck="true">
+ <p>
+ A sentence which summarises the problem.
+ Please be descriptive and use lots of keywords.
+ </p>
+ <p>
+ <tt>
+ <font color="#990000">Bad example</font>: mail crashed
+ </tt>
+ <br>
+ <tt>
+ <font color="#009900">Good example</font>:
+ crash if I close the mail window while checking for new POP mail
+ </tt>
+ </p>
+ </td>
+ </tr>
+
+ <tr>
+ <td align="right" valign="top">
+ <b>Details</b>
+ </td>
+ <td valign="top">
+ [% INCLUDE global/textarea.html.tmpl
+ name = 'comment'
+ minrows = 6
+ cols = constants.COMMENT_COLS
+ %]
+ <p>
+ Expand on the Summary. Please be
+ as specific as possible about what is wrong.
+ </p>
+ <p>
+ <tt>
+ <font color="#990000">Bad example</font>: Mozilla crashed.
+ You suck!
+ </tt>
+ <br>
+ <tt>
+ <font color="#009900">Good example</font>: After a crash which
+ happened when I was sorting in the Bookmark Manager,<br> all of my
+ top-level bookmark folders beginning with the letters Q to Z are
+ no longer present.
+ </tt>
+ </p>
+ </td>
+ </tr>
+
+ <tr bgcolor="[% tablecolour %]">
+ <td align="right" valign="top">
+ <b>Reproducibility</b>
+ </td>
+ <td valign="top">
+ <select name="reproducible">
+ <option name="AlwaysReproducible" value="Always">
+ Happens every time.
+ </option>
+ <option name="Sometimes" value="Sometimes">
+ Happens sometimes, but not always.
+ </option>
+ <option name="DidntTry" value="Didn't try">
+ Haven't tried to reproduce it.
+ </option>
+ <option name="NotReproducible" value="Couldn't Reproduce">
+ Tried, but couldn't reproduce it.
+ </option>
+ </select>
+ </td>
+ </tr>
+
+ <tr>
+ <td align="right" valign="top">
+ <b>Steps to Reproduce</b>
+ </td>
+ <td valign="top">
+ [% INCLUDE global/textarea.html.tmpl
+ name = 'reproduce_steps'
+ minrows = 4
+ cols = constants.COMMENT_COLS
+ defaultcontent = "1.\n2.\n3."
+ %]
+ <p>
+ Describe how to reproduce the problem, step by
+ step. Include any special setup steps.
+ </p>
+ </td>
+ </tr>
+
+ <tr bgcolor="[% tablecolour %]">
+ <td valign="top" align="right">
+ <b>Actual Results</b>
+ </td>
+ <td valign="top">
+ [% INCLUDE global/textarea.html.tmpl
+ name = 'actual_results'
+ minrows = 4
+ cols = constants.COMMENT_COLS
+ %]
+ <p>
+ What happened after you performed the steps above?
+ </p>
+ </td>
+ </tr>
+
+ <tr>
+ <td valign="top" align="right">
+ <b>Expected Results</b>
+ </td>
+ <td valign="top">
+ [% INCLUDE global/textarea.html.tmpl
+ name = 'expected_results'
+ minrows = 4
+ cols = constants.COMMENT_COLS
+ %]
+ <p>
+ What should the software have done instead?
+ </p>
+ </td>
+ </tr>
+
+ <tr bgcolor="[% tablecolour %]">
+ <td valign="top" align="right">
+ <b>Additional Information</b>
+ </td>
+ <td valign="top">
+ [% INCLUDE global/textarea.html.tmpl
+ name = 'additional_info'
+ minrows = 8
+ cols = constants.COMMENT_COLS
+ %]
+ <p>
+ Add any additional information you feel may be
+ relevant to this [% terms.bug %], such as the <b>theme</b> you were
+ using (does the [% terms.bug %] still occur
+ with the default theme?), a
+ <b><a href="http://kb.mozillazine.org/Quality_Feedback_Agent">Talkback crash ID</a></b>, or special
+ information about <b>your computer's configuration</b>. Any information
+ longer than a few lines, such as a <b>stack trace</b> or <b>HTML
+ testcase</b>, should be added
+ using the "Add an Attachment" link on the [% terms.bug %], after
+ it is filed. If you believe that it's relevant, please also include
+ your build configuration, obtained by typing <tt>about:buildconfig</tt>
+ into your URL bar.
+ <br>
+ <br>
+ If you are reporting a crash, note the module in
+ which the software crashed (e.g., <tt>Application Violation in
+ gkhtml.dll</tt>).
+ </p>
+ </td>
+ </tr>
+
+ <tr>
+ <td valign="top" align="right">
+ <b>Severity</b>
+ </td>
+ <td valign="top">
+ <select name="bug_severity">
+ <option name="critical" value="critical">
+ Critical: The software crashes, hangs, or causes you to
+ lose data.
+ </option>
+ <option name="major" value="major">
+ Major: A major feature is broken.
+ </option>
+ <option name="normal" value="normal" selected="selected">
+ Normal: It's [% terms.abug %] that should be fixed.
+ </option>
+ <option name="minor" value="minor">
+ Minor: Minor loss of function, and there's an easy workaround.
+ </option>
+ <option name="trivial" value="trivial">
+ Trivial: A cosmetic problem, such as a misspelled word or
+ misaligned text.
+ </option>
+ <option name="enhancement" value="enhancement">
+ Enhancement: Request for new feature or enhancement.
+ </option>
+ </select>
+ <p>
+ Say how serious the problem is, or if your [% terms.bug %] is a
+ request for a new feature.
+ </p>
+ </td>
+ </tr>
+
+ [% Hook.process('form') %]
+</table>
+
+
+<h3 id="step3">Step 3 of 3 - submit the [% terms.bug %] report</h3>
+
+<p>
+ <input type="submit" id="report" value=" Submit [% terms.Bug %] Report "
+ onclick="if (this.form.comment.value == '')
+ { alert('Please enter some details about this [% terms.bug %].');
+ this.form.comment.focus();
+ return false; } return true;">
+</p>
+
+<p>
+ That's it! Thanks very much. You'll be notified by email about any
+ progress that is made on fixing your [% terms.bug %].
+
+<p>
+ Please be warned
+ that we get a lot of [% terms.bug %] reports filed - it may take quite a
+ while to get around to yours. You can help the process by making sure your
+ [%+ terms.bug %] is
+ complete and easy to understand, and by quickly replying to any questions
+ which may arrive by email.
+</p>
+
+ </form>
+
+[% PROCESS global/footer.html.tmpl %]
+
+[%############################################################################%]
+[%# Block for SELECT fields #%]
+[%############################################################################%]
+
+[% BLOCK select %]
+ <select name="[% sel %]">
+ [%- IF default.$sel %]
+ <option value="[% default.$sel FILTER html %]" selected="selected">
+ [% default.$sel FILTER html -%]
+ </option>
+ [% END %]
+ [%- FOREACH x = $sel %]
+ [% NEXT IF x == default.$sel %]
+ <option value="[% x FILTER html %]">
+ [% x FILTER html -%]
+ </option>
+ [%- END %]
+ </select>
+[% END %]
diff --git a/template/en/default/bug/create/create.html.tmpl b/template/en/default/bug/create/create.html.tmpl
new file mode 100644
index 000000000..9c27bff24
--- /dev/null
+++ b/template/en/default/bug/create/create.html.tmpl
@@ -0,0 +1,762 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ # Ville Skyttä <ville.skytta@iki.fi>
+ # Shane H. W. Travis <travis@sedsystems.ca>
+ # Marc Schumann <wurblzap@gmail.com>
+ # Akamai Technologies <bugzilla-dev@akamai.com>
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+[% title = BLOCK %]Enter [% terms.Bug %]: [% product.name FILTER html %][% END %]
+
+[% PROCESS global/header.html.tmpl
+ title = title
+ yui = [ 'autocomplete', 'calendar', 'datatable' ]
+ style_urls = [ 'skins/standard/attachment.css',
+ 'skins/standard/enter_bug.css' ]
+ javascript_urls = [ "js/attachment.js", "js/util.js",
+ "js/field.js", "js/TUI.js", "js/bug.js" ]
+ onload = 'set_assign_to();'
+%]
+
+<script type="text/javascript">
+<!--
+
+var initialowners = new Array([% product.components.size %]);
+var last_initialowner;
+var initialccs = new Array([% product.components.size %]);
+var components = new Array([% product.components.size %]);
+var comp_desc = new Array([% product.components.size %]);
+var flags = new Array([% product.components.size %]);
+[% IF Param("useqacontact") %]
+ var initialqacontacts = new Array([% product.components.size %]);
+ var last_initialqacontact;
+[% END %]
+[% count = 0 %]
+[%- FOREACH c = product.components %]
+ components[[% count %]] = "[% c.name FILTER js %]";
+ comp_desc[[% count %]] = "[% c.description FILTER html_light FILTER js %]";
+ initialowners[[% count %]] = "[% c.default_assignee.login FILTER js %]";
+ [% flag_list = [] %]
+ [% FOREACH f = c.flag_types.bug %]
+ [% NEXT UNLESS f.is_active %]
+ [% flag_list.push(f.id) %]
+ [% END %]
+ [% FOREACH f = c.flag_types.attachment %]
+ [% NEXT UNLESS f.is_active %]
+ [% flag_list.push(f.id) %]
+ [% END %]
+ flags[[% count %]] = [[% flag_list.join(",") FILTER js %]];
+ [% IF Param("useqacontact") %]
+ initialqacontacts[[% count %]] = "[% c.default_qa_contact.login FILTER js %]";
+ [% END %]
+
+ [% SET initial_cc_list = [] %]
+ [% FOREACH cc_user = c.initial_cc %]
+ [% initial_cc_list.push(cc_user.login) %]
+ [% END %]
+ initialccs[[% count %]] = "[% initial_cc_list.join(', ') FILTER js %]";
+
+ [% count = count + 1 %]
+[%- END %]
+
+function set_assign_to() {
+ // Based on the selected component, fill the "Assign To:" field
+ // with the default component owner, and the "QA Contact:" field
+ // with the default QA Contact. It also selectively enables flags.
+ var form = document.Create;
+ var assigned_to = form.assigned_to.value;
+
+[% IF Param("useqacontact") %]
+ var qa_contact = form.qa_contact.value;
+[% END %]
+
+ var index = -1;
+ if (form.component.type == 'select-one') {
+ index = form.component.selectedIndex;
+ } else if (form.component.type == 'hidden') {
+ // Assume there is only one component in the list
+ index = 0;
+ }
+ if (index != -1) {
+ var owner = initialowners[index];
+ var component = components[index];
+ if (assigned_to == last_initialowner
+ || assigned_to == owner
+ || assigned_to == '') {
+ form.assigned_to.value = owner;
+ last_initialowner = owner;
+ }
+
+ document.getElementById('initial_cc').innerHTML = initialccs[index];
+ document.getElementById('comp_desc').innerHTML = comp_desc[index];
+
+ [% IF Param("useqacontact") %]
+ var contact = initialqacontacts[index];
+ if (qa_contact == last_initialqacontact
+ || qa_contact == contact
+ || qa_contact == '') {
+ form.qa_contact.value = contact;
+ last_initialqacontact = contact;
+ }
+ [% END %]
+
+ // First, we disable all flags. Then we re-enable those
+ // which are available for the selected component.
+ var inputElements = document.getElementsByTagName("select");
+ var inputElement, flagField;
+ for ( var i=0 ; i<inputElements.length ; i++ ) {
+ inputElement = inputElements.item(i);
+ if (inputElement.name.search(/^flag_type-(\d+)$/) != -1) {
+ var id = inputElement.name.replace(/^flag_type-(\d+)$/, "$1");
+ inputElement.disabled = true;
+ // Also hide the requestee field, if it exists.
+ inputElement = document.getElementById("requestee_type-" + id);
+ if (inputElement)
+ YAHOO.util.Dom.addClass(inputElement.parentNode, 'bz_default_hidden');
+ }
+ }
+ // Now enable flags available for the selected component.
+ for (var i = 0; i < flags[index].length; i++) {
+ flagField = document.getElementById("flag_type-" + flags[index][i]);
+ // Do not enable flags the user cannot set nor request.
+ if (flagField && flagField.options.length > 1) {
+ flagField.disabled = false;
+ // Re-enabling the requestee field depends on the status
+ // of the flag.
+ toggleRequesteeField(flagField, 1);
+ }
+ }
+ }
+}
+
+function handleWantsAttachment(wants_attachment) {
+ if (wants_attachment) {
+ document.getElementById('attachment_false').style.display = 'none';
+ document.getElementById('attachment_true').style.display = 'block';
+ }
+ else {
+ document.getElementById('attachment_false').style.display = 'block';
+ document.getElementById('attachment_true').style.display = 'none';
+ clearAttachmentFields();
+ }
+}
+
+var status_comment_required = new Array();
+[% FOREACH status = bug_status %]
+ status_comment_required['[% status.name FILTER js %]'] =
+ [% status.comment_required_on_change_from() ? 'true' : 'false' %]
+[% END %]
+
+TUI_alternates['expert_fields'] = 'Show Advanced Fields';
+// Hide the Advanced Fields by default, unless the user has a cookie
+// that specifies otherwise.
+TUI_hide_default('expert_fields');
+
+-->
+</script>
+
+<form name="Create" id="Create" method="post" action="post_bug.cgi"
+ class="enter_bug_form" enctype="multipart/form-data"
+ onsubmit="return validateEnterBug(this)">
+<input type="hidden" name="product" value="[% product.name FILTER html %]">
+<input type="hidden" name="token" value="[% token FILTER html %]">
+
+<table>
+<tbody>
+ <tr>
+ <td colspan="4">
+ [%# Migration note: The following file corresponds to the old Param
+ # 'entryheaderhtml'
+ #%]
+ [% PROCESS 'bug/create/user-message.html.tmpl' %]
+ </td>
+ </tr>
+
+ <tr>
+ <td colspan="2">
+ <a id="expert_fields_controller" class="controller bz_default_hidden"
+ href="javascript:TUI_toggle_class('expert_fields')">Hide
+ Advanced Fields</a>
+ [%# Show the link if the browser supports JS %]
+ <script type="text/javascript">
+ YAHOO.util.Dom.removeClass('expert_fields_controller',
+ 'bz_default_hidden');
+ </script>
+ </td>
+ <td colspan="2">
+ (<span class="required_star">*</span> =
+ <span class="required_explanation">Required Field</span>)
+ </td>
+ </tr>
+
+ <tr>
+ [% INCLUDE bug/field.html.tmpl
+ bug = default, field = bug_fields.product, editable = 0,
+ value = product.name %]
+ [% INCLUDE bug/field.html.tmpl
+ bug = default, field = bug_fields.reporter, editable = 0,
+ value = user.login %]
+ </tr>
+
+ [%# We can't use the select block in these two cases for various reasons. %]
+ <tr>
+ [% component_desc_url = BLOCK -%]
+ describecomponents.cgi?product=[% product.name FILTER url_quote %]
+ [% END %]
+ [% INCLUDE "bug/field-label.html.tmpl"
+ field = bug_fields.component editable = 1
+ desc_url = component_desc_url
+ %]
+ <td id="field_container_component">
+ <select name="component" id="component" onchange="set_assign_to();"
+ size="7" aria-required="true" class="required">
+ [%# Build the lists of assignees and QA contacts if "usemenuforusers" is enabled. %]
+ [% IF Param("usemenuforusers") %]
+ [% assignees_list = user.get_userlist.clone %]
+ [% qa_contacts_list = user.get_userlist.clone %]
+ [% END %]
+
+ [%- FOREACH c = product.components %]
+ <option value="[% c.name FILTER html %]"
+ id="v[% c.id FILTER html %]_component"
+ [% IF c.name == default.component_ %]
+ [%# This is for bug/field.html.tmpl, for visibility-related
+ # controls. %]
+ [% default.component_id = c.id %]
+ selected="selected"
+ [% END %]>
+ [% c.name FILTER html -%]
+ </option>
+ [% IF Param("usemenuforusers") %]
+ [% INCLUDE build_userlist default_user = c.default_assignee,
+ userlist = assignees_list %]
+ [% INCLUDE build_userlist default_user = c.default_qa_contact,
+ userlist = qa_contacts_list %]
+ [% END %]
+ [%- END %]
+ </select>
+
+ <script type="text/javascript">
+ <!--
+ [%+ INCLUDE "bug/field-events.js.tmpl"
+ field = bug_fields.component %]
+ //-->
+ </script>
+ </td>
+
+ <td colspan="2" id="comp_desc_container">
+ [%# Enclose the fieldset in a nested table so that its width changes based
+ # on the length on the component description. %]
+ <table>
+ <tr>
+ <td>
+ <fieldset>
+ <legend>Component Description</legend>
+ <div id="comp_desc" class="comment">Select a component to read its description.</div>
+ </fieldset>
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+
+ <tr>
+ [% INCLUDE "bug/field-label.html.tmpl"
+ field = bug_fields.version editable = 1 rowspan = 3
+ %]
+ <td rowspan="3">
+ <select name="version" id="version" size="5">
+ [%- FOREACH v = version %]
+ <option value="[% v FILTER html %]"
+ [% ' selected="selected"' IF v == default.version %]>[% v FILTER html -%]
+ </option>
+ [%- END %]
+ </select>
+ </td>
+
+ [% INCLUDE bug/field.html.tmpl
+ bug = default, field = bug_fields.bug_severity, editable = 1,
+ value = default.bug_severity %]
+ </tr>
+
+ <tr>
+ [% INCLUDE bug/field.html.tmpl
+ bug = default, field = bug_fields.rep_platform, editable = 1,
+ value = default.rep_platform %]
+ </tr>
+
+ <tr>
+ [% INCLUDE bug/field.html.tmpl
+ bug = default, field = bug_fields.op_sys, editable = 1,
+ value = default.op_sys %]
+ </tr>
+ [% IF !Param('defaultplatform') || !Param('defaultopsys') %]
+ <tr>
+ <th colspan="3">&nbsp;</th>
+ <td id="os_guess_note" class="comment">
+ <div>We've made a guess at your
+ [% IF Param('defaultplatform') %]
+ operating system. Please check it
+ [% ELSIF Param('defaultopsys') %]
+ platform. Please check it
+ [% ELSE %]
+ operating system and platform. Please check them
+ [% END %]
+ and make any corrections if necessary.</div>
+ </td>
+ </tr>
+ [% END %]
+</tbody>
+
+<tbody class="expert_fields">
+ <tr>
+ [% IF Param('usetargetmilestone') && Param('letsubmitterchoosemilestone') %]
+ [% INCLUDE select field = bug_fields.target_milestone %]
+ [% ELSE %]
+ <td colspan="2">&nbsp;</td>
+ [% END %]
+
+ [% IF Param('letsubmitterchoosepriority') %]
+ [% INCLUDE bug/field.html.tmpl
+ bug = default, field = bug_fields.priority, editable = 1,
+ value = default.priority %]
+ [% ELSE %]
+ <td colspan="2">&nbsp;</td>
+ [% END %]
+ </tr>
+</tbody>
+
+<tbody class="expert_fields">
+ <tr>
+ <td colspan="4">&nbsp;</td>
+ </tr>
+
+ <tr>
+ [% INCLUDE bug/field.html.tmpl
+ bug = default, field = bug_fields.bug_status,
+ editable = (bug_status.size > 1), value = default.bug_status
+ override_legal_values = bug_status %]
+
+ <td>&nbsp;</td>
+ [%# Calculate the number of rows we can use for flags %]
+ [% num_rows = 6 + (Param("useqacontact") ? 1 : 0) +
+ (user.is_timetracker ? 3 : 0) +
+ (Param("usebugaliases") ? 1 : 0)
+ %]
+
+ <td rowspan="[% num_rows FILTER html %]">
+ [% IF product.flag_types.bug.size > 0 %]
+ [% display_flag_headers = 0 %]
+ [% any_flags_requesteeble = 0 %]
+
+ [% FOREACH flag_type = product.flag_types.bug %]
+ [% NEXT UNLESS flag_type.is_active %]
+ [% display_flag_headers = 1 %]
+ [% SET any_flags_requesteeble = 1 IF flag_type.is_requestable && flag_type.is_requesteeble %]
+ [% END %]
+
+ [% IF display_flag_headers %]
+ [% PROCESS "flag/list.html.tmpl" flag_types = product.flag_types.bug
+ any_flags_requesteeble = any_flags_requesteeble
+ flag_table_id = "bug_flags"
+ %]
+ [% END %]
+ [% END %]
+ </td>
+ </tr>
+
+ <tr>
+ [% INCLUDE "bug/field-label.html.tmpl"
+ field = bug_fields.assigned_to editable = 1
+ %]
+ <td colspan="2">
+ [% INCLUDE global/userselect.html.tmpl
+ id => "assigned_to"
+ name => "assigned_to"
+ value => assigned_to
+ disabled => assigned_to_disabled
+ size => 30
+ emptyok => 1
+ custom_userlist => assignees_list
+ %]
+ <noscript>(Leave blank to assign to component's default assignee)</noscript>
+ </td>
+ </tr>
+
+[% IF Param("useqacontact") %]
+ <tr>
+ [% INCLUDE "bug/field-label.html.tmpl"
+ field = bug_fields.qa_contact editable = 1
+ %]
+ <td colspan="2">
+ [% INCLUDE global/userselect.html.tmpl
+ id => "qa_contact"
+ name => "qa_contact"
+ value => qa_contact
+ disabled => qa_contact_disabled
+ size => 30
+ emptyok => 1
+ custom_userlist => qa_contacts_list
+ %]
+ <noscript>(Leave blank to assign to default qa contact)</noscript>
+ </td>
+ </tr>
+[% END %]
+
+ <tr>
+ [% INCLUDE "bug/field-label.html.tmpl"
+ field = bug_fields.cc editable = 1
+ %]
+ <td colspan="2">
+ [% INCLUDE global/userselect.html.tmpl
+ id => "cc"
+ name => "cc"
+ value => cc
+ disabled => cc_disabled
+ size => 30
+ multiple => 5
+ %]
+ </td>
+ </tr>
+
+ <tr>
+ <th>Default [% field_descs.cc FILTER html %]:</th>
+ <td colspan="2">
+ <div id="initial_cc">
+ </div>
+ </td>
+ </tr>
+
+ <tr>
+ <td colspan="3">&nbsp;</td>
+ </tr>
+
+[% IF user.is_timetracker %]
+ <tr>
+ [% INCLUDE "bug/field-label.html.tmpl"
+ field = bug_fields.estimated_time editable = 1
+ %]
+ <td colspan="2">
+ <input name="estimated_time" size="6" maxlength="6" value="[% estimated_time FILTER html %]">
+ </td>
+ </tr>
+ <tr>
+ [% INCLUDE bug/field.html.tmpl
+ bug = default, field = bug_fields.deadline, value = deadline,
+ editable = 1, value_span = 2 %]
+ </tr>
+
+ <tr>
+ <td colspan="3">&nbsp;</td>
+ </tr>
+[% END %]
+
+[% IF Param("usebugaliases") %]
+ <tr>
+ [% INCLUDE "bug/field-label.html.tmpl"
+ field = bug_fields.alias editable = 1
+ %]
+ <td colspan="2">
+ <input name="alias" size="20" value="[% alias FILTER html %]">
+ </td>
+ </tr>
+[% END %]
+
+ <tr>
+ [% INCLUDE "bug/field-label.html.tmpl"
+ field = bug_fields.bug_file_loc editable = 1
+ %]
+ <td colspan="2" class="field_value">
+ <input name="bug_file_loc" id="bug_file_loc" class="text_input"
+ size="40" value="[% bug_file_loc FILTER html %]">
+ </td>
+ </tr>
+</tbody>
+
+<tbody>
+ [% USE Bugzilla %]
+
+ [% FOREACH field = Bugzilla.active_custom_fields %]
+ [% NEXT UNLESS field.enter_bug %]
+ [% SET value = ${field.name}.defined ? ${field.name} : "" %]
+ <tr [% 'class="expert_fields"' IF !field.is_mandatory %]>
+ [% INCLUDE bug/field.html.tmpl
+ bug = default, field = field, value = value, editable = 1,
+ value_span = 3 %]
+ </tr>
+ [% END %]
+</tbody>
+
+<tbody>
+
+ <tr>
+ [% INCLUDE "bug/field-label.html.tmpl"
+ field = bug_fields.short_desc editable = 1
+ %]
+ <td colspan="3" class="field_value">
+ <input name="short_desc" size="70" value="[% short_desc FILTER html %]"
+ maxlength="255" spellcheck="true" aria-required="true"
+ class="required text_input" id="short_desc">
+ </td>
+ </tr>
+
+ [% IF feature_enabled('jsonrpc') AND !cloned_bug_id %]
+ <tr id="possible_duplicates_container" class="bz_default_hidden">
+ <th>Possible<br>Duplicates:</th>
+ <td colspan="3">
+ <div id="possible_duplicates"></div>
+ <script type="text/javascript">
+ var dt_columns = [
+ { key: "id", label: "[% field_descs.bug_id FILTER js %]",
+ formatter: YAHOO.bugzilla.dupTable.formatBugLink },
+ { key: "summary",
+ label: "[% field_descs.short_desc FILTER js %]",
+ formatter: "text" },
+ { key: "status",
+ label: "[% field_descs.bug_status FILTER js %]",
+ formatter: YAHOO.bugzilla.dupTable.formatStatus },
+ { key: "update_token", label: '',
+ formatter: YAHOO.bugzilla.dupTable.formatCcButton }
+ ];
+ YAHOO.bugzilla.dupTable.addCcMessage = "Add Me to the CC List";
+ YAHOO.bugzilla.dupTable.init({
+ container: 'possible_duplicates',
+ columns: dt_columns,
+ product_name: '[% product.name FILTER js %]',
+ summary_field: 'short_desc',
+ options: {
+ MSG_LOADING: 'Searching for possible duplicates...',
+ MSG_EMPTY: 'No possible duplicates found.',
+ SUMMARY: 'Possible Duplicates'
+ }
+ });
+ </script>
+ </td>
+ </tr>
+ [% END %]
+
+ <tr>
+ <th>Description:</th>
+ <td colspan="3">
+
+ [% defaultcontent = BLOCK %]
+ [% IF cloned_bug_id %]
++++ This [% terms.bug %] was initially created as a clone of [% terms.Bug %] #[% cloned_bug_id %] +++
+
+
+ [% END %]
+ [%-# We are within a BLOCK. The comment will be correctly HTML-escaped
+ # by global/textarea.html.tmpl. So we must not escape the comment here. %]
+ [% comment FILTER none %]
+ [%- END %]
+ [% INCLUDE global/textarea.html.tmpl
+ name = 'comment'
+ id = 'comment'
+ minrows = 10
+ maxrows = 25
+ cols = constants.COMMENT_COLS
+ defaultcontent = defaultcontent
+ %]
+ <br>
+ </td>
+ </tr>
+
+ [% IF user.is_insider %]
+ <tr class="expert_fields">
+ <th>&nbsp;</th>
+ <td colspan="3">
+ &nbsp;&nbsp;
+ <input type="checkbox" id="comment_is_private" name="comment_is_private"
+ [% ' checked="checked"' IF comment_is_private %]
+ onClick="updateCommentTagControl(this, 'comment')">
+ <label for="comment_is_private">
+ Make description and any new attachment private (visible only to members
+ of the <strong>[% Param('insidergroup') FILTER html %]</strong> group)
+ </label>
+ </td>
+ </tr>
+ [% END %]
+
+ [% IF Param("maxattachmentsize") %]
+ <tr>
+ <th>Attachment:</th>
+ <td colspan="3">
+ <script type="text/javascript">
+ <!--
+ document.write( '<div id="attachment_false">'
+ + '<input type="button" value="Add an attachment" '
+ + 'onClick="handleWantsAttachment(true)"> '
+ + '<em style="display: none">This button has no '
+ + 'functionality for you because your browser does '
+ + 'not support CSS or does not use it.<\/em>'
+ + '<\/div>'
+ + '<div id="attachment_true" style="display: none">'
+ + '<input type="button" '
+ + 'value="Don\'t add an attachment " '
+ + 'onClick="handleWantsAttachment(false)">');
+ //-->
+ </script>
+ <fieldset>
+ <legend>Add an attachment</legend>
+ <table class="attachment_entry">
+ [% PROCESS attachment/createformcontents.html.tmpl
+ flag_types = product.flag_types.attachment
+ any_flags_requesteeble = 1
+ flag_table_id ="attachment_flags" %]
+ </table>
+ </fieldset>
+ <script type="text/javascript">
+ <!--
+ document.write('<\/div>');
+ //-->
+ </script>
+ </td>
+ </tr>
+ [% END %]
+</tbody>
+
+<tbody class="expert_fields">
+ [% IF user.in_group('editbugs', product.id) %]
+ [% IF use_keywords %]
+ <tr>
+ [% INCLUDE bug/field.html.tmpl
+ bug = default, field = bug_fields.keywords, editable = 1,
+ value = keywords, desc_url = "describekeywords.cgi",
+ value_span = 2
+ %]
+ </tr>
+ [% END %]
+
+ <tr>
+ [% INCLUDE "bug/field-label.html.tmpl"
+ field = bug_fields.dependson editable = 1
+ %]
+ <td colspan="3">
+ <input name="dependson" accesskey="d" value="[% dependson FILTER html %]">
+ </td>
+ </tr>
+ <tr>
+ [% INCLUDE "bug/field-label.html.tmpl"
+ field = bug_fields.blocked editable = 1
+ %]
+ <td colspan="3">
+ <input name="blocked" accesskey="b" value="[% blocked FILTER html %]">
+ </td>
+ </tr>
+ [% END %]
+</tbody>
+
+<tbody class="expert_fields">
+ [% IF product.groups_available.size %]
+ <tr>
+ <th>&nbsp;</th>
+ <td colspan="3">
+ <br>
+ <strong>
+ Only users in all of the selected groups can view this
+ [%+ terms.bug %]:
+ </strong>
+ <br>
+ <font size="-1">
+ (Leave all boxes unchecked to make this a public [% terms.bug %].)
+ </font>
+ <br>
+ <br>
+
+ <!-- Checkboxes -->
+ [% FOREACH group = product.groups_available %]
+ <input type="checkbox" id="group_[% group.id FILTER html %]"
+ name="groups" value="[% group.name FILTER html %]"
+ [% ' checked="checked"' IF default.groups.contains(group.name)
+ OR group.is_default %]>
+ <label for="group_[% group.id FILTER html %]">
+ [%- group.description FILTER html_light %]</label><br>
+ [% END %]
+ </td>
+ </tr>
+ [% END %]
+</tbody>
+
+<tbody>
+ [%# Form controls for entering additional data about the bug being created. %]
+ [% Hook.process("form") %]
+
+ <tr>
+ <th>&nbsp;</th>
+ <td colspan="3">
+ <input type="submit" id="commit" value="Submit [% terms.Bug %]">
+ &nbsp;&nbsp;&nbsp;&nbsp;
+ <input type="submit" name="maketemplate" id="maketemplate"
+ value="Remember values as bookmarkable template"
+ class="expert_fields">
+ </td>
+ </tr>
+</tbody>
+ </table>
+ <input type="hidden" name="form_name" value="enter_bug">
+</form>
+
+[%# Links or content with more information about the bug being created. %]
+[% Hook.process("end") %]
+
+[% PROCESS global/footer.html.tmpl %]
+
+[%############################################################################%]
+[%# Block for SELECT fields #%]
+[%############################################################################%]
+
+[% BLOCK select %]
+
+ [% INCLUDE "bug/field-label.html.tmpl"
+ field = field editable = 1
+ %]
+ <td>
+ <select name="[% field.name FILTER html %]"
+ id="[% field.name FILTER html %]">
+ [%- FOREACH x = ${field.name} %]
+ <option value="[% x FILTER html %]"
+ [% " selected=\"selected\"" IF x == default.${field.name} %]>
+ [% display_value(field.name, x) FILTER html %]
+ </option>
+ [% END %]
+ </select>
+ </td>
+[% END %]
+
+[% BLOCK build_userlist %]
+ [% user_found = 0 %]
+ [% default_login = default_user.login %]
+ [% RETURN UNLESS default_login %]
+
+ [% FOREACH user = userlist %]
+ [% IF user.login == default_login %]
+ [% user_found = 1 %]
+ [% LAST %]
+ [% END %]
+ [% END %]
+
+ [% userlist.push({login => default_login,
+ identity => default_user.identity,
+ visible => 1})
+ UNLESS user_found %]
+[% END %]
diff --git a/template/en/default/bug/create/created.html.tmpl b/template/en/default/bug/create/created.html.tmpl
new file mode 100644
index 000000000..d9eaccbbf
--- /dev/null
+++ b/template/en/default/bug/create/created.html.tmpl
@@ -0,0 +1,61 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ #%]
+
+[%# INTERFACE:
+ # id: number; the ID of the bug that was created.
+ # sentmail: array of hash; bugs for which BugMail should be sent, contains:
+ # type: string; type of change for this bug, either 'created' if this bug
+ # was created or 'dep' if it was added as a dependent/blocker
+ # id: integer; the ID of the bug
+ # bug: object; Bugzilla::Bug object of the bug that was created (used in
+ # template bug/edit.html.tmpl
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% PROCESS "bug/show-header.html.tmpl" %]
+[% PROCESS global/header.html.tmpl
+ title = "$terms.Bug $id Submitted &ndash; $filtered_desc"
+ header = "$terms.Bug&nbsp;$id Submitted"
+%]
+
+[% header_done = 1 %]
+
+[% FOREACH item = sentmail %]
+ [% PROCESS bug/process/results.html.tmpl
+ type = item.type
+ id = item.id
+ sent_bugmail = item
+ %]
+[% END %]
+
+<br>
+
+<hr>
+
+[% PROCESS bug/edit.html.tmpl %]
+
+<hr>
+
+[% PROCESS bug/navigate.html.tmpl bottom_navigator => 1 %]
+
+<br>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/bug/create/make-template.html.tmpl b/template/en/default/bug/create/make-template.html.tmpl
new file mode 100644
index 000000000..13974833a
--- /dev/null
+++ b/template/en/default/bug/create/make-template.html.tmpl
@@ -0,0 +1,46 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ # Marc Schumann <wurblzap@gmail.com>
+ #%]
+
+[%# INTERFACE:
+ # url: URL to a pre-filled bug entry form.
+ # short_desc: Bug summary as entered in the form.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% PROCESS global/header.html.tmpl
+ title = "Bookmarks are your friend"
+ header = "Template constructed"
+%]
+
+<p>
+ You can bookmark this link:
+ &ldquo;<a href="enter_bug.cgi?[% url FILTER html %]">
+ [% IF short_desc %]
+ [% short_desc FILTER html %]
+ [% ELSE %]
+ [% terms.Bug %] entry template
+ [% END %]</a>&rdquo;.
+ This bookmark will bring up the <em>Enter [% terms.Bug %]</em> page with the
+ fields initialized as you've requested.
+</p>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/bug/create/user-message.html.tmpl b/template/en/default/bug/create/user-message.html.tmpl
new file mode 100644
index 000000000..ac2cc29df
--- /dev/null
+++ b/template/en/default/bug/create/user-message.html.tmpl
@@ -0,0 +1,36 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Matthew Tuck <matty@chariot.net.au>
+ #%]
+
+[%# Migration note: this file corresponds to the old Param
+ # 'entryheaderhtml'
+ #%]
+
+[%# You can make the output of this template product-specific by using
+ # Template Toolkit IF statements. The current product name is stored in
+ # the 'product' variable.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+Before reporting [% terms.abug %], please read the
+<a href="page.cgi?id=bug-writing.html">
+[% terms.bug %] writing guidelines</a>, please look at the list of
+<a href="duplicates.cgi">most frequently reported [% terms.bugs %]</a>, and please
+<a href="query.cgi">search</a> for the [% terms.bug %].
diff --git a/template/en/default/bug/dependency-graph.html.tmpl b/template/en/default/bug/dependency-graph.html.tmpl
new file mode 100644
index 000000000..37dcde012
--- /dev/null
+++ b/template/en/default/bug/dependency-graph.html.tmpl
@@ -0,0 +1,106 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ #%]
+
+[%# INTERFACE:
+ # bug_id: integer. The number of the bug(s).
+ # multiple_bugs: boolean. True if bug_id contains > 1 bug number.
+ # doall: boolean. True if we are displaying every bug in the database.
+ # showsummary: boolean. True if we are showing bug summaries.
+ # rankdir: string. "TB" if we are ranking top-to-bottom,
+ "LR" if left-to-right.
+ # image_url: string. The URL of the graphic showing the dependencies.
+ # map_url: string. The URL of the map file for the image. (Optional)
+ # image_map: string. The image map for the graphic showing the
+ dependencies. (Optional)
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% title = "Dependency Graph"
+ header = title
+ %]
+
+[% IF NOT multiple_bugs AND NOT doall %]
+ [% filtered_desc = short_desc FILTER html %]
+ [% title = "$title for $terms.bug $bug_id"
+ header = "$header for $terms.bug <a href=\"show_bug.cgi?id=$bug_id\">$bug_id</a>"
+ subheader = filtered_desc
+ %]
+[% END %]
+
+[% PROCESS global/header.html.tmpl %]
+
+[% image_map %]
+
+<p>
+ Green circles represent open [% terms.bugs %].
+</p>
+
+[% IF image_map %]
+ <img src="[% image_url %]" alt="Dependency graph" usemap="#imagemap">
+[% ELSE %]
+ <a href="[% map_url %]">
+ <img src="[% image_url %]" alt="Dependency graph" ismap="ismap">
+ </a>
+[% END %]
+
+<hr>
+
+<form action="showdependencygraph.cgi" method="GET">
+ <table>
+ <tr>
+ <th align="left"><label for="id">[% terms.Bug %] numbers</label>:</th>
+ <td><input id="id" name="id" value="[% bug_id %]"></td>
+ <td>
+ <input type="checkbox" id="showsummary" name="showsummary" [% " checked" IF showsummary %]>
+ <label for="showsummary">Show the summaries of all displayed [% terms.bugs %]</label>
+ </td>
+ </tr>
+
+ <tr>
+ <th align="left"><label for="display">Display:</label></th>
+ <td colspan="2">
+ <select id="display" name="display">
+ <option value="tree"[% 'selected="selected"' IF (!display || display == "tree") %]>
+ Restrict to [% terms.bugs %] having a direct relationship with entered [% terms.bugs %]</option>
+ <option value="web" [% 'selected="selected"' IF display == "web" %]>
+ Show all [% terms.bugs %] having any relationship with entered [% terms.bugs %]</option>
+ <option value="doall" [% 'selected="selected"' IF display == "doall" %]>
+ Show every [% terms.bug %] in the system with dependencies</option>
+ </select>
+ </td>
+ </tr>
+
+ <tr>
+ <th align="left"><label for="rankdir">Orientation:</label></th>
+ <td colspan="2">
+ <select id="rankdir" name="rankdir">
+ <option value="TB"[% " selected" IF rankdir == "TB" %]>Top to bottom</option>
+ <option value="BT"[% " selected" IF rankdir == "BT" %]>Bottom to top</option>
+ <option value="LR"[% " selected" IF rankdir == "LR" %]>Left to right</option>
+ <option value="RL"[% " selected" IF rankdir == "RL" %]>Right to left</option>
+ </select>
+ </td>
+ </tr>
+ </table>
+ <input type="submit" id="change" value="Change Parameters">
+</form>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/bug/dependency-tree.html.tmpl b/template/en/default/bug/dependency-tree.html.tmpl
new file mode 100644
index 000000000..627c89d60
--- /dev/null
+++ b/template/en/default/bug/dependency-tree.html.tmpl
@@ -0,0 +1,266 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Tobias Burnus <burnus@net-b.de>
+ # Ville Skyttä <ville.skytta@iki.fi>
+ # Myk Melez <myk@mozilla.org>
+ # André Batosti <batosti@async.com.br>
+ #%]
+
+[% PROCESS 'global/field-descs.none.tmpl' %]
+
+[% filtered_desc = blocked_tree.$bugid.short_desc FILTER html %]
+[% PROCESS global/header.html.tmpl
+ title = "Dependency tree for $terms.Bug $bugid"
+ header = "Dependency tree for
+ <a href=\"show_bug.cgi?id=$bugid\">$terms.Bug $bugid</a>"
+ javascript_urls = ["js/expanding-tree.js"]
+ style_urls = ["skins/standard/dependency-tree.css"]
+ subheader = filtered_desc
+ doc_section = "hintsandtips.html#dependencytree"
+%]
+
+[% PROCESS depthControlToolbar %]
+
+[% INCLUDE tree_section ids=dependson_ids type=1 %]
+
+[% INCLUDE tree_section ids=blocked_ids type=2 %]
+
+[% PROCESS depthControlToolbar %]
+
+[% PROCESS global/footer.html.tmpl %]
+
+[%###########################################################################%]
+[%# Tree-drawing blocks #%]
+[%###########################################################################%]
+
+[% BLOCK tree_section %]
+ [%# INTERFACE
+ # - ids: a list of bug IDs to be displayed as children
+ # - type: the type of tree. 1 = depends on, 2 = blockeds
+ # GLOBALS
+ # - seen: Maintains a global hash of bugs that have been displayed
+ #%]
+ [% global.seen = {} %]
+ [%# Display the tree of bugs that this bug depends on. %]
+ <h3>
+ <a href="show_bug.cgi?id=[% bugid %]">[% terms.Bug %] [%+ bugid %]</a>
+ [% IF type == 1 %]
+ [% tree_name = "dependson_tree" %]
+ [% IF ids.size %]
+ depends on
+ [% ELSE %]
+ does not depend on any [% terms.bugs %].
+ [% END %]
+ [% ELSIF type == 2 %]
+ [% tree_name = "blocked_tree" %]
+ [% IF ids.size %]
+ blocks
+ [% ELSE %]
+ does not block any [% terms.bugs %].
+ [% END %]
+ [% END %]
+ [% IF ids.size %]
+ [%+ (ids.size == 1) ? "one" : ids.size %]
+ [%+ IF hide_resolved %]open[% END %]
+ [%+ (ids.size == 1) ? terms.bug : terms.bugs %]:
+ [% END %]
+ </h3>
+ [% IF ids.size %]
+ ([% IF maxdepth -%]Up to [% maxdepth %] level[% "s" IF maxdepth > 1 %] deep | [% END -%]
+ <a href="buglist.cgi?bug_id=[% ids.join(",") %]">view as [% terms.bug %] list</a>
+ [% IF user.in_group('editbugs') && ids.size > 1 %]
+ | <a href="buglist.cgi?bug_id=[% ids.join(",") %]&amp;tweak=1">change several</a>
+ [% END %])
+ <ul class="tree">
+ [% INCLUDE display_tree tree=$tree_name %]
+ </ul>
+ [% END %]
+[% END %]
+
+
+[% BLOCK display_tree %]
+ [%# INTERFACE
+ # - bugid: the ID of the bug being displayed
+ # - tree: a hash of bug objects and of bug dependencies
+ #%]
+ [% bug = tree.$bugid %]
+ <li>
+ [%- INCLUDE bullet bugid=bugid tree=tree -%]
+ <span class="summ[% "_deep" IF tree.dependencies.$bugid.size %]"
+ id="[% bugid FILTER html %]"
+ [% IF global.seen.$bugid %]
+ onMouseover="duplicatedover('[% bugid FILTER html %]')"
+ onMouseout="duplicatedout('[% bugid FILTER html %]')"
+ [% END %]>
+ [%- INCLUDE buglink bug=bug bugid=bugid %]
+ </span>
+ [% IF global.seen.$bugid %]
+ <b><a title="Already displayed above; click to locate"
+ onclick="duplicated('[% bugid FILTER html %]')"
+ href="#b[% bugid %]">(*)</a></b>
+ [% ELSIF tree.dependencies.$bugid.size %]
+ <ul>
+ [% FOREACH depid = tree.dependencies.$bugid %]
+ [% INCLUDE display_tree bugid=depid %]
+ [% END %]
+ </ul>
+ [% END %]
+ </li>
+ [% global.seen.$bugid = 1 %]
+[% END %]
+
+[% BLOCK bullet %]
+ [% IF tree.dependencies.$bugid.size && ! global.seen.$bugid %]
+ [% extra_class = " b_open" %]
+ [% extra_args = 'onclick="return doToggle(this, event)"' %]
+ [% END %]
+ <a id="b[% bugid %]"
+ class="b [%+ extra_class FILTER none %]"
+ title="Click to expand or contract this portion of the tree. Hold down the Ctrl key while clicking to expand or contract all subtrees."
+ [% extra_args FILTER none %]>&nbsp;&nbsp;</a>
+[% END %]
+
+[% BLOCK buglink %]
+ [% isclosed = !bug.isopened %]
+ [% FILTER closed(isclosed) -%]
+ <a title="[% INCLUDE buginfo bug=bug %]"
+ href="show_bug.cgi?id=[% bugid %]">
+ <b>[%- bugid %]:</b>
+ <span class="summ_text">[%+ bug.short_desc FILTER html %]</span>
+ <span class="summ_info">[[% INCLUDE buginfo %]]</span>
+ </a>
+ <a href="showdependencytree.cgi?id=[% bugid FILTER url_quote %]"
+ class="tree_link">
+ <img src="skins/standard/dependency-tree/tree.png"
+ title="See dependency tree for [% terms.bug %] [%+ bugid FILTER html %]">
+ </a>
+ [% END %]
+[% END %]
+
+[% BLOCK buginfo %]
+ [% display_value("bug_status", bug.bug_status) FILTER html -%] [%+ display_value("resolution", bug.resolution) FILTER html %];
+ [%-%] assigned to [% bug.assigned_to.login FILTER email FILTER html %]
+ [%-%][% "; Target: " _ bug.target_milestone IF bug.target_milestone %]
+[% END %]
+
+[%###########################################################################%]
+[%# Block for depth control toolbar #%]
+[%###########################################################################%]
+
+[% BLOCK depthControlToolbar %]
+ <table cellpadding="3" border="0" cellspacing="0" bgcolor="#e0e0e0">
+ <tr>
+ [%# Hide/show resolved button
+ Swaps text depending on the state of hide_resolved %]
+ <td align="center">
+ <form method="get" action="showdependencytree.cgi"
+ style="display: inline; margin: 0px;">
+ <input name="id" type="hidden" value="[% bugid %]">
+ [% IF maxdepth %]
+ <input name="maxdepth" type="hidden" value="[% maxdepth %]">
+ [% END %]
+ <input type="hidden" name="hide_resolved" value="[% hide_resolved ? 0 : 1 %]">
+ <input type="submit" id="toggle_visibility"
+ value="[% IF hide_resolved %]Show[% ELSE %]Hide[% END %] Resolved">
+ </form>
+ </td>
+
+ <td>
+ Max Depth:
+ </td>
+
+ <td>
+ &nbsp;
+ </td>
+
+ <td>
+ <form method="get" action="showdependencytree.cgi"
+ style="display: inline; margin: 0px;">
+ [%# set to one form %]
+ <input type="submit" id="change_maxdepth"
+ value="&nbsp;1&nbsp;"
+ [% "disabled" IF realdepth < 2 || maxdepth == 1 %]>
+ <input name="id" type="hidden" value="[% bugid %]">
+ <input name="maxdepth" type="hidden" value="1">
+ <input name="hide_resolved" type="hidden" value="[% hide_resolved %]">
+ </form>
+ </td>
+
+ <td>
+ <form method="get" action="showdependencytree.cgi"
+ style="display: inline; margin: 0px;">
+ [%# Minus one form
+ Allow subtracting only when realdepth and maxdepth > 1 %]
+ <input name="id" type="hidden" value="[% bugid %]">
+ <input name="maxdepth" type="hidden" value="[%
+ maxdepth == 1 ? 1
+ : ( maxdepth ? maxdepth - 1 : realdepth - 1 )
+ %]">
+ <input name="hide_resolved" type="hidden" value="[% hide_resolved %]">
+ <input type="submit" id="decrease_depth" value="&nbsp;&lt;&nbsp;"
+ [% "disabled" IF realdepth < 2 || ( maxdepth && maxdepth < 2 ) %]>
+ </form>
+ </td>
+
+ <td>
+ <form method="get" action="showdependencytree.cgi"
+ style="display: inline; margin: 0px;">
+ [%# Limit entry form: the button can not do anything when total depth
+ is less than two, so disable it %]
+ <input name="maxdepth" size="4" maxlength="4" value="[%
+ maxdepth > 0 && maxdepth <= realdepth ? maxdepth : ""
+ %]">
+ <input name="id" type="hidden" value="[% bugid %]">
+ <input name="hide_resolved" type="hidden" value="[% hide_resolved %]">
+ <noscript>
+ <input type="submit" id="change_depth" value="Change"
+ [% "disabled" IF realdepth < 2 %]>
+ </noscript>
+ </form>
+ </td>
+
+ <td>
+ <form method="get" action="showdependencytree.cgi"
+ style="display: inline; margin: 0px;">
+ [%# plus one form
+ Disable button if total depth < 2, or if depth set to unlimited %]
+ <input name="id" type="hidden" value="[% bugid %]">
+ [% IF maxdepth %]
+ <input name="maxdepth" type="hidden" value="[% maxdepth + 1 %]">
+ [% END %]
+ <input name="hide_resolved" type="hidden" value="[% hide_resolved %]">
+ <input type="submit" id="increase_depth" value="&nbsp;&gt;&nbsp;"
+ [% "disabled" IF realdepth < 2 || !maxdepth || maxdepth >= realdepth %]>
+ </form>
+ </td>
+
+ <td>
+ <form method="get" action="showdependencytree.cgi"
+ style="display: inline; margin: 0px;">
+ [%# Unlimited button %]
+ <input name="id" type="hidden" value="[% bugid %]">
+ <input name="hide_resolved" type="hidden" value="[% hide_resolved %]">
+ <input type="submit" id="remove_limit"
+ value="&nbsp;Unlimited&nbsp;"
+ [% "disabled" IF maxdepth == 0 || maxdepth == realdepth %]>
+ </form>
+ </td>
+ </tr>
+</table>
+
+[% END %]
diff --git a/template/en/default/bug/edit.html.tmpl b/template/en/default/bug/edit.html.tmpl
new file mode 100644
index 000000000..7105e3418
--- /dev/null
+++ b/template/en/default/bug/edit.html.tmpl
@@ -0,0 +1,1179 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ # Vaskin Kissoyan <vkissoyan@yahoo.com>
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ # Olav Vitters <olav@bkor.dhs.org>
+ # Guy Pyrzak <guy.pyrzak@gmail.com>
+ # Elliotte Martin <emartin@everythingsolved.com>
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+[% PROCESS bug/time.html.tmpl %]
+
+ <script type="text/javascript">
+ <!--
+
+ /* Outputs a link to call replyToComment(); used to reduce HTML output */
+ function addReplyLink(id, real_id) {
+ /* XXX this should really be updated to use the DOM Core's
+ * createElement, but finding a container isn't trivial.
+ */
+ [% IF user.settings.quote_replies.value != 'off' %]
+ document.write('[<a href="#add_comment" onclick="replyToComment(' +
+ id + ',' + real_id + '); return false;">reply<' + '/a>]');
+ [% END %]
+ }
+
+ /* Adds the reply text to the `comment' textarea */
+ function replyToComment(id, real_id) {
+ var prefix = "(In reply to comment #" + id + ")\n";
+ var replytext = "";
+ [% IF user.settings.quote_replies.value == 'quoted_reply' %]
+ /* pre id="comment_name_N" */
+ var text_elem = document.getElementById('comment_text_'+id);
+ var text = getText(text_elem);
+
+ /* make sure we split on all newlines -- IE or Moz use \r and \n
+ * respectively.
+ */
+ text = text.split(/\r|\n/);
+
+ for (var i=0; i < text.length; i++) {
+ replytext += "> " + text[i] + "\n";
+ }
+
+ replytext = prefix + replytext + "\n";
+ [% ELSIF user.settings.quote_replies.value == 'simple_reply' %]
+ replytext = prefix;
+ [% END %]
+
+ [% IF user.is_insider %]
+ if (document.getElementById('isprivate_' + real_id).checked) {
+ document.getElementById('newcommentprivacy').checked = 'checked';
+ updateCommentTagControl(document.getElementById('newcommentprivacy'), 'comment');
+ }
+ [% END %]
+
+ /* <textarea id="comment"> */
+ var textarea = document.getElementById('comment');
+ textarea.value += replytext;
+
+ textarea.focus();
+ }
+
+ if (typeof Node == 'undefined') {
+ /* MSIE doesn't define Node, so provide a compatibility object */
+ window.Node = {
+ TEXT_NODE: 3,
+ ENTITY_REFERENCE_NODE: 5
+ };
+ }
+
+ /* Concatenates all text from element's childNodes. This is used
+ * instead of innerHTML because we want the actual text (and
+ * innerText is non-standard).
+ */
+ function getText(element) {
+ var child, text = "";
+ for (var i=0; i < element.childNodes.length; i++) {
+ child = element.childNodes[i];
+ var type = child.nodeType;
+ if (type == Node.TEXT_NODE || type == Node.ENTITY_REFERENCE_NODE) {
+ text += child.nodeValue;
+ } else {
+ /* recurse into nodes of other types */
+ text += getText(child);
+ }
+ }
+ return text;
+ }
+
+[% IF user.is_timetracker %]
+ var fRemainingTime = [% bug.remaining_time %]; // holds the original value
+ function adjustRemainingTime() {
+ // subtracts time spent from remaining time
+ var new_time;
+
+ // prevent negative values if work_time > fRemainingTime
+ new_time =
+ Math.max(fRemainingTime - document.changeform.work_time.value, 0.0);
+ // get upto 2 decimal places
+ document.changeform.remaining_time.value =
+ Math.round(new_time * 100)/100;
+ }
+
+ function updateRemainingTime() {
+ // if the remaining time is changed manually, update fRemainingTime
+ fRemainingTime = document.changeform.remaining_time.value;
+ }
+
+[% END %]
+
+ /* Index all classifications so we can keep track of the classification
+ * for the selected product, which could control field visibility.
+ */
+ var all_classifications = new Array([% bug.choices.product.size %]);
+ [%- FOREACH product = bug.choices.product %]
+ all_classifications['[% product.name FILTER js %]'] = '
+ [%- product.classification.name FILTER js %]';
+ [%- END %]
+
+ //-->
+ </script>
+
+<form name="changeform" id="changeform" method="post" action="process_bug.cgi">
+
+ <input type="hidden" name="delta_ts" value="[% bug.delta_ts %]">
+ <input type="hidden" name="longdesclength" value="[% bug.comments.size %]">
+ <input type="hidden" name="id" value="[% bug.bug_id %]">
+ <input type="hidden" name="token" value="[% issue_hash_token([bug.id, bug.delta_ts]) FILTER html %]">
+
+ [% PROCESS section_title %]
+ <table class="edit_form">
+ <tr>
+ [%# 1st Column %]
+ <td id="bz_show_bug_column_1" class="bz_show_bug_column">
+ <table>
+ [%# *** ID, product, component, status, resolution, Hardware, and OS *** %]
+ [% PROCESS section_status %]
+
+ [% PROCESS section_spacer %]
+
+ [% PROCESS section_details1 %]
+
+ [% PROCESS section_spacer %]
+
+ [%# *** severity, priority, version and milestone *** %]
+ [% PROCESS section_details2 %]
+
+ [%# *** assigned to and qa contact *** %]
+ [% PROCESS section_people %]
+
+ [% PROCESS section_spacer %]
+
+ [% PROCESS section_url_keyword_whiteboard %]
+
+ [% PROCESS section_spacer %]
+
+ [%# *** Dependencies *** %]
+ [% PROCESS section_dependson_blocks %]
+
+ </table>
+ </td>
+ <td>
+ <div class="bz_column_spacer">&nbsp;</div>
+ </td>
+ [%# 2nd Column %]
+ <td id="bz_show_bug_column_2" class="bz_show_bug_column">
+ <table cellpadding="3" cellspacing="1">
+ [%# *** Reported and modified dates *** %]
+ [% PROCESS section_dates %]
+
+ [% PROCESS section_cclist %]
+
+ [% PROCESS section_spacer %]
+
+ [% PROCESS section_see_also %]
+
+ [% PROCESS section_customfields %]
+
+ [% PROCESS section_spacer %]
+
+ [% Hook.process("after_custom_fields") %]
+
+ [% PROCESS section_flags %]
+
+ </table>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="3">
+ <hr id="bz_top_half_spacer">
+ </td>
+ </tr>
+ </table>
+
+ <table id="bz_big_form_parts" cellspacing="0" cellpadding="0"><tr>
+ <td>
+ [% IF user.is_timetracker %]
+ [% PROCESS section_timetracking %]
+ [% END %]
+
+ [%# *** Attachments *** %]
+
+ [% PROCESS attachment/list.html.tmpl
+ attachments = bug.attachments
+ bugid = bug.bug_id
+ num_attachment_flag_types = bug.num_attachment_flag_types
+ show_attachment_flags = bug.show_attachment_flags
+ %]
+
+ [% IF user.settings.comment_box_position.value == 'before_comments' %]
+ [% PROCESS comment_box %]
+ [% END %]
+ </td>
+ <td>
+ [% PROCESS section_restrict_visibility %]
+ </td>
+ </tr></table>
+
+ [%# *** Additional Comments *** %]
+ <div id="comments">
+ [% PROCESS bug/comments.html.tmpl
+ comments = bug.comments
+ mode = user.id ? "edit" : "show"
+ %]
+ </div>
+
+ [% IF user.settings.comment_box_position.value == 'after_comments' %]
+ <hr>
+ [% PROCESS comment_box %]
+ [% END %]
+
+</form>
+
+[%############################################################################%]
+[%# Block for the Title (alias and short desc) #%]
+[%############################################################################%]
+
+[% BLOCK section_title %]
+ [%# That's the main table, which contains all editable fields. %]
+ <div class="bz_alias_short_desc_container edit_form">
+ [% PROCESS commit_button id="_top"%]
+ <a href="show_bug.cgi?id=[% bug.bug_id %]">
+ [%-# %]<b>[% terms.Bug %]&nbsp;[% bug.bug_id FILTER html %]</b>
+ [%-# %]</a> -<span id="summary_alias_container" class="bz_default_hidden">
+ [% IF Param("usebugaliases") %]
+ [% IF bug.alias != "" %]
+ (<span id="alias_nonedit_display">[% bug.alias FILTER html %]</span>)
+ [% END %]
+ [% END %]
+ <span id="short_desc_nonedit_display">[% bug.short_desc FILTER quoteUrls(bug) %]</span>
+ [% IF bug.check_can_change_field('short_desc', 0, 1) ||
+ bug.check_can_change_field('alias', 0, 1) %]
+ <small class="editme">(<a href="#" id="editme_action">edit</a>)</small>
+ [% END %]
+ </span>
+
+
+ <div id="summary_alias_input">
+ <table id="summary">
+ [% IF Param("usebugaliases") %]
+ <tr>
+ [% IF bug.check_can_change_field('alias', 0, 1) %]
+ <td>
+ <label
+ for="alias"
+ title="a name for the
+ [% terms.bug %] that can be used in place of its ID number,
+ [%%] e.g. when adding it to a list of dependencies"
+ >Alias</label>:</td><td>
+ [% ELSIF bug.alias %]
+ <td colspan="2">(
+ [% ELSE %]
+ <td colspan="2">
+ [% END %]
+ [% PROCESS input inputname => "alias"
+ size => "20"
+ maxlength => "20"
+ no_td => 1
+ %][% ")" IF NOT bug.check_can_change_field('alias', 0, 1)
+ && bug.alias %]
+ </td>
+ </tr>
+ [% END %]
+ [%# *** Summary *** %]
+ <tr>
+ <td>
+ <label accesskey="s" for="short_desc"><u>S</u>ummary</label>:
+ </td>
+ <td>
+ [% PROCESS input inputname => "short_desc" size => "80" colspan => 2
+ maxlength => 255 spellcheck => "true" no_td => 1 %]
+ </td>
+ </tr>
+ </table>
+ </div>
+ </div>
+ <script type="text/javascript">
+ hideAliasAndSummary('[% bug.short_desc FILTER js %]', '[% bug.alias FILTER js %]');
+ </script>
+[% END %]
+
+[%############################################################################%]
+[%# Block for the first table in the "Details" section #%]
+[%############################################################################%]
+
+[% BLOCK section_details1 %]
+
+ [%#############%]
+ [%# PRODUCT #%]
+ [%#############%]
+ <tr>
+ [% INCLUDE bug/field.html.tmpl
+ bug = bug, field = bug_fields.product,
+ override_legal_values = bug.choices.product
+ desc_url = 'describecomponents.cgi', value = bug.product
+ editable = bug.check_can_change_field('product', 0, 1) %]
+ </tr>
+
+ [%# Classification is here so that it can be used in value controllers
+ # and visibility controllers. It comes after product because
+ # it uses some javascript that depends on the existence of the
+ # product field.
+ #%]
+ <tr class="bz_default_hidden">
+ [% INCLUDE bug/field.html.tmpl
+ bug = bug field = bug_fields.classification
+ override_legal_values = bug.choices.classification
+ value = bug.classification
+ editable = bug.check_can_change_field('product', 0, 1) %]
+ </tr>
+ [%###############%]
+ [%# Component #%]
+ [%###############%]
+ <tr>
+ [% INCLUDE bug/field.html.tmpl
+ bug = bug, field = bug_fields.component, value = bug.component
+ override_legal_values = bug.choices.component
+ desc_url = "describecomponents.cgi?product=$bug.product"
+ editable = bug.check_can_change_field('component', 0, 1)
+ %]
+ </tr>
+ <tr>
+ <td class="field_label">
+ <label for="version"><b>Version</b></label>:
+ </td>
+
+ [% PROCESS select selname => "version" %]
+ </tr>
+ [%############%]
+ [%# PLATFORM #%]
+ [%############%]
+ <tr>
+ <td class="field_label">
+ <label for="rep_platform" accesskey="h"><b>Platform</b></label>:
+ </td>
+ <td class="field_value">
+ [% INCLUDE bug/field.html.tmpl
+ bug = bug, field = bug_fields.rep_platform,
+ no_tds = 1, value = bug.rep_platform
+ editable = bug.check_can_change_field('rep_platform', 0, 1) %]
+ [%+ INCLUDE bug/field.html.tmpl
+ bug = bug, field = bug_fields.op_sys,
+ no_tds = 1, value = bug.op_sys
+ editable = bug.check_can_change_field('op_sys', 0, 1) %]
+ <script type="text/javascript">
+ assignToDefaultOnChange(['product', 'component']);
+ </script>
+ </td>
+ </tr>
+
+
+
+[% END %]
+
+[%############################################################################%]
+[%# Block for the status section #%]
+[%############################################################################%]
+
+[% BLOCK section_status %]
+ <tr>
+ <td class="field_label">
+ <b><a href="page.cgi?id=fields.html#status">Status</a></b>:
+ </td>
+ <td id="bz_field_status">
+ <span id="static_bug_status">
+ [% display_value("bug_status", bug.bug_status) FILTER html %]
+ [% IF bug.resolution %]
+ [%+ display_value("resolution", bug.resolution) FILTER html %]
+ [% IF bug.dup_id %]
+ of [% "${terms.bug} ${bug.dup_id}" FILTER bug_link(bug.dup_id) FILTER none %]
+ [% END %]
+ [% END %]
+ [% IF bug.user.canedit || bug.user.isreporter %]
+ (<a href="#add_comment"
+ onclick="window.setTimeout(function() { document.getElementById('bug_status').focus(); }, 10)">edit</a>)
+ [% END %]
+ </span>
+ </td>
+ </tr>
+[% END %]
+
+[%############################################################################%]
+[%# Block for the second table in the "Details" section #%]
+[%############################################################################%]
+
+[% BLOCK section_details2 %]
+
+ [%###############################################################%]
+ [%# Importance (priority and severity) #%]
+ [%###############################################################%]
+ <tr>
+ <td class="field_label">
+ <label for="priority" accesskey="i">
+ <b><a href="page.cgi?id=fields.html#importance"><u>I</u>mportance</a></b></label>:
+ </td>
+ <td>
+ [% INCLUDE bug/field.html.tmpl
+ bug = bug, field = bug_fields.priority,
+ no_tds = 1, value = bug.priority
+ editable = bug.check_can_change_field('priority', 0, 1) %]
+ [%+ INCLUDE bug/field.html.tmpl
+ bug = bug, field = bug_fields.bug_severity,
+ no_tds = 1, value = bug.bug_severity
+ editable = bug.check_can_change_field('bug_severity', 0, 1) %]
+ [% Hook.process('after_importance', 'bug/edit.html.tmpl') %]
+ </td>
+ </tr>
+
+ [% IF Param("usetargetmilestone") && bug.target_milestone %]
+ <tr>
+ <td class="field_label">
+ <label for="target_milestone">
+ <a href="page.cgi?id=fields.html#target_milestone">
+ Target&nbsp;Milestone</a></label>:
+ </td>
+ [% PROCESS select selname = "target_milestone" %]
+ </tr>
+ [% END %]
+
+[% END %]
+
+[%############################################################################%]
+[%# Block for the table in the "People" section #%]
+[%############################################################################%]
+
+[% BLOCK section_people %]
+
+ <tr>
+ <td class="field_label">
+ <b><a href="page.cgi?id=fields.html#assigned_to">Assigned To</a></b>:
+ </td>
+ <td>
+ [% IF bug.check_can_change_field("assigned_to", 0, 1) %]
+ <div id="bz_assignee_edit_container" class="bz_default_hidden">
+ <span>
+ [% INCLUDE global/user.html.tmpl who = bug.assigned_to %]
+ (<a href="#" id="bz_assignee_edit_action">edit</a>)
+ </span>
+ </div>
+ <div id="bz_assignee_input">
+ [% INCLUDE global/userselect.html.tmpl
+ id => "assigned_to"
+ name => "assigned_to"
+ value => bug.assigned_to.login
+ classes => ["bz_userfield"]
+ size => 30
+ %]
+ <br>
+ <input type="checkbox" id="set_default_assignee" name="set_default_assignee" value="1">
+ <label id="set_default_assignee_label" for="set_default_assignee">Reset Assignee to default</label>
+ </div>
+ <script type="text/javascript">
+ hideEditableField('bz_assignee_edit_container',
+ 'bz_assignee_input',
+ 'bz_assignee_edit_action',
+ 'assigned_to',
+ '[% bug.assigned_to.login FILTER js %]' );
+ initDefaultCheckbox('assignee');
+ </script>
+ [% ELSE %]
+ [% INCLUDE global/user.html.tmpl who = bug.assigned_to %]
+ [% END %]
+ </td>
+ </tr>
+
+ [% IF Param('useqacontact') %]
+ <tr>
+ <td class="field_label">
+ <label for="qa_contact" accesskey="q"><b><u>Q</u>A Contact</b></label>:
+ </td>
+ <td>
+ [% IF bug.check_can_change_field("qa_contact", 0, 1) %]
+ [% IF bug.qa_contact != "" %]
+ <div id="bz_qa_contact_edit_container" class="bz_default_hidden">
+ <span>
+ <span id="bz_qa_contact_edit_display">
+ [% INCLUDE global/user.html.tmpl who = bug.qa_contact %]</span>
+ (<a href="#" id="bz_qa_contact_edit_action">edit</a>)
+ </span>
+ </div>
+ [% END %]
+ <div id="bz_qa_contact_input">
+ [% INCLUDE global/userselect.html.tmpl
+ id => "qa_contact"
+ name => "qa_contact"
+ value => bug.qa_contact.login
+ size => 30
+ classes => ["bz_userfield"]
+ emptyok => 1
+ %]
+ <br>
+ <input type="checkbox" id="set_default_qa_contact" name="set_default_qa_contact" value="1">
+ <label for="set_default_qa_contact" id="set_default_qa_contact_label">Reset QA Contact to default</label>
+ </div>
+ <script type="text/javascript">
+ [% IF bug.qa_contact != "" %]
+ hideEditableField('bz_qa_contact_edit_container',
+ 'bz_qa_contact_input',
+ 'bz_qa_contact_edit_action',
+ 'qa_contact',
+ '[% bug.qa_contact.login FILTER js %]');
+ [% END %]
+ initDefaultCheckbox('qa_contact');
+ </script>
+ [% ELSE %]
+ [% INCLUDE global/user.html.tmpl who = bug.qa_contact %]
+ [% END %]
+ </td>
+ </tr>
+ [% END %]
+[% END %]
+
+[%############################################################################%]
+[%# Block for URL Keyword and Whiteboard #%]
+[%############################################################################%]
+[% BLOCK section_url_keyword_whiteboard %]
+[%# *** URL Whiteboard Keywords *** %]
+ <tr>
+ <td class="field_label">
+ <label for="bug_file_loc" accesskey="u"><b>
+ [% IF is_safe_url(bug.bug_file_loc) %]
+ <a href="[% bug.bug_file_loc FILTER html %]"><u>U</u>RL</a>
+ [% ELSE %]
+ <u>U</u>RL
+ [% END %]
+ [%%]</b></label>:
+ </td>
+ <td>
+ [% IF bug.check_can_change_field("bug_file_loc", 0, 1) %]
+ <span id="bz_url_edit_container" class="bz_default_hidden">
+ [% IF is_safe_url(bug.bug_file_loc) %]
+ <a href="[% bug.bug_file_loc FILTER html %]" target="_blank"
+ title="[% bug.bug_file_loc FILTER html %]">
+ [% bug.bug_file_loc FILTER truncate(40) FILTER html %]</a>
+ [% ELSE %]
+ [% bug.bug_file_loc FILTER html %]
+ [% END %]
+ (<a href="#" id="bz_url_edit_action">edit</a>)</span>
+ [% END %]
+ <span id="bz_url_input_area">
+ [% url_output = PROCESS input no_td=1 inputname => "bug_file_loc" size => "40" colspan => 2 %]
+ [% IF NOT bug.check_can_change_field("bug_file_loc", 0, 1)
+ AND is_safe_url(bug.bug_file_loc) %]
+ <a href="[% bug.bug_file_loc FILTER html %]">[% url_output FILTER none %]</a>
+ [% ELSE %]
+ [% url_output FILTER none %]
+ [% END %]
+ </span>
+ [% IF bug.check_can_change_field("bug_file_loc", 0, 1) %]
+ <script type="text/javascript">
+ hideEditableField('bz_url_edit_container',
+ 'bz_url_input_area',
+ 'bz_url_edit_action',
+ 'bug_file_loc',
+ "[% bug.bug_file_loc FILTER js %]");
+ </script>
+ [% END %]
+ </td>
+ </tr>
+
+ [% IF Param('usestatuswhiteboard') %]
+ <tr>
+ <td class="field_label">
+ <label for="status_whiteboard" accesskey="w"><b><u>W</u>hiteboard</b></label>:
+ </td>
+ [% PROCESS input inputname => "status_whiteboard" size => "40" colspan => 2 %]
+ </tr>
+ [% END %]
+
+ [% IF use_keywords %]
+ <tr>
+ <td class="field_label">
+ <label for="keywords" accesskey="k">
+ <b><a href="describekeywords.cgi"><u>K</u>eywords</a></b></label>:
+ </td>
+ <td class="field_value" colspan="2">
+ [% INCLUDE bug/field.html.tmpl
+ bug = bug, field = bug_fields.keywords, value = bug.keywords
+ editable = bug.check_can_change_field("keywords", 0, 1),
+ no_tds = 1
+ %]
+ </td>
+ </tr>
+ [% END %]
+[% END %]
+
+[%############################################################################%]
+[%# Block for Depends On / Blocks #%]
+[%############################################################################%]
+[% BLOCK section_dependson_blocks %]
+ <tr>
+ [% PROCESS dependencies
+ dep = { title => "Depends&nbsp;on", fieldname => "dependson" } %]
+ </tr>
+
+ <tr>
+ [% PROCESS dependencies accesskey = "b"
+ dep = { title => "<u>B</u>locks", fieldname => "blocked" } %]
+
+ <tr>
+ <th>&nbsp;</th>
+
+ <td colspan="2" align="left" id="show_dependency_tree_or_graph">
+ Show dependency <a href="showdependencytree.cgi?id=[% bug.bug_id %]&amp;hide_resolved=1">tree</a>
+
+ [% IF Param('webdotbase') %]
+ /&nbsp;<a href="showdependencygraph.cgi?id=[% bug.bug_id %]">graph</a>
+ [% END %]
+ </td>
+ </tr>
+[% END %]
+
+
+[%############################################################################%]
+[%# Block for Restricting Visibility #%]
+[%############################################################################%]
+
+[% BLOCK section_restrict_visibility %]
+ [% RETURN UNLESS bug.groups.size %]
+
+ <div class="bz_group_visibility_section">
+ [% inallgroups = 1 %]
+ [% inagroup = 0 %]
+ [% emitted_description = 0 %]
+
+ [% FOREACH group = bug.groups %]
+ [% SET inallgroups = 0 IF NOT group.ingroup %]
+ [% SET inagroup = 1 IF group.ison %]
+
+ [% NEXT IF group.mandatory %]
+
+ [% IF NOT emitted_description %]
+ [% emitted_description = 1 %]
+ <div id="bz_restrict_group_visibility_help">
+ <b>Only users in all of the selected groups can view this
+ [%+ terms.bug %]:</b>
+ <p class="instructions">
+ Unchecking all boxes makes this a more public [% terms.bug %].
+ </p>
+ </div>
+ [% END %]
+
+ [% IF group.ingroup %]
+ <input type="hidden" name="defined_groups"
+ value="[% group.name FILTER html %]">
+ [% END %]
+
+ <input type="checkbox" value="[% group.name FILTER html %]"
+ name="groups" id="group_[% group.bit %]"
+ [% ' checked="checked"' IF group.ison %]
+ [% ' disabled="disabled"' IF NOT group.ingroup %]>
+ <label for="group_[% group.bit %]">
+ [%- group.description FILTER html_light %]</label>
+ <br>
+ [% END %]
+
+ [% IF emitted_description %]
+ [% IF NOT inallgroups %]
+ <p class="instructions">Only members of a group can change the
+ visibility of [% terms.abug %] for that group.</p>
+ [% END %]
+ [% END %]
+
+ [% IF inagroup %]
+ <div id="bz_enable_role_visibility_help">
+ <b>Users in the roles selected below can always view
+ this [% terms.bug %]:</b>
+ </div>
+ <div id="bz_enable_role_visibility">
+ <div>
+ [% user_can_edit_accessible =
+ bug.check_can_change_field("reporter_accessible", 0, 1)
+ %]
+ [% IF user_can_edit_accessible %]
+ <input type="hidden" name="defined_reporter_accessible" value="1">
+ [% END %]
+ <input type="checkbox" value="1"
+ name="reporter_accessible" id="reporter_accessible"
+ [% " checked" IF bug.reporter_accessible %]
+ [% " disabled=\"disabled\"" UNLESS user_can_edit_accessible %]>
+ <label for="reporter_accessible">Reporter</label>
+ </div>
+ <div>
+ [% user_can_edit_accessible =
+ bug.check_can_change_field("cclist_accessible", 0, 1)
+ %]
+ [% IF user_can_edit_accessible %]
+ <input type="hidden" name="defined_cclist_accessible" value="1">
+ [% END %]
+ <input type="checkbox" value="1"
+ name="cclist_accessible" id="cclist_accessible"
+ [% " checked" IF bug.cclist_accessible %]
+ [% " disabled=\"disabled\"" UNLESS user_can_edit_accessible %]>
+ <label for="cclist_accessible">CC List</label>
+ </div>
+ <p class="instructions">
+ The assignee
+ [% IF (Param('useqacontact')) %]
+ and QA contact
+ [% END %]
+ can always see [% terms.abug %], and this section does not
+ take effect unless the [% terms.bug %] is restricted to at
+ least one group.
+ </p>
+ </div>
+ [% END %]
+ </div> [%# bz_group_visibility_section %]
+[% END %]
+
+[%############################################################################%]
+[%# Block for Dates #%]
+[%############################################################################%]
+
+[% BLOCK section_dates %]
+ <tr>
+ <td class="field_label">
+ <b>Reported</b>:
+ </td>
+ <td>
+ [% bug.creation_ts FILTER time %] by [% INCLUDE global/user.html.tmpl who = bug.reporter %]
+ </td>
+ </tr>
+
+ <tr>
+ <td class="field_label">
+ <b> Modified</b>:
+ </td>
+ <td>
+ [% bug.delta_ts FILTER time FILTER replace(':\d\d$', '') FILTER replace(':\d\d ', ' ')%]
+ (<a href="show_activity.cgi?id=[% bug.bug_id %]">[%# terms.Bug %]History</a>)
+ </td>
+
+ </tr>
+[% END %]
+
+[%############################################################################%]
+[%# Block for CC LIST #%]
+[%############################################################################%]
+[% BLOCK section_cclist %]
+ <tr>
+ <td class="field_label">
+ <label for="newcc" accesskey="a"><b>CC List</b>:</label>
+ </td>
+ <td>
+ [% IF user.id %]
+ [% IF NOT bug.cc || NOT bug.cc.contains(user.login) %]
+ [% has_role = bug.user.isreporter
+ || bug.assigned_to.id == user.id
+ || (Param('useqacontact')
+ && bug.qa_contact
+ && bug.qa_contact.id == user.id) %]
+ <input type="checkbox" id="addselfcc" name="addselfcc"
+ [% " checked=\"checked\""
+ IF user.settings.state_addselfcc.value == 'always'
+ || (!has_role
+ && user.settings.state_addselfcc.value == 'cc_unless_role') %]>
+ <label for="addselfcc">Add me to CC list</label>
+ <br>
+ [% END %]
+ [% END %]
+ [% bug.cc.size || 0 FILTER html %]
+ [% IF bug.cc.size == 1 %]
+ user
+ [% ELSE %]
+ users
+ [% END %]
+ [% IF user.id %]
+ [% IF bug.cc.contains( user.email ) %]
+ including you
+ [% END %]
+ [% END %]
+ [% IF user.id || bug.cc.size %]
+ <span id="cc_edit_area_showhide_container" class="bz_default_hidden">
+ (<a href="#" id="cc_edit_area_showhide">[% IF user.id %]edit[% ELSE %]show[% END %]</a>)
+ </span>
+ [% END %]
+ <div id="cc_edit_area">
+ <br>
+ [% IF user.id %]
+ <div>
+ <div><label for="cc"><b>Add</b></label></div>
+ [% INCLUDE global/userselect.html.tmpl
+ id => "newcc"
+ name => "newcc"
+ value => ""
+ size => 30
+ classes => ["bz_userfield"]
+ multiple => 5
+ %]
+ </div>
+ [% END %]
+ [% IF bug.cc %]
+ <select id="cc" name="cc" multiple="multiple" size="5">
+ [% FOREACH c = bug.cc %]
+ <option value="[% c FILTER email FILTER html %]">
+ [% c FILTER email FILTER html %]</option>
+ [% END %]
+ </select>
+ [% IF user.id %]
+ <br>
+ <input type="checkbox" id="removecc" name="removecc">
+ [%%]<label for="removecc">Remove selected CCs</label>
+ <br>
+ [% END %]
+ [% END %]
+ </div>
+ [% IF user.id || bug.cc.size %]
+ <script type="text/javascript">
+ hideEditableField( 'cc_edit_area_showhide_container',
+ 'cc_edit_area',
+ 'cc_edit_area_showhide',
+ '',
+ '');
+ </script>
+ [% END %]
+ </td>
+ </tr>
+[% END %]
+
+[%############################################################################%]
+[%# Block for See Also #%]
+[%############################################################################%]
+[% BLOCK section_see_also %]
+ [% IF Param('use_see_also') || bug.see_also.size %]
+ <tr>
+ [% INCLUDE bug/field.html.tmpl
+ field = bug_fields.see_also
+ value = bug.see_also
+ editable = bug.check_can_change_field('see_also', 0, 1)
+ %]
+ </tr>
+ [% END %]
+[% END %]
+
+[%############################################################################%]
+[%# Block for FLAGS #%]
+[%############################################################################%]
+
+[% BLOCK section_flags %]
+ [%# *** Flags *** %]
+ [% show_bug_flags = 0 %]
+ [% FOREACH type = bug.flag_types %]
+ [% IF (type.flags && type.flags.size > 0) || (user.id && type.is_active) %]
+ [% show_bug_flags = 1 %]
+ [% LAST %]
+ [% END %]
+ [% END %]
+ [% IF show_bug_flags %]
+ <tr>
+ <td class="field_label flags_label">
+ <label><b>Flags:</b></label>
+ </td>
+ <td></td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ [% IF bug.flag_types.size > 0 %]
+ [% PROCESS "flag/list.html.tmpl" flag_no_header = 1
+ flag_types = bug.flag_types
+ any_flags_requesteeble = bug.any_flags_requesteeble %]
+ [% END %]
+ </td>
+ </tr>
+ [% END %]
+[% END %]
+
+[%############################################################################%]
+[%# Block for Custom Fields #%]
+[%############################################################################%]
+
+[% BLOCK section_customfields %]
+[%# *** Custom Fields *** %]
+ [% USE Bugzilla %]
+ [% FOREACH field = Bugzilla.active_custom_fields %]
+ <tr>
+ [% PROCESS bug/field.html.tmpl value = bug.${field.name}
+ editable = bug.check_can_change_field(field.name, 0, 1)
+ value_span = 2 %]
+ </tr>
+ [% IF extra_field_item %]
+ <tr>
+ <th class="field_label">[% extra_field_item.header FILTER none %]</th>
+ <td>[% extra_field_item.data FILTER none %]</td>
+ </tr>
+ [% END %]
+ [% END %]
+[% END %]
+
+[%############################################################################%]
+[%# Block for Section Spacer #%]
+[%############################################################################%]
+
+[% BLOCK section_spacer %]
+ <tr>
+ <td colspan="2" class="bz_section_spacer"></td>
+ </tr>
+[% END %]
+
+
+
+
+[%############################################################################%]
+[%# Block for dependencies #%]
+[%############################################################################%]
+
+[% BLOCK dependencies %]
+
+ <th class="field_label">
+ <label for="[% dep.fieldname %]"[% " accesskey=\"$accesskey\"" IF accesskey %]>
+ [% dep.title %]</label>:
+ </th>
+ <td>
+ <span id="[% dep.fieldname %]_input_area">
+ [% IF bug.check_can_change_field(dep.fieldname, 0, 1) %]
+ <input name="[% dep.fieldname %]" id="[% dep.fieldname %]"
+ class="text_input"
+ value="[% bug.${dep.fieldname}.join(', ') %]">
+ [% END %]
+ </span>
+
+ [% FOREACH depbug = bug.${dep.fieldname} %]
+ [% depbug FILTER bug_link(depbug, use_alias => 1) FILTER none %][% " " %]
+ [% END %]
+ [% IF bug.check_can_change_field(dep.fieldname, 0, 1) %]
+ <span id="[% dep.fieldname %]_edit_container" class="edit_me bz_default_hidden" >
+ (<a href="#" id="[% dep.fieldname %]_edit_action">edit</a>)
+ </span>
+ <script type="text/javascript">
+ hideEditableField('[% dep.fieldname %]_edit_container',
+ '[% dep.fieldname %]_input_area',
+ '[% dep.fieldname %]_edit_action',
+ '[% dep.fieldname %]',
+ "[% bug.${dep.fieldname}.join(', ') %]");
+ </script>
+ [% END %]
+ </td>
+
+ [% accesskey = undef %]
+
+[% END %]
+
+[%############################################################################%]
+[%# Block for Time Tracking Group #%]
+[%############################################################################%]
+
+[% BLOCK section_timetracking %]
+ <table class="bz_time_tracking_table">
+ <tr>
+ <th>
+ <label for="estimated_time">Orig. Est.</label>
+ </th>
+ <th>
+ Current Est.
+ </th>
+ <th>
+ <label for="work_time">Hours Worked</label>
+ </th>
+ <th>
+ <label for="remaining_time">Hours Left</label>
+ </th>
+ <th>
+ %Complete
+ </th>
+ <th>
+ Gain
+ </th>
+ <th>
+ <label for="deadline">Deadline</label>
+ </th>
+ </tr>
+ <tr>
+ <td>
+ <input name="estimated_time" id="estimated_time"
+ value="[% PROCESS formattimeunit
+ time_unit=bug.estimated_time %]"
+ size="6" maxlength="6">
+ </td>
+ <td>
+ [% PROCESS formattimeunit
+ time_unit=(bug.actual_time + bug.remaining_time) %]
+ </td>
+ <td>
+ [% PROCESS formattimeunit time_unit=bug.actual_time %] +
+ <input name="work_time" id="work_time"
+ value="0" size="3" maxlength="6"
+ onchange="adjustRemainingTime();">
+ </td>
+ <td>
+ <input name="remaining_time" id="remaining_time"
+ value="[% PROCESS formattimeunit
+ time_unit=bug.remaining_time %]"
+ size="6" maxlength="6" onchange="updateRemainingTime();">
+ </td>
+ <td>
+ [% PROCESS calculatepercentage act=bug.actual_time
+ rem=bug.remaining_time %]
+ </td>
+ <td>
+ [% PROCESS formattimeunit time_unit=bug.estimated_time - (bug.actual_time + bug.remaining_time) %]
+ </td>
+ <td>
+ [% INCLUDE bug/field.html.tmpl
+ field = bug_fields.deadline, value = bug.deadline, no_tds = 1
+ editable = bug.check_can_change_field('deadline', 0, 1) %]
+ </td>
+ </tr>
+ <tr>
+ <td colspan="7" class="bz_summarize_time">
+ <a href="summarize_time.cgi?id=[% bug.bug_id %]&amp;do_depends=1">
+ Summarize time (including time for [% terms.bugs %]
+ blocking this [% terms.bug %])</a>
+ </td>
+ </tr>
+ </table>
+[% END %]
+
+[%############################################################################%]
+[%# Block for the Additional Comments box #%]
+[%############################################################################%]
+
+[% BLOCK comment_box %]
+ <div id="add_comment" class="bz_section_additional_comments">
+ [% IF user.id %]
+ <label for="comment" accesskey="c"><b>Additional
+ <u>C</u>omments</b></label>:
+
+ [% IF user.is_insider %]
+ <input type="checkbox" name="comment_is_private" value="1"
+ id="newcommentprivacy"
+ onClick="updateCommentTagControl(this, 'comment')">
+ <label for="newcommentprivacy">
+ Make comment private (visible only to members of the
+ <strong>[% Param('insidergroup') FILTER html %]</strong> group)
+ </label>
+ [% END %]
+
+ <!-- This table keeps the submit button aligned with the box. -->
+ <table><tr><td>
+ [% INCLUDE global/textarea.html.tmpl
+ name = 'comment'
+ id = 'comment'
+ minrows = 10
+ maxrows = 25
+ cols = constants.COMMENT_COLS
+ %]
+ [% Hook.process("after_comment_textarea", 'bug/edit.html.tmpl') %]
+ <br>
+ [% PROCESS commit_button id=""%]
+
+ <table id="bug_status_bottom"
+ class="status" cellspacing="0" cellpadding="0">
+ <tr>
+ <td class="field_label">
+ <b><a href="page.cgi?id=fields.html#status">Status</a></b>:
+ </td>
+ <td>
+ [% PROCESS bug/knob.html.tmpl %]
+ </td>
+ </tr>
+ </table>
+ </td></tr></table>
+
+ [%# For logged-out users %]
+ [% ELSE %]
+ <table><tr><td><fieldset>
+ <legend>Note</legend>
+ You need to
+ <a href="show_bug.cgi?id=
+ [%- bug.bug_id %]&amp;GoAheadAndLogIn=1">log in</a>
+ before you can comment on or make changes to this [% terms.bug %].
+ </fieldset></table><tr></td>
+ [% END %]
+ </div>
+[% END %]
+
+[%############################################################################%]
+[%# Block for SELECT fields #%]
+[%############################################################################%]
+
+[% BLOCK select %]
+ <td>
+ [% IF bug.check_can_change_field(selname, 0, 1)
+ AND bug.choices.${selname}.size > 1 %]
+ <select id="[% selname %]" name="[% selname %]">
+ [% FOREACH x = bug.choices.${selname} %]
+ <option value="[% x.name FILTER html %]"
+ [% " selected" IF x.name == bug.${selname} %]>
+ [%- x.name FILTER html %]
+ </option>
+ [% END %]
+ </select>
+ [% ELSE %]
+ [% bug.${selname} FILTER html %]
+ [% END %]
+ </td>
+[% END %]
+
+[%############################################################################%]
+[%# Block for INPUT fields #%]
+[%############################################################################%]
+
+[% BLOCK input %]
+ [% IF no_td != 1 %]
+ <td[% " colspan=\"$colspan\"" IF colspan %]>
+ [% END %]
+ [% val = value ? value : bug.$inputname %]
+ [% IF bug.check_can_change_field(inputname, 0, 1) %]
+ <input id="[% inputname %]" name="[% inputname %]" class="text_input"
+ value="[% val FILTER html %]"[% " size=\"$size\"" IF size %]
+ [% " maxlength=\"$maxlength\"" IF maxlength %]
+ [% " spellcheck=\"$spellcheck\"" IF spellcheck %]>
+ [% ELSE %]
+ [% IF size && val.length > size %]
+ <span title="[% val FILTER html %]">
+ [% val FILTER truncate(size) FILTER html %]
+ </span>
+ [% ELSE %]
+ [% val FILTER html %]
+ [% END %]
+ [% END %]
+ [% IF no_td != 1 %]
+ </td>
+ [% END %]
+ [% no_td = 0 %]
+ [% maxlength = 0 %]
+ [% colspan = 0 %]
+ [% size = 0 %]
+ [% value = undef %]
+ [% spellcheck = undef %]
+[% END %]
+[% BLOCK commit_button %]
+ [% IF user.id %]
+ <div class="knob-buttons">
+ <input type="submit" value="Save Changes"
+ id="commit[% id FILTER css_class_quote %]">
+ </div>
+ [% END %]
+[% END %]
diff --git a/template/en/default/bug/field-events.js.tmpl b/template/en/default/bug/field-events.js.tmpl
new file mode 100644
index 000000000..f9e0ea93d
--- /dev/null
+++ b/template/en/default/bug/field-events.js.tmpl
@@ -0,0 +1,44 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is the San Jose State
+ # University Foundation. Portions created by the Initial Developer
+ # are Copyright (C) 2008 the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[%# INTERFACE:
+ # field: a Bugzilla::Field object
+ #%]
+
+[% FOREACH controlled_field = field.controls_visibility_of %]
+ showFieldWhen('[% controlled_field.name FILTER js %]',
+ '[% field.name FILTER js %]',
+ '[% controlled_field.visibility_value.name FILTER js %]');
+[% END %]
+[% FOREACH legal_value = field.legal_values %]
+ [% FOREACH controlled_field = legal_value.controlled_values.keys %]
+ [% SET cont_ids = [] %]
+ [% FOREACH val = legal_value.controlled_values.$controlled_field %]
+ [% cont_ids.push(val.id) %]
+ [% END %]
+ [% NEXT IF !cont_ids.size %]
+ showValueWhen('[% controlled_field FILTER js %]',
+ [[% cont_ids.join(',') FILTER js %]],
+ '[% field.name FILTER js %]',
+ [% legal_value.id FILTER js %]);
+ [% END %]
+[% END %]
+[% IF field.name == 'classification' %]
+ YAHOO.util.Event.on('product', 'change', setClassification);
+[% END %]
diff --git a/template/en/default/bug/field-help.none.tmpl b/template/en/default/bug/field-help.none.tmpl
new file mode 100644
index 000000000..558cad6c6
--- /dev/null
+++ b/template/en/default/bug/field-help.none.tmpl
@@ -0,0 +1,239 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[%# This file describes both bug fields and search fields. %]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+[% help_html = {
+
+# Note that all these keys here are in alphabetical order, though
+# search-specific fields are at the bottom.
+
+##############
+# Bug Fields #
+##############
+
+alias =>
+ "A short, unique name assigned to $terms.abug in order to assist with
+ looking it up and referring to it in other places in ${terms.Bugzilla}.",
+
+assigned_to =>
+ "The person in charge of resolving the ${terms.bug}.",
+
+blocked =>
+ "This $terms.bug must be resolved before the $terms.bugs listed in this
+ field can be resolved.",
+
+bug_file_loc =>
+ "$terms.Bugs can have a URL associated with them - for example, a"
+ _ " pointer to a web site where the problem is seen.",
+
+bug_id =>
+ "The numeric id of $terms.abug, unique within this entire installation"
+ _ " of ${terms.Bugzilla}.",
+
+bug_severity =>
+ "How severe the $terms.bug is, or whether it's an enhancement.",
+
+bug_status =>
+ "$terms.Abug may be in any of a number of states.",
+
+cc =>
+ "Users who may not have a direct role to play on this $terms.bug, but who
+ are interested in its progress.",
+
+classification =>
+ "$terms.Bugs are categorised into Classifications, Products and"
+ _ " Components. classifications is the top-level categorisation.",
+
+component =>
+ "Components are second-level categories; each belongs to a"
+ _ " particular Product. Select a Product to narrow down this list.",
+
+creation_ts =>
+ "When the $terms.bug was filed.",
+
+deadline =>
+ "The date that this $terms.bug must be resolved by, entered in YYYY-MM-DD
+ format.",
+
+delta_ts =>
+ "When this $terms.bug was last updated.",
+
+dependson =>
+ "The $terms.bugs listed here must be resolved before this $terms.bug
+ can be resolved.",
+
+estimated_time =>
+ "The amount of time that has been estimated it will take to resolve
+ this ${terms.bug}.",
+
+keywords =>
+ "You can add keywords from a defined list to $terms.bugs, in order"
+ _ " to tag and group them.",
+
+longdesc =>
+ "$terms.Bugs have comments added to them by $terms.Bugzilla users."
+ _ "You can search for some text in those comments.",
+
+op_sys =>
+ "The operating system the $terms.bug was observed on.",
+
+percentage_complete =>
+ "How close to 100% done this $terms.bug is, by comparing its
+ $field_descs.work_time to its ${field_descs.estimated_time}.",
+
+priority =>
+ "Engineers prioritize their $terms.bugs using this field.",
+
+# Note that this has extra text added below if useclassification is on.
+product =>
+ "$terms.Bugs are categorised into Products and Components.",
+
+qa_contact =>
+ "The person responsible for confirming this $terms.bug if it is"
+ _ " unconfirmed, and for verifying the fix once the $terms.bug"
+ _ " has been resolved.",
+
+remaining_time =>
+ "The number of hours of work left on this $terms.bug, calculated by
+ subtracting the $field_descs.work_time from the
+ ${field_descs.estimated_time}.",
+
+rep_platform =>
+ "The hardware platform the $terms.bug was observed on.",
+
+reporter =>
+ "The person who filed this ${terms.bug}.",
+
+resolution =>
+ "If $terms.abug is in a resolved state, then one of these reasons"
+ _ " will be given for its resolution.",
+
+see_also =>
+ "This allows you to refer to $terms.bugs in other installations.
+ You can enter a URL to $terms.abug in the 'Add $terms.Bug URLs'
+ field to note that that $terms.bug is related to this one. You can
+ enter multiple URLs at once by separating them with a comma.
+
+ <p>You should normally use this field to refer to $terms.bugs in
+ <em>other</em> installations. For $terms.bugs in this
+ installation, it is better to use the $field_descs.dependson and
+ $field_descs.blocked fields.</p>",
+
+short_desc =>
+ "The $terms.bug summary is a short sentence which succinctly"
+ _ " describes what the $terms.bug is about.",
+
+status_whiteboard =>
+ "Each $terms.bug has a free-form single line text entry box for"
+ _ " adding tags and status information.",
+
+target_milestone =>
+ "The $field_descs.target_milestone field is used to define when the"
+ _ " engineer the $terms.bug is assigned to expects to fix it.",
+
+version =>
+ "The version field defines the version of the software the"
+ _ " $terms.bug was found in.",
+
+votes =>
+ "Some $terms.bugs can be voted for, and you can limit your search to"
+ _ " $terms.bugs with more than a certain number of votes.",
+
+work_time =>
+ "The total amount of time spent on this $terms.bug so far.",
+
+##########################
+# Search-specific fields #
+##########################
+
+chfield =>
+ "You can search for specific types of change - this field defines"
+ _" which field you are interested in changes for.",
+
+# Duplicated to chfieldto below, also.
+chfieldfrom =>
+ "Specify the start and end dates either in YYYY-MM-DD format
+ optionally followed by HH:mm, in 24 hour clock), or in relative
+ dates such as 1h, 2d, 3w, 4m, 5y, which respectively mean one hour,
+ two days, three weeks, four months, or five years ago. 0d is last
+ midnight, and 0h, 0w, 0m, 0y is the beginning of this hour, week,
+ month, or year.",
+
+chfieldvalue =>
+ "The value the field defined above changed to during that time.",
+
+content =>
+ "This is a field available in searches that does a Google-like
+ 'full-text' search on the $field_descs.short_desc and
+ $field_descs.longdesc fields.",
+
+# Duplicated to email2 below, also.
+email1 =>
+ "Every $terms.bug has people associated with it in different"
+ _ " roles. Here, you can search on what people are in what role.",
+
+} %]
+
+[% help_html.email2 = help_html.email1 %]
+[% help_html.chfieldto = help_html.chfieldfrom %]
+[% help_html.deadlinefrom = help_html.deadline %]
+[% help_html.deadlineto = help_html.deadline %]
+
+[% help_all_note = BLOCK %]
+ <strong>Note:</strong> When searching, selecting the option "All"
+ only finds [% terms.bugs %] whose value for this field is literally
+ the word "All".
+[% END %]
+[% FOREACH all_field = ['op_sys', 'rep_platform'] %]
+ [% help_html.$all_field = help_html.$all_field _ ' ' _ help_all_note %]
+[% END %]
+
+[% IF Param('useclassification') %]
+ [% help_html.product = help_html.product
+ _ " Select a Classification to narrow down this list." %]
+[% END %]
+
+[% FOREACH help_field = bug_fields.keys %]
+
+ [%# Add help for custom fields. %]
+ [% IF !help_html.${help_field}.defined %]
+ [% SET field_type = bug_fields.${help_field}.type %]
+ [% field_type_desc = BLOCK -%]
+ [% field_types.$field_type FILTER html %]
+ [%- END %]
+ [% help_html.${help_field} =
+ "A custom $field_type_desc field in this installation"
+ _ " of ${terms.Bugzilla}." %]
+ [% END %]
+
+ [%# Add help for the search types, for query.cgi. %]
+ [% type_desc = BLOCK %]
+ The type of [% field_descs.${help_field} FILTER html %] search you
+ would like.
+ [% END %]
+ [% SET type_name = help_field _ '_type' %]
+ [% help_html.$type_name = type_desc %]
+[% END %]
+
+[% Hook.process("end") %]
diff --git a/template/en/default/bug/field-label.html.tmpl b/template/en/default/bug/field-label.html.tmpl
new file mode 100644
index 000000000..0b794f82a
--- /dev/null
+++ b/template/en/default/bug/field-label.html.tmpl
@@ -0,0 +1,53 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010 the
+ # Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[%# INTERFACE:
+ # field: a Bugzilla::Field object
+ # desc_url: An alternate link to help for the field.
+ # hidden: True if the field label should start hidden.
+ # rowspan: a "rowspan" value for the label's <th>.
+ # tag_name: the tag to use to surround the label
+ #%]
+
+[% PROCESS "bug/field-help.none.tmpl" %]
+[% DEFAULT tag_name = "th" %]
+<[% tag_name FILTER html %] class="field_label [% ' bz_hidden_field' IF hidden %]
+ [%- ' required' IF field.is_mandatory && NOT bug.id %]"
+ id="field_label_[% field.name FILTER html %]"
+ [% IF rowspan %] rowspan="[% rowspan FILTER html %]"[% END %]>
+
+ [% IF editable %]
+ <label for="[% field.name FILTER html %]">
+ [% END %]
+
+ <a
+ [% IF help_html.${field.name}.defined %]
+ title="[% help_html.${field.name} FILTER txt FILTER collapse FILTER html %]"
+ class="field_help_link"
+ [% END %]
+ [% IF desc_url %]
+ href="[% desc_url FILTER html %]"
+ [% ELSE %]
+ href="page.cgi?id=fields.html#[% field.name FILTER url_quote %]"
+ [% END %]
+ >[%- field_descs.${field.name} FILTER html %]:</a>
+
+ [% '</label>' IF editable %]
+</[% tag_name FILTER html %]>
diff --git a/template/en/default/bug/field.html.tmpl b/template/en/default/bug/field.html.tmpl
new file mode 100644
index 000000000..b014a6e6f
--- /dev/null
+++ b/template/en/default/bug/field.html.tmpl
@@ -0,0 +1,230 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Myk Melez <myk@mozilla.org>
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ # Elliotte Martin <elliotte_martin@yahoo.com>
+ # Guy Pyrzak <guy.pyrzak@gmail.com>
+ # Reed Loden <reed@reedloden.com>
+ #%]
+
+[%# INTERFACE:
+ # field: a Bugzilla::Field object
+ # value: The value of the field for this bug.
+ # override_legal_values (optional): The list of legal values, for select fields.
+ # editable: Whether the field should be displayed as an editable
+ # <input> or as just the plain text of its value.
+ # allow_dont_change: display the --do_not_change-- option for select fields.
+ # value_span: A colspan for the table cell containing
+ # the field value.
+ # no_tds: boolean; if true, don't display the label <th> or the
+ # wrapping <td> for the field.
+ # desc_url: string; Normally the label of a non-custom field links to
+ # fields.html. If you want it to link elsewhere, specify the
+ # relative URL you want to link to, here. Remember to call
+ # url_quote on any query string arguments.
+ # bug (optional): The current Bugzilla::Bug being displayed, or a hash
+ # with default field values being displayed on a page.
+ #%]
+
+[% SET hidden = 0 %]
+[% IF field.visibility_field.defined AND bug
+ AND !field.visibility_value.is_set_on_bug(bug)
+%]
+ [% SET hidden = 1 %]
+[% END %]
+
+[% IF NOT no_tds %]
+ [% PROCESS "bug/field-label.html.tmpl" %]
+ <td class="field_value [% ' bz_hidden_field' IF hidden %]"
+ id="field_container_[% field.name FILTER html %]"
+ [% " colspan=\"$value_span\"" FILTER none IF value_span %]>
+[% END %]
+[% Hook.process('start_field_column') %]
+[% IF editable %]
+ [% SWITCH field.type %]
+ [% CASE constants.FIELD_TYPE_FREETEXT %]
+ <input id="[% field.name FILTER html %]" class="text_input"
+ name="[% field.name FILTER html %]"
+ value="[% value FILTER html %]" size="40"
+ maxlength="[% constants.MAX_FREETEXT_LENGTH FILTER none %]"
+ [% ' aria-required="true"' IF field.is_mandatory %]>
+ [% CASE constants.FIELD_TYPE_DATETIME %]
+ <input name="[% field.name FILTER html %]" size="20"
+ id="[% field.name FILTER html %]"
+ value="[% value FILTER html %]"
+ [% ' aria-required="true"' IF field.is_mandatory %]
+ onchange="updateCalendarFromField(this)">
+ <button type="button" class="calendar_button"
+ id="button_calendar_[% field.name FILTER html %]"
+ onclick="showCalendar('[% field.name FILTER js %]')">
+ <span>Calendar</span>
+ </button>
+
+ <div id="con_calendar_[% field.name FILTER html %]"></div>
+
+ <script type="text/javascript">
+ createCalendar('[% field.name FILTER js %]')
+ </script>
+ [% CASE constants.FIELD_TYPE_BUG_ID %]
+ <span id="[% field.name FILTER html %]_input_area">
+ <input name="[% field.name FILTER html %]" id="[% field.name FILTER html %]"
+ value="[% value FILTER html %]" size="7"
+ [% ' aria-required="true"' IF field.is_mandatory %]>
+
+ </span>
+
+ [% IF value %]
+ [% value FILTER bug_link(value, use_alias => 1) FILTER none %]
+ [% END %]
+ <span id="[% field.name FILTER html %]_edit_container" class="edit_me bz_default_hidden">
+ (<a href="#" id="[% field.name FILTER html %]_edit_action">edit</a>)
+ </span>
+ <script type="text/javascript">
+ hideEditableField('[% field.name FILTER js %]_edit_container',
+ '[% field.name FILTER js %]_input_area',
+ '[% field.name FILTER js %]_edit_action',
+ '[% field.name FILTER js %]',
+ "[% value FILTER js %]");
+ </script>
+ [% CASE [ constants.FIELD_TYPE_SINGLE_SELECT
+ constants.FIELD_TYPE_MULTI_SELECT ] %]
+ <select id="[% field.name FILTER html %]"
+ name="[% field.name FILTER html %]"
+ [% IF field.type == constants.FIELD_TYPE_MULTI_SELECT %]
+ [% SET field_size = 5 %]
+ [% IF field.legal_values.size < 5 %]
+ [% SET field_size = field.legal_values.size %]
+ [% END %]
+ size="[% field_size FILTER html %]" multiple="multiple"
+ [% ' aria-required="true"' IF field.is_mandatory %]
+ [% END %]
+ >
+ [% IF allow_dont_change %]
+ <option value="[% dontchange FILTER html %]"
+ [% ' selected="selected"' IF value == dontchange %]>
+ [% dontchange FILTER html %]
+ </option>
+ [% END %]
+ [% IF override_legal_values %]
+ [% legal_values = override_legal_values %]
+ [% ELSE %]
+ [% legal_values = field.legal_values %]
+ [% END %]
+ [% FOREACH legal_value = legal_values %]
+ <option value="[% legal_value.name FILTER html %]"
+ id="v[% legal_value.id FILTER html %]_
+ [%- field.name FILTER html %]"
+ [%# We always show selected values, even if they should be
+ # hidden %]
+ [% IF value.contains(legal_value.name).size %]
+ selected="selected"
+ [% ELSIF bug AND !legal_value.is_visible_on_bug(bug) %]
+ class="bz_hidden_option" disabled="disabled"
+ [% END %]>
+ [%- display_value(field.name, legal_value.name) FILTER html ~%]
+ </option>
+ [% END %]
+ </select>
+ [%# When you pass an empty multi-select in the web interface,
+ # it doesn't appear at all in the CGI object. Instead of
+ # forcing all users of process_bug to always specify every
+ # multi-select, we have this field defined if the multi-select
+ # field is defined, and then if this is passed but the multi-select
+ # isn't, we know that the multi-select was emptied.
+ %]
+ [% IF field.type == constants.FIELD_TYPE_MULTI_SELECT %]
+ <input type="hidden" name="defined_[% field.name FILTER html %]">
+ [% END %]
+
+ <script type="text/javascript">
+ <!--
+ initHidingOptionsForIE('[% field.name FILTER js %]');
+ [%+ INCLUDE "bug/field-events.js.tmpl" field = field %]
+ //-->
+ </script>
+
+ [% CASE constants.FIELD_TYPE_TEXTAREA %]
+ [% INCLUDE global/textarea.html.tmpl
+ id = field.name name = field.name minrows = 4 maxrows = 8
+ cols = 60 defaultcontent = value mandatory = field.is_mandatory %]
+ [% CASE constants.FIELD_TYPE_BUG_URLS %]
+ [% '<ul class="bug_urls">' IF value.size %]
+ [% FOREACH url = value %]
+ <li>
+ <a href="[% url FILTER html %]">[% url FILTER html %]</a>
+ <label><input type="checkbox" value="[% url FILTER html %]"
+ name="remove_[% field.name FILTER html %]">
+ Remove</label>
+ </li>
+ [% END %]
+ [% '</ul>' IF value.size %]
+
+ [% IF Param('use_see_also') %]
+ <label for="[% field.name FILTER html %]">
+ <strong>Add [% terms.Bug %] URLs:</strong>
+ </label><br>
+ <input type="text" id="[% field.name FILTER html %]" size="40"
+ class="text_input" name="[% field.name FILTER html %]">
+ [% END %]
+ [% CASE constants.FIELD_TYPE_KEYWORDS %]
+ <div id="keyword_container">
+ <input type="text" id="[% field.name FILTER html %]" size="40"
+ class="text_input" name="[% field.name FILTER html %]"
+ value="[% value FILTER html %]">
+ <div id="keyword_autocomplete"></div>
+ </div>
+ <script type="text/javascript" defer="defer">
+ YAHOO.bugzilla.keyword_array = [
+ [%- FOREACH keyword = all_keywords %]
+ [%-# %]"[% keyword.name FILTER js %]"
+ [%- "," IF NOT loop.last %][% END %]];
+ YAHOO.bugzilla.keywordAutocomplete.init('[% field.name FILTER js %]',
+ 'keyword_autocomplete');
+ </script>
+ [% END %]
+[% ELSIF field.type == constants.FIELD_TYPE_TEXTAREA %]
+ <div class="uneditable_textarea">[% value FILTER wrap_comment(60)
+ FILTER html %]</div>
+[% ELSIF field.type == constants.FIELD_TYPE_BUG_ID %]
+ [% IF value %]
+ [% value FILTER bug_link(value, use_alias => 1) FILTER none %]
+ [% END %]
+[% ELSIF field.type == constants.FIELD_TYPE_BUG_URLS %]
+ [% '<ul class="bug_urls">' IF value.size %]
+ [% FOREACH url = value %]
+ <li><a href="[% url FILTER html %]">[% url FILTER html %]</a></li>
+ [% END %]
+ [% '</ul>' IF value.size %]
+[% ELSE %]
+ [% value.join(', ') FILTER html %]
+[% END %]
+[% Hook.process('end_field_column') %]
+[% '</td>' IF NOT no_tds %]
+
+[%# for reverse relationships, we show this pseudo-field after the main field %]
+[% IF bug.id && field.is_relationship %]
+ [% extra_field_item = {} %]
+ [% extra_field_item.header = field.reverse_desc _ ":" FILTER html %]
+ [% extra_field_item.data = BLOCK %]
+ [% FOREACH depbug = bug.related_bugs(field) %]
+ [% depbug.id FILTER bug_link(depbug, use_alias => 1) FILTER none %][% " " %]
+ [% END %]
+ [% END %]
+[% ELSE %]
+ [% extra_field_item = '' %]
+[% END %]
diff --git a/template/en/default/bug/format_comment.txt.tmpl b/template/en/default/bug/format_comment.txt.tmpl
new file mode 100644
index 000000000..ed89188a6
--- /dev/null
+++ b/template/en/default/bug/format_comment.txt.tmpl
@@ -0,0 +1,60 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Marc Schumann.
+ # Portions created by Marc Schumann are Copyright (c) 2008 Marc Schumann.
+ # All rights reserved.
+ #
+ # Contributor(s): Marc Schumann <wurblzap@gmail.com>
+ #%]
+
+[%# NOTE: Everywhere you use this template, you must call
+ # "FILTER remove('^X')" on the result. This is unfortunately the only way
+ # to preserve leading whitespace in comments.
+ #%]
+
+[%# INTERFACE:
+ # comment: A Bugzilla::Comment object.
+ # is_bugmail: boolean; True if this comment is going into a plain-text
+ # bugmail.
+ #%]
+
+[%# Please don't use field-descs here. It can slow down Bugzilla. %]
+[% PROCESS 'global/variables.none.tmpl' %]
+
+[% SET comment_body = comment.body %]
+
+[% IF comment.type == constants.CMT_DUPE_OF %]
+X[% comment_body %]
+
+*** This [% terms.bug %] has been marked as a duplicate of [% terms.bug %] [%+ comment.extra_data %] ***
+[% ELSIF comment.type == constants.CMT_HAS_DUPE %]
+*** [% terms.Bug %] [%+ comment.extra_data %] has been marked as a duplicate of this [% terms.bug %]. ***
+[% ELSIF comment.type == constants.CMT_ATTACHMENT_CREATED %]
+Created attachment [% comment.extra_data %]
+[% IF is_bugmail %]
+ --> [% urlbase _ "attachment.cgi?id=" _ comment.extra_data %]
+[% END %]
+[%+ comment.attachment.description %]
+
+[%+ comment.body %]
+[% ELSIF comment.type == constants.CMT_ATTACHMENT_UPDATED %]
+Comment on attachment [% comment.extra_data %]
+[% IF is_bugmail %]
+ --> [% urlbase _ "attachment.cgi?id=" _ comment.extra_data %]
+[% END %]
+[%+ comment.attachment.description %]
+
+[%+ comment.body %]
+[% ELSE %]
+X[% Hook.process('type') %]
+[% END %]
diff --git a/template/en/default/bug/knob.html.tmpl b/template/en/default/bug/knob.html.tmpl
new file mode 100644
index 000000000..ac14e6dc0
--- /dev/null
+++ b/template/en/default/bug/knob.html.tmpl
@@ -0,0 +1,100 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ # Vaskin Kissoyan <vkissoyan@yahoo.com>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ # Guy Pyrzak <guy.pyrzak@gmail.com>
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+<div id="status">
+ [% PROCESS bug/field.html.tmpl
+ no_tds = 1
+ field = bug_fields.bug_status
+ value = bug.bug_status
+ override_legal_values = bug.choices.bug_status
+ editable = bug.choices.bug_status.size > 1
+ %]
+
+ [% IF bug.resolution
+ OR bug.check_can_change_field('resolution', bug.resolution, 1)
+ %]
+ <noscript><br>resolved&nbsp;as&nbsp;</noscript>
+ [% END %]
+
+ <span id="resolution_settings">
+ [% PROCESS bug/field.html.tmpl
+ no_tds = 1
+ field = bug_fields.resolution
+ value = bug.resolution
+ override_legal_values = bug.choices.resolution
+ editable = bug.check_can_change_field('resolution', bug.resolution, 1)
+ %]
+ </span>
+
+ [% IF bug.check_can_change_field('dup_id', 0, 1) %]
+ <noscript><br> duplicate</noscript>
+ <span id="duplicate_settings">of
+ <span id="dup_id_container" class="bz_default_hidden">
+ [% "${terms.bug} ${bug.dup_id}" FILTER bug_link(bug.dup_id) FILTER none %]
+ (<a href="#" id="dup_id_edit_action">edit</a>)
+ </span
+ ><input id="dup_id" name="dup_id" size="6"
+ value="[% bug.dup_id FILTER html %]">
+ </span>
+ [% IF bug.dup_id %]
+ <noscript>[% bug.dup_id FILTER bug_link(bug.dup_id) FILTER none %]</noscript>
+ [% END %]
+ <div id="dup_id_discoverable" class="bz_default_hidden">
+ <a href="#" id="dup_id_discoverable_action">Mark as Duplicate</a>
+ </div>
+ [% ELSIF bug.dup_id %]
+ <noscript><br> duplicate</noscript>
+ <span id="duplicate_display">of
+ [% "${terms.bug} ${bug.dup_id}" FILTER bug_link(bug.dup_id) FILTER none %]</span>
+ [% END %]
+</div>
+
+<script type="text/javascript">
+ var close_status_array = [
+ [% FOREACH status = bug.choices.bug_status %]
+ [% NEXT IF status.is_open %]
+ '[% status.name FILTER js %]'[% ',' UNLESS loop.last %]
+ [% END %]
+ ];
+ YAHOO.util.Dom.removeClass('dup_id_discoverable', 'bz_default_hidden');
+ hideEditableField( "dup_id_container", "dup_id", 'dup_id_edit_action',
+ 'dup_id', '[% bug.dup_id FILTER js %]' )
+ showHideStatusItems( "", ['[% "is_duplicate" IF bug.dup_id %]',
+ '[% bug.bug_status FILTER js %]']);
+ YAHOO.util.Event.addListener( 'bug_status', "change", showHideStatusItems,
+ ['[% "is_duplicate" IF bug.dup_id %]',
+ '[% bug.bug_status FILTER js %]']);
+ YAHOO.util.Event.addListener( 'resolution', "change", showDuplicateItem);
+ YAHOO.util.Event.addListener( 'dup_id_discoverable_action',
+ 'click',
+ setResolutionToDuplicate,
+ '[% Param('duplicate_or_move_bug_status')
+ FILTER js %]');
+ YAHOO.util.Event.addListener( window, 'load', showHideStatusItems,
+ ['[% "is_duplicate" IF bug.dup_id %]',
+ '[% bug.bug_status FILTER js %]'] );
+
+ [% INCLUDE "bug/field-events.js.tmpl" field = select_fields.bug_status %]
+ [% INCLUDE "bug/field-events.js.tmpl" field = select_fields.resolution %]
+</script>
diff --git a/template/en/default/bug/navigate.html.tmpl b/template/en/default/bug/navigate.html.tmpl
new file mode 100644
index 000000000..19af18ade
--- /dev/null
+++ b/template/en/default/bug/navigate.html.tmpl
@@ -0,0 +1,87 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% RETURN IF !bug %]
+
+[% PROCESS global/variables.none.tmpl %]
+[% IF bottom_navigator == 1 %]
+ <ul class="related_actions">
+ <li><a href="show_bug.cgi?format=multiple&amp;id=
+ [% bug.bug_id FILTER url_quote %]">Format For Printing</a></li>
+ <li>&nbsp;-&nbsp;<a href="show_bug.cgi?ctype=xml&amp;id=
+ [% bug.bug_id FILTER url_quote %]">XML</a></li>
+ <li>&nbsp;-&nbsp;<a href="enter_bug.cgi?cloned_bug_id=
+ [% bug.bug_id FILTER url_quote %]">Clone This
+ [% terms.Bug %]</a></li>
+ [%# Links to more things users can do with this bug. %]
+ [% Hook.process("links") %]
+ <li>&nbsp;-&nbsp;<a href="#">Top of page </a></li>
+ </ul>
+[% END %]
+
+
+<div class="navigation">
+[% SET my_search = user.recent_search_for(bug) %]
+[% IF my_search %]
+ [% SET last_bug_list = my_search.bug_list %]
+ [% SET this_bug_idx = lsearch(last_bug_list, bug.id) %]
+ <b>[% terms.Bug %] List:</b>
+
+ ([% this_bug_idx + 1 %] of [% last_bug_list.size %])
+
+ <a href="show_bug.cgi?id=
+ [%- last_bug_list.first FILTER url_quote %]&amp;list_id=
+ [%- my_search.id FILTER url_quote %]">First</a>
+ <a href="show_bug.cgi?id=
+ [%- last_bug_list.last FILTER url_quote %]&amp;list_id=
+ [%- my_search.id FILTER url_quote %]">Last</a>
+
+ [% IF this_bug_idx > 0 %]
+ [% prev_bug = this_bug_idx - 1 %]
+ <a href="show_bug.cgi?id=
+ [%- last_bug_list.$prev_bug FILTER url_quote %]&amp;list_id=
+ [%- my_search.id FILTER url_quote %]">Prev</a>
+ [% ELSE %]
+ <i><font color="#777777">Prev</font></i>
+ [% END %]
+
+ [% IF this_bug_idx + 1 < last_bug_list.size %]
+ [% next_bug = this_bug_idx + 1 %]
+ <a href="show_bug.cgi?id=
+ [%- last_bug_list.$next_bug FILTER url_quote %]&amp;list_id=
+ [%- my_search.id FILTER url_quote %]">Next</a>
+ [% ELSE %]
+ <i><font color="#777777">Next</font></i>
+ [% END %]
+
+ &nbsp;&nbsp;<a href="buglist.cgi?regetlastlist=
+ [%- my_search.id FILTER url_quote %]">Show last search results</a>
+[% ELSE %]
+ [%# With no list, don't show link to search results %]
+ <i><font color="#777777">First</font></i>
+ <i><font color="#777777">Last</font></i>
+ <i><font color="#777777">Prev</font></i>
+ <i><font color="#777777">Next</font></i>
+ &nbsp;&nbsp;
+ <i><font color="#777777">This [% terms.bug %] is not in your last
+ search results.</font></i>
+[% END %]
+</div>
diff --git a/template/en/default/bug/process/bugmail.html.tmpl b/template/en/default/bug/process/bugmail.html.tmpl
new file mode 100644
index 000000000..b0132a2fe
--- /dev/null
+++ b/template/en/default/bug/process/bugmail.html.tmpl
@@ -0,0 +1,60 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Bradley Baetz <bbaetz@student.usyd.edu.au>
+ # J. Paul Reed <preed@sigkill.com>
+ #%]
+
+[%# INTERFACE:
+ # mailing_bugid: The bug ID that email is being sent for.
+ # sent_bugmail: The results of Bugzilla::BugMail::Send().
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+<dl>
+[% PROCESS emails
+ description = "Email sent to"
+ names = sent_bugmail.sent
+%]
+
+[% PROCESS emails
+ description = "Excluding"
+ names = sent_bugmail.excluded
+%]
+</dl>
+
+[%############################################################################%]
+[%# Block for a set of email addresses #%]
+[%############################################################################%]
+
+[% BLOCK emails %]
+ <dt>[% description FILTER html %]:</dt>
+ <dd>
+ [% IF user.can_see_bug(mailing_bugid) %]
+ [% IF names.size > 0 %]
+ [%+ FOREACH name = names %]
+ <code>[% name FILTER html %]</code>[% ", " UNLESS loop.last() %]
+ [% END %]
+ [% ELSE %]
+ no one
+ [% END %]
+ [% ELSE %]
+ (list of e-mails not available)
+ [% END %]
+ </dd>
+[% END %]
diff --git a/template/en/default/bug/process/confirm-duplicate.html.tmpl b/template/en/default/bug/process/confirm-duplicate.html.tmpl
new file mode 100644
index 000000000..d89c8da23
--- /dev/null
+++ b/template/en/default/bug/process/confirm-duplicate.html.tmpl
@@ -0,0 +1,75 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Myk Melez <myk@mozilla.org>
+ #%]
+
+[%# INTERFACE:
+ # original_bug_id: number; the bug number for the bug
+ # against which a bug is being duped
+ # duplicate_bug_id: number; the bug number for the bug
+ # being duped
+ # cclist_accessible: boolean; whether or not users on the cc: list
+ # of the original bug can access that bug.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+[% PROCESS global/header.html.tmpl title="Duplicate Warning" %]
+[% orig_bug = "$terms.bug $original_bug_id"
+ FILTER bug_link(original_bug_id) %]
+
+<p>
+ When marking [% terms.abug %] as a duplicate, the reporter of the duplicate
+ is normally added to the CC list of the original. The permissions
+ on [% orig_bug FILTER none %] (the original) are currently set
+ such that the reporter would not normally be able to see it.
+</p>
+
+<p>
+ <b>Adding the reporter to the CC list of [% orig_bug FILTER none %]
+ [% IF cclist_accessible %]
+ will immediately
+ [% ELSE %]
+ might, in the future,
+ [% END %]
+ allow him/her access to view this [% terms.bug %].</b>
+ Do you wish to do this?
+</p>
+
+<form method="post" action="process_bug.cgi">
+
+[% PROCESS "global/hidden-fields.html.tmpl" exclude="^Bugzilla_(login|password)$" %]
+
+<p>
+ <input type="radio" name="confirm_add_duplicate" value="1">
+ Yes, add the reporter to CC list on [% orig_bug FILTER none %]
+</p>
+<p>
+ <input type="radio" name="confirm_add_duplicate" value="0" checked="checked">
+ No, do not add the reporter to CC list on [% orig_bug FILTER none %]
+</p>
+<p>
+ [% "Throw away my changes, and revisit $terms.bug $duplicate_bug_id"
+ FILTER bug_link(duplicate_bug_id) FILTER none %]
+</p>
+<p>
+ <input type="submit" id="process" value="Submit">
+</p>
+
+</form>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/bug/process/header.html.tmpl b/template/en/default/bug/process/header.html.tmpl
new file mode 100644
index 000000000..6b608b9ed
--- /dev/null
+++ b/template/en/default/bug/process/header.html.tmpl
@@ -0,0 +1,46 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ #%]
+
+[%# INTERFACE:
+ # As global/header.html.tmpl.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% USE Bugzilla %]
+
+[% PROCESS "bug/show-header.html.tmpl" %]
+
+[% IF title_tag == "bug_processed" %]
+ [% title = BLOCK %]
+ [% IF Bugzilla.cgi.param('id') %]
+ [% terms.Bug %] [%+ id FILTER html %]
+ [% ELSE %]
+ [% terms.Bugs %]
+ [% END %]
+ processed
+ [% END %]
+[% ELSIF title_tag == "mid_air" %]
+ [% title = "Mid-air collision!" %]
+[% END %]
+
+[% Hook.process('title') %]
+
+[% PROCESS global/header.html.tmpl %]
diff --git a/template/en/default/bug/process/midair.html.tmpl b/template/en/default/bug/process/midair.html.tmpl
new file mode 100644
index 000000000..8a49f7cdc
--- /dev/null
+++ b/template/en/default/bug/process/midair.html.tmpl
@@ -0,0 +1,111 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Myk Melez <myk@mozilla.org>
+ #%]
+
+[%# INTERFACE:
+ # operations: array; bug activity since the user last displayed the bug form,
+ # used by bug/activity/table.html.tmpl to display recent changes that will
+ # be overwritten if the user submits these changes. See that template
+ # for further documentation.
+ # start_at: number; the comment at which show/comments.tmpl should begin
+ # displaying comments, either the index of the last comment (if the user
+ # entered a comment along with their change) or a number less than that
+ # (if they didn't), in which case no comments are displayed.
+ # comments: array; all the comments on the bug.
+ # bug: Bugzilla::Bug; the bug being changed.
+ #%]
+
+[%# The global Bugzilla->cgi object is used to obtain form variable values. %]
+[% USE Bugzilla %]
+[% cgi = Bugzilla.cgi %]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% UNLESS header_done %]
+ [% PROCESS bug/process/header.html.tmpl %]
+[% END %]
+
+<h1>Mid-air collision detected!</h1>
+
+<p>
+ Someone else has made changes to
+ [%+ "$terms.bug $bug.id" FILTER bug_link(bug) FILTER none %]
+ at the same time you were trying to.
+ The changes made were:
+</p>
+
+<p>
+ [% PROCESS "bug/activity/table.html.tmpl" incomplete_data=0 %]
+</p>
+
+[% IF comments.size > start_at %]
+<p>
+ Added the comment(s):
+ <blockquote>
+ [% PROCESS "bug/comments.html.tmpl" %]
+ </blockquote>
+</p>
+[% END %]
+
+[% IF cgi.param("comment") %]
+<p>
+ Your comment was:<br>
+ <blockquote><pre class="bz_comment_text">
+ [% cgi.param("comment") FILTER wrap_comment FILTER html %]
+ </pre></blockquote>
+</p>
+[% END %]
+
+<p>
+You have the following choices:
+</p>
+
+<ul>
+ <li>
+ <form method="post" action="process_bug.cgi">
+ <input type="hidden" name="delta_ts"
+ value="[% bug.delta_ts FILTER html %]">
+ [% PROCESS "global/hidden-fields.html.tmpl"
+ exclude="^Bugzilla_login|Bugzilla_password|delta_ts$" %]
+ <input type="submit" id="process" value="Submit my changes anyway">
+ This will cause all of the above changes to be overwritten
+ [% ", except for the added comment(s)" IF comments.size > start_at %].
+ </form>
+ </li>
+ [% IF cgi.param("comment") %]
+ <li>
+ <form method="post" action="process_bug.cgi">
+ <input type="hidden" name="id" value="[% cgi.param("id") FILTER html %]">
+ <input type="hidden" name="delta_ts" value="[% bug.delta_ts FILTER html %]">
+ <input type="hidden" name="comment" value="[% cgi.param("comment") FILTER html %]">
+ <input type="hidden" name="comment_is_private"
+ value="[% cgi.param("comment_is_private") FILTER html %]">
+ <input type="hidden" name="longdesclength" value="[% bug.comments.size %]">
+ <input type="hidden" name="token" value="[% cgi.param("token") FILTER html %]">
+ <input type="submit" id="process_comment" value="Submit only my new comment">
+ </form>
+ </li>
+ [% END %]
+ <li>
+ Throw away my changes, and
+ [%+ "revisit $terms.bug $bug.id" FILTER bug_link(bug) FILTER none %]
+ </li>
+</ul>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/bug/process/results.html.tmpl b/template/en/default/bug/process/results.html.tmpl
new file mode 100644
index 000000000..c62a7a597
--- /dev/null
+++ b/template/en/default/bug/process/results.html.tmpl
@@ -0,0 +1,59 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Myk Melez <myk@mozilla.org>
+ #%]
+
+[%# INTERFACE:
+ # id: number; the ID of the bug that was changed/checked.
+ #
+ # type: string; the type of change/check that was made: "bug" when a bug
+ # is changed, "dupe" when a duplication notation is added to a bug,
+ # and "dep" when a bug is checked for changes to its dependencies.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% UNLESS header_done %]
+ [% PROCESS bug/process/header.html.tmpl %]
+[% END %]
+
+[% DEFAULT type="bug" %]
+
+[% Link = BLOCK %][% "$terms.Bug $id" FILTER bug_link(id) %][% END %]
+[% link = BLOCK %][% "$terms.bug $id" FILTER bug_link(id) %][% END %]
+
+[%
+ title = {
+ 'bug' => "Changes submitted for $link" ,
+ 'dupe' => "Duplicate notation added to $link" ,
+ 'dep' => "Checking for dependency changes on $link" ,
+ 'created' => "$Link has been added to the database" ,
+ 'move' => "$Link has been moved to another database" ,
+ }
+%]
+
+[% Hook.process('title') %]
+
+<dl>
+ <dt>[% title.$type %]</dt>
+ <dd>
+ [% PROCESS "bug/process/bugmail.html.tmpl" mailing_bugid = id %]
+ [%# Links to more information about the changed bug. %]
+ [% Hook.process("links") %]
+ </dd>
+</dl>
diff --git a/template/en/default/bug/process/verify-new-product.html.tmpl b/template/en/default/bug/process/verify-new-product.html.tmpl
new file mode 100644
index 000000000..c02c26470
--- /dev/null
+++ b/template/en/default/bug/process/verify-new-product.html.tmpl
@@ -0,0 +1,205 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Myk Melez <myk@mozilla.org>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[%# INTERFACE:
+ # product: object; the new product.
+ # versions: array; versions for the new product.
+ # components: array; components for the new product.
+ # milestones: array; milestones for the new product.
+ # defaults: hash; keys are names of fields, values are defaults for
+ # those fields
+ #
+ # verify_bug_groups: If groups need to be confirmed in addition to fields.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% PROCESS global/header.html.tmpl
+ title = 'Verify New Product Details...' %]
+
+<form action="process_bug.cgi" method="post">
+
+[% SET exclude_items = ['version', 'component', 'target_milestone'] %]
+[% IF verify_bug_groups %]
+ [% exclude_items.push('groups', 'defined_groups') %]
+[% END %]
+[% Hook.process('exclude') %]
+
+[% PROCESS "global/hidden-fields.html.tmpl"
+ exclude = '^' _ exclude_items.join('|') _ '$' %]
+
+<input type="hidden" name="confirm_product_change" value="1">
+
+[%# Verify the version, component, and target milestone fields. %]
+<h3>Verify Version, Component
+ [%- ", Target Milestone"
+ IF Param("usetargetmilestone")
+ && bug.check_can_change_field('target_milestone', 0, 1) %]</h3>
+
+<p>
+[% IF Param("usetargetmilestone")
+ && bug.check_can_change_field('target_milestone', 0, 1)
+%]
+ You are moving the [% terms.bug %](s) to the product
+ <b>[% product.name FILTER html %]</b>,
+ and the version, component, and/or target milestone fields are no longer
+ correct. Please set the correct version, component, and target milestone now:
+[% ELSE %]
+ You are moving the [% terms.bug %](s) to the product
+ <b>[% product.name FILTER html %]</b>,
+ and the version and component fields are no longer correct.
+ Please set the correct version and component now:
+[% END %]
+</p>
+
+<table>
+ <tr>
+ <td>
+ <b>Version:</b><br>
+ [% IF versions.size == 1 %]
+ [% SET default_version = versions.0 %]
+ [% ELSE %]
+ [% SET default_version = defaults.version %]
+ [% END %]
+ [% PROCESS "global/select-menu.html.tmpl"
+ name="version"
+ options=versions
+ default=default_version
+ size=10 %]
+ </td>
+ <td>
+ <b>Component:</b><br>
+ [% IF components.size == 1 %]
+ [% SET default_component = components.0 %]
+ [% ELSE %]
+ [% SET default_component = defaults.component %]
+ [% END %]
+ [% PROCESS "global/select-menu.html.tmpl"
+ name="component"
+ options=components
+ default=default_component
+ size=10 %]
+ </td>
+ [% IF Param("usetargetmilestone")
+ && bug.check_can_change_field('target_milestone', 0, 1)
+ %]
+ <td>
+ <b>Target Milestone:</b><br>
+ [% PROCESS "global/select-menu.html.tmpl"
+ name="target_milestone"
+ options=milestones
+ default=defaults.milestone
+ size=10 %]
+ </td>
+ [% END %]
+ [% Hook.process('field') %]
+ </tr>
+</table>
+
+[% IF verify_bug_groups %]
+ <h3>Verify [% terms.Bug %] Group</h3>
+
+ [% IF old_groups.size %]
+ <p>These groups are not legal for the '[% product.name FILTER html %]'
+ product or you are not allowed to restrict [% terms.bugs %] to these groups.
+ [%+ terms.Bugs %] will no longer be restricted to these groups and may become
+ public if no other group applies:<br>
+ [% FOREACH group = old_groups %]
+ <input type="checkbox" id="group_[% group.id FILTER html %]"
+ name="groups" disabled="disabled" value="[% group.name FILTER html %]">
+ <label for="group_[% group.id FILTER html %]">
+ [% group.name FILTER html %]: [% group.description FILTER html %]
+ </label>
+ <br>
+ [% END %]
+ </p>
+ [% END %]
+
+ [% mandatory_groups = [] %]
+ [% optional_groups = [] %]
+
+ [% FOREACH gid = product.group_controls.keys %]
+ [% group = product.group_controls.$gid %]
+ [% NEXT UNLESS group.group.is_active %]
+
+ [% IF group.membercontrol == constants.CONTROLMAPMANDATORY
+ || (group.othercontrol == constants.CONTROLMAPMANDATORY && !user.in_group(group.group.name)) %]
+ [% mandatory_groups.push(group) %]
+ [% ELSIF (group.membercontrol != constants.CONTROLMAPNA && user.in_group(group.group.name))
+ || group.othercontrol != constants.CONTROLMAPNA %]
+ [% optional_groups.push(group) %]
+ [% END %]
+ [% END %]
+
+ [% IF optional_groups.size %]
+ <p>These groups are optional. You can decide to restrict [% terms.bugs %] to
+ one or more of the following groups:<br>
+ [% FOREACH group = optional_groups %]
+ <input type="hidden" name="defined_groups"
+ value="[% group.group.name FILTER html %]">
+ <input type="checkbox" id="group_[% group.group.id FILTER html %]"
+ name="groups"
+ [% ' checked="checked"' IF ((group.membercontrol == constants.CONTROLMAPDEFAULT && user.in_group(group.group.name))
+ || (group.othercontrol == constants.CONTROLMAPDEFAULT && !user.in_group(group.group.name))
+ || cgi.param("groups").contains(group.group.name)) %]
+ value="[% group.group.name FILTER html %]">
+ <label for="group_[% group.group.id FILTER html %]">
+ [% group.group.name FILTER html %]: [% group.group.description FILTER html %]
+ </label>
+ <br>
+ [% END %]
+ </p>
+ [% END %]
+
+ [% IF mandatory_groups.size %]
+ <p>These groups are mandatory and [% terms.bugs %] will be automatically
+ restricted to these groups:<br>
+ [% FOREACH group = mandatory_groups %]
+ <input type="checkbox" id="group_[% group.group.id FILTER html %]"
+ checked="checked" disabled="disabled"
+ name="groups" value="[% group.group.name FILTER html %]">
+ <label for="group_[% group.group.id FILTER html %]">
+ [% group.group.name FILTER html %]: [% group.group.description FILTER html %]
+ </label>
+ <br>
+ [% END %]
+ </p>
+ [% END %]
+[% END %]
+
+<input type="submit" id="change_product" value="Commit">
+
+</form>
+<hr>
+
+[%# If 'id' is defined, then we are editing a single bug.
+ # Else we are editing several bugs at once. %]
+
+[% IF cgi.param('id') AND cgi.param('id').match('^\d+$') %]
+ [% id = cgi.param('id') %]
+ Cancel and Return to [% "$terms.bug $id" FILTER bug_link(id) FILTER none %]
+[% ELSE %]
+ Cancel and Return to <a href="buglist.cgi?regetlastlist=1">the last search results</a>
+[% END %]
+
+[% PROCESS global/footer.html.tmpl %]
+
diff --git a/template/en/default/bug/show-header.html.tmpl b/template/en/default/bug/show-header.html.tmpl
new file mode 100644
index 000000000..a17c3201a
--- /dev/null
+++ b/template/en/default/bug/show-header.html.tmpl
@@ -0,0 +1,49 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ # Vaskin Kissoyan <vkissoyan@yahoo.com>
+ # Bradley Baetz <bbaetz@student.usyd.edu.au>
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[%# This template should be called with PROCESS before processing
+ # "global/header.html.tmpl" in any template that is going to load the
+ # bug form. It expects only a "bug" object, and can even manage to get
+ # along without that. Some of these variables are just defaults that will
+ # be overridden by the calling templates.
+ #%]
+
+[% filtered_desc = bug.short_desc FILTER html %]
+[% subheader = filtered_desc %]
+[% filtered_timestamp = bug.delta_ts FILTER time %]
+[% title = "$terms.Bug $bug.bug_id &ndash; $filtered_desc" %]
+[% header = "$terms.Bug&nbsp;$bug.bug_id" %]
+[% header_addl_info = "Last modified: $filtered_timestamp" %]
+[% yui = ['autocomplete', 'calendar'] %]
+[% javascript_urls = [ "js/util.js", "js/field.js" ] %]
+[% style_urls = [ "skins/standard/show_bug.css" ] %]
+[% doc_section = "bug_page.html" %]
+[% bodyclasses = ['bz_bug',
+ "bz_status_$bug.bug_status",
+ "bz_product_$bug.product",
+ "bz_component_$bug.component",
+ "bz_bug_$bug.bug_id",
+ ] %]
+[% FOREACH group = bug.groups_in %]
+ [% bodyclasses.push("bz_group_$group.name") %]
+[% END %]
diff --git a/template/en/default/bug/show-multiple.html.tmpl b/template/en/default/bug/show-multiple.html.tmpl
new file mode 100644
index 000000000..406de16fb
--- /dev/null
+++ b/template/en/default/bug/show-multiple.html.tmpl
@@ -0,0 +1,373 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Terry Weissman <terry@mozilla.org>
+ # Gervase Markham <gerv@gerv.net>
+ # Toms Baugis <toms@myrealbox.com>
+ # Olav Vitters <olav@bkor.dhs.org>
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ # Elliotte Martin <emartin@everythingsolved.com>
+ #%]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+[% PROCESS global/header.html.tmpl
+ title = "Full Text $terms.Bug Listing"
+ h1 = ""
+ style_urls = ["skins/standard/show_multiple.css",
+ "skins/standard/buglist.css"]
+ doc_section = "bug_page.html"
+%]
+[% PROCESS bug/time.html.tmpl %]
+[% IF bugs.first %]
+ [% ids = [] %]
+ [% FOREACH bug = bugs %]
+ [% PROCESS bug_display %]
+ [% ids.push(bug.bug_id) UNLESS bug.error %]
+ [% END %]
+ [% IF ids.size > 1 %]
+ <div class="bz_query_buttons">
+ <form method="post" action="buglist.cgi">
+ <input type="hidden" name="bug_id" value="[% ids.join(",") FILTER html %]">
+ <input type="submit" id="short_format" value="Short Format">
+ </form>
+ </div>
+ [% END %]
+[% ELSE %]
+ <p>
+ You'd have more luck if you gave me some [% terms.bug %] numbers.
+ </p>
+[% END %]
+
+
+[% PROCESS global/footer.html.tmpl %]
+
+
+[%###########################################################################%]
+[%# Block for an individual bug #%]
+[%###########################################################################%]
+
+[% BLOCK bug_display %]
+ <h1>
+ [% terms.Bug %]
+ <a href="show_bug.cgi?id=[% bug.bug_id FILTER html %]">[% bug.bug_id FILTER html %]</a>
+ [% IF Param("usebugaliases") AND bug.alias AND NOT bug.error %]
+ (<a href="show_bug.cgi?id=[% bug.alias FILTER url_quote %]">
+ [% bug.alias FILTER html %]</a>)
+ [% END %]
+ </h1>
+
+ <table class="bugfields">
+ [% IF bug.error %]
+ <tr>
+ <td class="error">
+ [% IF bug.error == "InvalidBugId" %]
+ '[%+ bug.bug_id FILTER html %]' is not a valid [% terms.bug %] number
+ [%- IF Param("usebugaliases") %] nor a known [% terms.bug %] alias[% END %].
+ [% ELSIF bug.error == "NotPermitted" %]
+ You are not allowed to view this [% terms.bug %].
+ [% ELSIF bug.error == "NotFound" %]
+ This [% terms.bug %] cannot be found.
+ [% ELSE %]
+ [%+ bug.error FILTER html %]
+ [% END %]
+ </td>
+ </tr>
+ [% ELSE %]
+ [%# The rightcell block (also called by the row block) automatically shows
+ # the fields from rightcells %]
+ [% rightcells = ['reporter', 'assigned_to'] %]
+ [% IF Param('useqacontact') %]
+ [% rightcells.push('qa_contact') %]
+ [% END %]
+ [% rightcells.push('') %]
+ [% IF bug.cc %]
+ [% rightcells.push('cc') %]
+ [% END %]
+ [% IF bug.keywords %]
+ [% rightcells.push('keywords') %]
+ [% END %]
+
+ [%# Determine if the bug has a flag %]
+ [% FOREACH type = bug.flag_types %]
+ [% IF type.flags.size %]
+ [% rightcells.push('flags') %]
+ [% LAST %]
+ [% END %]
+ [% END %]
+
+ [% PROCESS row cell = "short_desc" fullrow = 1 %]
+
+ <tr>
+ <th>[% field_descs.product FILTER html %]:</th>
+ <td>
+ [% IF Param("useclassification") && bug.classification_id != 1 %]
+ [[% bug.classification FILTER html %]]&nbsp;
+ [% END %]
+ [% bug.product FILTER html %]
+ </td>
+
+ [% PROCESS rightcell %]
+ </tr>
+
+ [% PROCESS row cell = "component" %]
+
+ <tr>
+ <th>[% field_descs.bug_status FILTER html %]:</th>
+ <td>
+ [% display_value("bug_status", bug.bug_status) FILTER html %]
+ [%+ display_value("resolution", bug.resolution) FILTER html %]
+ </td>
+
+ [% PROCESS rightcell %]
+ </tr>
+
+ <tr>
+ <th>[% field_descs.bug_severity FILTER html %]:</th>
+ <td class="bz_[% bug.bug_severity FILTER css_class_quote -%]">
+ [% display_value("bug_severity", bug.bug_severity) FILTER html %]
+ </td>
+
+ [% PROCESS rightcell %]
+ </tr>
+
+ <tr>
+ <th>[% field_descs.priority FILTER html %]:</th>
+ <td class="bz_[% bug.priority FILTER css_class_quote -%]">
+ [% bug.priority FILTER html %]
+ </td>
+
+ [% PROCESS rightcell %]
+ </tr>
+
+ [% PROCESS row cell = "version" %]
+ [% PROCESS row cell = "target_milestone" IF Param('usetargetmilestone') %]
+ [% PROCESS row cell = "rep_platform" %]
+ [% PROCESS row cell = "op_sys" %]
+
+ [% IF bug.bug_file_loc %]
+ <tr>
+ <th>[% field_descs.bug_file_loc FILTER html %]:</th>
+ <td colspan="3">
+ [% IF is_safe_url(bug.bug_file_loc) %]
+ <a href="[% bug.bug_file_loc FILTER html %]">
+ [% bug.bug_file_loc FILTER html %]</a>
+ [% ELSE %]
+ [% bug.bug_file_loc FILTER html %]
+ [% END %]
+ </td>
+ </tr>
+ [% END %]
+
+ [% IF Param("usestatuswhiteboard") %]
+ [% PROCESS row cell = "status_whiteboard" fullrow = 1 %]
+ [% END %]
+
+ [% USE Bugzilla %]
+ [% field_counter = 0 %]
+ [% FOREACH field = Bugzilla.active_custom_fields %]
+ [% field_counter = field_counter + 1 %]
+ [%# Odd-numbered fields get an opening <tr> %]
+ [% '<tr>' IF field_counter % 2 %]
+ [% PROCESS bug/field.html.tmpl value=bug.${field.name} editable=0 %]
+ [%# Even-numbered fields get a closing <tr> %]
+ [% '</tr>' IF !(field_counter % 2) %]
+ [% IF extra_field_item %]
+ [% field_counter = field_counter + 1 %]
+ [% '<tr>' IF field_counter % 2 %]
+ <th>[% extra_field_item.header FILTER none %]</th>
+ <td>[% extra_field_item.data FILTER none %]</td>
+ [% '</tr>' IF !(field_counter % 2) %]
+ [% END %]
+ [% END %]
+ [%# And we have to finish the row if we ended on an odd number. %]
+ [% '<th></th><td></td></tr>' IF field_counter % 2 %]
+
+ [% IF (bug.dependson.size || bug.blocked.size) %]
+ [% PROCESS dependencies name = "dependson" %]
+ [% PROCESS dependencies name = "blocked" %]
+ [% END %]
+
+ [% IF user.is_timetracker %]
+ <tr>
+ <th>Time tracking:</th>
+ <td colspan="3">
+ <table class="timetracking">
+ <tr>
+ <th>[% field_descs.estimated_time FILTER html %]</th>
+ <th>[% field_descs.actual_time FILTER html %]</th>
+ <th>[% field_descs.work_time FILTER html %]</th>
+ <th>[% field_descs.remaining_time FILTER html %]</th>
+ <th>[% field_descs.percentage_complete FILTER html %]</th>
+ <th>Gain</th>
+ </tr>
+ <tr>
+ <td>
+ [% PROCESS formattimeunit time_unit = bug.estimated_time %]
+ </td>
+ <td>
+ [% PROCESS formattimeunit
+ time_unit=(bug.remaining_time + bug.actual_time) %]
+ </td>
+ <td>[% PROCESS formattimeunit time_unit = bug.actual_time %]</td>
+ <td>
+ [% PROCESS formattimeunit time_unit = bug.remaining_time %]
+ </td>
+ <td>
+ [% PROCESS calculatepercentage act = bug.actual_time
+ rem = bug.remaining_time %]
+ </td>
+ <td>
+ [% PROCESS formattimeunit
+ time_unit=bug.estimated_time -
+ (bug.actual_time + bug.remaining_time) %]
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ [% PROCESS row cell="deadline" %]
+ [% END %]
+
+ [% IF bug.attachments.size %]
+ <tr>
+ <th>Attachments:</th>
+ <td colspan="3">
+ [% IF bug.show_attachment_flags %]
+ <table>
+ <tr>
+ <th>Description</th>
+ <th>Flags</th>
+ </tr>
+ [% FOREACH attachment = bug.attachments %]
+ <tr>
+ <td>
+ <a href="attachment.cgi?id=[% attachment.id %]">
+ [% attachment.description FILTER html %]
+ </a>[% "<br>" IF not loop.last() %]
+ </td>
+ <td>
+ [% IF attachment.flags.size == 0 %]
+ <i>none</i>
+ [% ELSE %]
+ [% FOREACH flag = attachment.flags %]
+ [% flag.setter.nick FILTER html %]:
+ [%+ flag.type.name FILTER html FILTER no_break %][% flag.status %]
+ [% IF flag.status == "?" && flag.requestee %]
+ ([% flag.requestee.nick FILTER html %])
+ [% END %][% ", " IF not loop.last() %]
+ [% END %]
+ [% END %]
+ </td>
+ </tr>
+ [% END %]
+ </table>
+ [% ELSE %]
+ [% FOREACH attachment = bug.attachments %]
+ <a href="attachment.cgi?id=[% attachment.id %]">
+ [% attachment.description FILTER html %]
+ </a>[% "<br>" IF not loop.last() %]
+ [% END %]
+ [% END %]
+ </td>
+ </tr>
+ [% END %]
+ [% END %]
+
+ [% Hook.process("last_row", "bug/show-multiple.html.tmpl") %]
+
+ </table>
+
+
+ <br>
+
+ [% PROCESS bug/comments.html.tmpl
+ comments = bug.comments %]
+
+[% END %]
+
+
+[%###########################################################################%]
+[%# Block for standard table rows #%]
+[%###########################################################################%]
+
+[% BLOCK row %]
+ <tr>
+ <th>[% field_descs.${cell} FILTER html %]:</th>
+ <td[% " colspan=3" IF fullrow %]>[% display_value(cell, bug.${cell}) FILTER html %]</td>
+ [% PROCESS rightcell IF !fullrow %]
+ </tr>
+ [% fullrow = 0 %]
+[% END %]
+
+
+[%############################################################################%]
+[%# Block for dependencies #%]
+[%############################################################################%]
+
+[% BLOCK dependencies %]
+ <tr>
+ <th>[% terms.Bug %] [%+ field_descs.${name} FILTER html %]:</th>
+ <td>
+ [% FOREACH depbug = bug.${name} %]
+ [% depbug FILTER bug_link(depbug) FILTER none %][% ", " IF not loop.last() %]
+ [% END %]
+ </td>
+
+ [% PROCESS rightcell %]
+ </tr>
+[% END %]
+
+[%############################################################################%]
+[%# Block for cells shown right of the table #%]
+[%############################################################################%]
+
+[% BLOCK rightcell %]
+ [% IF rightcells %]
+ [% name = rightcells.shift %]
+ [% IF name == "cc" %]
+ <th class="rightcell">[% field_descs.cc FILTER html %]:</th>
+ <td>
+ [% FOREACH c = bug.cc %]
+ [% c FILTER email FILTER html %][% ", " IF not loop.last() %]
+ [% END %]
+ [% ELSIF name == "reporter" || name == "assigned_to"
+ || name == "qa_contact" %]
+ <th class="rightcell">[% field_descs.${name} FILTER html %]:</th>
+ <td>[% bug.${name}.identity FILTER email FILTER html %]</td>
+ [% ELSIF name == "flags" %]
+ <th class="rightcell">Flags:</th>
+ <td>
+ [% FOREACH type = bug.flag_types %]
+ [% FOREACH flag = type.flags %]
+ [% flag.setter.nick FILTER html %]:
+ [%+ flag.type.name FILTER html FILTER no_break %][% flag.status %]
+ [%+ IF flag.status == "?" && flag.requestee %]
+ ([% flag.requestee.nick FILTER html %])
+ [% END %]<br>
+ [% END %]
+ [% END %]
+ </td>
+ [% ELSIF name != "" %]
+ <th class="rightcell">[% field_descs.${name} FILTER html %]:</th>
+ <td>[% display_value(name, bug.${name}) FILTER html %]</td>
+ [% ELSE %]
+ <td>&nbsp;</td>
+ <td>&nbsp;</td>
+ [% END %]
+ [% END %]
+[% END %]
diff --git a/template/en/default/bug/show.html.tmpl b/template/en/default/bug/show.html.tmpl
new file mode 100644
index 000000000..8d8e63a20
--- /dev/null
+++ b/template/en/default/bug/show.html.tmpl
@@ -0,0 +1,53 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ # Vaskin Kissoyan <vkissoyan@yahoo.com>
+ # Bradley Baetz <bbaetz@student.usyd.edu.au>
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[%# This script/template only handles one bug #%]
+[% bug = bugs.0 %]
+
+[% IF !header_done %]
+ [% PROCESS "bug/show-header.html.tmpl" %]
+ [% PROCESS global/header.html.tmpl %]
+[% END %]
+
+[% IF nextbug %]
+ <hr>
+ <p>
+ The next [% terms.bug %] in your list is [% terms.bug %]
+ <a href="show_bug.cgi?id=[% bug.bug_id %]">[% bug.bug_id %]</a>:
+ </p>
+ <hr>
+[% END %]
+
+[% PROCESS bug/navigate.html.tmpl %]
+
+[% PROCESS bug/edit.html.tmpl %]
+
+<hr>
+
+[% PROCESS bug/navigate.html.tmpl bottom_navigator => 1%]
+
+<br>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/bug/show.xml.tmpl b/template/en/default/bug/show.xml.tmpl
new file mode 100644
index 000000000..2349602dc
--- /dev/null
+++ b/template/en/default/bug/show.xml.tmpl
@@ -0,0 +1,157 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Bradley Baetz <bbaetz@student.usyd.edu.au>
+ # Colin Ogilvie <mozilla@colinogilvie.co.uk>
+ #
+ #%]
+[% PROCESS bug/time.html.tmpl %]
+<?xml version="1.0" [% IF Param('utf8') %]encoding="UTF-8" [% END %]standalone="yes" ?>
+<!DOCTYPE bugzilla SYSTEM "[% urlbase FILTER html %]bugzilla.dtd">
+
+<bugzilla version="[% constants.BUGZILLA_VERSION %]"
+ urlbase="[% urlbase FILTER xml %]"
+ [%# Note that the maintainer's email is not filtered,
+ # intentionally. Even logged-out users should be able
+ # to see that, since it will be in error messages anyway.
+ %]
+ maintainer="[% Param('maintainer') FILTER xml %]"
+[% IF user.id %]
+ exporter="[% user.email FILTER email FILTER xml %]"
+[% END %]
+>
+
+[% FOREACH bug = bugs %]
+ [% IF bug.error %]
+ <bug error="[% bug.error FILTER xml %]">
+ <bug_id>[% bug.bug_id FILTER xml %]</bug_id>
+ </bug>
+ [% ELSE %]
+ <bug>
+ [% FOREACH field = bug.fields %]
+ [% IF displayfields.$field %]
+ [%+ PROCESS bug_field %]
+ [% END %]
+ [% END %]
+
+ [%# This is here so automated clients can still use process_bug.cgi %]
+ [% IF displayfields.token && user.id %]
+ <token>[% issue_hash_token([bug.id, bug.delta_ts]) FILTER xml %]</token>
+ [% END %]
+
+ [%# Now handle 'special' fields #%]
+ [% IF displayfields.group %]
+ [% FOREACH g = bug.groups %]
+ [% NEXT UNLESS g.ison %]
+ <group id="[% g.bit FILTER xml %]">[% g.name FILTER xml %]</group>
+ [% END %]
+ [% END %]
+
+ [%# Bug Flags %]
+ [% PROCESS section_flags obj => bug %]
+
+ [% IF displayfields.long_desc %]
+ [% FOREACH c = bug.comments %]
+ [% NEXT IF c.is_private && !user.is_insider %]
+ <long_desc isprivate="[% c.is_private FILTER xml %]">
+ <commentid>[% c.id FILTER xml %]</commentid>
+ [% IF c.is_about_attachment %]
+ <attachid>[% c.extra_data FILTER xml %]</attachid>
+ [% END %]
+ <who name="[% c.author.name FILTER xml %]">[% c.author.email FILTER email FILTER xml %]</who>
+ <bug_when>[% c.creation_ts FILTER time("%Y-%m-%d %T %z") FILTER xml %]</bug_when>
+ [% IF user.is_timetracker && (c.work_time - 0 != 0) %]
+ <work_time>[% PROCESS formattimeunit time_unit = c.work_time FILTER xml %]</work_time>
+ [% END %]
+ <thetext>[% c.body_full FILTER xml %]</thetext>
+ </long_desc>
+ [% END %]
+ [% END %]
+
+ [% IF displayfields.attachment %]
+ [% FOREACH a = bug.attachments %]
+ [% NEXT IF a.isprivate && !user.is_insider %]
+ <attachment
+ isobsolete="[% a.isobsolete FILTER xml %]"
+ ispatch="[% a.ispatch FILTER xml %]"
+ isprivate="[% a.isprivate FILTER xml %]"
+ isurl="[% a.isurl FILTER xml %]"
+ >
+ <attachid>[% a.id %]</attachid>
+ <date>[% a.attached FILTER time("%Y-%m-%d %T %z") FILTER xml %]</date>
+ <delta_ts>[% a.modification_time FILTER time("%Y-%m-%d %T %z") FILTER xml %]</delta_ts>
+ <desc>[% a.description FILTER xml %]</desc>
+ <filename>[% a.filename FILTER xml %]</filename>
+ <type>[% a.contenttype FILTER xml %]</type>
+ <size>[% a.datasize FILTER xml %]</size>
+ <attacher>[% a.attacher.email FILTER email FILTER xml %]</attacher>
+ [%# This is here so automated clients can still use attachment.cgi %]
+ [% IF displayfields.token && user.id %]
+ <token>[% issue_hash_token([a.id, a.modification_time]) FILTER xml %]</token>
+ [% END %]
+ [% IF displayfields.attachmentdata %]
+ <data encoding="base64">[% a.data FILTER base64 %]</data>
+ [% END %]
+
+ [% PROCESS section_flags obj => a %]
+ </attachment>
+ [% END %]
+ [% END %]
+
+ [% Hook.process("bug_end") %]
+
+ </bug>
+ [% END %]
+[% END %]
+
+</bugzilla>
+
+[% BLOCK bug_field %]
+ [% FOREACH val = bug.$field %]
+ [%# We need to handle some fields differently. This should become
+ # nicer once we have custfields, and a type attribute for the fields
+ #%]
+ [% name = '' %]
+ [% IF field == 'reporter' OR field == 'assigned_to' OR
+ field == 'qa_contact' %]
+ [% name = val.name %]
+ [% val = val.email FILTER email %]
+ [% ELSIF field == 'cc' %]
+ [% val = val FILTER email %]
+ [% ELSIF field == 'creation_ts' OR field == 'delta_ts' %]
+ [% val = val FILTER time("%Y-%m-%d %T %z") %]
+ [% END %]
+ <[% field %][% IF name != '' %] name="[% name FILTER xml %]"[% END -%]>
+ [%- val FILTER xml %]</[% field %]>
+ [% END %]
+[% END %]
+
+[% BLOCK section_flags %]
+ [% RETURN UNLESS displayfields.flag %]
+
+ [% FOREACH flag = obj.flags %]
+ <flag name="[% flag.type.name FILTER xml %]"
+ id="[% flag.id FILTER xml %]"
+ type_id="[% flag.type_id FILTER xml %]"
+ status="[% flag.status FILTER xml %]"
+ setter="[% flag.setter.email FILTER email FILTER xml %]"
+ [% IF flag.status == "?" && flag.requestee %]
+ requestee="[% flag.requestee.email FILTER email FILTER xml %]"
+ [% END %]
+ />
+ [% END %]
+[% END %]
diff --git a/template/en/default/bug/summarize-time.html.tmpl b/template/en/default/bug/summarize-time.html.tmpl
new file mode 100644
index 000000000..21c26e8c8
--- /dev/null
+++ b/template/en/default/bug/summarize-time.html.tmpl
@@ -0,0 +1,351 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Christian Reis <kiko@async.com.br>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+[% title = "Time Summary " %]
+[% IF do_depends %]
+ [% title = title _ "for " %]
+ [% header = "$terms.Bug $ids.0" FILTER bug_link(ids.0) FILTER none %]
+ [% header = title _ header _ " (and $terms.bugs blocking it)" %]
+ [% title = title _ "$terms.Bug $ids.0" %]
+[% ELSE %]
+ [% title = title _ "($ids.size $terms.bugs selected)" %]
+ [% header = title %]
+[% END %]
+
+[% PROCESS global/header.html.tmpl
+ title = title
+ header = header
+ style_urls = ["skins/standard/summarize-time.css"]
+ doc_section = "timetracking.html"
+ yui = ['calendar']
+ javascript_urls = [ "js/util.js", "js/field.js" ]
+ %]
+
+[% INCLUDE query_form %]
+
+[% IF do_report %]
+
+ [% global.grand_total = 0 global.estimated = 0 global.remaining = 0 %]
+
+ [% FOREACH workdata = part_list %]
+ [%# parts contains date ranges (from, to). %]
+ [% part = parts.shift %]
+ <div align="right">
+ <h4 style="padding-right: 2em; margin: 0;">
+ [% IF part.0 or part.1 %]
+ [% part.0 OR "Up" FILTER html %] to [% part.1 OR "now" FILTER html %]
+ [% ELSE %]
+ Full summary (no period specified)
+ [% END %]
+ </h4>
+ </div>
+ [% IF group_by == "number" %]
+ [% INCLUDE number_report %]
+ [% ELSE %]
+ [% INCLUDE owner_report %]
+ [% END %]
+ [% END %]
+
+ [% IF detailed %]
+ <h4 style="margin: 0">
+ Total of [% global.remaining FILTER format("%.2f") %]h remains from
+ original estimate of [% global.estimated FILTER format("%.2f") %]h
+ [% IF global.deadline %]
+ (deadline [% global.deadline FILTER html %])
+ [% END %]
+ </h4>
+ [% END %]
+
+ [% IF monthly %]
+ <h4 style="margin: 0">Total of [% global.grand_total FILTER format("%.2f") %] hours worked</h4>
+ <hr noshade size="1">
+ [% END %]
+
+ [% IF null.size > 0 %]
+ [% INCLUDE inactive_report %]
+ <h4 style="margin: 0">Total of [% null.size %] inactive [% terms.bugs %]</h4>
+ [% END %]
+
+[% END %]
+
+[% PROCESS global/footer.html.tmpl %]
+
+[%#
+ #
+ # Developer reporting
+ #
+ #%]
+
+[% BLOCK owner_report %]
+ [% global.total = 0 global.bug_count = {} global.owner_count = {} %]
+ <table cellpadding="4" cellspacing="0" width="90%" class="realitems owner">
+ [% FOREACH owner = workdata.keys.sort %]
+ [% INCLUDE do_one_owner owner=owner ownerdata=workdata.$owner
+ detailed=detailed %]
+ [% END %]
+
+ [% additional = "$global.owner_count.size developers @
+ $global.bug_count.size $terms.bugs" %]
+ [% INCLUDE section_total colspan=3 additional=additional %]
+ </table>
+[% END %]
+
+[% BLOCK do_one_owner %]
+ [% global.owner_count.$owner = 1 %]
+ <tr><td colspan="5" class="owner_header">
+ <b>[% owner FILTER html %]</b>
+ </td></tr>
+ [% col = 0 subtotal = 0%]
+ [% FOREACH bugdata=ownerdata.nsort("bug_id") %]
+ [% bug_id = bugdata.bug_id %]
+ [% INCLUDE calc_bug_total id=bug_id %]
+ [% global.bug_count.$bug_id = 1 %]
+ [% IF detailed %]
+ [% INCLUDE bug_header cid=col id=bug_id bugdata=bugdata extra=1 %]
+ [% col = col + 1 %]
+ [% END %]
+ [% subtotal = subtotal + bugdata.total_time %]
+ [% END %]
+ <tr>
+ <td colspan="4" align="right"><b>Total</b>:</td>
+ <td align="right" class="subtotal" width="100">
+ <b>[% subtotal FILTER format("%.2f") %]</b></td>
+ [% global.total = global.total + subtotal %]
+ </tr>
+[% END %]
+
+[%#
+ #
+ # Bug Number reporting
+ #
+ #%]
+
+[% BLOCK number_report %]
+ [% global.total = 0 global.owner_count = {} global.bug_count = {} %]
+
+ <table cellpadding="4" cellspacing="0" width="90%" class="realitems number">
+ [% FOREACH bug = workdata.keys.nsort %]
+ [% INCLUDE do_one_bug id=bug bugdata=workdata.$bug
+ detailed=detailed %]
+ [% END %]
+
+ [% additional = "$global.bug_count.size $terms.bugs &
+ $global.owner_count.size developers" %]
+ [% INCLUDE section_total additional=additional colspan=2 %]
+ </table>
+[% END %]
+
+[% BLOCK do_one_bug %]
+ [% subtotal = 0.00 cid = 0 %]
+ [% INCLUDE calc_bug_total id=id %]
+ [% global.bug_count.$id = 1 %]
+ [% INCLUDE bug_header id=id %]
+
+ [% FOREACH owner = bugdata.sort("login_name") %]
+ [% work_time = owner.total_time %]
+ [% subtotal = subtotal + work_time %]
+ [% login_name = owner.login_name %]
+ [% global.owner_count.$login_name = 1 %]
+ [% IF detailed %]
+ [% cid = cid + 1 %]
+ <tr class="owner_header[% 2 FILTER none IF cid % 2 %]">
+ <td>&nbsp;</td>
+ <td colspan="2"><b>[% login_name FILTER html %]</b></td>
+ <td align="right">
+ [% work_time FILTER format("%.2f") %]</td>
+ </tr>
+ [% END %]
+ [% END %]
+ <tr>
+ <td colspan="2">&nbsp;</td>
+ <td align="right">
+ <b>Total</b>:
+ </td>
+ <td align="right" class="subtotal" width="100">
+ <b>[% subtotal FILTER format("%.2f") %]</b>
+ </td>
+ </tr>
+ [% global.total = global.total + subtotal %]
+[% END %]
+
+[% BLOCK bug_header %]
+ <tr class="bug_header[% '2' IF cid % 2 %]">
+ <td width="80" valign="top">
+ <b>[% "$terms.Bug $id" FILTER bug_link(id) FILTER none %]</b>
+ </td>
+ <td width="100"><b>[% display_value("bug_status", bugs.$id.bug_status) FILTER html %]</b></td>
+ <td colspan="2">[% bugs.$id.short_desc FILTER html %]</td>
+ [% IF extra %]
+ <td align="right" valign="top">[% bugdata.total_time FILTER html %]</td>
+ [% END %]
+ </tr>
+ [% IF detailed %]
+ <tr class="bug_header[% '2' IF cid % 2 %]">
+ <td>&nbsp;</td>
+ <td colspan="3">
+ <table width="100%" cellpadding="0" cellspacing="0">
+ <tr>
+ <td width="33%">
+ Estimated: [% bugs.$id.estimated_time FILTER format("%.2f") %]h
+ </td>
+ <td width="33%">
+ Remaining: [% bugs.$id.remaining_time FILTER format("%.2f") %]h
+ </td>
+ <td width="33%">
+ Deadline: [% bugs.$id.deadline || "<b>Not set</b>" %]
+ </td>
+ </tr>
+ </table>
+ </td>
+ [% IF extra %]
+ <td>&nbsp;</td>
+ [% END %]
+ </tr>
+ [% END %]
+[% END %]
+
+[% BLOCK calc_bug_total %]
+ [% IF !global.bug_count.$id %]
+ [% global.estimated = global.estimated + bugs.$id.estimated_time %]
+ [% global.remaining = global.remaining + bugs.$id.remaining_time %]
+ [% IF !global.deadline || bugs.$id.deadline &&
+ global.deadline.replace("-", "") < bugs.$id.deadline.replace("-", "") %]
+ [% SET global.deadline = bugs.$id.deadline %]
+ [% END %]
+ [% END %]
+[% END %]
+
+[% BLOCK inactive_report %]
+ <h3>Inactive [% terms.bugs %]</h3>
+ <table cellpadding="4" cellspacing="0" width="90%" class="zeroitems">
+ [% cid = 0 %]
+ [% FOREACH bug_id = null.nsort %]
+ [% INCLUDE bug_header id=bug_id cid=cid %]
+ [% cid = cid + 1 %]
+ [% END %]
+ </table>
+[% END %]
+
+
+[% BLOCK section_total %]
+ [% IF global.total > 0 %]
+ <tr class="section_total">
+ <td><b>Totals</b></td>
+ <td colspan="[% colspan FILTER html %]" align="right"><b>[% additional FILTER html %]</b></td>
+ <td align="right"><b>[% global.total FILTER format("%.2f") %]</b></td>
+ </tr>
+ [% ELSE %]
+ <tr>
+ <td>No time allocated during the specified period.</td>
+ </tr>
+ [% END %]
+ [% global.grand_total = global.grand_total + global.total %]
+[% END %]
+
+[%#
+ #
+ # The query form
+ #
+ #%]
+
+[% BLOCK query_form %]
+<hr noshade size=1>
+<form method="post" name="summary" style="display: inline" action="summarize_time.cgi">
+<input type="hidden" name="do_depends" value="[% do_depends FILTER html %]">
+<input type="hidden" name="id" value="[% ids.join(",") FILTER html %]">
+<input type="hidden" name="do_report" value="1">
+
+[% IF warn_swap_dates %]
+ <h4 style="border: 1px solid red; margin: 1em; padding: 0.5em">The
+ end date specified occurs before the start date, which doesn't
+ make sense; the dates below have therefore been swapped.</h4>
+[% END %]
+
+<table>
+<tr>
+<td align="right">
+ <b><label accesskey="s"
+ for="start_date">Period <u>s</u>tarting</label></b>:
+</td><td colspan="3">
+ <input type="text" id="start_date" name="start_date" size="11"
+ align="right" value="[% start_date FILTER html %]" maxlength="10"
+ onchange="updateCalendarFromField(this)">
+ <button type="button" class="calendar_button"
+ id="button_calendar_start_date"
+ onclick="showCalendar('start_date')"><span>Calendar</span>
+ </button>
+ <div id="con_calendar_start_date"></div>
+ &nbsp;
+ <b>and <label accesskey="e" for="end_date"><u>e</u>nding</label></b>:
+ <input type="text" name="end_date" size="11" id="end_date"
+ align="right" value ="[% end_date FILTER html %]" maxlength="10"
+ onchange="updateCalendarFromField(this)">
+ <button type="button" class="calendar_button"
+ id="button_calendar_end_date"
+ onclick="showCalendar('end_date')"><span>Calendar</span>
+ </button>
+ <div id="con_calendar_end_date"></div>
+</td><td align="right">
+ <input type="submit" id="summarize" value="Summarize">
+</td></tr>
+<tr>
+<td>&nbsp;</td><td colspan="4">
+ <small>(Dates are optional, and in YYYY-MM-DD format)</small>
+</td>
+<tr><td align="right">
+ <b>Group by</b>:
+</td><td colspan="2">
+ <input type="radio" name="group_by" id="number" value="number" [%+
+ 'checked="checked"' IF group_by == "number"
+ %]><label
+ for="number" accesskey="n">[% terms.Bug %] <u>N</u>umber</label>
+ <input type="radio" name="group_by" id="owner" value="owner" [%+
+ 'checked="checked"' IF group_by == "owner"
+ %]><label
+ for="owner" accesskey="d"><u>D</u>eveloper</label>
+</td><td colspan="2">
+ <label for="ctype"><b>Format</b></label>: <select name="ctype" id="ctype">
+ <option value="html">HTML Report</option>
+ </select>
+</td></tr><tr>
+<td>&nbsp;</td><td colspan="4">
+ <input type="checkbox" name="monthly" [% 'checked="checked"' IF
+ monthly %] id="monthly">
+ <label for="monthly" accesskey="m">Split by <u>m</u>onth</label>
+ [%# XXX: allow splitting by other intervals %]
+ &nbsp;
+ <input type="checkbox" name="detailed" [% 'checked="checked"' IF
+ detailed %] id="detailed">
+ <label for="detailed" accesskey="t">De<u>t</u>ailed summaries</label>
+ &nbsp;
+ <input type="checkbox" name="inactive" [% 'checked="checked"' IF
+ inactive %] id="inactive">
+ <label for="inactive" accesskey="i">Also show <u>i</u>nactive
+ [%+ terms.bugs %]</label>
+</td>
+</tr></table>
+
+</form>
+<script type="text/javascript">
+<!--
+ createCalendar('start_date');
+ createCalendar('end_date');
+ document.forms['summary'].start_date.focus();
+//--></script>
+<hr noshade size=1>
+[% END %]
diff --git a/template/en/default/bug/time.html.tmpl b/template/en/default/bug/time.html.tmpl
new file mode 100644
index 000000000..e070e7de0
--- /dev/null
+++ b/template/en/default/bug/time.html.tmpl
@@ -0,0 +1,47 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Jeff Hedlund <jeff.hedlund@matrixsi.com>
+ #
+ #%]
+
+[% BLOCK formattimeunit %]
+ [%# INTERFACE:
+ # time_unit: the number converting, converts to 2 decimal places
+ # unless the last character is a 0, then it truncates to
+ # 1 decimal place
+ #%]
+ [% time_unit = time_unit FILTER format('%.2f') %]
+ [% IF time_unit.match('0\Z') %]
+ [% time_unit FILTER format('%.1f') %]
+ [% ELSE %]
+ [% time_unit FILTER format('%.2f') %]
+ [% END %]
+[% END %]
+
+[% BLOCK calculatepercentage %]
+ [%# INTERFACE:
+ # act: actual time
+ # rem: remaining time
+ # %]
+ [% IF (act + rem) > 0 %]
+ [% (act / (act + rem)) * 100
+ FILTER format("%d") %]
+ [% ELSE %]
+ 0
+ [% END %]
+[% END %]
diff --git a/template/en/default/config.js.tmpl b/template/en/default/config.js.tmpl
new file mode 100644
index 000000000..0d6358312
--- /dev/null
+++ b/template/en/default/config.js.tmpl
@@ -0,0 +1,142 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Myk Melez <myk@mozilla.org>
+ #%]
+//
+// This file contains installation specific values for third-party clients.
+//
+// Note: this interface is experimental and under development.
+// We may and probably will make breaking changes to it in the future.
+
+// the global bugzilla url
+var installation = {
+ base_url : '[% urlbase FILTER js %]',
+ install_version : '[% constants.BUGZILLA_VERSION FILTER js %]',
+ maintainer : '[% Param('maintainer') FILTER js %]'
+};
+
+
+// Status and Resolution
+// =====================
+var status = [ [% FOREACH x = status %]'[% x FILTER js %]', [% END %] ];
+var status_open = [ [% FOREACH x = open_status %]'[% x FILTER js %]', [% END %] ];
+var status_closed = [ [% FOREACH x = closed_status %]'[% x FILTER js %]', [% END %] ];
+var resolution = [ [% FOREACH x = resolution %]'[% x FILTER js %]', [% END %] ];
+
+
+// Keywords
+// ========
+
+var keyword = [ [% FOREACH x = keyword %]'[% x FILTER js %]', [% END %] ];
+
+
+// Platforms
+// =========
+
+var platform = [ [% FOREACH x = platform %]'[% x FILTER js %]', [% END %] ];
+
+
+// Severities
+// ==========
+
+var severity = [ [% FOREACH x = severity %]'[% x FILTER js %]', [% END %] ];
+
+
+// Custom Fields
+// =============
+
+[% FOREACH cf = custom_fields %]
+var [% cf.name FILTER js %] = [ [% FOREACH x = cf.legal_values %]'[% x.name FILTER js %]', [% END %] ];
+[% END %]
+
+
+// Products and Components
+// =======================
+//
+// It is not necessary to list all products and components here.
+// Instead, you can define a "blacklist" for some commonly used words
+// or word fragments that occur in a product or component name
+// but should _not_ trigger product/component search.
+
+
+// A list of all products and their components, versions, and target milestones:
+
+var component = new Object();
+var version = new Object();
+var target_milestone = new Object();
+
+[% FOREACH p = products %]
+ component['[% p.name FILTER js %]'] = [ [% FOREACH x = p.components %]'[% x.name FILTER js %]', [% END %] ];
+ version['[% p.name FILTER js %]'] = [ [% FOREACH x = p.versions %]'[% x.name FILTER js %]', [% END %] ];
+ target_milestone['[% p.name FILTER js %]'] = [ [% FOREACH x = p.milestones %]'[% x.name FILTER js %]', [% END %] ];
+[% END %]
+
+// Product and Component Exceptions
+// ================================
+//
+// A blacklist for some commonly used words or word fragments
+// that occur in a product or component name but should *not*
+// trigger product/component search in QuickSearch.
+
+var product_exceptions = new Array(
+ // Example:
+ //"row" // [Browser]
+ // // ^^^
+ //,"new" // [MailNews]
+ // // ^^^
+);
+
+var component_exceptions = new Array(
+ // Example:
+ //"hang" // [mozilla.org] Bugzilla: Component/Keyword Changes
+ // // ^^^^
+);
+
+// Queryable Fields
+// ================
+[% PROCESS "global/field-descs.none.tmpl" %]
+var field = [
+[% FOREACH x = field %]
+ { name: '[% x.name FILTER js %]',
+ description: '[% (field_descs.${x.name} OR x.description) FILTER js %]',
+ [%-# These values are meaningful for custom fields only. %]
+ [% IF x.custom %]
+ type: '[% x.type FILTER js %]',
+ type_desc: '[% field_types.${x.type} FILTER js %]',
+ enter_bug: '[% x.enter_bug FILTER js %]',
+ [% END %]
+ },
+[% END %]
+];
+
+// Deprecated Variables
+// ====================
+//
+// Other names for various variables. These are deprecated
+// and could go away at any time. Use them at your own risk!
+
+var bugzilla = installation.base_url;
+var statuses = status;
+var statuses_resolved = status_closed;
+var resolutions = resolution;
+var keywords = keyword;
+var platforms = platform;
+var severities = severity;
+var cpts = component;
+var vers = version;
+var tms = target_milestone;
diff --git a/template/en/default/config.rdf.tmpl b/template/en/default/config.rdf.tmpl
new file mode 100644
index 000000000..2d69c6e26
--- /dev/null
+++ b/template/en/default/config.rdf.tmpl
@@ -0,0 +1,267 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Myk Melez <myk@mozilla.org>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+
+[%# The url to the installation is going to be displayed many times.
+ # So we cache it here for better performance.
+ %]
+[% escaped_urlbase = BLOCK %][% urlbase FILTER xml %][% END %]
+<?xml version="1.0"[% IF Param('utf8') %] encoding="UTF-8"[% END %]?>
+<!-- Note: this interface is experimental and under development.
+ - We may and probably will make breaking changes to it in the future. -->
+
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:bz="http://www.bugzilla.org/rdf#">
+
+<bz:installation rdf:about="[% escaped_urlbase %]">
+ <bz:install_version>[% constants.BUGZILLA_VERSION FILTER html %]</bz:install_version>
+ <bz:maintainer>[% Param('maintainer') FILTER html %]</bz:maintainer>
+
+ <bz:status>
+ <Seq>
+ [% FOREACH item = status %]
+ <li>[% item FILTER html %]</li>
+ [% END %]
+ </Seq>
+ </bz:status>
+
+ <bz:status_open>
+ <Seq>
+ [% FOREACH item = open_status %]
+ <li>[% item FILTER html %]</li>
+ [% END %]
+ </Seq>
+ </bz:status_open>
+
+ <bz:status_closed>
+ <Seq>
+ [% FOREACH item = closed_status %]
+ <li>[% item FILTER html %]</li>
+ [% END %]
+ </Seq>
+ </bz:status_closed>
+
+ <bz:resolution>
+ <Seq>
+ [% FOREACH item = resolution %]
+ <li>[% item FILTER html %]</li>
+ [% END %]
+ </Seq>
+ </bz:resolution>
+
+ <bz:keyword>
+ <Seq>
+ [% FOREACH item = keyword %]
+ <li>[% item FILTER html %]</li>
+ [% END %]
+ </Seq>
+ </bz:keyword>
+
+ <bz:platform>
+ <Seq>
+ [% FOREACH item = platform %]
+ <li>[% item FILTER html %]</li>
+ [% END %]
+ </Seq>
+ </bz:platform>
+
+ <bz:op_sys>
+ <Seq>
+ [% FOREACH item = op_sys %]
+ <li>[% item FILTER html %]</li>
+ [% END %]
+ </Seq>
+ </bz:op_sys>
+
+ <bz:priority>
+ <Seq>
+ [% FOREACH item = priority %]
+ <li>[% item FILTER html %]</li>
+ [% END %]
+ </Seq>
+ </bz:priority>
+
+ <bz:severity>
+ <Seq>
+ [% FOREACH item = severity %]
+ <li>[% item FILTER html %]</li>
+ [% END %]
+ </Seq>
+ </bz:severity>
+
+[% FOREACH cf = custom_fields %]
+ <bz:[% cf.name FILTER html %]>
+ <Seq>
+ [% FOREACH item = cf.legal_values %]
+ <li>[% item.name FILTER html %]</li>
+ [% END %]
+ </Seq>
+ </bz:[% cf.name FILTER html %]>
+
+[% END %]
+
+ <bz:products>
+ <Seq>
+ [% FOREACH product = products %]
+ <li>
+ <bz:product rdf:about="[% escaped_urlbase %]product.cgi?name=[% product.name FILTER url_quote %]">
+ <bz:name>[% product.name FILTER html %]</bz:name>
+ <bz:allows_unconfirmed>[% product.allows_unconfirmed FILTER html %]</bz:allows_unconfirmed>
+
+ <bz:components>
+ <Seq>
+ [% FOREACH component = product.components %]
+ <li resource="[% escaped_urlbase %]component.cgi?name=[% component.name FILTER url_quote
+ %]&amp;product=[% product.name FILTER url_quote %]"/>
+ [% END %]
+ </Seq>
+ </bz:components>
+
+ <bz:versions>
+ <Seq>
+ [% FOREACH version = product.versions %]
+ <li resource="[% escaped_urlbase %]version.cgi?name=[% version.name FILTER url_quote %]"/>
+ [% END %]
+ </Seq>
+ </bz:versions>
+
+ [% IF Param('usetargetmilestone') %]
+ <bz:target_milestones>
+ <Seq>
+ [% FOREACH milestone = product.milestones %]
+ <li resource="[% escaped_urlbase %]milestone.cgi?name=[% milestone.name FILTER url_quote %]"/>
+ [% END %]
+ </Seq>
+ </bz:target_milestones>
+ [% END %]
+
+ </bz:product>
+ </li>
+ [% END %]
+ </Seq>
+ </bz:products>
+
+ [% all_visible_flag_types = {} %]
+ <bz:components>
+ <Seq>
+ [% FOREACH product = products %]
+ [% FOREACH component = product.components %]
+ <li>
+ <bz:component rdf:about="[% escaped_urlbase %]component.cgi?name=[% component.name FILTER url_quote
+ %]&amp;product=[% product.name FILTER url_quote %]">
+ <bz:name>[% component.name FILTER html %]</bz:name>
+ [% IF show_flags %]
+ <bz:flag_types>
+ <Seq>
+ [% flag_types = component.flag_types.bug.merge(component.flag_types.attachment) %]
+ [% FOREACH flag_type = flag_types %]
+ [% NEXT UNLESS flag_type.is_active %]
+ [% all_visible_flag_types.${flag_type.id} = flag_type %]
+ <li resource="[% escaped_urlbase %]flag.cgi?id=[% flag_type.id FILTER url_quote
+ %]&amp;name=[% flag_type.name FILTER url_quote %]" />
+ [% END %]
+ </Seq>
+ </bz:flag_types>
+ [% END %]
+ </bz:component>
+ </li>
+ [% END %]
+ [% END %]
+ </Seq>
+ </bz:components>
+
+ <bz:versions>
+ <Seq>
+ [% FOREACH product = products %]
+ [% FOREACH version = product.versions %]
+ <li>
+ <bz:version rdf:about="[% escaped_urlbase %]version.cgi?name=[% version.name FILTER url_quote %]">
+ <bz:name>[% version.name FILTER html %]</bz:name>
+ </bz:version>
+ </li>
+ [% END %]
+ [% END %]
+ </Seq>
+ </bz:versions>
+
+ [% IF Param('usetargetmilestone') %]
+ <bz:target_milestones>
+ <Seq>
+ [% FOREACH product = products %]
+ [% FOREACH milestone = product.milestones %]
+ <li>
+ <bz:target_milestone rdf:about="[% escaped_urlbase %]milestone.cgi?name=[% milestone.name FILTER url_quote %]">
+ <bz:name>[% milestone.name FILTER html %]</bz:name>
+ </bz:target_milestone>
+ </li>
+ [% END %]
+ [% END %]
+ </Seq>
+ </bz:target_milestones>
+ [% END %]
+
+ [% IF show_flags %]
+ <bz:flag_types>
+ <Seq>
+ [% FOREACH flag_type = all_visible_flag_types.values.sort('name') %]
+ <li>
+ <bz:flag_type rdf:about="[% escaped_urlbase %]flag.cgi?id=[% flag_type.id FILTER url_quote
+ %]&amp;name=[% flag_type.name FILTER url_quote %]">
+ <bz:id>[% flag_type.id FILTER html %]</bz:id>
+ <bz:name>[% flag_type.name FILTER html %]</bz:name>
+ <bz:description>[% flag_type.description FILTER html %]</bz:description>
+ <bz:type>[% flag_type.target_type FILTER html %]</bz:type>
+ <bz:requestable>[% flag_type.is_requestable FILTER html %]</bz:requestable>
+ <bz:specifically_requestable>[% flag_type.is_requesteeble FILTER html %]</bz:specifically_requestable>
+ <bz:multiplicable>[% flag_type.is_multiplicable FILTER html %]</bz:multiplicable>
+ [% IF user.in_group("editcomponents") %]
+ <bz:grant_group>[% flag_type.grant_group.name FILTER html %]</bz:grant_group>
+ <bz:request_group>[% flag_type.request_group.name FILTER html %]</bz:request_group>
+ [% END %]
+ </bz:flag_type>
+ </li>
+ [% END %]
+ </Seq>
+ </bz:flag_types>
+ [% END %]
+
+ <bz:fields>
+ <Seq>
+ [% PROCESS "global/field-descs.none.tmpl" %]
+ [% FOREACH item = field %]
+ <li>
+ <bz:field rdf:about="[% escaped_urlbase %]field.cgi?name=[% item.name FILTER url_quote %]">
+ <bz:name>[% item.name FILTER html %]</bz:name>
+ <bz:description>[% (field_descs.${item.name} OR item.description) FILTER html %]</bz:description>
+ [%-# These values are meaningful for custom fields only. %]
+ [% IF item.custom %]
+ <bz:type>[% item.type FILTER html %]</bz:type>
+ <bz:type_desc>[% field_types.${item.type} FILTER html %]</bz:type_desc>
+ <bz:enter_bug>[% item.enter_bug FILTER html %]</bz:enter_bug>
+ [% END %]
+ </bz:field>
+ </li>
+ [% END %]
+ </Seq>
+ </bz:fields>
+</bz:installation>
+
+</RDF>
diff --git a/template/en/default/email/lockout.txt.tmpl b/template/en/default/email/lockout.txt.tmpl
new file mode 100644
index 000000000..ac6525779
--- /dev/null
+++ b/template/en/default/email/lockout.txt.tmpl
@@ -0,0 +1,39 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is the Mozilla Corporation.
+ # Portions created by the Initial Developer are Copyright (C) 2008
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+From: [% Param('mailfrom') %]
+To: [% Param('maintainer') %]
+Subject: [[% terms.Bugzilla %]] Account Lock-Out: [% locked_user.login %] ([% attempts.0.ip_addr %])
+X-Bugzilla-Type: admin
+
+The IP address [% attempts.0.ip_addr %] failed too many login attempts (
+[%- constants.MAX_LOGIN_ATTEMPTS +%]) for
+the account [% locked_user.login %].
+
+The login attempts occurred at these times:
+
+[% FOREACH login = attempts %]
+ [%+ login.login_time FILTER time %]
+[% END %]
+
+This IP will be able to log in again using this account at
+[%+ unlock_at FILTER time %].
diff --git a/template/en/default/email/newchangedmail.txt.tmpl b/template/en/default/email/newchangedmail.txt.tmpl
new file mode 100644
index 000000000..44af3055d
--- /dev/null
+++ b/template/en/default/email/newchangedmail.txt.tmpl
@@ -0,0 +1,70 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): André Batosti <batosti@async.com.br>
+ #%]
+
+[% PROCESS "global/variables.none.tmpl" %]
+[% PROCESS "global/reason-descs.none.tmpl" %]
+
+From: [% Param('mailfrom') %]
+To: [% to_user.email %]
+Subject: [[% terms.Bug %] [%+ bug.id %]] [% 'New: ' IF isnew %][%+ bug.short_desc %]
+Date: [% bug.delta_ts FILTER time("%a, %d %b %Y %T %z", to_user.timezone) %]
+X-Bugzilla-Reason: [% reasonsheader %]
+X-Bugzilla-Type: [% isnew ? 'new' : 'changed' %]
+X-Bugzilla-Watch-Reason: [% reasonswatchheader %]
+[% IF Param('useclassification') %]
+X-Bugzilla-Classification: [% bug.classification %]
+[% END %]
+X-Bugzilla-Product: [% bug.product %]
+X-Bugzilla-Component: [% bug.component %]
+X-Bugzilla-Keywords: [% bug.keywords %]
+X-Bugzilla-Severity: [% bug.bug_severity %]
+X-Bugzilla-Who: [% changer.login %]
+X-Bugzilla-Status: [% bug.bug_status %]
+X-Bugzilla-Priority: [% bug.priority %]
+X-Bugzilla-Assigned-To: [% bug.assigned_to.login %]
+X-Bugzilla-Target-Milestone: [% bug.target_milestone %]
+X-Bugzilla-Changed-Fields: [% changedfields.join(" ") %]
+[%+ threadingmarker %]
+
+[%+ urlbase %]show_bug.cgi?id=[% bug.id %]
+[%- IF diffs %]
+
+[%+ diffs %]
+[% END -%]
+[% FOREACH comment = new_comments %]
+
+[%- IF comment.count %]
+--- Comment #[% comment.count %] from [% comment.author.identity %] [%+ comment.creation_ts FILTER time(undef, to_user.timezone) %] ---
+[% END %]
+[%+ comment.body_full({ is_bugmail => 1, wrap => 1 }) %]
+[% END %]
+
+-- [%# Protect the trailing space of the signature marker %]
+Configure [% terms.bug %]mail: [% urlbase %]userprefs.cgi?tab=email
+------- You are receiving this mail because: -------
+[% SET reason_lines = [] %]
+[% FOREACH reason = reasons %]
+ [% reason_lines.push(reason_descs.$reason) IF reason_descs.$reason %]
+[% END %]
+[% FOREACH reason = reasons_watch %]
+ [% reason_lines.push(watch_reason_descs.$reason)
+ IF watch_reason_descs.$reason %]
+[% END %]
+[%+ reason_lines.join("\n") %]
diff --git a/template/en/default/email/sanitycheck.txt.tmpl b/template/en/default/email/sanitycheck.txt.tmpl
new file mode 100644
index 000000000..8826d4e39
--- /dev/null
+++ b/template/en/default/email/sanitycheck.txt.tmpl
@@ -0,0 +1,36 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+
+[% PROCESS "global/variables.none.tmpl" %]
+From: [% Param('mailfrom') %]
+To: [% addressee %]
+Subject: [[% terms.Bugzilla %]] Sanity Check Results
+X-Bugzilla-Type: sanitycheck
+
+[%+ urlbase %]sanitycheck.cgi
+
+Below can you read the sanity check results.
+[% IF error_found %]
+Some errors have been found.
+[% ELSE %]
+No errors have been found.
+[% END %]
+
+[% output %]
diff --git a/template/en/default/email/sudo.txt.tmpl b/template/en/default/email/sudo.txt.tmpl
new file mode 100644
index 000000000..74fbc4976
--- /dev/null
+++ b/template/en/default/email/sudo.txt.tmpl
@@ -0,0 +1,43 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 2005 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): A. Karl Kornel <karl@kornel.name>
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+Content-Type: text/plain
+From: [% Param('mailfrom') %]
+To: [% user.email %]
+Subject: [[% terms.Bugzilla %]] Your account [% user.login -%]
+ is being impersonated
+X-Bugzilla-Type: admin
+
+ [%+ sudoer.identity %] has used the 'sudo' feature to access
+[%+ terms.Bugzilla %] using your account.
+
+[% IF reason %]
+ [%+ sudoer.identity %] provided the following reason for doing this:
+
+[% reason FILTER wrap_comment %]
+[% ELSE %]
+ [%+ sudoer.identity %] did not provide a reason for doing this.
+[% END %]
+
+ If you feel that this action was inappropriate, please contact
+[%+ Param("maintainer") %]. For more information on this feature,
+visit <[% urlbase %]page.cgi?id=sudo.html>.
diff --git a/template/en/default/email/whine.txt.tmpl b/template/en/default/email/whine.txt.tmpl
new file mode 100644
index 000000000..32d8da8a0
--- /dev/null
+++ b/template/en/default/email/whine.txt.tmpl
@@ -0,0 +1,64 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Emmanuel Seyman <eseyman@linagora.com>
+ #%]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+From: [% Param("mailfrom") %]
+To: [% email %][% Param("emailsuffix") %]
+Subject: Your [% terms.Bugzilla %] [%+ terms.bug %] list needs attention.
+X-Bugzilla-Type: whine
+
+[This e-mail has been automatically generated.]
+
+You have one or more [% terms.bugs %] assigned to you in the [% terms.Bugzilla %]
+[% terms.bug %] tracking system ([% urlbase %]) that require
+attention.
+
+All of these [% terms.bugs %] are in the [% display_value("bug_status", "CONFIRMED") %]
+state, and have not been touched in [% Param("whinedays") %] days or more.
+You need to take a look at them, and decide on an initial action.
+
+Generally, this means one of three things:
+
+(1) You decide this [% terms.bug %] is really quick to deal with (like, it's [% display_value("resolution", "INVALID") %]),
+ and so you get rid of it immediately.
+(2) You decide the [% terms.bug %] doesn't belong to you, and you reassign it to
+ someone else. (Hint: if you don't know who to reassign it to, make
+ sure that the Component field seems reasonable, and then use the
+ "Reset Assignee to default" option.)
+(3) You decide the [% terms.bug %] belongs to you, but you can't solve it this moment.
+ Accept the [% terms.bug %] by setting the status to [% display_value("bug_status", "IN_PROGRESS") %].
+
+To get a list of all [% display_value("bug_status", "CONFIRMED") %] [%+ terms.bugs %], you can use this URL (bookmark
+it if you like!):
+
+ [% urlbase %]buglist.cgi?bug_status=CONFIRMED&assigned_to=[% email %]
+
+Or, you can use the general query page, at
+[%+ urlbase %]query.cgi
+
+Appended below are the individual URLs to get to all of your [% display_value("bug_status", "CONFIRMED") %] [%+ terms.bugs %]
+that haven't been touched for [% Param("whinedays") %] days or more.
+
+You will get this message once a day until you've dealt with these [% terms.bugs %]!
+
+[% FOREACH bug = bugs %]
+ [%+ bug.summary %]
+ -> [% urlbase %]show_bug.cgi?id=[% bug.id %]
+[% END %]
diff --git a/template/en/default/extensions/config.pm.tmpl b/template/en/default/extensions/config.pm.tmpl
new file mode 100644
index 000000000..6997ec178
--- /dev/null
+++ b/template/en/default/extensions/config.pm.tmpl
@@ -0,0 +1,41 @@
+[%# -*- mode: perl -*- %]
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2009 the
+ # Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[%# INTERFACE:
+ # name: string; The name of the extension.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% PROCESS extensions/license.txt.tmpl %]
+
+package B[% %]ugzilla::Extension::[% name %];
+use strict;
+
+use constant NAME => '[% name %]';
+
+use constant REQUIRED_MODULES => [
+];
+
+use constant OPTIONAL_MODULES => [
+];
+
+__PACKAGE__->NAME;
diff --git a/template/en/default/extensions/extension.pm.tmpl b/template/en/default/extensions/extension.pm.tmpl
new file mode 100644
index 000000000..249227103
--- /dev/null
+++ b/template/en/default/extensions/extension.pm.tmpl
@@ -0,0 +1,46 @@
+[%# -*- mode: perl -*- %]
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2009 the
+ # Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[%# INTERFACE:
+ # name: string; The name of the extension.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% PROCESS extensions/license.txt.tmpl %]
+
+package B[% %]ugzilla::Extension::[% name %];
+use strict;
+use base qw(B[% %]ugzilla::Extension);
+
+# This code for this is in [% path %]/lib/Util.pm
+use B[% %]ugzilla::Extension::[% name %]::Util;
+
+our $VERSION = '0.01';
+
+# See the documentation of B[% %]ugzilla::Hook ("perldoc B[% %]ugzilla::Hook"
+# in the bugzilla directory) for a list of all available hooks.
+sub install_update_db {
+ my ($self, $args) = @_;
+
+}
+
+__PACKAGE__->NAME;
diff --git a/template/en/default/extensions/hook-readme.txt.tmpl b/template/en/default/extensions/hook-readme.txt.tmpl
new file mode 100644
index 000000000..efceec136
--- /dev/null
+++ b/template/en/default/extensions/hook-readme.txt.tmpl
@@ -0,0 +1,27 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2009 the
+ # Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+Template hooks go in this directory. Template hooks are called in normal
+[%+ terms.Bugzilla %] templates like [[% '%' %] Hook.process('some-hook') %].
+More information about them can be found in the documentation of
+B[% %]ugzilla::Extension. (Do "perldoc B[% %]ugzilla::Extension" from the main
+[%+ terms.Bugzilla %] directory to see that documentation.)
diff --git a/template/en/default/extensions/license.txt.tmpl b/template/en/default/extensions/license.txt.tmpl
new file mode 100644
index 000000000..964e07505
--- /dev/null
+++ b/template/en/default/extensions/license.txt.tmpl
@@ -0,0 +1,47 @@
+[%# -*- mode: perl -*- %]
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2009 the
+ # Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[%# INTERFACE:
+ # name: string; The name of the extension.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the [% name %] [%+ terms.Bugzilla %] Extension.
+#
+# The Initial Developer of the Original Code is YOUR NAME
+# Portions created by the Initial Developer are Copyright (C) [% year %] the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# YOUR NAME <YOUR EMAIL ADDRESS>
diff --git a/template/en/default/extensions/name-readme.txt.tmpl b/template/en/default/extensions/name-readme.txt.tmpl
new file mode 100644
index 000000000..6d25c839e
--- /dev/null
+++ b/template/en/default/extensions/name-readme.txt.tmpl
@@ -0,0 +1,38 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2009 the
+ # Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+Normal templates go in this directory. You can load them in your
+code like this:
+
+use B[% %]ugzilla::Error;
+my $template = B[% %]ugzilla->template;
+$template->process('[% name FILTER lower %]/some-template.html.tmpl')
+ or ThrowTemplateError($template->error());
+
+That would be how to load a file called some-template.html.tmpl that
+was in this directory.
+
+Note that you have to be careful that the full path of your template
+never conflicts with a template that exists in [% terms.Bugzilla %] or in
+another extension, or your template might override that template. That's why
+we created this directory called '[% name FILTER lower %]' for you, so you
+can put your templates in here to help avoid conflicts.
diff --git a/template/en/default/extensions/util.pm.tmpl b/template/en/default/extensions/util.pm.tmpl
new file mode 100644
index 000000000..32076a665
--- /dev/null
+++ b/template/en/default/extensions/util.pm.tmpl
@@ -0,0 +1,42 @@
+[%# -*- mode: perl -*- %]
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2009 the
+ # Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[%# INTERFACE:
+ # name: string; The name of the extension.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% PROCESS extensions/license.txt.tmpl %]
+
+package B[% %]ugzilla::Extension::[% name %]::Util;
+use strict;
+use base qw(Exporter);
+our @EXPORT = qw(
+
+);
+
+# This file can be loaded by your extension via
+# "use B[% %]ugzilla::Extension::[% name %]::Util". You can put functions
+# used by your extension in here. (Make sure you also list them in
+# @EXPORT.)
+
+1;
diff --git a/template/en/default/extensions/web-readme.txt.tmpl b/template/en/default/extensions/web-readme.txt.tmpl
new file mode 100644
index 000000000..55e593914
--- /dev/null
+++ b/template/en/default/extensions/web-readme.txt.tmpl
@@ -0,0 +1,29 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010 the
+ # Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+Web-accessible files, like JavaScript, CSS, and images go in this
+directory. You can reference them directly in your HTML. For example,
+if you have a file called "style.css" and your extension is called
+"Foo", you would put it in "extensions/Foo/web/style.css", and then
+you could link to it in HTML like:
+
+<link href="extensions/Foo/web/style.css" rel="stylesheet" type="text/css">
diff --git a/template/en/default/filterexceptions.pl b/template/en/default/filterexceptions.pl
new file mode 100644
index 000000000..099748122
--- /dev/null
+++ b/template/en/default/filterexceptions.pl
@@ -0,0 +1,511 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code are the Bugzilla tests.
+#
+# The Initial Developer of the Original Code is Jacob Steenhagen.
+# Portions created by Jacob Steenhagen are
+# Copyright (C) 2001 Jacob Steenhagen. All
+# Rights Reserved.
+#
+# Contributor(s): Gervase Markham <gerv@gerv.net>
+
+# Important! The following classes of directives are excluded in the test,
+# and so do not need to be added here. Doing so will cause warnings.
+# See 008filter.t for more details.
+#
+# Comments - [%#...
+# Directives - [% IF|ELSE|UNLESS|FOREACH...
+# Assignments - [% foo = ...
+# Simple literals - [% " selected" ...
+# Values always used for numbers - [% (i|j|k|n|count) %]
+# Params - [% Param(...
+# Safe functions - [% (time2str)...
+# Safe vmethods - [% foo.size %] [% foo.length %]
+# [% foo.push() %]
+# TT loop variables - [% loop.count %]
+# Already-filtered stuff - [% wibble FILTER html %]
+# where the filter is one of html|csv|js|url_quote|quoteUrls|time|uri|xml|none
+
+%::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.id',
+ 'flag.status',
+ 'type.id',
+],
+
+'search/boolean-charts.html.tmpl' => [
+ '"field${chartnum}-${rownum}-${colnum}"',
+ 'field.name',
+ '"${chartnum}-${rownum}-${newor}"',
+ '"${chartnum}-${newand}-0"',
+ 'newchart',
+ 'jsmagic',
+],
+
+'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;$tbl_vals" IF tbl_vals',
+ '"&amp;$col_vals" IF col_vals',
+ '"&amp;$row_vals" IF row_vals',
+ 'classes.$row_idx.$col_idx',
+ 'urlbase',
+ 'data.$tbl.$col.$row',
+ 'row_total',
+ 'col_totals.$col',
+ 'grand_total',
+],
+
+'reports/report.html.tmpl' => [
+ 'width',
+ 'height',
+ 'imageurl',
+ 'formaturl',
+ 'other_format.name',
+ 'sizeurl',
+ 'switchbase',
+ 'format',
+ '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',
+ 'abbrev.$id.title || field_descs.$id || column.title',
+],
+
+'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' => [
+ 'message_tag',
+ 'series.frequency * 2',
+],
+
+'global/per-bug-queries.html.tmpl' => [
+ '" value=\"$bugids\"" IF bugids',
+],
+
+'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',
+ 'fields.${field_name}.flag_type.name',
+],
+
+'global/site-navigation.html.tmpl' => [
+ 'bug.bug_id',
+],
+
+'bug/comments.html.tmpl' => [
+ 'comment.id',
+ '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',
+ 'dep.title',
+ 'dep.fieldname',
+ 'bug.${dep.fieldname}.join(\', \')',
+ 'selname',
+ '" accesskey=\"$accesskey\"" IF accesskey',
+ '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 FILTER format(\'%.1f\')',
+ 'time_unit FILTER format(\'%.2f\')',
+ '(act / (act + rem)) * 100
+ FILTER format("%d")',
+],
+
+'bug/process/results.html.tmpl' => [
+ 'title.$type',
+ '"$terms.Bug $id" FILTER bug_link(id)',
+ '"$terms.bug $id" FILTER bug_link(id)',
+],
+
+'bug/create/create.html.tmpl' => [
+ 'cloned_bug_id',
+],
+
+'bug/create/create-guided.html.tmpl' => [
+ 'tablecolour',
+ 'sel',
+ 'productstring',
+],
+
+'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',
+ 'a',
+ 'editable_or_hide',
+],
+
+'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' => [
+ 'lxr_prefix',
+ 'file.minus_lines',
+ 'file.plus_lines',
+ 'bonsai_prefix',
+ 'section.old_start',
+ 'section_num',
+ 'current_line_old',
+ 'current_line_new',
+],
+
+'admin/admin.html.tmpl' => [
+ 'class'
+],
+
+'admin/table.html.tmpl' => [
+ 'link_uri'
+],
+
+'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' => [
+ 'action',
+ 'type.id',
+ 'type.target_type',
+ 'type.sortkey || 1',
+ 'typeLabelLowerPlural',
+ 'typeLabelLowerSingular',
+ '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/flag/list.html.tmpl b/template/en/default/flag/list.html.tmpl
new file mode 100644
index 000000000..5f88fc168
--- /dev/null
+++ b/template/en/default/flag/list.html.tmpl
@@ -0,0 +1,216 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Myk Melez <myk@mozilla.org>
+ #%]
+
+[% IF user.id AND !read_only_flags %]
+
+[%# We list flags by looping twice over the flag types relevant for the bug.
+ # In the first loop, we display existing flags and then, for active types,
+ # we display UI for adding new flags. In the second loop, we display UI
+ # for adding additional new flags for those types for which a flag already
+ # exists but which are multiplicable (can have multiple flags of the type
+ # on a single bug/attachment).
+ #%]
+
+[% DEFAULT flag_table_id = "flags" %]
+
+<script src="[% 'js/flag.js' FILTER mtime %]" type="text/javascript"></script>
+
+<table id="[% flag_table_id FILTER html %]">
+ [% UNLESS flag_no_header %]
+ <tr>
+ <th colspan="3">
+ Flags:
+ </th>
+ [% IF any_flags_requesteeble %]
+ <th>
+ Requestee:
+ </th>
+ [% END %]
+ </tr>
+ [% END %]
+
+ [%# Step 1: Display every flag type (except inactive types with no flags). %]
+ [% FOREACH type = flag_types %]
+
+ [%# Step 1a: Display existing flag(s). %]
+ [% FOREACH flag = type.flags %]
+ <tr>
+ <td>
+ <span title="[% flag.setter.identity FILTER html %]">[% flag.setter.nick FILTER html %]</span>:
+ </td>
+ <td>
+ <label title="[% type.description FILTER html %]"
+ for="flag-[% flag.id %]">
+ [%- type.name FILTER html FILTER no_break -%]</label>
+ </td>
+ <td>
+ <select id="flag-[% flag.id %]" name="flag-[% flag.id %]"
+ title="[% type.description FILTER html %]"
+ onchange="toggleRequesteeField(this);"
+ class="flag_select flag_type-[% type.id %]">
+ [%# Only display statuses the user is allowed to set. %]
+ [% IF user.can_request_flag(type) || flag.setter_id == user.id %]
+ <option value="X"></option>
+ [% END %]
+ [% IF type.is_active %]
+ [% IF (type.is_requestable && user.can_request_flag(type)) || flag.status == "?" %]
+ <option value="?" [% "selected" IF flag.status == "?" %]>?</option>
+ [% END %]
+ [% IF user.can_set_flag(type) || flag.status == "+" %]
+ <option value="+" [% "selected" IF flag.status == "+" %]>+</option>
+ [% END %]
+ [% IF user.can_set_flag(type) || flag.status == "-" %]
+ <option value="-" [% "selected" IF flag.status == "-" %]>-</option>
+ [% END %]
+ [% ELSE %]
+ <option value="[% flag.status %]" selected="selected">[% flag.status %]</option>
+ [% END %]
+ </select>
+ </td>
+ [% IF any_flags_requesteeble %]
+ <td>
+ [% IF (type.is_active && type.is_requestable && type.is_requesteeble) || flag.requestee %]
+ <span style="white-space: nowrap;">
+ [% SET flag_custom_list = [] %]
+ [% IF Param('usemenuforusers') %]
+ [% flag_custom_list = flag.type.grant_list %]
+ [% IF !(type.is_active && type.is_requestable && type.is_requesteeble) %]
+ [%# We are here only because there was already a requestee. In this case,
+ the only valid action is to remove the requestee or leave it alone;
+ nothing else. %]
+ [% flag_custom_list = [flag.requestee] %]
+ [% END %]
+ [% END %]
+ [% INCLUDE global/userselect.html.tmpl
+ name => "requestee-$flag.id"
+ id => "requestee-$flag.id"
+ value => flag.requestee.login
+ multiple => 0
+ emptyok => 1
+ classes => ["requestee"]
+ custom_userlist => flag_custom_list
+ %]
+ </span>
+ [% END %]
+ </td>
+ [% END %]
+ </tr>
+ [% END %]
+
+ [%# Step 1b: Display UI for setting flag. %]
+ [% IF (!type.flags || type.flags.size == 0) && type.is_active %]
+
+ [% PROCESS flag_row first_cell_empty = 1 addl_text = "" %]
+ [% END %]
+ [% END %]
+
+ [%# Step 2: Display flag type again (if type is multiplicable). %]
+ [% FOREACH type = flag_types %]
+ [% NEXT UNLESS type.flags && type.flags.size > 0 && type.is_multiplicable && type.is_active %]
+ [% IF !separator_displayed %]
+ <tr><td colspan="3"><hr></td></tr>
+ [% separator_displayed = 1 %]
+ [% END %]
+
+ [% PROCESS flag_row first_cell_empty = 0 addl_text = "addl." %]
+ [% END %]
+</table>
+
+[% ELSE %]
+ [%# The user is logged out. Display flags as read-only. %]
+ [% header_displayed = 0 %]
+ [% FOREACH type = flag_types %]
+ [% FOREACH flag = type.flags %]
+ [% IF !flag_no_header AND !header_displayed %]
+ <p><b>Flags:</b></p>
+ [% header_displayed = 1 %]
+ [% END %]
+ [% IF flag.setter.name %]
+ <span title="[% flag.setter.name FILTER html %]">[% flag.setter.nick FILTER html %]</span>:
+ [% ELSE %]
+ [% flag.setter.nick FILTER html %]:
+ [% END %]
+ [%+ type.name FILTER html FILTER no_break %][% flag.status %]
+ [% IF flag.requestee %]
+ [% IF flag.requestee.name %]
+ (<span title="[% flag.requestee.name FILTER html %]">[% flag.requestee.nick FILTER html %]</span>)
+ [% ELSE %]
+ ([% flag.requestee.nick FILTER html %])
+ [% END %]
+ [% END %]<br>
+ [% END %]
+ [% END %]
+[% END %]
+
+[%# Display a table row for unset flags %]
+
+[% BLOCK flag_row %]
+ <tr>
+ [% IF first_cell_empty %]
+ <td>&nbsp;</td>
+ <td>
+ [% ELSE %]
+ <td colspan="2">
+ [% END %]
+
+ [% addl_text FILTER html %]
+ <label title="[% type.description FILTER html %]" for="flag_type-[% type.id %]">
+ [%- type.name FILTER html FILTER no_break %]</label>
+ </td>
+ <td>
+ <select id="flag_type-[% type.id %]" name="flag_type-[% type.id %]"
+ title="[% type.description FILTER html %]"
+ [% " disabled=\"disabled\"" UNLESS (type.is_requestable && user.can_request_flag(type)) || user.can_set_flag(type) %]
+ onchange="toggleRequesteeField(this);"
+ class="flag_select flag_type-[% type.id %]">
+ <option value="X"></option>
+ [% IF type.is_requestable && user.can_request_flag(type) %]
+ <option value="?">?</option>
+ [% END %]
+ [% IF user.can_set_flag(type) %]
+ <option value="+">+</option>
+ <option value="-">-</option>
+ [% END %]
+ </select>
+ </td>
+ [% IF any_flags_requesteeble %]
+ <td>
+ [% IF type.is_requestable && type.is_requesteeble %]
+ <span style="white-space: nowrap;">
+ [% SET grant_list = [] %]
+ [% IF Param('usemenuforusers') %]
+ [% grant_list = type.grant_list %]
+ [% END %]
+ [% INCLUDE global/userselect.html.tmpl
+ name => "requestee_type-$type.id"
+ id => "requestee_type-$type.id"
+ multiple => type.is_multiplicable * 3
+ emptyok => !type.is_multiplicable
+ value => ""
+ custom_userlist => grant_list
+ classes => ["requestee"]
+ %]
+
+ </span>
+ [% END %]
+ </td>
+ [% END %]
+ </tr>
+[% END %]
diff --git a/template/en/default/global/banner.html.tmpl b/template/en/default/global/banner.html.tmpl
new file mode 100644
index 000000000..ab1c2a820
--- /dev/null
+++ b/template/en/default/global/banner.html.tmpl
@@ -0,0 +1,26 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ # Matthew Tuck <matty@chariot.net.au>
+ # Vitaly Harisov <vitaly@rathedg.com>
+ #%]
+
+[%# Migration note: this file corresponds to the old Param 'bannerhtml' %]
+
+ <div id="banner">
+ </div>
diff --git a/template/en/default/global/choose-classification.html.tmpl b/template/en/default/global/choose-classification.html.tmpl
new file mode 100644
index 000000000..9342d814a
--- /dev/null
+++ b/template/en/default/global/choose-classification.html.tmpl
@@ -0,0 +1,63 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Albert Ting
+ #
+ # Contributor(s): Albert Ting <alt@sonic.net>
+ #%]
+
+[%# INTERFACE:
+ # classifications: an array of classification objects containing
+ # at least one product accessible by the user.
+ #%]
+
+[% IF target == "enter_bug.cgi" %]
+ [% title = "Select Classification" %]
+ [% subheader = "Please select the classification." %]
+[% END %]
+
+[% DEFAULT title = "Choose the classification" %]
+[% PROCESS global/header.html.tmpl %]
+
+<table>
+ <tr>
+ <th align="right">
+ <a href="[% target FILTER url_quote %]?classification=__all
+ [% IF cloned_bug_id %]&amp;cloned_bug_id=[% cloned_bug_id FILTER url_quote %][% END -%]
+ [%- IF format %]&amp;format=[% format FILTER url_quote %][% END %]">
+ All</a>:
+ </th>
+
+ <td valign="top">&nbsp;Show all products</td>
+ </tr>
+ <tr>
+ <th colspan="2">&nbsp;</th>
+ </tr>
+
+[% FOREACH class = classifications %]
+ <tr>
+ <th align="right">
+ <a href="[% target FILTER url_quote %]?classification=[% class.name FILTER url_quote -%]
+ [%- IF cloned_bug_id %]&amp;cloned_bug_id=[% cloned_bug_id FILTER url_quote %][% END -%]
+ [%- IF format %]&amp;format=[% format FILTER url_quote %][% END %]">
+ [% class.name FILTER html %]</a>:
+ </th>
+
+ [% IF class.description %]
+ <td valign="top">&nbsp;[% class.description FILTER html_light %]</td>
+ [% END %]
+ </tr>
+[% END %]
+
+</table>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/global/choose-product.html.tmpl b/template/en/default/global/choose-product.html.tmpl
new file mode 100644
index 000000000..5c9ad0916
--- /dev/null
+++ b/template/en/default/global/choose-product.html.tmpl
@@ -0,0 +1,75 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ #%]
+
+[%# INTERFACE:
+ # classifications: array of hashes, with an 'object' key representing a
+ # classification object and 'products' the list of
+ # product objects the user can enter bugs into.
+ # target: the script that displays this template.
+ # cloned_bug_id: ID of the bug being cloned.
+ # format: the desired format to display the target.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% IF target == "enter_bug.cgi" %]
+ [% title = "Enter $terms.Bug" %]
+ [% h2 = BLOCK %]First, you must pick a product on which to enter [% terms.abug %]: [% END %]
+[% ELSIF target == "describecomponents.cgi" %]
+ [% title = "Browse" %]
+ [% h2 = "Select a product category to browse:" %]
+[% END %]
+
+[% DEFAULT title = "Choose a Product" %]
+[% PROCESS global/header.html.tmpl %]
+
+<h2>[% h2 FILTER html %]</h2>
+
+<table>
+
+[% FOREACH c = classifications %]
+ [% IF c.object %]
+ <tr>
+ <th colspan="2" align="left">[% c.object.name FILTER html %]:
+ [%+ c.object.description FILTER html_light %]</th>
+ </tr>
+ [% END %]
+
+ [% FOREACH p = c.products %]
+ <tr>
+ <th align="right" valign="top">
+ <a href="[% target %]?product=[% p.name FILTER url_quote -%]
+ [%- IF cloned_bug_id %]&amp;cloned_bug_id=[% cloned_bug_id FILTER url_quote %][% END -%]
+ [%- IF format %]&amp;format=[% format FILTER url_quote %][% END %]">
+ [% p.name FILTER html FILTER no_break %]</a>:&nbsp;
+ </th>
+
+ <td valign="top">[% p.description FILTER html_light %]</td>
+ </tr>
+ [% END %]
+
+ <tr>
+ <th colspan="2">&nbsp;</th>
+ </tr>
+[% END %]
+
+</table>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/global/code-error.html.tmpl b/template/en/default/global/code-error.html.tmpl
new file mode 100644
index 000000000..188b91a95
--- /dev/null
+++ b/template/en/default/global/code-error.html.tmpl
@@ -0,0 +1,547 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ #%]
+
+[%# INTERFACE:
+ # header_done: boolean. True if the header has already been printed.
+ # error: string. The tag of the error.
+ # variables: hash. Useful data about the problem. The keys are the variable
+ # names, and the values the variable values.
+ #%]
+
+[%# This is a list of all the possible code errors. Please keep them in
+ # alphabetical order by error tag, and leave a blank line between errors.
+ #
+ # Note that you must explicitly filter every single template variable
+ # in this file; if you do not wish to change it, use the "none" filter.
+ #%]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+[% DEFAULT title = "Internal Error" %]
+
+[% error_message = BLOCK %]
+ [% IF error == "attachment_local_storage_disabled" %]
+ [% title = "Local Storage Disabled" %]
+ You cannot store attachments locally. This feature is disabled.
+
+ [% ELSIF error == "attachment_url_disabled" %]
+ [% title = "Attachment URL Disabled" %]
+ You cannot attach a URL. This feature is currently disabled.
+
+ [% ELSIF error == "auth_invalid_email" %]
+ [% title = "Invalid Email Address" %]
+ We received an email address (<b>[% addr FILTER html %]</b>)
+ that didn't pass our syntax checking for a legal email address,
+ when trying to create or update your account.
+ [% IF default %]
+ A legal address must contain exactly one '@',
+ and at least one '.' after the @.
+ [% ELSE %]
+ [%+ Param('emailregexpdesc') %]
+ [% END %]
+ It must also not contain any of these special characters:
+ <tt>\ ( ) &amp; &lt; &gt; , ; : &quot; [ ]</tt>, or any whitespace.
+
+ [% ELSIF error == "authres_unhandled" %]
+ The result value of [% value FILTER html %] was not handled by
+ the login code.
+
+ [% ELSIF error == "bad_page_cgi_id" %]
+ [% title = "Invalid Page ID" %]
+ The ID <code>[% page_id FILTER html %]</code> is not a
+ valid page identifier.
+
+ [% ELSIF error == "bad_arg" %]
+ Bad argument <code>[% argument FILTER html %]</code> sent to
+ <code>[% function FILTER html %]</code> function.
+
+ [% ELSIF error == "bug_error" %]
+ Trying to retrieve [% terms.bug %] [%+ bug.bug_id FILTER html %] returned
+ the error [% bug.error FILTER html %].
+
+ [% ELSIF error == "chart_data_not_generated" %]
+ [% admindocslinks = {'extraconfig.html' => 'Setting up Charting'} %]
+ [% IF product %]
+ Charts for the <em>[% product FILTER html %]</em> product are not
+ available yet because no charting data has been collected for it since it
+ was created.
+ [% ELSE %]
+ No charting data has been collected yet.
+ [% END %]
+ Please wait a day and try again.
+ If you're seeing this message after a day, then you should contact
+ <a href="mailto:[% Param('maintainer') %]">[% Param('maintainer') %]</a>
+ and reference this error.
+
+ [% ELSIF error == "chart_datafile_corrupt" %]
+ The chart data file [% file FILTER html %] is corrupt.
+
+ [% ELSIF error == "chart_dir_nonexistent" %]
+ One of the directories <tt>[% dir FILTER html %]</tt> and
+ <tt>[% graph_dir FILTER html %]</tt> does not exist.
+
+ [% ELSIF error == "chart_file_open_fail" %]
+ Unable to open the chart datafile <tt>[% filename FILTER html %]</tt>.
+
+ [% ELSIF error == "column_not_null_without_default" %]
+ Failed adding the column [% name FILTER html %]:
+ You cannot add a NOT NULL column with no default to an existing table
+ unless you specify something for the <code>$init_value</code> argument.
+
+ [% ELSIF error == "column_not_null_no_default_alter" %]
+ You cannot alter the [% name FILTER html %] column to be NOT NULL
+ without specifying a default or something for $set_nulls_to, because
+ there are NULL values currently in it.
+
+ [% ELSIF error == "comment_extra_data_not_allowed" %]
+ You tried to set the <code>extra_data</code> field to
+ '[% extra_data FILTER html %]' but comments of type [% type FILTER html %]
+ do not accept an <code>extra_data</code> argument.
+
+ [% ELSIF error == "comment_extra_data_required" %]
+ Comments of type [% type FILTER html %] require an <code>extra_data</code>
+ argument to be set.
+
+ [% ELSIF error == "comment_extra_data_not_numeric" %]
+ You tried to set the <code>extra_data</code> field to
+ '[% extra_data FILTER html %]' but comments of type [% type FILTER html %]
+ require a numeric <code>extra_data</code> argument.
+
+ [% ELSIF error == "comment_type_invalid" %]
+ '[% type FILTER html %]' is not a valid comment type.
+
+ [% ELSIF error == "db_rename_conflict" %]
+ Name conflict: Cannot rename [% old FILTER html %] to
+ [% new FILTER html %] because [% new FILTER html %] already exists.
+
+ [% ELSIF error == "cookies_need_value" %]
+ Every cookie must have a value.
+
+ [% ELSIF error == "env_no_email" %]
+ [% terms.Bugzilla %] did not receive an email address from the
+ environment.
+ [% IF Param("auth_env_email") %]
+ This means that the '[% Param("auth_env_email") FILTER html %]'
+ environment variable was empty or did not exist.
+ [% ELSE %]
+ You need to set the "auth_env_email" parameter to the name of
+ the environment variable that will contain the user's email
+ address.
+ [% END %]
+
+ [% ELSIF error == "extension_disabled" %]
+ [% title = "Extension Disabled" %]
+ You cannot access this page because the extension '[% name FILTER html %]'
+ is disabled.
+
+ [% ELSIF error == "extension_must_be_subclass" %]
+ <code>[% package FILTER html %]</code> from
+ <code>[% filename FILTER html %]</code> is not a subclass of
+ <code>[% class FILTER html %]</code>.
+
+ [% ELSIF error == "extension_must_return_name" %]
+ <code>[% extension FILTER html %]</code> returned
+ <code>[% returned FILTER html %]</code>, 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
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/Extension.html">Bugzilla::Extension</a>
+ for details.
+
+ [% ELSIF error == "extension_no_name" %]
+ We did not find a <code>NAME</code> method in
+ <code>[% package FILTER html %]</code> (loaded from
+ <code>[% filename FILTER html %]</code>). This means that
+ the extension has one or more of the following problems:
+
+ <ul>
+ <li><code>[% filename FILTER html %]</code> did not define a
+ <code>[% package FILTER html %]</code> package.</li>
+ <li><code>[% package FILTER html %]</code> did not define a
+ <code>NAME</code> method (or the <code>NAME</code> method
+ returned an empty string).</li>
+ </ul>
+
+ [% ELSIF error == "extern_id_conflict" %]
+ The external ID '[% extern_id FILTER html %]' already exists
+ in the database for '[% username FILTER html %]', but your
+ account source says that '[% extern_user FILTER html %]' has that ID.
+
+ [% ELSIF error == "field_choice_must_use_type" %]
+ When you call a class method on <code>Bugzilla::Field::Choice</code>,
+ you must call <code>Bugzilla::Field::Choice-&gt;type('some_field')</code>
+ to generate the right class (you can't call class methods directly
+ on Bugzilla::Field::Choice).
+
+ [% ELSIF error == "field_not_custom" %]
+ '[% field.description FILTER html %]' ([% field.name FILTER html %])
+ is not a custom field.
+
+ [% ELSIF error == "field_type_mismatch" %]
+ Cannot seem to handle <code>[% field FILTER html %]</code>
+ and <code>[% type FILTER html %]</code> together.
+
+ [% ELSIF error == "field_type_not_specified" %]
+ [% title = "Field Type Not Specified" %]
+ You must specify a type when creating a custom field.
+
+ [% ELSIF error == "illegal_content_type_method" %]
+ Your form submission got corrupted somehow. The <em>content
+ method</em> field, which specifies how the content type gets determined,
+ should have been either <em>autodetect</em>, <em>list</em>,
+ or <em>manual</em>, but was instead
+ <em>[% contenttypemethod FILTER html %]</em>.
+
+ [% ELSIF error == "illegal_field" %]
+ A legal [% field FILTER html %] was not set.
+
+ [% ELSIF error == "invalid_attach_id_to_obsolete" %]
+ The attachment number of one of the attachments you wanted to obsolete,
+ [% attach_id FILTER html %], is invalid.
+
+ [% ELSIF error == "invalid_customfield_type" %]
+ [% title = "Invalid Field Type" %]
+ The type <em>[% type FILTER html %]</em> is not a valid field type.
+
+ [% ELSIF error == "invalid_dimensions" %]
+ [% title = "Invalid Dimensions" %]
+ The width or height specified is not a positive integer.
+
+ [% ELSIF error == "invalid_feature" %]
+ [% title = "Invalid Feature Name" %]
+ [% feature FILTER html %] is not a valid feature name. See
+ <code>OPTIONAL_MODULES</code> in
+ <code>Bugzilla::Install::Requirements</code> for valid names.
+
+ [% ELSIF error == "invalid_flag_association" %]
+ [% title = "Invalid Flag Association" %]
+ Some flags do not belong to
+ [% IF attach_id %]
+ attachment [% attach_id FILTER html %].
+ [% ELSE %]
+ [%+ terms.bug %] [%+ bug_id FILTER html %].
+ [% END %]
+
+ [% ELSIF error == "invalid_series_id" %]
+ [% title = "Invalid Series" %]
+ The series_id [% series_id FILTER html %] is not valid. It may be that
+ this series has been deleted.
+
+ [% ELSIF error == "invalid_webservergroup" %]
+ There is no such group: [% group FILTER html %]. Check your $webservergroup
+ setting in [% constants.bz_locations.localconfig FILTER html %].
+
+ [% ELSIF error == "mismatched_bug_ids_on_obsolete" %]
+ Attachment [% attach_id FILTER html %] ([% description FILTER html %])
+ is attached to [% terms.bug %] [%+ attach_bug_id FILTER html %],
+ but you tried to flag it as obsolete while creating a new attachment to
+ [% terms.bug %] [%+ my_bug_id FILTER html %].
+
+ [% ELSIF error == "feature_disabled" %]
+ The [% install_string("feature_$feature") FILTER html %] feature is not
+ available in this [% terms.Bugzilla %].
+ [% IF user.in_group('admin') %]
+ If you would like to enable this feature, please run
+ <kbd>checksetup.pl</kbd> to see how to install the necessary
+ requirements for this feature.
+ [% END %]
+
+ [% ELSIF error == "flag_unexpected_object" %]
+ [% title = "Object Not Recognized" %]
+ Flags cannot be set for objects of type [% caller FILTER html %].
+ They can only be set for [% terms.bugs %] and attachments.
+
+ [% ELSIF error == "flag_requestee_disabled" %]
+ [% title = "Flag not Requestable from Specific Person" %]
+ You can't ask a specific person for
+ <em>[% type.name FILTER html %]</em>.
+
+ [% ELSIF error == "flag_type_inactive" %]
+ [% title = "Inactive Flag Type" %]
+ The flag type [% type FILTER html %] is inactive and cannot be used
+ to create new flags.
+
+ [% ELSIF error == "flag_type_nonexistent" %]
+ There is no flag type with the ID <em>[% id FILTER html %]</em>.
+
+ [% ELSIF error == "flag_type_target_type_invalid" %]
+ The target type was neither <em>[% terms.bug %]</em> nor <em>attachment</em>
+ but rather <em>[% target_type FILTER html %]</em>.
+
+ [% ELSIF error == "invalid_field_name" %]
+ Can't use [% field FILTER html %] as a field name.
+
+ [% ELSIF error == "invalid_keyword_id" %]
+ The keyword ID <em>[% id FILTER html %]</em> couldn't be
+ found.
+
+ [% ELSIF error == "invalid_user" %]
+ [% title = "Invalid User" %]
+ There is no user account
+ [% IF user_id %]
+ with ID <em>[% user_id FILTER html %]</em>.
+ [% ELSIF user_login %]
+ with login name <em>[% user_login FILTER html %]</em>.
+ [% ELSE %]
+ given.
+ [% END %]
+
+ [% ELSIF error == "jobqueue_insert_failed" %]
+ [% title = "Job Queue Failure" %]
+ Inserting a <code>[% job FILTER html %]</code> job into the Job
+ Queue failed with the following error: [% errmsg FILTER html %]
+
+ [% ELSIF error == "jobqueue_no_job_mapping" %]
+ <code>Bugzilla::JobQueue</code> has not been configured to handle
+ the job "[% job FILTER html %]". You need to add this job type
+ to the <code>JOB_MAP</code> constant in <code>Bugzilla::JobQueue</code>.
+
+ [% ELSIF error == "ldap_bind_failed" %]
+ Failed to bind to the LDAP server. The error message was:
+ <code>[% errstr FILTER html %]</code>
+
+ [% ELSIF error == "ldap_cannot_retreive_attr" %]
+ The specified LDAP attribute [% attr FILTER html %] was not found.
+
+ [% ELSIF error == "ldap_connect_failed" %]
+ Could not connect to the LDAP server(s) <code>[% server FILTER html %]</code>.
+
+ [% ELSIF error == "ldap_start_tls_failed" %]
+ Could not start TLS with LDAP server: <code>[% error FILTER html %]</code>.
+
+ [% ELSIF error == "ldap_search_error" %]
+ An error occurred while trying to search LDAP for
+ &quot;[% username FILTER html %]&quot;:
+ [% IF errstr %]
+ <code>[% errstr FILTER html %]</code>
+ [% ELSE %]
+ Unable to find user in LDAP
+ [% END %]
+
+ [% ELSIF error == "ldap_server_not_defined" %]
+ The LDAP server for authentication has not been defined.
+
+ [% ELSIF error == "mail_send_error" %]
+ There was an error sending mail from '[% mail.header('From') FILTER html %]'
+ to '[% mail.header('To') FILTER html %]':
+ [% msg FILTER html %]
+
+ [% ELSIF error == "missing_bug_id" %]
+ No [% terms.bug %] ID was given.
+
+ [% ELSIF error == "missing_series_id" %]
+ Having inserted a series into the database, no series_id was returned for
+ it. Series: [% series.category FILTER html %] /
+ [%+ series.subcategory FILTER html %] /
+ [%+ series.name FILTER html %].
+
+ [% ELSIF error == "need_quipid" %]
+ A valid quipid is needed.
+
+ [% ELSIF error == "object_dep_sort_loop" %]
+ There is a loop in VALIDATOR_DEPENDENCIES involving
+ '[%+ field FILTER html %]'. Here are the fields we considered:
+ [%+ considered.join(', ') FILTER html %].
+
+ [% ELSIF error == "param_invalid" %]
+ [% title = "Invalid Parameter" %]
+ <code>[% param FILTER html %]</code> is not a valid parameter
+ for the [% function FILTER html %] function.
+
+ [% ELSIF error == "param_must_be_numeric" %]
+ [% title = "Invalid Parameter" %]
+ Invalid parameter <code>[% param FILTER html %]</code> passed to
+ <code>[% function FILTER html %]</code>: It must be numeric.
+
+ [% ELSIF error == "param_required" %]
+ [% title = "Missing Parameter" %]
+ The function <code>[% function FILTER html %]</code> requires
+ a <code>[% param FILTER html %]</code> argument, and that
+ argument was not set.
+
+ [% ELSIF error == "params_required" %]
+ [% title = "Missing Parameter" %]
+ The function <code>[% function FILTER html %]</code> requires
+ that you set one of the following parameters:
+ <code>[% params.join(', ') FILTER html %]</code>
+
+ [% ELSIF error == "product_empty_group_controls" %]
+ [% title = "Missing Group Controls" %]
+ New settings must be defined to edit group controls for
+ the [% group.name FILTER html %] group.
+
+ [% ELSIF error == "product_illegal_group_control" %]
+ [% title = "Illegal Group Control" %]
+ '[% value FILTER html %]' is not a legal value for
+ the '[% field FILTER html %]' field.
+
+ [% ELSIF error == "protection_violation" %]
+ The function <code>[% function FILTER html %]</code> was called
+
+ [% IF argument %]
+ with the argument <code>[% argument FILTER html %]</code>
+ [% END %]
+
+ from
+
+ [% IF caller %]
+ <code>[%+ caller FILTER html %]</code>, which is
+ [% END %]
+
+ outside the package. This function may only be called from
+ a subclass of <code>[% superclass FILTER html %]</code>.
+
+ [% ELSIF error == "radius_preparation_error" %]
+ An error occurred while preparing for a RADIUS authentication request:
+ <code>[% errstr FILTER html %]</code>.
+
+ [% ELSIF error == "report_axis_invalid" %]
+ <em>[% val FILTER html %]</em> is not a valid value for
+ [%+ IF fld == "x" %]the horizontal axis
+ [%+ ELSIF fld == "y" %]the vertical axis
+ [%+ ELSIF fld == "z" %]the multiple tables/images
+ [%+ ELSE %]a report axis[% END %] field.
+
+ [% ELSIF error == "setting_info_invalid" %]
+ To create a new setting, you must supply a setting name, a list of
+ value/sortindex pairs, and the default value.
+
+ [% ELSIF error == "setting_name_invalid" %]
+ The setting name <em>[% name FILTER html %]</em> is not a valid
+ option. Setting names must begin with a letter, and contain only
+ letters, digits, or the symbols '_', '-', '.', or ':'.
+
+ [% ELSIF error == "setting_subclass_invalid" %]
+ There is no such Setting subclass as
+ <code>[% subclass FILTER html %]</code>.
+
+ [% ELSIF error == "setting_value_invalid" %]
+ The value "<code>[% value FILTER html %]</code>" is not in the list of
+ legal values for the <em>[% name FILTER html %]</em> setting.
+
+ [% ELSIF error == "token_generation_error" %]
+ Something is seriously wrong with the token generation system.
+
+ [% ELSIF error == "template_error" %]
+ [% template_error_msg FILTER html %]
+
+ [% ELSIF error == "template_invalid" %]
+ Template with invalid file name found in hook call: [% name FILTER html %].
+
+ [% ELSIF error == "unable_to_retrieve_password" %]
+ I was unable to retrieve your old password from the database.
+
+ [% ELSIF error == "undefined_field" %]
+ Form field [% field FILTER html %] was not defined.
+
+ [% ELSIF error == "unknown_method" %]
+ The requested method '[% method FILTER html %]' was not found.
+
+ [% ELSIF error == "usage_mode_invalid" %]
+ '[% invalid_usage_mode FILTER html %]' is not a valid usage mode.
+
+ [% ELSIF error == "must_be_patch" %]
+ [% title = "Attachment Must Be Patch" %]
+ Attachment #[% attach_id FILTER html %] must be a patch.
+
+ [% ELSIF error == "not_in_transaction" %]
+ Attempted to end transaction without starting one first.
+
+ [% ELSIF error == "comma_operator_deprecated" %]
+ [% title = "SQL query generator internal error" %]
+ There is an internal error in the SQL query generation code,
+ creating queries with implicit JOIN.
+
+ [% ELSIF error == "invalid_post_bug_submit_action" %]
+ Invalid setting for post_bug_submit_action
+
+ [% ELSE %]
+ [%# Try to find hooked error messages %]
+ [% error_message = Hook.process("errors") %]
+ [% IF NOT error_message %]
+ [% title = "Internal error" %]
+ An internal error has occurred, but [% terms.Bugzilla %] doesn't know
+ what <code>[% error FILTER html %]</code> means.
+
+ If you are a [% terms.Bugzilla %] end-user seeing this message, please save
+ this page and send it to [% Param('maintainer') %].
+ [% ELSE %]
+ [% error_message FILTER none %]
+ [% END %]
+ [% END %]
+[% END %]
+
+[%# We only want HTML error messages for ERROR_MODE_WEBPAGE %]
+[% USE Bugzilla %]
+[% IF Bugzilla.error_mode != constants.ERROR_MODE_WEBPAGE %]
+ [% IF Bugzilla.usage_mode == constants.USAGE_MODE_BROWSER %]
+ [% error_message FILTER none %]
+ [% ELSE %]
+ [% error_message FILTER txt %]
+ [% END %]
+ [% RETURN %]
+[% END %]
+
+[% UNLESS header_done %]
+ [% PROCESS global/header.html.tmpl %]
+[% END %]
+
+[% PROCESS global/docslinks.html.tmpl
+ docslinks = docslinks
+ admindocslinks = admindocslinks
+%]
+
+<tt>
+ <p>
+ [% terms.Bugzilla %] has suffered an internal error. Please save this page and send
+ it to [% Param("maintainer") %] with details of what you were doing at
+ the time this message appeared.
+ </p>
+ <script type="text/javascript"> <!--
+ document.write("<p>URL: " +
+ document.location.href.replace(/&/g,"&amp;")
+ .replace(/</g,"&lt;")
+ .replace(/>/g,"&gt;") + "</p>");
+ // -->
+ </script>
+</tt>
+
+<table cellpadding="20">
+ <tr>
+ <td bgcolor="#ff0000">
+ <font size="+2">
+ [% error_message FILTER none %]
+ </font>
+ </td>
+ </tr>
+</table>
+
+<p>Traceback:</p>
+<pre>[% traceback FILTER html_linebreak %]</pre>
+
+[% IF variables %]
+ <pre>
+Variables:
+ [% FOREACH key = variables.keys %]
+ [%+ key FILTER html %]: [%+ variables.$key FILTER html %]
+ [% END %]
+ </pre>
+[% END %]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/global/common-links.html.tmpl b/template/en/default/global/common-links.html.tmpl
new file mode 100644
index 000000000..fe665df67
--- /dev/null
+++ b/template/en/default/global/common-links.html.tmpl
@@ -0,0 +1,117 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ # Svetlana Harisova <light@rathedg.com>
+ #%]
+
+[% DEFAULT qs_suffix = "" %]
+[% USE Bugzilla %]
+
+<ul class="links">
+ <li><a href="./">Home</a></li>
+ <li><span class="separator">| </span><a href="enter_bug.cgi">New</a></li>
+ <li><span class="separator">| </span><a href="describecomponents.cgi">Browse</a></li>
+ <li><span class="separator">| </span><a href="query.cgi">Search</a></li>
+
+ <li class="form">
+ <span class="separator">| </span>
+ <form action="buglist.cgi" method="get"
+ onsubmit="if (this.quicksearch.value == '')
+ { alert('Please enter one or more search terms first.');
+ return false; } return true;">
+ <input class="txt" type="text" id="quicksearch[% qs_suffix FILTER html %]" name="quicksearch"
+ value="[% quicksearch FILTER html %]">
+ <input class="btn" type="submit" value="Search"
+ id="find[% qs_suffix FILTER html %]">
+ [%-# Work around FF bug: keep this on one line %]</form>
+ <a href="page.cgi?id=quicksearch.html" title="Quicksearch Help">[?]</a></li>
+
+ <li><span class="separator">| </span><a href="report.cgi">Reports</a></li>
+
+ <li>
+ [% IF Param('shutdownhtml') || Bugzilla.has_flags %]
+ <span class="separator">| </span>
+ [% IF user.id %]
+ <a href="request.cgi?requester=[% user.login FILTER url_quote %]&amp;requestee=
+ [% user.login FILTER url_quote %]&amp;do_union=1&amp;group=type&amp;action=queue">My Requests</a>
+ [% ELSE %]
+ <a href="request.cgi">Requests</a>
+ [% END %]
+ [% END %]
+ [%-# Work around FF bug: keep this on one line %]</li>
+
+ [% IF user.login %]
+ <li><span class="separator">| </span><a href="userprefs.cgi">Preferences</a></li>
+ [% IF user.in_group('tweakparams') || user.in_group('editusers') || user.can_bless
+ || (Param('useclassification') && user.in_group('editclassifications'))
+ || user.in_group('editcomponents') || user.in_group('admin') || user.in_group('creategroups')
+ || user.in_group('editkeywords') || user.in_group('bz_canusewhines')
+ || user.get_products_by_permission("editcomponents").size %]
+ <li><span class="separator">| </span><a href="admin.cgi">Administration</a></li>
+ [% END %]
+
+ [% PROCESS link_to_documentation %]
+
+ <li>
+ <span class="separator">| </span>
+ [% IF user.authorizer.can_logout %]
+ <a href="index.cgi?logout=1">Log&nbsp;out</a>
+ [% ELSE %]
+ Logged&nbsp;in&nbsp;as
+ [% END %]
+ [% IF sudoer %]
+ [%+ sudoer.login FILTER html %] (<b>impersonating
+ [%+ user.login FILTER html %]</b>
+ <a href="relogin.cgi?action=end-sudo">end session</a>)
+ [% ELSE %]
+ [%+ user.login FILTER html %]
+ [% END %]
+ [%-# Work around FF bug: keep this on one line %]</li>
+ [% ELSE %]
+
+ [% PROCESS link_to_documentation %]
+
+ [% IF Param('createemailregexp')
+ && user.authorizer.user_can_create_account %]
+ <li id="new_account_container[% qs_suffix FILTER html %]">
+ <span class="separator">| </span>
+ <a href="createaccount.cgi">New&nbsp;Account</a>
+ </li>
+ [% END %]
+
+ [%# Only display one login form when we're on a LOGIN_REQUIRED page. That
+ # way, we're guaranteed that the user will use the form that has
+ # hidden_fields in it (the center form) instead of this one. Also, it's
+ # less confusing to have one form (as opposed to three) when you're
+ # required to log in.
+ #%]
+ [% IF user.authorizer.can_login && !Bugzilla.page_requires_login %]
+ [% PROCESS "account/auth/login-small.html.tmpl" %]
+ [% END %]
+ [% END %]
+</ul>
+
+[% Hook.process("link-row") %]
+[% BLOCK link_to_documentation %]
+ [% IF doc_section && Param('docs_urlbase') %]
+ <li>
+ <span class="separator">| </span>
+ <a href="[% docs_urlbase _ doc_section FILTER html %]" target="_blank">Help</a>
+ </li>
+ [% END %]
+[% END %]
diff --git a/template/en/default/global/confirm-action.html.tmpl b/template/en/default/global/confirm-action.html.tmpl
new file mode 100644
index 000000000..9f9be31db
--- /dev/null
+++ b/template/en/default/global/confirm-action.html.tmpl
@@ -0,0 +1,64 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Frédéric Buclin.
+ # Portions created by Frédéric Buclin are Copyright (C) 2008
+ # Frédéric Buclin. All Rights Reserved.
+ #
+ # Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+
+[%# INTERFACE:
+ # script_name: the script generating this warning.
+ # token: a valid token for the current action.
+ # reason: reason of the failure.
+ #%]
+
+[% PROCESS global/header.html.tmpl title = "Suspicious Action"
+ style_urls = ['skins/standard/global.css'] %]
+
+<div class="throw_error">
+<!--reason=[%reason FILTER html %]-->
+ [% IF reason == "expired_token" %]
+ Your changes have been rejected because you exceeded the time limit
+ of [% constants.MAX_TOKEN_AGE FILTER html %] days before submitting your
+ changes to [% script_name FILTER html %]. Your page may have been displayed
+ for too long, or old changes have been resubmitted by accident.
+
+ [% ELSIF reason == "missing_token" %]
+ It looks like you didn't come from the right page.
+ One reason could be that you entered the URL in the address bar of your
+ web browser directly, which should be safe. Another reason could be that
+ you clicked on a URL which redirected you here <b>without your consent</b>.
+
+ [% ELSIF reason == "invalid_token" %]
+ You submitted changes to [% script_name FILTER html %] with an invalid
+ token, which may indicate that someone tried to abuse you, for instance
+ by making you click on a URL which redirected you here <b>without your
+ consent</b>.
+ [% END %]
+ <p>
+ Are you sure you want to commit these changes?
+ </p>
+</div>
+
+<form name="check" id="check" method="post" action="[% script_name FILTER html %]">
+ [% PROCESS "global/hidden-fields.html.tmpl"
+ exclude="^(Bugzilla_login|Bugzilla_password|token)$" %]
+ <input type="hidden" name="token" value="[% token FILTER html %]">
+ <input type="submit" id="confirm" value="Yes, Confirm Changes">
+</form>
+
+<p><a href="index.cgi">No, throw away these changes</a> (you will be redirected
+to the home page).</p>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/global/confirm-user-match.html.tmpl b/template/en/default/global/confirm-user-match.html.tmpl
new file mode 100644
index 000000000..5549b516d
--- /dev/null
+++ b/template/en/default/global/confirm-user-match.html.tmpl
@@ -0,0 +1,207 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Myk Melez <myk@mozilla.org>
+ # Erik Stambaugh <not_erik@dasbistro.com>
+ #%]
+
+[%# INTERFACE:
+ # fields: hash/record; the fields being matched, each of which has:
+ # type: single|multi: whether or not the user can select multiple matches
+ # flag_type: for flag requestee fields, the type of flag being requested
+ # matches: hash; Hierarchical. The levels go like this:
+ # field_name {
+ # pattern_text {
+ # 'users' = @user_list (user objects)
+ # 'status' = success|fail|trunc (result of search.
+ # 'trunc' (truncated) means max was reached)
+ # }
+ # }
+ # script: string; The name of the calling script, used to create a
+ # self-referential URL
+ #%]
+
+[%# use the global field descs %]
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+[%# This lists fields which use the user auto-completion feature and which
+ # are not listed in field_descs. %]
+[% field_labels = { # Used by editcomponents.cgi
+ "initialcc" => "Default CC List",
+ "initialowner" => "Default Assignee",
+ "initialqacontact" => "Default QA Contact",
+ # Used by process_bug.cgi
+ "masscc" => "CC List",
+ # Used by request.cgi
+ "requester" => "Requester",
+ "requestee" => "Requestee",
+ # Used by userprefs.cgi
+ "new_watchedusers" => "Watch List",
+
+ }
+%]
+[% IF matchsuccess == 1 %]
+ [% PROCESS global/header.html.tmpl title="Confirm Match" %]
+
+ [% USE Bugzilla %]
+
+ <form method="post"
+ [% IF script -%]
+ action="[% script %]"
+ [%- END -%]
+ [% IF Bugzilla.cgi.param("data") %]
+ enctype="multipart/form-data"
+ [% END %]
+ >
+
+ <p>
+ [% IF matchmultiple %]
+ [% terms.Bugzilla %] cannot make a conclusive match for one or more
+ of the names and/or email addresses you entered on the previous page.
+ <br>Please examine the lists of potential matches below and select the
+ ones you want,
+ [% ELSE %]
+ [% terms.Bugzilla %] is configured to require verification whenever
+ you enter a name or partial email address.
+ <br>Below are the names/addresses you entered and the matched accounts.
+ Please confirm that they are correct,
+ [% END %]
+ or go back to the previous page to revise the names you entered.
+ </p>
+[% ELSE %]
+ [% PROCESS global/header.html.tmpl title="Match Failed" %]
+ <p>
+ [% terms.Bugzilla %] was unable to make any match at all for one or more of
+ the names and/or email addresses you entered on the previous page.
+ [% IF !user.id %]
+ <b>Note: You are currently logged out. Only exact matches against e-mail
+ addresses will be performed.</b>
+ [% END %]
+ </p>
+ <p>Please go back and try other names or email addresses.</p>
+[% END %]
+
+ <table border="0">
+ <tr>
+ <td colspan="2">
+ <hr width="100%" size="1">
+ </td>
+ </tr>
+
+ [%# this is messy to allow later expansion %]
+
+ [% FOREACH field = matches %]
+ <tr>
+ <td align="left" valign="top">
+ [% PROCESS field_names field_name=field.key %]:
+ </td>
+ <td align="left" valign="top">
+ [% FOREACH query = field.value %]
+ <div class="user_match">
+ <b>[% query.key FILTER html %]</b>
+ [% IF query.value.users.size %]
+ [% IF query.value.users.size > 1 %]
+ [% IF query.value.status == 'fail' %]
+ <font color="#FF0000">
+ matches multiple users.
+ </font>
+ Please go back and try again with a more specific
+ name/address.
+ [% ELSE %]
+ [% IF query.value.status == 'trunc' %]
+ matched
+ more than the maximum
+ of [% query.value.users.size %] users:<br>
+ [% ELSE %]
+ matched:<br>
+ [% END %]
+ <select name="[% field.key FILTER html %]"
+ id="[% field.key FILTER html %]"
+ [% IF fields.${field.key}.type == 'multi' %]
+ multiple="multiple"
+ [% IF query.value.users.size > 5 %]
+ size="5"
+ [% ELSE %]
+ size="[% query.value.users.size %]"
+ [% END %]
+ [% END %]
+ >
+ [% FOREACH match = query.value.users %]
+ <option value="[% match.login FILTER html %]">
+ [%- match.identity FILTER html -%]
+ </option>
+ [% END %]
+ </select>
+ [% END %]
+ [% ELSE %]
+ matched
+ <b>[% query.value.users.0.identity FILTER html %]</b>
+ <input type="hidden" name="[% field.key FILTER html %]"
+ value="[% query.value.users.0.login FILTER html %]">
+ [% END %]
+ [% ELSE %]
+ [% IF (query.key.length < 3) && !Param('emailsuffix') %]
+ <font color="#FF0000">was too short for substring match
+ (minimum 3 characters)</font>
+ [% ELSE %]
+ <font color="#FF0000">did not match anything</font>
+ [% END %]
+ [% END %]
+ </div>
+ [% END %]
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <hr width="100%" size="1">
+ </td>
+ </tr>
+ [% END %]
+
+ </table>
+
+[% IF matchsuccess == 1 %]
+
+ [% SET exclude_these =
+ matches.keys.merge(['Bugzilla_login', 'Bugzilla_password']) %]
+ [% SET exclude = '^' _ exclude_these.join('|') _ '$' %]
+ [% PROCESS "global/hidden-fields.html.tmpl" exclude = exclude %]
+
+ <p>
+ <input type="submit" id="continue" value="Continue">
+ </p>
+
+ </form>
+
+[% END %]
+
+[% PROCESS global/footer.html.tmpl %]
+
+
+[% BLOCK field_names %]
+
+ [% IF field_descs.$field_name %]
+ [% field_descs.$field_name FILTER html %]
+ [% ELSIF field_labels.$field_name %]
+ [% field_labels.$field_name FILTER html %]
+ [% ELSIF field_name.match("^requestee") %]
+ [% fields.${field_name}.flag_type.name %] requestee
+ [% ELSE %]
+ [% field_name FILTER html %]
+ [% END %]
+
+[% END %]
diff --git a/template/en/default/global/docslinks.html.tmpl b/template/en/default/global/docslinks.html.tmpl
new file mode 100644
index 000000000..712dfb45b
--- /dev/null
+++ b/template/en/default/global/docslinks.html.tmpl
@@ -0,0 +1,52 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Marc Schumann <wurblzap@gmail.com>
+ #%]
+
+[%# INTERFACE:
+ # docslinks: hash. Hash keys will be used as text of the documentation links,
+ # hash values will be used as links to the document, relative to
+ # the main Bugzilla documentation directory.
+ # Example: If you want a 'FAQ' link to point to, the "faq-general"
+ # named anchor on faq.html, assign
+ # { 'FAQ' => "faq.html#faq-general" }
+ # to docslinks.
+ # You may only link to sections by their given ID; it is not allowed
+ # to link to a section which is not given an ID (thus getting
+ # assigned an automatically generated ID). Otherwise, the link
+ # would break on a recompilation of the documentation.
+ # admindocslinks: hash. Same as docslinks, but will only be displayed to
+ # members of the admin group.
+ #%]
+
+[% IF Param('docs_urlbase') &&
+ docslinks.keys.size || (admindocslinks.keys.size && user.in_group('admin')) %]
+ <div id="docslinks">
+ <h2>Related documentation</h2>
+ <ul>
+ [% IF user.in_group('admin') %]
+ [% PROCESS docslinkslist docstype = admindocslinks %]
+ [% END %]
+ [% PROCESS docslinkslist docstype = docslinks %]
+ </ul>
+ </div>
+[% END %]
+
+[% BLOCK docslinkslist %]
+ [% FOREACH docslink = docstype.keys %]
+ <li>
+ <a href="[% docs_urlbase FILTER html %]
+ [% docslink FILTER none %]">[% docstype.$docslink FILTER html %]</a>
+ </li>
+ [% END %]
+[% END %]
diff --git a/template/en/default/global/field-descs.none.tmpl b/template/en/default/global/field-descs.none.tmpl
new file mode 100644
index 000000000..2ef558ce4
--- /dev/null
+++ b/template/en/default/global/field-descs.none.tmpl
@@ -0,0 +1,178 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ # Elliotte Martin <elliotte_martin@yahoo.com>
+ #%]
+
+[%# Remember to PROCESS rather than INCLUDE this template. %]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% SET search_descs = {
+ "noop" => "---",
+ "equals" => "is equal to",
+ "notequals" => "is not equal to",
+ "anyexact" => "is equal to any of the strings",
+ "substring" => "contains the string",
+ "casesubstring" => "contains the string (exact case)",
+ "notsubstring" => "does not contain the string",
+ "anywordssubstr" => "contains any of the strings",
+ "allwordssubstr" => "contains all of the strings",
+ "nowordssubstr" => "contains none of the strings",
+ "regexp" => "matches regular expression",
+ "notregexp" => "does not match regular expression",
+ "lessthan" => "is less than",
+ "lessthaneq" => "is less than or equal to",
+ "greaterthan" => "is greater than",
+ "greaterthaneq" => "is greater than or equal to",
+ "anywords" => "contains any of the words",
+ "allwords" => "contains all of the words",
+ "nowords" => "contains none of the words",
+ "changedbefore" => "changed before",
+ "changedafter" => "changed after",
+ "changedfrom" => "changed from",
+ "changedto" => "changed to",
+ "changedby" => "changed by",
+ "matches" => "matches",
+ "notmatches" => "does not match",
+} %]
+
+[% field_types = { ${constants.FIELD_TYPE_UNKNOWN} => "Unknown Type",
+ ${constants.FIELD_TYPE_FREETEXT} => "Free Text",
+ ${constants.FIELD_TYPE_SINGLE_SELECT} => "Drop Down",
+ ${constants.FIELD_TYPE_MULTI_SELECT} => "Multiple-Selection Box",
+ ${constants.FIELD_TYPE_TEXTAREA} => "Large Text Box",
+ ${constants.FIELD_TYPE_DATETIME} => "Date/Time",
+ ${constants.FIELD_TYPE_BUG_ID} => "$terms.Bug ID",
+ } %]
+
+[%# You can use this hash to localize (translate) the values displayed
+ # for drop-down and multiple-select fields. Lines starting with "#"
+ # are comments.
+ #%]
+[% value_descs = {
+ "bug_status" => {
+ # "UNCONFIRMED" => "UNCO",
+ # "CONFIRMED" => "ITSABUG",
+ },
+
+ "resolution" => {
+ "" => "---",
+ # "FIXED" => "NO LONGER AN ISSUE",
+ # "WORKSFORME" => "NOTMYPROBLEM!",
+ },
+} %]
+
+[%# We use "FILTER none" here because only the caller can know how to
+ # filter the result appropriately.
+ #%]
+[% MACRO display_value(field_name, value_name) BLOCK %][% FILTER trim %]
+ [% IF value_descs.${field_name}.${value_name}.defined %]
+ [% value_descs.${field_name}.${value_name} FILTER none %]
+ [% ELSE %]
+ [% value_name FILTER none %]
+ [% END %]
+[% END %][% END %]
+
+[% IF in_template_var %]
+ [% vars.terms = terms %]
+
+ [%# field_descs is loaded as a global template variable and cached
+ # across all templates--see VARIABLES in Bugzilla/Template.pm.
+ #%]
+ [% vars.field_descs = {
+ "[Bug creation]" => "[$terms.Bug creation]",
+ "actual_time" => "Actual Hours",
+ "alias" => "Alias",
+ "assigned_to" => "Assignee",
+ "assigned_to_realname" => "Assignee Real Name",
+ "attach_data.thedata" => "Attachment data",
+ "attachments.description" => "Attachment description",
+ "attachments.filename" => "Attachment filename",
+ "attachments.mimetype" => "Attachment mime type",
+ "attachments.ispatch" => "Attachment is patch",
+ "attachments.isobsolete" => "Attachment is obsolete",
+ "attachments.isprivate" => "Attachment is private",
+ "attachments.isurl" => "Attachment is a URL",
+ "attachments.submitter" => "Attachment creator",
+ "blocked" => "Blocks",
+ "bug_file_loc" => "URL",
+ "bug_group" => "Group",
+ "bug_id" => "$terms.Bug ID",
+ "bug_severity" => "Severity",
+ "bug_status" => "Status",
+ "changeddate" => "Changed",
+ "cc" => "CC",
+ "classification" => "Classification",
+ "cclist_accessible" => "CC list accessible",
+ "commenter" => "Commenter",
+ "component_id" => "Component ID",
+ "component" => "Component",
+ "content" => "Content",
+ "creation_ts" => "Creation date",
+ "deadline" => "Deadline",
+ "delta_ts" => "Changed",
+ "dependson" => "Depends on",
+ "dup_id" => "Duplicate",
+ "estimated_time" => "Orig. Est.",
+ "everconfirmed" => "Ever confirmed",
+ "flagtypes.name" => "Flags",
+ "keywords" => "Keywords",
+ "longdesc" => "Comment",
+ "longdescs.isprivate" => "Comment is private",
+ "newcc" => "CC",
+ "op_sys" => "OS",
+ "opendate" => "Opened",
+ "owner_idle_time" => "Time Since Assignee Touched",
+ "percentage_complete" => "%Complete",
+ "priority" => "Priority",
+ "product_id" => "Product ID",
+ "product" => "Product",
+ "qa_contact" => "QA Contact",
+ "qa_contact_realname" => "QA Contact Real Name",
+ "remaining_time" => "Hours Left",
+ "rep_platform" => "Hardware",
+ "reporter" => "Reporter",
+ "reporter_accessible" => "Reporter accessible",
+ "reporter_realname" => "Reporter Real Name",
+ "requestees.login_name" => "Flag Requestee",
+ "resolution" => "Resolution",
+ "see_also" => "See Also",
+ "setters.login_name" => "Flag Setter",
+ "setting" => "Setting",
+ "settings" => "Settings",
+ "short_desc" => "Summary",
+ "status_whiteboard" => "Whiteboard",
+ "target_milestone" => "Target Milestone",
+ "version" => "Version",
+ "work_time" => "Hours Worked",
+ } %]
+
+ [%# Also include any custom fields or fields which don't have a
+ Description here, by copying their Description from the
+ database. If you want to override this for your language
+ or your installation, just use a hook. %]
+ [% UNLESS Param('shutdownhtml') %]
+ [% FOREACH bz_field = bug_fields.values %]
+ [% SET vars.field_descs.${bz_field.name} = bz_field.description
+ IF !vars.field_descs.${bz_field.name}.defined %]
+ [% END %]
+ [% END %]
+[% END %]
+
+[% Hook.process("end") %]
diff --git a/template/en/default/global/footer.html.tmpl b/template/en/default/global/footer.html.tmpl
new file mode 100644
index 000000000..661f8afe6
--- /dev/null
+++ b/template/en/default/global/footer.html.tmpl
@@ -0,0 +1,51 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ # Svetlana Harisova <light@rathedg.com>
+ #%]
+
+[%# INTERFACE:
+ # This template has no interface. However, you must fulfill the interface to
+ # global/useful-links.html.tmpl.
+ #%]
+
+[% INCLUDE "global/help.html.tmpl" %]
+
+</div>
+
+[%# Migration note: below this point, this file corresponds to the old Param
+ # 'footerhtml'
+ #%]
+
+<div id="footer">
+ <div class="intro">[% Hook.process('intro') %]</div>
+
+[%# Migration note: the old param 'blurbhtml' goes here %]
+
+[%# Migration note: useful-links.html.tmpl corresponds to %commandmenu% %]
+
+ [% PROCESS "global/useful-links.html.tmpl" %]
+
+ <div class="outro">[% Hook.process('outro') %]</div>
+</div>
+
+[% Hook.process("end") %]
+
+</body>
+</html>
+
diff --git a/template/en/default/global/header.html.tmpl b/template/en/default/global/header.html.tmpl
new file mode 100644
index 000000000..6b7034045
--- /dev/null
+++ b/template/en/default/global/header.html.tmpl
@@ -0,0 +1,346 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ # Vaskin Kissoyan <vkissoyan@yahoo.com>
+ # Vitaly Harisov <vitaly@rathedg.com>
+ # Svetlana Harisova <light@rathedg.com>
+ #%]
+
+[%# INTERFACE:
+ # (All the below interface elements are optional.)
+ # title: string. Page title.
+ # header: string. Main page header.
+ # subheader: string. Page subheader.
+ # header_addl_info: string. Additional header information.
+ # bodyclasses: array of extra CSS classes for the <body>
+ # onload: string. JavaScript code to run when the page finishes loading.
+ # javascript: string. Javascript to go in the header.
+ # javascript_urls: list. List of URLs to Javascript.
+ # style: string. CSS style.
+ # style_urls: list. List of URLs to CSS style sheets.
+ # message: string. A message to display to the user. May contain HTML.
+ # atomlink: Atom link URL, May contain HTML
+ #%]
+
+[% IF message %]
+ [% PROCESS global/messages.html.tmpl %]
+[% END %]
+
+[% DEFAULT
+ subheader = ""
+ header_addl_info = ""
+ onload = ""
+ style_urls = []
+ yui = []
+%]
+
+[% SET yui_css = {
+ autocomplete => 1,
+ calendar => 1,
+ datatable => 1,
+} %]
+
+[%# Note: This is simple dependency resolution--you can't have dependencies
+ # that depend on each other. You have to specify all of a module's deps,
+ # if that module is going to be specified in "yui".
+ #%]
+[% SET yui_deps = {
+ autocomplete => ['json', 'connection', 'datasource'],
+ datatable => ['json', 'connection', 'datasource', 'element'],
+} %]
+
+[%# When using certain YUI modules, we need to process certain
+ # extra JS templates.
+ #%]
+[% SET yui_templates = {
+ datatable => ['global/value-descs.js.tmpl'],
+} %]
+
+[%# These are JS URLs that are *always* on the page and come before
+ # every other JS URL.
+ #%]
+[% SET starting_js_urls = [
+ "js/yui/yahoo-dom-event/yahoo-dom-event.js",
+ "js/yui/cookie/cookie-min.js",
+] %]
+
+
+[%# We should be able to set the default value of the header variable
+ # to the value of the title variable using the DEFAULT directive,
+ # but that doesn't work if a caller sets header to the empty string
+ # to avoid header inheriting the value of title, since DEFAULT
+ # mistakenly treats empty strings as undefined and gives header the
+ # value of title anyway. To get around that problem we explicitly
+ # set header's default value here only if it is undefined. %]
+[% IF !header.defined %][% header = title %][% END %]
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ [% Hook.process("start") %]
+ <title>[% title %]</title>
+
+[%# Migration note: contents of the old Param 'headerhtml' would go here %]
+
+ [% PROCESS "global/site-navigation.html.tmpl" %]
+
+ [% PROCESS 'global/setting-descs.none.tmpl' %]
+
+ [% SET yui = yui_resolve_deps(yui, yui_deps) %]
+ [% SET css_sets = css_files(style_urls, yui, yui_css) %]
+
+ [%# CSS cascade, part 1: Standard Bugzilla stylesheet set (persistent).
+ # Always present.
+ #%]
+ [%# This allows people to switch back to the "Classic" skin if they
+ # are in another skin.
+ #%]
+ <link href="[% 'skins/standard/global.css' FILTER mtime FILTER html %]"
+ rel="alternate stylesheet"
+ title="[% setting_descs.standard FILTER html %]">
+ [% FOREACH style_url = css_sets.standard %]
+ [% PROCESS format_css_link css_set_name = 'standard' %]
+ [% END %]
+
+ [%# CSS cascade, part 2 & 3: Third-party stylesheet set (selected and
+ # selectable). All third-party skins are present as alternate
+ # stylesheets, even if they are not currently in use.
+ #%]
+ [% FOREACH style_url = css_sets.skin %]
+ [% PROCESS format_css_link css_set_name = user.settings.skin.value %]
+ [% END %]
+
+ [% FOREACH alternate_skin = css_sets.alternate.keys %]
+ [% FOREACH style_url = css_sets.alternate.$alternate_skin %]
+ [% PROCESS format_css_link css_set_name = alternate_skin %]
+ [% END %]
+ [% END %]
+
+ [%# CSS cascade, part 4: page-specific styles.
+ #%]
+ [% IF style %]
+ <style type="text/css">
+ [% style %]
+ </style>
+ [% END %]
+
+ [%# CSS cascade, part 5: Custom Bugzilla stylesheet set (persistent).
+ # Always present. Site administrators may override all other style
+ # definitions, including skins, using custom stylesheets.
+ #%]
+ [% FOREACH style_url = css_sets.custom %]
+ [% PROCESS format_css_link css_set_name = 'standard' %]
+ [% END %]
+
+ [%# YUI Scripts %]
+ [% FOREACH yui_name = yui %]
+ [% starting_js_urls.push("js/yui/$yui_name/${yui_name}-min.js") %]
+ [% END %]
+ [% starting_js_urls.push('js/global.js') %]
+
+ [% FOREACH javascript_url = starting_js_urls %]
+ [% PROCESS format_js_link %]
+ [% END %]
+
+ <script type="text/javascript">
+ <!--
+ YAHOO.namespace('bugzilla');
+ YAHOO.util.Event.addListener = function (el, sType, fn, obj, overrideContext) {
+ if ( ("onpagehide" in window || YAHOO.env.ua.gecko) && sType === "unload") { sType = "pagehide"; };
+ var capture = ((sType == "focusin" || sType == "focusout") && !YAHOO.env.ua.ie) ? true : false;
+ return this._addListener(el, this._getType(sType), fn, obj, overrideContext, capture);
+ };
+ if ( "onpagehide" in window || YAHOO.env.ua.gecko) {
+ YAHOO.util.Event._simpleRemove(window, "unload",
+ YAHOO.util.Event._unload);
+ }
+ [%# The language selector needs javascript to set its cookie,
+ # so it is hidden in HTML/CSS by the "bz_default_hidden" class.
+ # If the browser can run javascript, it will then "unhide"
+ # the language selector using the following code.
+ #%]
+ function unhide_language_selector() {
+ YAHOO.util.Dom.removeClass(
+ 'lang_links_container', 'bz_default_hidden'
+ );
+ }
+ YAHOO.util.Event.onDOMReady(unhide_language_selector);
+
+ [%# Make some Bugzilla information available to all scripts.
+ # We don't import every parameter and constant because we
+ # don't want to add a lot of uncached JS to every page.
+ #%]
+ var BUGZILLA = {
+ param: {
+ cookiepath: '[% Param('cookiepath') FILTER js %]',
+ maxusermatches: [% Param('maxusermatches') FILTER js %]
+ },
+
+ string: {
+ [%# Please keep these in alphabetical order. %]
+
+ attach_desc_required:
+ 'You must enter a Description for this attachment.',
+ component_required:
+ 'You must select a Component for this [% terms.bug %].',
+ description_required:
+ 'You must enter a Description for this [% terms.bug %].',
+ short_desc_required:
+ 'You must enter a Summary for this [% terms.bug %].',
+ version_required:
+ 'You must select a Version for this [% terms.bug %].'
+ }
+ };
+
+ [% FOREACH yui_name = yui %]
+ [% FOREACH yui_template = yui_templates.$yui_name %]
+ [% PROCESS $yui_template %]
+ [% END %]
+ [% END %]
+ [% IF javascript %]
+ [% javascript %]
+ [% END %]
+ // -->
+ </script>
+
+ [% FOREACH javascript_url = javascript_urls %]
+ [% PROCESS format_js_link %]
+ [% END %]
+
+ [%# this puts the live bookmark up on firefox for the Atom feed %]
+ [% IF atomlink %]
+ <link rel="alternate"
+ type="application/atom+xml" title="Atom feed"
+ href="[% atomlink FILTER html %]">
+ [% END %]
+
+ [%# Required for the 'Autodiscovery' feature in Firefox 2 and IE 7. %]
+ <link rel="search" type="application/opensearchdescription+xml"
+ title="[% terms.Bugzilla %]" href="./search_plugin.cgi">
+ <link rel="shortcut icon" href="images/favicon.ico" >
+ [% Hook.process("additional_header") %]
+ </head>
+
+[%# Migration note: contents of the old Param 'bodyhtml' go in the body tag,
+ # but set the onload attribute in the DEFAULT directive above.
+ #%]
+
+ <body onload="[% onload %]"
+ class="[% urlbase.replace('^https?://','').replace('/$','').replace('[-~@:/.]+','-') %]
+ [% FOREACH class = bodyclasses %]
+ [% ' ' %][% class FILTER css_class_quote %]
+ [% END %] yui-skin-sam">
+
+[%# Migration note: the following file corresponds to the old Param
+ # 'bannerhtml'
+ #%]
+
+<div id="header">
+
+[% INCLUDE global/banner.html.tmpl %]
+
+<table border="0" cellspacing="0" cellpadding="0" id="titles">
+<tr>
+ <td id="title">
+ <p>[% terms.Bugzilla %]
+ [% " &ndash; $header" IF header %]</p>
+ </td>
+
+ [% IF subheader %]
+ <td id="subtitle">
+ <p class="subheader">[% subheader %]</p>
+ </td>
+ [% END %]
+
+ [% IF header_addl_info %]
+ <td id="information">
+ <p class="header_addl_info">[% header_addl_info %]</p>
+ </td>
+ [% END %]
+</tr>
+</table>
+
+<table id="lang_links_container" cellpadding="0" cellspacing="0"
+ class="bz_default_hidden"><tr><td>
+[% IF Bugzilla.languages.size > 1 %]
+ <ul class="links">
+ [% FOREACH lang = Bugzilla.languages.sort %]
+ <li>[% IF NOT loop.first %]<span class="separator"> | </span>[% END %]
+ [% IF lang == current_language %]
+ <span class="lang_current">[% lang FILTER html FILTER upper %]</span>
+ [% ELSE %]
+ <a href="#" onclick="set_language('[% lang FILTER none %]');">
+ [%- lang FILTER html FILTER upper %]</a>
+ [% END %]
+ </li>
+ [% END %]
+ </ul>
+[% END %]
+</td></tr></table>
+
+[% PROCESS "global/common-links.html.tmpl" qs_suffix = "_top" %]
+</div> [%# header %]
+
+<div id="bugzilla-body">
+
+[% IF Param('announcehtml') %]
+[% Param('announcehtml') FILTER none %]
+[% END %]
+
+[% IF message %]
+<div id="message">[% message %]</div>
+[% END %]
+
+[% BLOCK format_css_link %]
+ [% IF style_url.match('/IE-fixes\.css') %]
+ <!--[if lte IE 7]>
+ [%# Internet Explorer treats [if IE] HTML comments as uncommented.
+ # We use it to import CSS fixes so that Bugzilla looks decent on IE 7
+ # and below.
+ #%]
+ [% END %]
+
+ [% IF css_set_name == 'standard'
+ OR css_set_name == user.settings.skin.value
+ %]
+ [% SET css_rel = 'stylesheet' %]
+ [% SET css_set_display_name = setting_descs.${user.settings.skin.value}
+ || user.settings.skin.value %]
+ [% ELSE %]
+ [% SET css_rel = 'alternate stylesheet' %]
+ [% SET css_set_display_name = setting_descs.$css_set_name || css_set_name %]
+ [% END %]
+
+ [% IF css_set_name == 'standard' %]
+ [% SET css_title_link = '' %]
+ [% ELSE %]
+ [% css_title_link = BLOCK ~%]
+ title="[% css_set_display_name FILTER html %]"
+ [% END %]
+ [% END %]
+
+ <link href="[% style_url FILTER html %]" rel="[% css_rel FILTER none %]"
+ type="text/css" [% css_title_link FILTER none %]>
+
+ [% '<![endif]-->' IF style_url.match('/IE-fixes\.css') %]
+[% END %]
+
+[% BLOCK format_js_link %]
+ <script type="text/javascript" src="[% javascript_url FILTER mtime FILTER html %]"></script>
+[% END %]
diff --git a/template/en/default/global/help.html.tmpl b/template/en/default/global/help.html.tmpl
new file mode 100644
index 000000000..c0ff819ce
--- /dev/null
+++ b/template/en/default/global/help.html.tmpl
@@ -0,0 +1,33 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ #%]
+
+[% USE Bugzilla %]
+[% cgi = Bugzilla.cgi %]
+
+[% IF cgi.param("help") %]
+ <script type="text/javascript"> <!--
+ [% FOREACH help_name = help_html.keys %]
+ g_helpTexts["[% help_name FILTER js %]"] =
+ "[%- help_html.$help_name FILTER js -%]";
+ [% END %]
+ // -->
+ </script>
+[% END %]
+
diff --git a/template/en/default/global/hidden-fields.html.tmpl b/template/en/default/global/hidden-fields.html.tmpl
new file mode 100644
index 000000000..c141c6409
--- /dev/null
+++ b/template/en/default/global/hidden-fields.html.tmpl
@@ -0,0 +1,58 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Myk Melez <myk@mozilla.org>
+ #%]
+
+[%# INTERFACE:
+ # exclude: string; a regular expression matching fields to exclude
+ # from the list of hidden fields generated by this template
+ #%]
+
+[%# The global Bugzilla->cgi object is used to obtain form variable values. %]
+[% USE Bugzilla %]
+[% cgi = Bugzilla.cgi %]
+
+[%# Generate hidden form fields for non-excluded fields. %]
+[% FOREACH field = cgi.param() %]
+ [% NEXT IF exclude && field.search(exclude) %]
+ [%# The '.slice(0)' bit is here to force the 'param(field)' to be evaluated
+ in a list context, so we can avoid extra code checking for single valued or
+ empty fields %]
+ [% IF field == "data" && cgi.param("data") %]
+ <div class="box">
+ <p>
+ We were unable to store the file you uploaded because of incomplete information
+ in the form you just submitted. Because we are unable to retain the file between
+ form submissions, you must re-attach the file in addition to completing the
+ remaining missing information above.
+ </p>
+ <p>
+ Please re-attach the file <b>[% cgi.param(field) FILTER html %]</b> in
+ the field below:
+ </p>
+ <p>
+ <input type="file" id="data" name="data" size="50">
+ </p>
+ </div>
+ [% ELSE %]
+ [% FOREACH mvalue = cgi.param(field).slice(0) %]
+ <input type="hidden" name="[% field FILTER html %]"
+ value="[% mvalue FILTER html_linebreak %]">
+ [% END %]
+ [% END %]
+[% END %]
diff --git a/template/en/default/global/initialize.none.tmpl b/template/en/default/global/initialize.none.tmpl
new file mode 100644
index 000000000..a6c48973c
--- /dev/null
+++ b/template/en/default/global/initialize.none.tmpl
@@ -0,0 +1,32 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Myk Melez <myk@mozilla.org>
+ #%]
+
+[%# This template is a place to put directives that should get processed
+ # every time a primary template gets processed. Primary templates are those
+ # called from Perl code rather than from other templates via the PROCESS
+ # and INCLUDE directives.
+ #
+ # This template gets auto-processed at the beginning of primary templates
+ # via the PRE_PROCESS configuration parameter. Note that it gets processed
+ # for non-HTML templates too, so don't put HTML-specific stuff in here;
+ # put that into header.html.tmpl instead.
+ #%]
+
+[% USE Hook %]
diff --git a/template/en/default/global/js-products.html.tmpl b/template/en/default/global/js-products.html.tmpl
new file mode 100644
index 000000000..8ca206fbd
--- /dev/null
+++ b/template/en/default/global/js-products.html.tmpl
@@ -0,0 +1,34 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Myk Melez <myk@mozilla.org>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+
+[%# The javascript block gets used in header.html.tmpl. %]
+[% javascript = BLOCK %]
+ var useclassification = false; // No classification level in use
+ var first_load = true; // Is this the first time we load the page?
+ var last_sel = []; // Caches last selection
+ var cpts = new Array();
+ [% n = 1 %]
+ [% FOREACH prod = products %]
+ cpts['[% n %]'] = [
+ [%- FOREACH comp = prod.components %]'[% comp.name FILTER js %]'[% ", " UNLESS loop.last %] [%- END -%] ];
+ [% n = n+1 %]
+ [% END %]
+[% END %]
diff --git a/template/en/default/global/message.html.tmpl b/template/en/default/global/message.html.tmpl
new file mode 100644
index 000000000..e578a7f1b
--- /dev/null
+++ b/template/en/default/global/message.html.tmpl
@@ -0,0 +1,42 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s):
+ #%]
+
+[%# INTERFACE:
+ # url: string. An optional URL to go to.
+ # link: string. The link text for that URL.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% DEFAULT title = "$terms.Bugzilla Message" %]
+
+[% PROCESS global/header.html.tmpl %]
+
+[%# The "header" template automatically displays a message if it finds one.
+ Note that the global messages list is in messages.html.tmpl. %]
+
+[%# Display a URL if the calling script or message block has included one. %]
+[% IF url && link %]
+ <p>
+ <a href="[% url FILTER html %]">[% link FILTER html %]</a>
+ </p>
+[% END %]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/global/message.txt.tmpl b/template/en/default/global/message.txt.tmpl
new file mode 100644
index 000000000..9329cdbbf
--- /dev/null
+++ b/template/en/default/global/message.txt.tmpl
@@ -0,0 +1,25 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Max Kanat-Alexander.
+ # Portions created by Max Kanat-Alexander are Copyright (C) 2005
+ # Max Kanat-Alexander. All Rights Reserved.
+ #
+ # Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[%# Yes, this may show some HTML. But it's the best we
+ # can do at the moment. %]
+[% PROCESS global/messages.html.tmpl %]
+[% message FILTER txt %]
diff --git a/template/en/default/global/messages.html.tmpl b/template/en/default/global/messages.html.tmpl
new file mode 100644
index 000000000..ac0bc8ae7
--- /dev/null
+++ b/template/en/default/global/messages.html.tmpl
@@ -0,0 +1,851 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[%# This is a list of all the possible messages. Please keep them in
+ # alphabetical order by message tag, and leave a blank line between messages.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+[% message_tag = message %]
+
+[% message = BLOCK %]
+ [% IF message_tag == "account_created" %]
+ The user account [% otheruser.login FILTER html %] has been created
+ successfully.
+ [% IF groups.size %]
+ You may want to edit the group settings now, using the form below.
+ [% END %]
+
+ [% ELSIF message_tag == "account_creation_canceled" %]
+ [% title = "User Account Creation Canceled" %]
+ The creation of the user account [% account FILTER html %] has been
+ canceled.
+
+ [% ELSIF message_tag == "account_updated" %]
+ [% IF changed_fields.size
+ + groups_added_to.size + groups_removed_from.size
+ + groups_granted_rights_to_bless.size + groups_denied_rights_to_bless.size %]
+ [% title = "User $loginold updated" %]
+ The following changes have been made to the user account
+ [%+ loginold FILTER html %]:
+ <ul>
+ [% FOREACH field = changed_fields %]
+ <li>
+ [% IF field == 'login_name' %]
+ The login is now [% otheruser.login FILTER html %].
+ [% ELSIF field == 'realname' %]
+ The real name has been updated.
+ [% ELSIF field == 'cryptpassword' %]
+ A new password has been set.
+ [% ELSIF field == 'disabledtext' %]
+ The disable text has been modified.
+ [% ELSIF field == 'disable_mail' %]
+ [% IF otheruser.email_disabled %]
+ [% terms.Bug %]mail has been disabled.
+ [% ELSE %]
+ [% terms.Bug %]mail has been enabled.
+ [% END %]
+ [% END %]
+ </li>
+ [% END %]
+ [% IF groups_added_to.size %]
+ <li>
+ The account has been added to the
+ [%+ groups_added_to.join(', ') FILTER html %]
+ group[% 's' IF groups_added_to.size > 1 %].
+ </li>
+ [% END %]
+ [% IF groups_removed_from.size %]
+ <li>
+ The account has been removed from the
+ [%+ groups_removed_from.join(', ') FILTER html %]
+ group[% 's' IF groups_removed_from.size > 1 %].
+ </li>
+ [% END %]
+ [% IF groups_granted_rights_to_bless.size %]
+ <li>
+ The account has been granted rights to bless the
+ [%+ groups_granted_rights_to_bless.join(', ') FILTER html %]
+ group[% 's' IF groups_granted_rights_to_bless.size > 1 %].
+ </li>
+ [% END %]
+ [% IF groups_denied_rights_to_bless.size %]
+ <li>
+ The account has been denied rights to bless the
+ [%+ groups_denied_rights_to_bless.join(', ') FILTER html %]
+ group[% 's' IF groups_denied_rights_to_bless.size > 1 %].
+ </li>
+ [% END %]
+ </ul>
+ [% ELSE %]
+ [% title = "User $otheruser.login not changed" %]
+ You didn't request any changes to the user's account
+ [%+ otheruser.login FILTER html %].
+ [% END %]
+
+ [% ELSIF message_tag == "account_deleted" %]
+ [% title = "User $otheruser.login deleted" %]
+ The user account [% otheruser.login FILTER html %] has been deleted
+ successfully.
+
+ [% ELSIF message_tag == "account_disabled" %]
+ The user account [% account FILTER html %] is disabled, so you
+ cannot change its password.
+
+ [% ELSIF message_tag == "attachment_creation_failed" %]
+ The [% terms.bug %] was created successfully, but attachment creation
+ failed.
+ Please add your attachment by clicking the "Add an Attachment" link
+ below.
+
+ [% ELSIF message_tag == "bug_group_description" %]
+ Access to [% terms.bugs %] in the [% product.name FILTER html %] product
+
+ [% ELSIF message_tag == "buglist_adding_field" %]
+ [% title = "Adding field to search page..." %]
+ [% link = "Click here if the page does not redisplay automatically." %]
+
+ [% ELSIF message_tag == "buglist_updated_named_query" %]
+ [% title = "Search updated" %]
+ Your search named <code><a
+ href="buglist.cgi?cmdtype=runnamed&amp;namedcmd=[% queryname FILTER url_quote %]"
+ >[% queryname FILTER html %]</a></code> has been updated.
+
+ [% ELSIF message_tag == "buglist_new_default_query" %]
+ OK, you now have a new default search. You may
+ also bookmark the result of any individual search.
+
+ [% ELSIF message_tag == "buglist_new_named_query" %]
+ [% title = "Search created" %]
+ OK, you have a new search named <code><a
+ href="buglist.cgi?cmdtype=runnamed&amp;namedcmd=[% queryname FILTER url_quote %]"
+ >[% queryname FILTER html %]</a></code>.
+
+ [% ELSIF message_tag == "buglist_query_gone" %]
+ [% title = "Search is gone" %]
+ [% link = "Un-forget the search" %]
+ OK, the <b>[% namedcmd FILTER html %]</b> search is gone.
+
+ [% ELSIF message_tag == "buglist_sorted_by_relevance" %]
+ [% terms.Bugs %] on this list are sorted by relevance, with the most
+ relevant [% terms.bugs %] at the top.
+
+ [% ELSIF message_tag == "change_columns" %]
+ [% title = "Change columns" %]
+ Resubmitting your search with new columns...
+ Click <a href="[% redirect_url FILTER html %]">here</a>
+ if the page does not automatically refresh.
+
+ [% ELSIF message_tag == "classification_created" %]
+ [% title = "New Classification Created" %]
+ The <em>[% classification.name FILTER html %]</em> classification has been created.
+
+ [% ELSIF message_tag == "classification_deleted" %]
+ [% title = "Classification Deleted" %]
+ The <em>[% classification.name FILTER html %]</em> classification has been deleted.
+
+ [% ELSIF message_tag == "classification_updated" %]
+ [% title = "Classification Updated" %]
+ [% IF changes.keys.size %]
+ Changes to the <em>[% classification.name FILTER html %]</em> classification
+ have been saved:
+ <ul>
+ [% IF changes.name.defined %]
+ <li>Name updated to '[% classification.name FILTER html %]'</li>
+ [% END %]
+ [% IF changes.description.defined %]
+ [% IF classification.description %]
+ <li>Description updated to '[% classification.description FILTER html %]'</li>
+ [% ELSE %]
+ <li>Description removed</li>
+ [% END %]
+ [% END %]
+ [% IF changes.sortkey.defined %]
+ <li>Sortkey updated to '[% classification.sortkey FILTER html %]'</li>
+ [% END %]
+ [% Hook.process('classification_updated_fields') %]
+ </ul>
+ [% ELSE %]
+ No changes made to <em>[% classification.name FILTER html %]</em>.
+ [% END %]
+
+ [% ELSIF message_tag == "component_created" %]
+ [% title = "Component Created" %]
+ The component <em>[% comp.name FILTER html %]</em> has been created.
+
+ [% ELSIF message_tag == "component_deleted" %]
+ [% title = "Component Deleted" %]
+ The component <em>[% comp.name FILTER html %]</em> has been deleted.
+ [% IF comp.bug_count %]
+ All [% terms.bugs %] being in this component and all references
+ to them have also been deleted.
+ [% END %]
+
+ [% ELSIF message_tag == "component_updated" %]
+ [% title = "Component Updated" %]
+ [% IF changes.keys.size %]
+ Changes to the component <em>[% comp.name FILTER html %]</em> have been saved:
+ <ul>
+ [% IF changes.name.defined %]
+ <li>Name updated to '[% comp.name FILTER html %]'</li>
+ [% END %]
+ [% IF changes.description.defined %]
+ <li>Description updated to '[% comp.description FILTER html_light %]'</li>
+ [% END %]
+ [% IF changes.initialowner.defined %]
+ <li>Default assignee updated to '[% comp.default_assignee.login FILTER html %]'</li>
+ [% END %]
+ [% IF changes.initialqacontact.defined %]
+ [% IF comp.default_qa_contact.id %]
+ <li>Default QA contact updated to '[% comp.default_qa_contact.login FILTER html %]'</li>
+ [% ELSE %]
+ <li>Default QA contact deleted</li>
+ [% END %]
+ [% END %]
+ [% IF changes.cc_list.defined %]
+ [% IF comp.initial_cc.size %]
+ [% cc_list = [] %]
+ [% FOREACH cc_user = comp.initial_cc %]
+ [% cc_list.push(cc_user.login) %]
+ [% END %]
+ <li>Default CC list updated to [% cc_list.join(", ") FILTER html %]</li>
+ [% ELSE %]
+ <li>Default CC list deleted</li>
+ [% END %]
+ [% END %]
+ [% Hook.process('component_updated_fields') %]
+ </ul>
+ [% ELSE %]
+ No changes made to <em>[% comp.name FILTER html %]</em>.
+ [% END %]
+
+ [% ELSIF message_tag == "custom_field_created" %]
+ [% title = "Custom Field Created" %]
+ The new custom field '[% field.name FILTER html %]' has been
+ successfully created.
+
+ [% ELSIF message_tag == "custom_field_deleted" %]
+ [% title = "Custom Field Deleted" %]
+ The custom field '[% field.name FILTER html %]' has been
+ successfully deleted.
+
+ [% ELSIF message_tag == "custom_field_updated" %]
+ [% title = "Custom Field Updated" %]
+ Properties of the '[% field.name FILTER html %]' field have been
+ successfully updated.
+
+ [% ELSIF message_tag == "default_settings_updated" %]
+ [% IF changes_saved %]
+ Changes to default preferences have been saved.
+ [% ELSE %]
+ No changes made.
+ [% END %]
+
+ [% ELSIF message_tag == "emailold_change_canceled" %]
+ [% title = "Cancel Request to Change Email Address" %]
+ The request to change the email address for your account to
+ [%+ new_email FILTER html %] has been canceled.
+
+ [% ELSIF message_tag == "email_change_canceled" %]
+ [% title = "Cancel Request to Change Email Address" %]
+ The request to change the email address for the
+ account [%+ old_email FILTER html %] to
+ [%+ new_email FILTER html %] has been canceled.
+
+ [% ELSIF message_tag == "email_change_canceled_reinstated" %]
+ [% title = "Cancel Request to Change Email Address" %]
+ The request to change the email address for the
+ account [%+ old_email FILTER html %] to
+ [%+ new_email FILTER html %] has been canceled.
+ Your old account settings have been reinstated.
+
+ [% ELSIF message_tag == "extension_created" %]
+ An extension named [% name FILTER html %] has been created
+ in [% path FILTER html %]. Make sure you change "YOUR NAME" and
+ "YOUR EMAIL ADDRESS" in the code to your name and your email address.
+
+ [% ELSIF message_tag == "field_value_created" %]
+ [% title = "New Field Value Created" %]
+ The value <em>[% value.name FILTER html %]</em> has been added as a
+ valid choice for the <em>[% field.description FILTER html %]</em>
+ (<em>[% field.name FILTER html %]</em>) field.
+ [% IF field.name == "bug_status" %]
+ You should now visit the <a href="editworkflow.cgi">status workflow
+ page</a> to include your new [% terms.bug %] status.
+ [% END %]
+
+ [% ELSIF message_tag == "field_value_deleted" %]
+ [% title = "Field Value Deleted" %]
+ The value <em>[% value.name FILTER html %]</em> of the
+ <em>[% field.description FILTER html %]</em>
+ (<em>[% field.name FILTER html %]</em>) field has been deleted.
+
+ [% ELSIF message_tag == "field_value_updated" %]
+ [% title = "Field Value Updated" %]
+ [% IF changes.keys.size %]
+ The <em>[% value_old FILTER html %]</em> value of the
+ <em>[% field.description FILTER html %]</em>
+ (<em>[% field.name FILTER html %]</em>) field has been changed:
+ <ul>
+ [% IF changes.value %]
+ <li>Field value updated to
+ <em>[% changes.value.1 FILTER html %]</em>.
+ [% IF value.is_default %]
+ (Note that this value is the default for this field. All
+ references to the default value will now point to this new value.)
+ [% END %]
+ </li>
+ [% END %]
+ [% IF changes.sortkey %]
+ <li>Sortkey updated to
+ <em>[% changes.sortkey.1 FILTER html %]</em>.</li>
+ [% END %]
+ [% IF changes.visibility_value_id %]
+ [% IF value.visibility_value.defined %]
+ <li>It only appears when
+ [%+ value.field.value_field.description FILTER html %] is set to
+ '[%+ value.visibility_value.name FILTER html %]'.</li>
+ [% ELSE %]
+ <li>It now always appears, no matter what
+ [%+ value.field.value_field.description FILTER html %] is set to.
+ </li>
+ [% END %]
+ [% END %]
+ </ul>
+ [% ELSE %]
+ No changes made to the field value <em>[% value_old FILTER html %]</em>.
+ [% END %]
+
+ [% ELSIF message_tag == "flag_cleared" %]
+ Some flags didn't apply in the new product/component
+ and have been cleared.
+
+ [% ELSIF message_tag == "flag_creation_failed" %]
+ [% title = "Flag Creation Failure" %]
+ An error occured while validating flags:
+ [%+ flag_creation_error FILTER none %]
+
+ [% ELSIF message_tag == "get_field_desc" %]
+ [% field_descs.$field_name FILTER html %]
+
+ [% ELSIF message_tag == "get_resolution" %]
+ [% display_value("resolution", resolution) FILTER html %]
+
+ [% ELSIF message_tag == "get_status" %]
+ [% display_value("bug_status", status) FILTER html %]
+
+ [% ELSIF message_tag == "group_created" %]
+ [% title = "New Group Created" %]
+ The group <em>[% group.name FILTER html %]</em> has been created.
+
+ [% ELSIF message_tag == "group_deleted" %]
+ [% title = "Group Deleted" %]
+ The group <em>[% name FILTER html %]</em> has been deleted.
+
+ [% ELSIF message_tag == "group_membership_removed" %]
+ [% title = "Group Membership Removed" %]
+ [% IF users.size %]
+ Explicit membership to the <em>[% group FILTER html %]</em> group removed
+ [% IF regexp %] for users matching '[% regexp FILTER html %]'[% END %]:
+ [% FOREACH user = users %]
+ [%+ user.login FILTER html %]
+ [% END %]
+ [% ELSE %]
+ No users are being affected by your action.
+ [% END %]
+
+ [% ELSIF message_tag == "group_updated" %]
+ [% IF changes.keys.size %]
+ The following changes have been made to the '[% group.name FILTER html %]'
+ group:
+ <ul>
+ [% FOREACH field = changes.keys.sort %]
+ [% SWITCH field %]
+ [% CASE 'name' %]
+ <li>The name was changed to '[% changes.name.1 FILTER html %]'</li>
+ [% CASE 'description' %]
+ <li>The description was updated.</li>
+ [% CASE 'userregexp' %]
+ <li>The regular expression was updated.</li>
+ [% CASE 'isactive' %]
+ [% IF changes.isactive.1 %]
+ <li>The group will now be used for [% terms.bugs %].</li>
+ [% ELSE %]
+ <li>The group will no longer be used for [% terms.bugs %].</li>
+ [% END %]
+ [% CASE 'icon_url' %]
+ <li>The group icon URL has been updated.</li>
+ [% CASE 'members_add' %]
+ <li>The following groups are now members of this group:
+ [%+ changes.members_add.join(', ') FILTER html %]</li>
+ [% CASE 'members_remove' %]
+ <li>The following groups are no longer members of this group:
+ [%+ changes.members_remove.join(', ') FILTER html %]</li>
+ [% CASE 'member_of_add' %]
+ <li>This group is now a member of the following groups:
+ [%+ changes.member_of_add.join(', ') FILTER html %]</li>
+ [% CASE 'member_of_remove' %]
+ <li>This group is no longer a member of the following groups:
+ [%+ changes.member_of_remove.join(', ') FILTER html %]</li>
+ [% CASE 'bless_from_add' %]
+ <li>The following groups may now add users to this group:
+ [%+ changes.bless_from_add.join(', ') FILTER html %]</li>
+ [% CASE 'bless_from_remove' %]
+ <li>The following groups may no longer add users to this group:
+ [%+ changes.bless_from_remove.join(', ') FILTER html %]</li>
+ [% CASE 'bless_to_add' %]
+ <li>This group may now add users to the following groups:
+ [%+ changes.bless_to_add.join(', ') FILTER html %]</li>
+ [% CASE 'bless_to_remove' %]
+ <li>This group may no longer add users to the following groups:
+ [%+ changes.bless_to_remove.join(', ') FILTER html %]</li>
+ [% CASE 'visible_from_add' %]
+ <li>The following groups can now see users in this group:
+ [%+ changes.visible_from_add.join(', ') FILTER html %]</li>
+ [% CASE 'visible_from_remove' %]
+ <li>The following groups may no longer see users in this group:
+ [%+ changes.visible_from_remove.join(', ') FILTER html %]</li>
+ [% CASE 'visible_to_me_add' %]
+ <li>This group may now see users in the following groups:
+ [%+ changes.visible_to_me_add.join(', ') FILTER html %]</li>
+ [% CASE 'visible_to_me_remove' %]
+ <li>This group may no longer see users in the following groups:
+ [%+ changes.visible_to_me_remove.join(', ') FILTER html %]</li>
+ [% END %]
+ [% END %]
+ </ul>
+ [% ELSE %]
+ You didn't request any change for the '[% group.name FILTER html %]'
+ group.
+ [% END %]
+
+ [% ELSIF message_tag == "invalid_column_name" %]
+ The custom sort order specified contains one or more invalid
+ column names: <em>[% invalid_fragments.join(', ') FILTER html %]</em>.
+ They have been removed from the sort list.
+
+ [% ELSIF message_tag == "job_queue_depth" %]
+ [% count FILTER html %] jobs in the queue.
+
+ [% ELSIF message_tag == "keyword_created" %]
+ [% title = "New Keyword Created" %]
+ The keyword <em>[% name FILTER html %]</em> has been created.
+
+ [% ELSIF message_tag == "keyword_deleted" %]
+ [% title = "Keyword Deleted" %]
+ The <em>[% keyword.name FILTER html %]</em> keyword has been deleted.
+
+ [% ELSIF message_tag == "keyword_updated" %]
+ [% title = "Keyword Updated" %]
+ [% IF changes.keys.size %]
+ Changes to the <em>[% keyword.name FILTER html %]</em> keyword have
+ been saved:
+ <ul>
+ [% IF changes.name.defined %]
+ <li>Keyword renamed to <em>[% keyword.name FILTER html %]</em>.</li>
+ [% END %]
+ [% IF changes.description.defined %]
+ <li>Description updated to <em>[% keyword.description FILTER html %]</em></li>
+ [% END %]
+ </ul>
+ [% ELSE %]
+ No changes made.
+ [% END %]
+
+ [% ELSIF message_tag == "logged_out" %]
+ [% title = "Logged Out" %]
+ [% url = "index.cgi?GoAheadAndLogIn=1" %]
+ [% link = "Log in again." %]
+ <b>Your login has been forgotten</b>.
+ The cookie that was remembering your login is now gone. You will be
+ prompted for a login the next time it is required.
+
+ [% ELSIF message_tag == "login_changed" %]
+ [% title = "$terms.Bugzilla Login Changed" %]
+ Your [% terms.Bugzilla %] login has been changed.
+
+ [% ELSIF message_tag == "migrate_component_created" %]
+ Component created: [% comp.name FILTER html %]
+ (in [% product.name FILTER html %])
+
+ [% ELSIF message_tag == "migrate_creating_bugs" %]
+ Creating [% terms.bugs %]...
+
+ [% ELSIF message_tag == "migrate_field_created" %]
+ New custom field: [% field.description FILTER html %]
+ ([% field.name FILTER html %])
+
+ [% ELSIF message_tag == "migrate_product_created" %]
+ Product created: [% created.name FILTER html %]
+
+ [% ELSIF message_tag == "migrate_reading_bugs" %]
+ Reading [% terms.bugs %]...
+
+ [% ELSIF message_tag == "migrate_reading_products" %]
+ Reading products...
+
+ [% ELSIF message_tag == "migrate_reading_users" %]
+ Reading users...
+
+ [% ELSIF message_tag == "migrate_translating_bugs" %]
+ Converting [% terms.bug %] values to be appropriate for
+ [%+ terms.Bugzilla %]...
+
+ [% ELSIF message_tag == "migrate_user_created" %]
+ User created: [% created.email FILTER html %]
+ [% IF password %] Password: [% password FILTER html %][% END %]
+
+ [% ELSIF message_tag == "migrate_value_created" %]
+ [% IF product.defined %]
+ [% product.name FILTER html %]
+ [% END %]
+ [%+ field_descs.${field.name} FILTER html %] value
+ created: [% value FILTER html %]
+
+ [% ELSIF message_tag == "milestone_created" %]
+ [% title = "Milestone Created" %]
+ The milestone <em>[% milestone.name FILTER html %]</em> has been created.
+
+ [% ELSIF message_tag == "milestone_deleted" %]
+ [% title = "Milestone Deleted" %]
+ The milestone <em>[% milestone.name FILTER html %]</em> has been deleted.
+ [% IF milestone.bug_count %]
+ [%+ terms.Bugs %] targetted to this milestone have been retargetted to
+ the default milestone <em>[% product.default_milestone FILTER html %]</em>.
+ [% END %]
+
+ [% ELSIF message_tag == "milestone_updated" %]
+ [% title = "Milestone Updated" %]
+ [% IF changes.size %]
+ Changes to the milestone <em>[% milestone.name FILTER html %]</em>
+ have been saved:
+ <ul>
+ [% IF changes.value.defined %]
+ <li>Milestone name updated to <em>[% milestone.name FILTER html %]</em></li>
+ [% END %]
+ [% IF changes.sortkey.defined %]
+ <li>Sortkey updated to <em>[% milestone.sortkey FILTER html %]</em>
+ [% END %]
+ </ul>
+ [% ELSE %]
+ No changes made to milestone <em>[% milestone.name FILTER html %]</em>.
+ [% END %]
+
+ [% ELSIF message_tag == "parameters_updated" %]
+ [% title = "Parameters Updated" %]
+ [% IF param_changed.size > 0 %]
+ [% FOREACH param = param_changed %]
+ Changed <em>[% param FILTER html %]</em><br>
+ [% IF param == 'utf8' && Param('utf8') %]
+ <strong>You must now re-run <kbd>checksetup.pl</kbd>.</strong><br>
+ [% END %]
+ [% END %]
+ [% ELSE %]
+ No changes made.
+ [% END %]
+
+ [% IF shutdown_is_active == 1 %]
+ <hr>
+ [% terms.Bugzilla %] has now been shut down. To re-enable the system,
+ clear the <em>shutdownhtml</em> field.
+ [% END%]
+
+ [% ELSIF message_tag == "password_change_canceled" %]
+ [% title = "Cancel Request to Change Password" %]
+ Your request has been canceled.
+
+ [% ELSIF message_tag == "password_change_request" %]
+ [% title = "Request to Change Password" %]
+ A token for changing your password has been emailed to you.
+ Follow the instructions in that email to change your password.
+
+ [% ELSIF message_tag == "password_changed" %]
+ [% title = "Password Changed" %]
+ Your password has been changed.
+
+ [% ELSIF message_tag == "flag_type_created" %]
+ [% title = BLOCK %]Flag Type '[% name FILTER html %]' Created[% END %]
+ The flag type <em>[% name FILTER html %]</em> has been created.
+
+ [% ELSIF message_tag == "flag_type_changes_saved" %]
+ [% title = BLOCK %]Flag Type '[% name FILTER html %]' Changes Saved[% END %]
+ Your changes to the flag type <em>[% name FILTER html %]</em>
+ have been saved.
+
+ [% ELSIF message_tag == "flag_type_deleted" %]
+ [% title = BLOCK %]Flag Type '[% name FILTER html %]' Deleted[% END %]
+ The flag type <em>[% name FILTER html %]</em> has been deleted.
+
+ [% ELSIF message_tag == "flag_type_deactivated" %]
+ [% title = BLOCK %]Flag Type '[% flag_type.name FILTER html %]' Deactivated[% END %]
+ The flag type <em>[% flag_type.name FILTER html %]</em> has been deactivated.
+
+ [% ELSIF message_tag == "install_admin_get_email" %]
+ Enter the e-mail address of the administrator:
+
+ [% ELSIF message_tag == "install_admin_get_name" %]
+ Enter the real name of the administrator:
+
+ [% ELSIF message_tag == "install_admin_get_password" %]
+ Enter a password for the administrator account:
+
+ [% ELSIF message_tag == "install_admin_created" %]
+ [% user.login FILTER html %] is now set up as an administrator.
+
+ [% ELSIF message_tag == "install_admin_setup" %]
+ Looks like we don't have an administrator set up yet.
+ Either this is your first time using [% terms.Bugzilla %], or your
+ administrator's privileges might have accidentally been deleted.
+
+ [% ELSIF message_tag == "install_column_add" %]
+ Adding new column '[% column FILTER html %]' to the '[% table FILTER html %]' table...
+
+ [% ELSIF message_tag == "install_column_drop" %]
+ Deleting the '[% column FILTER html %]' column from the '[% table FILTER html %]' table...
+
+ [% ELSIF message_tag == "install_column_rename" %]
+ Renaming column '[% old FILTER html %]' to '[% new FILTER html %]'...
+
+ [% ELSIF message_tag == "install_confirm_password" %]
+ Please retype the password to verify:
+
+ [% ELSIF message_tag == "install_default_classification" %]
+ Creating default classification '[% name FILTER html %]'...
+
+ [% ELSIF message_tag == "install_default_product" %]
+ Creating initial dummy product '[% name FILTER html %]'...
+
+ [% ELSIF message_tag == "install_file_perms_fix" %]
+ Fixing file permissions...
+
+ [% ELSIF message_tag == "install_fk_add" %]
+ Adding foreign key: [% table FILTER html %].[% column FILTER html %] -&gt; [% fk.TABLE FILTER html %].[% fk.COLUMN FILTER html %]...
+
+ [% ELSIF message_tag == "install_fk_drop" %]
+ Dropping foreign key: [% table FILTER html %].[% column FILTER html %] -&gt; [% fk.TABLE FILTER html %].[% fk.COLUMN FILTER html %]...
+
+ [% ELSIF message_tag == "install_fk_invalid" %]
+ ERROR: There are invalid values for the [% column FILTER html %] column in the [% table FILTER html %]
+ table. (These values do not exist in the [% foreign_table FILTER html %] table, in the
+ [% foreign_column FILTER html %] column.)
+
+ Before continuing with checksetup, you will need to fix these values,
+ either by deleting these rows from the database, or changing the values
+ of [% column FILTER html %] in [% table FILTER html %] to point to valid values in [% foreign_table FILTER html %].[% foreign_column FILTER html %].
+
+ The bad values from the [% table FILTER html %].[% column FILTER html %] column are:
+ [%+ values.join(', ') FILTER html %]
+
+ [% ELSIF message_tag == "install_fk_invalid_fixed" %]
+ WARNING: There were invalid values in [% table FILTER html %].[% column FILTER html %]
+ that have been [% IF action == 'delete' %]deleted[% ELSE %]set to NULL[% END %]:
+ [%+ values.join(', ') FILTER html %]
+
+ [% ELSIF message_tag == "install_group_create" %]
+ Creating group [% name FILTER html %]...
+
+ [% ELSIF message_tag == "install_setting_new" %]
+ Adding a new user setting called '[% name FILTER html %]'
+
+ [% ELSIF message_tag == "install_table_drop" %]
+ Dropping the '[% name FILTER html %]' table...
+
+ [% ELSIF message_tag == "install_table_rename" %]
+ Renaming the '[% old FILTER html %]' table to '[% new FILTER html %]'...
+
+ [% ELSIF message_tag == "install_urlbase_default" %]
+ Now that you have installed [% terms.Bugzilla %], you should visit the
+ 'Parameters' page (linked in the footer of the Administrator
+ account) to ensure it is set up as you wish - this includes
+ setting the 'urlbase' option to the correct URL.
+
+ [% ELSIF message_tag == "install_reset_password" %]
+ Enter a new password for [% user.login FILTER html %]:
+
+ [% ELSIF message_tag == "install_reset_password_done" %]
+ New password set.
+
+ [% ELSIF message_tag == "install_webservergroup_empty" %]
+ ****************************************************************************
+ WARNING! You have not entered a value for the "webservergroup" parameter
+ in localconfig. This means that certain files and directories which need
+ to be editable by both you and the web server must be world writable, and
+ other files (including the localconfig file which stores your database
+ password) must be world readable. This means that _anyone_ who can obtain
+ local access to this machine can do whatever they want to your
+ [%+ terms.Bugzilla %] installation, and is probably also able to run
+ arbitrary Perl code as the user that the web server runs as.
+
+ You really, really, really need to change this setting.
+ ****************************************************************************
+
+ [% ELSIF message_tag == "install_webservergroup_not_in" %]
+ Warning: you have entered a value for the "webservergroup" parameter in
+ localconfig, but you are not either a) running this script as [% constants.ROOT_USER FILTER html %];
+ or b) a member of this group. This can cause permissions problems and
+ decreased security. If you experience problems running [% terms.Bugzilla %]
+ scripts, log in as [% constants.ROOT_USER FILTER html %] and re-run this script, become a
+ member of the group, or remove the value of the "webservergroup" parameter.
+
+ [% ELSIF message_tag == "install_webservergroup_windows" %]
+ Warning: You have set webservergroup in [% constants.bz_locations.localconfig FILTER html %]
+ Please understand that this does not bring you any security when
+ running under Windows.
+ Verify that the file permissions in your [% terms.Bugzilla %] directory are
+ suitable for your system. Avoid unnecessary write access.
+
+ [% ELSIF message_tag == "install_workflow_init" %]
+ Setting up the default status workflow...
+
+ [% ELSIF message_tag == "product_created" %]
+ [% title = "Product Created" %]
+ The product <em>[% product.name FILTER html %]</em> has been created. You will need to
+ <a href="editcomponents.cgi?action=add&product=[% product.name FILTER url_quote %]">
+ add at least one component</a> before anyone can enter [% terms.bugs %] against this product.
+
+ [% ELSIF message_tag == "product_deleted" %]
+ [% title = "Product Deleted" %]
+ The product <em>[% product.name FILTER html %]</em> and all its versions,
+ components, milestones and group controls have been deleted.
+ [% IF product.bug_count %]
+ All [% terms.bugs %] being in this product and all references
+ to them have also been deleted.
+ [% END %]
+
+ [% ELSIF message_tag == "product_invalid" %]
+ [% title = "$terms.Bugzilla Component Descriptions" %]
+ The product <em>[% product FILTER html %]</em> does not exist
+ or you don't have access to it. The following is a list of the
+ products you can choose from.
+
+ [% ELSIF message_tag == "remaining_time_zeroed" %]
+ The [% field_descs.remaining_time FILTER html %] field has been
+ set to zero automatically as part of closing this [% terms.bug %]
+ or moving it from one closed state to another.
+
+ [% ELSIF message_tag == "sanitycheck" %]
+ [%# We use this way to call sanitycheck-specific messages so that
+ # we can still use get_text(). %]
+ [% PROCESS "admin/sanitycheck/messages.html.tmpl" %]
+
+ [% ELSIF message_tag == "series_all_open" %]
+ All Open
+
+ [% ELSIF message_tag == "series_all_closed" %]
+ All Closed
+
+ [% ELSIF message_tag == "series_subcategory" %]
+ -All-
+
+ [% ELSIF message_tag == "sudo_started" %]
+ [% title = "Sudo session started" %]
+ The sudo session has been started. For the next 6 hours, or until you
+ end the session, everything you do you do as the user you are
+ impersonating ([% target FILTER html %]).
+
+ [% ELSIF message_tag == "sudo_ended" %]
+ [% title = "Sudo session complete" %]
+ The sudo session has been ended. From this point forward, everything you
+ do you do as yourself.
+
+ [% ELSIF message_tag == "series_created" %]
+ [% title = "Series Created" %]
+ The series <em>[% series.category FILTER html %] /
+ [%+ series.subcategory FILTER html %] /
+ [%+ series.name FILTER html %]</em>
+ has been created. Note that you may need to wait up to
+ [%+ series.frequency * 2 %] days before there will be enough data for a
+ chart of this series to be produced.
+
+ [% ELSIF message_tag == "series_deleted" %]
+ [% title = "Series Deleted" %]
+ The series <em>[% series.category FILTER html %] /
+ [%+ series.subcategory FILTER html %] /
+ [%+ series.name FILTER html %]</em>
+ has been deleted.
+
+ [% ELSIF message_tag == "shutdown" %]
+ [% title = "$terms.Bugzilla is Down" %]
+ [% Param("shutdownhtml") %]
+ [% IF userid %]
+ <p>For security reasons, you have been logged out automatically.
+ The cookie that was remembering your login is now gone.
+ [% END %]
+
+ [% ELSIF message_tag == "term" %]
+ [% terms.$term FILTER html %]
+
+ [% ELSIF message_tag == "unexpected_flag_types" %]
+ Some flags could not be set. Please check your changes.
+
+ [% ELSIF message_tag == "user_match_failed" %]
+ You entered a username that did not match any known
+ [% terms.Bugzilla %] users, so we have instead left
+ the [% match_field FILTER html %] field blank.
+
+ [% ELSIF message_tag == "user_match_multiple" %]
+ You entered a username that matched more than one
+ user, so we have instead left the [% match_field FILTER html %]
+ field blank.
+
+ [% ELSIF message_tag == "version_created" %]
+ [% title = "Version Created" %]
+ The version <em>[% version.name FILTER html %]</em> of product
+ <em>[% product.name FILTER html %]</em> has been created.
+
+ [% ELSIF message_tag == "version_deleted" %]
+ [% title = "Version Deleted" %]
+ The version <em>[% version.name FILTER html %]</em> of product
+ <em>[% product.name FILTER html %]</em> has been deleted.
+
+ [% ELSIF message_tag == "version_updated" %]
+ [% title = "Version Updated" %]
+ [% IF changes.size %]
+ [% IF changes.value.defined %]
+ Version renamed to <em>[% version.name FILTER html %]</em>.
+ [% END %]
+ [% ELSE %]
+ No changes made to version <em>[% version.name FILTER html %]</em>.
+ [% END %]
+
+ [% ELSIF message_tag == "workflow_updated" %]
+ The workflow has been updated.
+ [% END %]
+[% END %]
+
+[% IF !message %]
+ [% message = Hook.process('messages') %]
+[% END %]
+
+[%# Give sensible error if the message function is used incorrectly. #%]
+[% IF !message %]
+ [% message = BLOCK %]
+ You are using [% terms.Bugzilla %]'s messaging functions incorrectly. You
+ passed in the string '[% message_tag %]'. The correct use is to pass
+ in a tag, and define that tag in the file messages.html.tmpl.<br>
+ <br>
+ If you are a [% terms.Bugzilla %] end-user seeing this message, please
+ save this page and send it to [% Param('maintainer') %].
+ [% END %]
+[% END %]
diff --git a/template/en/default/global/per-bug-queries.html.tmpl b/template/en/default/global/per-bug-queries.html.tmpl
new file mode 100644
index 000000000..a7c073ba1
--- /dev/null
+++ b/template/en/default/global/per-bug-queries.html.tmpl
@@ -0,0 +1,101 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+
+[% IF user.id && user.settings.per_bug_queries.value == "on" %]
+ <li id="links-special">
+ <script type="text/javascript">
+ <!--
+ function update_text() {
+ // 'lob' means list_of_bugs.
+ var lob_action = document.getElementById('lob_action');
+ var action = lob_action.options[lob_action.selectedIndex].value;
+ var text = document.getElementById('lob_direction');
+ var new_query_text = document.getElementById('lob_new_query_text');
+
+ if (action == "add") {
+ text.innerHTML = "to";
+ new_query_text.style.display = 'inline';
+ }
+ else {
+ text.innerHTML = "from";
+ new_query_text.style.display = 'none';
+ }
+ }
+
+ function manage_old_lists() {
+ var old_lists = document.getElementById('lob_oldqueryname');
+ // If there is no saved searches available, returns.
+ if (!old_lists) return;
+
+ var new_query = document.getElementById('lob_newqueryname').value;
+
+ if (new_query != "") {
+ old_lists.disabled = true;
+ }
+ else {
+ old_lists.disabled = false;
+ }
+ }
+ //-->
+ </script>
+
+ [%# Get existing lists of bugs for this user %]
+ [% lists_of_bugs = [] %]
+ [% FOREACH q = user.queries %]
+ [% NEXT UNLESS q.type == constants.LIST_OF_BUGS %]
+ [% lists_of_bugs.push(q.name) %]
+ [% END %]
+ <div class="label"></div>
+ <ul class="links"><li class="form">
+ <form id="list_of_bugs" action="buglist.cgi" method="get">
+ <input type="hidden" name="cmdtype" value="doit">
+ <input type="hidden" name="remtype" value="asnamed">
+ <input type="hidden" name="list_of_bugs" value="1">
+ <input type="hidden" name="token" value="[% issue_hash_token(['savedsearch']) FILTER html %]">
+ <select id="lob_action" name="action" onchange="update_text();">
+ <option value="add">Add</option>
+ [% IF lists_of_bugs.size %]
+ <option value="remove">Remove</option>
+ [% END %]
+ </select>
+
+ [% IF Param('docs_urlbase') %]
+ <a href="[% docs_urlbase FILTER html %]query.html#individual-buglists">the named tag</a>
+ [% ELSE %]
+ the named tag
+ [% END %]
+
+ [% IF lists_of_bugs.size %]
+ <select id="lob_oldqueryname" name="oldqueryname">
+ [% FOREACH query = lists_of_bugs %]
+ <option value="[% query FILTER html %]">[% query FILTER html %]</option>
+ [% END %]
+ </select>
+ [% END %]
+ <span id="lob_new_query_text">
+ [% " or create and add the tag" IF lists_of_bugs.size %]
+ <input class="txt" type="text" id="lob_newqueryname"
+ size="20" maxlength="64" name="newqueryname"
+ onkeyup="manage_old_lists();">
+ </span>
+ <span id="lob_direction">to</span>
+ [%+ terms.bugs %]
+ <input type="text" name="bug_ids" size="12" maxlength="80"
+ [%- " value=\"$bugids\"" IF bugids %]>
+ <input type="submit" value="Commit" id="commit_list_of_bugs">
+ </form>
+ </li></ul>
+ </li>
+[% END %]
diff --git a/template/en/default/global/reason-descs.none.tmpl b/template/en/default/global/reason-descs.none.tmpl
new file mode 100644
index 000000000..426085f0d
--- /dev/null
+++ b/template/en/default/global/reason-descs.none.tmpl
@@ -0,0 +1,40 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% SET reason_descs = {
+ ${constants.REL_ASSIGNEE} => "You are the assignee for the ${terms.bug}.",
+ ${constants.REL_REPORTER} => "You reported the ${terms.bug}.",
+ ${constants.REL_QA} => "You are the QA Contact for the ${terms.bug}.",
+ ${constants.REL_CC} => "You are on the CC list for the ${terms.bug}.",
+ ${constants.REL_GLOBAL_WATCHER} => "You are watching all $terms.bug changes.",
+} %]
+
+[% SET watch_reason_descs => {
+ ${constants.REL_ASSIGNEE} =>
+ "You are watching the assignee of the ${terms.bug}.",
+ ${constants.REL_REPORTER} =>
+ "You are watching the reporter of the ${terms.bug}.",
+ ${constants.REL_QA} =>
+ "You are watching the QA Contact of the ${terms.bug}.",
+ ${constants.REL_CC} =>
+ "You are watching someone on the CC list of the ${terms.bug}.",
+} %]
+
+[% Hook.process('end') %]
diff --git a/template/en/default/global/select-menu.html.tmpl b/template/en/default/global/select-menu.html.tmpl
new file mode 100644
index 000000000..f8d4d68e2
--- /dev/null
+++ b/template/en/default/global/select-menu.html.tmpl
@@ -0,0 +1,64 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Myk Melez <myk@mozilla.org>
+ #%]
+
+[%# INTERFACE:
+ # name: string; the name of the menu.
+ #
+ # multiple: boolean; whether or not the menu is multi-select
+ #
+ # size: integer; the number of items to display at once
+ #
+ # options: array or hash; the items with which to populate the array.
+ # If a hash is passed, the hash keys become the names displayed
+ # to the user while the hash values become the value of the item.
+ #
+ # default: string; the item selected in the menu by default.
+ #
+ # onchange: code; JavaScript to be run when the user changes the value
+ # selected in the menu.
+ #%]
+
+[%# Get the scalar representation of the options reference,
+ # which looks like "ARRAY(0xA352BA3F)" or "HASH(0xA352BA3F)",
+ # so we can figure out whether it is a reference to an array
+ # or a hash.
+ #%]
+[% options_type = BLOCK %][% options %][% END %]
+
+<select name="[% name FILTER html %]"
+ [% IF onchange %]onchange="[% onchange FILTER html %]"[% END %]
+ [% IF multiple %] multiple [% END %]
+ [% IF size %] size="[% size %]" [% END %]>
+ [% IF options_type.search("ARRAY") %]
+ [% FOREACH value = options %]
+ <option value="[% value FILTER html %]"
+ [% " selected" IF value == default %]>
+ [% value FILTER html %]
+ </option>
+ [% END %]
+ [% ELSIF options_type.search("HASH") %]
+ [% FOREACH option = options %]
+ <option value="[% option.value FILTER html %]"
+ [% " selected" IF option.value == default %]>
+ [% option.key FILTER html %]
+ </option>
+ [% END %]
+ [% END %]
+</select>
diff --git a/template/en/default/global/setting-descs.none.tmpl b/template/en/default/global/setting-descs.none.tmpl
new file mode 100644
index 000000000..e96e3169d
--- /dev/null
+++ b/template/en/default/global/setting-descs.none.tmpl
@@ -0,0 +1,54 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Shane H. W. Travis <travis@sedsystems.ca>
+ #
+ #%]
+
+[%# Remember to PROCESS rather than INCLUDE this template. %]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% setting_descs = {
+ "comment_sort_order" => "When viewing $terms.abug, show comments in this order",
+ "csv_colsepchar" => "Field separator character for CSV files",
+ "display_quips" => "Show a quip at the top of each $terms.bug list",
+ "zoom_textareas" => "Zoom textareas large when in use (requires JavaScript)",
+ "newest_to_oldest" => "Newest to Oldest",
+ "newest_to_oldest_desc_first" => "Newest to Oldest, but keep Description at the top",
+ "off" => "Off",
+ "oldest_to_newest" => "Oldest to Newest",
+ "on" => "On",
+ "per_bug_queries" => "Enable tags for $terms.bugs",
+ "post_bug_submit_action" => "After changing $terms.abug",
+ "next_bug" => "Show next $terms.bug in my list",
+ "same_bug" => "Show the updated $terms.bug",
+ "standard" => "Classic",
+ "skin" => "$terms.Bugzilla's general appearance (skin)",
+ "nothing" => "Do Nothing",
+ "state_addselfcc" => "Automatically add me to the CC list of $terms.bugs I change",
+ "always" => "Always",
+ "never" => "Never",
+ "cc_unless_role" => "Only if I have no role on them",
+ "lang" => "Language used in email",
+ "quote_replies" => "Quote the associated comment when you click on its reply link",
+ "quoted_reply" => "Quote the full comment",
+ "simple_reply" => "Reference the comment number only",
+ "comment_box_position" => "Position of the Additional Comments box",
+ "before_comments" => "Before other comments",
+ "after_comments" => "After other comments",
+ "timezone" => "Timezone used to display dates and times",
+ "local" => "Same as the server",
+ }
+%]
+
+[% Hook.process('settings') %]
diff --git a/template/en/default/global/site-navigation.html.tmpl b/template/en/default/global/site-navigation.html.tmpl
new file mode 100644
index 000000000..60a8ddf96
--- /dev/null
+++ b/template/en/default/global/site-navigation.html.tmpl
@@ -0,0 +1,90 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Toms Baugis <toms.baugis@tietoenator.com>
+ # Gervase Markham <gerv@gerv.net>
+ #%]
+
+[%# INTERFACE:
+ # bug.bug_id: integer. Number of current bug (for navigation purposes)
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% USE Bugzilla %]
+[% cgi = Bugzilla.cgi %]
+
+[% IF NOT (cgi.user_agent("MSIE [1-6]") OR cgi.user_agent("Mozilla/4")) %]
+ <link rel="Top" href="[% urlbase FILTER html %]">
+
+ [%# *** Attachment *** %]
+ [% IF attachment && attachment.bug_id %]
+ <link rel="Up" href="show_bug.cgi?id=[% attachment.bug_id FILTER none %]">
+ [% END %]
+
+
+ [%# *** Dependencies, Activity, Print-version *** %]
+ [% IF bug %]
+ <link rel="Show" title="Dependency Tree"
+ href="showdependencytree.cgi?id=[% bug.bug_id %]&amp;hide_resolved=1">
+ [% IF Param('webdotbase') %]
+ <link rel="Show" title="Dependency Graph"
+ href="showdependencygraph.cgi?id=[% bug.bug_id %]">
+ [% END %]
+
+ <link rel="Show" title="[% terms.Bug %] Activity"
+ href="show_activity.cgi?id=[% bug.bug_id %]">
+ <link rel="Show" title="Printer-Friendly Version"
+ href="show_bug.cgi?format=multiple&amp;id=[% bug.bug_id %]">
+ [% END %]
+
+
+ [%# *** Saved Searches *** %]
+ [% IF user.showmybugslink %]
+ [% user_login = user.login FILTER url_quote %]
+ <link rel="Saved&nbsp;Searches" title="My [% terms.Bugs %]"
+ href="[% Param('mybugstemplate').replace('%userid%', user_login) %]">
+ [% END %]
+
+ [% FOREACH q = user.queries_subscribed %]
+ <link rel="Saved&nbsp;Search"
+ title="[% q.name FILTER html %] ([% q.user.login FILTER html %])"
+ href="buglist.cgi?cmdtype=dorem&amp;remaction=run&amp;namedcmd=
+ [% q.name FILTER url_quote %]&amp;sharer_id=
+ [% q.user.id FILTER url_quote %]">
+ [% END %]
+
+ [%# *** Bugzilla Administration Tools *** %]
+ [% IF user.login %]
+ [% '<link rel="Administration" title="Parameters"
+ href="editparams.cgi">' IF user.in_group('tweakparams') %]
+ [% '<link rel="Administration" title="Users"
+ href="editusers.cgi">' IF user.in_group('editusers') %]
+ [% '<link rel="Administration" title="Products" href="editproducts.cgi">'
+ IF user.in_group('editcomponents') || user.get_products_by_permission("editcomponents").size %]
+ [% '<link rel="Administration" title="Flag Types"
+ href="editflagtypes.cgi">' IF user.in_group('editcomponents') %]
+ [% '<link rel="Administration" title="Groups"
+ href="editgroups.cgi">' IF user.in_group('creategroups') %]
+ [% '<link rel="Administration" title="Keywords"
+ href="editkeywords.cgi">' IF user.in_group('editkeywords') %]
+ [% '<link rel="Administration" title="Whining"
+ href="editwhines.cgi">' IF user.in_group('bz_canusewhines') %]
+ [% '<link rel="Administration" title="Sanity Check"
+ href="sanitycheck.cgi">' IF user.in_group('editcomponents') %]
+ [% END %]
+[% END %]
diff --git a/template/en/default/global/tabs.html.tmpl b/template/en/default/global/tabs.html.tmpl
new file mode 100644
index 000000000..85556c482
--- /dev/null
+++ b/template/en/default/global/tabs.html.tmpl
@@ -0,0 +1,56 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ # Myk Melez <myk@mozilla.org>
+ # Marc Schumann <wurblzap@gmail.com>
+ #%]
+
+[%# INTERFACE:
+ # tabs: List of hashes. Must have at least one item. Each hash has:
+ # name: string. Name of the tab.
+ # link: string. relative URL to the tab's resource on this installation.
+ # label: string. text displayed in the tab.
+ # current_tab_name: string. name of the currently selected tab
+ #%]
+
+<div class="tabbed">
+ <table class="tabs" cellspacing="0" cellpadding="10" border="0" width="100%">
+ <tr>
+ <td class="spacer">&nbsp;</td>
+
+ [% FOREACH tab = tabs %]
+ [% IF tab.name == current_tab_name %]
+ <td id="tab_[% tab.name FILTER html %]" class="selected">
+ [% tab.label FILTER html %]</td>
+ [% ELSE %]
+ <td id="tab_[% tab.name FILTER html %]" class="clickable_area"
+ onClick="document.location='[% tab.link FILTER html %]'">
+ <a href="[% tab.link FILTER html %]">[% tab.label FILTER html %]</a>
+ </td>
+ [% END %]
+ [% END %]
+
+ <td class="spacer">&nbsp;</td>
+ </tr>
+ </table>
+
+ <div class="tabbody">
+ [% content %]
+ </div>
+
+</div>
diff --git a/template/en/default/global/textarea.html.tmpl b/template/en/default/global/textarea.html.tmpl
new file mode 100644
index 000000000..c158615bd
--- /dev/null
+++ b/template/en/default/global/textarea.html.tmpl
@@ -0,0 +1,56 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Marc Schumann <wurblzap@gmail.com>
+ #%]
+
+[%# INTERFACE:
+ #
+ # id: (optional) The "id"-attribute of the textarea.
+ # name: (optional) The "name"-attribute of the textarea.
+ # accesskey: (optional) The "accesskey"-attribute of the textarea.
+ # style: (optional) The "style"-attribute of the textarea.
+ # classes: (optional) The "class"-attribute of the textarea.
+ # wrap: (deprecated; optional) The "wrap"-attribute of the textarea.
+ # minrows: (required) Number of rows the textarea shall have initially
+ # and when not having focus.
+ # maxrows: (optional) Number of rows the textarea shall have if
+ # maximized (which happens on getting focus). If not given,
+ # the textarea doesn't maximize when getting focus.
+ # defaultrows: (optional) Number of rows the textarea shall have if
+ # the zoom_textareas user preference if off. If not given,
+ # minrows will be used.
+ # cols: (required) Number of columns the textarea shall have.
+ # defaultcontent: (optional) Default content for the textarea.
+ # mandatory: (optional) Boolean specifying whether or not the textarea
+ # is mandatory.
+ #%]
+
+<textarea [% IF name %]name="[% name FILTER html %]"[% END %]
+ [% IF id %] id="[% id FILTER html %]"[% END %]
+ [% IF accesskey %] accesskey="[% accesskey FILTER html %]"[% END %]
+ [% IF style %] style="[% style FILTER html %]"[% END %]
+ [% IF classes %] class="[% classes FILTER html %]"[% END %]
+ [% IF wrap %] wrap="[% wrap FILTER html %]"[% END %]
+ [% IF disabled %] disabled="disabled"[% END %]
+ [% IF defaultrows && user.settings.zoom_textareas.value == 'off' %]
+ rows="[% defaultrows FILTER html %]"
+ [% ELSE %]
+ rows="[% minrows FILTER html %]"
+ [% END %]
+ cols="[% cols FILTER html %]"
+ [% IF maxrows && user.settings.zoom_textareas.value == 'on' %]
+ onFocus="this.rows=[% maxrows FILTER html %]"
+ [% END %]
+ [% IF mandatory %]
+ aria-required="true"
+ [% END %]>[% defaultcontent FILTER html %]</textarea>
diff --git a/template/en/default/global/useful-links.html.tmpl b/template/en/default/global/useful-links.html.tmpl
new file mode 100644
index 000000000..1f1224980
--- /dev/null
+++ b/template/en/default/global/useful-links.html.tmpl
@@ -0,0 +1,81 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ # Svetlana Harisova <light@rathedg.com>
+ # Marc Schumann <wurblzap@gmail.com>
+ #%]
+
+[%# Migration note: this whole file corresponds to the old %commandmenu%
+ substitution param in 'footerhtml' %]
+
+[% PROCESS global/variables.none.tmpl %]
+
+<ul id="useful-links">
+ <li id="links-actions">
+ [% PROCESS "global/common-links.html.tmpl" qs_suffix = "_bottom" %]
+ </li>
+
+ [%# Saved searches %]
+
+ [% IF user.showmybugslink OR user.queries.size
+ OR user.queries_subscribed.size
+ %]
+ [% print_pipe = 0 %]
+ <li id="links-saved">
+ <ul class="links">
+ [% IF user.showmybugslink %]
+ [% filtered_username = user.login FILTER url_quote %]
+ <li><a href="[% Param('mybugstemplate').replace('%userid%', filtered_username) %]">My [% terms.Bugs %]</a></li>
+ [% print_pipe = 1 %]
+ [% END %]
+
+ [% FOREACH q = user.queries %]
+ [% IF q.link_in_footer %]
+ <li>[% '<span class="separator">| </span>' IF print_pipe %]
+ <a href="buglist.cgi?cmdtype=runnamed&amp;namedcmd=[% q.name FILTER url_quote %]">[% q.name FILTER html %]</a></li>
+ [% print_pipe = 1 %]
+ [% END %]
+ [% END %]
+ [% new_line = print_pipe %]
+ [% print_pipe = 0 %]
+ [% FOREACH q = user.queries_subscribed %]
+ [% IF new_line %]
+ <br>
+ [% new_line = 0 %]
+ [% END %]
+ <li>
+ [% '<span class="separator">| </span>' IF print_pipe %]
+ <a href="buglist.cgi?cmdtype=dorem&amp;remaction=run&amp;namedcmd=
+ [% q.name FILTER url_quote %]&amp;sharer_id=
+ [% q.user.id FILTER url_quote %]"
+ class="shared"
+ title="Shared by [% q.user.identity FILTER html %]"
+ >[% q.name FILTER html FILTER no_break %]</a></li>
+ [% print_pipe = 1 %]
+ [% END %]
+ </ul>
+ </li>
+ [% END %]
+
+ [%# Individual bugs addition %]
+
+ [% PROCESS "global/per-bug-queries.html.tmpl" %]
+
+ [%# Sections of links to more things users can do on this installation. %]
+ [% Hook.process("end") %]
+</ul>
diff --git a/template/en/default/global/user-error.html.tmpl b/template/en/default/global/user-error.html.tmpl
new file mode 100644
index 000000000..b1b8ae56d
--- /dev/null
+++ b/template/en/default/global/user-error.html.tmpl
@@ -0,0 +1,1798 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[%# INTERFACE:
+ # header_done: boolean. True if the Bugzilla header has already been printed.
+ # error: string. The tag of the error, or the error message to be displayed
+ # (deprecated). May contain HTML if it's an error message.
+ #%]
+
+[%# This is a list of all the possible user errors. Please keep them in
+ # alphabetical order by error tag, and leave a blank line between errors.
+ #
+ # Note that you must explicitly filter every single template variable
+ # in this file; if you do not wish to change it, use the "none" filter.
+ #
+ # Extension- or custom-specific error handling can be easily added
+ # via hooks: just place additional code into
+ # template/en/hook/global/user-error-errors.html.tmpl
+ # Note: be aware of uniqueness of error string parameter value, since
+ # nobody can guarantee the hook files processing order in the future.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+[% DEFAULT title = "Error" %]
+
+[% error_message = BLOCK %]
+ [% IF error == "account_creation_disabled" %]
+ [% title = "Account Creation Disabled" %]
+ User account creation has been disabled.
+ <hr>
+ New accounts must be created by an administrator. The
+ maintainer is [% Param("maintainer") %].
+
+ [% ELSIF error == "account_creation_restricted" %]
+ [% title = "Account Creation Restricted" %]
+ User account creation has been restricted.
+ <hr>
+ Contact your administrator or the maintainer
+ ([% Param("maintainer") %]) for information about
+ creating an account.
+
+ [% ELSIF error == "account_disabled" %]
+ [% title = "Account Disabled" %]
+ [% disabled_reason FILTER none %]
+ <hr>
+ If you believe your account should be restored, please
+ send email to [% Param("maintainer") %] explaining why.
+
+ [% ELSIF error == "account_exists" %]
+ [% title = "Account Already Exists" %]
+ There is already an account with
+ [% IF email %]
+ the login name [% email FILTER html %].
+ [% ELSE %]
+ that login name.
+ [% END %]
+
+ [% ELSIF error == "account_locked" %]
+ [% title = "Account Locked" %]
+ Your IP ([% ip_addr FILTER html %]) has been locked out of this
+ account until [% unlock_at FILTER time %], as you have
+ exceeded the maximum number of login attempts.
+
+ [% ELSIF error == "alias_has_comma_or_space" %]
+ [% title = "Invalid Characters In Alias" %]
+ The alias you entered, <em>[% alias FILTER html %]</em>,
+ contains one or more commas or spaces. Aliases cannot contain
+ commas or spaces because those characters are used to separate
+ aliases from each other in lists. Please choose an alias
+ that does not contain commas and spaces.
+
+ [% ELSIF error == "alias_in_use" %]
+ [% title = "Alias In Use" %]
+ [% terms.Bug %] [%+ bug_id FILTER bug_link(bug_id) FILTER none %]
+ has already taken the alias <em>[% alias FILTER html %]</em>.
+ Please choose another one.
+
+ [% ELSIF error == "alias_is_numeric" %]
+ [% title = "Alias Is Numeric" %]
+ You tried to give this [% terms.bug %] the alias <em>[% alias FILTER html %]</em>,
+ but aliases cannot be merely numbers, since they could
+ then be confused with [% terms.bug %] IDs. Please choose an
+ alias containing at least one letter.
+
+ [% ELSIF error == "alias_too_long" %]
+ [% title = "Alias Too Long" %]
+ [% terms.Bug %] aliases cannot be longer than 20 characters.
+ Please choose a shorter alias.
+
+ [% ELSIF error == "auth_cant_create_account" %]
+ [% title = "Can't create accounts" %]
+ This site is using an authentication scheme which does not permit
+ account creation. Please contact an administrator to get a new account
+ created.
+
+ [% ELSIF error == "auth_failure" %]
+ [% title = "Authorization Required" %]
+ [% admindocslinks = {'groups.html' => 'Group Security'} %]
+ Sorry,
+ [% IF group %]
+ you aren't a member of the '[% group FILTER html %]' group,
+ [% END %]
+
+ [% IF reason %]
+ [% IF group %] and [% END %]
+ [% IF reason == "cant_bless" %]
+ you don't have permissions to add or remove people from a group,
+ [% ELSIF reason == "not_visible" %]
+ there are visibility restrictions on certain user groups,
+ [% END %]
+ [% END %]
+
+ [% IF group || reason %] and so [% END %] you are not authorized to
+ [% IF action == "access" %]
+ access
+ [% ELSIF action == "add" %]
+ add new
+ [% ELSIF action == "begin" %]
+ begin
+ [% ELSIF action == "modify" %]
+ modify
+ [% ELSIF action == "delete" %]
+ delete
+ [% ELSIF action == "edit" %]
+ add, modify or delete
+ [% ELSIF action == "run" %]
+ run
+ [% ELSIF action == "schedule" %]
+ schedule
+ [% ELSIF action == "use" %]
+ use
+ [% ELSIF action == "approve" %]
+ approve
+ [% ELSE %]
+ [%+ Hook.process('auth_failure_action') %]
+ [% END %]
+
+ [% IF object == "administrative_pages" %]
+ administrative pages
+ [% ELSIF object == "attachment" %]
+ [% IF attach_id %]
+ attachment #[% attach_id FILTER html %]
+ [% ELSE %]
+ this attachment
+ [% END %]
+ [% ELSIF object == "bugs" %]
+ [%+ terms.bugs %]
+ [% ELSIF object == "charts" %]
+ the "New Charts" feature
+ [% ELSIF object == "classifications" %]
+ classifications
+ [% ELSIF object == "components" %]
+ components
+ [% ELSIF object == "custom_fields" %]
+ custom fields
+ [% ELSIF object == "field_values" %]
+ field values
+ [% ELSIF object == "flagtypes" %]
+ flag types
+ [% ELSIF object == "group_access" %]
+ group access
+ [% ELSIF object == "groups" %]
+ groups
+ [% ELSIF object == "keywords" %]
+ keywords
+ [% ELSIF object == "milestones" %]
+ milestones
+ [% ELSIF object == "multiple_bugs" %]
+ multiple [% terms.bugs %] at once
+ [% ELSIF object == "parameters" %]
+ parameters
+ [% ELSIF object == "products" %]
+ products
+ [% ELSIF object == "quips" %]
+ quips
+ [% ELSIF object == "reports" %]
+ whine reports
+ [% ELSIF object == "sanity_check" %]
+ a sanity check
+ [% ELSIF object == "settings" %]
+ settings
+ [% ELSIF object == "sudo_session" %]
+ a sudo session
+ [% ELSIF object == "timetracking_summaries" %]
+ time-tracking summary reports
+ [% ELSIF object == "user" %]
+ the user [% IF userid %] with ID '[% userid FILTER html %]'
+ [% ELSE %]you specified [% END %]
+ [% ELSIF object == "users" %]
+ users
+ [% ELSIF object == "versions" %]
+ versions
+ [% ELSIF object == "workflow" %]
+ the workflow
+ [% END %].
+
+ [% Hook.process("auth_failure") %]
+
+ [% ELSIF error == "attachment_deletion_disabled" %]
+ [% title = "Attachment Deletion Disabled" %]
+ Attachment deletion is disabled on this installation.
+
+ [% ELSIF error == "attachment_illegal_url" %]
+ [% title = "Illegal Attachment URL" %]
+ <em>[% url FILTER html %]</em> is not a legal URL for attachments.
+ It must start either with http://, https:// or ftp://.
+
+ [% ELSIF error == "attachment_removed" %]
+ [% title = "Attachment Removed" %]
+ The attachment you are attempting to access has been removed.
+
+ [% ELSIF error == "bug_access_denied" %]
+ [% title = "Access Denied" %]
+ [% admindocslinks = {'groups.html' => 'Group Security'} %]
+ You are not authorized to access [% terms.bug %] #[% bug_id FILTER html %].
+
+ [% ELSIF error == "bug_access_query" %]
+ [% title = "Access Denied" %]
+ [% docslinks = {'myaccount.html' => 'Creating an account'} %]
+ You are not authorized to access [% terms.bug %] #[% bug_id FILTER html %].
+ To see this [% terms.bug %], you must
+ first <a href="show_bug.cgi?id=
+ [% bug_id FILTER url_quote %]&amp;GoAheadAndLogIn=1">log
+ in to an account</a> with the appropriate permissions.
+
+ [% ELSIF error == "bug_url_invalid" %]
+ [% title = "Invalid $terms.Bug URL" %]
+ <code>[% url FILTER html %]</code> is not a valid URL to [% terms.abug %].
+ [% IF reason == 'http' %]
+ URLs must start with "http" or "https".
+ [% ELSIF reason == 'path_only' %]
+ You must specify a full URL.
+ [% ELSIF reason == 'show_bug' %]
+ [%+ field_descs.see_also FILTER html %] URLs should point to one of:
+ <ul>
+ <li><code>show_bug.cgi</code> in a [% terms.Bugzilla %]
+ installation.</li>
+ <li>A b[% %]ug on launchpad.net</li>
+ <li>An issue on code.google.com.</li>
+ <li>A b[% %]ug on b[% %]ugs.debian.org.</li>
+ </ul>
+ [% ELSIF reason == 'id' %]
+ There is no valid [% terms.bug %] id in that URL.
+ [% END %]
+
+ [% ELSIF error == "bug_url_too_long" %]
+ [% title = "Invalid $terms.Bug URL" %]
+ [% terms.Bug %] URLs can not be longer than
+ [%+ constants.MAX_BUG_URL_LENGTH FILTER none %] characters long.
+ <code>[% url FILTER html %]</code> is too long.
+
+ [% ELSIF error == "buglist_parameters_required" %]
+ [% title = "Parameters Required" %]
+ [% docslinks = {'query.html' => "Searching for $terms.bugs",
+ 'query.html#list' => "$terms.Bug lists"} %]
+ You may not search, or create saved searches, without any search terms.
+
+ [% ELSIF error == "chart_too_large" %]
+ [% title = "Chart Too Large" %]
+ Sorry, but 2000 x 2000 is the maximum size for a chart.
+
+ [% ELSIF error == "comment_id_invalid" %]
+ [% id FILTER html %] is not a valid comment id.
+
+ [% ELSIF error == "comment_invalid_isprivate" %]
+ You tried to modify the privacy of comment id [% id FILTER html %],
+ but that is not a valid comment on this [% terms.bug %].
+
+ [% ELSIF error == "comment_is_private" %]
+ Comment id [% id FILTER html %] is private.
+
+ [% ELSIF error == "comment_required" %]
+ [% title = "Comment Required" %]
+ You have to specify a
+ [% IF old && new %]
+ <b>comment</b> when changing the status of [% terms.abug %] from
+ [%+ old.name FILTER html %] to [% new.name FILTER html %].
+ [% ELSIF new %]
+ description for this [% terms.bug %].
+ [% ELSE %]
+ <b>comment</b> on this change.
+ [% END %]
+
+ [% ELSIF error == "comment_too_long" %]
+ [% title = "Comment Too Long" %]
+ Comments cannot be longer than
+ [%+ constants.MAX_COMMENT_LENGTH FILTER html %] characters.
+
+ [% ELSIF error == "auth_classification_not_enabled" %]
+ [% title = "Classification Not Enabled" %]
+ Sorry, classification is not enabled.
+
+ [% ELSIF error == "classification_name_too_long" %]
+ [% title = "Classification Name Too Long" %]
+ The name of a classification is limited to [% constants.MAX_CLASSIFICATION_SIZE FILTER html %]
+ characters. '[% name FILTER html %]' is too long ([% name.length %] characters).
+
+[% ELSIF error == "classification_not_specified" %]
+ [% title = "You Must Supply A Classification Name" %]
+ You must enter a classification name.
+
+ [% ELSIF error == "classification_already_exists" %]
+ [% title = "Classification Already Exists" %]
+ A classification with the name '[% name FILTER html %]' already exists.
+
+ [% ELSIF error == "classification_invalid_sortkey" %]
+ [% title = "Invalid Sortkey for Classification" %]
+ The sortkey '[% sortkey FILTER html %]' is invalid. It must be an
+ integer between 0 and [% constants.MAX_SMALLINT FILTER html %].
+
+ [% ELSIF error == "classification_not_deletable" %]
+ [% title = "Default Classification Can Not Be Deleted" %]
+ You can not delete the default classification
+
+ [% ELSIF error == "classification_has_products" %]
+ Sorry, there are products for this classification. You
+ must reassign those products to another classification before you
+ can delete this one.
+
+ [% ELSIF error == "component_already_exists" %]
+ [% title = "Component Already Exists" %]
+ The <em>[% product.name FILTER html %]</em> product already has
+ a component named <em>[% name FILTER html %]</em>.
+
+ [% ELSIF error == "component_blank_description" %]
+ [% title = "Blank Component Description Not Allowed" %]
+ You must enter a non-blank description for this component.
+
+ [% ELSIF error == "component_blank_name" %]
+ [% title = "Blank Component Name Not Allowed" %]
+ You must enter a name for this new component.
+
+ [% ELSIF error == "component_has_bugs" %]
+ [% title = BLOCK %]Component has [% terms.Bugs %][% END %]
+ There are [% nb FILTER html %] [%+ terms.bugs %] entered for this component!
+ You must reassign those [% terms.bugs %] to another component before you
+ can delete this one.
+
+ [% ELSIF error == "component_name_too_long" %]
+ [% title = "Component Name Is Too Long" %]
+ The name of a component is limited to [% constants.MAX_COMPONENT_SIZE FILTER html %]
+ characters. '[% name FILTER html %]' is too long ([% name.length %] characters).
+
+ [% ELSIF error == "component_need_initialowner" %]
+ [% title = "Component Requires Default Assignee" %]
+ A default assignee is required for this component.
+
+ [% ELSIF error == "customfield_nonexistent" %]
+ [% title = "Unknown Custom Field" %]
+ There is no custom field with the name '[% name FILTER html %]'.
+
+ [% ELSIF error == "customfield_not_obsolete" %]
+ [% title = "Custom Field Not Obsolete" %]
+ The custom field '[% name FILTER html %]' is not obsolete.
+ Please obsolete a custom field before attempting to delete it.
+
+ [% ELSIF error == "customfield_has_activity" %]
+ [% title = "Custom Field Has Activity" %]
+ The custom field '[% name FILTER html %]' cannot be deleted because
+ it has recorded activity.
+
+ [% ELSIF error == "customfield_has_contents" %]
+ [% title = "Custom Field Has Contents" %]
+ The custom field '[% name FILTER html %]' cannot be deleted because
+ at least one [% terms.bug %] has a non empty value for this field.
+
+ [% ELSIF error == "dependency_loop_multi" %]
+ [% title = "Dependency Loop Detected" %]
+ The following [% terms.bug %](s) would appear on both the "depends on"
+ and "blocks" parts of the dependency tree if these changes
+ are committed:
+ [% FOREACH dep = deps %]
+ [%+ dep FILTER bug_link(dep) FILTER none %]
+ [% END %].
+ This would create a circular dependency, which is not allowed.
+
+ [% ELSIF error == "dependency_loop_single" %]
+ [% title = "Dependency Loop Detected" %]
+ You can't make [% terms.abug %] block itself or depend on itself.
+
+ [% ELSIF error == "dupe_id_required" %]
+ [% title = "Duplicate $terms.Bug Id Required" %]
+ You must specify [% terms.abug %] id to mark this [% terms.bug %]
+ as a duplicate of.
+
+ [% ELSIF error == "dupe_not_allowed" %]
+ [% title = "Cannot mark $terms.bugs as duplicates" %]
+ You cannot mark [% terms.bugs %] as duplicates when
+ changing several [% terms.bugs %] at once.
+
+ [% ELSIF error == "dupe_loop_detected" %]
+ [% title = "Loop detected among duplicates" %]
+ You cannot mark [% terms.bug %] [%+ bug_id FILTER html %] as
+ a duplicate of
+ [% IF dupe_of == bug_id %]
+ itself
+ [% ELSE %]
+ [%+ terms.bug %] [%+ dupe_of FILTER html %], because it
+ would create a duplicate loop
+ [% END %].
+
+ [% ELSIF error == "email_change_in_progress" %]
+ [% title = "Email Change Already In Progress" %]
+ Email change already in progress; please check your email.
+
+ [% ELSIF error == "email_confirmation_failed" %]
+ [% title = "Email Address Confirmation Failed" %]
+ Email address confirmation failed.
+
+ [% ELSIF error == "email_no_text_plain" %]
+ Your message did not contain any text.[% terms.Bugzilla %] does not
+ accept HTML-only email, or HTML email with attachments.
+
+ [% ELSIF error == "empty_group_description" %]
+ [% title = "The group description can not be empty" %]
+ You must enter a description for the group.
+
+ [% ELSIF error == "empty_group_name" %]
+ [% title = "The group name can not be empty" %]
+ You must enter a name for the group.
+
+ [% ELSIF error == "entry_access_denied" %]
+ [% title = "Permission Denied" %]
+ [% admindocslinks = {'groups.html' => 'Group Security'} %]
+ Sorry, either the product <em>[% product FILTER html %]</em>
+ does not exist or you aren't authorized to
+ enter [% terms.abug %] into it.
+
+ [% ELSIF error == "extension_create_no_name" %]
+ You must specify a name for your extension, as an argument to this script.
+
+ [% ELSIF error == "extension_first_letter_caps" %]
+ The first letter of your extension's name must be a capital letter.
+ (You specified '[% name FILTER html %]'.)
+
+ [% ELSIF error == "field_already_exists" %]
+ [% title = "Field Already Exists" %]
+ The field '[% field.name FILTER html %]'
+ ([% field.description FILTER html %]) already exists. Please
+ choose another name.
+
+ [% ELSIF error == "field_cant_control_self" %]
+ [% title = "Field Can't Control Itself" %]
+ The [% field.description FILTER html %] field can't be set to control
+ itself.
+
+ [% ELSIF error == "field_control_must_be_select" %]
+ [% title = "Invalid Field Type Selected" %]
+ Only drop-down and multi-select fields can be used to control
+ the visibility/values of other fields. [% field.description FILTER html %]
+ is not the right type of field.
+
+ [% ELSIF error == "field_invalid_name" %]
+ [% title = "Invalid Field Name" %]
+ '[% name FILTER html %]' is not a valid name for a field.
+ A name may contain only letters, numbers, and the underscore character.
+
+ [% ELSIF error == "field_invalid_sortkey" %]
+ [% title = "Invalid Sortkey for Field" %]
+ The sortkey [% sortkey FILTER html %] that you have provided for
+ this field is not a valid positive integer.
+
+ [% ELSIF error == "field_missing_description" %]
+ [% title = "Missing Description for Field" %]
+ You must enter a description for this field.
+
+ [% ELSIF error == "field_missing_name" %]
+ [% title = "Missing Name for Field" %]
+ You must enter a name for this field.
+
+ [% ELSIF error == "field_value_control_select_only" %]
+ [% title = "Invalid Value Control Field" %]
+ Only Drop-Down or Multi-Select fields can have a field that
+ controls their values.
+
+ [% ELSIF error == "fieldname_invalid" %]
+ [% title = "Specified Field Does Not Exist" %]
+ The field '[% field.name FILTER html %]' does not exist or
+ cannot be edited with this interface.
+
+ [% ELSIF error == "fieldvalue_already_exists" %]
+ [% title = "Field Value Already Exists" %]
+ The value '[% value.name FILTER html %]' already exists for the
+ [%+ field.description FILTER html %] field.
+
+ [% ELSIF error == "fieldvalue_is_controller" %]
+ [% title = "Value Controls Other Fields" %]
+ You cannot delete the [% value.field.description FILTER html %]
+ '[% value.name FILTER html %]' because
+ [% IF fields.size %]
+ it controls the visibility of the following fields:
+ [%+ fields.join(', ') FILTER html %].
+ [% END %]
+ [% ' Also, ' IF fields.size AND vals.size %]
+ [% IF vals.size %]
+ it controls the visibility of the following field values:
+ <ul>
+ [% FOREACH field_name = vals.keys %]
+ [% FOREACH val = vals.${field_name} %]
+ <li>[% val.field.name FILTER html %]:
+ '[% val.name FILTER html %]'</li>
+ [% END %]
+ [% END %]
+ </ul>
+ [% END %]
+
+ [% ELSIF error == "fieldvalue_is_default" %]
+ [% title = "Specified Field Value Is Default" %]
+ '[% value.name FILTER html %]' is the default value for
+ the '[% field.description FILTER html %]' field and cannot be deleted
+ or disabled.
+ [% IF user.in_group('tweakparams') %]
+ You have to <a href="editparams.cgi?section=bugfields#
+ [%- param_name FILTER url_quote %]">change</a> the default value first.
+ [% END %]
+
+ [% ELSIF error == "fieldvalue_name_too_long" %]
+ [% title = "Field Value Is Too Long" %]
+ The value of a field is limited to
+ [%+ constants.FIELD_VALUE_MAX_SIZE FILTER none %] characters.
+ '[% value FILTER html %]' is too long ([% value.length %] characters).
+
+ [% ELSIF error == "fieldvalue_not_editable" %]
+ [% title = "Field Value Not Editable" %]
+ The value '[% old_value.name FILTER html %]' cannot be renamed because
+ it plays some special role for the '[% field.description FILTER html %]'
+ field.
+
+ [% ELSIF error == "fieldvalue_not_deletable" %]
+ [% title = "Field Value Not Deletable" %]
+ The value '[% value.name FILTER html %]' cannot be removed or
+ disabled, because it plays some special role for the
+ '[% field.description FILTER html %]' field.
+
+ [% ELSIF error == "fieldvalue_reserved_word" %]
+ [% title = "Reserved Word Not Allowed" %]
+ You cannot use the value '[% value FILTER html %]' for the
+ '[% field.description FILTER html %]' field. This value is used internally.
+ Please choose another one.
+
+ [% ELSIF error == "fieldvalue_sortkey_invalid" %]
+ [% title = "Invalid Field Value Sortkey" %]
+ The sortkey '[% sortkey FILTER html %]' for the
+ [%+ field.description FILTER html %] field is not a valid
+ (positive) number.
+
+ [% ELSIF error == "fieldvalue_still_has_bugs" %]
+ [% title = "You Cannot Delete This Field Value" %]
+ You cannot delete the value '[% value.name FILTER html %]' from the
+ [% field.description FILTER html %] field, because there are still
+ [%+ value.bug_count FILTER html %] [%+ terms.bugs %] using it.
+
+ [% ELSIF error == "fieldvalue_undefined" %]
+ [% title = "Undefined Value Not Allowed" %]
+ You must specify a value.
+
+ [% ELSIF error == "file_not_specified" %]
+ [% title = "No File Specified" %]
+ You did not specify a file to attach.
+
+ [% ELSIF error == "file_too_large" %]
+ [% title = "File Too Large" %]
+ The file you are trying to attach is [% filesize FILTER html %]
+ kilobytes (KB) in size. Attachments cannot be more than
+ [%+ Param('maxattachmentsize') %] KB. <br>
+ We recommend that you store your attachment elsewhere
+ [% IF Param("allow_attach_url") %]
+ and then specify the URL to this file on the attachment
+ creation page in the <b>AttachURL</b> field.
+ [% ELSE %]
+ and then insert the URL to it in a comment, or in the URL field
+ for this [% terms.bug %].
+ [% END %]
+ <br>Alternately, if your attachment is an image, you could convert
+ it to a compressible format like JPG or PNG and try again.
+
+ [% ELSIF error == "flag_requestee_needs_privs" %]
+ [% title = "Flag Requestee Needs Privileges" %]
+ [% requestee.identity FILTER html %] does not have permission to set the
+ <em>[% flagtype.name FILTER html %]</em> flag. Please select a user who is
+ a member of the <em>[% flagtype.grant_group.name FILTER html %]</em> group.
+
+ [% ELSIF error == "flag_requestee_unauthorized" %]
+ [% title = "Flag Requestee Not Authorized" %]
+ [% admindocslinks = {'flags-overview.html#flags-admin' => 'Administering Flags',
+ 'groups.html' => 'Group Security'} %]
+ [% docslinks = {'flags-overview.html' => 'An overview on Flags',
+ 'flags.html' => 'Using Flags'} %]
+
+ You asked [% requestee.identity FILTER html %]
+ for <code>[% flag_type.name FILTER html %]</code> on [% terms.bug %]
+ [%+ bug_id FILTER html -%]
+ [% IF attach_id && attach_id > 0 %], attachment [% attach_id FILTER html %][% END %],
+ but that [% terms.bug %] has been restricted to users in certain groups,
+ and the user you asked isn't in all the groups to which
+ the [% terms.bug %] has been restricted.
+ Please choose someone else to ask, or make the [% terms.bug %] accessible
+ to users on its CC: list and add that user to the list.
+
+ [% ELSIF error == "flag_requestee_unauthorized_attachment" %]
+ [% title = "Flag Requestee Not Authorized" %]
+ [% admindocslinks = {'flags-overview.html#flags-admin' => 'Administering Flags',
+ 'groups.html' => 'Group Security'} %]
+ [% docslinks = {'flags-overview.html' => 'An overview on Flags',
+ 'flags.html' => 'Using Flags'} %]
+
+ You asked [% requestee.identity FILTER html %]
+ for <code>[% flag_type.name FILTER html %]</code> on
+ [%+ terms.bug %] [%+ bug_id FILTER html %],
+ attachment [% attach_id FILTER html %], but that attachment
+ is restricted to users in the [% Param("insidergroup") FILTER html %] group,
+ and the user you asked isn't in that group. Please choose someone else
+ to ask, or ask an administrator to add the user to the group.
+
+ [% ELSIF error == "flag_status_invalid" %]
+ [% title = "Flag Status Invalid" %]
+ The flag status <em>[% status FILTER html %]</em>
+ [% IF id %]
+ for flag ID #[% id FILTER html %]
+ [% END %]
+ is invalid.
+
+ [% ELSIF error == "flag_type_cc_list_invalid" %]
+ [% title = "Flag Type CC List Invalid" %]
+ [% admindocslinks = {'flags-overview.html#flags-admin' => 'Administering Flags'} %]
+ The CC list [% cc_list FILTER html %] must be less than 200 characters long.
+
+ [% ELSIF error == "flag_type_component_without_product" %]
+ [% title = "Product Missing" %]
+ A component was selected without a product being selected.
+
+ [% ELSIF error == "flag_type_description_invalid" %]
+ [% title = "Flag Type Description Invalid" %]
+ [% admindocslinks = {'flags-overview.html#flags-admin' => 'Administering Flags'} %]
+ The description must be less than 32K.
+
+ [% ELSIF error == "flag_type_name_invalid" %]
+ [% title = "Flag Type Name Invalid" %]
+ [% admindocslinks = {'flags-overview.html#flags-admin' => 'Administering Flags'} %]
+ The name <em>[% name FILTER html %]</em> must be 1-50 characters long
+ and must not contain any spaces or commas.
+
+ [% ELSIF error == "flag_type_not_multiplicable" %]
+ [% docslinks = {'flags-overview.html' => 'An overview on Flags',
+ 'flags.html' => 'Using Flags'} %]
+ You cannot have several <em>[% type.name FILTER html %]</em> flags
+ for this [% IF attachment %] attachment [% ELSE %] [%+ terms.bug %] [% END %].
+
+ [% ELSIF error == "flag_update_denied" %]
+ [% title = "Flag Modification Denied" %]
+ [% admindocslinks = {'flags-overview.html#flags-admin' => 'Administering Flags',
+ 'groups.html' => 'Group Security'} %]
+ [% docslinks = {'flags-overview.html' => 'An overview on Flags',
+ 'flags.html' => 'Using Flags'} %]
+ You tried to [% IF status == "+" %] grant [% ELSIF status == "-" %] deny
+ [% ELSIF status == "X" %] clear [% ELSE %] request [% END %]
+ <code>[% name FILTER html %]
+ [% IF status == "X" %][% old_status FILTER html %][% END %]</code>.
+
+ Only a user with the required permissions may make this change.
+
+ [% ELSIF error == "format_not_found" %]
+ [% title = "Format Not Found" %]
+ The requested format <em>[% format FILTER html %]</em> does not exist with
+ a content type of <em>[% ctype FILTER html %]</em>.
+
+ [% ELSIF error == "flag_type_sortkey_invalid" %]
+ [% title = "Flag Type Sort Key Invalid" %]
+ The sort key must be an integer between 0 and 32767 inclusive.
+ It cannot be <em>[% sortkey FILTER html %]</em>.
+
+ [% ELSIF error == "freetext_too_long" %]
+ [% title = "Text Too Long" %]
+ The text you entered in the [% field_descs.$field FILTER html %]
+ field is too long ([% text.length FILTER html %] characters,
+ above the maximum length allowed of
+ [%+ constants.MAX_FREETEXT_LENGTH FILTER none %] characters).
+
+ [% ELSIF error == "group_cannot_delete" %]
+ [% title = "Cannot Delete Group" %]
+ The <em>[% group.name FILTER html %]</em> group cannot be deleted because
+ there are
+ <a href="editgroups.cgi?action=del&amp;group=
+ [%- group.id FILTER url_quote %]">records</a>
+ in the database which refer to it. All references to this group must
+ be removed before you can remove it.
+
+ [% ELSIF error == "group_change_denied" %]
+ [% title = "Cannot Add/Remove That Group" %]
+ You tried to add or remove the '[% group.name FILTER html %]' group
+ from [% terms.bug %] [%+ bug.id FILTER html %], but you do not
+ have permissions to do so.
+
+ [% ELSIF error == "group_exists" %]
+ [% title = "The group already exists" %]
+ The group [% name FILTER html %] already exists.
+
+ [% ELSIF error == "group_has_special_role" %]
+ [% title = "Group not deletable" %]
+ [% IF groups.size == 1 %]
+ [% attr = "it" %]
+ [% param = "parameter" %]
+ [% ELSE %]
+ [% attr = "them" %]
+ [% param = "parameters" %]
+ [% END %]
+ The group '[% name FILTER html %]' is used by the
+ '[% groups.join("' and '") FILTER html %]' [% param FILTER html %].
+ In order to delete this group, you first have to change the
+ [%+ param FILTER html %] to make [% attr FILTER html %] point to another group.
+
+
+ [% ELSIF error == "group_invalid_removal" %]
+ You tried to remove [% terms.bug %] [%+ bug.id FILTER html %]
+ from the '[% group.name FILTER html %]' group, but [% terms.bugs %]
+ in the '[% product FILTER html %]' product can not be removed from that
+ group.
+
+ [% ELSIF error == "group_invalid_restriction" %]
+ You tried to restrict [% terms.bug %] [%+ bug.id FILTER html %] to
+ to the '[% group.name FILTER html %]' group, but [% terms.bugs %] in the
+ '[% product FILTER html %]' product can not be restricted to
+ that group.
+
+ [% ELSIF error == "group_not_specified" %]
+ [% title = "Group not specified" %]
+ No group was specified.
+
+ [% ELSIF error == "system_group_not_deletable" %]
+ [% title = "System Groups not deletable" %]
+ <em>[% name FILTER html %]</em> is a system group.
+ This group cannot be deleted.
+
+ [% ELSIF error == "group_unknown" %]
+ [% title = "Unknown Group" %]
+ The group [% name FILTER html %] does not exist. Please specify
+ a valid group name. Create it first if necessary!
+
+ [% ELSIF error == "illegal_attachment_edit" %]
+ [% title = "Unauthorized Action" %]
+ You are not authorized to edit attachment [% attach_id FILTER html %].
+
+ [% ELSIF error == "illegal_attachment_edit_bug" %]
+ [% title = "Unauthorized Action" %]
+ You are not authorized to edit attachments on [% terms.bug %]
+ [%+ bug_id FILTER html %].
+
+ [% ELSIF error == "illegal_attachment_is_patch" %]
+ [% title = "Your Search Makes No Sense" %]
+ The only legal values for the <em>Attachment is patch</em> field are
+ 0 and 1.
+
+ [% ELSIF error == "illegal_bug_status_transition" %]
+ [% title = "Illegal $terms.Bug Status Change" %]
+ [% IF old.defined %]
+ You are not allowed to change the [% terms.bug %] status from
+ [%+ old.name FILTER html %] to [% new.name FILTER html %].
+ [% ELSE %]
+ You are not allowed to file new [% terms.bugs %] with the
+ [%+ new.name FILTER html %] status.
+ [% END %]
+
+ [% ELSIF error == "illegal_change" %]
+ [% title = "Not allowed" %]
+ You tried to change the
+ <strong>[% field_descs.$field FILTER html %]</strong> field
+ [% IF oldvalue.defined %]
+ from <em>[% oldvalue.join(', ') FILTER html %]</em>
+ [% END %]
+ [% IF newvalue.defined %]
+ to <em>[% newvalue.join(', ') FILTER html %]</em>
+ [% END %]
+ , but only
+ [% IF privs < constants.PRIVILEGES_REQUIRED_EMPOWERED %]
+ the assignee
+ [% IF privs < constants.PRIVILEGES_REQUIRED_ASSIGNEE %] or reporter [% END %]
+ of the [% terms.bug %], or
+ [% END %]
+ a user with the required permissions may change that field.
+
+ [% ELSIF error == "illegal_change_deps" %]
+ [% title = "Not allowed" %]
+ You tried to change the
+ <strong>[% field_descs.$field FILTER html %]</strong> field
+ but only a user allowed to edit
+ both related [% terms.bugs %] may change that field.
+
+ [% ELSIF error == "illegal_changed_in_last_x_days" %]
+ [% title = "Your Search Makes No Sense" %]
+ The <em>Changed in last ___ days</em> field must be a simple number.
+ You entered <tt>[% value FILTER html %]</tt>, which isn't.
+
+ [% ELSIF error == "illegal_date" %]
+ [% title = "Illegal Date" %]
+ '<tt>[% date FILTER html %]</tt>' is not a legal date.
+ [% IF format %]
+ Please use the format '<tt>[% format FILTER html %]</tt>'.
+ [% END %]
+
+ [% ELSIF error == "illegal_email_address" %]
+ [% title = "Invalid Email Address" %]
+ The e-mail address you entered (<b>[% addr FILTER html %]</b>)
+ didn't pass our syntax checking for a legal email address.
+ [% IF default %]
+ A legal address must contain exactly one '@',
+ and at least one '.' after the @.
+ [% ELSE %]
+ [%+ Param('emailregexpdesc') %]
+ [% END %]
+ It must also not contain any of these special characters:
+ <tt>\ ( ) &amp; &lt; &gt; , ; : &quot; [ ]</tt>, or any whitespace.
+
+ [% ELSIF error == "illegal_frequency" %]
+ [% title = "Too Frequent" %]
+ Unless you are an administrator, you may not create series which are
+ run more often than once every [% minimum FILTER html %] days.
+
+ [% ELSIF error == "illegal_group_control_combination" %]
+ [% title = "Your Group Control Combination Is Illegal" %]
+ [% admindocslinks = {'groups.html' => 'Assigning Group Controls to Products'} %]
+ Your group control combination for group &quot;
+ [% groupname FILTER html %]&quot; is illegal.
+
+ [% ELSIF error == "illegal_is_obsolete" %]
+ [% title = "Your Search Makes No Sense" %]
+ The only legal values for the <em>Attachment is obsolete</em> field are
+ 0 and 1.
+
+ [% ELSIF error == "illegal_query_name" %]
+ [% title = "Illegal Search Name" %]
+ The name of your search cannot contain any of the following characters:
+ &lt;, &gt;, &amp;.
+
+ [% ELSIF error == "illegal_series_creation" %]
+ [% admindocslinks = {'groups.html' => 'Group security'} %]
+ [% docslinks = {'reporting.html' => 'Reporting'} %]
+ You are not authorized to create series.
+
+ [% ELSIF error == "illegal_series_edit" %]
+ [% admindocslinks = {'groups.html' => 'Group security'} %]
+ [% docslinks = {'reporting.html' => 'Reporting'} %]
+ You are not authorized to edit this series. To do this, you must either
+ be its creator, or an administrator.
+
+ [% ELSIF error == "illegal_time" %]
+ [% title = "Illegal Time" %]
+ '<tt>[% time FILTER html %]</tt>' is not a legal time.
+ [% IF format %]
+ Please use the format '<tt>[% format FILTER html %]</tt>'.
+ [% END %]
+
+ [% ELSIF error == "illegal_regexp" %]
+ [% title = "Illegal Regular Expression" %]
+ The regular expression you provided [% value FILTER html %] is not valid.
+ The error was: [% dberror FILTER html %].
+
+ [% ELSIF error == "insufficient_data_points" %]
+ [% docslinks = {'reporting.html' => 'Reporting'} %]
+ We don't have enough data points to make a graph (yet).
+
+ [% ELSIF error == "invalid_attach_id" %]
+ [% title = "Invalid Attachment ID" %]
+ The attachment id [% attach_id FILTER html %] is invalid.
+
+ [% ELSIF error == "bug_id_does_not_exist" %]
+ [% title = BLOCK %]Invalid [% terms.Bug %] ID[% END %]
+ [% terms.Bug %] #[% bug_id FILTER html %] does not exist.
+
+ [% ELSIF error == "improper_bug_id_field_value" %]
+ [% title = BLOCK %]
+ [% IF bug_id %]Invalid [% ELSE %]Missing [% END %] [% terms.Bug %] ID
+ [% END %]
+ [% IF bug_id %]
+ '[% bug_id FILTER html %]' is not a valid [% terms.bug %] number
+ [% IF Param("usebugaliases") %]
+ nor an alias to [% terms.abug %]
+ [% END %].
+ [% ELSE %]
+ [% IF field %]
+ The '[% field_descs.$field FILTER html %]' field
+ cannot be empty.
+ [% END %]
+ You must enter a valid [% terms.bug %] number!
+ [% END %]
+
+ [% ELSIF error == "invalid_changedsince" %]
+ [% title = "Invalid 'Changed Since'" %]
+ The 'changed since' value, '[% changedsince FILTER html %]', must be an
+ integer >= 0.
+
+ [% ELSIF error == "invalid_content_type" %]
+ [% title = "Invalid Content-Type" %]
+ The content type <em>[% contenttype FILTER html %]</em> is invalid.
+ Valid types must be of the form <em>foo/bar</em> where <em>foo</em>
+ is one of <em>[% constants.LEGAL_CONTENT_TYPES.join(', ') FILTER html %]</em>.
+
+ [% ELSIF error == "invalid_context" %]
+ [% title = "Invalid Context" %]
+ The context [% context FILTER html %] is invalid (must be a number,
+ "file" or "patch").
+
+ [% ELSIF error == "invalid_datasets" %]
+ [% title = "Invalid Datasets" %]
+ Invalid datasets <em>[% datasets.join(":") FILTER html %]</em>. Only digits,
+ letters and colons are allowed.
+
+ [% ELSIF error == "invalid_format" %]
+ [% title = "Invalid Format" %]
+ The format "[% format FILTER html %]" is invalid (must be one of
+ [% FOREACH my_format = formats %]
+ "[% my_format FILTER html %]"
+ [% END %]
+ ).
+
+ [% ELSIF error == "invalid_group_ID" %]
+ [% title = "Invalid group ID" %]
+ The group you specified doesn't exist.
+
+ [% ELSIF error == "invalid_group_name" %]
+ [% title = "Invalid group name" %]
+ The group you specified, [% name FILTER html %], is not valid here.
+
+ [% ELSIF error == "invalid_maxrows" %]
+ [% title = "Invalid Max Rows" %]
+ The maximum number of rows, '[% maxrows FILTER html %]', must be
+ a positive integer.
+
+ [% ELSIF error == "invalid_parameter" %]
+ [% title = "Invalid Parameter" %]
+ The new value for [% name FILTER html %] is invalid: [% err FILTER html %].
+
+ [% ELSIF error == "invalid_product_name" %]
+ [% title = "Invalid Product Name" %]
+ The product name '[% product FILTER html %]' is invalid or does not exist.
+
+ [% ELSIF error == "invalid_regexp" %]
+ [% title = "Invalid regular expression" %]
+ The regular expression you entered is invalid.
+
+ [% ELSIF error == "invalid_user_group" %]
+ [% title = "Invalid User Group" %]
+ [% IF users.size > 1 %] Users [% ELSE %] User [% END %]
+ '[% users.join(', ') FILTER html %]'
+ [% IF users.size > 1 %] are [% ELSE %] is [% END %]
+ not able to edit the
+ [% IF product %]
+ '[% product FILTER html %]'
+ [% END %]
+ [%+ field_descs.product FILTER html %]
+ [% IF bug_id %]
+ for [% terms.bug %] '[% bug_id FILTER html %]'.
+ [% ELSIF new %]
+ and may not be included on a new [% terms.bug %].
+ [% ELSE %]
+ for at least one [% terms.bug %] being changed.
+ [% END %]
+
+ [% ELSIF error == "invalid_username" %]
+ [% title = "Invalid Username" %]
+ The name <tt>[% name FILTER html %]</tt> is not a valid username.
+ Either you misspelled it, or the person has not
+ registered for a [% terms.Bugzilla %] account.
+
+ [% ELSIF error == "invalid_username_or_password" %]
+ [% title = "Invalid Username Or Password" %]
+ The username or password you entered is not valid.
+ [%# People get two login attempts before being warned about
+ # being locked out.
+ #%]
+ [% IF remaining <= 2 %]
+ If you do not enter the correct password after
+ [%+ remaining FILTER html %] more attempt(s), you will be
+ locked out of this account for
+ [%+ constants.LOGIN_LOCKOUT_INTERVAL FILTER html %] minutes.
+ [% END %]
+
+ [% ELSIF error == "json_rpc_get_method_required" %]
+ When using JSON-RPC over GET, you must specify a 'method'
+ parameter. See the documentation at
+ [%+ docs_urlbase FILTER html %]api/Bugzilla/WebService/Server/JSONRPC.html
+
+ [% ELSIF error == "json_rpc_invalid_params" %]
+ Could not parse the 'params' argument as valid JSON.
+ Error: [% err_msg FILTER html %]
+ Value: [% params FILTER html %]
+
+ [% ELSIF error == "json_rpc_invalid_callback" %]
+ You cannot use '[% callback FILTER html %]' as your 'callback' parameter.
+ For security reasons, only letters, numbers, and the following
+ characters are allowed in the 'callback' parameter: <code>[]._</code>
+
+ [% ELSIF error == "json_rpc_post_only" %]
+ For security reasons, you must use HTTP POST to call the
+ '[% method FILTER html %]' method.
+
+ [% ELSIF error == "keyword_already_exists" %]
+ [% title = "Keyword Already Exists" %]
+ A keyword with the name [% name FILTER html %] already exists.
+
+ [% ELSIF error == "keyword_blank_description" %]
+ [% title = "Blank Keyword Description Not Allowed" %]
+ You must enter a non-blank description for the keyword.
+
+ [% ELSIF error == "keyword_blank_name" %]
+ [% title = "Blank Keyword Name Not Allowed" %]
+ You must enter a non-blank name for the keyword.
+
+ [% ELSIF error == "keyword_invalid_name" %]
+ [% title = "Invalid Keyword Name" %]
+ You may not use commas or whitespace in a keyword name.
+
+ [% ELSIF error == "local_file_too_large" %]
+ [% title = "Local File Too Large" %]
+ Local file uploads must not exceed
+ [% Param('maxlocalattachment') %] MB in size.
+
+ [% ELSIF error == "login_needed_for_password_change" %]
+ [% title = "Login Name Required" %]
+ You must enter a login name when requesting to change your password.
+
+ [% ELSIF error == "login_required_for_pronoun" %]
+ [% title = "Login Name Required" %]
+ You can't use %user% without being logged in, because %user% refers
+ to your login name, which we don't know.
+
+ [% ELSIF error == "login_required" %]
+ [%# Used for non-web-based LOGIN_REQUIRED situations. %]
+ You must log in before using this part of [% terms.Bugzilla %].
+
+ [% ELSIF error == "migrate_config_created" %]
+ The file <kbd>[% file FILTER html %]</kbd> contains configuration
+ variables that must be set before continuing with the migration.
+
+ [% ELSIF error == "migrate_from_invalid" %]
+ '[% from FILTER html %]' is not a valid type of [% terms.bug %]-tracker
+ to migrate from. See the contents of the <kbd>B[% %]ugzilla/Migrate/</kbd>
+ directory for a list of valid [% terms.bug %]-trackers.
+
+ [% ELSIF error == "milestone_already_exists" %]
+ [% title = "Milestone Already Exists" %]
+ [% admindocslinks = {'products.html' => 'Administering products',
+ 'milestones.html' => 'About Milestones'} %]
+ The milestone '[% name FILTER html %]' already exists for product '
+ [%- product FILTER html %]'.
+
+ [% ELSIF error == "milestone_blank_name" %]
+ [% title = "Blank Milestone Name Not Allowed" %]
+ You must enter a name for this milestone.
+
+ [% ELSIF error == "milestone_is_default" %]
+ [% title = "Default milestone not deletable" %]
+ [% admindocslinks = {'products.html' => 'Administering products',
+ 'milestones.html' => 'About Milestones'} %]
+ Sorry, but [% milestone.name FILTER html %] is the default milestone
+ for the '[% milestone.product.name FILTER html %]' product, and so
+ it cannot be deleted.
+
+ [% ELSIF error == "milestone_name_too_long" %]
+ [% title = "Milestone Name Is Too Long" %]
+ The name of a milestone is limited to [% constants.MAX_MILESTONE_SIZE FILTER html %]
+ characters. '[% name FILTER html %]' is too long ([% name.length %] characters).
+
+ [% ELSIF error == "milestone_required" %]
+ [% title = "Milestone Required" %]
+ You must select a target milestone for [% terms.bug %]
+ [%+ bug.id FILTER html %]
+ if you are going to accept it. Part of accepting
+ [%+ terms.abug %] is giving an estimate of when it will be fixed.
+
+ [% ELSIF error == "milestone_sortkey_invalid" %]
+ [% title = "Invalid Milestone Sortkey" %]
+ The sortkey '[% sortkey FILTER html %]' is not in the range
+ [%+ constants.MIN_SMALLINT FILTER html %] &le; sortkey &le;
+ [%+ constants.MAX_SMALLINT FILTER html %].
+
+ [% ELSIF error == "misarranged_dates" %]
+ [% title = "Misarranged Dates" %]
+ Your start date ([% datefrom FILTER html %]) is after
+ your end date ([% dateto FILTER html %]).
+
+ [% ELSIF error == "missing_attachment_description" %]
+ [% title = "Missing Attachment Description" %]
+ You must enter a description for the attachment.
+
+ [% ELSIF error == "missing_category" %]
+ [% title = "Missing Category" %]
+ You did not specify a category for this series.
+
+ [% ELSIF error == "missing_component" %]
+ [% title = "Missing Component" %]
+ [% admindocslinks = {'products.html' => 'Administering products',
+ 'components.html' => 'Creating a component'} %]
+ Sorry, the product <em>[% product.name FILTER html %]</em>
+ has to have at least one component in order for you to
+ enter [% terms.abug %] into it.<br>
+ [% IF user.in_group("editcomponents", product.id) %]
+ <a href="editcomponents.cgi?action=add&amp;product=[% product.name FILTER url_quote %]">Create
+ a new component</a>.
+ [% ELSE %]
+ Please contact [% Param("maintainer") %] and ask them
+ to add a component to this product.
+ [% END %]
+
+ [% ELSIF error == "missing_content_type" %]
+ [% title = "Missing Content-Type" %]
+ You asked [% terms.Bugzilla %] to auto-detect the content type, but
+ your browser did not specify a content type when uploading the file,
+ so you must enter a content type manually.
+
+ [% ELSIF error == "missing_content_type_method" %]
+ [% title = "Missing Content-Type Determination Method" %]
+ You must choose a method for determining the content type,
+ either <em>auto-detect</em>, <em>select from list</em>, or <em>enter
+ manually</em>.
+
+ [% ELSIF error == "missing_cookie" %]
+ [% title = "Missing Cookie" %]
+ Sorry, I seem to have lost the cookie that recorded
+ the results of your last search. I'm afraid you will have to start
+ again from the <a href="query.cgi">search page</a>.
+
+ [% ELSIF error == "missing_datasets" %]
+ [% title = "No Datasets Selected" %]
+ [% docslinks = {'reporting.html' => 'Reporting'} %]
+ You must specify one or more datasets to plot.
+
+ [% ELSIF error == "missing_frequency" %]
+ [% title = "Missing Frequency" %]
+ [% docslinks = {'reporting.html' => 'Reporting'} %]
+ You did not specify a valid frequency for this series.
+
+ [% ELSIF error == "missing_name" %]
+ [% title = "Missing Name" %]
+ [% docslinks = {'reporting.html' => 'Reporting'} %]
+ You did not specify a name for this series.
+
+ [% ELSIF error == "missing_query" %]
+ [% title = "Missing Search" %]
+ [% docslinks = {'query.html' => "Searching for $terms.bugs",
+ 'query.html#list' => "$terms.Bug lists"} %]
+ The search named <em>[% queryname FILTER html %]</em>
+ [% IF sharer_id && sharer_id != user.id %]
+ has not been made visible to you.
+ [% ELSE %]
+ does not exist.
+ [% END %]
+
+ [% ELSIF error == "missing_resolution" %]
+ [% title = "Resolution Required" %]
+ A valid resolution is required to mark [% terms.bugs %] as
+ [%+ status FILTER upper FILTER html %].
+
+ [% ELSIF error == "missing_subcategory" %]
+ [% title = "Missing Subcategory" %]
+ You did not specify a subcategory for this series.
+
+ [% ELSIF error == "missing_version" %]
+ [% title = "Missing Version" %]
+ [% admindocslinks = {'versions.html' => 'Defining versions'} %]
+ Sorry, the product <em>[% product.name FILTER html %]</em>
+ has to have at least one version in order for you to
+ enter [% terms.abug %] into it.<br>
+ [% IF user.in_group("editcomponents", product.id) %]
+ <a href="editversions.cgi?action=add&amp;product=[% product.name FILTER url_quote %]">Create
+ a new version</a>.
+ [% ELSE %]
+ Please contact [% Param("maintainer") %] and ask them
+ to add a version to this product.
+ [% END %]
+
+ [% ELSIF error == "multiple_alias_not_allowed" %]
+ You cannot set aliases when modifying multiple [% terms.bugs %]
+ at once.
+
+ [% ELSIF error == "need_quip" %]
+ [% title = "Quip Required" %]
+ [% docslinks = {'quips.html' => 'About quips'} %]
+ Please enter a quip in the text field.
+
+ [% ELSIF error == "new_password_missing" %]
+ [% title = "New Password Missing" %]
+ You must enter a new password.
+
+ [% ELSIF error == "no_axes_defined" %]
+ [% title = "No Axes Defined" %]
+ [% docslinks = {'reporting.html' => 'Reporting'} %]
+ You didn't define any axes to plot.
+
+ [% ELSIF error == "no_bugs_chosen" %]
+ [% title = BLOCK %]No [% terms.Bugs %] Selected[% END %]
+ You apparently didn't choose any [% terms.bugs %]
+ [% IF action == "modify" %]
+ to modify.
+ [% ELSIF action == "view" %]
+ to view.
+ [% END %]
+
+ [% ELSIF error == "no_bug_ids" %]
+ [% title = BLOCK %]No [% terms.Bugs %] Selected[% END %]
+ You didn't choose any [% terms.bugs %] to
+ [% IF action == "add" %] add to [% ELSE %] remove from [% END %]
+ the [% tag FILTER html %] tag.
+
+ [% ELSIF error == "no_bugs_in_list" %]
+ [% title = "Delete Tag?" %]
+ This will remove all [% terms.bugs %] from the
+ <em>[% name FILTER html %]</em> tag. This will delete the tag completely. Click
+ <a href="buglist.cgi?cmdtype=dorem&amp;remaction=forget&amp;namedcmd=
+ [%- name FILTER url_quote %]&amp;token=
+ [%- issue_hash_token([query_id, name]) FILTER url_quote %]">here</a>
+ if you really want to delete it.
+
+ [% ELSIF error == "no_bugs_to_remove" %]
+ [% title = "No Tag Selected" %]
+ You didn't select a tag from which to remove [% terms.bugs %].
+
+ [% ELSIF error == "no_initial_bug_status" %]
+ [% title = "No Initial $terms.Bug Status" %]
+ No [% terms.bug %] status is available on [% terms.bug %] creation.
+ Please report the problem to [% Param("maintainer") %].
+
+ [% ELSIF error == "no_new_quips" %]
+ [% title = "No New Quips" %]
+ [% admindocslinks = {'quips.html' => 'Controlling quip usage'} %]
+ This site does not permit the addition of new quips.
+
+ [% ELSIF error == "no_page_specified" %]
+ [% title = "No Page Specified" %]
+ You did not specify the id of a page to display.
+
+ [% ELSIF error == "no_products" %]
+ [% title = "No Products" %]
+ [% admindocslinks = {'products.html' => 'Setting up a product',
+ 'components.html' => 'Adding components to products',
+ 'groups.html' => 'Groups security'} %]
+ Either no products have been defined to enter [% terms.bugs %] against or you have not
+ been given access to any.
+
+ [% ELSIF error == "number_not_numeric" %]
+ [% title = "Numeric Value Required" %]
+ The value '[% num FILTER html %]' in the
+ <em>[% field_descs.$field FILTER html %]</em> field
+ is not a numeric value.
+
+ [% ELSIF error == "number_too_large" %]
+ [% title = "Number Too Large" %]
+ The value '[% num FILTER html %]' in the
+ <em>[% field_descs.$field FILTER html %]</em> field
+ is more than the maximum allowable value of '[% max_num FILTER html %]'.
+
+ [% ELSIF error == "number_too_small" %]
+ [% title = "Number Too Small" %]
+ The value '[% num FILTER html %]'
+ in the <em>[% field_descs.$field FILTER html %]</em> field
+ is less than the minimum allowable value of '[% min_num FILTER html %]'.
+
+ [% ELSIF error == "object_not_specified" %]
+ [% type = BLOCK %][% INCLUDE object_name class = class %][% END %]
+ [% title = BLOCK %][% type FILTER ucfirst FILTER html %] Not
+ Specified[% END %]
+ You must select/enter a [% type FILTER html %].
+
+ [% ELSIF error == "object_does_not_exist" %]
+ [% type = BLOCK %][% INCLUDE object_name class = class %][% END %]
+ [% title = BLOCK %]Invalid [% type FILTER ucfirst FILTER html %][% END %]
+ There is no [% type FILTER html %]
+ [% IF id.defined %]
+ with the id '[% id FILTER html %]'
+ [% ELSE %]
+ named '[% name FILTER html %]'
+ [% END %]
+ [% IF product.defined %]
+ in the '[% product.name FILTER html %]' product
+ [% END %].
+ [% IF class == "Bugzilla::User" %]
+ Either you mis-typed the name or that user has not yet registered
+ for a [% terms.Bugzilla %] account.
+ [% END %]
+ [% IF class == "Bugzilla::Keyword" %]
+ The legal keyword names are <a href="describekeywords.cgi">listed
+ here</a>.
+ [% END %]
+
+ [% ELSIF error == "old_password_incorrect" %]
+ [% title = "Incorrect Old Password" %]
+ You did not enter your old password correctly.
+
+ [% ELSIF error == "old_password_required" %]
+ [% title = "Old Password Required" %]
+ You must enter your old password to change your email address.
+
+ [% ELSIF error == "password_change_requests_not_allowed" %]
+ [% title = "Password Change Requests Not Allowed" %]
+ The system is not configured to allow password change requests.
+
+ [% ELSIF error == "passwords_dont_match" %]
+ [% title = "Passwords Don't Match" %]
+ The two passwords you entered did not match.
+
+ [% ELSIF error == "password_current_too_short" %]
+ [% title = "New Password Required" %]
+ Your password is currently less than
+ [%+ constants.USER_PASSWORD_MIN_LENGTH FILTER html %] characters long,
+ which is the new minimum length required for passwords.
+ You must <a href="token.cgi?a=reqpw&loginname=[% locked_user.email FILTER html %]">
+ request a new password</a> in order to log in again.
+
+ [% ELSIF error == "password_too_short" %]
+ [% title = "Password Too Short" %]
+ The password must be at least
+ [%+ constants.USER_PASSWORD_MIN_LENGTH FILTER html %] characters long.
+
+ [% ELSIF error == "patch_too_large" %]
+ [% title = "File Too Large" %]
+ The file you are trying to attach is [% filesize FILTER html %]
+ kilobytes (KB) in size.
+ Patches cannot be more than [% Param('maxattachmentsize') %] KB in size.
+ Try splitting your patch into several pieces.
+
+ [% ELSIF error == "product_access_denied" %]
+ Either the product
+ [%+ IF id.defined %]
+ with the id [% id FILTER html %]
+ [% ELSE %]
+ '[% name FILTER html %]'
+ [% END %]
+ does not exist or you don't have access to it.
+
+ [% ELSIF error == "product_illegal_group" %]
+ [% title = "Illegal Group" %]
+ [% group.name FILTER html %] is not an active [% terms.bug %] group
+ and so you cannot edit group controls for it.
+
+ [% ELSIF error == "product_name_already_in_use" %]
+ [% title = "Product name already exists" %]
+ [% admindocslinks = {'products.html' => 'Administering products'} %]
+ The product name '[% product FILTER html %]' already exists.
+
+ [% ELSIF error == "product_name_diff_in_case" %]
+ [% title = "Product name differs only in case" %]
+ [% admindocslinks = {'products.html' => 'Administering products'} %]
+ The product name '[% product FILTER html %]' differs from existing
+ product '[% existing_product FILTER html %]' only in case.
+
+ [% ELSIF error == "product_name_too_long" %]
+ [% title = "Product name too long" %]
+ The name of a product is limited to [% constants.MAX_PRODUCT_SIZE FILTER html %]
+ characters. '[% name FILTER html %]' is too long ([% name.length %] characters).
+
+ [% ELSIF error == "product_must_define_defaultmilestone" %]
+ [% title = "Must define new default milestone" %]
+ [% admindocslinks = {'products.html' => 'Administering products',
+ 'milestones.html' => 'About Milestones'} %]
+ You must <a href="editmilestones.cgi?action=add&amp;product=[% product FILTER url_quote %]">
+ create the milestone '[% milestone FILTER html %]'</a> before
+ it can be made the default milestone for product '[% product FILTER html %]'.
+
+ [% ELSIF error == "product_admin_denied" %]
+ [% title = "Product Access Denied" %]
+ You are not allowed to edit properties of product '[% product FILTER html %]'.
+
+ [% ELSIF error == "product_blank_name" %]
+ [% title = "Blank Product Name Not Allowed" %]
+ [% admindocslinks = {'products.html' => 'Administering products'} %]
+ You must enter a name for the product.
+
+ [% ELSIF error == "product_disabled" %]
+ [% title = BLOCK %]Product closed for [% terms.Bug %] Entry[% END %]
+ [% admindocslinks = {'products.html' => 'Administering products'} %]
+ Sorry, entering [% terms.abug %] into the
+ product <em>[% product.name FILTER html %]</em> has been disabled.
+
+ [% ELSIF error == "product_edit_denied" %]
+ [% title = "Product Edit Access Denied" %]
+ [% admindocslinks = {'products.html' => 'Administering products',
+ 'groups.html' => 'Group security'} %]
+ You are not permitted to edit [% terms.bugs %] in product
+ [%+ product FILTER html %].
+
+ [% ELSIF error == "product_has_bugs" %]
+ [% title = BLOCK %]Product has [% terms.Bugs %][% END %]
+ [% admindocslinks = {'products.html' => 'Administering products'} %]
+ There are [% nb FILTER html %] [%+ terms.bugs %] entered for this product!
+ You must move those [% terms.bugs %] to another product before you
+ can delete this one.
+
+ [% ELSIF error == "product_must_have_description" %]
+ [% title = "Product needs Description" %]
+ [% admindocslinks = {'products.html' => 'Administering products'} %]
+ You must enter a description for this product.
+
+ [% ELSIF error == "product_must_have_version" %]
+ [% title = "Product needs Version" %]
+ [% admindocslinks = {'products.html' => 'Administering products',
+ 'versions.html' => 'Administering versions'} %]
+ You must enter a valid version to create a new product.
+
+ [% ELSIF error == "query_name_exists" %]
+ [% title = "Search Name Already In Use" %]
+ The name <em>[% name FILTER html %]</em> is already used by another
+ saved search. You first have to
+ <a href="buglist.cgi?cmdtype=dorem&amp;remaction=forget&amp;namedcmd=
+ [%- name FILTER url_quote %]&amp;token=
+ [% issue_hash_token([query_id, name]) FILTER url_quote %]">delete</a>
+ it if you really want to use this name.
+
+ [% ELSIF error == "query_name_missing" %]
+ [% title = "No Search Name Specified" %]
+ [% docslinks = {'query.html#list' => "$terms.Bug lists"} %]
+ You must enter a name for your search.
+
+ [% ELSIF error == "query_name_too_long" %]
+ [% title = "Query Name Too Long" %]
+ The name of the query must be less than [% constants.MAX_LEN_QUERY_NAME FILTER html %]
+ characters long.
+
+ [% ELSIF error == "quicksearch_unknown_field" %]
+ [% title = "QuickSearch Error" %]
+ There is a problem with your search:
+ [% FOREACH field = unknown %]
+ <p><code>[% field FILTER html %]</code> is not a valid field name.</p>
+ [% END %]
+ [% FOREACH field = ambiguous.keys %]
+ <p><code>[% field FILTER html %]</code> matches more than one field:
+ [%+ ambiguous.${field}.join(', ') FILTER html %]</p>
+ [% END %]
+
+ [% IF unknown.size %]
+ <p>The legal field names are
+ <a href="page.cgi?id=quicksearch.html#fields">listed here</a>.</p>
+ [% END %]
+
+ [% ELSIF error == "reassign_to_empty" %]
+ [% title = "Illegal Reassignment" %]
+ To reassign [% terms.abug %], you must provide an address for
+ the new assignee.
+
+ [% ELSIF error == "require_component" %]
+ [% title = "Component Needed" %]
+ To file this [% terms.bug %], you must first choose a component.
+ If necessary, just guess.
+
+ [% ELSIF error == "relationship_loop_single" %]
+ [% title = "Relationship Loop Detected" %]
+ [% field_descs.$field_name FILTER html %]
+ for [% terms.bug %] [%+ bug_id FILTER html %]
+ has a circular dependency on [% terms.bug %] [%+ dep_id FILTER html %].
+
+ [% ELSIF error == "request_queue_group_invalid" %]
+ The group field <em>[% group FILTER html %]</em> is invalid.
+
+ [% ELSIF error == "require_new_password" %]
+ [% title = "New Password Needed" %]
+ You cannot change your password without choosing a new one.
+
+ [% ELSIF error == "required_field" %]
+ [% title = "Field Must Be Set" %]
+ A value must be set for the '[% field_descs.${field.name} FILTER html %]'
+ field.
+
+ [% ELSIF error == "require_summary" %]
+ [% title = "Summary Needed" %]
+ You must enter a summary for this [% terms.bug %].
+
+ [% ELSIF error == "resolution_cant_clear" %]
+ [% terms.Bug %] [%+ bug_id FILTER bug_link(bug_id) FILTER none %] is
+ closed, so you cannot clear its resolution.
+
+ [% ELSIF error == "resolution_not_allowed" %]
+ [% title = "Resolution Not Allowed" %]
+ You cannot set a resolution for open [% terms.bugs %].
+
+ [% ELSIF error == "saved_search_used_by_whines" %]
+ [% title = "Saved Search In Use" %]
+ [% docslinks = {'whining.html' => 'About Whining'} %]
+ The saved search <em>[% search_name FILTER html %]</em> is being used
+ by <a href="editwhines.cgi">Whining events</a> with the following subjects:
+ [%+ subjects FILTER html %]
+
+ [% ELSIF error == "search_content_without_matches" %]
+ [% title = "Illegal Search" %]
+ The "content" field can only be used with "matches" search
+ and the "matches" search can only be used with the "content"
+ field.
+
+ [% ELSIF error == "series_already_exists" %]
+ [% title = "Series Already Exists" %]
+ [% docslinks = {'reporting.html' => 'Reporting'} %]
+ A series named <em>[% series.category FILTER html %] /
+ [%+ series.subcategory FILTER html %] /
+ [%+ series.name FILTER html %]</em>
+ already exists.
+
+ [% ELSIF error == "sidebar_supports_mozilla_only" %]
+ Sorry - sidebar.cgi currently only supports Mozilla based web browsers.
+ <a href="http://www.mozilla.org">Upgrade today</a>. :-)
+
+ [% ELSIF error == "still_unresolved_bugs" %]
+ [% title = "Unresolved Dependencies" %]
+ [% terms.Bug %] [%+ bug_id FILTER bug_link(bug_id) FILTER none %]
+ has [% dep_count FILTER none %] unresolved
+ [% IF dep_count == 1 %]
+ dependency
+ [% ELSE %]
+ dependencies
+ [% END %].
+ They must either be resolved or removed from the
+ "[% field_descs.dependson FILTER html %]" field before you can resolve
+ this [% terms.bug %] as [% display_value("resolution", "FIXED") FILTER html %].
+
+ [% ELSIF error == "sudo_invalid_cookie" %]
+ [% title = "Invalid Sudo Cookie" %]
+ Your sudo cookie is invalid. Either it expired or you didn't start
+ a sudo session correctly. Refresh the page or load another page
+ to continue what you are doing as yourself.
+
+ [% ELSIF error == "sudo_illegal_action" %]
+ [% title = "Impersonation Not Authorized" %]
+ [% IF NOT sudoer.in_group("bz_sudoers") %]
+ You are not allowed to impersonate users.
+ [% ELSIF target_user AND target_user.in_group("bz_sudo_protect") %]
+ You are not allowed to impersonate [% target_user.identity FILTER html %].
+ [% ELSE %]
+ The user you tried to impersonate doesn't exist.
+ [% END %]
+
+ [% ELSIF error == "sudo_in_progress" %]
+ [% title = "Session In Progress" %]
+ A sudo session (impersonating [% target FILTER html %]) is in progress.
+ End that session (using the link in the footer) before starting a new one.
+
+ [% ELSIF error == "sudo_password_required" %]
+ [% title = "Password Required" %]
+ Your [% terms.Bugzilla %] password is required to begin a sudo
+ session. Please <a href="relogin.cgi?action=prepare-sudo&target_login=
+ [%- target_login FILTER html %]&reason=
+ [%- reason FILTER html %]">go back</a> and enter your password.
+
+ [% ELSIF error == "sudo_preparation_required" %]
+ [% title = "Preparation Required" %]
+ You may not start a sudo session directly. Please
+ <a href="relogin.cgi?action=prepare-sudo&target_login=
+ [%- target_login FILTER html %]&reason=
+ [%- reason FILTER html %]">start your session normally</a>.
+
+ [% ELSIF error == "sudo_protected" %]
+ [% title = "User Protected" %]
+ The user [% login FILTER html %] may not be impersonated by sudoers.
+
+ [% ELSIF error == "token_does_not_exist" %]
+ [% title = "Token Does Not Exist" %]
+ The token you submitted does not exist, has expired, or has
+ been canceled.
+
+ [% ELSIF error == "too_soon_for_new_token" %]
+ [% title = "Too Soon For New Token" %]
+ You have requested
+ [% IF type == "password" %]
+ a password
+ [% ELSIF type == "account" %]
+ an account
+ [% END %]
+ token too recently to request another. Please wait a while and try again.
+
+ [% ELSIF error == "unknown_action" %]
+ [% IF action %]
+ Unknown action [% action FILTER html %]!
+ [% ELSE %]
+ I could not figure out what you wanted to do.
+ [% END %]
+
+ [% ELSIF error == "unknown_tab" %]
+ [% title = "Unknown Tab" %]
+ <code>[% current_tab_name FILTER html %]</code> is not a legal tab name.
+
+ [% ELSIF error == "version_already_exists" %]
+ [% title = "Version Already Exists" %]
+ [% admindocslinks = {'versions.html' => 'Administering versions'} %]
+ The version '[% name FILTER html %]' already exists for product '
+ [%- product FILTER html %]'.
+
+ [% ELSIF error == "version_blank_name" %]
+ [% title = "Blank Version Name Not Allowed" %]
+ You must enter a name for this version.
+
+ [% ELSIF error == "version_has_bugs" %]
+ [% title = BLOCK %]Version has [% terms.Bugs %][% END %]
+ There are [% nb FILTER html %] [%+ terms.bugs %] associated with this
+ version! You must reassign those [% terms.bugs %] to another version
+ before you can delete this one.
+
+ [% ELSIF error == "users_deletion_disabled" %]
+ [% title = "Deletion not activated" %]
+ [% admindocslinks = {'useradmin.html' => 'User administration'} %]
+ Sorry, the deletion of user accounts is not allowed.
+
+ [% ELSIF error == "user_has_responsibility" %]
+ [% title = "Can't Delete User Account" %]
+ [% admindocslinks = {'useradmin.html' => 'User administration'} %]
+ The user you want to delete is set up as the default [% terms.bug %]
+ assignee
+ [% IF Param('useqacontact') %]
+ or QA contact
+ [% END %]
+ for at least one component.
+ For this reason, you cannot delete the account at this time.
+
+ [% ELSIF error == "user_access_by_id_denied" %]
+ [% title = "User Access By Id Denied" %]
+ Logged-out users cannot use the "ids" argument to this function
+ to access any user information.
+
+ [% ELSIF error == "user_access_by_match_denied" %]
+ [% title = "User-Matching Denied" %]
+ Logged-out users cannot use the "match" argument to this function
+ to access any user information.
+
+ [% ELSIF error == "user_login_required" %]
+ [% title = "Login Name Required" %]
+ [% admindocslinks = {'useradmin.html' => 'User administration'} %]
+ You must enter a login name for the new user.
+
+ [% ELSIF error == "user_match_failed" %]
+ [% title = "Match Failed" %]
+ <tt>[% name FILTER html %]</tt> does not exist or you are not allowed
+ to see that user.
+
+ [% ELSIF error == "user_match_too_many" %]
+ [% title = "No Conclusive Match" %]
+ [% terms.Bugzilla %] cannot make a conclusive match for one or more
+ of the names and/or email addresses you entered for
+ the [% fields.join(', ') FILTER html %] field(s).
+
+ [% ELSIF error == "user_not_insider" %]
+ [% title = "User Not In Insidergroup" %]
+ Sorry, but you are not allowed to (un)mark comments or attachments
+ as private.
+
+ [% ELSIF error == "wrong_token_for_cancelling_email_change" %]
+ [% title = "Wrong Token" %]
+ That token cannot be used to cancel an email address change.
+
+ [% ELSIF error == "wrong_token_for_changing_passwd" %]
+ [% title = "Wrong Token" %]
+ That token cannot be used to change your password.
+
+ [% ELSIF error == "wrong_token_for_confirming_email_change" %]
+ [% title = "Wrong Token" %]
+ That token cannot be used to change your email address.
+
+ [% ELSIF error == "wrong_token_for_creating_account" %]
+ [% title = "Wrong Token" %]
+ That token cannot be used to create a user account.
+
+ [% ELSIF error == "xmlrpc_invalid_value" %]
+ "[% value FILTER html %]" is not a valid value for a
+ &lt;[% type FILTER html %]&gt; field. (See the XML-RPC specification
+ for details.)
+
+ [% ELSIF error == "zero_length_file" %]
+ [% title = "File Is Empty" %]
+ The file you are trying to attach is empty, does not exist, or you don't
+ have permission to read it.
+
+ [% ELSIF error == "illegal_user_id" %]
+ [% title = "Illegal User ID" %]
+ User ID '[% userid FILTER html %]' is not valid integer.
+
+ [% ELSE %]
+
+ [%# Try to find hooked error messages %]
+ [% error_message = Hook.process("errors") %]
+
+ [% IF not error_message %]
+ [% title = "Error string not found" %]
+ The user error string <code>[% error FILTER html %]</code> was not found.
+ Please send email to [% Param("maintainer") %] describing the steps taken
+ to obtain this message.
+ [% ELSE %]
+ [% error_message FILTER none %]
+ [% END %]
+ [% END %]
+[% END %]
+
+[%# We only want HTML error messages for ERROR_MODE_WEBPAGE %]
+[% USE Bugzilla %]
+[% IF Bugzilla.error_mode != constants.ERROR_MODE_WEBPAGE %]
+ [% IF Bugzilla.usage_mode == constants.USAGE_MODE_BROWSER %]
+ [% error_message FILTER none %]
+ [% ELSE %]
+ [% error_message FILTER txt %]
+ [% END %]
+ [% RETURN %]
+[% END %]
+
+[% UNLESS header_done %]
+ [% PROCESS global/header.html.tmpl %]
+[% END %]
+
+[% PROCESS global/docslinks.html.tmpl
+ docslinks = docslinks
+ admindocslinks = admindocslinks
+%]
+
+<table cellpadding="20">
+ <tr>
+ <td id="error_msg" class="throw_error">
+ [% error_message FILTER none %]
+ </td>
+ </tr>
+</table>
+
+<p>
+ Please press <b>Back</b> and try again.
+</p>
+
+[%# If a saved search fails, people want the ability to edit or delete it.
+ # This is the best way of getting information about that possible saved
+ # search from any error call location. %]
+
+[% namedcmd = Bugzilla.cgi.param("namedcmd") %]
+[% sharer_id = Bugzilla.cgi.param("sharer_id") %]
+[% IF namedcmd AND error != "missing_query"
+ AND error != "saved_search_used_by_whines"
+ AND !sharer_id %]
+ <p>
+ Alternatively, you can
+ <a href="buglist.cgi?cmdtype=dorem&amp;remaction=forget&amp;namedcmd=
+ [% namedcmd FILTER url_quote %]">forget</a>
+
+ [% FOREACH q = Bugzilla.user.queries %]
+ [% IF q.name == namedcmd %]
+ or <a href="query.cgi?[% q.url FILTER html %]">edit</a>
+ [% END %]
+ [% END %]
+
+ the saved search '[% namedcmd FILTER html %]'.
+ </p>
+[% END %]
+
+[% PROCESS global/footer.html.tmpl %]
+
+[% BLOCK object_name %]
+ [% IF class == "Bugzilla::Attachment" %]
+ attachment
+ [% ELSIF class == "Bugzilla::User" %]
+ user
+ [% ELSIF class == "Bugzilla::Component" %]
+ component
+ [% ELSIF class == "Bugzilla::Version" %]
+ version
+ [% ELSIF class == "Bugzilla::Milestone" %]
+ milestone
+ [% ELSIF class == "Bugzilla::Status" %]
+ status
+ [% ELSIF class == "Bugzilla::Flag" %]
+ flag
+ [% ELSIF class == "Bugzilla::FlagType" %]
+ flagtype
+ [% ELSIF class == "Bugzilla::Field" %]
+ field
+ [% ELSIF class == "Bugzilla::Group" %]
+ group
+ [% ELSIF class == "Bugzilla::Keyword" %]
+ keyword
+ [% ELSIF class == "Bugzilla::Product" %]
+ product
+ [% ELSIF class == "Bugzilla::Search::Recent" %]
+ recent search
+ [% ELSIF class == "Bugzilla::Search::Saved" %]
+ saved search
+ [% ELSIF ( matches = class.match('^Bugzilla::Field::Choice::(.+)') ) %]
+ [% SET field_name = matches.0 %]
+ [% field_descs.$field_name FILTER html %]
+ [% END %]
+[% END %]
diff --git a/template/en/default/global/user.html.tmpl b/template/en/default/global/user.html.tmpl
new file mode 100644
index 000000000..df902b451
--- /dev/null
+++ b/template/en/default/global/user.html.tmpl
@@ -0,0 +1,39 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Daniel Brooks.
+ # Portions created by the Initial Developer are Copyright (C) 2007
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Daniel Brooks <db48x@db48x.net>
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[%# INTERFACE:
+ # who: A Bugzilla::User object that we are going to represent.
+ #%]
+
+<span class="vcard">
+ [% FILTER collapse %]
+ [% IF user.id %]
+ <a class="email" href="mailto:[% who.email FILTER html %]"
+ title="[% who.identity FILTER html %]">
+ [%- END -%]
+ [% IF who.name %]
+ <span class="fn">[% who.name FILTER html %]</span>
+ [% ELSE %]
+ [% who.login FILTER email FILTER html %]
+ [% END %]
+ [% '</a>' IF user.id %]
+ [% END %]
+</span>
diff --git a/template/en/default/global/userselect.html.tmpl b/template/en/default/global/userselect.html.tmpl
new file mode 100644
index 000000000..9fd2df79d
--- /dev/null
+++ b/template/en/default/global/userselect.html.tmpl
@@ -0,0 +1,109 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Byron Jones <bugzilla@glob.com.au>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ # Guy Pyrzak <guy.pyrzak@gmail.com>
+ # Reed Loden <reed@reedloden.com>
+ #%]
+
+[%# INTERFACE:
+ # name: mandatory; field name
+ # id: optional; field id
+ # value: optional; default field value/selection
+ # classes: optional; an array of classes to be added
+ # onchange: optional; onchange attribute value
+ # disabled: optional; if true, the field is disabled
+ # accesskey: optional, input only; accesskey attribute value
+ # size: optional, input only; size attribute value
+ # emptyok: optional, select only; if true, prepend menu option for "" to start of select
+ # hyphenok: optional, select only; if true, prepend menu option for "-" to start of select
+ # multiple: optional, do multiselect box, value is size (height) of box
+ # custom_userlist: optional, specify a limited list of users to use
+ # title: optional, extra information to display as a tooltip
+ #%]
+
+[% IF Param("usemenuforusers") %]
+<select name="[% name FILTER html %]"
+ [% IF id %] id="[% id FILTER html %]" [% END %]
+ [% IF classes %] class="[% classes.join(' ') FILTER html %]" [% END %]
+ [% IF onchange %] onchange="[% onchange FILTER html %]" [% END %]
+ [% IF disabled %] disabled="[% disabled FILTER html %]" [% END %]
+ [% IF accesskey %] accesskey="[% accesskey FILTER html %]" [% END %]
+ [% IF multiple %] multiple="multiple" size="[% multiple FILTER html %]" [% END %]
+ [% IF title %] title="[% title FILTER html %]" [% END %]
+>
+ [% IF emptyok %]
+ <option value=""></option>
+ [% END %]
+ [% IF hyphenok %]
+ <option value="-">-</option>
+ [% END %]
+
+ [% UNLESS custom_userlist %]
+ [% custom_userlist = user.get_userlist %]
+ [% END %]
+
+ [% SET selected = {} %]
+ [% IF value.defined %]
+ [% FOREACH selected_value IN value.split(', ') %]
+ [% SET selected.$selected_value = 1 %]
+ [% END %]
+ [% END %]
+
+ [% FOREACH tmpuser = custom_userlist %]
+ [% IF tmpuser.visible OR selected.${tmpuser.login} == 1 %]
+ <option value="[% tmpuser.login FILTER html %]"
+ [% IF selected.${tmpuser.login} == 1 %]
+ selected="selected"
+ [%# A user account appears only once. Remove it from the list, so that
+ # we know if there are some selected accounts which have not been listed. %]
+ [% selected.delete(tmpuser.login) %]
+ [% END %]
+ >[% tmpuser.identity FILTER html %]</option>
+ [% END %]
+ [% END %]
+
+ [%# If the list is not empty, this means some accounts have not been mentioned yet. %]
+ [% FOREACH selected_user = selected.keys %]
+ <option value="[% selected_user FILTER html %]" selected="selected">[% selected_user FILTER html %]</option>
+ [% END %]
+</select>
+[% ELSE %]
+ [% IF id && feature_enabled('jsonrpc') %]
+ <div id="[% id FILTER html %]_autocomplete"
+ [% IF classes %] class="[% classes.join(' ') FILTER html %]" [% END %]>
+ [% END %]
+ <input
+ name="[% name FILTER html %]"
+ value="[% value FILTER html %]"
+ [% IF classes %] class="[% classes.join(' ') FILTER html %]" [% END %]
+ [% IF onchange %] onchange="[% onchange FILTER html %]" [% END %]
+ [% IF disabled %] disabled="[% disabled FILTER html %]" [% END %]
+ [% IF accesskey %] accesskey="[% accesskey FILTER html %]" [% END %]
+ [% IF title %] title="[% title FILTER html %]" [% END %]
+ [% IF size %] size="[% size FILTER html %]" [% END %]
+ [% IF id %] id="[% id FILTER html %]" [% END %]
+ >
+ [% IF id && feature_enabled('jsonrpc') %]
+ <div id="[% id FILTER html %]_autocomplete_container"></div>
+ </div>
+ <script type="text/javascript">
+ if( typeof(YAHOO.bugzilla.userAutocomplete) !== 'undefined'
+ && YAHOO.bugzilla.userAutocomplete != null){
+ YAHOO.bugzilla.userAutocomplete.init( "[% id FILTER js %]",
+ "[% id FILTER js %]_autocomplete_container"
+ [% IF multiple %], true[% END%]);
+ }
+ </script>
+ [% END %]
+[% END %]
diff --git a/template/en/default/global/value-descs.js.tmpl b/template/en/default/global/value-descs.js.tmpl
new file mode 100644
index 000000000..d432cf135
--- /dev/null
+++ b/template/en/default/global/value-descs.js.tmpl
@@ -0,0 +1,33 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+BUGZILLA.value_descs = {
+ [% FOREACH vd_field = value_descs.keys %]
+ [% vd_field FILTER js %]: {
+ [% FOREACH vd_value = value_descs.${vd_field}.keys %]
+ '[% vd_value FILTER js %]':
+ '[% value_descs.${vd_field}.${vd_value} FILTER js %]'
+ [%~ ',' UNLESS loop.last %]
+ [% END %]
+ }[% ',' UNLESS loop.last %]
+ [% END %]
+};
diff --git a/template/en/default/global/variables.none.tmpl b/template/en/default/global/variables.none.tmpl
new file mode 100644
index 000000000..faf1a5427
--- /dev/null
+++ b/template/en/default/global/variables.none.tmpl
@@ -0,0 +1,44 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s):
+ #%]
+
+[%# This is a list of terms that may be changed to "brand" the Bugzilla
+ # instance (for example, referring to "bugs" as "issues".) When used, these
+ # strings are used in several different types of content, and are not
+ # protected with Template-Toolkit FILTERs. Consequently, no special
+ # characters are allowed.
+ #
+ # Remember to PROCESS rather than INCLUDE this template.
+ #%]
+
+[% terms = {
+ "bug" => "bug",
+ "Bug" => "Bug",
+ "abug" => "a bug",
+ "Abug" => "A bug",
+ "aBug" => "a Bug",
+ "ABug" => "A Bug",
+ "bugs" => "bugs",
+ "Bugs" => "Bugs",
+ "zeroSearchResults" => "Zarro Boogs found",
+ "Bugzilla" => "Bugzilla"
+ }
+%]
+
+[% Hook.process("end") %]
diff --git a/template/en/default/index.html.tmpl b/template/en/default/index.html.tmpl
new file mode 100644
index 000000000..a30968b94
--- /dev/null
+++ b/template/en/default/index.html.tmpl
@@ -0,0 +1,183 @@
+[%# -*- mode: html -*- %]
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Terry Weissman <terry@mozilla.org>
+ # Jacob Steenhagen <jake@bugzilla.org>
+ # Vitaly Harisov <vitaly@rathedg.com>
+ # Guy Pyrzak <guy.pyrzak@gmail.com>
+ #%]
+
+[%# INTERFACE:
+ # release: a hash containing data about new releases, if any.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+
+[% PROCESS global/header.html.tmpl
+ title = "$terms.Bugzilla Main Page"
+ header = "Main Page"
+ header_addl_info = "version $constants.BUGZILLA_VERSION"
+ style_urls = [ 'skins/standard/index.css' ]
+%]
+
+
+<script type="text/javascript">
+<!--
+function onLoadActions() {
+ quicksearchHelpText('quicksearch_main', 'show');
+ if( window.external.AddSearchProvider ){
+ YAHOO.util.Dom.removeClass('quicksearch_plugin', 'bz_default_hidden');
+ }
+ document.getElementById('quicksearch_top').focus();
+}
+function addSidebar() {
+ var sidebarname=window.location.host;
+ if (!/bug/i.test(sidebarname))
+ sidebarname="[% terms.Bugzilla %] "+sidebarname;
+ window.sidebar.addPanel (sidebarname, "[% urlbase FILTER html %]sidebar.cgi", "");
+}
+var quicksearch_message = "Enter [% terms.abug %] # or some search terms";
+
+function checkQuicksearch( form ) {
+ if (form.quicksearch.value == '' || form.quicksearch.value == quicksearch_message ) {
+ alert('Please enter one or more search terms first.');
+ return false;
+ }
+ return true;
+}
+
+function quicksearchHelpText(el_id, action){
+ var el = document.getElementById(el_id);
+ if ( action == "show") {
+ if( el.value == "" ) {
+ el.value = quicksearch_message
+ YAHOO.util.Dom.addClass(el, "quicksearch_help_text");
+ }
+ } else {
+ if( el.value == quicksearch_message ) {
+ el.value = "";
+ YAHOO.util.Dom.removeClass(el, "quicksearch_help_text");
+ }
+ }
+}
+YAHOO.util.Event.onDOMReady(onLoadActions);
+//-->
+</script>
+
+[% IF release %]
+ <div id="new_release">
+ [% IF release.data %]
+ [% IF release.deprecated %]
+ <p>[% terms.Bugzilla %] [%+ release.deprecated FILTER html %] is no longer
+ supported. You are highly encouraged to upgrade in order to keep your
+ system secure.</p>
+ [% END %]
+
+ <p>A new [% terms.Bugzilla %] version ([% release.data.latest_ver FILTER html %])
+ is available at
+ <a href="[% release.data.url FILTER html %]">[% release.data.url FILTER html %]</a>.<br>
+ Release date: [% release.data.date FILTER html %]</p>
+
+ <p class="notice">This message is only shown to logged in users with admin privs.
+ You can configure this notification from the
+ <a href="editparams.cgi?section=general#upgrade_notification_desc">Parameters</a> page.</p>
+ [% ELSIF release.error == "cannot_download" %]
+ <p>The local XML file '[% release.xml_file FILTER html %]' cannot be created.
+ Please make sure the web server can write in this directory and that you can access
+ the web. If you are behind a proxy, set the
+ <a href="editparams.cgi?section=advanced#proxy_url_desc">proxy_url</a> parameter correctly.</p>
+ [% ELSIF release.error == "no_update" %]
+ <p>The local XML file '[% release.xml_file FILTER html %]' cannot be updated.
+ Please make sure the web server can edit this file.</p>
+ [% ELSIF release.error == "no_access" %]
+ <p>The local XML file '[% release.xml_file FILTER html %]' cannot be read.
+ Please make sure this file has the correct rights set on it.</p>
+ [% ELSIF release.error == "corrupted" %]
+ <p>The local XML file '[% release.xml_file FILTER html %]' has an invalid XML format.
+ Please delete it and try accessing this page again.</p>
+ [% ELSIF release.error == "unknown_parameter" %]
+ <p>'[% Param("upgrade_notification") FILTER html %]' is not a valid notification
+ parameter. Please check this parameter in the
+ <a href="editparams.cgi?section=general#upgrade_notification_desc">Parameters</a> page.</p>
+ [% END %]
+ </div>
+[% END %]
+
+<div id="page-index">
+ <table>
+ <tr>
+ <td>
+ <h1 id="welcome"> Welcome to [% terms.Bugzilla %]</h1>
+ <div class="intro">[% Hook.process('intro') %]</div>
+ <a id="enter_bug" class="bz_common_actions"
+ href="enter_bug.cgi"><span>File [% terms.aBug %]</span></a>
+
+ <a id="query" class="bz_common_actions"
+ href="query.cgi"><span>Search</span></a>
+
+ <a id="account" class="bz_common_actions"
+ [% IF user.id %]
+ href="userprefs.cgi"><span>User Preferences</span></a>
+ [% ELSIF Param('createemailregexp')
+ && user.authorizer.user_can_create_account
+ %]
+ href="createaccount.cgi"><span>Open a New Account</span></a>
+ [% ELSE %]
+ href="?GoAheadAndLogIn=1"><span>Log In</span></a>
+ [% END %]
+
+ <form id="quicksearchForm" name="quicksearchForm" action="buglist.cgi"
+ onsubmit="return checkQuicksearch(this);">
+ <div>
+ <input id="quicksearch_main" type="text" name="quicksearch"
+ onfocus="quicksearchHelpText(this.id, 'hide');"
+ onblur="quicksearchHelpText(this.id, 'show');"
+ >
+ <input id="find" type="submit" value="Quick Search">
+ <ul class="additional_links" id="quicksearch_links">
+ <li>
+ <a href="page.cgi?id=quicksearch.html">Quick Search help</a>
+ </li>
+ <li class="bz_default_hidden" id="quicksearch_plugin">
+ |
+ <a href="javascript:window.external.AddSearchProvider('[% urlbase FILTER html %]search_plugin.cgi')">
+ Install the Quick Search plugin
+ </a>
+ </li>
+ </ul>
+ <ul class="additional_links">
+ <li>
+ <a href="[% docs_urlbase FILTER html %]using.html">
+ [%- terms.Bugzilla %] User's Guide</a>
+ </li>
+ <li>
+ |
+ <a href="page.cgi?id=release-notes.html">Release Notes</a>
+ </li>
+ [% Hook.process('additional_links') %]
+ </ul>
+ </div>
+ </form>
+ <div class="outro">[% Hook.process('outro') %]</div>
+ </td>
+ </tr>
+ </table>
+</div>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/list/change-columns.html.tmpl b/template/en/default/list/change-columns.html.tmpl
new file mode 100644
index 000000000..b13055c38
--- /dev/null
+++ b/template/en/default/list/change-columns.html.tmpl
@@ -0,0 +1,145 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Dave Lawrence <dkl@redhat.com>
+ # Pascal Held <paheld@gmail.com>
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% PROCESS global/header.html.tmpl
+ title = "Change Columns"
+ javascript_urls = "js/change-columns.js"
+ onload = "initChangeColumns()"
+%]
+
+<p>
+ Select the columns you wish to appear in your [% terms.bug %] lists. Note that
+ this feature requires cookies to work.
+</p>
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+[% field_descs.short_short_desc = "Summary (first 60 characters)" %]
+[% field_descs.short_desc = "Summary (Full)" %]
+[% field_descs.assigned_to_realname = "$field_descs.assigned_to Real Name" %]
+[% field_descs.reporter_realname = "$field_descs.reporter Real Name" %]
+[% field_descs.qa_contact_realname = "$field_descs.qa_contact Real Name" %]
+
+[%# Create a mapping of field descriptions to field names, so that
+ # the "Available Columns" list can be sorted alphabetically by
+ # field description.
+ #%]
+[% SET available_columns = {} %]
+[% FOREACH column = columns.keys %]
+ [% NEXT IF collist.contains(column) %]
+ [%# We lowecase the keys so that the sort happens case-insensitively. %]
+ [% SET column_desc = field_descs.$column || column FILTER lower %]
+ [% available_columns.$column_desc = column %]
+[% END %]
+
+<form name="changecolumns" action="colchange.cgi" onsubmit="change_submit();">
+ <input type="hidden" name="rememberedquery" value="[% buffer FILTER html %]">
+ <table>
+ <tr>
+ <th><div id="avail_header" class="bz_default_hidden">Available Columns</div></th>
+ <th></th>
+ <th>Selected Columns</th>
+ <th></th>
+ </tr>
+ <tr>
+ <td>
+ <select name="available_columns" id="available_columns"
+ size="15" multiple="multiple" onchange="updateView();"
+ class="bz_default_hidden">
+ </select>
+ </td>
+ <td>
+ <input class="image_button bz_default_hidden" type="button"
+ id="select_button" name="select" onclick="move_select()">
+ <br><br>
+ <input class="image_button bz_default_hidden" type="button"
+ id="deselect_button" name="deselect" onclick="move_deselect()">
+ </td>
+ <td>
+ <select name="selected_columns" id="selected_columns"
+ size="15" multiple="multiple" onchange="updateView();">
+ [% FOREACH column = collist %]
+ <option value="[% column FILTER html %]" selected="selected">
+ [% (field_descs.${column} || column) FILTER html %]
+ </option>
+ [% END %]
+ [% FOREACH key = available_columns.keys.sort %]
+ [% SET column = available_columns.$key %]
+ <option value="[% column FILTER html %]">
+ [%# Don't display the lower-cased column description,
+ # display the correct-case one. %]
+ [% (field_descs.$column || column) FILTER html %]
+ </option>
+ [% END %]
+ </select>
+ </td>
+ <td>
+ <input class="image_button bz_default_hidden" type="button"
+ id="up_button" name="up" onclick="move_up()">
+ <br><br>
+ <input class="image_button bz_default_hidden" type="button"
+ id="down_button" name="down" onclick="move_down()">
+ </td>
+ </tr>
+ </table>
+
+ <p>
+ <input id="nosplitheader" type="radio" name="splitheader" value="0"
+ [%+ "checked='checked'" IF NOT splitheader %]>
+ <label for="nosplitheader">
+ Normal headers (prettier)
+ </label>
+ <br>
+
+ <input id="splitheader" type="radio" name="splitheader" value="1"
+ [%+ "checked='checked'" IF splitheader %]>
+ <label for="splitheader">
+ Stagger headers (often makes list more compact)
+ </label>
+ </p>
+
+ [% IF saved_search %]
+ <p>
+ <input type="hidden" name="saved_search"
+ value="[% saved_search.id FILTER html%]" >
+ <input type="hidden" name="token"
+ value="[% issue_hash_token([saved_search.id, saved_search.name]) FILTER html %]">
+ <input type="checkbox" id="save_columns_for_search" checked="checked"
+ name="save_columns_for_search" value="1">
+ <label for="save_columns_for_search">Save this column list only
+ for search '[% saved_search.name FILTER html %]'</label>
+ </p>
+ [% ELSE %]
+ <input type="hidden" name="token"
+ value="[% issue_hash_token(['default-list']) FILTER html %]">
+ [% END %]
+
+ <p>
+ <input type="submit" id="change" value="Change Columns">
+ </p>
+
+ <input type="submit" id="resetit" name="resetit"
+ value="Reset to [% terms.Bugzilla %] default">
+</form>
+
+[% PROCESS global/footer.html.tmpl %]
+
diff --git a/template/en/default/list/edit-multiple.html.tmpl b/template/en/default/list/edit-multiple.html.tmpl
new file mode 100644
index 000000000..92e578e8f
--- /dev/null
+++ b/template/en/default/list/edit-multiple.html.tmpl
@@ -0,0 +1,441 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Myk Melez <myk@mozilla.org>
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ # Guy Pyrzak <guy.pyrzak@gmail.com>
+ # Reed Loden <reed@reedloden.com>
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% dontchange = "--do_not_change--" %]
+<input type="hidden" name="dontchange" value="[% dontchange FILTER html %]">
+<input type="hidden" name="token" value="[% token FILTER html %]">
+
+<script type="text/javascript">
+ function SetCheckboxes(value) {
+ var elements = document.forms.changeform.getElementsByTagName('input'),
+ numelements = elements.length,
+ item, i;
+ for (i = 0; i < numelements; i++) {
+ item = elements[i];
+ if (item.type === 'checkbox' && item.name.match(/^id_/)) {
+ item.checked = value;
+ }
+ }
+ }
+ document.write(' <input type="button" name="uncheck_all" value="Uncheck All" onclick="SetCheckboxes(false);">');
+ document.write(' <input type="button" name="check_all" value="Check All" onclick="SetCheckboxes(true);">');
+</script>
+
+<hr>
+
+<p style="font-size:smaller">
+ To change multiple [% terms.bugs %]:</p>
+<ol style="font-size:smaller">
+ <li>Check the [% terms.bugs %] you want to change above.</li>
+ <li>Make your changes in the form fields below. If the change
+ you are making requires an explanation, include it in
+ the comments box.</li>
+ <li>Click the <em>Commit</em> button.</li>
+</ol>
+
+<table id="form">
+ <tr>
+
+ <th><label for="product">Product:</label></th>
+ <td>
+ [% PROCESS selectmenu menuname = "product"
+ menuitems = products
+ property = "name" %]
+ </td>
+
+ <th><label for="version">Version:</label></th>
+ <td>
+ [% PROCESS selectmenu menuname = "version"
+ menuitems = versions
+ property = "" %]
+ </td>
+
+ </tr>
+ <tr>
+
+ <th><label for="component">Component:</label></th>
+ <td>
+ [% PROCESS selectmenu menuname = "component"
+ menuitems = components %]
+ </td>
+
+ <th>
+ <label for="priority">
+ <a href="page.cgi?id=fields.html#priority">Priority</a>:
+ </label>
+ </th>
+ <td>
+ [% PROCESS selectmenu menuname = "priority"
+ menuitems = priorities %]
+ </td>
+
+ </tr>
+ <tr>
+
+ <th>
+ <label for="rep_platform">
+ <a href="page.cgi?id=fields.html#rep_platform">Platform</a>:
+ </label>
+ </th>
+ <td>
+ [% PROCESS selectmenu menuname = "rep_platform"
+ menuitems = platforms %]
+ </td>
+
+ <th>
+ <label for="bug_severity">
+ <a href="page.cgi?id=fields.html#bug_severity">Severity</a>:
+ </label>
+ </th>
+ <td>
+ [% PROCESS selectmenu menuname = "bug_severity"
+ menuitems = severities %]
+ </td>
+
+ </tr>
+
+ <tr>
+ <th>
+ <label for="op_sys">
+ <a href="page.cgi?id=fields.html#op_sys">OS</a>:
+ </label>
+ </th>
+ <td [% " colspan=\"3\"" IF !Param("usetargetmilestone") %]>
+ [% PROCESS selectmenu menuname = "op_sys"
+ menuitems = op_sys %]
+ </td>
+
+ [% IF Param("usetargetmilestone") %]
+ <th><label for="target_milestone">Target Milestone:</label></th>
+ <td>
+ [% PROCESS selectmenu menuname = "target_milestone"
+ menuitems = targetmilestones %]
+ </td>
+ [% END %]
+ </tr>
+
+ <tr>
+ <th><label for="bug_status">Status:</label></th>
+ <td colspan="3">[% PROCESS status_section %]</td>
+ </tr>
+ [% IF user.is_timetracker %]
+ <tr>
+ <th><label for="estimated_time">Estimated Hours:</label></th>
+ <td>
+ <input id="estimated_time"
+ name="estimated_time"
+ value="[% dontchange FILTER html %]"
+ size="6">
+ </td>
+ [% PROCESS bug/field.html.tmpl
+ field = bug_fields.deadline, value = dontchange
+ editable = 1, allow_dont_change = 1 %]
+ </tr>
+ <tr>
+ <th><label for="remaining_time">Remaining Hours:</label></th>
+ <td>
+ <input id="remaining_time"
+ name="remaining_time"
+ value="[% dontchange FILTER html %]"
+ size="6">
+ </td>
+ <th>&nbsp;</th>
+ <td>&nbsp;</td>
+ </tr>
+ [% END %]
+
+ <tr>
+ <th><label for="assigned_to">Assignee:</label></th>
+ <td colspan="3">
+ [% INCLUDE global/userselect.html.tmpl
+ id => "assigned_to"
+ name => "assigned_to"
+ value => dontchange
+ size => 40
+ %]
+ <input type="checkbox" id="set_default_assignee" name="set_default_assignee" value="1">
+ <label for="set_default_assignee">Reset Assignee to default</label>
+ </td>
+ </tr>
+
+ [% IF Param("useqacontact") %]
+ <tr>
+ <th><label for="qa_contact">QA Contact:</label></th>
+ <td colspan="3">
+ [% INCLUDE global/userselect.html.tmpl
+ id => "qa_contact"
+ name => "qa_contact"
+ value => dontchange
+ size => 40
+ %]
+ <input type="checkbox" id="set_default_qa_contact" name="set_default_qa_contact" value="1">
+ <label for="set_default_qa_contact">Reset QA Contact to default</label>
+ </td>
+ </tr>
+ [% END %]
+
+ <tr>
+
+ <th><label for="masscc">CC List:</label></th>
+ <td colspan="3">
+ [% INCLUDE global/userselect.html.tmpl
+ id => "masscc"
+ name => "masscc"
+ value => ""
+ size => 40
+ multiple => 5
+ %]
+ <select name="ccaction">
+ <option value="add">Add these to the CC List</option>
+ <option value="remove">Remove these from the CC List</option>
+ </select>
+ </td>
+
+ </tr>
+
+ [% IF use_keywords %]
+ <tr>
+
+ [% INCLUDE "bug/field-label.html.tmpl"
+ field = bug_fields.keywords, editable = 1
+ desc_url = "describekeywords.cgi"
+ %]
+ <td colspan="3">
+ [% INCLUDE bug/field.html.tmpl
+ field = bug_fields.keywords, editable = 1, value = keywords
+ no_tds = 1
+ %]
+ <select name="keywordaction">
+ <option value="add">Add these keywords</option>
+ <option value="remove">Delete these keywords</option>
+ <option value="set">Make the keywords be exactly this list</option>
+ </select>
+ </td>
+
+ </tr>
+ [% END %]
+
+ <tr>
+ <th>
+ <label for="dependson">
+ Depends On:
+ </label>
+ </th>
+ <td colspan="3">
+ <input id="dependson" name="dependson" size="40">
+ <select name="dependson_action">
+ <option value="add">Add these IDs</option>
+ <option value="remove">Delete these IDs</option>
+ </select>
+ </td>
+ </tr>
+
+ <tr>
+ <th>
+ <label for="blocked">
+ Blocks:
+ </label>
+ </th>
+ <td colspan="3">
+ <input id="blocked" name="blocked" size="40">
+ <select name="blocked_action">
+ <option value="add">Add these IDs</option>
+ <option value="remove">Delete these IDs</option>
+ </select>
+ </td>
+ </tr>
+
+ [% IF Param('usestatuswhiteboard') %]
+ <tr>
+ <td align="right">
+ <b>Status Whiteboard:</b>
+ </td>
+ <td colspan="7">
+ <input name="status_whiteboard"
+ value="[% dontchange FILTER html %]" size="60">
+ </td>
+ </tr>
+ [% END %]
+
+ [% USE Bugzilla %]
+ [%# Show all legal values and all fields, ignoring visibility controls. %]
+ [% bug = 0 %]
+ [% FOREACH field = Bugzilla.active_custom_fields %]
+ <tr>
+ [% PROCESS bug/field.html.tmpl value = dontchange
+ editable = 1
+ allow_dont_change = 1 %]
+ </tr>
+ [% END %]
+
+ [% Hook.process("after_custom_fields") %]
+
+</table>
+
+<b><label for="comment">Additional Comments:</label></b>
+[% IF user.is_insider %]
+ <input type="checkbox" name="comment_is_private" value="1"
+ id="newcommentprivacy"
+ onClick="updateCommentTagControl(this, form)"/>
+ <label for="newcommentprivacy">
+ Make comment private (visible only to members of the
+ <strong>[% Param('insidergroup') FILTER html %]</strong> group)
+ </label>
+[% END %]
+<br>
+[% INCLUDE global/textarea.html.tmpl
+ name = 'comment'
+ id = 'comment'
+ minrows = 5
+ maxrows = 25
+ cols = constants.COMMENT_COLS
+%]<br>
+
+[% Hook.process('before_groups') %]
+
+[% IF groups.size > 0 %]
+
+ <script type="text/javascript">
+ function turn_off(myself, id) {
+ var other_checkbox = document.getElementById(id);
+ if (myself.checked && other_checkbox) {
+ other_checkbox.checked = false;
+ }
+ }
+ </script>
+
+ <b>Groups:</b><br>
+ <table border="1">
+ <tr>
+ <th>Remove<br>[% terms.bugs %]<br>from this<br>group</th>
+ <th>Add<br>[% terms.bugs %]<br>to this<br>group</th>
+ <th>Group Name:</th>
+ </tr>
+
+ [% FOREACH group = groups %]
+ <tr>
+ <td align="center">
+ <input type="checkbox" name="defined_groups"
+ id="defined_group_[% group.id %]"
+ value="[% group.name FILTER html %]"
+ onchange="turn_off(this, 'group_[% group.id %]')">
+ </td>
+ [% IF group.is_active %]
+ <td align="center">
+ <input type="checkbox" name="groups"
+ id="group_[% group.id FILTER html %]"
+ value="[% group.name FILTER html %]"
+ onchange="turn_off(this, 'defined_group_[% group.id %]')">
+ </td>
+ [% ELSE %]
+ <td>&nbsp;</td>
+ [% foundinactive = 1 %]
+ [% END %]
+
+ <td>
+ [% SET inactive = !group.is_active %]
+ [% group.description FILTER html_light FILTER inactive(inactive) %]
+ </td>
+
+ </tr>
+ [% END %]
+
+ </table>
+
+ [% IF foundinactive %]
+ <p style="font-size:smaller">(Note: [% terms.Bugs %] may not be added to [% FILTER inactive %]inactive
+ groups[% END %], only removed.)</p>
+ [% END %]
+
+[% END %]
+
+[%+ Hook.process('after_groups') %]
+
+<input type="submit" id="commit" value="Commit">
+
+[%############################################################################%]
+[%# Select Menu Block #%]
+[%############################################################################%]
+
+[% BLOCK selectmenu %]
+ <select id="[% menuname %]" name="[% menuname %]">
+ <option value="[% dontchange FILTER html %]" selected="selected">
+ [% dontchange FILTER html %]
+ </option>
+ [% FOREACH menuitem = menuitems %]
+ [% IF property %][% menuitem = menuitem.$property %][% END %]
+ <option value="[% menuitem FILTER html %]">[% display_value(menuname, menuitem) FILTER html %]</option>
+ [% END %]
+ </select>
+[% END %]
+
+[%############################################################################%]
+[%# Status Block #%]
+[%############################################################################%]
+
+[% BLOCK status_section %]
+ [% all_open_bugs = !current_bug_statuses.containsany(closedstates) %]
+ [% all_closed_bugs = !current_bug_statuses.containsany(openstates) %]
+ [% closed_status_array = [] %]
+
+ <select name="bug_status" id="bug_status">
+ <option value="[% dontchange FILTER html %]" selected="selected">[% dontchange FILTER html %]</option>
+
+ [% FOREACH bug_status = new_bug_statuses %]
+ <option value="[% bug_status.name FILTER html %]">
+ [% display_value("bug_status", bug_status.name) FILTER html %]
+ </option>
+ [% IF !bug_status.is_open %]
+ [% filtered_status = bug_status.name FILTER js %]
+ [% closed_status_array.push( filtered_status ) %]
+ [% END %]
+ [% END %]
+
+ [%# If all the bugs being changed are closed, allow the user to change their resolution. %]
+ [% IF all_closed_bugs %]
+ [% filtered_status = dontchange FILTER js %]
+ [% closed_status_array.push( filtered_status ) %]
+ [% END %]
+ </select>
+
+ <span id="resolution_settings">
+ <select id="resolution" name="resolution">
+ <option value="[% dontchange FILTER html %]" selected >[% dontchange FILTER html %]</option>
+ [% FOREACH r = resolutions %]
+ [% NEXT IF !r %]
+ [% NEXT IF r == "DUPLICATE" || r == "MOVED" %]
+ <option value="[% r FILTER html %]">[% display_value("resolution", r) FILTER html %]</option>
+ [% END %]
+ </select>
+ </span>
+
+ <script type="text/javascript">
+ var close_status_array = new Array("[% closed_status_array.join('", "') FILTER none %]");
+ YAHOO.util.Event.addListener('bug_status', "change", showHideStatusItems, '[% "is_duplicate" IF bug.dup_id %]');
+ YAHOO.util.Event.onDOMReady( showHideStatusItems );
+ </script>
+
+[% END %]
diff --git a/template/en/default/list/list-simple.html.tmpl b/template/en/default/list/list-simple.html.tmpl
new file mode 100644
index 000000000..f4c3549ed
--- /dev/null
+++ b/template/en/default/list/list-simple.html.tmpl
@@ -0,0 +1,54 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Myk Melez <myk@mozilla.org>
+ #%]
+
+[%# INTERFACE:
+ # title: string. The title for this page. (optional)
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[%############################################################################%]
+[%# Initialization #%]
+[%############################################################################%]
+
+[% DEFAULT title = "$terms.Bug List" %]
+
+[%############################################################################%]
+[%# Bug Table #%]
+[%############################################################################%]
+
+<html>
+
+ <head>
+ <title>[% title FILTER html %]</title>
+ <base href="[% urlbase FILTER html %]">
+ <link href="[% 'skins/standard/buglist.css' FILTER mtime %]"
+ rel="stylesheet" type="text/css">
+ </head>
+
+ <body>
+ [% IF bugs.size == 0 %]
+ <h3>[% terms.zeroSearchResults %].</h3>
+ [% ELSE %]
+ [% PROCESS list/table.html.tmpl %]
+ [% END %]
+ </body>
+
+</html>
diff --git a/template/en/default/list/list.atom.tmpl b/template/en/default/list/list.atom.tmpl
new file mode 100644
index 000000000..ed0c660af
--- /dev/null
+++ b/template/en/default/list/list.atom.tmpl
@@ -0,0 +1,101 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Walter Hoehn <wassa@columbia.edu>
+ # John Belmonte <john@neggie.net>
+ # Jason Remillard <jremillardshop@letterboxes.org>
+ # Phil Ringnalda <bugzilla@philringnalda.com>
+ #
+ # This is a template for generating an Atom representation of a buglist.
+ #%]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+[% DEFAULT title = "$terms.Bugzilla $terms.Bugs" %]
+
+<?xml version="1.0"[% IF Param('utf8') %] encoding="UTF-8"[% END %]?>
+<feed xmlns="http://www.w3.org/2005/Atom">
+ <title>[% title FILTER xml %]</title>
+ <link rel="alternate" type="text/html"
+ href="[% urlbase FILTER html %]buglist.cgi?
+ [%- urlquerypart.replace('ctype=atom[&]?','') FILTER xml %]"/>
+ <link rel="self" type="application/atom+xml"
+ href="[% urlbase FILTER html %]buglist.cgi?
+ [%- urlquerypart FILTER xml %]"/>
+ <updated>[% bugs.sort('changedtime').last.changedtime FILTER time("%Y-%m-%dT%H:%M:%SZ", "UTC")
+ FILTER xml %]</updated>
+ <id>[% urlbase FILTER html %]buglist.cgi?[% urlquerypart FILTER xml %]</id>
+
+ [% FOREACH bug = bugs %]
+ <entry>
+ <title>[% "@" IF bug.secure_mode %][[% terms.Bug %] [%+ bug.bug_id FILTER xml %]] [% bug.short_desc FILTER xml %]</title>
+ <link rel="alternate" type="text/html"
+ href="[% urlbase FILTER html %]show_bug.cgi?id=
+ [%- bug.bug_id FILTER xml %]"/>
+ <id>[% urlbase FILTER xml %]show_bug.cgi?id=[% bug.bug_id FILTER xml %]</id>
+ <author>
+ <name>[% bug.reporter_realname ? bug.reporter_realname : bug.reporter FILTER xml %]</name>
+ </author>
+ <updated>[% bug.changedtime FILTER time("%Y-%m-%dT%H:%M:%SZ", "UTC") FILTER xml %]</updated>
+ <summary type="html">
+ [%# Filter out the entire block, so that we don't need to escape the html code out %]
+ [% FILTER xml %]
+ <table>
+ <tr>
+ <th>Field</th><th>Value</th>
+ </tr><tr class="bz_feed_product">
+ <td>[% columns.product.title FILTER html %]</td>
+ <td>[% bug.product FILTER html %]</td>
+ </tr><tr class="bz_feed_component">
+ <td>[% columns.component.title FILTER html %]</td>
+ <td>[% bug.component FILTER html %]</td>
+ </tr><tr class="bz_feed_assignee">
+ <td>[% columns.assigned_to_realname.title FILTER html %]</td>
+ <td>[% bug.assigned_to_realname ? bug.assigned_to_realname : bug.assigned_to FILTER html %]</td>
+ </tr><tr class="bz_feed_reporter">
+ <td>[% columns.reporter_realname.title FILTER html %]</td>
+ <td>[% bug.reporter_realname ? bug.reporter_realname : bug.reporter FILTER html %]</td>
+ </tr><tr class="bz_feed_bug_status">
+ <td>[% columns.bug_status.title FILTER html %]</td>
+ <td>[% display_value("bug_status", bug.bug_status) FILTER html %]</td>
+ </tr><tr class="bz_feed_resolution">
+ <td>[% columns.resolution.title FILTER html %] </td>
+ <td>[% display_value("resolution", bug.resolution) FILTER html %]</td>
+ </tr><tr class="bz_feed_priority">
+ <td>[% columns.priority.title FILTER html %]</td>
+ <td>[% display_value("priority", bug.priority) FILTER html %]</td>
+ </tr><tr class="bz_feed_severity">
+ <td>[% columns.bug_severity.title FILTER html %] </td>
+ <td>[% display_value("bug_severity", bug.bug_severity) FILTER html %]</td>
+ [% IF Param("usetargetmilestone") %]
+ </tr><tr class="bz_feed_target_milestone">
+ <td>[% columns.target_milestone.title FILTER html %]</td>
+ <td>[% bug.target_milestone FILTER html %]</td>
+ [% END %]
+ </tr><tr class="bz_feed_creation_date">
+ <td>[% columns.opendate.title FILTER html %]</td>
+ <td>[% bug.opendate FILTER html %]</td>
+ </tr><tr class="bz_feed_changed_date">
+ <td>[% columns.changeddate.title FILTER html %]</td>
+ <td>[% bug.changeddate FILTER html -%]</td>
+ </tr>
+ </table>
+ [% END %]
+ </summary>
+ </entry>
+ [% END %]
+</feed>
diff --git a/template/en/default/list/list.csv.tmpl b/template/en/default/list/list.csv.tmpl
new file mode 100644
index 000000000..6114d6fae
--- /dev/null
+++ b/template/en/default/list/list.csv.tmpl
@@ -0,0 +1,46 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Myk Melez <myk@mozilla.org>
+ # Gervase Markham <gerv@gerv.net>
+ #%]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+[% colsepchar = user.settings.csv_colsepchar.value %]
+
+bug_id
+[% FOREACH column = displaycolumns %]
+ [% colsepchar %][% column FILTER csv %]
+[% END %]
+
+[% FOREACH bug = bugs %]
+ [% bug.bug_id %]
+ [% FOREACH column = displaycolumns %]
+ [% colsepchar %]
+ [% IF column == "opendate" OR column == "changeddate" %]
+ [% rawcolumn = column.replace("date", "time") %]
+ [% bug.$column = bug.$rawcolumn FILTER time("%Y-%m-%d %H:%M:%S") %]
+ [% ELSIF column == 'bug_status' %]
+ [% bug.$column = display_value("bug_status", bug.$column) %]
+ [% ELSIF column == 'resolution' %]
+ [%- bug.$column = display_value("resolution", bug.$column) %]
+ [% END %]
+ [% bug.$column FILTER csv %]
+ [% END %]
+
+[% END %]
diff --git a/template/en/default/list/list.html.tmpl b/template/en/default/list/list.html.tmpl
new file mode 100644
index 000000000..8d87b5c70
--- /dev/null
+++ b/template/en/default/list/list.html.tmpl
@@ -0,0 +1,293 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Myk Melez <myk@mozilla.org>
+ #%]
+
+[%# INTERFACE:
+ # searchtype: string. Type of search - either "series", "saved" or undef.
+ # ...
+ # defaultsavename: string. The default name for saving the query.
+ #%]
+
+[%############################################################################%]
+[%# Template Initialization #%]
+[%############################################################################%]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+[% title = "$terms.Bug List" %]
+[% IF searchname || defaultsavename %]
+ [% title = title _ ": " _ (searchname OR defaultsavename) FILTER html %]
+[% END %]
+
+[% qorder = order FILTER url_quote IF order %]
+
+
+[%############################################################################%]
+[%# Page Header #%]
+[%############################################################################%]
+
+[% PROCESS global/header.html.tmpl
+ title = title
+ style = style
+ atomlink = "buglist.cgi?$urlquerypart&title=$title&ctype=atom"
+ yui = [ 'autocomplete', 'calendar' ]
+ javascript_urls = [ "js/util.js", "js/field.js" ]
+ style_urls = [ "skins/standard/buglist.css" ]
+ doc_section = "query.html#list"
+%]
+
+<div class="bz_query_head" align="center">
+ <span class="bz_query_timestamp">
+ <b>[% currenttime FILTER time('%a %b %e %Y %T %Z') FILTER html %]</b><br>
+ </span>
+
+ [% IF debug %]
+ <p class="bz_query">[% query FILTER html %]</p>
+ [% IF query_explain.defined %]
+ <pre class="bz_query_explain">[% query_explain FILTER html %]</pre>
+ [% END %]
+ [% END %]
+
+ [% IF user.settings.display_quips.value == 'on' %]
+ [% DEFAULT quip = "$terms.Bugzilla would like to put a random quip here, but no one has entered any." %]
+ <span class="bz_quip">
+ <a href="quips.cgi"><i>[% quip FILTER html %]</i></a>
+ </span>
+ [% END %]
+
+</div>
+
+[% IF toolong %]
+ <h2 class="bz_smallminded">
+ This list is too long for [% terms.Bugzilla %]'s little mind; the
+ Next/Prev/First/Last buttons won't appear on individual [% terms.bugs %].
+ </h2>
+[% END %]
+
+[% SET shown_types = [
+ 'notequals', 'regexp', 'notregexp', 'lessthan', 'lessthaneq',
+ 'greaterthan', 'greaterthaneq', 'changedbefore', 'changedafter',
+ 'changedfrom', 'changedto', 'changedby', 'notsubstring', 'nowords',
+ 'nowordssubstr', 'notmatches',
+] %]
+<ul class="search_description">
+[% FOREACH desc_item = search_description %]
+ <li>
+ <strong>[% field_descs.${desc_item.field} FILTER html %]:</strong>
+ [% IF shown_types.contains(desc_item.type) || debug %]
+ ([% search_descs.${desc_item.type} FILTER html %])
+ [% END %]
+ [% FOREACH val IN desc_item.value.split(',') %]
+ [%+ display_value(desc_item.field, val) FILTER html %][% ',' UNLESS loop.last %]
+ [% END %]
+ [% IF debug %]
+ (<code>[% desc_item.term FILTER html %]</code>)
+ [% END %]
+ </li>
+[% END %]
+</ul>
+
+<hr>
+
+[%############################################################################%]
+[%# Preceding Status Line #%]
+[%############################################################################%]
+
+[% IF bugs.size > 9 %]
+ <span class="bz_result_count">
+ [% bugs.size %] [%+ terms.bugs %] found.
+ </span>
+[% END %]
+
+[%############################################################################%]
+[%# Start of Change Form #%]
+[%############################################################################%]
+
+[% IF dotweak %]
+ <form name="changeform" method="post" action="process_bug.cgi">
+[% END %]
+
+[%############################################################################%]
+[%# Bug Table #%]
+[%############################################################################%]
+
+[% FLUSH %]
+[% PROCESS list/table.html.tmpl %]
+
+[%############################################################################%]
+[%# Succeeding Status Line #%]
+[%############################################################################%]
+
+<span class="bz_result_count">
+ [% IF bugs.size == 0 %]
+ <span class="zero_results">[% terms.zeroSearchResults %].</span>
+ [% ELSIF bugs.size == 1 %]
+ One [% terms.bug %] found.
+ [% ELSE %]
+ [% bugs.size %] [%+ terms.bugs %] found.
+ [% END %]
+</span>
+
+[% IF bugs.size == 0 %]
+ <ul class="zero_result_links">
+ <li>[% PROCESS enter_bug_link %]</li>
+ [% IF one_product.defined %]
+ <li><a href="enter_bug.cgi">File a new [% terms.bug %] in a
+ different product</a></li>
+ [% END %]
+ <li><a href="[% PROCESS edit_search_url %]">Edit this search</a></li>
+ <li><a href="query.cgi">Start a new search</a></li>
+ </ul>
+[% END %]
+
+<br>
+
+[%############################################################################%]
+[%# Rest of Change Form #%]
+[%############################################################################%]
+
+[% IF dotweak %]
+ [% PROCESS "list/edit-multiple.html.tmpl" %]
+ </form>
+ <hr>
+[% END %]
+
+[%############################################################################%]
+[%# Navigation Bar #%]
+[%############################################################################%]
+
+<table>
+ <tr>
+ [% IF bugs.size > 0 %]
+ <td valign="middle" class="bz_query_buttons">
+ <form method="post" action="show_bug.cgi">
+ [% FOREACH id = buglist %]
+ <input type="hidden" name="id" value="[% id FILTER html %]">
+ [% END %]
+ <input type="hidden" name="format" value="multiple">
+ <input type="submit" id="long_format" value="Long Format">
+ </form>
+ <form method="post" action="show_bug.cgi">
+ <input type="hidden" name="ctype" value="xml">
+ [% FOREACH id = buglist %]
+ <input type="hidden" name="id" value="[% id FILTER html %]">
+ [% END %]
+ <input type="hidden" name="excludefield" value="attachmentdata">
+ <input type="submit" value="XML" id="xml">
+ </form>
+
+ [% IF user.is_timetracker %]
+ <form method="post" action="summarize_time.cgi">
+ <input type="hidden" name="id" value="[% buglist_joined FILTER html %]">
+ <input type="submit" id="timesummary" value="Time Summary">
+ </form>
+ [% END %]
+ </td>
+
+ <td>&nbsp;</td>
+
+ <td valign="middle" class="bz_query_links">
+ <a href="buglist.cgi?
+ [% urlquerypart FILTER html %]&amp;ctype=csv">CSV</a> |
+ <a href="buglist.cgi?
+ [% urlquerypart FILTER html %]&amp;title=
+ [%- title FILTER html %]&amp;ctype=atom">Feed</a> |
+ <a href="buglist.cgi?
+ [% urlquerypart FILTER html %]&amp;ctype=ics">iCalendar</a> |
+ <a href="colchange.cgi?
+ [% urlquerypart FILTER html %]&amp;query_based_on=
+ [% defaultsavename OR searchname FILTER url_quote %]">Change&nbsp;Columns</a> |
+
+ [% IF bugs.size > 1 && caneditbugs && !dotweak %]
+ <a href="buglist.cgi?[% urlquerypart FILTER html %]
+ [%- "&order=$qorder" FILTER html IF order %]&amp;tweak=1"
+ >Change&nbsp;Several&nbsp;[% terms.Bugs %]&nbsp;at&nbsp;Once</a>
+ |
+ [% END %]
+
+ [% IF bugowners && user.id %]
+ <a href="mailto:
+ [% bugowners FILTER html %]">Send&nbsp;Mail&nbsp;to&nbsp;[% terms.Bug %]&nbsp;Assignees</a> |
+ [% END %]
+
+ [%# Links to more things users can do with this bug list. %]
+ [% Hook.process("links") %]
+ </td>
+ [% END %]
+
+ <td valign="middle" class="bz_query_edit">
+ <a href="[% PROCESS edit_search_url %]">Edit&nbsp;Search</a>
+ </td>
+
+ [% IF searchtype == "saved" %]
+ <td valign="middle" nowrap="nowrap" class="bz_query_forget">
+ |
+ <a href="buglist.cgi?cmdtype=dorem&amp;remaction=forget&amp;namedcmd=
+ [% searchname FILTER url_quote %]&amp;token=
+ [% issue_hash_token([search_id, searchname]) FILTER url_quote %]">
+ Forget&nbsp;Search&nbsp;'[% searchname FILTER html %]'</a>
+ </td>
+ [% ELSE %]
+ <td>&nbsp;</td>
+
+ <td valign="middle" class="bz_query_remember">
+ <form method="get" action="buglist.cgi">
+ <input type="submit" id="remember" value="Remember search"> as
+ <input type="hidden" name="newquery"
+ value="[% urlquerypart FILTER html %][% "&order=$qorder" FILTER html IF order %]">
+ <input type="hidden" name="cmdtype" value="doit">
+ <input type="hidden" name="remtype" value="asnamed">
+ <input type="hidden" name="token" value="[% issue_hash_token(['savedsearch']) FILTER html %]">
+ <input type="text" id="save_newqueryname" name="newqueryname" size="20"
+ value="[% defaultsavename FILTER html %]">
+ </form>
+ </td>
+ [% END %]
+ </tr>
+</table>
+
+[% IF one_product.defined && bugs.size %]
+ <p class="bz_query_single_product">
+ [% PROCESS enter_bug_link %]
+ </p>
+[% END %]
+
+[%############################################################################%]
+[%# Page Footer #%]
+[%############################################################################%]
+
+[% PROCESS global/footer.html.tmpl %]
+
+[% BLOCK edit_search_url %]
+ [% editqueryname = searchname OR defaultsavename OR '' %]
+ query.cgi?[% urlquerypart FILTER html %]
+ [%- IF editqueryname != '' %]&amp;known_name=
+ [%- editqueryname FILTER url_quote %]
+ [% END %]
+[% END %]
+
+[% BLOCK enter_bug_link %]
+ <a href="enter_bug.cgi
+ [%- IF one_product.defined %]?product=
+ [%- one_product.name FILTER url_quote %][% END %]">File
+ a new [% terms.bug %]
+ [% IF one_product.defined %]
+ in the "[% one_product.name FILTER html %]" product
+ [% END %]</a>
+[% END %]
diff --git a/template/en/default/list/list.ics.tmpl b/template/en/default/list/list.ics.tmpl
new file mode 100644
index 000000000..b135267f4
--- /dev/null
+++ b/template/en/default/list/list.ics.tmpl
@@ -0,0 +1,103 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): William Jon McCann <mccann@jhu.edu>
+ #%]
+[% PROCESS global/variables.none.tmpl %]
+BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+[%+ PROCESS ics_prodid +%]
+VERSION:2.0
+[% FOREACH bug = bugs %]
+BEGIN:VTODO
+[%+ PROCESS ics_dtstart +%]
+[%+ PROCESS ics_summary +%]
+[%+ PROCESS ics_uid base_url=urlbase bug_id=bug.bug_id +%]
+[%+ PROCESS ics_url base_url=urlbase bug_id=bug.bug_id +%]
+[%+ PROCESS ics_status bug_status = bug.bug_status +%]
+[%+ PROCESS ics_dtstamp +%]
+[%+ ics_priorities.${bug.priority} FILTER ics('PRIORITY') +%]
+[% IF bug.changeddate %]
+[%+ bug.changedtime FILTER time("%Y%m%dT%H%M%SZ", "UTC") FILTER ics('LAST-MODIFIED') +%]
+[% END %]
+[% IF bug.percentage_complete %]
+[%+ bug.percentage_complete FILTER format('%d') FILTER ics('PERCENT-COMPLETE') +%]
+[% END %]
+[% IF bug.product %]
+[%+ bug.product FILTER ics('X-BUGZILLA-PRODUCT') +%]
+[% END %]
+[% IF bug.component %]
+[%+ bug.component FILTER ics('X-BUGZILLA-COMPONENT') +%]
+[% END %]
+[% IF bug.version %]
+[%+ bug.version FILTER ics('X-BUGZILLA-VERSION') +%]
+[% END %]
+[% IF bug.keywords %]
+[%+ bug.keywords FILTER ics('X-BUGZILLA-KEYWORDS') +%]
+[% END %]
+END:VTODO
+[% END %]
+END:VCALENDAR
+
+[% BLOCK ics_prodid %]
+ [% "-//Mozilla/Bugzilla $constants.BUGZILLA_VERSION//EN" FILTER ics('PRODID') %]
+[% END %]
+
+[% BLOCK ics_uid %]
+ [% "${bug_id}@${base_url}" FILTER url_quote FILTER ics('UID') %]
+[% END %]
+
+[% BLOCK ics_url %]
+ [% "${base_url}show_bug.cgi?id=${bug_id}" FILTER url_quote FILTER ics('URL;VALUE=URI') %]
+[% END %]
+
+[% BLOCK ics_dtstart %]
+ [% bug.opentime FILTER time("%Y%m%dT%H%M%SZ", "UTC") FILTER ics('DTSTART') %]
+[% END %]
+
+[% BLOCK ics_dtstamp %]
+ [% currenttime FILTER time("%Y%m%dT%H%M%SZ", "UTC") FILTER ics('DTSTAMP') %]
+[% END %]
+
+[% BLOCK ics_status %]
+ [% status = "" %]
+ [% FOREACH state = closedstates %]
+ [% IF bug_status == state %]
+ [% status = 'COMPLETED' %]
+ [% LAST %]
+ [% END %]
+ [% END %]
+ [% IF NOT status %]
+ [% IF bug_status == 'IN_PROGRESS' || bug_status == 'ASSIGNED' %]
+ [% status = 'IN-PROGRESS' %]
+ [% ELSE %]
+ [% status = 'NEEDS-ACTION' %]
+ [% END %]
+ [% END %]
+ [% status FILTER ics('STATUS') %]
+[% END %]
+
+[% BLOCK ics_summary %]
+ [% IF bug.short_desc %]
+ [% summary = bug.short_desc %]
+ [% ELSIF bug.short_short_desc %]
+ [% summary = bug.short_short_desc %]
+ [% ELSE %]
+ [% summary = "$terms.Bug $bug.bug_id" %]
+ [% END %]
+ [% summary FILTER ics('SUMMARY') %]
+[% END %]
diff --git a/template/en/default/list/list.js.tmpl b/template/en/default/list/list.js.tmpl
new file mode 100644
index 000000000..7e9664c43
--- /dev/null
+++ b/template/en/default/list/list.js.tmpl
@@ -0,0 +1,37 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ #%]
+
+// Note: only publicly-accessible bugs (those not in any group) will be
+// listed when using this JavaScript format. This is to prevent malicious
+// sites stealing information about secure bugs.
+
+bugs = new Array;
+
+[% FOREACH bug = bugs %]
+ bugs[[% bug.bug_id %]] = [
+ [% FOREACH column = displaycolumns %]
+ "[%- bug.$column FILTER js -%]"[% "," UNLESS loop.last %]
+ [% END %]
+ ];
+[% END %]
+
+if (window.buglistCallback) {
+ buglistCallback(bugs);
+}
diff --git a/template/en/default/list/list.rdf.tmpl b/template/en/default/list/list.rdf.tmpl
new file mode 100644
index 000000000..d7879a694
--- /dev/null
+++ b/template/en/default/list/list.rdf.tmpl
@@ -0,0 +1,56 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Myk Melez <myk@mozilla.org>
+ #%]
+
+<?xml version="1.0"[% IF Param('utf8') %] encoding="UTF-8"[% END %]?>
+<!-- [% template_version %] -->
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:bz="http://www.bugzilla.org/rdf#"
+ xmlns:nc="http://home.netscape.com/NC-rdf#">
+
+<bz:result rdf:about="[% urlbase FILTER xml %]buglist.cgi?[% urlquerypart FILTER html %]">
+ <bz:installation rdf:resource="[% urlbase FILTER xml %]" />
+ <bz:query_timestamp>[% currenttime FILTER time('%Y-%m-%d %T %Z') FILTER html %]</bz:query_timestamp>
+ <bz:bugs>
+ <Seq>
+ [% FOREACH bug = bugs %]
+ <li>
+
+ <bz:bug rdf:about="[% urlbase FILTER xml %]show_bug.cgi?id=[% bug.bug_id %]">
+
+ <bz:id nc:parseType="Integer">[% bug.bug_id %]</bz:id>
+
+ [% FOREACH column = displaycolumns %]
+ <bz:[% column %]>[% bug.$column FILTER html %]</bz:[% column %]>
+ [% END %]
+
+ </bz:bug>
+
+ </li>
+
+ [% END %]
+
+ </Seq>
+
+ </bz:bugs>
+
+</bz:result>
+
+</RDF>
diff --git a/template/en/default/list/quips.html.tmpl b/template/en/default/list/quips.html.tmpl
new file mode 100644
index 000000000..b330596c7
--- /dev/null
+++ b/template/en/default/list/quips.html.tmpl
@@ -0,0 +1,173 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ #%]
+
+[%# INTERFACE:
+ # added_quip: string. Defined if the CGI added a quip data before
+ # displaying anything; if defined, its value is that quip.
+ # show_quips: boolean. True if we are showing the entire quip list.
+ # quips: list of strings. Defined if and only if show_quips is true.
+ # List of all quips.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% PROCESS global/header.html.tmpl
+ title = "$terms.Bugzilla Quip System"
+ header = "Add your own clever headline"
+ %]
+
+[% IF added_quip %]
+ <p>
+ <font color="red">
+ Your quip '<tt>[% added_quip FILTER html %]</tt>' has been added.
+ [% IF Param("quip_list_entry_control") == "moderated" AND !user.in_group('admin') %]
+ It will be used as soon as it gets approved.
+ [% END %]
+ </font>
+ </p>
+[% END %]
+
+[% IF deleted_quip %]
+ <p>
+ <font color="red">
+ The quip '<tt>[% deleted_quip FILTER html %]</tt>' has been deleted.
+ </font>
+ </p>
+[% END %]
+
+[% IF approved or unapproved %]
+ <p>[% approved.size %] quips approved and [% unapproved.size %] quips unapproved</p>
+[% END %]
+
+
+<p>
+ [% terms.Bugzilla %] will pick a random quip for the headline on each
+ [% terms.bug %] list.
+</p>
+
+[% IF Param("quip_list_entry_control") != "closed" %]
+ <p>
+ You can extend the quip list. Type in something clever or funny or boring
+ (but not obscene or offensive, please) and bonk on the button.
+ [% IF Param("quip_list_entry_control") == "moderated" AND !user.in_group('admin') %]
+ Note that your quip has to be approved before it is used.
+ [% END %]
+ </p>
+
+ <form method="post" action="quips.cgi">
+ <input type="hidden" name="action" value="add">
+ <input type="hidden" name="token"
+ value="[% issue_hash_token(['create-quips']) FILTER html %]">
+ <input size="80" name="quip">
+ <p>
+ <input type="submit" id="add" value="Add This Quip">
+ </p>
+ </form>
+[% ELSE %]
+ <p>No new entries may be submitted at this time.
+ </p>
+[% END %]
+
+[% IF show_quips %]
+ [% IF !user.in_group('admin') %]
+ <h2>
+ Existing quips:
+ </h2>
+ <ul>
+ [% FOREACH quipid = quipids %]
+ [% NEXT IF NOT quips.$quipid.approved %]
+ <li>[% quips.$quipid.quip FILTER html %]</li>
+ [% END %]
+ </ul>
+ [% ELSE %]
+ <h2>Edit existing quips:</h2>
+ <p>
+ <strong>Note:</strong> Only approved quips will be shown.
+ If the parameter 'quip_list_entry_control' is set to <q>open</q>,
+ entered quips are automatically approved.
+ </p>
+ <form name="editform" method="post" action="quips.cgi">
+ <input type="hidden" name="action" value="approve">
+ <input type="hidden" name="token"
+ value="[% issue_hash_token(['approve-quips']) FILTER html %]">
+ <table border="1">
+ <thead><tr>
+ <th>Quip</th>
+ <th>Author</th>
+ <th>Action</th>
+ <th>Approved</th>
+ </tr></thead><tbody>
+ [% FOREACH quipid = quipids %]
+ <tr>
+ <td>[% quips.$quipid.quip FILTER html %]</td>
+ <td>
+ [% userid = quips.$quipid.userid %]
+ [% users.$userid FILTER html %]
+ [% "Unknown" IF NOT users.$userid %]
+ </td>
+ <td>
+ <a href="quips.cgi?action=delete&amp;quipid=[% quipid FILTER url_quote %]&amp;token=
+ [%- issue_hash_token(['quips', quipid]) FILTER url_quote %]">
+ Delete
+ </a>
+ </td>
+ <td>
+ <input type="hidden" name="defined_quipid_[% quipid FILTER html %]"
+ id="defined_quipid_[% quipid FILTER html %]"
+ value="1">
+ <input type="checkbox" name="quipid_[% quipid FILTER html %]"
+ id="quipid_[% quipid FILTER html %]"
+ [%- ' checked="checked"' IF quips.$quipid.approved %]>
+ </td>
+ </tr>
+ [% END %]
+ </tbody>
+ </table>
+ <script type="text/javascript"><!--
+ var numelements = document.forms.editform.elements.length;
+ function SetCheckboxes(value) {
+ var item;
+ for (var i=0 ; i<numelements ; i++) {
+ item = document.forms.editform.elements[i];
+ item.checked = value;
+ }
+ }
+ document.write(' <input type="button" name="uncheck_all" '
+ +'value="Uncheck All" onclick="SetCheckboxes(false);">');
+ document.write(' <input type="button" name="check_all" '
+ +'value="Check All" onclick="SetCheckboxes(true);">');
+ //--></script>
+
+ <input type="submit" id="update" value="Save Changes">
+ </form>
+ <br>
+ [% END %]
+[% ELSE %]
+ <p>
+ Those who like their wisdom in large doses can
+ <a href="quips.cgi?action=show">view
+ [% IF user.in_group('admin') %]
+ and edit
+ [% END %]
+ the whole quip list</a>.
+ </p>
+[% END %]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/list/server-push.html.tmpl b/template/en/default/list/server-push.html.tmpl
new file mode 100644
index 000000000..d1c157f72
--- /dev/null
+++ b/template/en/default/list/server-push.html.tmpl
@@ -0,0 +1,47 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Myk Melez <myk@mozilla.org>
+ #%]
+
+[%# INTERFACE:
+ # debug: boolean. True if we want the search displayed while we wait.
+ # query: string. The SQL query which makes the buglist.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+<html>
+ <head>
+ <title>[% terms.Bugzilla %] is pondering your search</title>
+ </head>
+ <body>
+ <h1 style="margin-top: 20%; text-align: center;">Please stand by ...</h1>
+
+ [% IF debug %]
+ <p>
+ [% FOREACH debugline = debugdata %]
+ <code>[% debugline FILTER html %]</code><br>
+ [% END %]
+ </p>
+ <p>
+ <code>[% query FILTER html %]</code>
+ </p>
+ [% END %]
+
+ </body>
+</html>
diff --git a/template/en/default/list/table.html.tmpl b/template/en/default/list/table.html.tmpl
new file mode 100644
index 000000000..1ad04867d
--- /dev/null
+++ b/template/en/default/list/table.html.tmpl
@@ -0,0 +1,263 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Myk Melez <myk@mozilla.org>
+ # Jesse Clark <jjclark1982@gmail.com>
+ #%]
+
+[%############################################################################%]
+[%# Initialization #%]
+[%############################################################################%]
+
+[%# Don't display the table or do any processing if there are no bugs
+ # to display %]
+[% RETURN IF !bugs.size %]
+
+[%# Columns whose titles or values should be abbreviated to make the list
+ # more compact. For columns whose titles should be abbreviated,
+ # the shortened title is included. For columns whose values should be
+ # abbreviated, a maximum length is provided along with the ellipsis that
+ # should be added to an abbreviated value, if any.
+ # wrap is set if a column's contents should be allowed to be word-wrapped
+ # by the browser.
+ #%]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+[% field_descs.short_short_desc = field_descs.short_desc %]
+[% field_descs.assigned_to_realname = field_descs.assigned_to %]
+[% field_descs.reporter_realname = field_descs.reporter %]
+[% field_descs.qa_contact_realname = field_descs.qa_contact %]
+
+[% abbrev =
+ {
+ "bug_severity" => { maxlength => 3 , title => "Sev" } ,
+ "priority" => { maxlength => 3 , title => "Pri" } ,
+ "rep_platform" => { maxlength => 3 , title => "Plt" } ,
+ "bug_status" => { maxlength => 4 } ,
+ "assigned_to" => { maxlength => 30 , ellipsis => "..." } ,
+ "assigned_to_realname" => { maxlength => 20 , ellipsis => "..." } ,
+ "reporter" => { maxlength => 30 , ellipsis => "..." } ,
+ "reporter_realname" => { maxlength => 20 , ellipsis => "..." } ,
+ "qa_contact" => { maxlength => 30 , ellipsis => "..." , title => "QAContact" } ,
+ "qa_contact_realname" => { maxlength => 20 , ellipsis => "..." , title => "QAContact" } ,
+ "resolution" => { maxlength => 4 } ,
+ "short_desc" => { wrap => 1 } ,
+ "short_short_desc" => { maxlength => 60 , ellipsis => "..." , wrap => 1 } ,
+ "status_whiteboard" => { title => "Whiteboard" , wrap => 1 } ,
+ "keywords" => { wrap => 1 } ,
+ "flagtypes.name" => { wrap => 1 } ,
+ "component" => { maxlength => 8 , title => "Comp" } ,
+ "product" => { maxlength => 8 } ,
+ "version" => { maxlength => 5 , title => "Vers" } ,
+ "op_sys" => { maxlength => 4 } ,
+ "bug_file_loc" => { maxlength => 30 } ,
+ "target_milestone" => { title => "TargetM" } ,
+ "percentage_complete" => { format_value => "%d %%" } ,
+ }
+%]
+
+[% PROCESS bug/time.html.tmpl %]
+
+[% Hook.process("before_table") %]
+
+[%############################################################################%]
+[%# Table Header #%]
+[%############################################################################%]
+
+[% tableheader = BLOCK %]
+ <table class="bz_buglist" cellspacing="0" cellpadding="4" width="100%">
+ <tr class="bz_buglist_header bz_first_buglist_header">
+ [% IF dotweak %]
+ <th>&nbsp;</th>
+ [% END %]
+ <th colspan="[% splitheader ? 2 : 1 %]" class="first-child">
+ <a href="buglist.cgi?
+ [% urlquerypart FILTER html %]&amp;order=
+ [% PROCESS new_order id='bug_id' %]
+ [%-#%]&amp;query_based_on=
+ [% defaultsavename OR searchname FILTER url_quote %]">ID
+ [% PROCESS order_arrow id='bug_id' ~%]
+ </a>
+ </th>
+
+ [% IF splitheader %]
+
+ [% FOREACH id = displaycolumns %]
+ [% NEXT UNLESS loop.count() % 2 == 0 %]
+ [% column = columns.$id %]
+ [% PROCESS columnheader %]
+ [% END %]
+
+ </tr><tr class="bz_buglist_header">
+ [% IF dotweak %]
+ <th>&nbsp;</th>
+ [% END %]
+ <th>&nbsp;</th>
+
+ [% FOREACH id = displaycolumns %]
+ [% NEXT IF loop.count() % 2 == 0 %]
+ [% column = columns.$id %]
+ [% PROCESS columnheader %]
+ [% END %]
+
+ [% ELSE %]
+
+ [% FOREACH id = displaycolumns %]
+ [% column = columns.$id %]
+ [% PROCESS columnheader %]
+ [% END %]
+
+ [% END %]
+
+ </tr>
+[% END %]
+
+[% BLOCK columnheader %]
+ <th colspan="[% splitheader ? 2 : 1 %]">
+ <a href="buglist.cgi?[% urlquerypart FILTER html %]&amp;order=
+ [% PROCESS new_order %]
+ [%-#%]&amp;query_based_on=
+ [% defaultsavename OR searchname FILTER url_quote %]">
+ [%- abbrev.$id.title || field_descs.$id || column.title -%]
+ [% PROCESS order_arrow ~%]
+ </a>
+ </th>
+[% END %]
+
+[% BLOCK new_order %]
+ [% desc = '' %]
+ [% IF (om = order.match("\\b$id( DESC)?")) %]
+ [% desc = ' DESC' IF NOT om.0 %]
+ [% END %]
+ [% id _ desc FILTER url_quote %]
+ [% IF id != 'bug_id' AND order %]
+ [% ',' _ order.remove("\\b$id( DESC)?(,\\s*|\$)") FILTER url_quote %]
+ [% END %]
+[% END %]
+
+[% BLOCK order_arrow %]
+ [% IF order.match("^$id DESC") %]
+ <span class="bz_sort_order_primary">&#x25BC;</span>
+ [% ELSIF order.match("^$id(,\\s*|\$)") %]
+ <span class="bz_sort_order_primary">&#x25B2;</span>
+ [% ELSIF order.match("\\b$id DESC") %]
+ <span class="bz_sort_order_secondary">&#x25BC;</span>
+ [% ELSIF order.match("\\b$id(,\\s*|\$)") %]
+ <span class="bz_sort_order_secondary">&#x25B2;</span>
+ [% END %]
+[% END %]
+
+[%############################################################################%]
+[%# Bug Table #%]
+[%############################################################################%]
+
+[% tableheader %]
+
+[% FOREACH bug = bugs %]
+ [% count = loop.count() %]
+
+ <tr id="b[% bug.bug_id %]" class="bz_bugitem
+ bz_[% bug.bug_severity FILTER css_class_quote -%]
+ bz_[% bug.priority FILTER css_class_quote -%]
+ bz_[% bug.bug_status FILTER css_class_quote -%]
+ [%+ "bz_$bug.resolution" FILTER css_class_quote IF bug.resolution -%]
+ [%+ "bz_secure" IF bug.secure_mode -%]
+ [%+ "bz_secure_mode_$bug.secure_mode" FILTER css_class_quote IF bug.secure_mode -%]
+ [%+ count % 2 == 1 ? "bz_row_odd" : "bz_row_even" -%]
+ ">
+
+ [% IF dotweak %]
+ <td class="bz_checkbox_column">
+ <input type="checkbox" name="id_[% bug.bug_id %]">
+ </td>
+ [% END %]
+ <td class="first-child bz_id_column">
+ <a href="show_bug.cgi?id=[% bug.bug_id %]">[% bug.bug_id %]</a>
+ <span style="display: none">[%+ '[SEC]' IF bug.secure_mode %]</span>
+ </td>
+
+ [% FOREACH column = displaycolumns %]
+ <td [% 'style="white-space: nowrap"' IF NOT abbrev.$column.wrap %]
+ class="bz_[% column FILTER css_class_quote %]_column">
+ [% IF abbrev.$column.maxlength %]
+ <span title="[%- display_value(column, bug.$column) FILTER html %]">
+ [% END %]
+ [% IF abbrev.$column.format_value %]
+ [%- bug.$column FILTER format(abbrev.$column.format_value) FILTER html -%]
+ [% ELSIF column == 'actual_time' ||
+ column == 'remaining_time' ||
+ column == 'estimated_time' %]
+ [% PROCESS formattimeunit time_unit=bug.$column %]
+ [%# Display the login name of the user if their real name is empty. %]
+ [% ELSIF column.match('_realname$') && bug.$column == '' %]
+ [% SET login_column = column.remove('_realname$') %]
+ [% bug.${login_column}.truncate(abbrev.$column.maxlength,
+ abbrev.$column.ellipsis) FILTER html %]
+ [% ELSIF column == 'short_desc' || column == "short_short_desc" %]
+ <a href="show_bug.cgi?id=[% bug.bug_id FILTER html %]">
+ [%- bug.$column.truncate(abbrev.$column.maxlength, abbrev.$column.ellipsis) FILTER html -%]
+ </a>
+ [% ELSE %]
+ [%- display_value(column, bug.$column).truncate(abbrev.$column.maxlength, abbrev.$column.ellipsis) FILTER html -%]
+ [% END %]
+ [% IF abbrev.$column.maxlength %]
+ </span>
+ [% END %]
+ </td>
+ [% END %]
+
+ </tr>
+
+ [% IF loop.last() && time_info.time_present == 1 %]
+ [% PROCESS time_summary_line %]
+ [% END %]
+
+[% END %]
+
+</table>
+
+[% BLOCK time_summary_line %]
+ <tr class="bz_time_summary_line">
+ [% columns_to_span = 1 %] [%# bugID %]
+ [% IF dotweak %]
+ [% columns_to_span = columns_to_span + 1 %]
+ [% END %]
+ [% FOREACH column = displaycolumns %]
+ [% IF column == 'actual_time' ||
+ column == 'remaining_time' ||
+ column == 'estimated_time' ||
+ column == 'percentage_complete' %]
+ [% IF columns_to_span > 0 %]
+ <td class="bz_total bz_total_label" colspan="
+ [%- columns_to_span FILTER html %]"><b>Totals</b></td>
+ [% columns_to_span = 0 %]
+ [% END %]
+ [% IF column == 'percentage_complete' %]
+ <td class="bz_total">[% time_info.percentage_complete
+ FILTER format(abbrev.$column.format_value) FILTER html %]</td>
+ [% ELSE %]
+ <td class="bz_total">
+ [%- PROCESS formattimeunit time_unit=time_info.$column %]</td>
+ [% END %]
+ [% ELSIF columns_to_span == 0 %] [%# A column following the first total %]
+ <td class="bz_total">&nbsp;</td>
+ [% ELSE %] [%# We haven't gotten to a time column yet, keep computing span %]
+ [% columns_to_span = columns_to_span + 1 %]
+ [% END %]
+ [% END %]
+ </tr>
+[% END %]
diff --git a/template/en/default/pages/bug-writing.html.tmpl b/template/en/default/pages/bug-writing.html.tmpl
new file mode 100644
index 000000000..ec997be0a
--- /dev/null
+++ b/template/en/default/pages/bug-writing.html.tmpl
@@ -0,0 +1,181 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Eli Goldberg <eli@prometheus-music.com>
+ # Gervase Markham <gerv@gerv.net>
+ # Vera Horiuchi
+ # Claudius Gayle
+ # Peter Mock
+ # Chris Pratt
+ # Tom Schutter
+ # Chris Yeh
+ #%]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+[% INCLUDE global/header.html.tmpl title = "$terms.Bug Writing Guidelines" %]
+
+ <p>Effective [% terms.bug %] reports are the most likely to be fixed.
+ These guidelines explain how to write such reports.
+
+<h3>Principles</h3>
+
+ <ul>
+ <li>Be precise</li>
+ <li>Be clear - explain it so others can reproduce the [% terms.bug %]</li>
+ <li>One [% terms.bug %] per report</li>
+ <li>No [% terms.bug %] is too trivial to report -
+ small [% terms.bugs %] may hide big [% terms.bugs %]</li>
+ <li>Clearly separate fact from speculation</li>
+ </ul>
+
+<h3>Preliminaries</h3>
+
+<ol>
+ <li>Reproduce your [% terms.bug %] using a recent build of the
+ software, to see whether it has already been fixed.
+ </li>
+
+ <li><a href="query.cgi?format=specific">Search</a>
+ [% terms.Bugzilla %], to see whether your [% terms.bug %] has
+ already been reported.</li>
+</ol>
+
+<h3>Reporting a New [% terms.Bug %]</h3>
+
+<p>If you have reproduced the [% terms.bug %] in a recent build and
+no-one else appears to have reported it, then:</p>
+
+<ol>
+ <li>Choose
+ "<a href="enter_bug.cgi">Enter a new [% terms.bug %]</a>"</li>
+ <li>Select the product in which you've found the [% terms.bug %]</li>
+ <li>Fill out the form. Here is some help understanding it:</li>
+</ol>
+
+ <blockquote>
+ <p><b>Component:</b> In which sub-part of the software does it
+ exist?<br>
+ This field is required.
+ Click the word "Component" to see a description of each
+ component. If none seems appropriate, look for a "General" component.</p>
+
+ <p><b>OS:</b> On which operating system (OS) did you find
+ it?
+ (e.g. Linux, Windows XP, Mac OS X.)<br>
+ If you know the [% terms.bug %] happens on more than one type of
+ operating system, choose <em>[% display_value("op_sys", "All") FILTER html %]</em>.
+ If your OS isn't listed, choose <em>[% display_value("op_sys", "Other") FILTER html %]</em>.</p>
+
+ <p><b>Summary:</b> How would you describe the [% terms.bug %], in
+ approximately 60 or fewer characters?<br>
+ A good summary should <b>quickly and uniquely identify [% terms.abug %]
+ report</b>. It should explain the problem, not your suggested solution.<br>
+ <ul>
+ <li>Good: "<tt>Cancelling a File Copy dialog crashes
+ File Manager</tt>"</li>
+ <li>Bad: "<tt>Software crashes</tt>"</li>
+ <li>Bad: "<tt>Browser should work with my web site</tt>"</li>
+ </ul>
+
+ <b>Description:</b>
+ The details of your problem report, including:</p>
+
+ <blockquote>
+ <p><b>Overview:</b> More detailed restatement of
+ summary.</p>
+
+ <blockquote>
+<pre>
+Drag-selecting any page crashes Mac builds in the NSGetFactory function.
+</pre>
+ </blockquote>
+
+ <p><b>Steps to Reproduce:</b> Minimized, easy-to-follow steps that
+ will trigger the [% terms.bug %]. Include any special setup steps.</p>
+
+ <blockquote>
+<pre>
+1) View any web page. (I used the default sample page,
+resource:/res/samples/test0.html)
+
+2) Drag-select the page. (Specifically, while holding down
+the mouse button, drag the mouse pointer downwards from any
+point in the browser's content region to the bottom of the
+browser's content region.)
+</pre>
+ </blockquote>
+
+ <p><b>Actual Results:</b> What the application did after performing
+ the above steps.</p>
+
+ <blockquote>
+<pre>
+The application crashed.
+</pre>
+ </blockquote>
+
+ <p><b>Expected Results:</b> What the application should have done,
+ were the [% terms.bug %] not present.</p>
+
+ <blockquote>
+<pre>
+The window should scroll downwards. Scrolled content should be selected.
+(Or, at least, the application should not crash.)
+</pre>
+ </blockquote>
+
+ <p><b>Build Date &amp; Platform:</b> Date and platform of the build
+ in which you first encountered the [% terms.bug %].</p>
+
+ <blockquote>
+<pre>
+Build 2006-08-10 on Mac OS 10.4.3
+</pre>
+ </blockquote>
+
+ <p><b>Additional Builds and Platforms:</b> Whether or not
+ the [% terms.bug %] takes place on other platforms (or browsers,
+ if applicable).</p>
+
+ <blockquote>
+<pre>
+Doesn't Occur On Build 2006-08-10 on Windows XP Home (Service Pack 2)
+</pre>
+ </blockquote>
+
+ <p><b>Additional Information:</b> Any other useful information.
+ <br><br>For crashing [% terms.bugs %]:</p>
+
+ <ul>
+ <li><b>Windows:</b> Note the type of the crash, and the module that the
+ application crashed in (e.g. access violation in apprunner.exe).</li>
+
+ <li><b>Mac OS X:</b> Attach the "Crash Reporter" log that appears
+ upon crash.
+ Only include the section directly below the crashing thread, usually
+ titled "Thread 0 Crashed". Please do not paste the entire log!</li>
+ </ul>
+ </blockquote>
+
+ <p>Double-check your report for errors and omissions, then press "Commit".
+ Your [% terms.bug %] report will now be in
+ the [% terms.Bugzilla %] database.<br>
+ </p>
+</blockquote>
+
+[% INCLUDE global/footer.html.tmpl %]
diff --git a/template/en/default/pages/fields.html.tmpl b/template/en/default/pages/fields.html.tmpl
new file mode 100644
index 000000000..9c905abad
--- /dev/null
+++ b/template/en/default/pages/fields.html.tmpl
@@ -0,0 +1,234 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Terry Weissman <terry@mozilla.org>
+ # Gervase Markham <gerv@gerv.net>
+ #%]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+[% PROCESS global/header.html.tmpl
+ title = "$terms.Bug Fields"
+ style_urls = ['skins/standard/page.css']
+%]
+
+<p>This page describes the various fields that you see
+ on [% terms.abug %].</p>
+
+<table class="field_value_explanation">
+ <thead>
+ <tr>
+ <td id="bug_status">
+ <h2>[% field_descs.bug_status FILTER upper FILTER html %]</h2>
+ </td>
+
+ <td id="resolution">
+ <h2>[% field_descs.resolution FILTER upper FILTER html %]</h2>
+ </td>
+ </tr>
+
+ <tr>
+ <td>The [% field_descs.bug_status FILTER html %] field indicates the
+ current state of a [% terms.bug %]. Only certain status transitions
+ are allowed.</td>
+
+ <td>The [% field_descs.resolution FILTER html %] field indicates what
+ happened to this [%+ terms.bug %].</td>
+ </tr>
+ </thead>
+
+ <tbody>
+ <tr class="header_row">
+ <td colspan="2">Open [% terms.Bugs %]</td>
+ </tr>
+ <tr>
+ <td>
+ <dl>
+ <dt>
+ [% display_value("bug_status", "UNCONFIRMED") FILTER html %]
+ </dt>
+ <dd>
+ This [% terms.bug %] has recently been added to the database.
+ Nobody has confirmed that this [% terms.bug %] is valid. Users
+ who have the "canconfirm" permission set may confirm
+ this [% terms.bug %], changing its state to
+ <b>[% display_value("bug_status", "CONFIRMED") FILTER html %]</b>.
+ Or, it may be directly resolved and marked
+ <b>[% display_value("bug_status", "RESOLVED") FILTER html %]</b>.
+ </dd>
+
+ <dt>
+ [% display_value("bug_status", "CONFIRMED") FILTER html %]
+ </dt>
+ <dd>
+ This [% terms.bug %] is valid and has recently been filed.
+ [%+ terms.Bugs %] in this state become
+ <b>[% display_value("bug_status", "IN_PROGRESS") FILTER html %]</b>
+ when somebody is working on them, or become resolved and marked
+ <b>[% display_value("bug_status", "RESOLVED") FILTER html %]</b>.
+ </dd>
+
+ <dt>
+ [% display_value("bug_status", "IN_PROGRESS") FILTER html %]
+ </dt>
+ <dd>
+ This [% terms.bug %] is not yet resolved, but is assigned to the
+ proper person who is working on the [% terms.bug %]. From here,
+ [%+ terms.bugs %] can be given to another person and become
+ <b>[% display_value("bug_status", "CONFIRMED") FILTER html %]</b>, or
+ resolved and become
+ <b>[% display_value("bug_status", "RESOLVED") FILTER html %]</b>.
+ </dd>
+ </dl>
+ </td>
+
+ <td>
+ No resolution yet. All [% terms.bugs %] which are in one of
+ these "open" states have no resolution set.
+ </td>
+ </tr>
+
+ <tr class="header_row">
+ <td colspan="2">Closed [% terms.Bugs %]</td>
+ </tr>
+
+ <tr>
+ <td>
+ <dl>
+ <dt>
+ [% display_value("bug_status", "RESOLVED") FILTER html %]
+ </dt>
+ <dd>
+ A resolution has been performed, and it is awaiting verification by
+ QA. From here [% terms.bugs %] are either reopened and given some
+ open status, or are verified by QA and marked
+ <b>[% display_value("bug_status", "VERIFIED") FILTER html %]</b>.
+ </dd>
+
+ <dt>
+ [% display_value("bug_status", "VERIFIED") FILTER html %]
+ </dt>
+ <dd>
+ QA has looked at the [% terms.bug %] and the resolution and
+ agrees that the appropriate resolution has been taken. This is
+ the final status for [% terms.bugs %].
+ </dd>
+ </dl>
+ </td>
+
+ <td>
+ <dl>
+ <dt>
+ [% display_value("resolution", "FIXED") FILTER html %]
+ </dt>
+ <dd>
+ A fix for this [% terms.bug %] is checked into the tree and
+ tested.
+ </dd>
+
+ <dt>
+ [% display_value("resolution", "INVALID") FILTER html %]
+ </dt>
+ <dd>
+ The problem described is not [% terms.abug %].
+ </dd>
+
+ <dt>
+ [% display_value("resolution", "WONTFIX") FILTER html %]
+ </dt>
+ <dd>
+ The problem described is [% terms.abug %] which will never be
+ fixed.
+ </dd>
+
+ <dt>
+ [% display_value("resolution", "DUPLICATE") FILTER html %]
+ </dt>
+ <dd>
+ The problem is a duplicate of an existing [% terms.bug %].
+ When [% terms.abug %] is marked as a
+ <b>[% display_value("resolution", "DUPLICATE") FILTER html %]</b>,
+ you will see which [% terms.bug %] it is a duplicate of,
+ next to the resolution.
+ </dd>
+
+ <dt>
+ [% display_value("resolution", "WORKSFORME") FILTER html %]
+ </dt>
+ <dd>
+ All attempts at reproducing this [% terms.bug %] were futile,
+ and reading the code produces no clues as to why the described
+ behavior would occur. If more information appears later,
+ the [% terms.bug %] can be reopened.
+ </dd>
+ </dl>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+<h2>Other Fields</h2>
+
+[% PROCESS "bug/field-help.none.tmpl" %]
+
+[% SET field_help_map = {} %]
+[% FOREACH field = bug_fields.keys %]
+ [% SET field_desc = field_descs.$field %]
+ [% field_help_map.$field_desc = { help => help_html.$field,
+ field => field } %]
+[% END %]
+
+[%# These are fields that don't need to be documented, either because
+ # they have docs somewhere else in the UI, or they don't show up on bugs.
+ # %]
+[% SET skip_fields = [
+ 'days_elapsed',
+ 'everconfirmed',
+ 'reporter_accessible',
+ 'cclist_accessible',
+ 'bug_group',
+ 'commenter',
+ 'owner_idle_time',
+ 'bug_status',
+ 'resolution',
+] %]
+
+<dl class="field_descriptions">
+[% FOREACH field_desc = field_help_map.keys.sort %]
+ [% SET field = field_help_map.${field_desc}.field %]
+ [% SET field_object = bug_fields.$field %]
+
+ [% NEXT IF field_object.obsolete %]
+ [% NEXT IF !user.is_timetracker AND field_object.is_timetracking %]
+
+ [% NEXT IF field == 'status_whiteboard' AND !Param('usestatuswhiteboard') %]
+ [% NEXT IF field == 'target_milestone' AND !Param('usetargetmilestone') %]
+ [% NEXT IF field == 'alias' AND !Param('usebugaliases') %]
+
+ [%# For now we don't have help for attachment fields and so on. %]
+ [% NEXT IF field.match('\.') %]
+
+ [% NEXT IF skip_fields.contains(field) %]
+
+ <dt id="[% field FILTER html %]">[% field_desc FILTER html %]</dt>
+ <dd>
+ [% SET help_text = field_help_map.${field_desc}.help %]
+ [% help_text FILTER none %]
+ </dd>
+[% END %]
+</dl>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/pages/linked.html.tmpl b/template/en/default/pages/linked.html.tmpl
new file mode 100644
index 000000000..52b1735f6
--- /dev/null
+++ b/template/en/default/pages/linked.html.tmpl
@@ -0,0 +1,55 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Stefan Seifert <nine@detonation.org>
+ # Gervase Markham <gerv@gerv.net>
+ #%]
+
+[% INCLUDE global/header.html.tmpl title = "Your Linkified Text" %]
+[% USE Bugzilla %]
+[% cgi = Bugzilla.cgi %]
+
+<p>
+ Copy and paste the text below:
+</p>
+
+<hr>
+
+<p>
+<pre class="bz_comment_text">
+[%- cgi.param("text") FILTER wrap_comment FILTER quoteUrls FILTER html -%]
+</pre>
+</p>
+
+<hr>
+
+<p>
+ If you place it in <tt>&lt;pre&gt;</tt> tags,
+ the text will end up looking like this:
+</p>
+
+<hr>
+
+<p>
+<pre class="bz_comment_text">
+[%- cgi.param("text") FILTER wrap_comment FILTER quoteUrls -%]
+</pre>
+</p>
+
+<hr>
+
+[% INCLUDE global/footer.html.tmpl %]
diff --git a/template/en/default/pages/linkify.html.tmpl b/template/en/default/pages/linkify.html.tmpl
new file mode 100644
index 000000000..b936e8645
--- /dev/null
+++ b/template/en/default/pages/linkify.html.tmpl
@@ -0,0 +1,42 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Stefan Seifert <nine@detonation.org>
+ # Gervase Markham <gerv@gerv.net>
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+[% INCLUDE global/header.html.tmpl title = "Linkify Text" %]
+
+<p>
+ If you enter some text, this form will return it marked up like a
+ standard [% terms.Bugzilla %] comment. That is, valid [% terms.bug %] numbers,
+ URLs, email addresses and so on will be replaced with appropriate HTML links.
+</p>
+
+<form action="page.cgi" method="post">
+ [% INCLUDE global/textarea.html.tmpl
+ name = 'text'
+ minrows = 20
+ cols = constants.COMMENT_COLS
+ %]
+ <br>
+ <input type="hidden" name="id" value="linked.html">
+ <input type="submit" id="linkify" value="Linkify">
+</form>
+
+[% INCLUDE global/footer.html.tmpl %]
diff --git a/template/en/default/pages/quicksearch.html.tmpl b/template/en/default/pages/quicksearch.html.tmpl
new file mode 100644
index 000000000..e6398eade
--- /dev/null
+++ b/template/en/default/pages/quicksearch.html.tmpl
@@ -0,0 +1,274 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2009
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+[% INCLUDE global/header.html.tmpl
+ title = "$terms.Bugzilla QuickSearch",
+ style_urls = ['skins/standard/page.css']
+ onload = 'document.forms[\'f\'].quicksearch.focus()'
+ %]
+
+[% USE Bugzilla %]
+
+<p>Type in one or more words (or pieces of words) to search for:</p>
+
+<form name="f" action="buglist.cgi" method="get"
+ onsubmit="if (this.quicksearch.value == '')
+ { alert('Please enter one or more search terms first.');
+ return false; } return true;">
+ <input type="text" size="40" name="quicksearch">
+ <input type="submit" value="Search" id="find">
+</form>
+
+<h2>The Basics</h2>
+
+<ul class="qs_help">
+ <li>If you just put a word or series of words in the search box,
+ [%+ terms.Bugzilla %] will search the
+ [%+ field_descs.product FILTER html %],
+ [%+ field_descs.component FILTER html %],
+ [%+ IF use_keywords %][%+ field_descs.keywords FILTER html %],[% END %]
+ [%+ IF Param('usebugaliases') %][% field_descs.alias FILTER html %],[% END %]
+ [%+ field_descs.short_desc FILTER html %],
+ [%+ IF Param('usestatuswhiteboard') %][% field_descs.status_whiteboard FILTER html %],[% END %]
+ and [% field_descs.longdesc FILTER html %] fields for your word or words.</li>
+
+ <li>Typing just a <strong>number</strong> in the search box will take
+ you directly to the [% terms.bug %] with that ID.
+ [% IF Param('usebugaliases') %]
+ Also, just typing the <strong>alias</strong> of [% terms.abug %]
+ will take you to that [% terms.bug %].
+ [% END %]
+ </li>
+
+ <li>Adding more terms <strong>narrows down</strong> the search, it does not
+ expand it. (In other words, [% terms.Bugzilla %] searches for
+ [%+ terms.bugs %] that match <em>all</em> your criteria, not
+ [%+ terms.bugs %] that match <em>any</em> of your criteria.)</li>
+
+ <li>Searching is <strong>case-insensitive</strong>. So <kbd>table</kbd>,
+ <kbd>Table</kbd>, and <kbd>TABLE</kbd> are all the same.</li>
+
+ <li>[% terms.Bugzilla %] does not just search for the exact word you put in,
+ but also for any word that <strong>contains</strong> that word.
+ So, for example, searching for "cat" would also find [% terms.bugs %]
+ that contain it as part of other words&mdash;for example, [% terms.abug %]
+ mentioning "<strong>cat</strong>ch" or "certifi<strong>cat</strong>e". It
+ will not find partial words in the [% field_descs.longdesc FILTER html %]
+ or [% field_descs.keywords FILTER html %] fields,
+ though&mdash;only full words are matched, there.</li>
+
+ <li>By default, only <strong>open</strong> [% terms.bugs %] are
+ searched. If you want to know how to also search closed [% terms.bugs %],
+ see the <a href="#shortcuts">Advanced Shortcuts</a> section.</li>
+
+ <li>If you want to search <strong>specific fields</strong>, you do it like
+ <kbd>field:value</kbd>, where <kbd>field</kbd> is one of the
+ <a href="#fields">field names</a> lower down in this
+ document and <kbd>value</kbd> is the value you want to search for
+ in that field. If you put commas in the <kbd>value</kbd>, then it is
+ interpreted as a list of values, and [% terms.bugs %] that match
+ <em>any</em> of those values will be searched for.</li>
+</ul>
+
+<p>You may also want to read up on the <a href="#advanced">Advanced
+ Features</a>.</p>
+
+<h2 id="fields">Fields You Can Search On</h2>
+
+<p>You can specify any of these fields like <kbd>field:value</kbd>
+ in the search box, to search on them. You can also abbreviate
+ the field name, as long as your abbreviation matches only one field name.
+ So, for example, searching on <kbd>stat:VERIFIED</kbd> will find all
+ [%+ terms.bugs %] in the <kbd>VERIFIED</kbd> status. Some fields have
+ multiple names, and you can use any of those names to search for them.</p>
+
+[% IF Bugzilla.active_custom_fields.size %]
+ [% SET first_field = Bugzilla.active_custom_fields.0 %]
+ <p>For custom fields, they can be used and abbreviated
+ based on the part of their name <em>after</em> the <kbd>cf_</kbd>
+ if you'd like, in addition to their standard name starting with
+ <kbd>cf_</kbd>. So for example,
+ <kbd>[% first_field.name FILTER html %]</kbd> can be
+ referred to as
+ <kbd>[% first_field.name.replace('^cf_') FILTER html %]</kbd>,
+ also. However, if this causes a conflict between the standard
+ [%+ terms.Bugzilla %] field names and the custom field names, the
+ standard field names always take precedence.</p>
+[% END %]
+
+[% SET field_table = {} %]
+[% FOREACH field = quicksearch_field_names.keys %]
+ [% description = field_descs.$field %]
+ [% field_table.$description = quicksearch_field_names.${field} %]
+[% END %]
+
+
+<table cellspacing="0" cellpadding="0" border="0" class="qs_fields">
+ <thead>
+ <tr>
+ <th class="field_name">Field</th>
+ <th class="field_nickname">Field Name(s) For Search</th>
+ </tr>
+ </thead>
+ <tbody>
+ [% FOREACH desc = field_table.keys.sort %]
+ <tr>
+ <td class="field_name">[% desc FILTER html %]</td>
+ <td class="field_nickname">
+ [% FOREACH nickname = field_table.$desc %]
+ <kbd>[% nickname FILTER html %]</kbd>
+ [% ",&nbsp; " UNLESS loop.last %]
+ [% END %]
+ </tr>
+ [% END %]
+ </tbody>
+</table>
+
+<h2 id="advanced">Advanced Features</h2>
+
+<ul class="qs_help">
+ <li>If you want to search for a <strong>phrase</strong> or something that
+ contains spaces, you can put it in quotes, like:
+ <kbd>"this is a phrase"</kbd>. You can also use quotes to search for
+ characters that would otherwise be interpreted specially by quicksearch.
+ For example, <kbd>"this|thing"</kbd> would search for the literal phrase
+ <em>this|thing</em>.</li>
+
+ <li>You can use <strong>AND</strong>, <strong>NOT</strong>,
+ and <strong>OR</strong> in searches.
+
+ You can also use <kbd>-</kbd> to mean "NOT", and <kbd>|</kbd> to mean "OR".
+ There is no special character for "AND", because by default any search
+ terms that are separated by a space are joined by an "AND".
+ Examples:
+ <ul>
+ <li>
+ <strong>NOT</strong>:<br>
+ Use <kbd><strong>-</strong><em>summary:foo</em></kbd> to exclude
+ [%+ terms.bugs %] with <kbd>foo</kbd> in the summary.<br>
+ <kbd><em>NOT summary:foo</em></kbd> would have the same effect.
+ </li>
+ <li>
+ <strong>AND</strong>:<br>
+ <kbd><em>foo bar</em></kbd> searches for [% terms.bugs %] that contains
+ both <kbd>foo</kbd> and <kbd>bar</kbd>.<br>
+ <kbd><em>foo AND bar</em></kbd> would have the same effect.
+ </li>
+ <li>
+ <strong>OR</strong>:<br>
+ <kbd><em>foo<strong>|</strong>bar</em></kbd> would search
+ for [% terms.bugs %] that contain <kbd>foo</kbd> OR <kbd>bar</kbd>.<br>
+ <kbd><em>foo OR bar</em></kbd> would have the same effect.<br>
+ </li>
+ </ul>
+
+ <p>OR has higher precedence than AND; AND is the top level operation.
+ For example:</p>
+ <p>Searching for <em><kbd>url|location bar|field -focus</kbd></em> means
+ (<kbd>url</kbd> OR <kbd>location</kbd>) AND (<kbd>bar</kbd> OR
+ <kbd>field</kbd>) AND (NOT <kbd>focus</kbd>)</p>
+ </li>
+</ul>
+
+<h2 id="shortcuts">Advanced Shortcuts</h2>
+
+<p>In addition to using <a href="#fields">field names</a> to search
+ specific fields, there are certain characters or words that you can
+ use as a "shortcut" for searching certain fields:</p>
+
+<table cellspacing="0" cellpadding="0" border="0" class="qs_fields">
+ <thead>
+ <tr>
+ <th class="field_name">Field</th>
+ <th class="field_nickname">Shortcut(s)</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td class="field_name">[% field_descs.bug_status FILTER html %]</td>
+ <td class="field_nickname">
+ Make the <strong>first word</strong> of your search the name of any
+ status, or even an abbreviation of any status, and [% terms.bugs %]
+ in that status will be searched. <strong><kbd>ALL</kbd></strong>
+ is a special shortcut that means "all statuses".
+ <strong><kbd>OPEN</kbd></strong> is a special shortcut that means
+ "all open statuses".
+ </td>
+ </tr>
+ <tr>
+ <td class="field_name">[% field_descs.resolution FILTER html %]</td>
+ <td class="field_nickname">
+ Make the <strong>first word</strong> of your search the name of any
+ resolution, or even an abbreviation of any resolution, and
+ [%+ terms.bugs %] with that resolution will be searched. For example,
+ making <kbd>FIX</kbd> the first word of your search will find all
+ [%+ terms.bugs %] with a resolution of <kbd>FIXED</kbd> .
+ </tr>
+ <tr>
+ <td class="field_name">[% field_descs.priority FILTER html %]</td>
+ <td class="field_nickname">"<strong>P1</strong>" (as a word anywhere in
+ the search) means "find [% terms.bugs %] with the highest priority.
+ "P2" means the second-highest priority, and so on.
+ <p>Searching for "<strong>P1-3</strong>" will find [% terms.bugs %] in
+ any of the three highest priorities, and so on.</p>
+ </td>
+ </tr>
+ <tr>
+ <td class="field_name">[% field_descs.assigned_to FILTER html %]</td>
+ <td class="field_nickname"><strong>@</strong><em>value</em></td>
+ </tr>
+ <tr>
+ <td class="field_name">[% field_descs.product FILTER html %] or
+ [%+ field_descs.component FILTER html %]</td>
+ <td class="field_nickname"><strong>:</strong><em>value</em></td>
+ </tr>
+ [% IF use_keywords %]
+ <tr>
+ <td class="field_name">[% field_descs.keywords FILTER html %]</td>
+ <td class="field_nickname"><strong>!</strong><em>value</em></td>
+ </tr>
+ [% END %]
+ <tr>
+ [% SET key = "flagtypes.name" %]
+ <td class="field_name">[% field_descs.$key FILTER html %]</td>
+ <td class="field_nickname">
+ <em>flag</em><strong>?</strong><em>requestee</em>
+ </td>
+ </tr>
+ <tr>
+ <td class="field_name">[% field_descs.longdesc FILTER html %]
+ or [% field_descs.short_desc FILTER html %]</td>
+ <td class="field_nickname">
+ <strong>#</strong><em>value</em>
+ </td>
+ </tr>
+ [% IF Param('usestatuswhiteboard') %]
+ <tr>
+ <td class="field_name">[% field_descs.short_desc FILTER html %]
+ or [% field_descs.status_whiteboard FILTER html %]</td>
+ <td class="field_nickname"><strong>[</strong><em>value</em></td>
+ </tr>
+ [% END %]
+ </tbody>
+</table>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/pages/release-notes.html.tmpl b/template/en/default/pages/release-notes.html.tmpl
new file mode 100644
index 000000000..55141d1e2
--- /dev/null
+++ b/template/en/default/pages/release-notes.html.tmpl
@@ -0,0 +1,4150 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved.
+ # Portions created by Everything Solved are Copyright (C) 2006
+ # Everything Solved. All Rights Reserved.
+ #
+ # Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+[% SET title = "$terms.Bugzilla 4.0 Release Notes" %]
+[% INCLUDE global/header.html.tmpl
+ title = title
+ style_urls = ['skins/standard/page.css']
+%]
+
+<h1>[% title FILTER html %]</h1>
+
+<ul class="bz_toc">
+ <li><a href="#v40_introduction">Introduction</a></li>
+ <li><a href="#v40_req">Minimum Requirements</a></li>
+ <li><a href="#v40_feat">New Features and Improvements</a></li>
+ <li><a href="#v40_issues">Outstanding Issues</a></li>
+ <li><a href="#v40_upgrading">Notes On Upgrading From a Previous Version</a></li>
+ <li><a href="#v40_code_changes">Code Changes Which May Affect
+ Customizations and Extensions</a></li>
+ <li><a href="#v40_previous">Release Notes for Previous Versions</a></li>
+</ul>
+
+<h2 id="v40_introduction">Introduction</h2>
+
+<p>This is [% terms.Bugzilla %] 4.0! Since 3.6 (our previous major
+ release) we've come a long way, and we've come even further compared to
+ 3.0 in 2007! Since [% terms.Bugzilla %] 3.0, almost every major user
+ interface in [% terms.Bugzilla %] has been redesigned, the WebServices have
+ evolved enormously, there's a great new Extensions system, and there
+ are hundreds of other new features. With the major redesigns that come
+ particularly in this release compared to 3.6, we felt that it was time to
+ call this release 4.0.</p>
+
+<p>It's not just major WebService and UI enhancements that are new in
+ [%+ terms.Bugzilla %] 4.0&mdash;there are many other exciting new features,
+ including automatic duplicate detection, enhanced custom field
+ functionality, autocomplete for users, search improvements, and much
+ more. Overall, 4.0 is far and away the best version of [% terms.Bugzilla %]
+ we've ever released.</p>
+
+<p>If you're upgrading, make sure to read <a href="#v40_upgrading">Notes
+ On Upgrading From a Previous Version</a>. If you are upgrading from a release
+ before 3.6, make sure to read the release notes for all the
+ <a href="#v40_previous">previous versions</a> in between your version
+ and this one, <strong>particularly the Upgrading section of each
+ version's release notes</strong>.</p>
+
+<p>We would like to thank
+ <a href="http://www.itasoftware.com/">ITA Software</a>,
+ the <a href="http://www.ibm.com/linux/ltc/">IBM Linux Technology Center</a>,
+ and <a href="http://www.redhat.com/">Red Hat</a> for funding the development
+ of certain features and improvements in this release of
+ [%+ terms.Bugzilla %].</p>
+
+<h2 id="v40_req">Minimum Requirements</h2>
+
+<p>Any requirements that are new since 3.6.3 will look like
+ <span class="req_new">this</span>.</p>
+
+<ul>
+ <li><a href="#v40_req_perl">Perl</a></li>
+ <li><a href="#v40_req_mysql">For MySQL Users</a></li>
+ <li><a href="#v40_req_pg">For PostgreSQL Users</a></li>
+ <li><a href="#v40_req_oracle">For Oracle Users</a></li>
+ <li><a href="#v40_req_modules">Required Perl Modules</a></li>
+ <li><a href="#v40_req_optional_mod">Optional Perl Modules</a></li>
+ <li><a href="#v40_req_apache">Optional Apache Modules</a></li>
+</ul>
+
+
+<h3 id="v40_req_perl">Perl</h3>
+
+<p>Perl v5.8.1</p>
+
+[% INCLUDE db_req db='mysql' %]
+
+[% INCLUDE db_req db='pg' %]
+
+[% INCLUDE db_req db='oracle' %]
+
+<h3 id="v40_req_modules">Required Perl Modules</h3>
+
+[% INCLUDE req_table reqs = REQUIRED_MODULES
+ new = ['List-MoreUtils']
+ updated = ['Email-MIME', 'CGI.pm', 'Apache-SizeLimit'] %]
+
+<h3 id="v40_req_optional_mod">Optional Perl Modules</h3>
+
+<p>The following perl modules, if installed, enable various
+ features of [% terms.Bugzilla %]:</p>
+
+[% INCLUDE req_table reqs = OPTIONAL_MODULES
+ new = ['JSON-XS', 'Win32-API', 'Math-Random-Secure']
+ updated = ['Apache-SizeLimit', 'SOAP-Lite']
+ include_feature = 1 %]
+
+<h3 id="v40_req_apache">Optional Apache Modules</h3>
+
+<p>If you are using Apache as your webserver, [% terms.Bugzilla %] can
+ now take advantage of some Apache features if you have the below Apache
+ modules installed and enabled. Currently,
+ <a href="#v40_feat_js_css_update">certain [% terms.Bugzilla %] features</a>
+ are enabled only if you have all of the following modules installed
+ and enabled:</p>
+
+<ul>
+ <li>mod_headers</li>
+ <li>mod_expires</li>
+ <li>mod_env</li>
+</ul>
+
+<p>On most systems (but not on Windows), <kbd>checksetup.pl</kbd> is able to
+ tell whether or not you have these modules installed, and it will tell
+ you.</p>
+
+<h2 id="v40_feat">New Features and Improvements</h2>
+
+<ul>
+ <li><a href="#v40_feat_dup">Automatic Duplicate Detection When Filing
+ [%+ terms.Bugs %]</a></li>
+ <li><a href="#v40_feat_search_ui">New Advanced Search UI</a></li>
+ <li><a href="#v40_feat_attach_ui">New Attachment Details UI</a></li>
+ <li><a href="#v40_feat_autocomplete">Autocomplete for Users and
+ Keywords</a></li>
+ <li><a href="#v40_feat_ui">General Usability Improvements</a></li>
+ <li><a href="#v40_feat_workflow">New Default Status Workflow</a></li>
+ <li><a href="#v40_feat_lists">"Last Search" Now Remembers Multiple
+ Searches</a></li>
+ <li><a href="#v40_feat_jsonp">Cross-Domain WebServices with JSONP</a></li>
+ <li><a href="#v40_feat_ws">Major WebService Enhancements</a></li>
+ <li><a href="#v40_feat_mandatory">Mandatory Custom Fields</a></li>
+ <li><a href="#v40_feat_vot_ext">Voting Is Now An Extension</a></li>
+ <li><a href="#v40_feat_js_css_update">Users Get New CSS and Javascript
+ Automatically</a></li>
+ <li><a href="#v40_feat_hooks">Many New Hooks</a></li>
+ <li><a href="#v40_feat_apache_config">New Apache Configuration</a></li>
+ <li><a href="#v40_feat_other">Other Enhancements and Changes</a></li>
+</ul>
+
+<h3 id="v40_feat_dup">Automatic Duplicate Detection When Filing
+ [%+ terms.Bugs %]</h3>
+
+<p>When filing [% terms.abug %], as soon as you start typing in the summary
+ field, [% terms.Bugzilla %] will suggest possible duplicates of the
+ [%+ terms.bug %] you are filing.</p>
+
+<p>In order for this feature to work, all pre-requisites for JSON-RPC
+ support must be installed on your [% terms.Bugzilla %]. It will be
+ much faster on installations that run under mod_perl than it will
+ be on other installations.</p>
+
+<p>This automatic duplicate detection does not currently work for installations
+ running on PostgreSQL.</p>
+
+<h3 id="v40_feat_search_ui">New Advanced Search UI</h3>
+
+<p>Thanks to the UI work of <a href="http://guy-pyrzak.blogspot.com/">Guy
+ Pyrzak</a>, the Advanced Search UI has been completely redesigned.
+ It is now much simpler, and far more approachable for new users, while
+ still retaining all of the features that power users are used to.</p>
+
+<h3 id="v40_feat_attach_ui">New Attachment Details UI</h3>
+
+<p>The UI used for editing attachment details has been completely
+ redesigned, allowing for a normally-size comment box to be used
+ when commenting on attachments, and allowing nearly the entire screen
+ width to be used when doing code reviews or editing an attachment as
+ a comment.</p>
+
+<p>Thanks to <a href="http://guy-pyrzak.blogspot.com/">Guy Pyrzak</a> for
+ his excellent work on this UI redesign.</p>
+
+<h3 id="v40_feat_autocomplete">Autocomplete for Users and Keywords</h3>
+
+<p>Once you type at least three characters in any field that can contain a user
+ (including the [% field_descs.cc FILTER html %],
+ [%+ field_descs.qa_contact FILTER html %], or
+ [%+ field_descs.assigned_to FILTER html %] fields), a list will appear
+ containing all of the users whose real names or usernames match what you are
+ typing. Your [% terms.Bugzilla %] must have all of the optional Perl
+ modules required for JSON-RPC support installed, though, in order for
+ this feature to work. Also, this feature will be <strong>much</strong>
+ faster on installations that run under mod_perl than it will be on
+ other installations.</p>
+
+<p>There is also a similar autocomplete for the Keywords field. The
+ Keywords autocomplete does not require JSON-RPC.</p>
+
+<h3 id="v40_feat_ui">General Usability Improvements</h3>
+
+<p>In addition to the enhancements listed above, there have been
+ <strong>many</strong> improvements made across the [% terms.Bugzilla %]
+ user interface. For a list of specific enhancements that were significant,
+ see the <a href="#v40_feat_other">Other Enhancements and Changes</a>
+ section.</p>
+
+<h3 id="v40_feat_workflow">New Default Status Workflow</h3>
+
+<p>For new installations of [% terms.Bugzilla %], the default set of
+ statuses will now be:</p>
+
+<ul>
+ <li>UNCONFIRMED</li>
+ <li>CONFIRMED</li>
+ <li>IN_PROGRESS</li>
+ <li>RESOLVED</li>
+ <li>VERIFIED</li>
+</ul>
+
+<p>And the UNCONFIRMED status will be enabled by default in all products.</p>
+
+<p>On upgrade, existing installations will not be affected--you will retain
+ your existing status workflow. However, we strongly recommend that you
+ update your existing workflow to the new one, using a special tool
+ we've included, <kbd>contrib/convert-workflow.pl</kbd>, which you
+ can run after you use <kbd>checksetup.pl</kbd> to upgrade. The
+ <kbd>whineatnews.pl</kbd> and <kbd>bugzilla-submit</kbd> scripts
+ will probably not work properly if you continue to use the old workflow
+ (though most other parts of [% terms.Bugzilla %] will still function
+ normally).</p>
+
+<p>For more information about the workflow and our rationale for changing
+ it, see the
+ <a href="http://bugzillaupdate.wordpress.com/2010/07/06/bugzilla-4-0-has-a-new-default-status-workflow/">blog
+ post about it</a> and the
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=486292">[% terms.bug %]
+ where the change was made</a>.</p>
+
+<h3 id="v40_feat_lists">"Last Search" Now Remembers Multiple Searches</h3>
+
+<p>At the top of every [% terms.bug %] in [% terms.Bugzilla %], there are
+ links that look like: "First", "Last", "Prev", "Next", and
+ "Show last search results". In earlier versions of [% terms.Bugzilla %],
+ if you did two separate searches in separate windows, these links would
+ only work for the <em>last</em> search you did. Now, [% terms.Bugzilla %]
+ will "remember" which search result you came from and give you the right
+ "last search results" or "next [% terms.bug %]" from <em>that</em> list,
+ instead of always using your most recent search.</p>
+
+<p>There are still some situations where [% terms.Bugzilla %] will have to
+ "guess" which search you are trying to navigate through, but it does its
+ best to get it right.</p>
+
+<h3 id="v40_feat_jsonp">Cross-Domain WebServices with JSONP</h3>
+
+<p>[% terms.Bugzilla %] now supports making WebService calls from
+ another domain, inside of a web browser, thanks to support for
+ <a href="http://bob.pythonmac.org/archives/2005/12/05/remote-json-jsonp/">JSONP</a>.
+ This will allow for web "mash-ups" to use [% terms.Bugzilla %] data.
+ When using JSONP, you may only call functions that <em>get</em> data,
+ you may not call functions that <em>change</em> data.</p>
+
+<p>For more details, see the
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/WebService/Server/JSONRPC.html#JSONP">JSONP
+ section</a> of the JSON-RPC WebService documentation.</p>
+
+<h3 id="v40_feat_ws">Major WebService Enhancements</h3>
+
+<p>The WebService has been expanded considerably. The WebService should now be
+ able to do everything with [% terms.bugs %] that you can do via the
+ web interface, including updating [% terms.bugs %], adding attachments,
+ and getting attachment data. For specifics, see the
+ <a href="#v40_feat_ws_changes">WebService Changes</a> section of these
+ release notes.</p>
+
+<h3 id="v40_feat_mandatory">Mandatory Custom Fields</h3>
+
+<p>You can now specify that certain custom fields are "mandatory",
+ meaning that they must have a value when [% terms.abug %] is filed,
+ and they can never be empty after that.</p>
+
+<h3 id="v40_feat_vot_ext">Voting Is Now An Extension</h3>
+
+<p>All of the code for voting in [% terms.Bugzilla %] has been moved
+ into an extension, called "Voting", in the <kbd>extensions/Voting/</kbd>
+ directory. To enable it, you must remove the <kbd>disabled</kbd> file
+ from that directory, and run <kbd>checksetup.pl</kbd>.</p>
+
+<p>In a future version of [% terms.Bugzilla %], the Voting extension will
+ be moved outside of the [% terms.Bugzilla %] core code, so we are looking
+ for somebody who has an interest in the Voting system and would like to
+ maintain it as a separate extension. There are many enhancement requests
+ that have been made against the Voting system, and the best way for those
+ to get addressed is for somebody to step up and offer to maintain the
+ system outside of [% terms.Bugzilla %]'s core code.</p>
+
+<h3 id="v40_feat_js_css_update">Users Get New CSS and Javascript
+ Automatically</h3>
+
+<p>In past versions of [% terms.Bugzilla %], if you changed
+ [%+ terms.Bugzilla %]'s CSS or Javascript files, then every user of
+ [%+ terms.Bugzilla %] would have to clear their cache in order to get
+ the updated files. Now, if you are using Apache as your webserver and
+ you have the <a href="#v40_req_apache">optional Apache modules</a>
+ installed and enabled, users will automatically get every new version of
+ [%+ terms.Bugzilla %]'s Javascript and CSS without having to clear
+ their caches.</p>
+
+<p>This feature also gives a slight performance speedup to
+ [%+ terms.Bugzilla %] in some cases, and so we recommend that all
+ administrators install and enable the optional Apache modules if possible.</p>
+
+<h3 id="v40_feat_hooks">Many New Hooks</h3>
+
+<p>Many new code hooks have been added for use by Extensions,
+ in [% terms.Bugzilla %] 4.0. Now Extensions can access and modify
+ nearly every part of [% terms.Bugzilla %].</p>
+
+<h3 id="v40_feat_apache_config">New Apache Configuration</h3>
+
+<p>If you run [% terms.Bugzilla %] under Apache (as most people do),
+ you most likely require a <strong>new Apache configuration</strong>
+ for this version of [% terms.Bugzilla %]. See the
+ <a href="#v40_upgrading">Notes On Upgrading From a Previous Version</a>
+ section for details.</p>
+
+<h3 id="v40_feat_other">Other Enhancements and Changes</h3>
+
+<h4>Enhancements for Users</h4>
+
+<ul>
+ <li>Now, everywhere in [% terms.Bugzilla %] where you can enter a date,
+ there is a Calendar widget where you can select the date on a
+ calendar.</li>
+ <li>The big icons on the front page have been replaced with much nicer
+ icons, thanks to Jon Pink of <a href="http://www.jpink.co.uk/">J.&nbsp;Pink&nbsp;Design</a>!</li>
+ <li><strong>[% terms.Bugs %]:</strong> When filing [% terms.bugs %],
+ you will now be warned if you forgot to fill in any mandatory fields,
+ <em>before</em> the page is submitted.</li>
+ <li><strong>[% terms.Bugs %]:</strong> When filing [% terms.abug %],
+ you can hover your mouse over any of the field labels on the page
+ to get a brief description of what that field is and what its purpose
+ is.</li>
+ <li><strong>[% terms.Bugs %]:</strong> When adding Hours Worked to [% terms.abug %],
+ you are no longer required to comment.</li>
+ <li><strong>[% terms.Bugs %]:</strong> There is now a user preference
+ for whether the comment box appears above or below the existing
+ comments.</li>
+ <li><strong>[% terms.Bugs %]:</strong> [% terms.Bugzilla %] will now
+ send an email for every comment that you mark or un-mark as being
+ private. (Previous versions of [% terms.Bugzilla %] did not send emails
+ to users about this change.) The state of comments being made private
+ is also now stored in [% terms.abug %]'s history.</li>
+ <li><strong>[% terms.Bugs %]:</strong> The box to "Add [% terms.Bug %] URLs"
+ in the See Also field is now hidden behind an "(add)" link that you
+ have to click to see the box.</li>
+
+ <li><strong>Searches:</strong> You can now properly search for field values
+ that have commas in their name, when using the Advanced Search form.</li>
+ <li><strong>Searches:</strong> The "URL" field can now be shown as a column
+ in search results.</li>
+ <li><strong>Searches:</strong> When viewing a search result, you can now
+ click on the Summary of the [% terms.bug %] in order to go to the
+ [%+ terms.bug %]-view page, in addition to being able to click on the
+ [%+ terms.bug %] ID.</li>
+ <li><strong>Searches:</strong> When doing a search using the "quicksearch"
+ box in the header or footer, the box will still contain what you searched
+ for when viewing the search results page.</li>
+ <li><strong>Searches:</strong> Multi-select custom fields can now be
+ shown as columns in the search results.</li>
+ <li><strong>Searches:</strong> When using the Boolean Charts (now called
+ "Custom Search"), if you specify both a criterion for an attachment
+ and a criteron for a flag, then only [% terms.bugs %] that have
+ attachments with that flag will be found.</li>
+ <li><strong>Searches:</strong> If you hover your mouse over the field labels
+ on the Advanced Search page, you will get a description of what that
+ field is.</li>
+ <li><strong>Searches:</strong> When searching via a saved search, if you
+ accidentally click on "Forget Search", there is a link to undo it.</li>
+ <li><strong>Searches:</strong> When using the Boolean Charts (now called
+ "Custom Search"), you can search for values "greater than or equal to"
+ or "less than or equal to" some value.</li>
+
+ <li><strong>Flags:</strong> If you hover your mouse over the name of
+ a flag setter when viewing [% terms.abug %], you can see that
+ flag setter's full name and complete username.</li>
+ <li><strong>Flags:</strong> When setting a flag on [% terms.abug %],
+ the box for entering a requestee does not appear until you set the flag
+ to "?", now.</li>
+ <li><strong>Flags:</strong> On the "My Requests" page, [% terms.bugs %]
+ that are restricted to certain groups now properly have the "padlock"
+ icon shown next to them to indicate that they may contain confidential
+ information.</li>
+
+ <li>When using the Reports interface, you can now choose many more fields
+ as the X, Y, or Z axis of a report, including custom fields.</li>
+ <li>[% terms.Bugzilla %] now prevents
+ Internet Explorer 8 and later from attempting to render
+ <kbd>text/plain</kbd> attachments as HTML.</li>
+ <li>If you receive a Whine mail that is empty, there will now be a brief
+ message explaining that your search found no results.</li>
+ <li>The <a href="page.cgi?id=fields.html">Field Help Page</a> now
+ contains a description of every single field that can be on
+ [%+ terms.abug %] in [% terms.Bugzilla %].</li>
+</ul>
+
+<h4>Enhancements for Administrators and Developers</h4>
+
+<ul>
+ <li>The system for moving [% terms.bugs %] between installations has been
+ moved into an extension called <kbd>OldBugMove</kbd>. This system was used
+ by very few [% terms.Bugzilla %] installations--if you aren't certain
+ whether or not you are using it, you're not using it. To enable the system,
+ you have to remove the file <kbd>extensions/OldBugMove/disabled</kbd>
+ and then run <kbd>checksetup.pl</kbd>. In a future version of [% terms.Bugzilla %],
+ this extension may be moved outside of the core [% terms.Bugzilla %] code,
+ so if you are interested in maintaining it, please let us know.</li>
+ <li><strong>Custom Fields: </strong> "[% terms.Bug %] ID" custom fields can
+ now represent relationships between [% terms.bugs %], similarly to how the
+ [%+ field_descs.blocked FILTER html %] and
+ [%+ field_descs.dependson FILTER html %] fields work now.</li>
+ <li><strong>Custom Fields:</strong> You can now restrict the visibility
+ of custom fields and their values to a specific Component or
+ Classification.</li>
+ <li>The "keyword cache" has been removed. When you edit keywords, you no
+ longer will have to "rebuild the keyword cache" after you are done.</li>
+ <li>Running <kbd>./collectstats.pl --regenerate</kbd> will now take
+ minutes or hours, instead of days.</li>
+ <li>When using <kbd>email_in.pl</kbd>, there are two new switches,
+ <kbd>--default</kbd> and <kbd>--override</kbd>, which allow you to
+ specify certain default values or override specified values for
+ <kbd>@field</kbd> values sent in emails. (This also allows you to specify
+ defaults for everything so that people do not have to specify any field
+ values when filing [% terms.abug %] via email.)</li>
+ <li><strong>Installation:</strong> If you are using a localized version of
+ [%+ terms.Bugzilla %] and your terminal does not understand Unicode,
+ <kbd>checksetup.pl</kbd> will now attempt to output its messages in your
+ terminal's character set.</li>
+ <li><strong>Installation:</strong> [% terms.Bugzilla %] no longer needs empty
+ "placeholder" CSS in the <kbd>skins/custom</kbd> directory and other
+ directories. When you update, <kbd>checksetup.pl</kbd> will remove these.
+ This also significantly reduces the number of HTTP requests required to
+ load a page for the first time in [% terms.Bugzilla %].</li>
+ <li><strong>Installation:</strong> For Windows users, [% terms.Bugzilla %]
+ now supports Strawberry Perl fully.</li>
+ <li><strong>Installation:</strong> Now, whenever <kbd>checksetup.pl</kbd>
+ throws an error, it will be printed in the color red, to make it
+ obvious that something is wrong.</li>
+ <li><strong>Installation:</strong> Some actions of <kbd>checksetup.pl</kbd> were
+ silent, in the past. Now, <kbd>checksetup.pl</kbd> will print a message for
+ almost anything it does.</li>
+ <li><strong>Installation:</strong> The process of adding foreign keys
+ to a table is now much faster. This will particularly improve the speed
+ of upgrading from [% terms.Bugzilla %] 3.4 or earlier.</li>
+ <li>If you are using <kbd>jobqueue.pl</kbd> and email gets heavily delayed
+ for some reason, those emails will now have a Date header reflecting the
+ time they were <em>supposed</em> to be sent, instead of when they actually
+ <em>were</em> sent.</li>
+ <li><kbd>./jobqueue.pl install</kbd> now works on SuSE Linux.</li>
+ <li>[% terms.Bugzilla %] now runs much better in Apache's suexec mode
+ than it used to. As part of this, <kbd>checksetup.pl</kbd> sets
+ much stricter permissions on all the files in [% terms.Bugzilla %]
+ than it used to. In particular, any files that [% terms.Bugzilla %]
+ does not know about will not be readable by the webserver.</li>
+ <li>The <kbd>sendmailnow</kbd> parameter has been removed, as it was
+ not necessary for any modern version of Sendmail or other Mail Transfer
+ Agent.</li>
+ <li>When editing a user via the Users administration panel, you can now
+ see if they are a Default CC on any component.</li>
+ <li>For new installations of [% terms.Bugzilla %], all users will be
+ able to see and use the Whining system by default.</li>
+ <li>When you are using SSL with [% terms.Bugzilla %], you can now
+ turn on the <kbd>strict_transport_security</kbd> parameter to
+ send the
+ <a href="https://developer.mozilla.org/en/Security/HTTP_Strict_Transport_Security">Strict-Transport-Security</a>
+ header with every HTTPS connection, for additional security.</li>
+ <li>New code hooks (see their documentation in
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/Hook.html">Bugzilla::Hook</a>):
+ bug_check_can_change_field, search_operator_field_override,
+ bugmail_relationships, object_columns, object_update_columns,
+ and object_validators. The colchange_columns hook has been removed,
+ as it is no longer necessary (buglist_columns will be used for data
+ about which columns can be on the [% terms.bug %] list).</li>
+ <li>When [% terms.Bugzilla %] throws certain types of errors, it will
+ now include a "traceback" of where exactly the error occurred in the
+ code, to help administrators and developers debug problems.</li>
+ <li>There is now a test, <kbd>xt/search.t</kbd>, that assures that all
+ of the functionality of <kbd>Bugzilla::Search</kbd> is working properly.
+ If you customize the search functionality of [% terms.Bugzilla %],
+ you may wish to run this test to assure that your changes are correct.
+ You can see more information about running this test by doing
+ <kbd>perldoc xt/search.t</kbd> at the command line.</li>
+ <li>[% terms.Bugzilla %] now sends the
+ <a href="https://developer.mozilla.org/en/the_x-frame-options_response_header"><code>X-Frame-Options: SAMEORIGIN</code></a> header
+ with every page request in order to prevent "clickjacking" attacks. Note
+ that this prevents other domains from displaying [% terms.Bugzilla %]
+ in an HTML frame.</li>
+</ul>
+
+<h4 id="v40_feat_ws_changes">WebService Changes</h4>
+
+<ul>
+ <li>You can now call some JSON-RPC methods using HTTP GET, in addition to
+ using HTTP POST. See the
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/WebService/Server/JSONRPC.html#Connecting_via_GET">JSON-RPC
+ documentation</a> for details.</li>
+ <li>You can now update existing [% terms.bugs %] using the
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/WebService/Bug.html#update">B[% %]ug.update</a>
+ function.</li>
+ <li>You can now add attachments to [% terms.bugs %] using the
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/WebService/Bug.html#add_attachment">B[% %]ug.add_attachment</a>
+ function.</li>
+ <li>The <kbd>B[% %]ug.get</kbd> function now returns all of [% terms.abug %]'s
+ information other than comments and attachments.</li>
+ <li><kbd>B[% %]ug.get</kbd> no longer returns the <kbd>internals</kbd> hash.</li>
+ <li>The <kbd>B[% %]ug.attachments</kbd> function now also returns attachment
+ data.</li>
+ <li>The following functions now support the <kbd>include_fields</kbd>
+ and <kbd>exclude_fields</kbd> arguments: <kbd>B[% %]ug.get</kbd>,
+ <kbd>B[% %]ug.search</kbd>, and <kbd>B[% %]ug.attachments</kbd>. Also,
+ server-side performance of the WebService is actually increased when
+ using these arguments, now, as [% terms.Bugzilla %] will no longer
+ get data from the database for fields you haven't asked for.</li>
+ <li>You can now mark the initial description of [% terms.abug %] as
+ private when filing [% terms.abug %] via the <kbd>B[% %]ug.create</kbd>
+ function.</li>
+ <li>You can now specify groups to put [% terms.abug %] in, in the
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/WebService/Bug.html#create">B[% %]ug.create</a>
+ function. (This also means that you can specify groups when filing
+ [%+ terms.abug %] via email_in.pl.)</li>
+ <li>The <kbd>User.get</kbd> function now accepts <kbd>groups</kbd>
+ and <kbd>group_ids</kbd> arguments, to limit the returned values to
+ only users in the specified groups.</li>
+ <li>There is a new, undocumented B[% %]ug.possible_duplicates
+ function that helps implement the automatic duplicate detection
+ system. Because this function is not documented, its API may change
+ between releases of [% terms.Bugzilla %].</li>
+ <li>You can no longer search using the <kbd>votes</kbd> argument in
+ <kbd>B[% %]ug.search</kbd>.</li>
+ <li><kbd>B[% %]ug.attachments</kbd> now returns the attachment's description
+ using the name "summary" instead of the name "description", to be
+ consistent with the fact that [% terms.bug %] summaries are called
+ "summary". The value is still <em>also</em> returned as "description",
+ for backwards compatibility, but this backwards compatibility will go
+ away in [% terms.Bugzilla %] 5.0.</li>
+ <li>In the return values of various <kbd>B[% %]ug</kbd> functions, the author
+ of comments, [% terms.bugs %], and attachments is now called "creator",
+ instead of sometimes being called "reporter", "author", or "attacher".
+ The old names are retained for backwards-compatibility, and will stay
+ around until [% terms.Bugzilla %] 5.0.</li>
+</ul>
+
+<h2 id="v40_issues">Outstanding Issues</h2>
+
+<ul>
+ <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=423439">
+ [%- terms.Bug %] 423439</a>: Tabs in comments will be converted
+ to four spaces, due to a b<!-- -->ug in Perl as of Perl 5.8.8.</li>
+ <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=89822">
+ [%- terms.Bug %] 89822</a>: When changing multiple [% terms.bugs %] at
+ the same time, there is no "mid-air collision" protection.</li>
+ <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=276230">
+ [%- terms.Bug %] 276230</a>: The support for restricting access to
+ particular Categories of New Charts is not complete. You should treat
+ the 'chartgroup' Param as the only access mechanism available.<br>
+ However, charts migrated from Old Charts will be restricted to
+ the groups that are marked MANDATORY for the corresponding Product.
+ There is currently no way to change this restriction, and the
+ groupings will not be updated if the group configuration
+ for the Product changes.</li>
+</ul>
+
+<h2 id="v40_upgrading">Notes On Upgrading From a Previous Version</h2>
+
+<h3>IMPORTANT: Apache Configuration Change</h3>
+
+<h4>mod_cgi</h4>
+
+<p>If you run [% terms.Bugzilla %] under mod_cgi (this is the most common
+ configuration, involving a &lt;Directory&gt; block in your Apache config
+ file), you will need to update the configuration of Apache for
+ [%+ terms.Bugzilla %]. In particular, this line in the [% terms.Bugzilla %]
+ <kbd>&lt;Directory&gt;</kbd> block:</p>
+
+<blockquote><code>AllowOverride Limit</code></blockquote>
+
+<p>needs to become:</p>
+
+<blockquote><code>AllowOverride Limit FileInfo Indexes</code></blockquote>
+
+<p>For full details on how to configure Apache for [% terms.Bugzilla %],
+ see the
+ <a href="[% docs_urlbase FILTER html %]configuration.html#http-apache">Configuration</a>
+ section of the [% terms.Bugzilla %] Guide.</p>
+
+<h4>mod_perl</h4>
+
+<p>If your [% terms.Bugzilla %] runs under mod_perl, the required Apache
+ configuration is now simpler. The line that used to look like:</p>
+
+<blockquote><code>PerlSwitches -w -T -I/var/www/html/bugzilla
+ -I/var/www/html/bugzilla/lib</code></blockquote>
+
+<p>Now should be only:</p>
+
+<blockquote><code>PerlSwitches -w -T</code></blockquote>
+
+<p>The <code>PerlConfigRequire</code> line should stay the same, however.</p>
+
+<h3>New .htaccess file</h3>
+
+<p>In previous versions of [% terms.Bugzilla %], there was a file
+ in [% terms.Bugzilla %]'s root directory called ".htaccess" that was
+ generated by <kbd>checksetup.pl</kbd>. This file is now shipped with
+ [%+ terms.Bugzilla %] instead of being generated during installation.</p>
+
+<p>If you update via CVS or bzr, you will get a message that your existing
+ .htaccess file conflicts with the new one. You must
+ <strong>remove your existing .htaccess file</strong> and use the new one
+ instead. Continuing to use your old .htaccess file will cause certain new
+ features of [% terms.Bugzilla %] to not work properly, and may also lead
+ to security issues for your system in the future.</p>
+
+<h2 id="v40_code_changes">Code Changes Which May Affect Customizations and
+ Extensions</h2>
+
+<ul>
+ <li>In Extensions, if you want to serve files to the user via the web,
+ they must now be in a <kbd>web/</kbd> subdirectory of your Extension.
+ (For example, <kbd>extensions/Foo/web/</kbd>). <kbd>checksetup.pl</kbd>
+ sets permissions on extensions much more strictly now, and files in
+ other locations (such as your base <kbd>extensions/Foo/</kbd> directory)
+ will no longer be available to [% terms.Bugzilla %] users via the web
+ under certain configurations.</li>
+ <li>Previous versions of [% terms.Bugzilla %] used to allow putting a
+ single file into the "skins" directory and having that be an entire
+ skin. That is no longer allowed, and on upgrade, <kbd>checksetup.pl</kbd>
+ will convert any such skins into a directory with a single
+ <kbd>global.css</kbd> file in them.</li>
+ <li>When updating [% terms.bugs %], you should now use
+ <code>$bug-&gt;set_all</code> instead of using the individual
+ <kbd>set_</kbd> methods. In particular, <kbd>set_all</kbd> is now the
+ <em>only</em> way to set the product of [% terms.abug %]. See
+ <kbd>process_bug.cgi</kbd> for an example of how <kbd>set_all</kbd>
+ should be used.</li>
+ <li>You should not insert &lt;script&gt; tags and &lt;link&gt; CSS tags
+ into HTML anymore, in Extensions or in your customizations. Instead,
+ you should push new values into the <kbd>style_urls</kbd> or
+ <kbd>javascript_urls</kbd> parameters. If you have to insert manual
+ tags for some reason, be sure to call "FILTER mtime" on the URL. (Search
+ for other uses of "FILTER mtime" in the templates to see how it is
+ used.)</li>
+ <li>When calling <kbd>Bugzilla::BugMail::Send</kbd>, the "changer"
+ argument must now be a <kbd>Bugzilla::User</kbd> object, not just
+ a login name. The "owner" and "qacontact" arguments are still
+ just login names.</li>
+ <li>When creating a new subclass of Bugzilla::Object, you should no
+ longer use <kbd>UPDATE_VALIDATORS</kbd>. Also, in most cases you will
+ no longer need to override <kbd>run_create_validators</kbd>. Instead,
+ there is a new constant called
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/Object.html#VALIDATOR_DEPENDENCIES">VALIDATOR_DEPENDENCIES</a>,
+ that specifies that certain fields have to be validated before other fields.
+ Then, all validators receive each already-validated value in a hash
+ as their fourth argument, so each validator can know the other values
+ that were passed in, while an object is being created. For an example of
+ how to use <kbd>VALIDATOR_DEPENDENCIES</kbd>, see
+ <kbd>Bugzilla/Field.pm</kbd>.</li>
+ <li>In previous versions of [% terms.Bugzilla %], you had to call
+ <code>Bugzilla-&gt;template_inner("")</code> after any time
+ that you called <kbd>template_inner</kbd> for a specific language.
+ It is no longer necessary to do this second <kbd>template_inner</kbd>
+ call.</li>
+ <li><kbd>post_bug.cgi</kbd> and <kbd>Bugzilla::Bug-&gt;create</kbd> now take
+ the <em>names</em> of groups instead of group ids.</li>
+ <li>Bugzilla::Bugmail now uses Bugzilla::Bug objects internally instead of
+ a lot of direct SQL.</li>
+ <li>For sending changes about [% terms.bugs %], there is now a method
+ called <kbd>send_changes</kbd> that you can call on Bugzilla::Bug
+ objects. For an example of its use, see <kbd>process_bug.cgi</kbd>.</li>
+ <li>The <kbd>Bugzilla::Search</kbd> class has been refactored, and should
+ now be easier to customize.</li>
+ <li>The <kbd>Bugzilla::Util::lsearch</kbd> function is gone. Use
+ <kbd>firstidx</kbd> from <kbd>List::MoreUtils</kbd>, instead.</li>
+ <li>[% terms.Bugzilla %] now includes YUI 2.8.2.</li>
+ <li><kbd>long_list.cgi</kbd>, <kbd>showattachment.cgi</kbd> and
+ <kbd>xml.cgi</kbd> are deprecated scripts which are no longer actively
+ used since [% terms.Bugzilla %] 2.19. These scripts will be removed in
+ [%+ terms.Bugzilla %] 4.2.</li>
+</ul>
+
+
+<h1 id="v40_previous">[% terms.Bugzilla %] 3.6 Release Notes</h1>
+
+<ul class="bz_toc">
+ <li><a href="#v36_introduction">Introduction</a></li>
+ <li><a href="#v36_point">Updates in this 3.6.x Release</a></li>
+ <li><a href="#v36_req">Minimum Requirements</a></li>
+ <li><a href="#v36_feat">New Features and Improvements</a></li>
+ <li><a href="#v36_issues">Outstanding Issues</a></li>
+ <li><a href="#v36_upgrading">Notes On Upgrading From a Previous Version</a></li>
+ <li><a href="#v36_code_changes">Code Changes Which May Affect
+ Customizations</a></li>
+ <li><a href="#v36_previous">Release Notes for Previous Versions</a></li>
+</ul>
+
+<h2 id="v36_introduction">Introduction</h2>
+
+<p>Welcome to [% terms.Bugzilla %] 3.6! The focus of the 3.6 release is
+ on improving usability and "polishing up" all our features (by adding
+ some pieces that were "missing" or always wanted), although we
+ also have a few great new features for you, as well!</p>
+
+<p>If you're upgrading, make sure to read <a href="#v36_upgrading">Notes
+ On Upgrading From a Previous Version</a>. If you are upgrading from a release
+ before 3.4, make sure to read the release notes for all the
+ <a href="#v36_previous">previous versions</a> in between your version
+ and this one, <strong>particularly the Upgrading section of each
+ version's release notes</strong>.</p>
+
+<p>We would like to thank <a href="http://www.canonical.com/">Canonical
+ Ltd.</a>, <a href="http://www.itasoftware.com/">ITA Software</a>,
+ the <a href="http://www.ibm.com/linux/ltc/">IBM Linux Technology Center</a>,
+ <a href="http://www.redhat.com/">Red Hat</a>, and
+ <a href="http://www.novell.com/">Novell</a> for funding the development
+ of various features and improvements in this release of
+ [%+ terms.Bugzilla %].</p>
+
+<h2 id="v36_point">Updates in this 3.6.x Release</h2>
+
+<h3>3.6.2</h3>
+
+<p>This release fixes various security issues. See the
+ <a href="http://www.bugzilla.org/security/3.2.7/">Security Advisory</a>
+ for details.</p>
+
+<p>In addition, the following important fixes/changes have been made in
+ this release:</p>
+
+<ul>
+ <li>[% terms.Bugzilla %] installations running on older versions of IIS
+ will no longer experience the "Undef to trick_taint" errors that would
+ sometimes occur.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=521416">[% terms.Bug %] 521416</a>)
+ </li>
+ <li>Email notifications were missing the dates that comments were made.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=578003">[% terms.Bug %] 578003</a>)
+ </li>
+ <li>Putting a phrase in quotes in the Quicksearch box now works properly,
+ again.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=578494">[% terms.Bug %] 578494</a>
+ and <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=553884">[% terms.Bug %] 553884</a>)
+ </li>
+ <li>Quicksearch was usually (incorrectly) being limited to 200 results.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=581622">[% terms.Bug %] 581622</a>)
+ </li>
+ <li>On Windows, <kbd>install-module.pl</kbd> can now properly install
+ DateTime and certain other Perl modules that didn't install properly
+ before.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=576105">[% terms.Bug %] 576105</a>)
+ </li>
+ <li>Searching "keywords" for "contains none of the words" or "does not
+ match regular expression" now works properly.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=562014">[% terms.Bug %] 562014</a>)
+ </li>
+ <li>Doing <kbd>collectstats.pl --regenerate</kbd> now works on installations
+ using PostgreSQL.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=577058">[% terms.Bug %] 577058</a>)
+ </li>
+ <li>The "Field Values" administrative control panel was sometimes denying
+ admins the ability to delete field values when there was no reason
+ to deny the deletion.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=577054">[% terms.Bug %] 577054</a>)
+ </li>
+ <li>Eliminate the "uninitialized value" warnings that would happen when
+ editing a product's components.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=576911">[% terms.Bug %] 576911</a>)
+ </li>
+ <li>The updating of bugs_fulltext that happens during
+ <kbd>checksetup.pl</kbd> for upgrades to 3.6 should now be MUCH faster.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=577754">[% terms.Bug %] 577754</a>)
+ </li>
+ <li><kbd>email_in.pl</kbd> was not allowing the setting of time-tracking
+ fields via inbound emails.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=583622">[% terms.Bug %] 583622</a>)
+ </li>
+</ul>
+
+<h3>3.6.1</h3>
+
+<p>This release fixes two security issues. See the
+ <a href="http://www.bugzilla.org/security/3.2.6/">Security Advisory</a>
+ for details.</p>
+
+<p>In addition, the following important fixes/changes have been made in
+ this release:</p>
+
+<ul>
+ <li>Using the "Change Columns" page would sometimes result in a
+ plain-text page instead of HTML.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=376044">[% terms.Bug %] 376044</a>)
+ </li>
+ <li>Extensions that have only templates and no code are now working.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=562551">[% terms.Bug %] 562551</a>)
+ </li>
+ <li><kbd>install-module.pl</kbd> has been fixed so that it installs
+ modules properly on both new and old versions of Perl.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=560318">[% terms.Bug %] 560318</a>
+ and <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=560330">[% terms.Bug %] 560330</a>)
+ </li>
+ <li>It is now possible to upgrade from 3.4 to 3.6 when using Oracle.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=561379">[% terms.Bug %] 561379</a>)
+ </li>
+ <li>Editing a field value's name (using the Field Values admin control
+ panel) wasn't working if the value was set as the default for that
+ field.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=561296">[% terms.Bug %] 561296</a>)
+ </li>
+ <li>If you had the <kbd>noresolveonopenblockers</kbd> parameter set,
+ [%+ terms.bugs %] couldn't be edited at all if they were marked FIXED
+ and had any open blockers. (The parameter is only supposed to prevent
+ <em>changing</em> [% terms.bugs %] to FIXED, not modifying already-FIXED
+ [%+ terms.bugs %].)
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=565314">[% terms.Bug %] 565314</a>)
+ </li>
+ <li>Some minor issues with Perl 5.12 were fixed (mostly warnings that Perl
+ 5.12 was throwing). [% terms.Bugzilla %] now supports Perl 5.12.</li>
+</ul>
+
+<h2 id="v36_req">Minimum Requirements</h2>
+
+<p>Any requirements that are new since 3.4.5 will look like
+ <span class="req_new">this</span>.</p>
+
+<ul>
+ <li><a href="#v36_req_perl">Perl</a></li>
+ <li><a href="#v36_req_mysql">For MySQL Users</a></li>
+ <li><a href="#v36_req_pg">For PostgreSQL Users</a></li>
+ <li><a href="#v36_req_oracle">For Oracle Users</a></li>
+ <li><a href="#v36_req_modules">Required Perl Modules</a></li>
+ <li><a href="#v36_req_optional_mod">Optional Perl Modules</a></li>
+</ul>
+
+<h3 id="v36_req_perl">Perl</h3>
+
+<p>Perl v5.8.1</p>
+
+<h3 id="v36_req_mysql">For MySQL Users</h3>
+
+ <ul>
+ <li>MySQL
+ v4.1.2
+ </li>
+ <li><strong>perl module:</strong>
+ DBD::mysql v4.00</li>
+ </ul>
+
+<h3 id="v36_req_pg">For PostgreSQL Users</h3>
+
+ <ul>
+ <li>PostgreSQL
+ v8.00.0000
+ </li>
+ <li><strong>perl module:</strong>
+ DBD::Pg v1.45</li>
+ </ul>
+<h3 id="v36_req_oracle">For Oracle Users</h3>
+
+ <ul>
+ <li>Oracle
+ v10.02.0
+ </li>
+ <li><strong>perl module:</strong>
+ DBD::Oracle v1.19</li>
+ </ul>
+
+<h3 id="v36_req_modules">Required Perl Modules</h3>
+
+<table class="req_table" border="0" cellspacing="0" cellpadding="0">
+ <tr>
+ <th>Module</th> <th>Version</th>
+ </tr>
+ <tr>
+ <td >CGI</td>
+ <td >3.21
+ </td>
+ </tr>
+ <tr>
+ <td >Digest::SHA</td>
+ <td >
+ (Any)
+ </td>
+ </tr>
+ <tr>
+ <td >Date::Format</td>
+ <td >2.21
+ </td>
+ </tr>
+ <tr>
+ <td >DateTime</td>
+ <td >0.28
+ </td>
+ </tr>
+ <tr>
+ <td >DateTime::TimeZone</td>
+ <td >0.71
+ </td>
+ </tr>
+ <tr>
+ <td >DBI</td>
+ <td >1.41
+ </td>
+ </tr>
+ <tr>
+ <td >Template</td>
+ <td >2.22
+ </td>
+ </tr>
+ <tr>
+ <td >Email::Send</td>
+ <td >2.00
+ </td>
+ </tr>
+ <tr>
+ <td >Email::MIME</td>
+ <td >1.861
+ </td>
+ </tr>
+ <tr>
+ <td >Email::MIME::Encodings</td>
+ <td >1.313
+ </td>
+ </tr>
+ <tr>
+ <td >Email::MIME::Modifier</td>
+ <td >1.442
+ </td>
+ </tr>
+ <tr>
+ <td >URI</td>
+ <td >
+ (Any)
+ </td>
+ </tr>
+</table>
+
+<h3 id="v36_req_optional_mod">Optional Perl Modules</h3>
+
+<p>The following perl modules, if installed, enable various
+ features of [% terms.Bugzilla %]:</p>
+
+<table class="req_table" border="0" cellspacing="0" cellpadding="0">
+ <tr>
+ <th>Module</th> <th>Version</th>
+ <th>Enables Feature</th>
+ </tr>
+ <tr>
+ <td >GD</td>
+ <td >1.20
+ </td>
+ <td>Graphical Reports, New Charts, Old Charts</td>
+ </tr>
+ <tr>
+ <td >Chart::Lines</td>
+ <td class="req_new">2.1
+ </td>
+ <td>New Charts, Old Charts</td>
+ </tr>
+ <tr>
+ <td >Template::Plugin::GD::Image</td>
+ <td >
+ (Any)
+ </td>
+ <td>Graphical Reports</td>
+ </tr>
+ <tr>
+ <td >GD::Text</td>
+ <td >
+ (Any)
+ </td>
+ <td>Graphical Reports</td>
+ </tr>
+ <tr>
+ <td >GD::Graph</td>
+ <td >
+ (Any)
+ </td>
+ <td>Graphical Reports</td>
+ </tr>
+ <tr>
+ <td >XML::Twig</td>
+ <td >
+ (Any)
+ </td>
+ <td>Move [% terms.Bugs %] Between Installations,
+ Automatic Update Notifications</td>
+ </tr>
+ <tr>
+ <td >MIME::Parser</td>
+ <td >5.406
+ </td>
+ <td>Move [% terms.Bugs %] Between Installations</td>
+ </tr>
+ <tr>
+ <td >LWP::UserAgent</td>
+ <td >
+ (Any)
+ </td>
+ <td>Automatic Update Notifications</td>
+ </tr>
+ <tr>
+ <td >PatchReader</td>
+ <td >0.9.4
+ </td>
+ <td>Patch Viewer</td>
+ </tr>
+ <tr>
+ <td >Net::LDAP</td>
+ <td >
+ (Any)
+ </td>
+ <td>LDAP Authentication</td>
+ </tr>
+ <tr>
+ <td >Authen::SASL</td>
+ <td >
+ (Any)
+ </td>
+ <td>SMTP Authentication</td>
+ </tr>
+ <tr>
+ <td >Authen::Radius</td>
+ <td >
+ (Any)
+ </td>
+ <td>RADIUS Authentication</td>
+ </tr>
+ <tr>
+ <td >SOAP::Lite</td>
+ <td >0.710.06
+ </td>
+ <td>XML-RPC Interface</td>
+ </tr>
+ <tr>
+ <td class="req_new">JSON::RPC</td>
+ <td class="req_new">
+ (Any)
+ </td>
+ <td>JSON-RPC Interface</td>
+ </tr>
+ <tr>
+ <td class="req_new">Test::Taint</td>
+ <td class="req_new">
+ (Any)
+ </td>
+ <td>JSON-RPC Interface, XML-RPC Interface</td>
+ </tr>
+ <tr>
+ <td >HTML::Parser</td>
+ <td >3.40
+ </td>
+ <td>More HTML in Product/Group Descriptions</td>
+ </tr>
+ <tr>
+ <td >HTML::Scrubber</td>
+ <td >
+ (Any)
+ </td>
+ <td>More HTML in Product/Group Descriptions</td>
+ </tr>
+ <tr>
+ <td >Email::MIME::Attachment::Stripper</td>
+ <td >
+ (Any)
+ </td>
+ <td>Inbound Email</td>
+ </tr>
+ <tr>
+ <td >Email::Reply</td>
+ <td >
+ (Any)
+ </td>
+ <td>Inbound Email</td>
+ </tr>
+ <tr>
+ <td >TheSchwartz</td>
+ <td >
+ (Any)
+ </td>
+ <td>Mail Queueing</td>
+ </tr>
+ <tr>
+ <td >Daemon::Generic</td>
+ <td >
+ (Any)
+ </td>
+ <td>Mail Queueing</td>
+ </tr>
+ <tr>
+ <td >mod_perl2</td>
+ <td >1.999022
+ </td>
+ <td>mod_perl</td>
+ </tr>
+</table>
+
+<h2 id="v36_feat">New Features and Improvements</h2>
+
+<ul>
+ <li><a href="#v36_feat_usability">General Usability Improvements</a></li>
+ <li><a href="#v36_feat_extensions">New Extensions System</a></li>
+ <li><a href="#v36_feat_qs">Improved Quicksearch</a></li>
+ <li><a href="#v36_feat_browse">Simple "Browse" Interface</a></li>
+ <li><a href="#v36_feat_suexec">SUExec Support</a></li>
+ <li><a href="#v36_feat_mpwindows">Experimental mod_perl Support on Windows</a></li>
+ <li><a href="#v36_email_attachments">Send Attachments by Email</a></li>
+ <li><a href="#v36_feat_jsonrpc">JSON-RPC Interface</a></li>
+ <li><a href="#v36_feat_migrate">Migration From Other [% terms.Bug %]-Trackers</a></li>
+ <li><a href="#v36_feat_other">Other Enhancements and Changes</a></li>
+</ul>
+
+<h3 id="v36_feat_usability">General Usability Improvements</h3>
+
+<p>A <a href="https://wiki.mozilla.org/Bugzilla:CMU_HCI_Research_2008">scientific
+ usability study</a> was done on [% terms.Bugzilla %] by researchers
+ from Carnegie-Mellon University. As a result of this study,
+ <a href="https://bugzilla.mozilla.org/showdependencytree.cgi?id=490786&amp;hide_resolved=0">several
+ usability issues</a> were prioritized to be fixed, based on specific data
+ from the study.</p>
+
+<p>As a result, you will see many small improvements in [% terms.Bugzilla %]'s
+ usability, such as using Javascript to validate certain forms before
+ they are submitted, standardizing the words that we use in the user interface,
+ being clearer about what [% terms.Bugzilla %] needs from the user,
+ and other changes, all of which are also listed individually in this New
+ Features section.</p>
+
+<p>Work continues on improving usability for the next release of
+ [%+ terms.Bugzilla %], but the results of the research have already
+ had an impact on this 3.6 release.</p>
+
+<h3 id="v36_feat_extensions">New Extensions System</h3>
+
+<p>[% terms.Bugzilla %] has a brand-new Extensions system. The system is
+ consistent, fast, and
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/Extension.html">fully
+ documented</a>. It makes it possible to easily extend [% terms.Bugzilla %]'s
+ code and user interface to add new features or change existing features.
+ There's even
+ <a href="[% docs_urlbase FILTER html %]api/extensions/create.html">a
+ script</a> that will create the basic layout of an extension for you, to
+ help you get started. For more information about the new system, see the
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/Extension.html">Extensions
+ documentation</a>.</p>
+
+<p>If you had written any extensions using [% terms.Bugzilla %]'s previous
+ extensions system, there is
+ <a href="[% docs_urlbase FILTER html %]api/contrib/extension-convert.html">a
+ script to help convert old extensions into the new format</a>.</p>
+
+<h3 id="v36_feat_qs">Improved Quicksearch</h3>
+
+<p>The "quicksearch" box that appears on the front page of
+ [%+ terms.Bugzilla %] and in the header/footer of every page
+ is now simplified and made more powerful. There is a
+ <kbd>[?]</kbd> link next to the box that will take you to
+ the simplified <a href="page.cgi?id=quicksearch.html">Quicksearch Help</a>,
+ which describes every single feature of the system in a simple layout,
+ including new features such as the ability to use partial field names
+ when searching.</p>
+
+<p>Quicksearch should also be much faster than it was before, particularly
+ on large installations.</p>
+
+<p>Note that in order to implement the new quicksearch, certain old
+ and rarely-used features had to be removed:
+
+<ul>
+ <li><b>+</b> as a prefix to mean "search additional resolutions", and
+ <b>+</b> as a prefix to mean "search just the summary". You can
+ instead use <kbd>summary:</kbd> to explicitly search summaries.</li>
+ <li>Searching the Severity field if you type something that matches
+ the first few characters of a severity. You can explicitly search
+ the Severity field if you want to find [% terms.bugs %] by severity.</li>
+ <li>Searching the Priority field if you typed something that exactly
+ matched the name of a priority. You can explicitly search the
+ Priority field if you want to find [% terms.bugs %] by priority.</li>
+ <li>Searching the Platform and OS fields if you typed in one of a
+ certain hard-coded list of strings (like "pc", "windows", etc.).
+ You can explicitly search these fields, instead, if you want to
+ find [% terms.bugs %] with a specific Platform or OS set.</li>
+</ul>
+
+<h3 id="v36_feat_browse">Simple "Browse" Interface</h3>
+
+<p>There is now a "Browse" link in the header of each [% terms.Bugzilla %]
+ page that presents a very basic interface that allows users to simply
+ browse through all open [% terms.bugs %] in particular components.</p>
+
+<h3 id="v36_feat_suexec">SUExec Support</h3>
+
+<p>[% terms.Bugzilla %] can now be run in Apache's "SUExec" mode,
+ which is what control panel software like cPanel and Plesk use
+ (so [% terms.Bugzilla %] should now be much easier to install
+ on shared hosting). SUExec support shows up as an option
+ in the <kbd>localconfig</kbd> file during installation.</p>
+
+<h3 id="v36_feat_mpwindows">Experimental mod_perl Support on Windows</h3>
+
+<p>There is now experimental support for running [% terms.Bugzilla %]
+ under mod_perl on Windows, for a significant performance enhancement
+ (in exchange for using more memory).</p>
+
+<h3 id="v36_email_attachments">Send Attachments by Email</h3>
+
+<p>The <a href="[% docs_urlbase FILTER html %]api/email_in.html">email_in</a>
+ script now supports attaching multiple attachments to [% terms.abug %]
+ by email, both when filing and when updating [% terms.abug %].</p>
+
+<h3 id="v36_feat_jsonrpc">JSON-RPC Interface</h3>
+
+<p>[% terms.Bugzilla %] now has support for the
+ <a href="http://json-rpc.org/">JSON-RPC</a> WebServices protocol via
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/WebService/Server/JSONRPC.html">jsonrpc.cgi</a>.
+ The JSON-RPC interface is experimental in this release--if you want any
+ fundamental changes in how it works,
+ <a href="http://www.bugzilla.org/developers/reporting_bugs.html">let us
+ know</a>, for the next release of [% terms.Bugzilla %].</p>
+
+<h3 id="v36_feat_migrate">Migration From Other [% terms.Bug %]-Trackers</h3>
+
+<p>[% terms.Bugzilla %] 3.6 comes with a new script,
+ <a href="[% docs_urlbase FILTER html %]api/migrate.html">migrate.pl</a>,
+ which allows migration from other [% terms.bug %]-tracking systems.
+ Among the various features of the migration system are:</p>
+
+<ul>
+ <li>It is non-destructive--you can migrate into an existing
+ [%+ terms.Bugzilla %] installation without destroying any data
+ in the installation.</li>
+ <li>It has a "dry-run" mode so you can test your migration
+ before actually running it.</li>
+ <li>It is relatively easy to write new migrators for new systems,
+ if you know Perl. The basic migration framework does most of the work
+ for you, you just have to provide it with the data from your
+ [%+ terms.bug %]-tracker. See the
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/Migrate.html">Bugzilla::Migrate</a>
+ documentation and see our current migrator,
+ <kbd>Bugzilla/Migrate/GNATS.pm</kbd> for information on how to make your
+ own migrator.</li>
+</ul>
+
+<p>The first migrator that has been implemented is for the GNATS
+ [%+ terms.bug %]-tracking system. We'd love to see migrators for
+ other systems! If you want to contribute a new migrator, see our
+ <a href="http://wiki.mozilla.org/Bugzilla:Developers">development
+ process</a> for details on how to get code into [% terms.Bugzilla %].</p>
+
+<p>Thanks to <a href="http://lambdares.com/">Lambda Research</a> for
+ funding the initial development of this feature.</p>
+
+<h3 id="v36_feat_other">Other Enhancements and Changes</h3>
+
+<h4>Enhancements for Users</h4>
+
+<ul>
+ <li><b>[% terms.Bug %] Filing:</b> When filing [% terms.abug %],
+ [%+ terms.Bugzilla %] now visually indicates which fields are
+ mandatory.</li>
+ <li><b>[% terms.Bug %] Filing:</b> "Bookmarkable templates" now
+ support the "alias" and "estimated hours" fields.</li>
+
+ <li><b>[% terms.Bug %] Editing:</b> In previous versions of
+ [%+ terms.Bugzilla %], if you added a private comment to [% terms.abug %],
+ then <em>none</em> of the changes that you made at that time were
+ sent to users who couldn't see the private comment. Now, for users
+ who can't see private comments, public changes are sent, but the private
+ comment is excluded from their email notification.</li>
+ <li><b>[% terms.Bug %] Editing:</b> The controls for groups now
+ appear to the right of the attachment and time-tracking tables,
+ when editing [% terms.abug %].</li>
+ <li><b>[% terms.Bug %] Editing:</b> The "Collapse All Comments"
+ and "Expand All Comments" links now appear to the right of the
+ comment list instead of above it.</li>
+ <li><b>[% terms.Bug %] Editing:</b> The See Also field now supports
+ URLs for Google Code Issues and the Debian B[% %]ug-Tracking System.</li>
+ <li><b>[% terms.Bug %] Editing:</b> There have been significant performance
+ improvements in <kbd>show_bug.cgi</kbd> (the script that displays the
+ [% terms.bug %]-editing form), particularly for [% terms.bugs %] that
+ have lots of comments or attachments.</li>
+
+ <li><b>Attachments:</b> The "Details" page of an attachment
+ now displays itself as uneditable if you can't edit the fields
+ there.</li>
+ <li><b>Attachments:</b> We now make sure that there is
+ a Description specified for an attachment, using JavaScript, before
+ the form is submitted.</li>
+ <li><b>Attachments:</b> There is now a link back to the [% terms.bug %]
+ at the bottom of the "Details" page for an attachment.</li>
+ <li><b>Attachments:</b> When you click on an "attachment 12345" link
+ in a comment, if the attachment is a patch, you will now see the
+ formatted "Diff" view instead of the raw patch.</li>
+ <li><b>Attachments</b>: For text attachments, we now let the browser
+ auto-detect the character encoding, instead of forcing the browser to
+ always assume the attachment is in UTF-8.</li>
+
+ <li><b>Search:</b> You can now display [% terms.bug %] flags as a column
+ in search results.</li>
+ <li><b>Search:</b> When viewing search results, you can see which columns are
+ being sorted on, and which direction the sort is on, as indicated
+ by arrows next to the column headers.</li>
+ <li><b>Search:</b> You can now search the Deadline field using relative
+ dates (like "1d", "2w", etc.).</li>
+ <li><b>Search:</b> The iCalendar format of search results now includes
+ a PRIORITY field.</li>
+ <li><b>Search:</b> It is no longer an error to enter an invalid search
+ order in a search URL--[% terms.Bugzilla %] will simply warn you that
+ some of your order options are invalid.</li>
+ <li><b>Search:</b> When there are no search results, some helpful
+ links are displayed, offering actions you might want to take.</li>
+ <li><b>Search:</b> For those who like to make their own
+ <kbd>buglist.cgi</kbd> URLs (and for people working on customizations),
+ <kbd>buglist.cgi</kbd> now accepts nearly every valid field in
+ [%+ terms.Bugzilla %] as a direct URL parameter, like
+ <kbd>&amp;field=value</kbd>.</li>
+
+ <li><b>Requests:</b> When viewing the "My Requests" page, you can now
+ see the lists as a normal search result by clicking a link at the
+ bottom of each table.</li>
+ <li><b>Requests:</b> When viewing the "My Requests" page, if you are
+ using Classifications, the Product drop-down will be grouped by
+ Classification.</li>
+
+ <li><b>Inbound Email:</b> When filing [% terms.abug %] by email, if the
+ product that you are filing the [% terms.bug %] into has some groups
+ set as Default for you, the [% terms.bug %] will now be placed into those
+ groups automatically.</li>
+ <li><b>Inbound Email:</b> The field names that can be used when creating
+ [%+ terms.bugs %] by email now exactly matches the set of valid parameters
+ to the
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/WebService/Bug.html#create">B[% %]ug.create
+ WebService function</a>. You can still use most of the old field names
+ that 3.4 and earlier used for inbound emails, though, for
+ backwards-compatibility.</li>
+
+ <li>If there are multiple languages available for your
+ [%+ terms.Bugzilla %], you can now select what language you want
+ [%+ terms.Bugzilla %] displayed in using links at the top of every
+ page.</li>
+ <li>When creating a new account, you will be automatically logged in
+ after setting your password.</li>
+ <li>There is no longer a maximum password length for accounts.</li>
+ <li>In the Dusk skin, it's now easier to see links.</li>
+ <li>In the Whining system, you can now choose to receive emails even
+ if there are no [% terms.bugs %] that match your searches.</li>
+ <li>The arrows in dependency graphs now point the other way, so that
+ [%+ terms.bugs %] point at their dependencies.</li>
+
+ <li><b>New Charts:</b> You can now convert an existing Saved Search
+ into a data series for New Charts.</li>
+ <li><b>New Charts:</b> There is now an interface that allows you to
+ delete data series.</li>
+ <li><b>New Charts:</b> When deleting a product, you now have the option
+ to delete the data series that are associated with that product.</li>
+</ul>
+
+<h4>Enhancements for Administrators and Developers</h4>
+
+<ul>
+ <li>Depending on how your workflow is set up, it is now possible to
+ have both UNCONFIRMED and REOPENED show up as status choices for
+ a closed [% terms.bug %]. If you only want one or the other to
+ show up, you should edit your status workflow appropriately
+ (possibly by removing or disabling the REOPENED status).</li>
+ <li>You can now "disable" field values so that they don't show
+ up as choices on [% terms.abug %] unless they are already set as
+ the value for that [% terms.bug %]. This doesn't work for the
+ per-product field values (component, target_milestone, and version)
+ yet, though.</li>
+ <li>Users are now locked out of their accounts for 30 minutes after
+ trying five bad passwords in a row during login. Every time a
+ user is locked out like this, the user in the "maintainer" parameter
+ will get an email.</li>
+ <li>The minimum length allowed for a password is now 6 characters.</li>
+ <li>The <kbd>UNCONFIRMED</kbd> status being enabled in a product
+ is now unrelated to the voting parameters. Instead, there is a checkbox
+ to enable the <kbd>UNCONFIRMED</kbd> status in a product.</li>
+ <li>Information about duplicates is now stored in the database instead
+ of being stored in the <kbd>data/</kbd> directory. On large installations
+ this could save several hundred megabytes of disk space.</li>
+
+ <li><b>Installation:</b> When installing [% terms.Bugzilla %], the
+ "maintainer" parameter will be automatically set to the administrator
+ that was created by <kbd>checksetup.pl</kbd>.</li>
+ <li><b>Installation:</b> <kbd>checksetup.pl</kbd> now prints out
+ certain errors in a special color so that you know that something
+ needs to be done.</li>
+ <li><b>Installation:</b> <kbd>checksetup.pl</kbd> is now <em>much</em>
+ faster at upgrading installations, particularly older installations.
+ Also, it's been made faster to run for the case where it's not
+ doing an upgrade.</li>
+ <li><b>Installation:</b> If you install [% terms.Bugzilla %] using the
+ tarball, the <kbd>CGI.pm</kbd> module from CPAN is now included in
+ the <kbd>lib/</kbd> dir. If you would rather use the CGI.pm from your
+ global Perl installation, you can delete <kbd>CGI.pm</kbd> and the
+ <kbd>CGI</kbd> directory from the <kbd>lib/</kbd> directory.</li>
+
+ <li>When editing a group, you can now specify that members of a group
+ are allowed to grant others membership in that group itself.</li>
+ <li>The ability to compress BMP attachments to PNGs is now an Extension.
+ To enable the feature, remove the file
+ <kbd>extensions/BmpConvert/disabled</kbd> and then run <kbd>checksetup.pl</kbd>.</li>
+ <li>The default list of values for the Priority field are now clear English
+ words instead of P1, P2, etc.</li>
+ <li>There is now a system in place so that all field values can be
+ localized. See the <kbd>value_descs</kbd> variable in
+ <kbd>template/en/default/global/field-descs.none.tmpl</kbd>.</li>
+ <li><kbd>config.cgi</kbd> now returns an ETag header and understands
+ the If-None-Match header in HTTP requests.</li>
+ <li>The XML format of <kbd>show_bug.cgi</kbd> now returns more information:
+ the numeric id of each comment, whether an attachment is a URL,
+ the modification time of an attachment, the numeric id of a flag,
+ and the numeric id of a flag's type.</li>
+
+ <li><b>Parameters:</b> Parameters that aren't actually required are no longer
+ in the "Required" section of the Parameters page. Instead, some are in the
+ new "General" section, and some are in the new "Advanced" section.</li>
+ <li><b>Parameters:</b> The old <kbd>ssl</kbd> parameter has been
+ changed to <kbd>ssl_redirect</kbd>, and can only be turned "on" or "off".
+ If "on", then all users will be forcibly redirected to SSL whenever
+ they access [% terms.Bugzilla %]. When the parameter is off,
+ no SSL-related redirects will occur (even if the user directly
+ accesses [% terms.Bugzilla %] via SSL, they will <em>not</em> be
+ redirected to a non-SSL page).</li>
+ <li><b>Parameters:</b> In the Advanced parameters, there is a new parameter,
+ <kbd>inbound_proxies</kbd>. If your [% terms.Bugzilla %] is behind a
+ proxy, you should set this parameter to the IP address of that proxy.
+ Then, [% terms.Bugzilla %] will "believe" any "X-Forwarded-For"
+ header sent from that proxy, and correctly use the X-Forwarded-For
+ as the end user's IP, instead of believing that all traffic is coming
+ from the proxy.</li>
+
+ <li><b>Removed Parameter:</b> The <kbd>loginnetmask</kbd> parameter has
+ been removed. Since [% terms.Bugzilla %] sends secure cookies, it's no
+ longer necessary to always restrict logins to a specific IP or block
+ of addresses.</li>
+ <li><b>Removed Parameter:</b> The <kbd>quicksearch_comment_cutoff</kbd>
+ parameter is gone. Quicksearch now always searches comments; however, it
+ uses a much faster algorithm to do it.</li>
+ <li><b>Removed Parameter:</b> The <kbd>usermatchmode</kbd> parameter has
+ been removed. User-matching is now <em>always</em> done.</li>
+ <li><b>Removed Parameter:</b> The <kbd>useentrygroupdefault</kbd> parameter
+ has been removed. [% terms.Bugzilla %] now always behaves as though
+ that parameter were off.</li>
+ <li>The <kbd>t/001compile.t</kbd> test should now always pass, no matter
+ what configuration of optional modules you do or don't have installed.</li>
+ <li>New script: <kbd>contrib/console.pl</kbd>, which allows you to have
+ a "command line" into [% terms.Bugzilla %] by inputting Perl code
+ or using a few custom commands.</li>
+</ul>
+
+<h4>WebService Changes</h4>
+
+<ul>
+ <li>The WebService now returns all dates and times in the UTC timezone.
+ <kbd>B[% %]ugzilla.time</kbd> now acts as though the [% terms.Bugzilla %]
+ server were in the UTC timezone, always. If you want to write clients
+ that are compatible across all [% terms.Bugzilla %] versions,
+ check the timezone from <kbd>B[% %]ugzilla.timezone</kbd> or
+ <kbd>B[% %]ugzilla.time</kbd>, and always input times in that timezone
+ and expect times to be returned in that format.</li>
+ <li>You can now log in by passing <kbd>Bugzilla_login</kbd> and
+ <kbd>Bugzilla_password</kbd> as arguments to any WebService function.
+ See the
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/WebService.html#LOGGING_IN">Bugzilla::WebService</a>
+ documentation for details.</li>
+ <li>New Method:
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/WebService/Bug.html#attachments">B[% %]ug.attachments</a>
+ which allows getting information about attachments.</li>
+ <li>New Method:
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/WebService/Bug.html#fields">B[% %]ug.fields</a>,
+ which gets information about all the fields that [% terms.abug %] can have
+ in [% terms.Bugzilla %], include custom fields and legal values for
+ all fields. The <kbd>B[% %]ug.legal_values</kbd> method is now deprecated.</li>
+ <li>In the <kbd>B[% %]ug.add_comment</kbd> method, the "private" parameter
+ has been renamed to "is_private" (for consistency with other methods).
+ You can still use "private", though, for backwards-compatibility.</li>
+ <li>The WebService now has Perl's "taint mode" turned on. This means that
+ it validates all data passed in before sending it to the database.
+ Also, all parameter names are validated, and if you pass in a parameter
+ whose name contains anything other than letters, numbers, or underscores,
+ that parameter will be ignored. Mostly this just affects
+ customizers--[% terms.Bugzilla %]'s WebService is not functionally
+ affected by these changes.</li>
+ <li>In previous versions of [% terms.Bugzilla %], error messages were
+ sent word-wrapped to the client, from the WebService. Error messages
+ are now sent as one unbroken line.</li>
+</ul>
+
+<h2 id="v36_issues">Outstanding Issues</h2>
+
+<ul>
+ <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=423439">
+ [%- terms.Bug %] 423439</a>: Tabs in comments will be converted
+ to four spaces, due to a b<!-- -->ug in Perl as of Perl 5.8.8.</li>
+ <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=69621">
+ [%- terms.Bug %] 69621</a>: If you rename or remove a keyword that is
+ in use on [% terms.bugs %], you will need to rebuild the "keyword cache"
+ by running <a href="sanitycheck.cgi">sanitycheck.cgi</a> and choosing
+ the option to rebuild the cache when it asks. Otherwise keywords may
+ not show up properly in search results.</li>
+ <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=89822">
+ [%- terms.Bug %] 89822</a>: When changing multiple [% terms.bugs %] at
+ the same time, there is no "mid-air collision" protection.</li>
+ <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=276230">
+ [%- terms.Bug %] 276230</a>: The support for restricting access to
+ particular Categories of New Charts is not complete. You should treat
+ the 'chartgroup' Param as the only access mechanism available.<br>
+ However, charts migrated from Old Charts will be restricted to
+ the groups that are marked MANDATORY for the corresponding Product.
+ There is currently no way to change this restriction, and the
+ groupings will not be updated if the group configuration
+ for the Product changes.</li>
+</ul>
+
+<h2 id="v36_upgrading">Notes On Upgrading From a Previous Version</h2>
+
+<p>When upgrading to 3.6, <kbd>checksetup.pl</kbd> will create foreign keys
+ for many columns in the database. Before doing this, it will check the
+ database for consistency. If there are an unresolvable consistency
+ problems, it will tell you what table and column in the database contain
+ the bad values, and which values are bad. If you don't know what else to do,
+ you can always delete the database records which contain the bad values by
+ logging in to your database and running the following command:</p>
+
+<p><code>DELETE FROM <var>table</var> WHERE <var>column</var> IN
+ (<var>1, 2, 3, 4</var>)</code></p>
+
+<p>Just replace "table" and "column" with the name of the table
+ and column that <kbd>checksetup.pl</kbd> mentions, and "1, 2, 3, 4"
+ with the invalid values that <kbd>checksetup.pl</kbd> prints out.</p>
+
+<p>Remember that you should always back up your database before doing
+ an upgrade.</p>
+
+<h2 id="v36_code_changes">Code Changes Which May Affect Customizations</h2>
+
+<ul>
+ <li>There is no longer a SendBugMail method in the templates, and bugmail
+ is no longer sent by processing a template. Instead, it is sent
+ by using <kbd>Bugzilla::BugMail::Send</kbd>.</li>
+ <li>Comments are now represented as a
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/Comment.html">Bugzilla::Comment</a>
+ object instead of just being hashes.</li>
+ <li>In previous versions of [% terms.Bugzilla %], the template for displaying
+ [%+ terms.abug %] required a lot of extra variables that are now global
+ template variables instead.</li>
+ <li>You can now check if optional modules are installed by using
+ <kbd>Bugzilla-&gt;feature</kbd> in Perl code or
+ <kbd>feature_enabled</kbd> in template code.</li>
+ <li>All of the various template header information required to display
+ the [% terms.bug %] form is now in one template,
+ <kbd>template/en/default/bug/show-header.html.tmpl</kbd>.</li>
+ <li>You should now use <kbd>display_value</kbd> instead of
+ <kbd>get_status</kbd> or <kbd>get_resolution</kbd> in templates.
+ <kbd>display_value</kbd> should be used anywhere that a
+ &lt;select&gt;-type field has its values displayed.</li>
+</ul>
+
+
+<h1 id="v36_previous">[% terms.Bugzilla %] 3.4 Release Notes</h1>
+
+<ul class="bz_toc">
+ <li><a href="#v34_introduction">Introduction</a></li>
+ <li><a href="#v34_point">Updates in this 3.4.x Release</a></li>
+ <li><a href="#v34_req">Minimum Requirements</a></li>
+ <li><a href="#v34_feat">New Features and Improvements</a></li>
+ <li><a href="#v34_issues">Outstanding Issues</a></li>
+ <li><a href="#v34_upgrading">Notes On Upgrading From a Previous Version</a></li>
+ <li><a href="#v34_code_changes">Code Changes Which May Affect
+ Customizations</a></li>
+ <li><a href="#v34_previous">Release Notes for Previous Versions</a></li>
+</ul>
+
+<h2 id="v34_introduction">Introduction</h2>
+
+<p>This is [% terms.Bugzilla %] 3.4! [% terms.Bugzilla %] 3.4 brings a lot
+ of great enhancements for [% terms.Bugzilla %] over previous versions,
+ with various improvements to the user interface, lots of interesting new
+ features, and many long-standing requests finally being addressed.</p>
+
+<p>If you're upgrading, make sure to read <a href="#v34_upgrading">Notes
+ On Upgrading From a Previous Version</a>. If you are upgrading from a release
+ before 3.2, make sure to read the release notes for all the
+ <a href="#v34_previous">previous versions</a> in between your version
+ and this one, <strong>particularly the Upgrading section of each
+ version's release notes</strong>.</p>
+
+<p>We would like to thank <a href="http://www.canonical.com/">Canonical
+ Ltd.</a> for funding development of one new feature, and NASA for funding
+ development of several new features through the
+ <a href="http://www.sjsufoundation.org/">San Jose State University
+ Foundation</a>.</p>
+
+<h2 id="v34_point">Updates In This 3.4.x Release</h2>
+
+<h3>3.4.6</h3>
+
+<ul>
+ <li>When doing a search that involves "not equals" or "does not contain the
+ string" or similar "negative" search types, the search description that
+ appears at the top of the resulting [% terms.bug %] list will indicate
+ that the search was of that type.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=474738">[% terms.Bug %] 474738</a>)
+ </li>
+ <li>In Internet Explorer, users couldn't easily mark a RESOLVED DUPLICATE
+ [%+ terms.bug %] as REOPENED, due to a JavaScript error.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=546719">[% terms.Bug %] 546719</a>)
+ </li>
+ <li>If you use a "bookmarkable template" to pre-fill forms on
+ the [% terms.bug %]-filing page, and you have custom fields
+ that are only supposed to appear (or only supposed to have certain
+ values) based on the values of other fields, those custom fields will
+ now work properly.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=538211">[% terms.Bug %] 538211</a>)
+ </li>
+ <li>If you have a custom field that's only supposed to appear when
+ a [% terms.bug %]'s resolution is FIXED, it will now behave properly
+ on the [% terms.bug %]-editing form when a user sets the [% terms.bug %]'s
+ status to RESOLVED.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=520993">[% terms.Bug %] 520993</a>)
+ </li>
+ <li>If you are logged-out and using <kbd>request.cgi</kbd>, the Requester
+ and Requestee fields no longer respect the <kbd>usermatching</kbd>
+ parameter--they always require full usernames.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=533018">[% terms.Bug %] 533018</a>)
+ </li>
+ <li>If you tried to do a search with too many terms (resulting in a URL
+ that was longer than about 7000 characters), Apache would return a
+ 500 error instead of your search results.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=513989">[% terms.Bug %] 513989</a>)
+ </li>
+ <li>[% terms.Bugzilla %] would sometimes lose fields from your sort order
+ when you added new fields to your sort order.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=470214">[% terms.Bug %] 470214</a>)
+ </li>
+ <li>The Atom format of search results would sometimes be missing the
+ Reporter or Assignee field for some [% terms.bugs %].
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=537834">[% terms.Bug %] 537834</a>)
+ </li>
+</ul>
+
+<h3>3.4.5</h3>
+
+<p>This release contains fixes for multiple security issues. See the
+ <a href="http://www.bugzilla.org/security/3.0.10/">Security Advisory</a>
+ for details.</p>
+
+<p>In addition, the following important fixes/changes have been made in
+ this release:</p>
+
+<ul>
+ <li>Whining was failing if jobqueue.pl was enabled.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=530270">[% terms.Bug %] 530270</a>)
+ </li>
+ <li>The Assignee field was empty in Whine mails.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=511216">[% terms.Bug %] 511216</a>)
+ </li>
+ <li>Administrators can now successfully create user accounts using
+ editusers.cgi when using the "Env" authentication method.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=483987">[% terms.Bug %] 483987</a>)
+ </li>
+ <li>[% terms.Bug %]mail now uses the timezone of the recipient of the email,
+ when displaying the time a comment was made, instead of the timezone of the
+ person who made the change.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=534587">[% terms.Bug %] 534587</a>)
+ </li>
+ <li>"[% terms.bug %] 1234" in comments sometimes would not become a link if
+ word-wrapping happened between "[% terms.bug %]" and the number.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=514703">[% terms.Bug %] 514703</a>)
+ </li>
+ <li>Running <kbd>checksetup.pl</kbd> on Windows will no longer pop up an error box
+ about OCI.dll.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=480968">[% terms.Bug %] 480968</a>)
+ </li>
+</ul>
+
+<h3>3.4.4</h3>
+
+<p>This release contains a fix for a security issue. See the
+ <a href="http://www.bugzilla.org/security/3.4.3/">Security Advisory</a>
+ for details.</p>
+
+<p>Additionally, this release fixes a few minor [% terms.bugs %].</p>
+
+<h3>3.4.3</h3>
+
+<ul>
+ <li>[% terms.Bugzilla %] installations running under mod_perl were leaking
+ about 512K of RAM per page load.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=517793">[% terms.Bug %] 517793</a>)
+ </li>
+ <li>Attachments with Unicode characters in their names were being downloaded
+ with mangled names.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=328628">[% terms.Bug %] 328628</a>)
+ </li>
+ <li>Creating custom fields with Unicode in their database column name
+ is now no longer allowed, as it would break [% terms.Bugzilla %]. If you
+ created such a custom field, you should delete it by first marking it
+ obsolete and then clicking "Delete" in the custom field list, using
+ <a href="editfields.cgi">editfields.cgi</a>.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=525025">[% terms.Bug %] 525025</a>)
+ </li>
+ <li>Clicking "submit only my comment" on the "mid-air collisions" page
+ was leading to a "Suspicious Action" warning.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=514378">[% terms.Bug %] 514378</a>)
+ </li>
+ <li>The XML format of [% terms.abug %] accidentally contained the
+ word-wrapped content of comments instead of the unwrapped content.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=509152">[% terms.Bug %] 509152</a>)
+ </li>
+ <li>You can now do <kbd>./install-module.pl --shell</kbd> to get a CPAN
+ shell using the configuration of
+ <a href="[% docs_urlbase FILTER html %]api/install-module.html">install-module.pl</a>,
+ which allows you to do more advanced Perl module installation tasks.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=445875">[% terms.Bug %] 445875</a>)
+ </li>
+</ul>
+
+<h3>3.4.2</h3>
+
+<p>This release contains fixes for multiple security issues, one of which
+ is highly critical. See the
+ <a href="http://www.bugzilla.org/security/3.0.8/">Security Advisory</a>
+ for details.</p>
+
+<p>In addition, the following important fixes/changes have been made in
+ this release:</p>
+
+<ul>
+ <li>Upgrades from older releases were sometimes failing during UTF-8
+ conversion with a foreign key error.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=508181">[% terms.Bug %] 508181</a>)
+ </li>
+ <li>Sorting [% terms.bug %] lists on certain fields would result in an error.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=510944">[% terms.Bug %] 510944</a>)
+ </li>
+ <li>[% terms.Bug %] update emails had two or three blank lines at the top
+ and between the various sections of the email. There is now only one
+ blank line in each of those places, making these emails more compact.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=73330">[% terms.Bug %] 73330</a>)
+ </li>
+ <li>[% terms.Bug %] email notifications for new [% terms.bugs %] incorrectly
+ had a line saying that the description was "Comment 0".
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=510798">[% terms.Bug %] 510798</a>)
+ </li>
+ <li>Running <kbd>./collectstats.pl --regenerate</kbd> is now much faster,
+ on the order of 20x or 100x faster.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=286625">[% terms.Bug %] 286625</a>)
+ </li>
+ <li>For users of RHEL, CentOS, Fedora, etc. jobqueue.pl can now automatically
+ be installed as a daemon by running <kbd>./jobqueue.pl install</kbd>
+ as root.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=475403">[% terms.Bug %] 475403</a>)
+ </li>
+ <li>XML-RPC interface responses had an incorrect Content-Length header
+ and would sometimes be truncated, if they contained certain UTF-8
+ characters.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=486306">[% terms.Bug %] 486306</a>)
+ </li>
+ <li>Users who didn't have access to the time-tracking fields would get an
+ empty [% terms.bug %] update email when the time-tracking fields were
+ changed.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=509035">[% terms.Bug %] 509035</a>)
+ </li>
+ <li>In the New Charts, non-public series now no longer show up as selectable
+ if you cannot access them.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=389396">[% terms.Bug %] 389396</a>)
+ </li>
+</ul>
+
+<h3>3.4.1</h3>
+
+<p>This release contains an important security fix. See the
+ <a href="http://www.bugzilla.org/security/3.4/">Security Advisory</a>
+ for details.</p>
+
+<h2 id="v34_req">Minimum Requirements</h2>
+
+<p>Any requirements that are new since 3.2.3 will look like
+ <span class="req_new">this</span>.</p>
+
+<ul>
+ <li><a href="#v34_req_perl">Perl</a></li>
+ <li><a href="#v34_req_mysql">For MySQL Users</a></li>
+ <li><a href="#v34_req_pg">For PostgreSQL Users</a></li>
+ <li><a href="#v34_req_oracle">For Oracle Users</a></li>
+ <li><a href="#v34_req_modules">Required Perl Modules</a></li>
+ <li><a href="#v34_req_optional_mod">Optional Perl Modules</a></li>
+</ul>
+
+<h3 id="v34_req_perl">Perl</h3>
+
+<p>Perl v5.8.1</p>
+
+<h3 id="v34_req_mysql">For MySQL Users</h3>
+
+<ul>
+ <li>MySQL v4.1.2</li>
+ <li><strong>perl module:</strong> DBD::mysql v4.00</li>
+</ul>
+
+<h3 id="v34_req_pg">For PostgreSQL Users</h3>
+
+<ul>
+ <li>PostgreSQL v8.00.0000</li>
+ <li><strong>perl module:</strong> DBD::Pg v1.45</li>
+</ul>
+
+<h3 id="v34_req_oracle">For Oracle Users</h3>
+
+<ul>
+ <li>Oracle v10.02.0</li>
+ <li><strong>perl module:</strong> DBD::Oracle v1.19</li>
+</ul>
+
+<h3 id="v34_req_modules">Required Perl Modules</h3>
+
+<table class="req_table" border="0" cellspacing="0" cellpadding="0">
+ <tr>
+ <th>Module</th> <th>Version</th>
+ </tr>
+ <tr>
+ <td>CGI</td>
+ <td>3.21</td>
+ </tr>
+ <tr>
+ <td class="req_new">Digest::SHA</td>
+ <td class="req_new"> (Any)</td>
+ </tr>
+ <tr>
+ <td>Date::Format</td>
+ <td>2.21</td>
+ </tr>
+ <tr>
+ <td class="req_new">DateTime</td>
+ <td class="req_new">0.28</td>
+ </tr>
+ <tr>
+ <td class="req_new">DateTime::TimeZone</td>
+ <td class="req_new">0.71</td>
+ </tr>
+ <tr>
+ <td>DBI</td>
+ <td>1.41</td>
+ </tr>
+ <tr>
+ <td>Template</td>
+ <td class="req_new">2.22</td>
+ </tr>
+ <tr>
+ <td>Email::Send</td>
+ <td>2.00</td>
+ </tr>
+ <tr>
+ <td>Email::MIME</td>
+ <td>1.861</td>
+ </tr>
+ <tr>
+ <td>Email::MIME::Encodings</td>
+ <td>1.313</td>
+ </tr>
+ <tr>
+ <td>Email::MIME::Modifier</td>
+ <td>1.442</td>
+ </tr>
+ <tr>
+ <td class="req_new">URI</td>
+ <td class="req_new">(Any)</td>
+ </tr>
+</table>
+
+<h3 id="v34_req_optional_mod">Optional Perl Modules</h3>
+
+<p>The following perl modules, if installed, enable various
+ features of [% terms.Bugzilla %]:</p>
+
+<table class="req_table" border="0" cellspacing="0" cellpadding="0">
+ <tr>
+ <th>Module</th>
+ <th>Version</th>
+ <th>Enables Feature</th>
+ </tr>
+ <tr>
+ <td>LWP::UserAgent</td>
+ <td>(Any)</td>
+ <td>Automatic Update Notifications</td>
+ </tr>
+ <tr>
+ <td>Template::Plugin::GD::Image</td>
+ <td>(Any)</td>
+ <td>Graphical Reports</td>
+ </tr>
+ <tr>
+ <td>GD::Text</td>
+ <td>(Any)</td>
+ <td>Graphical Reports</td>
+ </tr>
+ <tr>
+ <td>GD::Graph</td>
+ <td>(Any)</td>
+ <td>Graphical Reports</td>
+ </tr>
+ <tr>
+ <td>GD</td>
+ <td>1.20</td>
+ <td>Graphical Reports, New Charts, Old Charts</td>
+ </tr>
+ <tr>
+ <td>Email::MIME::Attachment::Stripper</td>
+ <td>(Any)</td>
+ <td>Inbound Email</td>
+ </tr>
+ <tr>
+ <td>Email::Reply</td>
+ <td>(Any)</td>
+ <td>Inbound Email</td>
+ </tr>
+ <tr>
+ <td>Net::LDAP</td>
+ <td>(Any)</td>
+ <td>LDAP Authentication</td>
+ </tr>
+ <tr>
+ <td class="req_new">TheSchwartz</td>
+ <td class="req_new">(Any)</td>
+ <td>Mail Queueing</td>
+ </tr>
+ <tr>
+ <td class="req_new">Daemon::Generic</td>
+ <td class="req_new">(Any)</td>
+ <td>Mail Queueing</td>
+ </tr>
+ <tr>
+ <td>HTML::Parser</td>
+ <td>3.40</td>
+ <td>More HTML in Product/Group Descriptions</td>
+ </tr>
+ <tr>
+ <td>HTML::Scrubber</td>
+ <td>(Any)</td>
+ <td>More HTML in Product/Group Descriptions</td>
+ </tr>
+ <tr>
+ <td>XML::Twig</td>
+ <td>(Any)</td>
+ <td>Move [% terms.Bugs %] Between Installations</td>
+ </tr>
+ <tr>
+ <td>MIME::Parser</td>
+ <td>5.406</td>
+ <td>Move [% terms.Bugs %] Between Installations</td>
+ </tr>
+ <tr>
+ <td>Chart::Base</td>
+ <td>1.0</td>
+ <td>New Charts, Old Charts</td>
+ </tr>
+ <tr>
+ <td>Image::Magick</td>
+ <td>(Any)</td>
+ <td>Optionally Convert BMP Attachments to PNGs</td>
+ </tr>
+ <tr>
+ <td>PatchReader</td>
+ <td>0.9.4</td>
+ <td>Patch Viewer</td>
+ </tr>
+ <tr>
+ <td>Authen::Radius</td>
+ <td>(Any)</td>
+ <td>RADIUS Authentication</td>
+ </tr>
+ <tr>
+ <td>Authen::SASL</td>
+ <td>(Any)</td>
+ <td>SMTP Authentication</td>
+ </tr>
+ <tr>
+ <td>SOAP::Lite</td>
+ <td>0.710.06</td>
+ <td>XML-RPC Interface</td>
+ </tr>
+ <tr>
+ <td>mod_perl2</td>
+ <td>1.999022</td>
+ <td>mod_perl</td>
+ </tr>
+</table>
+
+<h2 id="v34_feat">New Features and Improvements</h2>
+
+<ul>
+ <li><a href="#v34_feat_enter">Simple [% terms.Bug %] Filing</a></li>
+ <li><a href="#v34_feat_index">New Home Page</a></li>
+ <li><a href="#v34_feat_spam">Email Addresses Hidden From Logged-Out
+ Users</a></li>
+ <li><a href="#v34_feat_urls">Shorter Search URLs</a></li>
+ <li><a href="#v34_feat_async">Asynchronous Email Sending</a></li>
+ <li><a href="#v34_feat_tz">Dates and Times Displayed In User's Time
+ Zone</a></li>
+ <li><a href="#v34_feat_vis">Custom Fields That Only Appear When
+ Another Field Has a Particular Value</a></li>
+ <li><a href="#v34_feat_vals">Custom Fields Whose List of Values
+ Change Depending on the Value of Another Field</a></li>
+ <li><a href="#v34_feat_bugid">New Custom Field Type:
+ [%+ terms.Bug %] ID</a></li>
+ <li><a href="#v34_feat_see">"See Also" Field</a></li>
+ <li><a href="#v34_feat_cols">Re-order Columns in Search Results</a></li>
+ <li><a href="#v34_feat_desc">Search Descriptions</a></li>
+ <li><a href="#v34_feat_other">Other Enhancements and Changes</a></li>
+</ul>
+
+<h3 id="v34_feat_enter">Simple [% terms.Bug %] Filing</h3>
+
+<p>When entering a new [% terms.bug %], the vast majority of fields are
+ now hidden by default, which enormously simplifies the bug-filing form.
+ You can click "Show Advanced Fields" to show all the fields, if you want
+ them. [%+ terms.Bugzilla %] remembers whether you last used the "Advanced"
+ or "Simple" version of the [% terms.bug %]-entry form, and will display the
+ same version to you again next time you file [% terms.abug %].</p>
+
+<h3 id="v34_feat_index">New Home Page</h3>
+
+<p>[% terms.Bugzilla %]'s front page has been redesigned to be better at
+ guiding new users into the activities that they most commonly want to
+ do. Further enhancements to the home page are coming in future versions
+ of [% terms.Bugzilla %].</p>
+
+<h3 id="v34_feat_spam">Email Addresses Hidden From Logged-Out Users</h3>
+
+<p>To help prevent spam to [% terms.Bugzilla %] users, all email addresses
+ stored in [% terms.Bugzilla %] are now displayed only if you are logged in.
+ If you are logged out, only the part before the "@" of the email address is
+ displayed. This includes [% terms.bug %] lists, viewing [% terms.bugs %], the
+ XML format of [% terms.abug %], and any other place in the web interface that
+ an email address could appear.</p>
+
+<p>Email addresses are not filtered out of [% terms.bug %] comments.
+ The WebService still returns full email addresses, even if you are logged
+ out.</p>
+
+<h3 id="v34_feat_urls">Shorter Search URLs</h3>
+
+<p>When submitting a search, all the unused fields are now stripped from
+ the URL, so search URLs are much more meaningful, and much shorter.</p>
+
+<h3 id="v34_feat_async">Asynchronous Email Sending</h3>
+
+<p>The largest performance problem in former versions of [% terms.Bugzilla %]
+ was that when updating [% terms.bugs %], email would be sent immediately
+ to every user who needed to be notified, and <kbd>process_bug.cgi</kbd>
+ would wait for the emails to be sent before continuing.</p>
+
+<p>Now [% terms.Bugzilla %] is capable of queueing emails to be sent
+ while [% terms.abug %] is being updated, and sending them in the
+ background. This requires the administrator to run a daemon
+ that comes with [% terms.Bugzilla %], named
+ <a href="[% docs_urlbase FILTER html %]api/jobqueue.html">jobqueue.pl</a>,
+ and to enable the <a href="editparams.cgi?section=mta#use_mailer_queue">
+ use_mailer_queue</a> parameter.</p>
+
+<p>Using the background email-sending daemon instead of sending mail directly
+ should result in a very large speed-up for updating [% terms.bugs %],
+ particularly on larger installations.</p>
+
+<h3 id="v34_feat_tz">Dates and Times Displayed In User's Time Zone</h3>
+
+<p>Users can now select what time zone they are in and [% terms.Bugzilla %]
+ will adjust displayed times to be correct for their time zone. However,
+ times the user inputs are unfortunately still in [% terms.Bugzilla %]'s
+ time zone.</p>
+
+<h3 id="v34_feat_vis">Custom Fields That Only Appear When Another Field
+ Has a Particular Value</h3>
+
+<p>When creating a new custom field (or updating the definition of
+ an existing custom field), you can now say that "this field only
+ appears when field X has value Y". (In the future, you will be able
+ to select multiple values for "Y", so a field will appear when any
+ one of those values is selected.)</p>
+
+<p>This feature only hides fields--it doesn't make their values go away.
+ So [% terms.bugs %] will still show up in searches for that field's
+ value, but the field won't appear in the user interface.</p>
+
+<p>This is a good way of making Product-specific fields.</p>
+
+<h3 id="v34_feat_vals">Custom Fields Whose List of Values Change
+ Depending on the Value of Another Field</h3>
+
+<p>When creating a drop-down or multiple-selection custom field, you can
+ now specify that another field "controls the values" of this field.
+ Then, when adding values to this field, you can say that a particular
+ value only appears when the other field is set to a particular
+ value.</p>
+
+<p>Here's an example: Let's say that we create a field called "Colors",
+ and we make the Product field "control the values" for Colors. Then we
+ add Blue, Red, Black, and Yellow as legal values for the "Colors" field.
+ Now we can say that "Blue" and "Red" only appear as valid choices in
+ Product A, "Yellow" only appears in Product B, but "Black" <em>always</em>
+ appears.</p>
+
+<p>One thing to note is that this feature only controls what values appear in
+ the <em>user interface</em>. [% terms.Bugzilla %] itself will still accept
+ any combination of values as valid, in the backend.</p>
+
+<h3 id="v34_feat_bugid">New Custom Field Type: [% terms.Bug %] ID</h3>
+
+<p>You can now create a custom field that holds a reference to a single
+ valid [% terms.bug %] ID. In the future this will be enhanced to allow
+ [%+ terms.bugs %] to refer to each other via this field.</p>
+
+<h3 id="v34_feat_see">"See Also" Field</h3>
+
+<p>We have added a new standard field called "See Also" to
+ [%+ terms.Bugzilla %]. In this field, you can put URLs to multiple
+ [%+ terms.bugs %] in any [% terms.Bugzilla %] installation, to indicate
+ that those [% terms.bugs %] are related to this one. It also supports
+ adding URLs to [% terms.bugs %] in
+ <a href="http://launchpad.net/">Launchpad</a>.</p>
+
+<p>Right now, the field just validates the URLs and then displays them, but
+ in the future, it will grab information from the other installation about
+ the [% terms.bug %] and display it here, and possibly even update the
+ other installation.</p>
+
+<p>If your installation does not need this field, you can hide it by disabling
+ the <a href="editparams.cgi?section=bugfields#use_see_also">use_see_also
+ parameter</a>.</p>
+
+<h3 id="v34_feat_cols">Re-order Columns in Search Results</h3>
+
+<p>There is a new interface for choosing what columns appear in search
+ results, which allows you to change the order in which columns appear
+ from left to right when viewing the [% terms.bug %] list.</p>
+
+<h3 id="v34_feat_desc">Search Descriptions</h3>
+
+<p>When displaying search results, [% terms.Bugzilla %] will now show
+ a brief description of what you searched for, at the top of the
+ [%+ terms.bug %] list.</p>
+
+<h3 id="v34_feat_other">Other Enhancements and Changes</h3>
+
+<h4>Enhancements for Users</h4>
+
+<ul>
+ <li>You can now log in from every page, using the login form that appears
+ in the header or footer when you click "Log In".</li>
+ <li>When viewing [% terms.abug %], obsolete attachments are now
+ hidden from the attachment list by default. You can show them
+ by clicking "Show Obsolete" at the bottom of the attachment list.</li>
+ <li>In the Email Preferences, you can now choose to get email when
+ a new [% terms.bug %] report is filed and you have a particular
+ role on it.</li>
+ <li>When resolving a mid-air collision, you can now choose to submit
+ only your comment.</li>
+ <li>You can now set the Blocks and Depends On field on the "Change
+ Several [% terms.Bugs %] At Once" page.</li>
+ <li>If your installation uses the "insidergroup" feature, you can now add
+ private comments on the "Change Several [% terms.Bugs %] At Once"
+ page.</li>
+ <li>When viewing a search result, you can now hover over any abbreviated
+ field to see its full value.</li>
+ <li>When logging out, users are now redirected to the main page of
+ [%+ terms.Bugzilla %] instead of an empty page.</li>
+ <li>When editing [% terms.abug %], text fields (except the comment box) now
+ grow longer when you widen your browser window.</li>
+ <li>When viewing [% terms.abug %], the Depends On and Blocks list will
+ display [% terms.abug %]'s alias if it has one, instead of its id.
+ Also, closed [% terms.bugs %] will be sorted to the end of the list.</li>
+
+ <li>If you use the time-tracking features of [% terms.Bugzilla %], and
+ you enable the time-tracking related columns in a search result,
+ then you will see a summary of the time-tracking data at the
+ bottom of the search result.</li>
+ <li>For users of time-tracking, the <kbd>summarize_time.cgi</kbd> page
+ now contains more data.</li>
+
+ <li>When viewing an attachment's details page while you are logged-out,
+ flags are no longer shown as editable.</li>
+ <li>Cloning [% terms.abug %] will now retain the "Blocks" and "Depends On"
+ fields from the [% terms.bug %] being cloned.</li>
+ <li>[% terms.Bug %]mail for new [% terms.bugs %] will now indicate
+ what security groups the [% terms.bug %] has been restricted to.</li>
+ <li>You can now use any custom drop-down field as an axis for a tabular
+ or graphical report.</li>
+ <li>The <kbd>X-Bugzilla-Type</kbd> header in emails sent by
+ [%+ terms.Bugzilla %] is now "new" for [% terms.bug %]mail sent for
+ newly-filed [% terms.bugs %], and "changed" for emails having to do
+ with updated [% terms.bugs %].</li>
+ <li>Mails sent by the "Whining" system now contain the header
+ <kbd>X-Bugzilla-Type: whine</kbd>.</li>
+ <li>[% terms.bug %]mail now contains a X-Bugzilla-URL header to uniquely
+ identify which [% terms.Bugzilla %] installation the email came from.</li>
+ <li>If you input an invalid regular expression anywhere in
+ [%+ terms.Bugzilla %], it will now tell you explicitly instead of failing
+ cryptically.</li>
+ <li>The <kbd>duplicates.xul</kbd> page (which wasn't used by very many
+ people) is now gone.</li>
+</ul>
+
+<h4>Enhancements for Administrators and Developers</h4>
+
+<ul>
+ <li>[% terms.Bugzilla %] now uses the SHA-256 algorithm (a variant of
+ SHA-2) to encrypt passwords in the database, instead of using Unix's
+ "crypt" function. This allows passwords longer than eight characters
+ to actually be effective. Each user's password will be converted to
+ SHA-256 the first time they log in after you upgrade to
+ [%+ terms.Bugzilla %] 3.4 or later.</li>
+ <li>If you are using database replication with [% terms.Bugzilla %],
+ many more scripts now take advantage of the read-only slave (the
+ "shadowdb"). It may be safe to open up <kbd>show_bug.cgi</kbd>
+ to search-engine indexing by editing your <kbd>robots.txt</kbd> file,
+ now, if your [% terms.Bugzilla %] is on fast-enough hardware.</li>
+ <li>The database now uses foreign keys to enforce the validity of
+ relationships between tables. Not every single table has all its
+ foreign keys yet, but most do.</li>
+ <li>Various parameters have been removed, in an effort to de-clutter
+ the parameter interface and simplify [% terms.Bugzilla %]'s code.
+ The parameters that were removed were: timezone, supportwatchers,
+ maxpatchsize, commentonclearresolution, commentonreassignbycomponent,
+ showallproducts. They have all been replaced with sensible default
+ behaviors. (For example, user watching is now always enabled.)</li>
+ <li>When adding <code>&amp;debug=1</code> to the end of a
+ <kbd>buglist.cgi</kbd> URL, [% terms.Bugzilla %] will now also do an
+ EXPLAIN on the query, to help debug performance issues.</li>
+ <li>When editing flag types in the administrative interface, you can now
+ see how many flags of each type have been set.</li>
+</ul>
+
+<h4>WebService Changes</h4>
+
+<ul>
+ <li>Various functions have been added to the WebService:
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/WebService/Bug.html#history">B[% %]ug.history</a>,
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/WebService/Bug.html#search">B[% %]ug.search</a>,
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/WebService/Bug.html#comments">B[% %]ug.comments</a>,
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/WebService/Bug.html#update_see_also">B[% %]ug.update_see_also</a>,
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/WebService/User.html#get">User.get</a>,
+ and <a href="[% docs_urlbase FILTER html %]api/Bugzilla/WebService/Bugzilla.html#time">B[% %]ugzilla.time</a>
+ (<kbd>B[% %]ugzilla.timezone</kbd> is now deprecated).
+ </li>
+ <li>For network efficiency, you can now limit which fields are returned
+ from certain WebService functions, like <kbd>User.get</kbd>.</li>
+ <li>There is now a "permissive" argument for the <kbd>B[% %]ug.get</kbd>
+ WebService function, which causes it not to throw an error when you
+ ask for [% terms.bugs %] you can't see.</li>
+
+ <li>The <kbd>B[% %]ug.get</kbd> method now returns many more fields.</li>
+ <li>The <kbd>B[% %]ug.add_comment</kbd> method now returns the ID of the comment
+ that was just added.</li>
+ <li>The <kbd>B[% %]ug.add_comment</kbd> method will now throw an error if you
+ try to add a private comment but do not have the correct permissions.
+ (In previous versions, it would just silently ignore the <kbd>private</kbd>
+ argument if you didn't have the correct permissions.)</li>
+ <li>Many WebService function parameters now take individual values in
+ addition to arrays.</li>
+ <li>The WebService now validates input types--it makes sure that dates
+ are in the right format, that ints are actually ints, etc. It will throw
+ an error if you send it invalid data. It also accepts empty ints, doubles,
+ and dateTimes, and translates them to <kbd>undef</kbd>.</li>
+</ul>
+
+<h2 id="v34_issues">Outstanding Issues</h2>
+
+<ul>
+ <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=423439">
+ [%- terms.Bug %] 423439</a>: Tabs in comments will be converted
+ to four spaces, due to a b<!-- -->ug in Perl as of Perl 5.8.8.</li>
+ <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=69621">
+ [%- terms.Bug %] 69621</a>: If you rename or remove a keyword that is
+ in use on [% terms.bugs %], you will need to rebuild the "keyword cache"
+ by running <a href="sanitycheck.cgi">sanitycheck.cgi</a> and choosing
+ the option to rebuild the cache when it asks. Otherwise keywords may
+ not show up properly in search results.</li>
+ <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=89822">
+ [%- terms.Bug %] 89822</a>: When changing multiple [% terms.bugs %] at
+ the same time, there is no "mid-air collision" protection.</li>
+ <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=276230">
+ [%- terms.Bug %] 276230</a>: The support for restricting access to
+ particular Categories of New Charts is not complete. You should treat
+ the 'chartgroup' Param as the only access mechanism available.<br>
+ However, charts migrated from Old Charts will be restricted to
+ the groups that are marked MANDATORY for the corresponding Product.
+ There is currently no way to change this restriction, and the
+ groupings will not be updated if the group configuration
+ for the Product changes.</li>
+ <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=370370">
+ [%- terms.Bug %] 370370</a>: mod_perl support is currently not
+ working on Windows machines.</li>
+</ul>
+
+<h2 id="v34_upgrading">Notes On Upgrading From a Previous Version</h2>
+
+<p>When upgrading to 3.4, <kbd>checksetup.pl</kbd> will create foreign keys
+ for many columns in the database. Before doing this, it will check the
+ database for consistency. If there are an unresolvable consistency
+ problems, it will tell you what table and column in the database contain
+ the bad values, and which values are bad. If you don't know what else to do,
+ you can always delete the database records which contain the bad values by
+ logging in to your database and running the following command:</p>
+
+<p><code>DELETE FROM <var>table</var> WHERE <var>column</var> IN
+ (<var>1, 2, 3, 4</var>)</code></p>
+
+<p>Just replace "table" and "column" with the name of the table
+ and column that <kbd>checksetup.pl</kbd> mentions, and "1, 2, 3, 4"
+ with the invalid values that <kbd>checksetup.pl</kbd> prints out.</p>
+
+<p>Remember that you should always back up your database before doing
+ an upgrade.</p>
+
+<h2 id="v34_code_changes">Code Changes Which May Affect Customizations</h2>
+
+<ul>
+ <li><kbd>checksetup.pl</kbd> now re-writes the <kbd>localconfig</kbd>
+ file every time it runs, keeping the current values set (if there
+ are any), but moving any unexpected variables into a file called
+ <kbd>localconfig.old</kbd>. If you want to continue having custom
+ varibles in <kbd>localconfig</kbd>, you will have to add them to
+ the <code>LOCALCONFIG_VARS</code> constant in
+ <kbd>Bugzilla::Install::Localconfig</kbd>.</li>
+ <li><kbd>Bugzilla::Object-&gt;update()</kbd> now returns something different
+ in list context than it does in scalar context.</li>
+ <li><kbd>Bugzilla::Object-&gt;check()</kbd> now can take object
+ ids in addition to names. Just pass in <code>{ id =&gt; $some_value
+ }</code>.</li>
+ <li>Instead of being defined in <kbd>buglist.cgi</kbd>, columns for
+ search results are now defined in a subroutine called <code>COLUMNS</code>
+ in <kbd>Bugzilla::Search</kbd>. The data now mostly comes from the
+ <kbd>fielddefs</kbd> table in the database. Search.pm now takes a list
+ of column names from fielddefs for its <kbd>fields</kbd> argument instead
+ of literal SQL columns.</li>
+ <li><kbd>Bugzilla::Field-&gt;legal_values</kbd> now returns an array of
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/Field/Choice.html">Bugzilla::Field::Choice</a>
+ objects instead of an array of strings. Bugzilla::Field::Choice will be used
+ in more places, in the future.</li>
+ <li>We now use <kbd>Bugzilla::Bug-&gt;check()</kbd> instead of
+ <kbd>ValidateBugId</kbd>.</li>
+ <li>The <kbd>groups</kbd> and <kbd>bless_groups</kbd> methods in
+ <kbd>Bugzilla::User</kbd> now return an arrayref of
+ <kbd>Bugzilla::Group</kbd> objects instead of a hashref with
+ group ids and group names.</li>
+ <li>Standard [% terms.Bugzilla %] drop-down fields now have their type
+ set to <kbd>FIELD_TYPE_SINGLE_SELECT</kbd> in the fielddefs table.</li>
+ <li><kbd>Bugzilla-&gt;usage_mode</kbd> now defaults to
+ <kbd>USAGE_MODE_CMDLINE</kbd> if we are not running inside a web
+ server.</li>
+ <li>We no longer delete environment variables like <kbd>$ENV{PATH}</kbd>
+ automatically unless we're actually running in taint mode.</li>
+ <li>We are now using YUI 2.6.0.</li>
+ <li>In <a href="config.cgi?ctype=rdf">the RDF format of config.cgi</a>,
+ the "resource" attribute for flags now contains "flag.cgi" instead
+ of "flags.cgi".</li>
+</ul>
+
+
+
+
+
+
+
+<h1 id="v34_previous">[% terms.Bugzilla %] 3.2 Release Notes</h1>
+
+<h2>Table of Contents</h2>
+
+<ul class="bz_toc">
+ <li><a href="#v32_introduction">Introduction</a></li>
+ <li><a href="#v32_point">Updates In This 3.2.x Release</a></li>
+ <li><a href="#v32_security">Security Fixes In This 3.2.x Release</a></li>
+ <li><a href="#v32_req">Minimum Requirements</a></li>
+ <li><a href="#v32_feat">New Features and Improvements</a></li>
+ <li><a href="#v32_issues">Outstanding Issues</a></li>
+ <li><a href="#v32_upgrading">How to Upgrade From An Older Version</a></li>
+ <li><a href="#v32_code_changes">Code Changes Which May Affect
+ Customizations</a></li>
+ <li><a href="#v32_previous">Release Notes for Previous Versions</a></li>
+</ul>
+
+<h2 id="v32_introduction">Introduction</h2>
+
+<p>Welcome to [% terms.Bugzilla %] 3.2! This is our first major feature
+ release since [% terms.Bugzilla %] 3.0, and it brings a lot of great
+ improvements and polish to the [% terms.Bugzilla %] experience.</p>
+
+<p>If you're upgrading, make sure to read <a href="#v32_upgrading">How to
+ Upgrade From An Older Version</a>. If you are upgrading from a release
+ before 3.0, make sure to read the release notes for all the
+ <a href="#v32_previous">previous versions</a> in between your version
+ and this one, <strong>particularly the "Notes For Upgraders" section of each
+ version's release notes</strong>.</p>
+
+<h2 id="v32_point">Updates in this 3.2.x Release</h2>
+
+<p>This section describes what's changed in the most recent b<!-- -->ug-fix
+ releases of [% terms.Bugzilla %] after 3.2. We only list the
+ most important fixes in each release. If you want a detailed list of
+ <em>everything</em> that's changed in each version, you should use our
+ <a href="http://www.bugzilla.org/status/changes.html">Change Log
+ Page</a>.</p>
+
+<h3>3.2.3</h3>
+
+<ul>
+ <li>[% terms.Bugzilla %] is now compatible with MySQL 5.1.x versions 5.1.31
+ and greater.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=480001">[% terms.Bug %] 480001</a>)</li>
+ <li>On Windows, [% terms.Bugzilla %] sometimes would send mangled emails
+ (that would often fail to send).
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=467920">[% terms.Bug %] 467920</a>)</li>
+ <li><code>recode.pl</code> would sometimes crash when trying to convert
+ databases from older versions of [% terms.Bugzilla %].
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=431201">[% terms.Bug %] 431201</a>)</li>
+ <li>Running a saved search with Unicode characters in its name would
+ cause [% terms.Bugzilla %] to crash.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=477513">[% terms.Bug %] 477513</a>)</li>
+ <li>[% terms.Bugzilla %] clients like Mylyn can now update [% terms.bugs %]
+ again (the [% terms.bug %] XML format now contains a "token" element that
+ can be used when updating [% terms.abug %]).
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=476678">[% terms.Bug %] 476678</a>)</li>
+ <li>For installations using the <code>shadowdb</code> parameter,
+ [%+ terms.Bugzilla %] was accidentally writing to the "tokens" table
+ in the shadow database (instead of the master database) when using the
+ "Change Several [% terms.Bugs %] at Once" page.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=476943">[% terms.Bug %] 476943</a>)</li>
+</ul>
+
+<p>This release also contains a security fix. See the
+ <a href="#v32_security">Security Fixes Section</a> for details.</p>
+
+<h3>3.2.2</h3>
+
+<p>This release fixes one security issue that is critical for installations
+ running 3.2.1 under mod_perl. See the
+ <a href="http://www.bugzilla.org/security/3.0.7/">Security Advisory</a>
+ for details.</p>
+
+<h3>3.2.1</h3>
+
+<ul>
+ <li>Attachments, charts, and graphs would sometimes be garbled on Windows.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=464992">[% terms.Bug %] 464992</a>)</li>
+
+ <li>Saving changes to parameters would sometimes fail silently (particularly
+ on Windows when the web server didn't have the right permissions to
+ update the <code>params</code> file). [% terms.Bugzilla %] will now
+ throw an error in this case, telling you what is wrong.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=347707">[% terms.Bug %] 347707</a>)</li>
+
+ <li>If you were using the <code>usemenuforusers</code> parameter,
+ and [% terms.abug %] was assigned to (or had a QA Contact of) a disabled
+ user, that field would be reset to the first user in the list when
+ updating [% terms.abug %].
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=465589">[% terms.Bug %] 465589</a>)</li>
+
+ <li>If you were using the <code>PROJECT</code> environment variable
+ to have multiple [% terms.Bugzilla %] installations using one codebase,
+ project-specific templates were being ignored.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=467324">[% terms.Bug %] 467324</a>)</li>
+
+ <li>Some versions of the SOAP::Lite Perl module had a b[% %]ug that caused
+ [%+ terms.Bugzilla %]'s XML-RPC service to break.
+ <kbd>checksetup.pl</kbd> now checks for these bad versions and
+ will reject them.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=468009">[% terms.Bug %] 468009</a>)</li>
+
+ <li>The font sizes in various places were too small, when using the
+ Classic skin.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=469136">[% terms.Bug %] 469136</a>)</li>
+</ul>
+
+<h2 id="v32_security">Security Fixes In This 3.2.x Release</h2>
+
+<h3>3.2.3</h3>
+
+<p>This release fixes one security issue related to attachments. See the
+ <a href="http://www.bugzilla.org/security/3.2.2/">Security Advisory</a>
+ for details.</p>
+
+<h3>3.2.2</h3>
+
+<p>This release fixes one security issue that is critical for installations
+ running 3.2.1 under mod_perl. See the
+ <a href="http://www.bugzilla.org/security/3.0.7/">Security Advisory</a>
+ for details.</p>
+
+<h3>3.2.1</h3>
+
+<p>This release contains several security fixes. One fix may break any
+ automated scripts you have that are loading <kbd>process_bug.cgi</kbd>
+ directly. We recommend that you read the entire
+ <a href="http://www.bugzilla.org/security/2.22.6/">Security Advisory</a>
+ for this release.</p>
+
+<h2 id="v32_req">Minimum Requirements</h2>
+
+<p>Any requirements that are new since 3.0.5 will look like
+ <span class="req_new">this</span>.</p>
+
+<ul>
+ <li><a href="#v32_req_perl">Perl</a></li>
+ <li><a href="#v32_req_mysql">For MySQL Users</a></li>
+ <li><a href="#v32_req_pg">For PostgreSQL Users</a></li>
+ <li><a href="#v32_req_oracle">For Oracle Users</a></li>
+ <li><a href="#v32_req_modules">Required Perl Modules</a></li>
+ <li><a href="#v32_req_optional_mod">Optional Perl
+ Modules</a></li>
+</ul>
+
+<h3 id="v32_req_perl">Perl</h3>
+
+<p>Perl <span class="req_new">v<strong>5.8.1</strong></span></p>
+
+<h3 id="v32_req_mysql">For MySQL Users</h3>
+
+<ul>
+ <li>MySQL v4.1.2</li>
+ <li><strong>perl module:</strong>
+ DBD::mysql <span class="req_new">v4.00</span></li>
+</ul>
+
+<h3 id="v32_req_pg">For PostgreSQL Users</h3>
+
+<ul>
+ <li>PostgreSQL v8.00.0000</li>
+ <li><strong>perl module:</strong> DBD::Pg v1.45</li>
+</ul>
+
+<h3 id="v32_req_oracle">Email Addresses Hidden From Logged-Out Users
+ For Oracle Users</h3>
+
+<ul>
+ <li>Oracle v10.02.0</li>
+ <li><strong>perl module:</strong> DBD::Oracle v1.19</li>
+</ul>
+
+<h3 id="v32_req_modules">Required Perl Modules</h3>
+
+<table class="req_table" border="0" cellpadding="0" cellspacing="0">
+<tr> <th>Module</th> <th>Version</th> </tr>
+<tr> <td>CGI</td> <td class="req_new">3.21 (on Perl 5.8.x)
+ or 3.33 (on Perl 5.10.x)</td> </tr>
+<tr> <td>Date::Format</td> <td>2.21</td> </tr>
+<tr> <td>File::Spec</td> <td>0.84</td> </tr>
+<tr> <td>DBI</td> <td>1.41</td> </tr>
+<tr> <td>Template</td> <td class="req_new">2.15</td> </tr>
+<tr> <td>Email::Send</td> <td>2.00</td> </tr>
+<tr> <td>Email::MIME</td> <td class="req_new">1.861</td> </tr>
+<tr>
+ <td class="req_new">Email::MIME::Encodings</td>
+ <td class="req_new">1.313</td>
+</tr>
+<tr>
+ <td>Email::MIME::Modifier</td>
+ <td class="req_new">1.442</td>
+</tr>
+</table>
+
+<h3 id="v32_req_optional_mod">Optional Perl Modules</h3>
+
+<p>The following perl modules, if installed, enable various
+ features of [% terms.Bugzilla %]:</p>
+
+<table class="req_table" border="0" cellpadding="0" cellspacing="0">
+<tr>
+ <th>Module</th>
+ <th>Version</th>
+ <th>Enables Feature</th>
+</tr>
+<tr>
+ <td>LWP::UserAgent</td>
+ <td>(Any)</td>
+ <td>Automatic Update Notifications</td>
+</tr>
+<tr>
+ <td>Template::Plugin::GD::Image</td>
+ <td>(Any)</td>
+ <td>Graphical Reports</td>
+</tr>
+<tr>
+ <td>GD::Text</td>
+ <td>(Any)</td>
+ <td>Graphical Reports</td>
+</tr>
+<tr>
+ <td>GD::Graph</td>
+ <td>(Any)</td>
+ <td>Graphical Reports</td>
+</tr>
+<tr>
+ <td>GD</td>
+ <td>1.20</td>
+ <td>Graphical Reports, New Charts, Old Charts</td>
+</tr>
+<tr>
+ <td>Email::MIME::Attachment::Stripper</td>
+ <td>(Any)</td>
+ <td>Inbound Email</td>
+</tr>
+<tr>
+ <td>Email::Reply</td>
+ <td>(Any)</td>
+ <td>Inbound Email</td>
+</tr>
+<tr>
+ <td>Net::LDAP</td>
+ <td>(Any)</td>
+ <td>LDAP Authentication</td>
+</tr>
+<tr>
+ <td>HTML::Parser</td>
+ <td>3.40</td>
+ <td>More HTML in Product/Group Descriptions</td>
+</tr>
+<tr>
+ <td>HTML::Scrubber</td>
+ <td>(Any)</td>
+ <td>More HTML in Product/Group Descriptions</td>
+</tr>
+<tr>
+ <td>XML::Twig</td>
+ <td>(Any)</td>
+ <td>Move [% terms.Bugs %] Between Installations</td>
+</tr>
+<tr>
+ <td>MIME::Parser</td>
+ <td>5.406</td>
+ <td>Move [% terms.Bugs %] Between Installations</td>
+</tr>
+<tr>
+ <td>Chart::Base</td>
+ <td>1.0</td>
+ <td>New Charts, Old Charts</td>
+</tr>
+<tr>
+ <td>Image::Magick</td>
+ <td>(Any)</td>
+ <td>Optionally Convert BMP Attachments to PNGs</td>
+</tr>
+<tr>
+ <td>PatchReader</td>
+ <td>0.9.4</td>
+ <td>Patch Viewer</td>
+</tr>
+<tr>
+ <td class="req_new">Authen::Radius</td>
+ <td class="req_new">(Any)</td>
+ <td>RADIUS Authentication</td>
+</tr>
+<tr>
+ <td class="req_new">Authen::SASL</td>
+ <td class="req_new">(Any)</td>
+ <td>SMTP Authentication</td>
+</tr>
+<tr>
+ <td>SOAP::Lite</td>
+ <td>(Any)</td>
+ <td>XML-RPC Interface</td>
+</tr>
+<tr>
+ <td>mod_perl2</td>
+ <td>1.999022</td>
+ <td>mod_perl</td>
+</tr>
+</table>
+
+<h2 id="v32_feat">New Features and Improvements</h2>
+
+<ul>
+ <li><a href="#v32_feat_ui">Major UI Improvements</a></li>
+ <li><a href="#v32_feat_skin">New Default Skin: Dusk</a></li>
+ <li><a href="#v32_feat_status">Custom Status Workflow</a></li>
+ <li><a href="#v32_feat_fields">New Custom Field Types</a></li>
+ <li><a href="#v32_feat_install">Easier Installation</a></li>
+ <li><a href="#v32_feat_oracle">Experimental Oracle Support</a></li>
+ <li><a href="#v32_feat_utf8">Improved UTF-8 Support</a></li>
+ <li><a href="#v32_feat_grcons">Group Icons</a></li>
+ <li><a href="#v32_feat_other">Other Enhancements and Changes</a></li>
+</ul>
+
+<h3 id="v32_feat_ui">Major UI Improvements</h3>
+
+<p>[% terms.Bugzilla %] 3.2 has had some UI assistance from the NASA
+ Human-Computer Interaction department and the new
+ <a href="http://wiki.mozilla.org/Bugzilla:UE">[% terms.Bugzilla %]
+ User Interface Team</a>.</p>
+
+<p>In particular, you will notice a massively redesigned [% terms.bug %]
+ editing form, in addition to our <a href="#v32_feat_skin">new skin</a>.</p>
+
+<h3 id="v32_feat_skin">New Default Skin: Dusk</h3>
+
+<p>[% terms.Bugzilla %] 3.2 now ships with a skin called "Dusk" that is
+ a bit more colorful than old default "Classic" skin.</p>
+
+<p>Upgrading installations will still default to the "Classic"
+ skin--administrators can change the default in the Default Preferences
+ control panel. Users can also choose to use the old skin in their
+ Preferences (or using the View :: Page Style menu in Firefox).</p>
+
+<p>The changes that [% terms.Bugzilla %] required for Dusk made
+ [%+ terms.Bugzilla %] much easier to skin. See the
+ <a href="http://wiki.mozilla.org/Bugzilla:Addons#Skins">Addons page</a>
+ for additional skins, or try making your own!</p>
+
+<h3 id="v32_feat_status">Custom Status Workflow</h3>
+
+<p>You can now customize the list of statuses in [% terms.Bugzilla %],
+ and transitions between them.</p>
+
+<p>You can also specify that a comment must be made on certain transitions.</p>
+
+<h3 id="v32_feat_fields">New Custom Field Types</h3>
+
+<p>[% terms.Bugzilla %] 3.2 has support for three new types of
+ custom fields:</p>
+
+<ul>
+ <li>Large Text: Adds a multi-line textbox to your [% terms.bugs %].</li>
+ <li>Multiple Selection Box: Adds a box that allows you to choose
+ multiple items from a list.</li>
+ <li>Date/Time: Displays a date and time, along with a JavaScript
+ calendar popup to make picking a date easier.</li>
+</ul>
+
+<h3 id="v32_feat_install">Easier Installation</h3>
+
+<p>[% terms.Bugzilla %] now comes with a script called
+ <kbd>install-module.pl</kbd> that can automatically download
+ and install all of the required Perl modules for [% terms.Bugzilla %].
+ It stores them in a directory inside your [% terms.Bugzilla %]
+ installation, so you can use it even if you don't have administrator-level
+ access to your machine, and without modifying your main Perl install.</p>
+
+<p><kbd>checksetup.pl</kbd> will print out instructions for using
+ <kbd>install-module.pl</kbd>, or you can read its
+ <a href="[% docs_urlbase FILTER html %]api/install-module.html">documentation</a>.</p>
+
+<h3 id="v32_feat_oracle">Experimental Oracle Support</h3>
+
+<p>[% terms.Bugzilla %] 3.2 contains experimental support for using
+ Oracle as its database. Some features of [% terms.Bugzilla %] are known
+ to be broken on Oracle, but hopefully will be working by our next major
+ release.</p>
+
+<p>The [% terms.Bugzilla %] Project, as an open-source project, of course
+ does not recommend the use of proprietary database solutions. However,
+ if your organization requires that you use Oracle, this will allow
+ you to use [% terms.Bugzilla %]!</p>
+
+<p>The [% terms.Bugzilla %] Project thanks Oracle Corp. for their extensive
+ development contributions to [% terms.Bugzilla %] which allowed this to
+ happen!</p>
+
+<h3 id="v32_feat_utf8">Improved UTF-8 Support</h3>
+
+<p>[% terms.Bugzilla %] 3.2 now has advanced UTF-8 support in its code,
+ including correct handling for truncating and wrapping multi-byte
+ languages. Major issues with multi-byte or unusual languages
+ are now resolved, and [% terms.Bugzilla %] should now be usable
+ by users in every country with little (or at least much less)
+ customization.</p>
+
+<h3 id="v32_feat_grcons">Group Icons</h3>
+
+<p>Administrators can now specify that users who are in certain groups
+ should have an icon appear next to their name whenever they comment.
+ This is particularly useful for distinguishing developers from
+ [%+ terms.bug %] reporters.</p>
+
+<h3 id="v32_feat_other">Other Enhancements and Changes</h3>
+
+<p>These are either minor enhancements, or enhancements that have
+ very short descriptions. Some of these are very useful, though!</p>
+
+<h4>Enhancements For Users</h4>
+
+<ul>
+ <li><strong>[% terms.Bugs %]</strong>: You can now reassign
+ [%+ terms.abug %] at the same time as you are changing its status.</li>
+ <li><strong>[% terms.Bugs %]</strong>: When entering [% terms.abug %],
+ you will now see the description of a component when you select it.</li>
+ <li><strong>[% terms.Bugs %]</strong>: The [% terms.bug %] view now
+ contains some <a href="http://microformats.org/about/">Microformats</a>,
+ most notably for users' names and email addresses.</li>
+ <li><strong>[% terms.Bugs %]</strong>: You can now remove a QA Contact
+ from [% terms.abug %] simply by clearing the QA Contact field.</li>
+ <li><strong>[% terms.Bugs %]</strong>: There is now a user preference
+ that will allow you to exclude the quoted text when replying
+ to comments.</li>
+ <li><strong>[% terms.Bugs %]</strong>: You can now expand or collapse
+ individual comments in the [% terms.bug %] view.</li>
+
+ <li><strong>Attachments</strong>: There is now "mid-air collision"
+ protection when editing attachments.</li>
+ <li><strong>Attachments</strong>: Patches in the Diff Viewer now show
+ line numbers (<a href="https://bugzilla.mozilla.org/attachment.cgi?id=327546">Example</a>).</li>
+ <li><strong>Attachments</strong>: After creating or updating an attachment,
+ you will be immediately shown the [% terms.bug %] that the attachment
+ is on.</li>
+
+ <li><strong>Search</strong>: You can now reverse the sort of
+ [%+ terms.abug %] list by clicking on a column header again.</li>
+ <li><strong>Search</strong>: Atom feeds of [% terms.bug %] lists now
+ contain more fields.</li>
+ <li><strong>Search</strong>: QuickSearch now supports searching flags
+ and groups. It also now includes the OS field in the list of fields
+ it searches by default.</li>
+ <li><strong>Search</strong>: "Help" text can now appear on query.cgi
+ for Internet Explorer and other non-Firefox browsers. (It always
+ could appear for Firefox.)</li>
+
+ <li>[% terms.Bugzilla %] now ships with an icon that will show
+ up next to the URL in most browsers. If you want to replace it,
+ it's in <kbd>images/favicon.ico</kbd>.</li>
+
+ <li>You can now set the Deadline when using "Change Several
+ [%+ terms.Bugs %] At Once"</li>
+ <li><strong>Saved Searches</strong> now save their column list, so if
+ you customize the list of columns and save your search, it will
+ always contain those columns.</li>
+ <li><strong>Saved Searches</strong>: When you share a search, you can
+ now see how many users have subscribed to it, on
+ <kbd>userprefs.cgi</kbd>.</li>
+ <li><strong>Saved Searches</strong>: You can now see what group a
+ shared search was shared to, on the list of available shared searches
+ in <kbd>userprefs.cgi</kbd>.</li>
+ <li><strong>Flags</strong>: If your installation uses drop-down user
+ lists, the flag requestee box will now contain only users who are
+ actually allowed to take requests.</li>
+ <li><strong>Flags</strong>: If somebody makes a request to you, and you
+ change the requestee to somebody else, the requester is no longer set
+ to you. In other words, you can "redirect" requests and maintain the
+ original requester.</li>
+ <li><strong>Flags</strong>: Emails about flags now will thread properly
+ in email clients to be a part of [% terms.abug %]'s thread.</li>
+ <li>When using <kbd>email_in.pl</kbd>, you can now add users to the CC
+ list by just using <kbd>@cc</kbd> as the field name.</li>
+ <li>Many pages (particularly administrative pages) now contain links to
+ the relevant section of the [% terms.Bugzilla %] Guide, so you can read
+ the documentation for that page.</li>
+ <li>Dependency Graphs should render more quickly, as they now (by default)
+ only include the same [% terms.bugs %] that you'd see in the dependency
+ tree.</li>
+</ul>
+
+<h4>Enhancements For Administrators</h4>
+
+<ul>
+ <li><strong>Admin UI</strong>: Instead of having the Administration
+ Control Panel links in the footer, there is now just one link called
+ "Administration" that takes you to a page that links to all the
+ administrative controls for [% terms.Bugzilla %].</li>
+ <li><strong>Admin UI</strong>: Administrative pages no longer display
+ confirmation pages, instead they redirect you to some useful page
+ and display a message about what changed.</li>
+ <li><strong>Admin UI</strong>: The interface for editing group
+ inheritance in <kbd>editgroups.cgi</kbd> is much clearer now.</li>
+ <li><strong>Admin UI</strong>: When editing a user, you can now see
+ all the components where that user is the Default Assignee or Default
+ QA Contact.</li>
+
+ <li><strong>Email</strong>: For installations that use SMTP to send
+ mail (as opposed to Sendmail), [%+ terms.Bugzilla %] now supports
+ SMTP Authentication, so that it can log in to your mail server
+ before sending messages.</li>
+ <li><strong>Email</strong>: Using the "Test" mail delivery method now
+ creates a valid mbox file to make testing easier.</li>
+
+ <li><strong>Authentication</strong>: [% terms.Bugzilla %] now correctly
+ handles LDAP records which contain multiple email addresses. (The first
+ email address in the list that is a valid [% terms.Bugzilla %] account
+ will be used, or if this is a new user, the first email address in
+ the list will be used.)</li>
+ <li><strong>Authentication</strong>: [% terms.Bugzilla %] can now take
+ a list of LDAP servers to try in order until it gets a successful
+ connection.</li>
+ <li><strong>Authentication</strong>: [% terms.Bugzilla %] now supports
+ RADIUS authentication.</li>
+
+ <li><strong>Security</strong>: The login cookie is now created as
+ "HTTPOnly" so that it can't be read by possibly malicious scripts.
+ Also, if SSL is enabled on your installation, the login cookie is
+ now only sent over SSL connections.</li>
+ <li><strong>Security</strong>: The <code>ssl</code> parameter now protects
+ every page a logged-in user accesses, when set to "authenticated sessions."
+ Also, SSL is now enforced appropriately in the WebServices interface when
+ the parameter is set.</li>
+
+ <li><strong>Database</strong>: [% terms.Bugzilla %] now uses transactions in
+ the database instead of table locks. This should generally improve
+ performance with many concurrent users. It also means if there is
+ an unexpected error in the middle of a page, all database changes made
+ during that page will be rolled back.</li>
+ <li><strong>Database</strong>: You no longer have to set
+ <code>max_packet_size</code> in MySQL to add large attachments. However,
+ you may need to set it manually if you restore a mysqldump into your
+ database.</li>
+
+ <li>New WebService functions:
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/WebService/Bug.html">B<!-- -->ug.add_comment</a>
+ and <a href="[% docs_urlbase FILTER html %]api/Bugzilla/WebService/Bugzilla.html">Bugzilla.extensions</a>.</li>
+
+ <li>You can now delete custom fields, but only if they have never been
+ set on any [% terms.bug %].</li>
+ <li>There is now a <kbd>--reset-password</kbd> argument to
+ <kbd>checksetup.pl</kbd> that allows you to reset a user's password
+ from the command line.</li>
+ <li>There is now a script called <kbd>sanitycheck.pl</kbd> that you can
+ run from the command line. It works just like <kbd>sanitycheck.cgi</kbd>.
+ By default, it only outputs anything if there's an error, so it's
+ ideal for administrators who want to run it nightly in a cron job.</li>
+ <li>The <kbd>strict_isolation</kbd> parameter now prevents you from setting
+ users who cannot see [% terms.abug %] as a CC, Assignee, or QA
+ Contact. Previously it only prevented you from adding users who
+ could not <em>edit</em> the [% terms.bug %].</li>
+ <li>Extensions can now add their own headers to the HTML &lt;head&gt;
+ for things like custom CSS and so on.</li>
+ <li><kbd>sanitycheck.cgi</kbd> has been templatized, meaning that the
+ entire [% terms.Bugzilla %] UI is now contained in templates.</li>
+ <li>When setting the <kbd>sslbase</kbd> parameter, you can now specify
+ a port number in the URL.</li>
+ <li>When importing [% terms.bugs %] using <kbd>importxml.pl</kbd>,
+ attachments will have their actual creator set as their creator,
+ instead of the person who exported the [% terms.bug %] from the other
+ system.</li>
+ <li>The voting system is off by default in new installs. This is to
+ prepare for the fact that it will be moved into an extension at
+ some point in the future.</li>
+ <li>The <code>shutdownhtml</code> parameter now works even when
+ [%+ terms.Bugzilla %]'s database server is down.</li>
+</ul>
+
+<h3>Enhancements for Localizers (or Localized Installations)</h3>
+
+<ul>
+ <li>The documentation can now be localized--in other words, you can have
+ documentation installed for multiple languages at once and
+ [%+ terms.Bugzilla %] will link to the correct language in its internal
+ documentation links.</li>
+ <li>[% terms.Bugzilla %] no longer uses the <kbd>languages</kbd> parameter.
+ Instead it reads the <kbd>template/</kbd> directory to see which
+ languages are available.</li>
+ <li>Some of the messages printed by <kbd>checksetup.pl</kbd> can now
+ be localized. See <kbd>template/en/default/setup/strings.txt.pl</kbd>.
+</ul>
+
+<h2 id="v32_issues">Outstanding Issues</h2>
+
+<ul>
+ <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=423439">
+ [%- terms.Bug %] 423439</a>: Tabs in comments will be converted
+ to four spaces, due to a b<!-- -->ug in Perl as of Perl 5.8.8.</li>
+ <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=69621">
+ [%- terms.Bug %] 69621</a>: If you rename or remove a keyword that is
+ in use on [% terms.bugs %], you will need to rebuild the "keyword cache"
+ by running <a href="sanitycheck.cgi">sanitycheck.cgi</a> and choosing
+ the option to rebuild the cache when it asks. Otherwise keywords may
+ not show up properly in search results.</li>
+ <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=89822">
+ [%- terms.Bug %] 89822</a>: When changing multiple [% terms.bugs %] at
+ the same time, there is no "mid-air collision" protection.</li>
+ <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=276230">
+ [%- terms.Bug %] 276230</a>: The support for restricting access to
+ particular Categories of New Charts is not complete. You should treat
+ the 'chartgroup' Param as the only access mechanism available.<br>
+ However, charts migrated from Old Charts will be restricted to
+ the groups that are marked MANDATORY for the corresponding Product.
+ There is currently no way to change this restriction, and the
+ groupings will not be updated if the group configuration
+ for the Product changes.</li>
+ <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=370370">
+ [%- terms.Bug %] 370370</a>: mod_perl support is currently not
+ working on Windows machines.</li>
+</ul>
+
+<h2 id="v32_upgrading">How to Upgrade From An Older Version</h2>
+
+<h3 id="v32_upgrading_notes">Notes For Upgraders</h3>
+
+<ul>
+ <li>If you upgrade by CVS, the <kbd>extensions</kbd> and
+ <kbd>skins/contrib</kbd> directories are now in CVS instead of
+ being created by <kbd>checksetup.pl</kbd> If you do a <kbd>cvs update</kbd>
+ from 3.0, you will be told that your directories are "in the way" and
+ you should delete (or move) them and then do <kbd>cvs update</kbd>
+ again. Also, the <kbd>docs</kbd> directory has been restructured
+ and after you <kbd>cvs update</kbd> you can delete the <kbd>docs/html</kbd>,
+ <kbd>docs/pdf</kbd>, <kbd>docs/txt</kbd>, and <kbd>docs/xml</kbd>
+ directories.</li>
+ <li>If you are using MySQL, you should know that [% terms.Bugzilla %]
+ now uses InnoDB for all tables. <kbd>checksetup.pl</kbd> will convert
+ your tables automatically, but if you have InnoDB disabled,
+ the upgrade will not be able to complete (and <kbd>checksetup.pl</kbd>
+ will tell you so).</li>
+
+ <li><strong>You should also read the
+ <a href="#v30_upgrading_notes">[% terms.Bugzilla %] 3.0 Notes For Upgraders
+ section</a> of the
+ <a href="#v32_previous">previous release notes</a> if you are upgrading
+ from a version before 3.0.</strong></li>
+</ul>
+
+<h3>Steps For Upgrading</h3>
+
+<p>Once you have read the notes above, see the
+ <a href="[% docs_urlbase FILTER html %]upgrade.html">Upgrading
+ documentation</a> for instructions on how to upgrade.</p>
+
+<h2 id="v32_code_changes">Code Changes Which May Affect Customizations</h2>
+
+<ul>
+ <li><a href="#v32_code_hooks">More Hooks!</a></li>
+ <li><a href="#v32_code_search">Search.pm Rearchitecture</a></li>
+ <li><a href="#v32_code_lib">lib Directory</a></li>
+ <li><a href="#v32_code_other">Other Changes</a></li>
+</ul>
+
+<h3 id="v32_code_hooks">More Hooks!</h3>
+
+<p>There are more code hooks in 3.2 than there were in 3.0. See the
+ documentation of <a href="[% docs_urlbase FILTER html %]api/Bugzilla/Hook.html">Bugzilla::Hook</a>
+ for more details.</p>
+
+<h3 id="v32_code_search">Search.pm Rearchitecture</h3>
+
+<p><kbd>Bugzilla/Search.pm</kbd> has been heavily modified, to be much
+ easier to read and use. It contains mostly the same code as it did in
+ 3.0, but it has been moved around and reorganized significantly.</p>
+
+<h3 id="v32_code_lib">lib Directory</h3>
+
+<p>As part of implementing <a href="#v32_feat_install">install-module.pl</a>,
+ [%+ terms.Bugzilla %] was given a local <kbd>lib</kbd> directory which
+ it searches for modules, in addition to the standard system path.</p>
+
+<p>This means that all [% terms.Bugzilla %] scripts now start with
+ <code>use lib qw(. lib);</code> as one of the first lines.</p>
+
+<h3 id="v32_code_other">Other Changes</h3>
+
+<ul>
+ <li>You should now be using <code>get_status('NEW')</code> instead of
+ <code>status_descs.NEW</code> in templates.</li>
+ <li>The <code>[&#37;# version = 1.0 &#37;]</code> comment at the top of every
+ template file has been removed.</li>
+</ul>
+
+<h1 id="v32_previous">[% terms.Bugzilla %] 3.0.x Release Notes</h1>
+
+<h2>Table of Contents</h2>
+
+<ul class="bz_toc">
+ <li><a href="#v30_introduction">Introduction</a></li>
+ <li><a href="#v30_point">Updates In This 3.0.x Release</a></li>
+ <li><a href="#v30_req">Minimum Requirements</a></li>
+ <li><a href="#v30_feat">New Features and Improvements</a></li>
+ <li><a href="#v30_issues">Outstanding Issues</a></li>
+ <li><a href="#v30_security">Security Fixes In This Release</a></li>
+ <li><a href="#v30_upgrading">How to Upgrade From An Older Version</a></li>
+ <li><a href="#v30_code_changes">Code Changes Which May Affect
+ Customizations</a></li>
+ <li><a href="#v30_previous">Release Notes for Previous Versions</a></li>
+</ul>
+
+<h2 id="v30_introduction">Introduction</h2>
+
+<p>Welcome to [% terms.Bugzilla %] 3.0! It's been over eight years since
+ we released [% terms.Bugzilla %] 2.0, and everything has changed since
+ then. Even just since our previous release, [% terms.Bugzilla %] 2.22,
+ we've added a <em>lot</em> of new features. So enjoy the release, we're
+ happy to bring it to you.</p>
+
+<p>If you're upgrading, make sure to read <a href="#v30_upgrading">How to
+ Upgrade From An Older Version</a>. If you are upgrading from a release
+ before 2.22, make sure to read the release notes for all the
+ <a href="#v30_previous">previous versions</a> in between your version
+ and this one.</p>
+
+<h2 id="v30_point">Updates in this 3.0.x Release</h2>
+
+<p>This section describes what's changed in the most recent b<!-- -->ug-fix
+ releases of [% terms.Bugzilla %] after 3.0. We only list the
+ most important fixes in each release. If you want a detailed list of
+ <em>everything</em> that's changed in each version, you should use our
+ <a href="http://www.bugzilla.org/status/changes.html">Change Log Page</a>.</p>
+
+<h3>3.0.6</h3>
+
+<ul>
+ <li>Before 3.0.6, unexpected fatal WebService errors would result in
+ a <code>faultCode</code> that was a string instead of a number.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=446327">[% terms.Bug %] 446327</a>)</li>
+ <li>If you created a product or component with the same name as one you
+ previously deleted, it would fail with an error about the series table.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=247936">[% terms.Bug %] 247936</a>)</li>
+</ul>
+
+<p>See also the <a href="#v30_security">Security Advisory</a> section for
+ information about a security issue fixed in this release.</p>
+
+<h3>3.0.5</h3>
+
+<ul>
+ <li>If you don't have permission to set a flag, it will now appear
+ unchangeable in the UI.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=433851">[% terms.Bug %] 433851</a>)</li>
+ <li>If you were running mod_perl, [% terms.Bugzilla %] was not correctly
+ closing its connections to the database since 3.0.3, and so sometimes
+ the DB would run out of connections.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=441592">[% terms.Bug %] 441592</a>)</li>
+ <li>The installation script is now clear about exactly which
+ <code>Email::</code> modules are required in Perl, thus avoiding the
+ problem where emails show up with a body like
+ <samp>SCALAR(0xBF126795)</samp>.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=441541">[% terms.Bug %] 441541</a>)</li>
+ <li><a href="[% docs_urlbase FILTER html %]api/email_in.html">email_in.pl</a>
+ is no longer case-sensitive for values of <kbd>@product</kbd>.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=365697">[% terms.Bug %] 365697</a>)</li>
+</ul>
+
+<p>See also the <a href="#v30_security">Security Advisory</a> section for
+ information about security issues fixed in this release.</p>
+
+<h3>3.0.4</h3>
+
+<ul>
+ <li>[% terms.Bugzilla %] administrators were not being correctly notified
+ about new releases.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=414726">[% terms.Bug %] 414726</a>)</li>
+
+ <li>There could be extra whitespace in email subject lines.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=411544">[% terms.Bug %] 411544</a>)</li>
+
+ <li>The priority, severity, OS, and platform fields were always required by
+ the <kbd>B<!-- -->ug.create</kbd> WebService function, even if they had
+ defaults specified.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=384009">[% terms.Bug %] 384009</a>)</li>
+
+ <li>Better threading of [% terms.bug %]mail in some email clients.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=376453">[% terms.Bug %] 376453</a>)</li>
+
+ <li>There were many fixes to the Inbound Email Interface
+ (<kbd>email_in.pl</kbd>).
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=92274">[% terms.Bug %] 92274</a>,
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=377025">[% terms.Bug %] 377025</a>,
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=412943">[% terms.Bug %] 412943</a>,
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=413672">[% terms.Bug %] 413672</a>, and
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=431721">[% terms.Bug %] 431721</a>)</li>
+
+ <li><kbd>checksetup.pl</kbd> now handles UTF-8 conversion more reliably during upgrades.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=374951">[% terms.Bug %] 374951</a>)</li>
+
+ <li>Comments written in CJK languages are now correctly word-wrapped.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=388723">[% terms.Bug %] 388723</a>)</li>
+
+ <li>All emails will now be sent in the correct language, when the user
+ has chosen a language for emails.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=405946">[% terms.Bug %] 405946</a>)
+
+ <li>On Windows, temporary files created when uploading attachments are now
+ correctly deleted when the upload is complete.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=414002">[% terms.Bug %] 414002</a>)</li>
+
+ <li><kbd>checksetup.pl</kbd> now prints correct installation instructions
+ for Windows users using Perl 5.10.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=414430">[% terms.Bug %] 414430</a>)
+</ul>
+
+<p>See also the <a href="#v30_security">Security Advisory</a> section for
+ information about security issues fixed in this release.</p>
+
+<h3>3.0.3</h3>
+
+<ul>
+ <li>mod_perl no longer compiles [% terms.Bugzilla %]'s code for each Apache
+ process individually. It now compiles code only once and shares it among
+ each Apache process. This greatly improves performance and highly
+ decreases the memory footprint.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=398241">[% terms.Bug %] 398241</a>)</li>
+
+ <li>You can now search for '---' (without quotes) in versions and milestones.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=362436">[% terms.Bug %] 362436</a>)</li>
+
+ <li>[% terms.Bugzilla %] should no longer break lines unnecessarily in
+ email subjects. This was causing trouble with some email clients.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=374424">[% terms.Bug %] 374424</a>)</li>
+
+ <li>If you had selected "I'm added to or removed from this capacity" option
+ for the "CC" role in your email preferences, you wouldn't get mail when
+ more than one person was added to the CC list at once.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=394796">[% terms.Bug %] 394796</a>)</li>
+
+ <li>Deleting a user account no longer deletes whines from another user who
+ has the deleted account as addressee. The schedule is simply removed,
+ but the whine itself is left intact.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=395924">[% terms.Bug %] 395924</a>)</li>
+
+ <li><kbd>contrib/merge-users.pl</kbd> now correctly merges all required
+ fields when merging two user accounts.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=400160">[% terms.Bug %] 400160</a>)</li>
+
+ <li>[% terms.Bugzilla %] no longer requires Apache::DBI to run under
+ mod_perl. It caused troubles such as lost connections with the DB and
+ didn't give any important performance gain.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=408766">[% terms.Bug %] 408766</a>)</li>
+</ul>
+
+<h3>3.0.2</h3>
+
+<ul>
+ <li>[% terms.Bugzilla %] should now work on Perl 5.9.5 (and thus the
+ upcoming Perl 5.10.0).
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=390442">[% terms.Bug %] 390442</a>)</li>
+</ul>
+
+<p>See also the <a href="#v30_security">Security Advisory</a> section for
+ information about an important security issue fixed in this release.</p>
+
+<h3>3.0.1</h3>
+
+<ul>
+ <li>For users of Firefox 2, the <code>show_bug.cgi</code> user interface
+ should no longer "collapse" after you modify [% terms.abug %].
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=370739">[% terms.Bug %] 370739</a>)</li>
+ <li>If you can bless a group, and you share a saved search with that
+ group, it will no longer automatically appear in all of that group's
+ footers unless you specifically request that it automatically appear
+ in their footers.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=365890">[% terms.Bug %] 365890</a>)</li>
+ <li>There is now a parameter to allow users to perform searches without
+ any search terms. (In other words, to search for just a Product
+ and Status on the Simple Search page.) The parameter is called
+ <code>specific_search_allow_empty_words</code>.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=385910">[% terms.Bug %] 385910</a>)</li>
+ <li>If you attach a file that has a MIME-type of <code>text/x-patch</code>
+ or <code>text/x-diff</code>, it will automatically be treated as a
+ patch by [% terms.Bugzilla %].
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=365756">[% terms.Bug %] 365756</a>)</li>
+ <li>Dependency Graphs now work correctly on all mod_perl installations.
+ There should now be no remaining signficant problems with running
+ [%+ terms.Bugzilla %] under mod_perl.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=370398">[% terms.Bug %] 370398</a>)</li>
+ <li>If moving [% terms.abug %] between products would remove groups
+ from the [% terms.bug %], you are now warned.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=303183">[% terms.Bug %] 303183</a>)</li>
+ <li>On IIS, whenever [% terms.Bugzilla %] threw a warning, it would
+ actually appear on the web page. Now warnings are suppressed,
+ unless you have a file in the <code>data</code> directory called
+ <code>errorlog</code>, in which case warnings will be printed there.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=390148">[% terms.Bug %] 390148</a>)</li>
+ <li>If you used <kbd>email_in.pl</kbd> to edit [% terms.abug %] that was
+ protected by groups, all of the groups would be cleared.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=385453">[% terms.Bug %] 385453</a>)</li>
+ <li>PostgreSQL users: New Charts were failing to collect data over time.
+ They will now start collecting data correctly.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=257351">[% terms.Bug %] 257351</a>)</li>
+ <li>Some flag mails didn't specify who the requestee was.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=379787">[% terms.Bug %] 379787</a>)</li>
+ <li>Instead of throwing real errors, <kbd>collectstats.pl</kbd> would
+ just say that it couldn't find <code>ThrowUserError</code>.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=380709">[% terms.Bug %] 380709</a>)</li>
+ <li>Logging into [% terms.Bugzilla %] from the home page works again
+ with IIS5.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=364008">[% terms.Bug %] 364008</a>)</li>
+ <li>If you were using SMTP for sending email, sometimes emails would
+ be missing the <code>Date</code> header.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=304999">[% terms.Bug %] 304999</a>).</li>
+ <li>In the XML-RPC WebService, <code>B<!-- -->ug.legal_values</code> now
+ correctly returns values for custom fields if you request values
+ for custom fields.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=381737">[% terms.Bug %] 381737</a>)</li>
+ <li>The "[% terms.Bug %]-Writing Guidelines" page has been shortened
+ and re-written.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=378590">[% terms.Bug %] 378590</a>)</li>
+ <li>If your <code>urlbase</code> parameter included a port number,
+ like <code>www.domain.com:8080</code>, SMTP might have failed.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=384501">[% terms.Bug %] 384501</a>)</li>
+ <li>For SMTP users, there is a new parameter, <code>smtp_debug</code>.
+ Turning on this parameter will log the full information about
+ every SMTP session to your web server's error log, to help with
+ debugging issues with SMTP.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=384497">[% terms.Bug %] 384497</a>)</li>
+ <li>If you are a "global watcher" (you get all mails from every
+ [%+ terms.bug %]), you can now see that in your Email Preferences.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=365302">[% terms.Bug %] 365302</a>)</li>
+ <li>The Status and Resolution of [% terms.bugs %] are now correctly
+ localized in CSV search results.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=389517">[% terms.Bug %] 389517</a>)</li>
+ <li>The "Subject" line of an email was being mangled if it contained
+ non-Latin characters.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=387860">[% terms.Bug %] 387860</a>)</li>
+ <li>Editing the "languages" parameter using <kbd>editparams.cgi</kbd> would
+ sometimes fail, causing [% terms.Bugzilla %] to throw an error.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=335354">[% terms.Bug %] 335354</a>)</li>
+</ul>
+
+<h2 id="v30_req">Minimum Requirements</h2>
+
+<p>Any requirements that are new since 2.22 will look like
+ <span class="req_new">this</span>.</p>
+
+<ul>
+ <li><a href="#v30_req_perl">Perl</a></li>
+ <li><a href="#v30_req_mysql">For MySQL Users</a></li>
+ <li><a href="#v30_req_pg">For PostgreSQL Users</a></li>
+ <li><a href="#v30_req_modules">Required Perl Modules</a></li>
+ <li><a href="#v30_req_optional_mod">Optional Perl
+ Modules</a></li>
+</ul>
+
+
+<h3 id="v30_req_perl">Perl</h3>
+
+<ul>
+ <li>Perl <span class="req_new">v<strong>5.8.0</strong></span> (non-Windows
+ platforms)</li>
+ <li>Perl v<strong>5.8.1</strong> (Windows platforms)</li>
+</ul>
+
+<h3 id="v30_req_mysql">For MySQL Users</h3>
+
+<ul>
+ <li>MySQL <span class="req_new">v4.1.2</span></li>
+ <li><strong>perl module:</strong> DBD::mysql v2.9003</li>
+</ul>
+
+<h3 id="v30_req_pg">For PostgreSQL Users</h3>
+
+<ul>
+ <li>PostgreSQL v8.00.0000</li>
+ <li><strong>perl module:</strong> DBD::Pg v1.45</li>
+</ul>
+
+<h3 id="v30_req_modules">Required Perl Modules</h3>
+
+<table class="req_table" border="0" cellspacing="0" cellpadding="0">
+ <tr>
+ <th>Module</th> <th>Version</th>
+ </tr>
+ <tr><td>CGI</td> <td>2.93</td>
+ </tr>
+ <tr>
+ <td>Date::Format</td> <td>2.21</td>
+ </tr>
+ <tr>
+ <td>DBI</td>
+ <td class="req_new">1.41</td>
+ </tr>
+ <tr>
+ <td>File::Spec</td> <td>0.84</td>
+ </tr>
+ <tr>
+ <td>Template</td> <td>2.12</td>
+ </tr>
+ <tr>
+ <td class="req_new">Email::Send</td>
+ <td class="req_new">2.00</td>
+ </tr>
+ <tr>
+ <td>Email::MIME</td>
+ <td>1.861</td>
+ </tr>
+ <tr>
+ <td class="req_new">Email::MIME::Modifier</td>
+ <td class="req_new">1.442</td>
+ </tr>
+</table>
+
+<h3 id="v30_req_optional_mod">Optional Perl Modules</h3>
+
+<p>The following perl modules, if installed, enable various
+ features of [% terms.Bugzilla %]:</p>
+
+<table class="req_table" border="0" cellspacing="0" cellpadding="0">
+ <tr>
+ <th>Module</th> <th>Version</th>
+ <th>Enables Feature</th>
+ </tr>
+ <tr>
+ <td class="req_new">LWP::UserAgent</td>
+ <td class="req_new">(Any)</td>
+ <td>Automatic Update Notifications</td>
+ </tr>
+ <tr>
+ <td>Template::Plugin::GD::Image</td>
+ <td>(Any)</td>
+ <td>Graphical Reports</td>
+ </tr>
+ <tr>
+ <td>GD::Graph</td>
+ <td>(Any)</td>
+ <td>Graphical Reports</td>
+ </tr>
+ <tr>
+ <td>GD::Text</td>
+ <td>(Any)</td>
+ <td>Graphical Reports</td>
+ </tr>
+ <tr>
+ <td>GD</td>
+ <td>1.20</td>
+ <td>Graphical Reports, New Charts, Old Charts</td>
+ </tr>
+ <tr>
+ <td class="req_new">Email::MIME::Attachment::Stripper</td>
+ <td class="req_new">(Any)</td>
+ <td>Inbound Email</td>
+ </tr>
+ <tr>
+ <td class="req_new">Email::Reply</td>
+ <td class="req_new">(Any)</td>
+ <td>Inbound Email</td>
+ </tr>
+ <tr>
+ <td>Net::LDAP</td>
+ <td>(Any)</td>
+ <td>LDAP Authentication</td>
+ </tr>
+ <tr>
+ <td>HTML::Parser</td>
+ <td>3.40</td>
+ <td>More HTML in Product/Group Descriptions</td>
+ </tr>
+ <tr>
+ <td>HTML::Scrubber</td>
+ <td>(Any)</td>
+ <td>More HTML in Product/Group Descriptions</td>
+ </tr>
+ <tr>
+ <td>XML::Twig</td>
+ <td>(Any)</td>
+ <td>Move [% terms.Bugs %] Between Installations</td>
+ </tr>
+ <tr>
+ <td>MIME::Parser</td>
+ <td>5.406</td>
+ <td>Move [% terms.Bugs %] Between Installations</td>
+ </tr>
+ <tr>
+ <td>Chart::Base</td>
+ <td>1.0</td>
+ <td>New Charts, Old Charts</td>
+ </tr>
+ <tr>
+ <td>Image::Magick</td>
+ <td>(Any)</td>
+ <td>Optionally Convert BMP Attachments to PNGs</td>
+ </tr>
+ <tr>
+ <td>PatchReader</td>
+ <td>0.9.4</td>
+ <td>Patch Viewer</td>
+ </tr>
+ <tr>
+ <td class="req_new">SOAP::Lite</td>
+ <td class="req_new">(Any)</td>
+ <td>XML-RPC Interface</td>
+ </tr>
+ <tr>
+ <td class="req_new">mod_perl2</td>
+ <td class="req_new">1.999022</td>
+ <td>mod_perl</td>
+ </tr>
+ <tr>
+ <td> CGI</td>
+ <td>3.11</td>
+ <td>mod_perl</td>
+ </tr>
+</table>
+
+<h2 id="v30_feat">New Features and Improvements</h2>
+
+<ul>
+ <li><a href="#v30_feat_cf">Custom Fields</a></li>
+ <li><a href="#v30_feat_mp">mod_perl Support</a></li>
+ <li><a href="#v30_feat_sq">Shared Saved Searches</a></li>
+ <li>
+ <a href="#v30_feat_afn">Attachments and Flags on New [% terms.Bugs %]</a>
+ </li>
+ <li><a href="#v30_feat_cr">Custom Resolutions</a></li>
+ <li><a href="#v30_feat_ppp">Per-Product Permissions</a></li>
+ <li><a href="#v30_feat_ui">User Interface Improvements</a></li>
+ <li><a href="#v30_feat_xml">XML-RPC Interface</a></li>
+ <li><a href="#v30_feat_skin">Skins</a></li>
+ <li><a href="#v30_feat_sbu">Unchangeable Fields Appear
+ Unchangeable</a></li>
+ <li><a href="#v30_feat_et">All Emails in Templates</a></li>
+ <li><a href="#v30_feat_df">No More Double-Filed [% terms.Bugs %]</a></li>
+ <li><a href="#v30_feat_cc">Default CC List for Components</a></li>
+ <li><a href="#v30_feat_emi">File/Modify [% terms.Bugs %] By Email</a></li>
+ <li><a href="#v30_feat_gw">Users Who Get All [% terms.Bug %]
+ Notifications</a></li>
+ <li><a href="#v30_feat_utf8">Improved UTF-8 Support</a></li>
+ <li><a href="#v30_feat_upda">Automatic Update Notification</a></li>
+ <li><a href="#v30_feat_welc">Welcome Page for New Installs</a></li>
+ <li><a href="#v30_feat_other">Other Enhancements and Changes</a></li>
+</ul>
+
+<h3 id="v30_feat_cf">Custom Fields</h3>
+
+<p>[% terms.Bugzilla %] now includes very basic support for custom fields.</p>
+
+<p>Users in the <kbd>admin</kbd> group can add plain-text or drop-down
+ custom fields. You can edit the values available for drop-down fields
+ using the &quot;Field Values&quot; control panel.</p>
+
+<p>Don't add too many custom fields! It can make [% terms.Bugzilla %]
+ very difficult to use. Try your best to get along with the default
+ fields, and then if you find that you can't live without custom fields
+ after a few weeks of using [% terms.Bugzilla %], only then should you
+ start your custom fields.</p>
+
+<h3 id="v30_feat_mp">mod_perl Support</h3>
+
+<p>[% terms.Bugzilla %] 3.0 supports mod_perl, which allows for extremely
+ enhanced page-load performance. mod_perl trades memory usage for performance,
+ allowing near-instantaneous page loads, but using much more memory.</p>
+
+<p>If you want to enable mod_perl for your [% terms.Bugzilla %], we recommend
+ a minimum of 1.5GB of RAM, and for a site with heavy traffic, 4GB to 8GB.</p>
+
+<p>If performance isn't that critical on your installation, you don't
+ have the memory, or you are running some other web server than
+ Apache, [% terms.Bugzilla %] still runs perfectly as a normal CGI
+ application, as well.</p>
+
+<h3 id="v30_feat_sq">Shared Saved Searches</h3>
+
+<p>Users can now choose to &quot;share&quot; their saved searches
+ with a certain group. That group will then be able to
+ &quot;subscribe&quot; to those searches, and have them appear
+ in their footer.</p>
+
+<p>If the sharer can &quot;bless&quot; the group he's sharing to,
+ (that is, if he can add users to that group), it's considered
+ that he's a manager of that group, and his queries show up
+ automatically in that group's footer (although they can
+ unsubscribe from any particular search, if they want.)</p>
+
+<p>In order to allow a user to share their queries, they also
+ have to be a member of the group specified in the
+ <code>querysharegroup</code> parameter.</p>
+
+<p>Users can control their shared and subscribed queries from
+ the &quot;Preferences&quot; screen.</p>
+
+<h3 id="v30_feat_afn">Attachments and Flags on New [% terms.Bugs %]</h3>
+
+<p>You can now add an attachment while you are filing a new
+ [%+ terms.bug %].</p>
+
+<p>You can also set flags on the [% terms.bug %] and on attachments, while
+ filing a new [% terms.bug %].</p>
+
+<h3 id="v30_feat_cr">Custom Resolutions</h3>
+
+<p>You can now customize the list of resolutions available
+ in [% terms.Bugzilla %], including renaming the default resolutions.</p>
+
+<p>The resolutions <code>FIXED</code>, <code>DUPLICATE</code>
+ and <code>MOVED</code> have a special meaning to [% terms.Bugzilla %],
+ though, and cannot be renamed or deleted.</p>
+
+<h3 id="v30_feat_ppp">Per-Product Permissions</h3>
+
+<p>You can now grant users <kbd>editbugs</kbd> and <kbd>canconfirm</kbd>
+ for only certain products. You can also grant users <kbd>editcomponents</kbd>
+ on a product, which means they will be able to edit that product
+ including adding/removing components and other product-specific
+ controls.</p>
+
+<h3 id="v30_feat_ui">User Interface Improvements</h3>
+
+<p>There has been some work on the user interface for [% terms.Bugzilla %] 3.0,
+ including:</p>
+
+<ul>
+ <li>There is now navigation and a search box a the <em>top</em> of
+ each page, in addition to the bar at the bottom of the page.</li>
+ <li>A re-designed &quot;Format for Printing&quot; page for
+ [%+ terms.bugs %].</li>
+ <li>The layout of <kbd>show_bug.cgi</kbd> (the [% terms.bug %] editing
+ page) has been changed, and the attachment table has been redesigned.</li>
+</ul>
+
+<h3 id="v30_feat_xml">XML-RPC Interface</h3>
+
+<p>[% terms.Bugzilla %] now has a Web Services interface using the XML-RPC
+ protocol. It can be accessed by external applications by going
+ to the <kbd>xmlrpc.cgi</kbd> on your installation.</p>
+
+<p>Documentation can be found in the
+ <a href="[% docs_urlbase FILTER html %]api/">[% terms.Bugzilla %]
+ API Docs</a>, in the various <kbd>Bugzilla::WebService</kbd> modules.</p>
+
+<h3 id="v30_feat_skin">Skins</h3>
+
+<p>[% terms.Bugzilla %] can have multiple &quot;skins&quot; installed,
+ and users can pick between them. To write a skin, you just have to
+ write several CSS files. See the <a href="[% docs_urlbase FILTER html %]cust-skins.html">Custom
+ Skins Documentation</a> for more details.</p>
+
+<p>We currently don't have any alternate skins shipping with
+ [%+ terms.Bugzilla %]. If you write an alternate skin, please
+ let us know!</p>
+
+<h3 id="v30_feat_sbu">Unchangeable Fields Appear Unchangeable</h3>
+
+<p>As long as you are logged in, when viewing [% terms.abug %], if you
+ cannot change a field, it will not look like you can change it. That
+ is, the value will just appear as plain text.</p>
+
+<h3 id="v30_feat_et">All Emails in Templates</h3>
+
+<p>All outbound emails are now controlled by the templating system.
+ What used to be the <code>passwordmail</code>, <code>whinemail</code>,
+ <code>newchangedmail</code> and <code>voteremovedmail</code>
+ parameters are now all templates in the <kbd>template/</kbd> directory.</p>
+
+<p>This means that it's now much easier to customize your outbound
+ emails, and it's also possible for localizers to have more
+ localized emails as part of their language packs, if they want.</p>
+
+<p>We also added a <code>mailfrom</code> parameter to let you set
+ who shows up in the <code>From</code> field on all emails that
+ [%+ terms.Bugzilla %] sends.</p>
+
+<h3 id="v30_feat_df">No More Double-Filed [% terms.Bugs %]</h3>
+
+<p>Users of [% terms.Bugzilla %] will sometimes accidentally submit
+ [%+ terms.abug %] twice, either by going back in their web browser,
+ or just by refreshing a page. In the past, this could file the same
+ [%+ terms.bug %] twice (or even three times) in a row, irritating
+ developers and confusing users.</p>
+
+<p>Now, if you try to submit [% terms.abug %] twice from the same screen
+ (by going back or by refreshing the page), [% terms.Bugzilla %] will warn
+ you about what you're doing, before it actually submits the duplicate
+ [%+ terms.bug %].</p>
+
+<h3 id="v30_feat_cc">Default CC List for Components</h3>
+
+<p>You can specify a list of users who will <em>always</em> be added to
+ the CC list of new [% terms.bugs %] in a component.</p>
+
+<h3 id="v30_feat_emi">File/Modify [% terms.Bugs %] By Email</h3>
+
+<p>You can now file or modify [% terms.bugs %] via email. Previous versions
+ of [% terms.Bugzilla %] included this feature only as an
+ unsupported add-on, but it is now an official interface to
+ [%+ terms.Bugzilla %].</p>
+
+<p>For more details see the <a href="[% docs_urlbase FILTER html %]api/email_in.html">documentation
+ for email_in.pl</a>.</p>
+
+<h3 id="v30_feat_gw">Users Who Get All [% terms.Bug %] Notifications</h3>
+
+<p>There is now a parameter called <kbd>globalwatchers</kbd>. This
+ is a comma-separated list of [% terms.Bugzilla %] users who will
+ get all [% terms.bug %] notifications generated by [% terms.Bugzilla %].</p>
+
+<p>Group controls still apply, though, so users who can't see [% terms.abug %]
+ still won't get notifications about that [% terms.bug %].</p>
+
+<h3 id="v30_feat_utf8">Improved UTF-8 Support</h3>
+
+<p>[% terms.Bugzilla %] users running MySQL should now have excellent
+ UTF-8 support if they turn on the <kbd>utf8</kbd> parameter. (New
+ installs have this parameter on by default.) [% terms.Bugzilla %]
+ now correctly supports searching and sorting in non-English languages,
+ including multi-bytes languages such as Chinese.</p>
+
+<h3 id="v30_feat_upda">Automatic Update Notification</h3>
+
+<p>If you belong to the <kbd>admin</kbd> group, you will be notified
+ when you log in if there is a new release of [% terms.Bugzilla %]
+ available to download.</p>
+
+<p>You can control these notifications by changing the
+ <kbd>upgrade_notification</kbd> parameter.</p>
+
+<p>If your [% terms.Bugzilla %] installation is on a machine that needs to go
+ through a proxy to access the web, you may also have to set the
+ <kbd>proxy_url</kbd> parameter.</p>
+
+<h3 id="v30_feat_welc">Welcome Page for New Installs</h3>
+
+<p>When you log in for the first time on a brand-new [% terms.Bugzilla %]
+ installation, you will be presented with a page that describes
+ where you should go from here, and what parameters you should set.</p>
+
+<h3 id="v30_feat_qs">QuickSearch Plugin for IE7 and Firefox 2</h3>
+
+<p>Firefox 2 users and Internet Explorer 7 users will be presented
+ with the option to add [% terms.Bugzilla %] to their search bar.
+ This uses the
+ <a href="page.cgi?id=quicksearch.html">QuickSearch syntax</a>.</p>
+
+<h3 id="v30_feat_other">Other Enhancements and Changes</h3>
+
+<p>These are either minor enhancements, or enhancements that have
+ very short descriptions. Some of these are very useful, though!</p>
+
+<h4>Enhancements That Affect [% terms.Bugzilla %] Users</h4>
+
+<ul>
+ <li>In comments, quoted text (lines that start with <kbd>&gt;</kbd>)
+ will be a different color from normal text.</li>
+ <li>There is now a user preference that will add you to the CC list
+ of any [% terms.bug %] you modify. Note that it's <strong>on</strong>
+ by default.</li>
+ <li>[% terms.Bugs %] can now be filed with an initial state of
+ <kbd>ASSIGNED</kbd>, if you are in the <kbd>editbugs</kbd> group.</li>
+ <li>By default, comment fields will zoom large when you are typing in them,
+ and become small when you move out of them. You can disable this
+ in your user preferences.</li>
+ <li>You can hide obsolete attachments on [% terms.abug %] by clicking
+ &quot;Hide Obsolete&quot; at the bottom of the attachment table.</li>
+ <li>If [% terms.abug %] has flags set, and you move it to a different
+ product that has flags with the same name, the flags will be
+ preserved.</li>
+ <li>You now can't request a flag to be set by somebody who can't set it
+ ([% terms.Bugzilla %] will throw an error if you try).</li>
+ <li>Many new headers have been added to outbound [% terms.Bugzilla %]
+ [%+ terms.bug %] emails: <code>X-Bugzilla-Status</code>,
+ <code>X-Bugzilla-Priority</code>, <code>X-Bugzilla-Assigned-To</code>,
+ <code>X-Bugzilla-Target-Milestone</code>, and
+ <code>X-Bugzilla-Changed-Fields</code>, <code>X-Bugzilla-Who</code>.
+ You can look at an email to get an idea of what they contain.</li>
+ <li>In addition to the old <code>X-Bugzilla-Reason</code> email header
+ which tells you why you got an email, if you got an email because
+ you were watching somebody, there is now an
+ <code>X-Bugzilla-Watch-Reason</code> header that tells you who you
+ were watching and what role they had.</li>
+ <li>If you hover your mouse over a full URL (like
+ <code>http://bugs.mycompany.com/show_bug.cgi?id=1212</code>) that
+ links to [% terms.abug %], you will see the title of the
+ [%+ terms.bug %]. Of course, this only works for [% terms.bugs %] in your
+ [%+ terms.Bugzilla %] installation.</li>
+ <li>If your installation has user watching enabled, you will now see
+ the users that you can remove from your watch-list as a multi-select
+ box, much like the current CC list. (Previously it was just a text
+ box.)</li>
+ <li>When a user creates their own account in [% terms.Bugzilla %], the
+ account is now not actually created until they verify their email
+ address by clicking on a link that is emailed to them.</li>
+ <li>You can change [% terms.abug %]'s resolution without reopening it.</li>
+ <li>When you view the dependency tree on [% terms.abug %], resolved
+ [%+ terms.bugs %] will be hidden by default. (In previous versions,
+ resolved [% terms.bugs %] were shown by default.)</li>
+ <li>When viewing [% terms.bug %] activity, fields that hold [% terms.bug %]
+ numbers (such as &quot;Blocks&quot;) will have the [% terms.bug %] numbers
+ displayed as links to those [% terms.bugs %].</li>
+ <li>When viewing the &quot;Keywords&quot; field in [% terms.abug %] list,
+ it will be sorted alphabetically, so you can sanely sort a list on
+ that field.</li>
+ <li>In most places, the Version field is now sorted using a version-sort
+ (so 1.10 is greater than 1.2) instead of an alphabetical sort.</li>
+ <li>Options for flags will only appear if you can set them. So, for
+ example, if you can't grant <kbd>+</kbd> on a flag, that option
+ won't appear for you.</li>
+ <li>You can limit the product-related output of <kbd>config.cgi</kbd>
+ by specifying a <kbd>product=</kbd> URL argument, containing the name
+ of a product. You can specify the argument more than once for multiple
+ products.</li>
+ <li>You can now search the boolean charts on whether or not a comment
+ is private.</li>
+</ul>
+
+<h4>Enhancements For Administrators</h4>
+
+<ul>
+ <li>Administrators can now delete attachments, making them disappear
+ entirely from [% terms.Bugzilla %].</li>
+ <li><kbd>sanitycheck.cgi</kbd> can now only be accessed by users
+ in the <kbd>editcomponents</kbd> group.</li>
+ <li>The &quot;Field Values&quot; control panel can now only be accessed
+ by users in the <kbd>admin</kbd> group. (Previously it was accessible
+ to anybody in the <kbd>editcomponents</kbd> group.)</li>
+ <li>There is a new parameter <kbd>announcehtml</kbd>, that will allow
+ you to enter some HTML that will be displayed at the top of every
+ page, as an announcement.</li>
+ <li>The <kbd>loginnetmask</kbd> parameter now defaults to 0 for new
+ installations, meaning that as long as somebody has the right
+ login cookie, they can log in from any IP address. This makes
+ life a lot easier for dial-up users or other users whose IP
+ changes a lot. This could be done because the login cookie is now
+ very random, and thus secure.</li>
+ <li>Classifications now have sortkeys, so they can be sorted in an
+ order that isn't alphabetical.</li>
+ <li>Authentication now supports LDAP over SSL (LDAPS) or TLS (using
+ the STARTLS command) in addition to plain LDAP.</li>
+ <li>LDAP users can have their LDAP username be their email address,
+ instead of having the LDAP <kbd>mail</kbd> attribute be their
+ email address. You may wish to set the <kbd>emailsuffix</kbd>
+ parameter if you do this.</li>
+ <li>Administrators can now see what has changed in a user account,
+ when using the &quot;Users&quot; control panel.</li>
+ <li><code>REMIND</code> and <code>LATER</code> are no longer part
+ of the default list of resolutions. Upgrading installations will
+ not be affected--they will still have these resolutions.</li>
+ <li><kbd>editbugs</kbd> is now the default for the <kbd>timetrackinggroup</kbd>
+ parameter, meaning that time-tracking will be on by default in a new
+ installation.</li>
+</ul>
+
+<h2 id="v30_issues">Outstanding Issues</h2>
+
+<ul>
+ <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=69621">
+ [%- terms.Bug %] 69621</a>: If you rename or remove a keyword that is
+ in use on [% terms.bugs %], you will need to rebuild the "keyword cache"
+ by running <a href="sanitycheck.cgi">sanitycheck.cgi</a> and choosing
+ the option to rebuild the cache when it asks. Otherwise keywords may
+ not show up properly in search results.</li>
+ <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=99215">
+ [%- terms.Bug %] 99215</a>: Flags are not protected by "mid-air
+ collision" detection. Nor are any attachment changes.</li>
+ <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=89822">
+ [%- terms.Bug %] 89822</a>: When changing multiple [% terms.bugs %] at
+ the same time, there is no "mid-air collision" protection.</li>
+ <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=276230">
+ [%- terms.Bug %] 276230</a>: The support for restricting access to
+ particular Categories of New Charts is not complete. You should treat
+ the 'chartgroup' Param as the only access mechanism available.<br>
+ However, charts migrated from Old Charts will be restricted to
+ the groups that are marked MANDATORY for the corresponding Product.
+ There is currently no way to change this restriction, and the
+ groupings will not be updated if the group configuration
+ for the Product changes.</li>
+ <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=370370">
+ [%- terms.Bug %] 370370</a>: mod_perl support is currently not
+ working on Windows machines.</li>
+ <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=361149">
+ [%- terms.Bug %] 361149</a>: If you are using Perl 5.8.0, you may
+ get a lot of warnings in your Apache error_log about "deprecated
+ pseudo-hashes." These are harmless--they are a b[%# fool test %]ug in
+ Perl 5.8.0. Perl 5.8.1 and later do not have this problem.</li>
+ <li>[% terms.Bugzilla %] 3.0rc1 allowed custom field column names in
+ the database to be mixed-case. [% terms.Bugzilla %] 3.0 only allows
+ lowercase column names. It will fix any column names that you have
+ made mixed-case, but if you have custom fields that previously were
+ mixed-case in any Saved Search, you will have to re-create that Saved
+ Search yourself.</li>
+</ul>
+
+<h2 id="v30_security">Security Updates in This Release</h2>
+
+<h3>3.0.6</h3>
+
+<p>[% terms.Bugzilla %] contains a minor security fix. For details, see the
+ <a href="http://www.bugzilla.org/security/2.20.6/">Security Advisory</a>.</p>
+
+<h3>3.0.5</h3>
+
+<p>[% terms.Bugzilla %] contains one security fix for
+ <a href="[% docs_urlbase FILTER html %]api/importxml.html">importxml.pl</a>.
+ For details, see the
+ <a href="http://www.bugzilla.org/security/2.22.4/">Security Advisory</a>.</p>
+
+<h3>3.0.4</h3>
+
+<p>[% terms.Bugzilla %] 3.0.4 contains three security fixes.
+ For details, see the
+ <a href="http://www.bugzilla.org/security/2.20.5/">Security Advisory</a>.</p>
+
+<h3>3.0.3</h3>
+
+<p>No security fixes in this release.</p>
+
+<h3>3.0.2</h3>
+
+<p>[% terms.Bugzilla %] 3.0.1 had an important security fix that is
+ critical for public installations with "requirelogin" turned on.
+ For details, see the
+ <a href="http://www.bugzilla.org/security/3.0.1/">Security Advisory</a></p>
+
+<h3>3.0.1</h3>
+
+<p>[% terms.Bugzilla %] 3.0 had three security issues that have been
+ fixed in this release: one minor information leak, one hole only
+ exploitable by an admin or using <code>email_in.pl</code>, and one in an
+ uncommonly-used template. For details, see the
+ <a href="http://www.bugzilla.org/security/2.20.4/">Security Advisory</a>.</p>
+
+<h2 id="v30_upgrading">How to Upgrade From An Older Version</h2>
+
+<h3 id="v30_upgrading_notes">Notes For Upgraders</h3>
+
+<ul>
+ <li>If you upgrade by CVS, there are several .cvsignore files
+ that are now in CVS instead of being locally created by
+ <kbd>checksetup.pl</kbd>. This means that you will have to
+ delete those files when CVS tells you there's a conflict, and
+ then run <kbd>cvs update</kbd> again.</li>
+ <li>In this version of [% terms.Bugzilla %], the Summary field
+ is now limited to 255 characters. When you upgrade, any Summary
+ longer than that will be truncated, and the old summary will be
+ preserved in a comment.</li>
+ <li>If you have the <kbd>utf8</kbd> parameter turned on, at some
+ point you will have to convert your database. <kbd>checksetup.pl</kbd>
+ will tell you when this is, and it will give you certain instructions
+ at that time, that you have to follow before you can complete
+ the upgrade. Don't do the conversion yourself manually--follow
+ the instructions of <kbd>checksetup.pl</kbd>.</li>
+ <li>If you ever ran 2.23.3, 2.23.4, or 3.0rc1, you will have to run
+ <kbd>./collectstats.pl --regenerate</kbd> at the command line, because
+ the data for your Old Charts is corrupted. This can take several days,
+ so you may only want to run it if you use Old Charts.</li>
+ <li>You should also read the Outstanding Issues sections of
+ <a href="#v30_previous">older release notes</a> if you are upgrading
+ from a version lower than 2.22.</li>
+</ul>
+
+<h3>Steps For Upgrading</h3>
+
+<p>Once you have read the notes above, see the
+ <a href="[% docs_urlbase FILTER html %]upgrade.html">Upgrading
+ documentation</a> for instructions on how to upgrade.</p>
+
+<h2 id="v30_code_changes">Code Changes Which May Affect Customizations</h2>
+
+<ul>
+ <li><a href="#v30_code_loc"><strong>Packagers:</strong> Location
+ Variables Have Moved</a></li>
+ <li><a href="#v30_code_hooks">Hooks!</a></li>
+ <li><a href="#v30_code_api">API Documentation</a></li>
+ <li><a href="#v30_code_globals">Elimination of globals.pl</a></li>
+ <li><a href="#v30_code_scope">Cleaned Up Variable Scoping Issues</a></li>
+ <li><a href="#v30_code_sql">No More SendSQL</a></li>
+ <li><a href="#v30_code_auth">Auth Re-write</a></li>
+ <li><a href="#v30_code_obj">Bugzilla::Object</a></li>
+ <li><a href="#v30_code_req">Bugzilla-&gt;request_cache</a></li>
+ <li><a href="#v30_code_other">Other Changes</a></li>
+</ul>
+
+<h3 id="v30_code_loc"><strong>Packagers:</strong> Location Variables
+ Have Moved</h3>
+
+<p>In previous versions of [% terms.Bugzilla %], <kbd>Bugzilla::Config</kbd>
+ held all the paths for different things, such as the path to localconfig
+ and the path to the <kbd>data/</kbd> directory.</p>
+
+<p>Now, all of this data is stored in a subroutine,
+ <kbd>Bugzilla::Constants::bz_locations</kbd>.</p>
+
+<p>Also, note that for mod_perl, <kbd>bz_locations</kbd> must return
+ <em>absolute</em> (not relative) paths. There is already code in that
+ subroutine to help you with this.</p>
+
+<h3 id="v30_code_hooks">Hooks!</h3>
+
+<p>[% terms.Bugzilla %] now supports a code hook mechanism. See the
+ documentation for
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/Hook.html">Bugzilla::Hook</a>
+ for more details.</p>
+
+<p>This gives [% terms.Bugzilla %] very advanced plugin support. You can
+ hook templates, hook code, add new parameters, and use the XML-RPC
+ interface. So we'd like to see some [% terms.Bugzilla %] plugins
+ written! Let us know on the <a href="http://bugzilla.org/cgi-bin/mj_wwwusr?func=lists-long-full&amp;extra=developers">developers&#64;bugzilla.org</a>
+ mailing list if you write a plugin.</p>
+
+<p>If you need more hooks, please
+ <a href="http://www.bugzilla.org/developers/reporting_bugs.html">File a b<!-- -->ug</a>!</p>
+
+<h3 id="v30_code_api">API Documentation</h3>
+
+<p>[% terms.Bugzilla %] now ships with all of its perldoc built
+ as HTML. Go ahead and read the
+ <a href="[% docs_urlbase FILTER html %]api/">API Documentation</a>
+ for all of the [% terms.Bugzilla %] modules now! Even scripts like
+ <kbd>checksetup.pl</kbd> have HTML documentation.</p>
+
+<h3 id="v30_code_globals">Elimination of globals.pl</h3>
+
+<p>The old file <kbd>globals.pl</kbd> has been eliminated.
+ Its code is now in various modules. Each function went to the module
+ that was appropriate for it.</p>
+
+<p>Usually we filed [% terms.abug %] in
+ <a href="https://bugzilla.mozilla.org">bugzilla.mozilla.org</a> for
+ each function we moved. You can search there for the old name of
+ the function, and that should get you the information about what
+ it's called now and where it lives.</p>
+
+<h3 id="v30_code_scope">Cleaned Up Variable Scoping Issues</h3>
+
+<p>In normal perl, you can have code like this:</p>
+<pre>my $var = 0;
+sub y { $var++ }</pre>
+
+<p>However, under mod_perl that doesn't work. So variables are no
+ longer &quot;shared&quot; with subroutines--instead all variables
+ that a subroutine needs must be declared inside the subroutine itself.</p>
+
+<h3 id="v30_code_sql">No More SendSQL</h3>
+
+<p>The old <kbd>SendSQL</kbd> function and all of its companions are
+ <strong>gone</strong>. Instead, we now use DBI for all database
+ interaction.</p>
+
+<p>For more information about how to use
+ <a href="http://search.cpan.org/perldoc?DBI">DBI</a> with
+ [%+ terms.Bugzilla %], see the
+ <a href="http://www.bugzilla.org/docs/developer.html#sql-sendreceive">Developer's
+ Guide Section About DBI</a></p>
+
+<h3 id="v30_code_auth">Auth Re-write</h3>
+
+<p>The <kbd>Bugzilla::Auth</kbd> family of modules have been completely
+ re-written. For details on how the new structure of authentication,
+ read the
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/Auth.html">Bugzilla::Auth
+ API docs</a>.</p>
+
+<p>It should be very easy to write new authentication plugins, now.</p>
+
+<h3 id="v30_code_obj">Bugzilla::Object</h3>
+
+<p>There is a new base class for most of our objects,
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/Object.html">Bugzilla::Object</a>.
+ It makes it really easy to create new objects based on things that are
+ in the database.</p>
+
+<h3 id="v30_code_req">Bugzilla-&gt;request-cache</h3>
+
+<p><kbd>Bugzilla.pm</kbd> used to cache things like the database
+ connection in package-global variables (like <kbd>$_dbh</kbd>).
+ That doesn't work in mod_perl, so instead now there's a hash
+ that can be accessed through <code>Bugzilla-&gt;request_cache</code>
+ to store things for the rest of the current page request.</p>
+
+<p>You shouldn't access <code>Bugzilla-&gt;request_cache</code> directly,
+ but you should use it inside of <kbd>Bugzilla.pm</kbd> if you modify
+ that. The only time you should be accessing it directly is if you need
+ to reset one of the caches. Hash keys are always named after the function
+ that they cache, so to reset the template object, you'd do:
+ <code>delete Bugzilla-&gt;request_cache-&gt;{template};</code>.</p>
+
+<h3 id="v30_code_other">Other Changes</h3>
+
+<ul>
+ <li><kbd>checksetup.pl</kbd> has been completely re-written, and most
+ of its code moved into modules in the <kbd>Bugzilla::Install</kbd>
+ namespace. See the
+ <a href="[% docs_urlbase FILTER html %]api/checksetup.html">checksetup
+ documentation</a> and <a href="https://bugzilla.mozilla.org/showdependencytree.cgi?id=277502&amp;hide_resolved=0">[% terms.Bugzilla %]
+ [%+ terms.bug %] 277502</a> for details.</li>
+ <li>Instead of <kbd>UserInGroup()</kbd>, all of [% terms.Bugzilla %] now
+ uses <kbd>Bugzilla-&gt;user-&gt;in_group</kbd></li>
+ <li>mod_perl doesn't like dependency loops in modules, so we now have
+ a test for that detects dependency loops in modules when you run
+ <kbd>runtests.pl</kbd>.</li>
+ <li><kbd>globals.pl</kbd> used to modify the environment variables,
+ like <kbd>PATH</kbd>. That now happens in <kbd>Bugzilla.pm</kbd>.</li>
+ <li>Templates can now link to the documentation more easily.
+ See the <kbd>global/code-error.html.tmpl</kbd> and
+ <kbd>global/user-error.html.tmpl</kbd> templates for examples.
+ (Search for &quot;docslinks.&quot;)</li>
+ <li>Parameters are accessed through <kbd>Bugzilla-&gt;params</kbd>
+ instead of using the <kbd>Param()</kbd> function, now.</li>
+ <li>The variables from the <kbd>localconfig</kbd> file are accessed
+ through the <code>Bugzilla-&gt;localconfig</code> hash instead of through
+ <kbd>Bugzilla::Config</kbd>.</li>
+ <li><kbd>Bugzilla::BugMail::MessageToMTA()</kbd> has moved into its
+ own module, along with other mail-handling code, called
+ <kbd>Bugzilla::Mailer</kbd></li>
+ <li>The <kbd>CheckCanChangeField()</kbd> subroutine in
+ <kbd>process_bug.cgi</kbd> has been moved to <kbd>Bugzilla::Bug</kbd>,
+ and is now a method of [% terms.abug %] object.</li>
+ <li>The code that used to be in the <kbd>global/banner.html.tmpl</kbd>
+ template is now in <kbd>global/header.html.tmpl</kbd>. The banner
+ still exists, but the file is empty.</li>
+</ul>
+
+<h2 id="v30_previous">Release Notes For Previous Versions</h2>
+
+<p>Release notes for versions of [% terms.Bugzilla %] for versions
+ prior to 3.0 are only available in text format:
+ <a href="[% docs_urlbase FILTER remove('html/$') FILTER html %]rel_notes.txt">Release Notes for [% terms.Bugzilla %] 2.22
+ and Earlier</a>.</p>
+
+[% INCLUDE global/footer.html.tmpl %]
+
+[% BLOCK db_req %]
+ [% SET m = DB_MODULE.$db %]
+ <h3 id="v40_req_[% db FILTER html %]">For [% m.name FILTER html %] Users</h3>
+
+ <ul>
+ <li>[% m.name FILTER html %]
+ [%+ '<span class="req_new">' IF db_new %]v[% m.db_version FILTER html %]
+ [% '</span>' IF db_new %]
+ </li>
+ <li><strong>perl module:</strong>
+ [%+ m.dbd.module FILTER html %]
+ [% '<span class="req_new">' IF dbd_new %]v[% m.dbd.version FILTER html %]
+ [% '</span>' IF dbd_new %]</li>
+ </ul>
+[% END %]
+
+
+[% BLOCK req_table %]
+ <table class="req_table" border="0" cellspacing="0" cellpadding="0">
+ <tr>
+ <th>Module</th> <th>Version</th>
+ [% IF include_feature %]
+ <th>Enables Feature</th>
+ [% END %]
+ </tr>
+ [% FOREACH req = reqs %]
+ <tr>
+ <td [% ' class="req_new"' IF new.contains(req.package) %]>
+ [%- req.module FILTER html %]</td>
+ <td [% ' class="req_new"' IF updated.contains(req.package)
+ OR new.contains(req.package) %]>
+ [%- IF req.version == 0 %]
+ (Any)
+ [% ELSE %]
+ [%- req.version FILTER html %]
+ [% END %]
+ </td>
+ [% IF include_feature %]
+ <td>[% req.feature.join(', ') FILTER html %]</td>
+ [% END %]
+ </tr>
+ [% END %]
+</table>
+[% END %]
diff --git a/template/en/default/pages/sudo.html.tmpl b/template/en/default/pages/sudo.html.tmpl
new file mode 100644
index 000000000..c790ff1ab
--- /dev/null
+++ b/template/en/default/pages/sudo.html.tmpl
@@ -0,0 +1,68 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 2005 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): A. Karl Kornel <karl@kornel.name>
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+[% INCLUDE global/header.html.tmpl title = "sudo: User Impersonation" %]
+
+<p>
+ [%+ terms.Bugzilla %] includes the ability to have one user impersonate
+another, in something called a <i>sudo session</i>, so long as the person
+doing the impersonating has the appropriate privileges.
+</p>
+
+<p>
+ While a session is in progress, [% terms.Bugzilla %] will act as if the
+ impersonated user is doing everything. This is especially useful for testing,
+ and for doing critical work when the impersonated user is unavailable. The
+ impersonated user will receive an email from [% terms.Bugzilla %] when the
+ session begins; they will not be told anything else.
+</p>
+
+<p>
+ To use this feature, you must be a member of the appropriate group. The group
+ includes all administrators by default. Other users, and members of other
+ groups, can be given access to this feature on a case-by-case basis. To
+ request access, contact the maintainer of this installation:
+ <a href="mailto:[% Param("maintainer") %]">
+ [%- Param("maintainer") %]</a>.
+</p>
+
+<p>
+ If you would like to be protected from impersonation, you should contact the
+ maintainer of this installation to see if that is possible. People with
+ access to this feature are protected automatically.
+</p>
+
+<p id="message">
+ [% IF user.in_group('bz_sudoers') %]
+ You are a member of the <b>bz_sudoers</b> group. You may use this
+ feature to impersonate others.
+ [% ELSE %]
+ You are not a member of an appropriate group. You may not use this
+ feature.
+ [% END %]
+ [% IF user.in_group('bz_sudo_protect') %]
+ <br>
+ You are a member of the <b>bz_sudo_protect</b> group. Other people will
+ not be able to use this feature to impersonate you.
+ [% END %]
+</p>
+
+[% INCLUDE global/footer.html.tmpl %]
diff --git a/template/en/default/reports/chart.csv.tmpl b/template/en/default/reports/chart.csv.tmpl
new file mode 100644
index 000000000..f9e2f2b39
--- /dev/null
+++ b/template/en/default/reports/chart.csv.tmpl
@@ -0,0 +1,44 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ #%]
+
+[% colsepchar = user.settings.csv_colsepchar.value %]
+
+[% data = chart.data %]
+Date\Series
+[% FOREACH label = chart.labels %]
+ [% colsepchar %][% label FILTER csv %]
+[% END %]
+[%# The data, which is in the correct format for GD, is conceptually the wrong
+ # way round for CSV output. So, we need to invert it here, which is why
+ # these loops aren't just plain FOREACH.
+ #%]
+[% i = 0 %]
+[% WHILE i < data.0.size %]
+ [% j = 0 %]
+ [% WHILE j < data.size %]
+ [% IF j > 0 %]
+ [% colsepchar %]
+ [% END %]
+ [% data.$j.$i %]
+ [% j = j + 1 %]
+ [% END %]
+ [% i = i + 1 %]
+
+[% END %]
diff --git a/template/en/default/reports/chart.html.tmpl b/template/en/default/reports/chart.html.tmpl
new file mode 100644
index 000000000..e14744d31
--- /dev/null
+++ b/template/en/default/reports/chart.html.tmpl
@@ -0,0 +1,67 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ #%]
+
+[%# INTERFACE:
+ #%]
+
+[% DEFAULT width = 600
+ height = 350
+%]
+
+[% time = time FILTER time('%Y-%m-%d %H:%M:%S') FILTER html %]
+
+[% PROCESS global/header.html.tmpl
+ title = "Chart"
+ header_addl_info = time
+%]
+
+<div align="center">
+
+ [% imageurl = BLOCK %]chart.cgi?
+ [% imagebase FILTER html %]&amp;ctype=png&amp;action=plot&amp;width=
+ [% width %]&amp;height=[% height -%]
+ [% END %]
+
+ <img alt="Graphical report results" src="[% imageurl %]"
+ width="[% width %]" height="[% height %]">
+ <p>
+ [% sizeurl = BLOCK %]chart.cgi?
+ [% imagebase FILTER html %]&amp;action=wrap
+ [% END %]
+ <a href="[% sizeurl %]&amp;width=[% width %]&amp;height=
+ [% height + 100 %]">Taller</a><br>
+ <a href="[% sizeurl %]&amp;width=[% width - 100 %]&amp;height=
+ [% height %]">Thinner</a> *
+ <a href="[% sizeurl %]&amp;width=[% width + 100 %]&amp;height=
+ [% height %]">Fatter</a>&nbsp;&nbsp;&nbsp;&nbsp;<br>
+ <a href="[% sizeurl %]&amp;width=[% width %]&amp;height=
+ [% height - 100 %]">Shorter</a><br>
+ </p>
+
+ <p>
+ <a href="chart.cgi?
+ [% imagebase FILTER html %]&amp;ctype=csv&amp;action=plot">CSV</a> |
+ <a href="chart.cgi?[% imagebase FILTER html %]&amp;action=assemble">Edit
+ this chart</a>
+ </p>
+
+</div>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/reports/chart.png.tmpl b/template/en/default/reports/chart.png.tmpl
new file mode 100644
index 000000000..c4fa04f66
--- /dev/null
+++ b/template/en/default/reports/chart.png.tmpl
@@ -0,0 +1,60 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ #%]
+
+[% y_label = "$terms.Bugs" %]
+[% x_label = "Time" %]
+
+[% IF chart.cumulate %]
+ [% USE graph = GD.Graph.area(width, height) %]
+ [% graph.set(cumulate => "true") %]
+[% ELSE %]
+ [% USE graph = GD.Graph.lines(width, height) %]
+[% END %]
+
+[% FILTER null;
+ x_label_skip = (30 * chart.data.0.size / width);
+
+ graph.set(x_label => x_label,
+ y_label => y_label,
+ y_tick_number => 8,
+ y_max_value => chart.y_max_value,
+ x_label_position => 0.5,
+ x_labels_vertical => 1,
+ x_label_skip => x_label_skip,
+ legend_placement => "RT",
+ line_width => 2,
+ dclrs => ["lred", "lgreen", "lblue", "lyellow",
+ "lpurple", "lorange", "black", "green",
+ "blue", "dpink", "lbrown", "gray",
+ "red", "dpurple", "gold", "marine"]);
+
+ # Workaround for the fact that set_legend won't take chart.labels directly,
+ # because chart.labels is an array reference rather than an array.
+ graph.set_legend(chart.labels.0, chart.labels.1, chart.labels.2,
+ chart.labels.3, chart.labels.4, chart.labels.5,
+ chart.labels.6, chart.labels.7, chart.labels.8,
+ chart.labels.9, chart.labels.10, chart.labels.11,
+ chart.labels.12, chart.labels.13, chart.labels.14,
+ chart.labels.15);
+
+ graph.plot(chart.data).png | stdout(1);
+ END;
+-%]
+
diff --git a/template/en/default/reports/components.html.tmpl b/template/en/default/reports/components.html.tmpl
new file mode 100644
index 000000000..2b7d1516e
--- /dev/null
+++ b/template/en/default/reports/components.html.tmpl
@@ -0,0 +1,104 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Bradley Baetz <bbaetz@student.usyd.edu.au>
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[%# INTERFACE:
+ # product: object. The product for which we want to display component
+ # descriptions.
+ #%]
+
+[% title = BLOCK %]
+ Components for [% product.name FILTER html %]
+[% END %]
+
+[% PROCESS global/header.html.tmpl
+ style_urls = [ "skins/standard/reports.css" ]
+ title = title
+%]
+
+[% IF Param("useqacontact") %]
+ [% numcols = 3 %]
+[% ELSE %]
+ [% numcols = 2 %]
+[% END %]
+
+<table cellpadding="0" cellspacing="0" id="components_header_table">
+ <tr>
+ <td class="instructions">
+ Select a component to see open [% terms.bugs %] in that component:
+ </td>
+ <td class="product_container">
+ <h2 class="product_name">[% product.name FILTER html %]</h2>
+ <div class="product_desc">
+ [% product.description FILTER html_light %]
+ </div>
+ </td>
+ </tr>
+</table>
+
+<h3 class="components_header">Components</h3>
+
+<table class="component_table" cellspacing="0" cellpadding="0">
+ <thead>
+ <tr>
+ <th>&nbsp;</th>
+ <th>Default Assignee</th>
+ [% IF Param("useqacontact") %]
+ <th>Default QA Contact</th>
+ [% END %]
+ </tr>
+ </thead>
+
+ <tbody>
+ [% FOREACH comp = product.components %]
+ [% INCLUDE describe_comp %]
+ [% END %]
+ </tbody>
+</table>
+
+[% PROCESS global/footer.html.tmpl %]
+
+[%############################################################################%]
+[%# BLOCK for components %]
+[%############################################################################%]
+
+[% BLOCK describe_comp %]
+ <tr id="[% comp.name FILTER html %]">
+ <td rowspan="2" class="component_name">
+ <a href="buglist.cgi?product=
+ [%- product.name FILTER url_quote %]&amp;component=
+ [%- comp.name FILTER url_quote %]&amp;resolution=---">
+ [% comp.name FILTER html %]</a>
+ </td>
+ <td class="component_assignee">
+ [% INCLUDE global/user.html.tmpl who = comp.default_assignee %]
+ </td>
+ [% IF Param("useqacontact") %]
+ <td class="component_qa_contact">
+ [% INCLUDE global/user.html.tmpl who = comp.default_qa_contact %]
+ </td>
+ [% END %]
+ </tr>
+ <tr>
+ <td colspan="[% numcols - 1 %]" class="component_description">
+ [% comp.description FILTER html_light %]
+ </td>
+ </tr>
+[% END %]
diff --git a/template/en/default/reports/create-chart.html.tmpl b/template/en/default/reports/create-chart.html.tmpl
new file mode 100644
index 000000000..e2b6090fe
--- /dev/null
+++ b/template/en/default/reports/create-chart.html.tmpl
@@ -0,0 +1,271 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ #%]
+
+[%# INTERFACE:
+ # chart: Chart object representing the currently assembled chart.
+ # category: hash (keyed by category) of hashes (keyed by subcategory) of
+ # hashes (keyed by name), with value being the series_id of the
+ # series. Contains details of all series the user can see.
+ #%]
+
+[% PROCESS global/header.html.tmpl
+ title = "Create Chart"
+%]
+
+[% PROCESS "reports/series-common.html.tmpl"
+ donames = 1
+%]
+
+<script type="text/javascript">
+[%# This function takes necessary action on selection of a subcategory %]
+function subcatSelected() {
+ var cat = document.chartform.category.value;
+ var subcat = document.chartform.subcategory.value;
+ var names = series[cat][subcat];
+
+ var namewidget = document.chartform.name;
+
+ namewidget.options.length = 0;
+ var i = 0;
+
+ for (x in names) {
+ namewidget.options[i] = new Option(x, names[x]);
+ i++;
+ }
+
+ namewidget.disabled = false;
+ namewidget.options[0].selected = true;
+
+ checkNewState();
+}
+</script>
+
+[% gttext = "Grand Total" %]
+
+<form method="get" action="chart.cgi" name="chartform">
+
+ <table cellpadding="2" cellspacing="2" border="0">
+ [% IF NOT category OR category.size == 0 %]
+ <tr>
+ <td>
+ <i>No data sets exist, or none are visible to you.</i>
+ </td>
+ </tr>
+ [% ELSE %]
+ <tr>
+ <th><label for="category">Category</label>:</th>
+ <th></th>
+ <th><label for="subcategory">Sub-category</label>:</th>
+ <th></th>
+ <th><label for="name" accesskey="N">Name</label>:</th>
+ <th><br>
+ </th>
+ </tr>
+ <tr>
+
+ [% PROCESS series_select sel = { name => 'category',
+ size => 5,
+ onchange = "catSelected();
+ subcatSelected();" } %]
+
+ <td>
+ <noscript>
+ <input type="submit" name="action-assemble" value="Update --&gt;"
+ id="action-assemble">
+ </noscript>
+ </td>
+
+ [% PROCESS series_select sel = { name => 'subcategory',
+ size => 5,
+ onchange = "subcatSelected()" } %]
+
+ <td>
+ <noscript>
+ <input type="submit" name="action-assemble" value="Update --&gt;"
+ id="action-assemble2">
+ </noscript>
+ </td>
+
+ [% PROCESS series_select sel = { name => 'name',
+ size => 5,
+ multiple => 1,
+ # We want to use the series ID as value,
+ # not its name.
+ value_in_hash => 1 } %]
+
+ <td align="center" valign="middle">
+ <input type="submit" name="action-add" value="Add To List"
+ id="action-add"><br>
+ </td>
+ </tr>
+ [% END %]
+ </table>
+
+ <h3>List Of Data Sets To Plot</h3>
+
+ [% IF chart.lines.size %]
+ <table cellspacing="2" cellpadding="2">
+ <tr>
+ <th style="width: 5em;">Select</th>
+ <th>Label</th>
+ <th></th>
+ <th>Data Set</th>
+ <th></th>
+ </tr>
+
+ [%# The external loop has two counters; one which keeps track of where we
+ # are in the old labels array, and one which keeps track of the new
+ # indexes for the form elements. They are different if chart.lines has
+ # empty slots in it.
+ #%]
+ [% labelidx = 0 %]
+ [% newidx = 0 %]
+
+ [% FOREACH line = chart.lines %]
+ [% IF NOT line %]
+ [%# chart.lines has an empty slot, so chart.labels will too. We
+ # increment labelidx only to keep the labels in sync with the data.
+ #%]
+ [% labelidx = labelidx + 1 %]
+ [% NEXT %]
+ [% END %]
+
+ [% FOREACH series = line %]
+ <tr>
+ [% IF loop.first %]
+ <td align="center" rowspan="[% line.size %]">
+ <input type="checkbox" value="1" name="select[% newidx %]">
+ </td>
+ <td rowspan="[% line.size %]">
+ <input type="text" size="20" name="label[% newidx %]"
+ value="[% (chart.labels.$labelidx OR series.name)
+ FILTER html %]">
+ </td>
+ [% END %]
+
+ <td>
+ [% "{" IF line.size > 1 %]
+ </td>
+
+ <td>
+ [% series.category FILTER html %] /
+ [%+ series.subcategory FILTER html %] /
+ [%+ series.name FILTER html %]
+ <input type="hidden" name="line[% newidx %]"
+ value="[% series.series_id %]">
+ </td>
+
+ <td align="center">
+ [% IF user.id == series.creator_id OR user.in_group("admin") %]
+ <a href="chart.cgi?action=edit&amp;series_id=
+ [% series.series_id %]">Edit</a> |
+ <a href="chart.cgi?action=confirm-delete&amp;series_id=
+ [%- series.series_id %]">Delete</a> |
+ [% END %]
+ <a href="buglist.cgi?cmdtype=dorem&amp;namedcmd=
+ [% series.category FILTER url_quote %]%20/%20
+ [% series.subcategory FILTER url_quote %]%20/%20
+ [% series.name FILTER url_quote -%]&amp;series_id=
+ [% series.series_id %]&amp;remaction=runseries">Run Search</a>
+ </td>
+ </tr>
+ [% END %]
+ [% labelidx = labelidx + 1 %]
+ [% newidx = newidx + 1 %]
+ [% END %]
+
+ [% IF chart.gt %]
+ <tr>
+ <td align="center">
+ <input type="checkbox" value="1" name="select65536">
+ <input type="hidden" value="1" name="gt">
+ </td>
+ <td>
+ <input type="text" size="20" name="labelgt"
+ value="[% (chart.labelgt OR gttext) FILTER html %]">
+ </td>
+ <td></td>
+ <td>
+ <i>[% gttext FILTER html %]</i>
+ </td>
+ <td></td>
+ </tr>
+ [% END %]
+ <tr>
+ <td colspan="6">&nbsp;</td>
+ </tr>
+
+ <tr>
+ <td valign="bottom" style="text-align: center;">
+ <input type="submit" name="action-sum" value="Sum"
+ style="width: 5em;" id="action-sum"><br>
+ <input type="submit" name="action-remove" value="Remove"
+ style="width: 5em;" id="action-remove">
+ </td>
+
+ <td style="text-align: right; vertical-align: bottom;">
+ <label for="cumulate"><b>Cumulate</b></label>:
+ <input type="checkbox" name="cumulate" id="cumulate" value="1"
+ [% " checked" IF chart.cumulate %]>
+ </td>
+
+ <td></td>
+ <td valign="bottom">
+ <label for="datefrom"><b>Date Range</b></label>:
+ <input type="text" size="12" name="datefrom" id="datefrom"
+ value="[% time2str("%Y-%m-%d", chart.datefrom) IF chart.datefrom%]">
+ <label for="dateto"><b>to</b></label>
+ <input type="text" size="12" name="dateto" id="dateto"
+ value="[% time2str("%Y-%m-%d", chart.dateto) IF chart.dateto %]">
+ </td>
+
+ <td style="text-align: right" valign="bottom">
+ <input type="submit" name="action-wrap" value="Chart This List"
+ id="action-wrap">
+ </td>
+ </tr>
+ </table>
+ [% ELSE %]
+ <p><i>None</i></p>
+ [% END %]
+</form>
+
+[% IF user.in_group('editbugs') %]
+ <h3>Create New Data Set</h3>
+ <p>
+ You can either create a new data set based on one of your saved searches
+ or start with a clean slate.
+ </p>
+
+ <form action="chart.cgi" id="create_series" name="create_series" method="GET">
+ <input type="hidden" name="action" value="convert_search">
+ <label for="series_from_search">Based on:</label>
+ <select id="series_from_search" name="series_from_search">
+ <option value="">(Clean slate)</option>
+ [% FOREACH q = user.queries %]
+ <option value="[% q.name FILTER html %]">[% q.name FILTER html %]</option>
+ [% END %]
+ </select>
+ <input id="submit_create" type="submit" value="Create a new data set">
+ </form>
+[% END %]
+
+[% PROCESS global/footer.html.tmpl %]
+
diff --git a/template/en/default/reports/delete-series.html.tmpl b/template/en/default/reports/delete-series.html.tmpl
new file mode 100644
index 000000000..5003551c7
--- /dev/null
+++ b/template/en/default/reports/delete-series.html.tmpl
@@ -0,0 +1,59 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Frédéric Buclin.
+ # Portions created by the Initial Developer are Copyright (C) 2009
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+
+[% series_name = BLOCK %]
+ [% series.category FILTER html %] /
+ [%+ series.subcategory FILTER html %] /
+ [%+ series.name FILTER html %]
+[% END %]
+
+[% PROCESS global/header.html.tmpl title = "Delete Series"
+ style_urls = ['skins/standard/admin.css'] %]
+
+<p>
+ You are going to completely remove the <b>[% series_name FILTER none %]</b> series
+ from the database. All data related to this series will be permanently deleted.
+</p>
+<p>
+ [% IF series.creator %]
+ This series has been created by <a href="mailto:[% series.creator.email FILTER html %]">
+ [% series.creator.email FILTER html %]</a>
+ [% ELSE %]
+ This series has been automatically created by [% terms.Bugzilla %]
+ [% END %]
+
+ [% IF series.public %]
+ and is public.
+ [% ELSIF series.creator %]
+ and is only visible by this user.
+ [% ELSE %]
+ and cannot be displayed by anybody.
+ [% END %]
+</p>
+
+<p class="areyoureallyreallysure">Are you sure you want to delete this series?</p>
+
+<p>
+ <a href="chart.cgi?action=delete&amp;series_id=[% series.series_id FILTER html %]&amp;token=
+ [%- issue_hash_token([series.id, series.name]) FILTER url_quote %]">Yes, delete</a> |
+ <a href="chart.cgi">No, go back to the charts page</a>
+</p>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/reports/duplicates-simple.html.tmpl b/template/en/default/reports/duplicates-simple.html.tmpl
new file mode 100644
index 000000000..a08522790
--- /dev/null
+++ b/template/en/default/reports/duplicates-simple.html.tmpl
@@ -0,0 +1,51 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s):
+ # Gervase Markham <gerv@gerv.net>
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[%# INTERFACE:
+ # You need to fulfill the interface to duplicates-table.html.tmpl.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ [% IF product.size %]
+ [% title = BLOCK %]
+ Most Frequently Reported [% terms.Bugs %] for [% product.join(', ') FILTER html %]
+ [% END %]
+ [% ELSE %]
+ [% title = "Most Frequently Reported $terms.Bugs" %]
+ [% END%]
+
+ <head>
+ <title>[% title FILTER html %]</title>
+ <link href="[% 'skins/standard/global.css' FILTER mtime %]"
+ rel="stylesheet" type="text/css">
+ <link href="[% 'skins/standard/duplicates.css' FILTER mtime %]"
+ rel="stylesheet" type="text/css">
+ </head>
+
+ <body>
+ [% PROCESS "reports/duplicates-table.html.tmpl" %]
+ </body>
+</html>
diff --git a/template/en/default/reports/duplicates-table.html.tmpl b/template/en/default/reports/duplicates-table.html.tmpl
new file mode 100644
index 000000000..38ab2d56b
--- /dev/null
+++ b/template/en/default/reports/duplicates-table.html.tmpl
@@ -0,0 +1,125 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s):
+ # Gervase Markham <gerv@gerv.net>
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[%# INTERFACE:
+ # bugs: list of hashes. May be empty. Each hash has three members:
+ # bug: A Bugzilla::Bug object
+ # count: integer. The number of dupes
+ # delta: integer. The change in count in the last $changedsince days
+ #
+ # bug_ids: list of integers. May be empty. The IDs of the bugs in $bugs.
+ #
+ # sortby: string. the column on which we are sorting the buglist.
+ # reverse: boolean. True if we are reversing the current sort.
+ # maxrows: integer. Max number of rows to display.
+ # changedsince: integer. The number of days ago for the changedsince column.
+ # openonly: boolean. True if we are only showing open bugs.
+ # product: array of strings. Restrict to these products only.
+ #%]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+[%# *** Column Headers *** %]
+
+[% SET columns = [
+ { name => "id", description => "$terms.Bug #" },
+ { name => "count", description => "Dupe<br>Count" },
+ { name => "delta",
+ description => "Change in last<br>$changedsince day(s)" },
+ { name => "component", description => field_descs.component },
+ { name => "bug_severity", description => field_descs.bug_severity },
+ { name => "op_sys", description => field_descs.op_sys },
+ { name => "target_milestone", description => field_descs.target_milestone },
+ { name => "short_desc", description => field_descs.short_desc },
+] %]
+
+[% SET base_args = [] %]
+[% FOREACH param = ['maxrows', 'openonly', 'format', 'sortvisible',
+ 'changedsince', 'product']
+%]
+ [% NEXT IF NOT ${param}.defined %]
+ [% FOREACH value = ${param} %]
+ [% filtered_value = value FILTER url_quote %]
+ [% base_args.push("$param=$filtered_value") %]
+ [% END %]
+[% END %]
+[% IF sortvisible %]
+ [% bug_ids_string = bug_ids.nsort.join(',') FILTER url_quote %]
+ [% base_args.push("bug_id=$bug_ids_string") %]
+[% END %]
+[% base_args_string = base_args.join('&amp;') %]
+
+[% IF bugs.size %]
+ <table id="duplicates_table" cellpadding="0" cellspacing="0">
+ <thead>
+ <tr>
+ [% FOREACH column = columns %]
+ [% IF column.name == sortby %]
+ [%# We add this to the column object so it doesn't affect future
+ # iterations of the loop.
+ #%]
+ [% column.reverse_sort = reverse ? 0 : 1 %]
+ [% END %]
+ <th class="[% column.name FILTER html %]">
+ <a href="duplicates.cgi?sortby=[% column.name FILTER url_quote %]
+ [% IF column.reverse_sort.defined %]
+ [%- %]&amp;reverse=[% column.reverse_sort FILTER url_quote %]
+ [% END %]
+ [% IF base_args_string %]
+ [% "&amp;$base_args_string" FILTER none %]
+ [% END %]"
+ >[% column.description FILTER none %]</a>
+ </th>
+ [% END %]
+ </tr>
+ </thead>
+
+ [%# *** Buglist *** %]
+
+ <tbody>
+ [% FOREACH item = bugs %]
+ [% SET bug = item.bug %]
+ <tr [% " class='resolved'" IF NOT bug.isopened %]>
+ <td class="id">
+ [% bug.id FILTER bug_link(bug) FILTER none %]
+ </td>
+ <td class="count">[% item.count FILTER html %]</td>
+ <td class="delta">[% item.delta FILTER html %]</td>
+ <td class="component">[% bug.component FILTER html %]</td>
+ <td class="bug_severity">
+ [%- display_value('bug_severity', bug.bug_severity) FILTER html %]
+ </td>
+ <td class="op_sys">
+ [%- display_value('op_sys', bug.op_sys) FILTER html %]
+ </td>
+ <td class="target_milestone">
+ [% display_value('target_milestone',
+ bug.target_milestone) FILTER html %]
+ </td>
+ <td class="short_desc">[% bug.short_desc FILTER html %]</td>
+ </tr>
+ [% END %]
+ </tbody>
+ </table>
+[% ELSE %]
+ <h3>No duplicate [% terms.bugs %] found.</h3>
+[% END %]
diff --git a/template/en/default/reports/duplicates.html.tmpl b/template/en/default/reports/duplicates.html.tmpl
new file mode 100644
index 000000000..ff1c271fe
--- /dev/null
+++ b/template/en/default/reports/duplicates.html.tmpl
@@ -0,0 +1,179 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ #%]
+
+[%# INTERFACE:
+ # sortby: string. the column on which we are sorting the buglist.
+ # reverse: boolean. True if we are reversing the current sort.
+ # maxrows: integer. Max number of rows to display.
+ # changedsince: integer. The number of days ago for the changedsince column.
+ # openonly: boolean. True if we are only showing open bugs.
+ # product: array of strings. The set of products we check for dups.
+ #
+ # Additionally, you need to fulfill the interface to
+ # duplicates-table.html.tmpl.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% IF product.size %]
+ [% title = BLOCK %]
+ Most Frequently Reported [% terms.Bugs %] for
+ [%+ product.join(', ') FILTER html %]
+ [% END %]
+[% ELSE %]
+ [% title = "Most Frequently Reported $terms.Bugs" %]
+[% END%]
+
+[% PROCESS global/header.html.tmpl
+ title = title
+ style_urls = ['skins/standard/duplicates.css']
+%]
+
+<p>
+ <a href="#explanation">What is this data?</a>
+ <br>
+ <a href="#params">Change parameters</a>
+</p>
+
+[% PROCESS "reports/duplicates-table.html.tmpl" %]
+
+[%# *** Parameters *** %]
+
+[% bug_ids_string = bug_ids.join(',') %]
+
+<h3 id="params">Change Parameters</h3>
+
+<form method="get" action="duplicates.cgi">
+ <input type="hidden" name="sortby" value="[% sortby FILTER html %]">
+ <input type="hidden" name="reverse" value="[% reverse FILTER html %]">
+ <input type="hidden" name="bug_id" value="[% bug_ids_string FILTER html %]">
+ <table>
+ <tr>
+ <td>When sorting or restricting, work with:</td>
+ <td>
+ <input type="radio" name="sortvisible" id="entirelist" value="0"
+ [% ' checked="checked"' IF NOT sortvisible %]>
+ <label for="entirelist">
+ entire list
+ </label>
+ <br>
+ <input type="radio" name="sortvisible" id="visiblelist" value="1"
+ [% ' checked="checked"' IF sortvisible %]>
+ <label for="visiblelist">
+ currently visible list
+ </label>
+ </td>
+ <td rowspan="4" valign="top">Restrict to products:</td>
+ <td rowspan="4" valign="top">
+ <select name="product" size="5" multiple="multiple">
+ [% FOREACH p = user.get_selectable_products %]
+ <option name="[% p.name FILTER html %]"
+ [% ' selected="selected"' IF product.contains(p.name) %]
+ >[% p.name FILTER html %]</option>
+ [% END %]
+ </select>
+ </td>
+ </tr>
+
+ <tr>
+ <td><label for="maxrows">Max rows:</label></td>
+ <td>
+ <input size="4" name="maxrows" id="maxrows"
+ value="[% maxrows FILTER html %]">
+ </td>
+ </tr>
+
+ <tr>
+ <td>
+ <label for="changedsince">Change column is change in the last:</label>
+ </td>
+ <td>
+ <input size="4" name="changedsince" id="changedsince"
+ value="[% changedsince FILTER html %]"> days
+ </td>
+ </tr>
+
+ <tr>
+ <td>
+ <label for="openonly">
+ Open [% terms.bugs %] only:
+ </label>
+ </td>
+ <td>
+ <input type="checkbox" name="openonly" id="openonly" value="1"
+ [% ' checked="checked"' IF openonly %]>
+ </td>
+ </tr>
+
+ </table>
+
+ <input type="submit" id="change" value="Change">
+</form>
+
+<form method="post" action="buglist.cgi">
+ <input type="hidden" name="bug_id" value="[% bug_ids_string FILTER html %]">
+ Or just give this to me as a <input type="submit" id="list"
+ value="[% terms.bug %] list">.
+ (Note: the order may not be the same.)
+</form>
+
+<hr>
+
+<h3 id="explanation">
+ What are "Most Frequently Reported [% terms.Bugs %]"?
+</h3>
+
+<p>
+ The Most Frequent [% terms.Bugs %] page lists the known open
+ [%+ terms.bugs %] which are reported most frequently,
+ counting the number of direct and indirect duplicates of [% terms.bugs %].
+ This information is provided in order to assist in minimizing
+ the amount of duplicate [% terms.bugs %] entered into [% terms.Bugzilla %],
+ which saves time for Quality Assurance engineers who have to triage
+ the [% terms.bugs %].
+</p>
+
+<b>How do I use this list?</b>
+
+<ul>
+ <li>Review the most frequent [% terms.bugs %] list.</li>
+ <li>If your problem is listed:</li>
+
+ <ul>
+ <li>Click on the [% terms.bug %] number to confirm that you have found the
+ same [% terms.bug %], and comment if you have additional information
+ or move on with your testing of the product.
+ </li>
+ </ul>
+
+ <li>If your problem not listed:</li>
+
+ <ul>
+ <li><a href="query.cgi">Try and locate a similar [% terms.bug %]</a>
+ that has already been filed.</li>
+ <li>If you find your [% terms.bug %] in [% terms.Bugzilla %],
+ feel free to comment with any new or additional data you may have.</li>
+ <li>If you cannot find your problem already documented in
+ [%+ terms.Bugzilla %],
+ <a href="enter_bug.cgi">file a new [% terms.bug %]</a>.</li>
+ </ul>
+</ul>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/reports/edit-series.html.tmpl b/template/en/default/reports/edit-series.html.tmpl
new file mode 100644
index 000000000..527a84f0a
--- /dev/null
+++ b/template/en/default/reports/edit-series.html.tmpl
@@ -0,0 +1,75 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ #%]
+
+[% title = "Edit Series" %]
+[% subheader = BLOCK %]
+ [% default.category FILTER html %] /
+ [%+ default.subcategory FILTER html %] /
+ [%+ default.name FILTER html %]
+[% END %]
+
+[% PROCESS global/header.html.tmpl %]
+
+[% IF changes_saved %]
+ <p>
+ <font color="red">
+ Series updated.
+ </font>
+ </p>
+[% END %]
+
+<form method="get" action="chart.cgi" name="chartform">
+
+ [% PROCESS reports/series.html.tmpl
+ button_name = "Change Data Set" %]
+ <input type="hidden" name="action" value="alter">
+ <input type="hidden" name="token"
+ value="[% issue_hash_token([default.id, default.name]) FILTER html %]">
+
+ [% IF default.series_id %]
+ <input type="hidden" name="series_id" value="[% default.series_id %]">
+ [% END %]
+</form>
+
+<p>
+ <b>Creator</b>:
+ [% IF default.creator %]
+ <a href="mailto:[% default.creator.email FILTER html %]">
+ [% default.creator.email FILTER html %]</a>
+ [% ELSE %]
+ (automatically created by [% terms.Bugzilla %])
+ [% END %]
+</p>
+
+<p>Note: it is not yet possible to edit the search associated with this data
+set.
+</p>
+
+<p>
+ <a href="query.cgi?[% default.query FILTER html %]">View
+ series search parameters</a> |
+ <a href="buglist.cgi?cmdtype=dorem&amp;namedcmd=
+ [% default.category FILTER url_quote %]-
+ [% default.subcategory FILTER url_quote %]-
+ [% default.name FILTER url_quote %]&amp;remaction=runseries&amp;series_id=
+ [% default.series_id %]">Run series search</a>
+</p>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/reports/keywords.html.tmpl b/template/en/default/reports/keywords.html.tmpl
new file mode 100644
index 000000000..d6fe03425
--- /dev/null
+++ b/template/en/default/reports/keywords.html.tmpl
@@ -0,0 +1,86 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ # Contributor(s): David D. Kilzer <ddkilzer@kilzer.net>
+ #%]
+
+[%# INTERFACE:
+ # keywords: array keyword objects. May be empty. Each has has four members:
+ # id: id of the keyword
+ # name: the name of the keyword
+ # description: keyword description. Can contain some limited HTML code.
+ # bug_count: number of bugs with that keyword
+ # caneditkeywords: boolean. True if this user can edit keywords
+ %]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% PROCESS global/header.html.tmpl
+ title = "$terms.Bugzilla Keyword Descriptions"
+%]
+
+[% FOREACH keyword = keywords %]
+ [% IF loop.index % 50 == 0 %]
+ [% IF loop.index != 0 %]
+ </table>
+ [% END %]
+
+ <table border="1" cellpadding="4" cellspacing="0">
+ <tr bgcolor="#6666FF">
+ <th align="left">Name</th>
+ <th align="left">Description</th>
+ <th align="left">Open [% terms.Bugs %]</th>
+ <th align="left">Total [% terms.Bugs %]</th>
+ </tr>
+ [% END %]
+
+ <tr id="[% keyword.name FILTER html %]">
+ <th>
+ [% keyword.name FILTER html %]
+ </th>
+ <td>[% keyword.description FILTER html_light %]</td>
+ <td align="center">
+ [% IF keyword.bug_count > 0 %]
+ <a href="buglist.cgi?keywords=[% keyword.name FILTER url_quote %]&amp;resolution=---">
+ Search</a>
+ [% ELSE %]
+ none
+ [% END %]
+ </td>
+ <td align="right">
+ [% IF keyword.bug_count > 0 %]
+ <a href="buglist.cgi?keywords=[% keyword.name FILTER url_quote %]">
+ [% keyword.bug_count %]</a>
+ [% ELSE %]
+ none
+ [% END %]
+ </td>
+ </tr>
+[% END %]
+
+[% IF keywords.size > 0 %]
+ </table>
+[% END %]
+
+[% IF caneditkeywords %]
+ <p>
+ <a href="editkeywords.cgi">Edit keywords</a>.
+ </p>
+[% END %]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/reports/menu.html.tmpl b/template/en/default/reports/menu.html.tmpl
new file mode 100644
index 000000000..5f26ac335
--- /dev/null
+++ b/template/en/default/reports/menu.html.tmpl
@@ -0,0 +1,85 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ #%]
+
+[%# INTERFACE:
+ # This template has no interface. It's a list of the available report
+ # types in Bugzilla.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% PROCESS global/header.html.tmpl
+ title = "Reporting and Charting Kitchen"
+ doc_section = "reporting.html"
+ style_urls = ['skins/standard/reports.css']
+%]
+
+<p>
+ [% terms.Bugzilla %] allows you to view and track the state of the [% terms.bug %] database in
+ all manner of exciting ways.
+</p>
+
+<h2>Current State</h2>
+
+<ul>
+ <li id="report_search">
+ <strong><a href="query.cgi">Search</a></strong> -
+ list sets of [% terms.bugs %].
+ </li>
+ <li id="report_tabular">
+ <strong>
+ <a href="query.cgi?format=report-table">Tabular reports</a>
+ </strong> -
+ tables of [% terms.bug %] counts in 1, 2 or 3 dimensions, as HTML or CSV.
+ </li>
+ [% IF feature_enabled('graphical_reports') %]
+ <li id="report_graphical">
+ <strong>
+ <a href="query.cgi?format=report-graph">Graphical reports</a>
+ </strong> -
+ line graphs, bar and pie charts.
+ </li>
+ [% END %]
+ [% Hook.process('current_state') %]
+</ul>
+
+[% IF feature_enabled('new_charts') OR feature_enabled('old_charts') %]
+ <h2>Change Over Time</h2>
+
+ <ul>
+ [% IF feature_enabled('old_charts') %]
+ <li id="old_charts">
+ <strong><a href="reports.cgi">Old Charts</a></strong> -
+ plot the status and/or resolution of [% terms.bugs %] against
+ time, for each product in your database.
+ </li>
+ [% END %]
+ [% IF feature_enabled('new_charts') AND user.in_group(Param("chartgroup")) %]
+ <li id="new_charts">
+ <strong><a href="chart.cgi">New Charts</a></strong> -
+ plot any arbitrary search against time. Far more powerful.
+ </li>
+ [% END %]
+ </ul>
+[% END %]
+
+[% Hook.process('end') %]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/reports/old-charts.html.tmpl b/template/en/default/reports/old-charts.html.tmpl
new file mode 100644
index 000000000..4bdc0cffa
--- /dev/null
+++ b/template/en/default/reports/old-charts.html.tmpl
@@ -0,0 +1,71 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+
+[%# INTERFACE:
+ # products: an array of product names the user is allowed to view.
+ # datasets: an array of hashes with available statuses and resolutions.
+ # url_image: URL of the generated graph.
+ #%]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+[% PROCESS global/header.html.tmpl
+ title = "$terms.Bug Charts"
+ h1 = "Welcome to the $terms.Bugzilla Charting Kitchen"
+ doc_section = "reporting.html#charts"
+%]
+
+<div align="center">
+ [% IF url_image %]
+ <img src="[% url_image FILTER html %]">
+ <br clear="both">
+ [% ELSE %]
+ <form id="choose_product" method="get" action="reports.cgi">
+ <table border="1" cellpadding="5" cellspacing="2">
+ <tr>
+ <th>Product:</th>
+ <td align="center">
+ <select id="product" name="product">
+ [% FOREACH product = products %]
+ <option value="[% product FILTER html %]">[% product FILTER html %]</option>
+ [% END %]
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <th>Chart datasets:</th>
+ <td align="center">
+ <select id="datasets" name="datasets" multiple="multiple" size="5">
+ [%# We cannot use translated statuses and resolutions from field-descs.none.html
+ # because old charts do not distinguish statuses from resolutions. %]
+ [% FOREACH dataset = datasets %]
+ <option value="[% dataset.value FILTER html %]"
+ [% " selected=\"selected\"" IF dataset.selected %]>
+ [% dataset.value FILTER html %]</option>
+ [% END %]
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <th colspan="2">
+ <input type="submit" id="submit" value="Continue">
+ </th>
+ </tr>
+ </table>
+ </form>
+ [% END %]
+</div>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/reports/report-bar.png.tmpl b/template/en/default/reports/report-bar.png.tmpl
new file mode 100644
index 000000000..649fba426
--- /dev/null
+++ b/template/en/default/reports/report-bar.png.tmpl
@@ -0,0 +1,64 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% y_label = "$terms.Bugs" %]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+[% col_field_disp = field_descs.$col_field || col_field %]
+
+[% FOR i IN [ 0 .. data.0.0.max ] %]
+ [% data.0.0.$i = display_value(col_field, data.0.0.$i) %]
+[% END %]
+
+[% FOR i IN [ 0 .. row_names.max ] %]
+ [% row_names.$i = display_value(row_field, row_names.$i) %]
+[% END %]
+
+[% FILTER null;
+ USE graph = GD.Graph.bars(width, height);
+
+ graph.set(x_label => col_field_disp,
+ y_label => y_label,
+ y_tick_number => 8,
+ y_number_format => "%d",
+ x_label_position => 0.5,
+ x_labels_vertical => x_labels_vertical,
+ bar_spacing => 8,
+ shadow_depth => 4,
+ shadowclr => 'dred',
+ show_values => 1,
+ legend_placement => "RT");
+
+ graph.set(cumulate => "true",
+ show_values => 0) IF cumulate;
+
+ # Workaround for the fact that set_legend won't take row_names directly,
+ # because row_names is an array reference rather than an array.
+ graph.set_legend(row_names.0, row_names.1, row_names.2, row_names.3,
+ row_names.4, row_names.5, row_names.6, row_names.7,
+ row_names.8, row_names.9, row_names.10, row_names.11,
+ row_names.12, row_names.13, row_names.14, row_names.15);
+
+ graph.plot(data.0).png | stdout(1);
+ END;
+-%]
diff --git a/template/en/default/reports/report-line.png.tmpl b/template/en/default/reports/report-line.png.tmpl
new file mode 100644
index 000000000..0edc0fee7
--- /dev/null
+++ b/template/en/default/reports/report-line.png.tmpl
@@ -0,0 +1,67 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% y_label = "$terms.Bugs" %]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+[% col_field_disp = field_descs.$col_field || col_field %]
+
+[% FOR i IN [ 0 .. data.0.0.max ] %]
+ [% data.0.0.$i = display_value(col_field, data.0.0.$i) %]
+[% END %]
+
+[% FOR i IN [ 0 .. row_names.max ] %]
+ [% row_names.$i = display_value(row_field, row_names.$i) %]
+[% END %]
+
+[% IF cumulate %]
+ [% USE graph = GD.Graph.area(width, height) %]
+ [% graph.set(cumulate => "true") %]
+[% ELSE %]
+ [% USE graph = GD.Graph.lines(width, height) %]
+[% END %]
+
+[% FILTER null;
+ graph.set(x_label => col_field_disp,
+ y_label => y_label,
+ y_tick_number => 8,
+ x_label_position => 0.5,
+ x_labels_vertical => x_labels_vertical,
+ legend_placement => "RT",
+ line_width => 2,
+ dclrs => ["lred", "lgreen", "lblue", "lyellow",
+ "lpurple", "lorange", "black", "green",
+ "blue", "dpink", "lbrown", "gray",
+ "red", "dpurple", "gold", "marine"]);
+
+ # Workaround for the fact that set_legend won't take row_names directly,
+ # because row_names is an array reference rather than an array.
+ graph.set_legend(row_names.0, row_names.1, row_names.2, row_names.3,
+ row_names.4, row_names.5, row_names.6, row_names.7,
+ row_names.8, row_names.9, row_names.10, row_names.11,
+ row_names.12, row_names.13, row_names.14, row_names.15);
+
+ graph.plot(data.0).png | stdout(1);
+ END;
+-%]
+
diff --git a/template/en/default/reports/report-pie.png.tmpl b/template/en/default/reports/report-pie.png.tmpl
new file mode 100644
index 000000000..27f5525dd
--- /dev/null
+++ b/template/en/default/reports/report-pie.png.tmpl
@@ -0,0 +1,39 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ #%]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+[% col_field_disp = field_descs.$col_field || col_field %]
+
+[% FOR i IN [ 0 .. data.0.0.max ] %]
+ [% data.0.0.$i = display_value(col_field, data.0.0.$i) %]
+[% END %]
+
+[% FILTER null;
+ USE graph = GD.Graph.pie(width, height);
+
+ graph.set(title => col_field_disp,
+ pie_height => 20,
+ suppress_angle => 2,
+ start_angle => 180);
+
+ graph.plot(data.0).png | stdout(1);
+ END;
+-%]
diff --git a/template/en/default/reports/report-simple.html.tmpl b/template/en/default/reports/report-simple.html.tmpl
new file mode 100644
index 000000000..d358109f7
--- /dev/null
+++ b/template/en/default/reports/report-simple.html.tmpl
@@ -0,0 +1,35 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Alan Starr (alanjstr)
+ #%]
+
+[%# INTERFACE:
+ # You need to fulfill the interface to report-table.html.tmpl.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+<html>
+
+ [% title = "$terms.Bug List" %]
+
+ <head>
+ <title>[% title FILTER html %]</title>
+ </head>
+
+ <body>
+ [% PROCESS "reports/report-table.html.tmpl" %]
+ </body>
+
+</html>
+
diff --git a/template/en/default/reports/report-table.csv.tmpl b/template/en/default/reports/report-table.csv.tmpl
new file mode 100644
index 000000000..4d8b50a85
--- /dev/null
+++ b/template/en/default/reports/report-table.csv.tmpl
@@ -0,0 +1,77 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ #%]
+[%# INTERFACE:
+ # See report-table.html.tmpl.
+ #%]
+[% PROCESS global/variables.none.tmpl %]
+
+[% colsepchar = user.settings.csv_colsepchar.value %]
+
+[% num_bugs = BLOCK %]Number of [% terms.bugs %][% END %]
+[% tbl_field_disp = field_descs.$tbl_field || tbl_field %]
+[% col_field_disp = field_descs.$col_field || col_field %]
+[% row_field_disp = field_descs.$row_field || row_field %]
+
+[% IF tbl_field %]
+ [% IF tbl_field == 'assigned_to' OR tbl_field == 'reporter'
+ OR tbl_field == 'qa_contact'
+ %]
+ [% tbl_disp = tbl FILTER email %]
+ [% ELSE %]
+ [% tbl_disp = tbl %]
+ [% END %]
+ [% tbl_field_disp FILTER csv %]: [% tbl_disp FILTER csv %]
+[% END %]
+[% IF row_field %]
+ [% row_field_disp FILTER csv %]
+[% END %]
+[% " / " IF col_field AND row_field %]
+[% col_field_disp FILTER csv %]
+[% IF col_field -%]
+ [% FOREACH col = col_names -%]
+ [% colsepchar %]
+ [% PROCESS value_display value = col field = col_field %]
+ [% END -%]
+[% ELSE -%]
+ [% colsepchar %][% num_bugs FILTER csv %]
+[% END %]
+
+[% FOREACH row = row_names %]
+ [% PROCESS value_display value = row field = row_field %]
+ [% FOREACH col = col_names %]
+ [% colsepchar %]
+ [% IF data.$tbl AND data.$tbl.$col AND data.$tbl.$col.$row %]
+ [% data.$tbl.$col.$row -%]
+ [% ELSE %]
+ [% -%]0
+ [% END %]
+ [% END %]
+
+[% END %]
+
+[% BLOCK value_display %]
+ [% SET disp_value = display_value(field, value) %]
+ [% IF field == 'assigned_to' OR field == 'reporter'
+ OR field == 'qa_contact'
+ %]
+ [% disp_value = value FILTER email %]
+ [% END %]
+ [% disp_value FILTER csv %]
+[% END %]
diff --git a/template/en/default/reports/report-table.html.tmpl b/template/en/default/reports/report-table.html.tmpl
new file mode 100644
index 000000000..76b80f893
--- /dev/null
+++ b/template/en/default/reports/report-table.html.tmpl
@@ -0,0 +1,164 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ # <rdean@cambianetworks.com>
+ #%]
+
+[%# INTERFACE:
+ # buglistbase: The base query for this table, in URL form
+ # col_field: string. Name of the field being plotted as columns.
+ # row_field: string. Name of the field being plotted as rows.
+ # tbl_field: string. Name of the field being plotted as tables.
+ # col_names: array. List of values for the field being plotted as columns.
+ # row_names: array. List of values for the field being plotted as rows.
+ # data: <depends on format>. Data to plot. Only data.$tbl is accessed.
+ # tbl: Name of a hash in data which is the table to be plotted.
+ #%]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+[% col_field_disp = field_descs.$col_field || col_field %]
+[% row_field_disp = field_descs.$row_field || row_field %]
+
+[% IF tbl == "-total-" %]
+ [% urlbase = BLOCK %]buglist.cgi?[% buglistbase FILTER html %]
+ [% "&amp;$tbl_vals" IF tbl_vals %][% END %]
+[% ELSE %]
+ [% urlbase = BLOCK %]buglist.cgi?[% buglistbase FILTER html %]&amp;
+ [% tbl_field FILTER url_quote %]=[% tbl FILTER url_quote %][% END %]
+[% END %]
+
+[% IF tbl_field %]
+ <h2>[% tbl_disp FILTER email FILTER html %]</h2>
+[% END %]
+
+<table>
+ <tr>
+ <td>
+ </td>
+ <td align="center">
+ <strong>[% col_field_disp FILTER html %]</strong>
+ </td>
+ </tr>
+
+ <tr>
+ <td valign="middle">
+ <strong>[% row_field_disp FILTER html %]</strong>
+ </td>
+ <td>
+
+
+[% classes = [ [ "t1", "t2" ] , [ "t3", "t4" ] ] %]
+[% col_idx = 0 %]
+[% row_idx = 0 %]
+[% grand_total = 0 %]
+
+<table border="1">
+ [% IF col_field %]
+ <tr>
+ <td class="[% classes.$row_idx.$col_idx %]">
+ </td>
+ [% FOREACH col = col_names %]
+ [% col_totals.$col = 0 %]
+ [% NEXT IF col == "" %]
+
+ [% col_idx = 1 - col_idx %]
+ <td class="[% classes.$row_idx.$col_idx %]">
+ [% PROCESS value_display value = col field = col_field %]
+ </td>
+ [% END %]
+ <td class="ttotal">
+ Total
+ </td>
+ </tr>
+ [% END %]
+
+ [% FOREACH row = row_names %]
+ [% row_total = 0 %]
+
+ [% row_idx = 1 - row_idx %]
+ <tr>
+ <td class="[% classes.$row_idx.$col_idx %]" align="right">
+ [% PROCESS value_display value = row field = row_field %]
+ </td>
+ [% FOREACH col = col_names %]
+ [% row_total = row_total + data.$tbl.$col.$row %]
+ [% NEXT IF col == "" %]
+ [% col_totals.$col = col_totals.$col + data.$tbl.$col.$row %]
+
+ [% col_idx = 1 - col_idx %]
+ <td class="[% classes.$row_idx.$col_idx %]" align="center">
+ [% IF data.$tbl.$col.$row AND data.$tbl.$col.$row > 0 %]
+ <a href="[% urlbase %]&amp;
+ [% row_field FILTER url_quote %]=[% row FILTER url_quote %]&amp;
+ [% col_field FILTER url_quote %]=[% col FILTER url_quote %]">
+ [% data.$tbl.$col.$row %]</a>
+ [% ELSE %]
+ .
+ [% END %]
+ </td>
+ [% END %]
+ <td class="ttotal" align="right">
+ <a href="[% urlbase %]&amp;
+ [% row_field FILTER url_quote %]=[% row FILTER url_quote %]
+ [% "&amp;$col_vals" IF col_vals %]">
+ [% row_total %]</a>
+ [% grand_total = grand_total + row_total %]
+ </td>
+ </tr>
+ [% END %]
+
+ <tr>
+ [% row_idx = 1 - row_idx %]
+ <td class="ttotal">
+ Total
+ </td>
+ [% FOREACH col = col_names %]
+ [% NEXT IF col == "" %]
+
+ <td class="ttotal" align="center">
+ <a href="[% urlbase %]&amp;
+ [% col_field FILTER url_quote %]=[% col FILTER url_quote %]
+ [% "&amp;$row_vals" IF row_vals %]">
+ [% col_totals.$col %]</a>
+ </td>
+ [% END %]
+ <td class="ttotal" align="right">
+ <strong>
+ <a href="[% urlbase %]
+ [% "&amp;$row_vals" IF row_vals %]
+ [% "&amp;$col_vals" IF col_vals %]">[% grand_total %]</a>
+ </strong>
+ </td>
+ </tr>
+</table>
+
+
+ </td>
+ </tr>
+</table>
+
+[% BLOCK value_display %]
+ [% SET disp_value = display_value(field, value) %]
+ [% IF field == 'assigned_to' OR field == 'reporter'
+ OR field == 'qa_contact'
+ %]
+ [% disp_value = value FILTER email %]
+ [% END %]
+ [% disp_value FILTER html FILTER replace('^ $','&nbsp;') %]
+[% END %]
diff --git a/template/en/default/reports/report.csv.tmpl b/template/en/default/reports/report.csv.tmpl
new file mode 100644
index 000000000..f26bc1f36
--- /dev/null
+++ b/template/en/default/reports/report.csv.tmpl
@@ -0,0 +1,25 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ #%]
+[% PROCESS "global/field-descs.none.tmpl" %]
+[% FOREACH tbl = tbl_names %]
+ [% PROCESS "reports/report-table.csv.tmpl" %]
+
+
+[% END %]
diff --git a/template/en/default/reports/report.html.tmpl b/template/en/default/reports/report.html.tmpl
new file mode 100644
index 000000000..4f7ee49b6
--- /dev/null
+++ b/template/en/default/reports/report.html.tmpl
@@ -0,0 +1,180 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ #%]
+
+[%# INTERFACE:
+ # col_field: string. Name of the field being plotted as columns.
+ # row_field: string. Name of the field being plotted as rows.
+ # tbl_field: string. Name of the field being plotted as tables.
+ # tbl_names: array. List of values for the field being plotted as tables.
+ # time: integer. Seconds since the epoch.
+ # data: <depends on format>. Data to plot.
+ # format: string. Format of the individual reports.
+ # width: integer. For image charts, height of the image.
+ # height: integer. For image charts, width of the image.
+ # imagebase: string. Base URL for chart image.
+ # switchbase: string. Base URL for format switching.
+ # cumulate: boolean. For bar/line charts, whether to cumulate data sets.
+ #%]
+
+[% DEFAULT width = 600
+ height = 350
+%]
+
+[% IF min_width AND width < min_width %]
+ [% width = min_width %]
+[% END %]
+
+[%# We ignore row_field for pie charts %]
+[% IF format == "pie" %]
+ [% row_field = "" %]
+[% END %]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+[% tbl_field_disp = field_descs.$tbl_field || tbl_field %]
+[% col_field_disp = field_descs.$col_field || col_field %]
+[% row_field_disp = field_descs.$row_field || row_field %]
+[% switchbase = switchbase FILTER html %]
+
+[% title = BLOCK %]
+ Report:
+ [% IF tbl_field %]
+ [% tbl_field_disp FILTER html %]
+ [% END %]
+ [% " / " IF tbl_field AND (col_field OR row_field) %]
+ [% IF row_field %]
+ [% row_field_disp FILTER html %]
+ [% END %]
+ [% " / " IF col_field AND row_field %]
+ [% col_field_disp FILTER html %]
+[% END %]
+
+[% time = time FILTER time('%Y-%m-%d %H:%M:%S') FILTER html %]
+
+[% PROCESS global/header.html.tmpl
+ style = "
+ .t1 { background-color: #ffffff } /* white */
+ .t2 { background-color: #dfefff } /* light blue */
+ .t3 { background-color: #dddddd } /* grey */
+ .t4 { background-color: #c3d3ed } /* darker blue */
+ .ttotal { background-color: #cfffdf } /* light green */
+ "
+ header_addl_info = time
+%]
+
+[% IF debug %]
+ <p>[% query FILTER html %]</p>
+[% END %]
+
+<div align="center">
+
+ [% FOREACH tbl = tbl_names %]
+ [% IF tbl == "-total-" %]
+ [% tbl_disp = "Total" %]
+ [% ELSE %]
+ [% tbl_disp = tbl %]
+ [% END %]
+
+ [% IF format == "table" %]
+ [% PROCESS "reports/report-table.html.tmpl" %]
+ [% ELSE %]
+ [% IF tbl %]
+ <h2>[% tbl_disp FILTER email FILTER html %]</h2>
+ [% END %]
+
+ [% imageurl = BLOCK %]report.cgi?[% imagebase FILTER html %]&amp;format=
+ [% format FILTER url_quote %]&amp;ctype=png&amp;action=plot&amp;
+ [% IF tbl_field %]
+ [% IF tbl != "-total-" %]
+ [% tbl_field FILTER url_quote %]=[% tbl FILTER url_quote %]&amp;
+ [% ELSE %]
+ [% FOREACH tblname = tbl_names %]
+ [% IF tblname != "-total-" %]
+ [% tbl_field FILTER url_quote %]=[% tblname FILTER url_quote %]&amp;
+ [% END %]
+ [% END %]
+ [% END %]
+ [% END %]width=[% width %]&amp;height=[% height %]
+ [% END %]
+
+ <img alt="Graphical report results" src="[% imageurl %]"
+ width="[% width %]" height="[% height %]">
+ [% END %]
+ <br>
+ [% END %]
+
+ <table>
+ <tr>
+ <td>
+ [% formats = [ { name => "pie", description => "Pie" },
+ { name => "bar", description => "Bar" },
+ { name => "line", description => "Line" },
+ { name => "table", description => "Table" } ] %]
+
+ [% formaturl = "report.cgi?$switchbase&amp;width=$width" _
+ "&amp;height=$height&amp;action=wrap" %]
+ [% FOREACH other_format = formats %]
+ [% NEXT IF other_format.name == "pie" AND row_field AND col_field %]
+ [% UNLESS other_format.name == format %]
+ <a href="[% formaturl %]&amp;format=[% other_format.name %]">
+ [% END %]
+ [% other_format.description FILTER html %]
+ [% "</a>" UNLESS other_format.name == format %] |
+ [% END %]
+ <a href="[% formaturl %]&amp;ctype=csv&amp;format=table">CSV</a>
+ </td>
+
+ [% IF format != "table" %]
+ <td>
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+ </td>
+
+ [% sizeurl = BLOCK %]report.cgi?
+ [% switchbase %]&amp;action=wrap&amp;format=
+ [% format FILTER html %][% END %]
+ <td align="center">
+ <a href="[% sizeurl %]&amp;width=[% width %]&amp;height=
+ [% height + 100 %]">Taller</a><br>
+ <a href="[% sizeurl %]&amp;width=[% width - 100 %]&amp;height=
+ [% height %]">Thinner</a> *
+ <a href="[% sizeurl %]&amp;width=[% width + 100 %]&amp;height=
+ [% height %]">Fatter</a>&nbsp;&nbsp;&nbsp;&nbsp;<br>
+ <a href="[% sizeurl %]&amp;width=[% width %]&amp;height=
+ [% height - 100 %]">Shorter</a><br>
+ </td>
+ [% END %]
+ </tr>
+ </table>
+
+ <p>
+ [% IF format == "table" %]
+ <a href="query.cgi?[% switchbase %]&amp;format=report-table">Edit
+ this report</a>
+ [% ELSE %]
+ <a href="query.cgi?[% switchbase %]&amp;chart_format=
+ [% format %]&amp;format=report-graph&amp;cumulate=[% cumulate %]">
+ Edit this report
+ </a>
+ [% END %]
+ </p>
+
+</div>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/reports/series-common.html.tmpl b/template/en/default/reports/series-common.html.tmpl
new file mode 100644
index 000000000..cecf288ec
--- /dev/null
+++ b/template/en/default/reports/series-common.html.tmpl
@@ -0,0 +1,119 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ #%]
+
+[%# INTERFACE:
+ # donames: boolean. True if we have a multi-select for names as well as
+ # categories and subcategories.
+ # category: hash (keyed by category) of hashes (keyed by subcategory) of
+ # hashes (keyed by name), with value being the series_id of the
+ # series. Contains details of all series the user can see.
+ #%]
+
+[% subcategory = category.${default.category} %]
+[% name = subcategory.${default.subcategory} %]
+
+<script type="text/javascript">
+[%# This structure holds details of the series the user can select from. %]
+var series = {
+[% FOREACH c = category.keys.sort %]
+ "[%+ c FILTER js %]" : {
+ [% FOREACH s = category.$c.keys.sort %]
+ "[%+ s FILTER js %]" : {
+ [% IF donames %]
+ [% FOREACH n = category.$c.$s.keys.sort %]
+ "[% n FILTER js %]":
+ [% category.$c.$s.$n FILTER js %][% ", " UNLESS loop.last %]
+ [% END %]
+ [% END %]
+ }[% ", " UNLESS loop.last %]
+ [% END %]
+ }[% ", " UNLESS loop.last %]
+[% END %]
+};
+
+[%# This function takes necessary action on selection of a category %]
+function catSelected() {
+ var cat = document.chartform.category.value;
+ var subcats = series[cat];
+
+ var subcatwidget = document.chartform.subcategory;
+
+ subcatwidget.options.length = 0;
+ var i = 0;
+
+ for (x in subcats) {
+ subcatwidget.options[i] = new Option(x, x);
+ i++;
+ }
+
+ [% IF newtext %]
+ subcatwidget.options[i] = new Option("[% newtext FILTER js %]", "");
+ [% END %]
+
+ subcatwidget.disabled = false;
+ subcatwidget.options[0].selected = true;
+
+ if (document.chartform.action[1]) {
+ [%# On the query form, select the right radio button. %]
+ document.chartform.action[1].checked = true;
+ }
+
+ checkNewState();
+}
+
+[%# This function updates the disabled state of the two "new" textboxes %]
+function checkNewState() {
+ var fm = document.chartform;
+ if (fm.newcategory) {
+ fm.newcategory.disabled =
+ (fm.category.value != "" ||
+ fm.action[1] && fm.action[1].checked == false);
+ fm.newsubcategory.disabled =
+ (fm.subcategory.value != "" ||
+ fm.action[1] && fm.action[1].checked == false);
+ }
+}
+</script>
+
+[%###########################################################################%]
+[%# Block for SELECT fields - pinched from search/form.html.tmpl #%]
+[%###########################################################################%]
+
+[% BLOCK series_select %]
+ <td align="left">
+ <select name="[% sel.name %]" id="[% sel.name %]"
+ size="[% sel.size %]" style="width: 15em"
+ [%+ 'multiple="multiple"' IF sel.multiple %]
+ [%+ "disabled=\"disabled\"" UNLESS ${sel.name}.keys.size || newtext %]
+ [%+ "onchange=\"$sel.onchange\"" IF sel.onchange %]>
+ [% FOREACH x = ${sel.name}.keys.sort %]
+ [% value = sel.value_in_hash ? ${sel.name}.$x : x %]
+ <option value="[% value FILTER html %]"
+ [% " selected" IF default.${sel.name} == value %]>
+ [% x FILTER html %]</option>
+ [% END %]
+ [% IF newtext %]
+ <option value="">[% newtext FILTER html %]</option>
+ [% ELSIF NOT ${sel.name}.keys.size %]
+ <option value="" disabled="disabled"></option>
+ [% END %]
+ </select>
+ </td>
+[% END %]
diff --git a/template/en/default/reports/series.html.tmpl b/template/en/default/reports/series.html.tmpl
new file mode 100644
index 000000000..3cf939003
--- /dev/null
+++ b/template/en/default/reports/series.html.tmpl
@@ -0,0 +1,97 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ #%]
+
+[%# INTERFACE:
+ # default: hash. Defaults for category, subcategory, name etc.
+ # button_name: string. What the button will say.
+ # category: hash (keyed by category) of hashes (keyed by subcategory) of
+ # hashes (keyed by name), with value being the series_id of the
+ # series. Contains details of all series the user can see.
+ #%]
+
+[% PROCESS "reports/series-common.html.tmpl"
+ newtext = "New (name below)"
+ %]
+
+<table cellpadding="2" cellspacing="2" border="0"
+ style="text-align: left; margin-left: 20px">
+ <tbody>
+ <tr>
+ <th>Category:</th>
+ <th></th>
+ <th>Sub-category:</th>
+ <th>Name:</th>
+ <td></td>
+ </tr>
+ <tr>
+ [% PROCESS series_select sel = { name => 'category',
+ size => 5,
+ onchange => "catSelected()" } %]
+ <td>
+ <noscript>
+ <input type="submit" name="action-edit" value="Update --&gt;"
+ id="action-edit">
+ </noscript>
+ </td>
+
+ [% PROCESS series_select sel = { name => 'subcategory',
+ size => 5,
+ onchange => "checkNewState()" } %]
+
+ <td valign="top" name="name">
+ <input type="text" name="name" maxlength="64"
+ value="[% default.name.0 FILTER html %]" size="25">
+ </td>
+
+ <td valign="top">
+ <span style="font-weight: bold;">Run every</span> &nbsp;
+ <input type="text" size="2" name="frequency"
+ value="[% (default.frequency.0 OR 7) FILTER html %]">
+ <span style="font-weight: bold;">&nbsp;day(s)</span><br>
+ [%# Change 'admin' here and in Series.pm, or remove the check
+ completely, if you want to change who can make series public. %]
+ [% IF user.in_group('admin') %]
+ <input type="checkbox" name="public"
+ [%+ "checked='checked'" IF default.public.0 %]>
+ <span style="font-weight: bold;">Visible to all<br>
+ (within group restrictions)</span>
+ [% END %]
+ </td>
+ </tr>
+
+ <tr>
+ <td>
+ <input type="text" style="width: 100%" name="newcategory"
+ maxlength="64" value="[% default.newcategory.0 FILTER html %]">
+ </td>
+ <td></td>
+ <td>
+ <input type="text" style="width: 100%" name="newsubcategory"
+ maxlength="64"
+ value="[% default.newsubcategory.0 FILTER html %]">
+ </td>
+ <td></td>
+ <td>
+ <input type="submit" name="submit-button" id="submit-button"
+ value="[% button_name FILTER html %]">
+ </td>
+ </tr>
+ </tbody>
+</table>
diff --git a/template/en/default/request/email.txt.tmpl b/template/en/default/request/email.txt.tmpl
new file mode 100644
index 000000000..03c5e57c0
--- /dev/null
+++ b/template/en/default/request/email.txt.tmpl
@@ -0,0 +1,87 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Myk Melez <myk@mozilla.org>
+ # Jeff Hedlund <jeff.hedlund@matrixsi.com>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% bugidsummary = bug.bug_id _ ': ' _ bug.short_desc %]
+[% attidsummary = attachment.id _ ': ' _ attachment.description %]
+[% flagtype_name = flag ? flag.type.name : old_flag.type.name %]
+[% statuses = { '+' => "granted" , '-' => 'denied' , 'X' => "canceled" ,
+ '?' => "asked" } %]
+
+[% to_identity = "" %]
+[% on_behalf_of = 0 %]
+[% action = flag.status || 'X' %]
+
+[% IF flag && flag.status == '?' %]
+ [% subject_status = "requested" %]
+ [% IF flag.setter_id == user.id %]
+ [% to_identity = flag.requestee.identity _ " for" %]
+ [% ELSE %]
+ [% on_behalf_of = 1 %]
+ [% IF flag.requestee %][% to_identity = " to " _ flag.requestee.identity %][% END %]
+ [% END %]
+[% ELSE %]
+ [% IF old_flag && old_flag.status == '?' %]
+ [% to_identity = old_flag.setter.identity _ "'s request for" %]
+ [% END %]
+ [% subject_status = statuses.$action %]
+[% END %]
+From: [% Param('mailfrom') %]
+To: [% to %]
+Subject: [% flagtype_name %] [%+ subject_status %]: [[% terms.Bug %] [%+ bug.bug_id %]] [% bug.short_desc %]
+[%- IF attachment %] :
+ [Attachment [% attachment.id %]] [% attachment.description %][% END %]
+Date: [% bug.delta_ts FILTER time("%a, %d %b %Y %T %z", timezone) %]
+X-Bugzilla-Type: request
+[%+ threadingmarker %]
+
+[%+ USE wrap -%]
+[%- FILTER bullet = wrap(80) -%]
+
+[% IF on_behalf_of %]
+[% user.identity %] has reassigned [% flag.setter.identity %]'s request for [% flagtype_name %]
+[% to_identity %]:
+[% ELSE %]
+[% user.identity %] has [% statuses.$action %] [%+ to_identity %] [%+ flagtype_name %]:
+[% END %]
+
+[% terms.Bug %] [%+ bugidsummary %]
+[% END %]
+[%+ urlbase %]show_bug.cgi?id=[% bug.bug_id %]
+[% IF attachment %]
+
+[% FILTER bullet = wrap(80) %]
+Attachment [% attidsummary %]
+[%- END %]
+[%+ urlbase %]attachment.cgi?id=[% attachment.id %]&action=edit
+[%- END %]
+[%- FILTER bullet = wrap(80) %]
+
+[% USE Bugzilla %]
+[%-# .defined is necessary to avoid a taint issue in Perl < 5.10.1, see bug 509794. %]
+[% IF Bugzilla.cgi.param("comment").defined && Bugzilla.cgi.param("comment").length > 0 %]
+------- Additional Comments from [% user.identity %]
+[%+ Bugzilla.cgi.param("comment") %]
+[% END %]
+
+[%- END %]
diff --git a/template/en/default/request/queue.html.tmpl b/template/en/default/request/queue.html.tmpl
new file mode 100644
index 000000000..92df4b2c2
--- /dev/null
+++ b/template/en/default/request/queue.html.tmpl
@@ -0,0 +1,267 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Myk Melez <myk@mozilla.org>
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% USE Bugzilla %]
+[% cgi = Bugzilla.cgi %]
+
+[% PROCESS global/header.html.tmpl
+ title="Request Queue"
+ style = "
+ table.requests th { text-align: left; }
+ table#filtering th { text-align: right; }
+ "
+ onload="var f = document.request_form; selectProduct(f.product, f.component, null, null, 'Any');"
+ javascript_urls=["js/productform.js"]
+ style_urls = ['skins/standard/buglist.css']
+%]
+
+<script type="text/javascript">
+ var useclassification = false; // No classification level in use
+ var first_load = true; // Is this the first time we load the page?
+ var last_sel = []; // Caches last selection
+ var cpts = new Array();
+ [% n = 1 %]
+ [% IF Param('useclassification') %]
+ [% FOREACH clas = user.get_selectable_classifications %]
+ [% FOREACH prod = user.get_selectable_products(clas.id) %]
+ [%+ PROCESS js_comp %]
+ [% END %]
+ [% END %]
+ [% ELSE %]
+ [% FOREACH prod = user.get_selectable_products %]
+ [%+ PROCESS js_comp %]
+ [% END %]
+ [% END %]
+</script>
+
+[% BLOCK js_comp %]
+ cpts['[% n %]'] = [
+ [%- FOREACH comp = prod.components %]'[% comp.name FILTER js %]'[% ", " UNLESS loop.last %] [%- END -%]];
+ [% n = n+1 %]
+[% END %]
+
+<p>
+When you are logged in, only requests made by you or addressed to you
+are shown by default. You can change the criteria using the form below.
+When you are logged out, all pending requests that are not restricted
+to some group are shown by default.
+</p>
+
+<form id="request_form" name="request_form" action="request.cgi" method="get">
+ <input type="hidden" name="action" value="queue">
+
+ <table id="filtering">
+ <tr>
+ <th>Requester:</th>
+ <td>
+ [% INCLUDE global/userselect.html.tmpl
+ id => "requester"
+ name => "requester"
+ value => cgi.param('requester')
+ size => 20
+ emptyok => 1
+ title => "Requester's email address"
+ %]
+ </td>
+ <th>Product:</th>
+ <td>
+ <select name="product" onchange="selectProduct(this, this.form.component, null, null, 'Any');">
+ <option value="">Any</option>
+ [% IF Param('useclassification') %]
+ [% FOREACH c = user.get_selectable_classifications %]
+ <optgroup label="[% c.name FILTER html %]">
+ [% FOREACH p = user.get_selectable_products(c.id) %]
+ <option value="[% p.name FILTER html %]"
+ [% " selected" IF cgi.param('product') == p.name %]>
+ [% p.name FILTER html %]
+ </option>
+ [% END %]
+ </optgroup>
+ [% END %]
+ [% ELSE %]
+ [% FOREACH p = user.get_selectable_products %]
+ <option value="[% p.name FILTER html %]"
+ [% " selected" IF cgi.param('product') == p.name %]>
+ [% p.name FILTER html %]
+ </option>
+ [% END %]
+ [% END %]
+ </select>
+ </td>
+ <th>Flag:</th>
+ <td>
+ [% PROCESS "global/select-menu.html.tmpl"
+ name="type"
+ options=types
+ default=cgi.param('type') %]
+ </td>
+
+ [%# We could let people see a "queue" of non-pending requests. %]
+ <!--
+ <th>Status:</th>
+ <td>
+ [%# PROCESS "global/select-menu.html.tmpl"
+ name="status"
+ options=["all", "?", "+-", "+", "-"]
+ default=cgi.param('status') %]
+ </td>
+ -->
+
+ </tr>
+ <tr>
+ <th>Requestee:</th>
+ <td>
+ [% INCLUDE global/userselect.html.tmpl
+ id => "requestee"
+ name => "requestee"
+ value => cgi.param('requestee')
+ size => 20
+ emptyok => 1
+ hyphenok => 1
+ title => "Requestee's email address or &quot;-&quot; (hyphen) for requests with no requestee"
+ %]
+ </td>
+ <th>Component:</th>
+ <td>
+ <select name="component">
+ <option value="">Any</option>
+ [% FOREACH comp = components %]
+ <option value="[% comp FILTER html %]" [% "selected" IF cgi.param('component') == comp %]>
+ [% comp FILTER html %]</option>
+ [% END %]
+ </select>
+ </td>
+ <th>Group By:</th>
+ <td>
+ [% groups = {
+ "Requester" => 'requester' ,
+ "Requestee" => 'requestee',
+ "Flag" => 'type' ,
+ "Product/Component" => 'category'
+ } %]
+ [% PROCESS "global/select-menu.html.tmpl" name="group" options=groups default=cgi.param('group') %]
+ </td>
+ <td><input type="submit" id="filter" value="Filter"></td>
+ </tr>
+ </table>
+
+</form>
+
+[% column_headers = {
+ "type" => "Flag" ,
+ "status" => "Status" ,
+ "bug" => "$terms.Bug" ,
+ "attachment" => "Attachment" ,
+ "requester" => "Requester" ,
+ "requestee" => "Requestee" ,
+ "created" => "Created" ,
+ "category" => "Product/Component" } %]
+
+[% DEFAULT display_columns = ["requester", "requestee", "type", "bug", "attachment", "created"]
+ group_field = "Requestee"
+ group_value = ""
+%]
+
+[% IF debug %]
+ <p>[% query FILTER html %]</p>
+[% END %]
+
+[% IF requests.size == 0 %]
+ <p>
+ No requests.
+ </p>
+[% ELSE %]
+ [% FOREACH request = requests %]
+ [% IF request.$group_field != group_value || loop.first %]
+ [% group_value = request.$group_field %]
+ [% PROCESS display_buglist UNLESS loop.first %]
+ [% PROCESS start_new_table %]
+ [% END %]
+ [% buglist.${request.bug_id} = 1 %]
+ <tr>
+ [% FOREACH column = display_columns %]
+ [% NEXT IF column == group_field || excluded_columns.contains(column) %]
+ <td>[% PROCESS "display_$column" %]</td>
+ [% END %]
+ </tr>
+ [% END %]
+ [% PROCESS display_buglist %]
+[% END %]
+
+[% PROCESS global/footer.html.tmpl %]
+
+[% BLOCK start_new_table %]
+ [% buglist = {} %]
+
+ <h3>[% column_headers.$group_field %]:
+ [%+ (request.$group_field || "None") FILTER email FILTER html %]</h3>
+ <table class="requests" cellspacing="0" cellpadding="4" border="1">
+ <tr>
+ [% FOREACH column = display_columns %]
+ [% NEXT IF column == group_field || excluded_columns.contains(column) %]
+ <th>[% column_headers.$column %]</th>
+ [% END %]
+ </tr>
+[% END %]
+
+[% BLOCK display_type %]
+ [% request.type FILTER html %]
+[% END %]
+
+[% BLOCK display_status %]
+ [% request.status %]
+[% END %]
+
+[% BLOCK display_bug %]
+ <a href="show_bug.cgi?id=[% request.bug_id %]"
+ [%- ' class="bz_secure"' IF request.restricted %]>
+ [% request.bug_id %]: [%+ request.bug_summary FILTER html %]</a>
+[% END %]
+
+[% BLOCK display_attachment %]
+ [% IF request.attach_id %]
+ <a href="attachment.cgi?id=[% request.attach_id %]&amp;action=edit">
+ [% request.attach_id %]: [%+ request.attach_summary FILTER html %]</a>
+ [% ELSE %]
+ N/A
+ [% END %]
+[% END %]
+
+[% BLOCK display_requestee %]
+ [% request.requestee FILTER email FILTER html %]
+[% END %]
+
+[% BLOCK display_requester %]
+ [% request.requester FILTER email FILTER html %]
+[% END %]
+
+[% BLOCK display_created %]
+ [% request.created FILTER time %]
+[% END %]
+
+[% BLOCK display_buglist %]
+ </table>
+ [% NEXT UNLESS buglist.keys.size %]
+ <a href="buglist.cgi?bug_id=
+ [%- buglist.keys.nsort.join(",") FILTER html %]">(view as
+ [%+ terms.bug %] list)</a>
+[% END %]
diff --git a/template/en/default/search/boolean-charts.html.tmpl b/template/en/default/search/boolean-charts.html.tmpl
new file mode 100644
index 000000000..90b5c790d
--- /dev/null
+++ b/template/en/default/search/boolean-charts.html.tmpl
@@ -0,0 +1,143 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ #%]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+[% types = [
+ "noop",
+ "equals",
+ "notequals",
+ "anyexact",
+ "substring",
+ "casesubstring",
+ "notsubstring",
+ "anywordssubstr",
+ "allwordssubstr",
+ "nowordssubstr",
+ "regexp",
+ "notregexp",
+ "lessthan",
+ "lessthaneq",
+ "greaterthan",
+ "greaterthaneq",
+ "anywords",
+ "allwords",
+ "nowords",
+ "changedbefore",
+ "changedafter",
+ "changedfrom",
+ "changedto",
+ "changedby",
+ "matches",
+ "notmatches",
+] %]
+<script type="text/javascript">
+ TUI_alternates['custom_search_query'] = '&#9658;';
+ TUI_hide_default('custom_search_query');
+</script>
+<div class="bz_section_title" id="custom_search_filter">
+ <div id="custom_search_query_controller" class="arrow">&#9660;</div>
+ <a id="chart" href="javascript:TUI_toggle_class('custom_search_query')" >
+ Custom Search</a> <span class="section_help">Didn't find what
+ you're looking for above? This area allows for ANDs, ORs,
+ and other more complex searches.</span>
+ </div>
+ <div id="custom_search_filter_section" class="bz_search_section custom_search_query" >
+[%# Whoever wrote the original version of boolean charts had a seriously twisted mind %]
+
+[% jsmagic = "onclick=\"this.form.action='query.cgi#chart'; this.form.method='POST'; return 1;\"" %]
+
+[% FOREACH chart = default.charts %]
+ [% chartnum = loop.count - 1 %]
+ <table>
+ <tr>
+ <td>
+ <input type="checkbox" id="negate[% chartnum FILTER html %]"
+ name="negate[% chartnum FILTER html %]" value="1"
+ [%+ "checked" IF chart.negate %]>
+ <label for="negate[% chartnum FILTER html %]">
+ Not (negate this whole chart)
+ </label>
+ </td>
+ </tr>
+ [% FOREACH row = chart.rows %]
+ [% rownum = loop.count - 1 %]
+ <tr>
+ [% FOREACH col = row %]
+ [% colnum = loop.count - 1 %]
+ <td>
+ <select name="[% "field${chartnum}-${rownum}-${colnum}" %]">
+ [% FOREACH field = fields %]
+ <option value="[% field.name %]" [% "selected" IF field.name == col.field %]>
+ [% field_descs.${field.name} || field.description FILTER html %]
+ </option>
+ [% END %]
+ </select>
+
+ [% INCLUDE "search/type-select.html.tmpl"
+ name = "type${chartnum}-${rownum}-${colnum}",
+ types = types, selected = col.type %]
+ <input name="[% "value${chartnum}-${rownum}-${colnum}" %]"
+ value="[% col.value FILTER html %]">
+ </td>
+
+ [% UNLESS loop.last %]
+ <td align="center">
+ Or
+ </td>
+ </tr>
+ <tr>
+ [% ELSE %]
+ <td>
+ [% newor = colnum + 1 %]
+ <input type="submit" value="Or" [% jsmagic %]
+ name="cmd-add[% "${chartnum}-${rownum}-${newor}" %]"
+ id="cmd-add[% "${chartnum}-${rownum}-${newor}" %]">
+ </td>
+ [% END %]
+
+ [% END %]
+ </tr>
+
+ [% UNLESS loop.last %]
+ <tr>
+ <td>And</td>
+ </tr>
+ [% ELSE %]
+ <tr>
+ <td>
+ [% newand = rownum + 1; newchart = chartnum + 1 %]
+ <input type="submit" value="And" [% jsmagic %]
+ name="cmd-add[% "${chartnum}-${newand}-0" %]"
+ id="cmd-add[% "${chartnum}-${newand}-0" %]">
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+ <input type="submit" value="Add another boolean chart" [% jsmagic %]
+ name="cmd-add[% newchart %]-0-0"
+ id="cmd-add[% newchart %]-0-0">
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+ </td>
+ </tr>
+ [% END %]
+
+ [% END %]
+ </table>
+ [% "<hr>" IF NOT loop.last %]
+[% END %]
+</div> \ No newline at end of file
diff --git a/template/en/default/search/field.html.tmpl b/template/en/default/search/field.html.tmpl
new file mode 100644
index 000000000..defc94cc3
--- /dev/null
+++ b/template/en/default/search/field.html.tmpl
@@ -0,0 +1,142 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Guy Pyrzak
+ # Portions created by the Initial Developer are Copyright (C) 2010 the
+ # Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s): Guy Pyrzak <guy.pyrzak@gmail.com>
+ # Reed Loden <reed@reedloden.com>
+ #
+ #%]
+[%# INTERFACE:
+ # field: a Bugzilla::Field object
+ # value: the value or values that should be used to prepopulate the field
+ # accesskey: the access key used to access the field more quickly
+ # onchange: js to run when the change event fires on the field
+ # type_selected: used by the free text to indicate which type of text
+ # search was selected for a particular field
+ #%]
+
+[% SWITCH field.type %]
+ [% CASE [ constants.FIELD_TYPE_FREETEXT,
+ constants.FIELD_TYPE_TEXTAREA,
+ constants.FIELD_TYPE_UNKNOWN ] %]
+ [% INCLUDE "bug/field-label.html.tmpl"
+ field = field
+ tag_name = "span"
+ editable = 1
+ %]
+ [% INCLUDE "search/type-select.html.tmpl"
+ name = field.name _ "_type",
+ types = types,
+ selected = type_selected
+ %]
+ <input name="[% field.name FILTER html %]"
+ id="[% field.name FILTER html %]" size="40"
+ [% IF onchange %] onchange="[% onchange FILTER html %]"[% END %]
+ value="[% value FILTER html %]">
+ [% CASE constants.FIELD_TYPE_KEYWORDS %]
+ [% INCLUDE "bug/field-label.html.tmpl"
+ field = field
+ tag_name = "span"
+ editable = 1
+ %]
+ [% INCLUDE "search/type-select.html.tmpl"
+ name = field.name _ "_type",
+ types = types,
+ selected = type_selected
+ %]
+ <div id="keyword_container">
+ <input name="[% field.name FILTER html %]"
+ id="[% field.name FILTER html %]" size="40"
+ [% IF onchange %] onchange="[% onchange FILTER html %]"[% END %]
+ value="[% value FILTER html %]">
+ <div id="keyword_autocomplete"></div>
+ </div>
+ <script type="text/javascript" defer="defer">
+ YAHOO.bugzilla.keyword_array = [
+ [%- FOREACH keyword = all_keywords %]
+ [%-# %]"[% keyword.name FILTER js %]"
+ [%- "," IF NOT loop.last %][% END %]];
+ YAHOO.bugzilla.keywordAutocomplete.init('[% field.name FILTER js %]',
+ 'keyword_autocomplete');
+ </script>
+ [% CASE constants.FIELD_TYPE_DATETIME %]
+ [% INCLUDE "bug/field-label.html.tmpl"
+ field = field
+ tag_name = "span"
+ editable = 1
+ %]
+ from <input name="[% field.name FILTER html %]from"
+ id="[% field.name FILTER html %]"
+ size="10" maxlength="10"
+ value="[% value.0 FILTER html %]"
+ onchange="updateCalendarFromField(this);[% onchange FILTER html %]">
+ <button type="button" class="calendar_button"
+ id="button_calendar_[% field.name FILTER html %]"
+ onclick="showCalendar('[% field.name FILTER js %]')">
+ <span>Calendar</span>
+ </button>
+ <span id="con_calendar_[% field.name FILTER html %]"></span>
+ to <input name="[% field.name FILTER html %]to"
+ id="[% field.name FILTER html %]to" size="10" maxlength="10"
+ value="[% value.1 FILTER html %]"
+ onchange="updateCalendarFromField(this);[% onchange FILTER html %]">
+ <button type="button" class="calendar_button"
+ id="button_calendar_[% field.name FILTER html %]to"
+ onclick="showCalendar('[% field.name FILTER js %]to')">
+ <span>Calendar</span>
+ </button>
+ <small>(YYYY-MM-DD or relative dates)</small>
+
+ <span id="con_calendar_[% field.name FILTER html %]to"></span>
+ <script type="text/javascript">
+ createCalendar('[% field.name FILTER js %]');
+ createCalendar('[% field.name FILTER js %]to');
+ </script>
+ [% CASE [ constants.FIELD_TYPE_SINGLE_SELECT,
+ constants.FIELD_TYPE_MULTI_SELECT ] %]
+ <div id="container_[% field.name FILTER html %]" class="search_field_grid">
+ [% INCLUDE "bug/field-label.html.tmpl"
+ field = field
+ editable = 1
+ tag_name = "span"
+ %]
+ <select name="[% field.name FILTER html%]"
+ id="[% field.name FILTER html %]"
+ [% IF onchange %] onchange="[% onchange FILTER html %]"[% END %]
+ multiple="multiple" size="7">
+ [% legal_values = ${field.name} %]
+ [% IF field.name == "component" %]
+ [% legal_values = ${"component_"} %]
+ [% END %]
+ [% FOREACH current_value = legal_values %]
+ [% IF current_value.id %]
+ [%# current_value is a hash instead of a value which
+ only applies for Resolution really, everywhere else current_value
+ is just the value %]
+ [% v = current_value.name OR '---' -%]
+ <option value="[% v FILTER html %]"
+ [% ' selected="selected"' IF value.contains( v ) %]>
+ [% display_value(field.name, current_value.name) FILTER html %]
+ </option>
+ [% ELSE %]
+ <option value="[% current_value OR '---' FILTER html %]"
+ [% ' selected="selected"' IF value.contains( current_value ) %]>
+ [% display_value(field.name, current_value) FILTER html %]
+ </option>
+ [% END %]
+ [% END %]
+ </select>
+ </div>
+ [% END %]
diff --git a/template/en/default/search/form.html.tmpl b/template/en/default/search/form.html.tmpl
new file mode 100644
index 000000000..f519c8f69
--- /dev/null
+++ b/template/en/default/search/form.html.tmpl
@@ -0,0 +1,424 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Chris Lahey <clahey@ximian.com> [javascript fixes]
+ # Christian Reis <kiko@async.com.br> [javascript rewrite]
+ # Gervase Markham <gerv@gerv.net>
+ # Guy Pyrzak <guy.pyrzak@gmail.com>
+ #%]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+<script type="text/javascript">
+
+var first_load = true; [%# is this the first time we load the page? %]
+var last_sel = new Array(); [%# caches last selection %]
+
+[% IF Param('useclassification') %]
+var useclassification = true;
+var prods = new Array();
+[% ELSE %]
+var useclassification = false;
+[% END %]
+var cpts = new Array();
+var vers = new Array();
+[% IF Param('usetargetmilestone') %]
+var tms = new Array();
+[% END %]
+
+[%# Create an array of products, indexed by the classification #%]
+
+[% nclass = 0 %]
+[% FOREACH c = classification %]
+ prods[[% nclass FILTER js %]] = [
+ [% sep = '' %]
+ [%- FOREACH item = user.get_selectable_products(c.id) -%]
+ [%- IF item.components.size -%]
+ [%- sep FILTER js %]'[% item.name FILTER js %]'
+ [%- sep = ',' -%]
+ [%- END -%]
+ [%- END -%] ];
+ [% nclass = nclass+1 %]
+[% END %]
+
+[%# Create three arrays of components, versions and target milestones, indexed
+ # numerically according to the product they refer to. #%]
+
+[% n = 0 %]
+[% FOREACH p = product %]
+ [% NEXT IF NOT p.components.size %]
+ [% IF Param('useclassification') %]
+ prods['[% p.name FILTER js %]'] = [% n %]
+ [% END %]
+ cpts[[% n %]] = [
+ [%- FOREACH item = p.components %]'[% item.name FILTER js %]'[% ", " UNLESS loop.last %] [%- END -%] ];
+ vers[[% n %]] = [
+ [%- FOREACH item = p.versions -%]'[% item.name FILTER js %]'[% ", " UNLESS loop.last %] [%- END -%] ];
+ [% IF Param('usetargetmilestone') %]
+ tms[[% n %]] = [
+ [%- FOREACH item = p.milestones %]'[% item.name FILTER js %]'[% ", " UNLESS loop.last %] [%- END -%] ];
+ [% END %]
+ [% n = n+1 %]
+[% END %]
+
+/*
+ * doOnSelectProduct determines which selection should get updated
+ *
+ * - selectmode = 0 - init
+ * selectmode = 1 - classification selected
+ * selectmode = 2 - product selected
+ *
+ * globals:
+ * queryform - string holding the name of the selection form
+ */
+function doOnSelectProduct(selectmode) {
+ var f = document.forms[queryform];
+ var milestone = (typeof(f.target_milestone) == "undefined" ?
+ null : f.target_milestone);
+ if (selectmode == 0) {
+ // If there is no classification selected, give us a chance to fill
+ // the select fields with values from the possibly selected product.
+ if (useclassification && f.classification.selectedIndex > -1) {
+ selectClassification(f.classification, f.product, f.component, f.version, milestone);
+ } else {
+ selectProduct(f.product, f.component, f.version, milestone, null);
+ }
+ } else if (selectmode == 1) {
+ selectClassification(f.classification, f.product, f.component, f.version, milestone);
+ } else {
+ selectProduct(f.product, f.component, f.version, milestone, null);
+ }
+}
+
+// Hide the Advanced Fields by default, unless the user has a cookie
+// that specifies otherwise.
+// &#9656; and &#9662; are both utf8 escaped characters for right
+// and down facing arrows respectivly.
+TUI_alternates['history_query'] = '&#9658;';
+TUI_alternates['people_query'] = '&#9658;';
+TUI_alternates['information_query'] = '&#9658;';
+
+TUI_hide_default('history_query');
+TUI_hide_default('people_query');
+TUI_hide_default('information_query');
+</script>
+
+[% query_types = [
+ "allwordssubstr",
+ "anywordssubstr",
+ "substring",
+ "casesubstring",
+ "allwords",
+ "anywords",
+ "regexp",
+ "notregexp",
+] %]
+
+[%# If we resubmit to ourselves, we need to know if we are using a format. %]
+[% thisformat = query_format != '' ? query_format : format %]
+<input type="hidden" name="query_format" value="[% thisformat FILTER html %]">
+
+[%# *** Summary *** %]
+
+ <div class="search_field_row" id="summary_field">
+ [% INCLUDE "search/field.html.tmpl"
+ field = bug_fields.short_desc
+ types = query_types
+ value = default.short_desc.0
+ type_selected = default.short_desc_type.0
+ accesskey = "s"
+ %]
+ <script type="text/javascript"> <!--
+ document.forms[queryform].short_desc.focus();
+ // -->
+ </script>
+
+ [% IF button_name %]
+ <input type="submit" id="[% button_name FILTER css_class_quote %]_top"
+ value="[% button_name FILTER html %]">
+ [% END %]
+ </div>
+
+[%# *** Classification Product Component *** %]
+
+[% Hook.process('before_selects_top') %]
+[% IF Param('useclassification') %]
+ [% fake_classfication = { name => bug_fields.classification.name,
+ type => constants.FIELD_TYPE_SINGLE_SELECT } %]
+ [% INCLUDE "search/field.html.tmpl"
+ field => fake_classfication
+ accesskey => "c"
+ onchange => "doOnSelectProduct(1);"
+ value => default.classification
+ %]
+[% END %]
+
+[% INCLUDE "search/field.html.tmpl"
+ field => bug_fields.product
+ accesskey => "p"
+ onchange => "doOnSelectProduct(2);"
+ value => default.product
+%]
+[% INCLUDE "search/field.html.tmpl"
+ field => bug_fields.component
+ accesskey => "m"
+ onchange => "doOnSelectProduct(2);"
+ value => default.component
+%]
+[% INCLUDE "search/field.html.tmpl"
+ field => bug_fields.bug_status
+ accesskey => "a"
+ value => default.bug_status
+%]
+[% INCLUDE "search/field.html.tmpl"
+ field => bug_fields.resolution
+ accesskey => "r"
+ value => default.resolution
+%]
+
+[% Hook.process('after_selects_top') %]
+
+<div id="detailed_information" class="bz_section_title">
+ <div id="information_query_controller" class="arrow">&#9660;</div>
+ <a href="javascript:TUI_toggle_class('information_query')">
+ Detailed [% terms.Bug %] Information
+ </a>
+ <span class="section_help">Narrow results by the following fields:
+ [%+ field_descs.longdesc FILTER html %]s, [%+ field_descs.bug_file_loc FILTER html %],
+ [% IF Param('usestatuswhiteboard') %] [%+ field_descs.status_whiteboard FILTER html %], [%+ END %]
+ [% IF use_keywords %] [%+ field_descs.keywords FILTER html %], [%+ END %]
+ [% IF user.is_timetracker %] [%+ field_descs.deadline FILTER html %], [%+ END %]
+ [% terms.Bug %] Numbers, [%+ field_descs.version FILTER html %],
+ [% IF Param('usetargetmilestone') %] [%+ field_descs.target_milestone FILTER html %], [%+ END %]
+ [% field_descs.bug_severity FILTER html %], [%+ field_descs.priority FILTER html %], [%+ field_descs.rep_platform FILTER html %],
+ [%+ field_descs.op_sys FILTER html %]
+ </span>
+</div>
+[%# *** Comment URL Whiteboard Keywords *** %]
+<div id="detailed_information_section" class="bz_search_section information_query">
+ [% SET freetext_fields = [
+ { field => bug_fields.longdesc, accesskey => 'c' },
+ { field => bug_fields.bug_file_loc, accesskey => 'u' },
+ { field => bug_fields.status_whiteboard, accesskey => 'w' },
+ { field => bug_fields.keywords, accesskey => 'k',
+ qtypes => ['allwords', 'anywords', 'nowords', 'regexp', 'notregexp'] }
+ ] %]
+ [% Hook.process('before_freetext_fields') %]
+
+ [%# loop through a bunch of free text fields and print out their text stuff %]
+ [% FOREACH field_container = freetext_fields %]
+ [% NEXT IF field_container.field.name == 'status_whiteboard'
+ AND NOT Param('usestatuswhiteboard')
+ %]
+ [% NEXT IF field_container.field.name == 'keywords'
+ AND NOT use_keywords
+ %]
+ <div class="search_field_row">
+ [% type = field_container.field.name _ "_type" %]
+ [% INCLUDE "search/field.html.tmpl"
+ field => field_container.field
+ types => field_container.qtypes || query_types
+ accesskey => field_container.accesskey
+ value => default.${field_container.field.name}.0
+ type_selected => default.$type.0
+ %]
+ </div>
+ [% END %]
+
+ [%# Deadline %]
+ [% IF user.is_timetracker %]
+ <div class="search_field_row">
+ [% INCLUDE "search/field.html.tmpl"
+ field = bug_fields.deadline
+ accesskey = "l"
+ value = [ default.deadlinefrom.0, default.deadlineto.0 ]
+ %]
+ </div>
+ [% END %]
+
+ <div class="search_field_row">
+ <span class="field_label"><label for="bug_id">[% terms.Bugs %] numbered</label></span>
+ <div id="bug_id_container" >
+ <input type="text" name="bug_id" id="bug_id"
+ value="[% default.bug_id.0 FILTER html %]" size="20">
+ <div class="field_help">(comma-separated list)</div>
+ </div>
+ should be
+ <select name="bug_id_type" id="bug_id_type">
+ <option value="anyexact"[% " selected" IF default.bug_id_type.0 == "anyexact" %]>only included in</option>
+ <option value="nowords"[% " selected" IF default.bug_id_type.0 == "nowords" %]>excluded from</option>
+ </select> the results
+ </div>
+
+ [% Hook.process('after_freetext_fields') %]
+
+ [%# *** Status Resolution Severity Priority Hardware OS *** %]
+ <div>
+ [% Hook.process('before_selects_bottom') %]
+ [% fake_version_field = { name => bug_fields.version.name,
+ type => constants.FIELD_TYPE_SINGLE_SELECT }%]
+ [% INCLUDE "search/field.html.tmpl"
+ field => fake_version_field
+ value => default.version
+ %]
+ [% IF Param('usetargetmilestone') %]
+ [% fake_target_milestone_field = { name => bug_fields.target_milestone.name ,
+ type => constants.FIELD_TYPE_SINGLE_SELECT } %]
+ [% INCLUDE "search/field.html.tmpl"
+ field => fake_target_milestone_field
+ value => default.target_milestone
+ %]
+ [% END %]
+ [% INCLUDE "search/field.html.tmpl"
+ field => bug_fields.bug_severity
+ accesskey=> "v"
+ value => default.bug_severity
+ %]
+ [% INCLUDE "search/field.html.tmpl"
+ field => bug_fields.priority
+ accesskey => "i"
+ value => default.priority
+ %]
+ [% INCLUDE "search/field.html.tmpl"
+ field => bug_fields.rep_platform
+ accesskey =>"h"
+ value => default.rep_platform
+ %]
+ [% INCLUDE "search/field.html.tmpl"
+ field => bug_fields.op_sys
+ accesskey =>"o"
+ value => default.op_sys
+ %]
+ [% Hook.process('after_selects_bottom') %]
+ </div>
+</div>
+[%# *** Email Numbering *** %]
+ <div class="bz_section_title" id="people_filter">
+ <div id="people_query_controller" class="arrow">&#9660;</div>
+ <a href="javascript:TUI_toggle_class('people_query')">Search By People</a>
+ <span>Narrow results to a role (i.e. [% field_descs.assigned_to FILTER html %],
+ [%+ field_descs.reporter FILTER html %], [% field_descs.commenter FILTER html %],
+ etc.) a person has on [% terms.abug %]
+ </span>
+ </div>
+ <div id="people_filter_section" class="bz_search_section people_query">
+ [% FOREACH n = [1, 2, 3] %]
+ <div class="search_email_fields">
+ Any of:
+ [% PROCESS role_types field = { count => n, name => "emailassigned_to",
+ label=> "the ${terms.Bug} ${field_descs.assigned_to}" } %]
+ [% PROCESS role_types field = { count => n, name => "emailreporter",
+ label=> "the ${field_descs.reporter}" } %]
+ [% IF Param('useqacontact') %]
+ [% PROCESS role_types field = { count => n, name => "emailqa_contact",
+ label=> "the ${field_descs.qa_contact}" } %]
+ [% END %]
+ [% PROCESS role_types field = { count => n, name => "emailcc",
+ label=> "a ${field_descs.cc} list member" } %]
+ [% PROCESS role_types field = { count => n, name => "emaillongdesc",
+ label=> " a ${field_descs.commenter}" } %]
+ <select name="emailtype[% n %]">
+ [% FOREACH qv = [
+ { name => "substring", description => "contains" },
+ { name => "exact", description => "is" },
+ { name => "notequals", description => "is not" },
+ { name => "regexp", description => "matches regexp" },
+ { name => "notregexp", description => "doesn't match regexp" } ] %]
+ <option value="[% qv.name %]"
+ [% " selected" IF default.emailtype.$n == qv.name %]>[% qv.description %]</option>
+ [% END %]
+ </select>
+ [% IF feature_enabled('jsonrpc') %]
+ <div id="email[% n %]_autocomplete">
+ [% END %]
+ <input name="email[% n %]" class="email" id="email[% n %]"
+ value="[% default.email.$n FILTER html %]">
+ [% IF feature_enabled('jsonrpc') %]
+ <div id="email[% n %]_autocomplete_container"></div>
+ </div>
+ <script type="text/javascript">
+ YAHOO.bugzilla.userAutocomplete.init( "email[% n %]",
+ "email[% n %]_autocomplete_container");
+ </script>
+ [% END %]
+ </div>
+ [% END %]
+ [% Hook.process('email_numbering_end') %]
+ </div>
+[%# *** Bug Changes *** %]
+<div class="bz_section_title" id="history_filter">
+ <div id="history_query_controller" class="arrow">&#9660;</div>
+ <a href="javascript:TUI_toggle_class('history_query')" >Search By Change History</a>
+ <span>Narrow results to how fields have changed during a specific time period</span>
+</div>
+<ul class="bug_changes bz_search_section history_query" id="history_filter_section" >
+ <li>
+ <label for="chfield">where ANY of the fields:</label>
+ [%# Create array, so we can sort it by description #%]
+ [% chfields = [] %]
+ [% FOREACH field = chfield %]
+ [% chfields.push({value => field, desc => (field_descs.$field || field) }) %]
+ [% END %]
+ <select name="chfield" id="chfield" multiple="multiple" size="4">
+ [% FOREACH field = chfields.sort('desc') %]
+ <option value="[% field.value FILTER html %]"
+ [% " selected" IF default.chfield.contains(field.value) %]>
+ [% field.desc FILTER html %]</option>
+ [% END %]
+ </select>
+ </li>
+ <li>
+ <label for="chfieldvalue">[% search_descs.changedto FILTER html %]:</label>
+ <input name="chfieldvalue" id="chfieldvalue"
+ size="20" value="[% default.chfieldvalue.0 FILTER html %]">
+ </li>
+ <li>
+ <label for="chfieldfrom">between:</label>
+ <input name="chfieldfrom" id="chfieldfrom" size="10"
+ value="[% default.chfieldfrom.0 FILTER html %]" onchange="updateCalendarFromField(this)">
+ <button type="button" class="calendar_button"
+ id="button_calendar_chfieldfrom"
+ onclick="showCalendar('chfieldfrom')"><span>Calendar</span></button>
+ and
+ <div id="con_calendar_chfieldfrom"></div>
+ <input name="chfieldto" size="10" id="chfieldto"
+ value="[% default.chfieldto.0 FILTER html %]" onchange="updateCalendarFromField(this)">
+ <button type="button" class="calendar_button"
+ id="button_calendar_chfieldto"
+ onclick="showCalendar('chfieldto')"><span>Calendar</span></button>
+ <div id="con_calendar_chfieldto"></div>
+ (YYYY-MM-DD or relative dates)
+ <script type="text/javascript">
+ createCalendar('chfieldfrom');
+ createCalendar('chfieldto');
+ </script>
+ </li>
+</ul>
+
+[%############################################################################%]
+[%# Block for email role type use to select which email to search through #%]
+[%############################################################################%]
+[% BLOCK role_types %]
+ <div class="role_type">
+ <input type="checkbox" name="[% field.name _ field.count FILTER html %]"
+ id="[% field.name _ field.count FILTER html %]" value="1"
+ [% " checked" IF default.${field.name}.${field.count} %]>
+ <label for="[% field.name _ field.count FILTER html%]">
+ [% field.label FILTER html%]
+ </label>
+ </div>
+[% END %]
diff --git a/template/en/default/search/knob.html.tmpl b/template/en/default/search/knob.html.tmpl
new file mode 100644
index 000000000..17ff63a10
--- /dev/null
+++ b/template/en/default/search/knob.html.tmpl
@@ -0,0 +1,85 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ # Tobias Burnus <burnus@net-b.de>
+ # Jouni Heikniemi <jouni@heikniemi.net>
+ #%]
+
+[%# INTERFACE:
+ # (incomplete!)
+ # ...
+ # known_name: string. Possibly known stored name for the query being
+ # edited. This value is just passed through in a
+ # hidden field.
+ #%]
+
+
+[% PROCESS global/variables.none.tmpl %]
+
+[%# This is not necessary for English templates, but useful for localizers. %]
+[% ordersdesc = {
+ "Reuse same sort as last time" => "Reuse same sort as last time",
+ "Bug Number" => "$terms.Bug Number",
+ "Importance" => "Importance",
+ "Assignee" => "Assignee",
+ "Last Changed" => "Last Changed" } %]
+
+<input type="hidden" name="cmdtype" value="doit">
+
+<p>
+ <label for="order">Sort results by</label>:
+ <select name="order" id="order">
+ [% FOREACH order = orders %]
+ <option value="[% order FILTER html %]"
+ [% " selected" IF default.order.0 == order %]>
+ [% ordersdesc.$order FILTER html %]</option>
+ [% END %]
+ </select>
+</p>
+
+<p>
+ <input type="submit" id="[% button_name FILTER html %]"
+ value="[% button_name FILTER html %]">
+ [% IF known_name %]
+ [%# We store known_name in case the user add a boolean chart. %]
+ <input type="hidden" name="known_name" value="[% known_name FILTER html %]">
+
+ [%# The name of the existing query will be passed to buglist.cgi. %]
+ <input type="hidden" name="query_based_on" value="[% known_name FILTER html %]">
+ [% END %]
+ [%# Preserve any custom column list that might be set. %]
+ [% IF columnlist %]
+ <input type="hidden" name="columnlist" value="[% columnlist FILTER html %]">
+ [% END %]
+</p>
+
+<p>
+ &nbsp;&nbsp;&nbsp;
+ <input type="checkbox" id="remasdefault"
+ name="remtype" value="asdefault">
+ <label for="remasdefault">
+ and remember these as my default search options
+ </label>
+</p>
+
+[% IF userdefaultquery %]
+ <p>
+ <a href="query.cgi?nukedefaultquery=1">
+ Set my default search back to the system default</a>.
+ </p>
+[% END %]
diff --git a/template/en/default/search/search-advanced.html.tmpl b/template/en/default/search/search-advanced.html.tmpl
new file mode 100644
index 000000000..ef7fa769a
--- /dev/null
+++ b/template/en/default/search/search-advanced.html.tmpl
@@ -0,0 +1,69 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ #%]
+
+[%# INTERFACE:
+ # This template has no interface. However, to use it, you need to fulfill
+ # the interfaces of search/form.html.tmpl, search/knob.html.tmpl and
+ # search/boolean-charts.html.tmpl.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% USE Bugzilla %]
+[% cgi = Bugzilla.cgi %]
+
+
+[% js_data = BLOCK %]
+var queryform = "queryform"
+[% END %]
+
+[% PROCESS global/header.html.tmpl
+ title = "Search for $terms.bugs"
+ onload = "doOnSelectProduct(0);"
+ javascript = js_data
+ yui = [ 'autocomplete', 'calendar' ]
+ javascript_urls = [ "js/productform.js", "js/util.js", "js/TUI.js", "js/field.js"]
+ style_urls = [ "skins/standard/search_form.css" ]
+ doc_section = "query.html"
+ style = "dl.bug_changes dt {
+ margin-top: 15px;
+ }"
+%]
+
+[% WRAPPER search/tabs.html.tmpl %]
+
+[% button_name = "Search" %]
+
+<p id="search_help">Hover your mouse over each field label to get help for that field.</p>
+
+<form method="post" action="buglist.cgi" name="queryform" id="queryform">
+
+[% PROCESS search/form.html.tmpl %]
+
+[% PROCESS "search/boolean-charts.html.tmpl" %]
+
+[% PROCESS search/knob.html.tmpl %]
+
+</form>
+
+
+[% END %]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/search/search-create-series.html.tmpl b/template/en/default/search/search-create-series.html.tmpl
new file mode 100644
index 000000000..468324abd
--- /dev/null
+++ b/template/en/default/search/search-create-series.html.tmpl
@@ -0,0 +1,71 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ #%]
+
+[%# INTERFACE:
+ # This template has no interface. However, to use it, you need to fulfill
+ # the interfaces of search/form.html.tmpl, reports/series.html.tmpl and
+ # search/boolean-charts.html.tmpl.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% js_data = BLOCK %]
+ var queryform = "chartform";
+[% END %]
+
+[% PROCESS global/header.html.tmpl
+ title = "Create New Data Set"
+ onload = "doOnSelectProduct(0);"
+ yui = [ 'autocomplete', 'calendar' ]
+ javascript = js_data
+ javascript_urls = [ "js/productform.js", "js/TUI.js", "js/field.js" ]
+ style_urls = [ "skins/standard/search_form.css" ]
+ doc_section = "reporting.html#charts-new-series"
+%]
+
+<form method="get" action="chart.cgi" name="chartform">
+
+[% PROCESS search/form.html.tmpl %]
+
+<p>
+ <input type="submit" name="action-search" value="Run Search">
+ to see which [% terms.bugs %] would be included in this data set.
+</p>
+
+<h3>Data Set Parameters</h3>
+
+[% PROCESS reports/series.html.tmpl
+ button_name = "Create Data Set" %]
+ <input type="hidden" name="action" value="create">
+ <input type="hidden" name="token" value="[% issue_hash_token(['create-series']) FILTER html %]">
+
+<script type="text/javascript">
+ document.chartform.category[0].selected = true;
+ catSelected();
+ checkNewState();
+</script>
+
+<hr>
+
+[% PROCESS "search/boolean-charts.html.tmpl" %]
+
+</form>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/search/search-plugin.xml.tmpl b/template/en/default/search/search-plugin.xml.tmpl
new file mode 100644
index 000000000..8564dca47
--- /dev/null
+++ b/template/en/default/search/search-plugin.xml.tmpl
@@ -0,0 +1,28 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
+ #
+ #%]
+[% PROCESS global/variables.none.tmpl %]
+<?xml version="1.0" encoding="UTF-8"?>
+<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
+<ShortName>[% terms.Bugzilla %]</ShortName>
+<Description>[% terms.Bugzilla %] Quick Search</Description>
+<InputEncoding>UTF-8</InputEncoding>
+[% IF favicon %]
+ <Image width="16" height="16">data:image/x-icon;base64,[% favicon FILTER base64 %]</Image>
+[% ELSE %]
+ <Image width="16" height="16">data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAABGdBTUEAAK%2FINwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAALBSURBVHjaYnxckcEAA3%2F%2B%2FT%2F17LUcH%2Fevf%2F8U%2BHmYGBkZMABAALEgc%2B68%2F3T227cf2tJKKhJLt59n%2FfmbnYnZV1KEhYkJrgYggBghNrz78fPIi3d8uvKBIdb%2FOaWPnzitLc97%2Bc5rFXnhnVO3%2BslLwjUABBDIhnsfPl%2Fj53VO91FX4Gfgkjxw%2Fd%2F6Q49%2FWStqyAj%2B%2B88gZqn%2B9u5rYU52iAaAAGL69%2F%2F%2F2d9%2FYiMclGT4fv76%2BZ9DbO%2FeA39%2BfJHVcvj5l%2Bnh03e%2FWThOvnwLtwEgAAAxAM7%2FBPj8%2FRYkHQYHAf3%2F%2Fv%2F%2B%2Fv8BAVNTUPX18yorLNHE2S8mB%2FT2%2Bq7a4dvu8iUSDgAAAAKICRgUv3%2F8ZGKGeIvpz6eXBvq61lZWLMwMv%2F5zMP7%2FqSAjVFyZ%2FNvZftuT10DnAAQAMQDO%2FwQIBAPz5Or6%2Ff0CBQEAAgT99ubq38z2%2BwT18%2FAM%2F%2BkNDAv6%2FQMCAA1GVVrhMze5h4kCCORpkd9%2F3n74KiHO%2B%2BffX8b%2Ff7m%2BXWP985%2Bf5R%2BPLNdfoK%2F%2F%2Ffv39%2BePj2%2FkZYR0fe0BAgikQZGX%2B9b9FzLS%2FH%2F%2B%2FGVgYGRlZWNlA7nv7z9QuDP8%2B8nw%2FRXjn68Mv4Gu%2FAwQQCCni3FxPLn7nIGZGegfNhYmNjYWZnBMASOakZER6Eumf9%2FYGT4y%2FHx%2F%2BfBFgAAC2cDGzPT99WeGvwzvv%2Fx89vrr%2F39%2FJER4pcT5Gf4z%2FP37D2jtj9%2B%2FL918fmzrKSsWNoAAgiaN%2Fz9%2Fff%2F6S4CP8%2BWbz9vWHfv54aukpAAz0Og%2Ff%2F7%2F%2Bs36668cO3ugED9QJUAAQTUArf7%2F8x87D9vRjcejhPiZhAUYcACAAGI5%2FOHH9ddvXzAxmjz%2B8P8lw4fXn5l4eRlwA4AAYmaTkBFg%2FKvJwfbkwZuXN57y%2Fv%2F34stXGR4uRmxpGwgAAgwA4%2FkfrfCWvLQAAAAASUVORK5CYII%3D</Image>
+[% END %]
+<Url type="text/html" method="GET" template="[% urlbase FILTER xml %]buglist.cgi?quicksearch={searchTerms}"/>
+</OpenSearchDescription>
diff --git a/template/en/default/search/search-report-graph.html.tmpl b/template/en/default/search/search-report-graph.html.tmpl
new file mode 100644
index 000000000..3c894cf73
--- /dev/null
+++ b/template/en/default/search/search-report-graph.html.tmpl
@@ -0,0 +1,142 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ #%]
+
+[%# INTERFACE:
+ # This template has no interface. However, to use it, you need to fulfill
+ # the interfaces of the templates it contains.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% js_data = BLOCK %]
+var queryform = "reportform"
+[% END %]
+
+[% PROCESS global/header.html.tmpl
+ title = "Generate Graphical Report"
+ onload = "doOnSelectProduct(0); chartTypeChanged()"
+ yui = [ 'autocomplete', 'calendar' ]
+ javascript = js_data
+ javascript_urls = [ "js/util.js", "js/productform.js", "js/TUI.js", "js/field.js" ]
+ style_urls = [ "skins/standard/search_form.css" ]
+ doc_section = "reporting.html#reports"
+%]
+
+[% PROCESS "search/search-report-select.html.tmpl" %]
+
+<p>
+ Choose one or more fields as your axes, and then refine your set of
+ [% terms.bugs %] using the rest of the form.
+</p>
+
+<script type="text/javascript"><!--
+ [%# The Y-axis fields are not used for pie charts %]
+ function chartTypeChanged() {
+ // format[2] is the pie chart radio button
+ if (document.reportform.format[2].checked == true) {
+ document.reportform.y_axis_field.disabled = true;
+ document.reportform.cumulate[0].disabled = true;
+ document.reportform.cumulate[1].disabled = true;
+ } else {
+ document.reportform.y_axis_field.disabled = false;
+ document.reportform.cumulate[0].disabled = false;
+ document.reportform.cumulate[1].disabled = false;
+ }
+ }
+// -->
+</script>
+
+[% button_name = "Generate Report" %]
+
+<form method="get" action="report.cgi" name="reportform" id="reportform">
+
+<table align="center">
+ <tr>
+ <td valign="middle">
+ <b>Vertical Axis:</b><br>
+ <noscript><small>(not for pie charts)</small><br></noscript>
+ [% PROCESS select name = 'y_axis_field' %]<br>
+ <br>
+ <b>Plot Data Sets:</b><br>
+ <input type="radio" name="cumulate" value="0"
+ [% " checked" IF default.cumulate.0 != "1" %]>
+ Individually<br>
+ <input type="radio" name="cumulate" value="1"
+ [% " checked" IF default.cumulate.0 == "1" %]>
+ Stacked
+ </td>
+ <td width="150" height="150">
+ <table border="1" width="100%" height="100%">
+ <tr>
+ <td align="center" valign="middle">
+ <b>Multiple Images:</b><br>
+ [% PROCESS select name = 'z_axis_field' %]
+ </td>
+ </tr>
+ </table>
+ </td>
+ <td rowspan="2">
+ <b>Format:</b><br>
+ [% chart_formats = [
+ { name => "line", description => "Line Graph" },
+ { name => "bar", description => "Bar Chart" },
+ { name => "pie", description => "Pie Chart" } ] %]
+ [% default.chart_format.0 = default.chart_format.0 || "bar" %]
+
+ [% FOREACH chart_format = chart_formats %]
+ <input type="radio" name="format"
+ value="[% chart_format.name FILTER html %]"
+ onchange="chartTypeChanged()"
+ [% " checked" IF default.chart_format.0 == chart_format.name %]>
+ [% chart_format.description FILTER html %]<br>
+ [% END %]
+ </td>
+ </tr>
+
+ <tr>
+ <td>
+ </td>
+ <td align="left">
+ <b>Horizontal Axis:</b>
+ [% PROCESS select name = 'x_axis_field' %]<br>
+ <label for="x_labels_vertical"><b>Vertical labels:</b></label>
+ <input type="checkbox" name="x_labels_vertical" id="x_labels_vertical"
+ value="1"
+ [% " checked" IF default.x_labels_vertical.0 == "1" %]>
+ </td>
+ <td>
+ </td>
+ </tr>
+</table>
+
+<hr>
+
+[% PROCESS search/form.html.tmpl %]
+
+[% PROCESS "search/boolean-charts.html.tmpl" %]
+
+ <div id="knob">
+ <input type="submit" id="[% button_name FILTER css_class_quote %]"
+ value="[% button_name FILTER html %]">
+ <input type="hidden" name="action" value="wrap">
+ </div>
+</form>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/search/search-report-select.html.tmpl b/template/en/default/search/search-report-select.html.tmpl
new file mode 100644
index 000000000..5e5db06e2
--- /dev/null
+++ b/template/en/default/search/search-report-select.html.tmpl
@@ -0,0 +1,50 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ #%]
+
+[%# INTERFACE:
+ # name: string. The name of the select block to output.
+ # default.$name.0: string. The default value for the block, if any.
+ #%]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+[% BLOCK select %]
+ [% Hook.process('rep_fields', 'search/search-report-select.html.tmpl') %]
+
+ <select name="[% name FILTER html %]">
+ <option value="">&lt;none&gt;</option>
+
+ [% FOREACH field = report_columns.keys.sort %]
+ [% NEXT IF field == "classification" AND !Param('useclassification') %]
+ [% NEXT IF field == "target_milestone" AND !Param('usetargetmilestone') %]
+ [% NEXT IF field == "qa_contact" AND !Param('useqacontact') %]
+ <option value="[% field FILTER html %]"
+ [% " selected" IF default.$name.0 == field %]>
+ [% field_descs.$field || field FILTER html %]</option>
+ [% END %]
+
+ [%# Single-select fields are also valid column names. %]
+ [% FOREACH field = custom_fields %]
+ <option value="[% field.name FILTER html %]"
+ [% " selected" IF default.$name.0 == field.name %]>
+ [% field.description FILTER html %]</option>
+ [% END %]
+ </select>
+[% END %]
diff --git a/template/en/default/search/search-report-table.html.tmpl b/template/en/default/search/search-report-table.html.tmpl
new file mode 100644
index 000000000..7e087e7fe
--- /dev/null
+++ b/template/en/default/search/search-report-table.html.tmpl
@@ -0,0 +1,95 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ #%]
+
+[%# INTERFACE:
+ # This template has no interface. However, to use it, you need to fulfill
+ # the interfaces of the templates it contains.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% js_data = BLOCK %]
+var queryform = "reportform"
+[% END %]
+
+[% PROCESS global/header.html.tmpl
+ title = "Generate Tabular Report"
+ onload = "doOnSelectProduct(0)"
+ yui = [ 'autocomplete', 'calendar' ]
+ javascript = js_data
+ javascript_urls = [ "js/util.js", "js/productform.js", "js/TUI.js", "js/field.js" ]
+ style_urls = [ "skins/standard/search_form.css" ]
+ doc_section = "reporting.html#reports"
+%]
+
+[% PROCESS "search/search-report-select.html.tmpl" %]
+
+<p>
+ Choose one or more fields as your axes, and then refine your set of
+ [% terms.bugs %] using the rest of the form.
+</p>
+
+[% button_name = "Generate Report" %]
+
+<form method="get" action="report.cgi" name="reportform" id="reportform">
+
+<table align="center">
+ <tr>
+ <td>
+ </td>
+ <td align="center">
+ <b>Horizontal Axis:</b>
+ [% PROCESS select name = 'x_axis_field' %]
+ </td>
+ </tr>
+
+ <tr>
+ <td valign="middle" align="center">
+ <b>Vertical Axis:</b><br>
+ [% PROCESS select name = 'y_axis_field' %]
+ </td>
+ <td width="150" height="150">
+ <table border="1" width="100%">
+ <tr>
+ <td align="center" valign="middle" height="150">
+ <b>Multiple Tables:</b><br>
+ [% PROCESS select name = 'z_axis_field' %]
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+</table>
+
+<hr>
+
+ [% PROCESS search/form.html.tmpl %]
+
+ [% PROCESS "search/boolean-charts.html.tmpl" %]
+
+ <div id="knob">
+ <input type="submit" id="[% button_name FILTER css_class_quote %]"
+ value="[% button_name FILTER html %]">
+ <input type="hidden" name="format" value="table">
+ <input type="hidden" name="action" value="wrap">
+ </div>
+</form>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/search/search-specific.html.tmpl b/template/en/default/search/search-specific.html.tmpl
new file mode 100644
index 000000000..78e5506a0
--- /dev/null
+++ b/template/en/default/search/search-specific.html.tmpl
@@ -0,0 +1,129 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Myk Melez <myk@mozilla.org>
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% PROCESS global/header.html.tmpl
+ title = "Simple Search"
+%]
+
+[% WRAPPER search/tabs.html.tmpl %]
+
+<p>
+Find a specific [% terms.bug %] by entering words that describe it.
+[% terms.Bugzilla %] will search [% terms.bug %] descriptions and comments
+for those words and return a list of matching [% terms.bugs %] sorted
+by relevance.
+</p>
+
+<p>
+For example, if the [% terms.bug %] you are looking for is a browser crash when you go to a secure web site with an embedded Flash animation, you might search
+for "crash secure SSL flash".
+</p>
+
+<form name="queryform" method="get" action="buglist.cgi">
+<input type="hidden" name="query_format" value="specific">
+<input type="hidden" name="order" value="relevance desc">
+
+<table>
+ <tr>
+ <td align="right" valign="baseline">
+ <b><label for="bug_status">Status:</label></b>
+ </td>
+ <td>
+ <select name="bug_status" id="bug_status">
+ [% statuses = [ { name = 'open', label = "Open" },
+ { name = 'closed', label = "Closed" },
+ { name = 'all', label = "All" } ] %]
+ [% FOREACH status = statuses %]
+ <option value="__[% status.name %]__"
+ [% " selected" IF default.bug_status.0 == "__${status.name}__" %]>
+ [% status.label FILTER html %]
+ </option>
+ [% END %]
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <td align="right" valign="baseline">
+ <b><label for="product">Product:</label></b>
+ </td>
+ <td>
+ <select name="product" id="product">
+ <option value="">All</option>
+ [% IF Param('useclassification') %]
+ [% FOREACH c = classification %]
+ <optgroup label="[% c.name FILTER html %]">
+ [% FOREACH p = user.get_selectable_products(c.id) %]
+ [% IF p.components.size %]
+ <option value="[% p.name FILTER html %]"
+ [% " selected" IF lsearch(default.product, p.name) != -1 %]>
+ [% p.name FILTER html %]
+ </option>
+ [% END %]
+ [% END %]
+ </optgroup>
+ [% END %]
+ [% ELSE %]
+ [% FOREACH p = product %]
+ <option value="[% p.name FILTER html %]"
+ [% " selected" IF lsearch(default.product, p.name) != -1 %]>
+ [% p.name FILTER html %]
+ </option>
+ [% END %]
+ [% END %]
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <td align="right" valign="baseline">
+ <b><label for="content">Words:</label></b>
+ </td>
+ <td>
+ <input name="content" size="40" id="content"
+ value="[% default.content.0 FILTER html %]">
+ <script type="text/javascript"> <!--
+ document.forms['queryform'].content.focus();
+ // -->
+ </script>
+ </td>
+ </tr>
+ <tr>
+ <td></td>
+ <td>
+
+ [% IF Param('specific_search_allow_empty_words') %]
+ <input type="submit" id="search" value="Search">
+ [% ELSE %]
+ <input type="submit" id="search" value="Search"
+ onclick="if (this.form.content.value == '')
+ {alert('The Words field cannot be empty. You have to ' +
+ 'enter at least one word in your search criteria.');
+ return false;} return true;">
+ [% END %]
+ </td>
+ </tr>
+</table>
+</form>
+
+[% END %]
+
+[% PROCESS global/footer.html.tmpl %]
+
diff --git a/template/en/default/search/tabs.html.tmpl b/template/en/default/search/tabs.html.tmpl
new file mode 100644
index 000000000..119b30fde
--- /dev/null
+++ b/template/en/default/search/tabs.html.tmpl
@@ -0,0 +1,36 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ # Myk Melez <myk@mozilla.org>
+ #%]
+
+[%# INTERFACE:
+ # This template has no interface.
+ #%]
+
+[% WRAPPER global/tabs.html.tmpl
+ tabs = [ { name => 'specific', label => "Simple Search",
+ link => "query.cgi?format=specific" },
+ { name => 'advanced', label => "Advanced Search",
+ link => "query.cgi?format=advanced" } ]
+ current_tab_name = query_format || format || "advanced"
+%]
+
+[% content %]
+
+[% END %]
diff --git a/template/en/default/search/type-select.html.tmpl b/template/en/default/search/type-select.html.tmpl
new file mode 100644
index 000000000..043c4194a
--- /dev/null
+++ b/template/en/default/search/type-select.html.tmpl
@@ -0,0 +1,29 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is the San Jose State
+ # University Foundation. Portions created by the Initial Developer are
+ # Copyright (C) 2008 the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+<select name="[% name FILTER html %]">
+ [% FOREACH type = types %]
+ <option value="[% type FILTER html %]"
+ [%- ' selected="selected"' IF type == selected %]>
+ [%- search_descs.$type FILTER html %]</option>
+ [% END %]
+</select>
diff --git a/template/en/default/setup/strings.txt.pl b/template/en/default/setup/strings.txt.pl
new file mode 100644
index 000000000..5824f9579
--- /dev/null
+++ b/template/en/default/setup/strings.txt.pl
@@ -0,0 +1,182 @@
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Initial Developer of the Original Code is Everything Solved.
+# Portions created by Everything Solved are Copyright (C) 2007
+# Everything Solved. All Rights Reserved.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+
+# This file contains a single hash named %strings, which is used by the
+# installation code to display strings before Template-Toolkit can safely
+# be loaded.
+#
+# Each string supports a very simple substitution system, where you can
+# have variables named like ##this## and they'll be replaced by the string
+# variable with that name.
+#
+# Please keep the strings in alphabetical order by their name.
+
+%strings = (
+ 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
+ blacklisted => '(blacklisted)',
+ 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 TO INSTALL REQUIRED MODULES (You *must* run all these commands
+and then re-run this script):
+EOT
+ done => 'done.',
+ 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_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_jsonrpc_faster => 'Make JSON-RPC Faster',
+ feature_new_charts => 'New Charts',
+ feature_old_charts => 'Old Charts',
+ feature_mod_perl => 'mod_perl',
+ feature_moving => 'Move Bugs Between Installations',
+ feature_patch_viewer => 'Patch Viewer',
+ feature_rand_security => 'Improve cookie and token security',
+ feature_smtp_auth => 'SMTP Authentication',
+ feature_updates => 'Automatic Update Notifications',
+ feature_xmlrpc => 'XML-RPC Interface',
+
+ 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:
+
+ ##perl## install-module.pl --all
+
+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. ***',
+ 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: ",
+
+# Note: When translating these "modules" messages, don't change the formatting
+# if possible, because there is hardcoded formatting in
+# Bugzilla::Install::Requirements to match the box formatting.
+ modules_message_apache => <<END,
+***********************************************************************
+* APACHE MODULES *
+***********************************************************************
+* Normally, when Bugzilla is upgraded, all Bugzilla users have to *
+* clear their browser cache or Bugzilla will break. If you enable *
+* certain modules in your Apache configuration (usually called *
+* httpd.conf or apache2.conf) then your users will not have to clear *
+* their caches when you upgrade Bugzilla. The modules you need to *
+* enable are: *
+* *
+END
+ modules_message_db => <<EOT,
+***********************************************************************
+* DATABASE ACCESS *
+***********************************************************************
+* In order to access your database, Bugzilla requires that the *
+* correct "DBD" module be installed for the database that you are *
+* running. See below for the correct command to run to install the *
+* appropriate module for your database. *
+EOT
+ modules_message_optional => <<EOT,
+***********************************************************************
+* OPTIONAL MODULES *
+***********************************************************************
+* Certain Perl modules are not required by Bugzilla, but by *
+* installing the latest version you gain access to additional *
+* features. *
+* *
+* The optional modules you do not have installed are listed below, *
+* with the name of the feature they enable. Below that table are the *
+* commands to install each module. *
+EOT
+ modules_message_required => <<EOT,
+***********************************************************************
+* REQUIRED MODULES *
+***********************************************************************
+* Bugzilla requires you to install some Perl modules which are either *
+* missing from your system, or the version on your system is too old. *
+* 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##.",
+ ppm_repo_add => <<EOT,
+***********************************************************************
+* Note For Windows Users *
+***********************************************************************
+* In order to install the modules listed below, you first have to run *
+* the following command as an Administrator: *
+* *
+* ppm repo add theory58S ##theory_url##
+EOT
+ ppm_repo_up => <<EOT,
+* *
+* Then you have to do (also as an Administrator): *
+* *
+* ppm repo up theory58S *
+* *
+* Do that last command over and over until you see "theory58S" at the *
+* top of the displayed list. *
+EOT
+ template_precompile => "Precompiling templates...",
+ template_removal_failed => <<END,
+WARNING: The directory '##datadir##/template' could not be removed.
+ It has been moved into '##datadir##/deleteme', which should be
+ deleted manually to conserve disk space.
+END
+ template_removing_dir => "Removing existing compiled templates...",
+);
+
+1;
diff --git a/template/en/default/sidebar.xul.tmpl b/template/en/default/sidebar.xul.tmpl
new file mode 100644
index 000000000..b2fa092ea
--- /dev/null
+++ b/template/en/default/sidebar.xul.tmpl
@@ -0,0 +1,128 @@
+[%# -*- mode: sgml -*- %]
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Jacob Steenhagen <jake@bugzilla.org>
+ # Scott Collins <scc@mozilla.org>
+ # Christopher A. Aillon <christopher@aillon.com>
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://communicator/skin/" type="text/css"?>
+<?xml-stylesheet href="[% urlbase FILTER xml %]skins/standard/panel.css" type="text/css"?>
+<window
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ orient="vertical"
+ onload="document.getElementById('query-field').addEventListener('keypress', initial_keypress_handler, true)">
+
+<script type="application/x-javascript"><![CDATA[
+
+function load_absolute_url( aAbsoluteURL ) {
+ content.location = aAbsoluteURL;
+}
+
+function load_relative_url( aRelativeURL ) {
+ aRelativeURL = '[% urlbase FILTER xml %]' + aRelativeURL;
+ _content.location = aRelativeURL;
+}
+
+function initial_keypress_handler( aEvent ) {
+ this.removeAttribute("class");
+ this.addEventListener("keypress", normal_keypress_handler, true);
+ this.removeEventListener("keypress", initial_keypress_handler, true);
+}
+
+function normal_keypress_handler( aEvent ) {
+ if ( aEvent.keyCode == 13 )
+ load_relative_url('buglist.cgi?quicksearch=' + this.value);
+}
+
+]]></script>
+
+ <textbox id="query-field" class="descriptive-content" value="enter search" onfocus="this.setSelectionRange(0,this.value.length)"/>
+
+ <separator class="groove"/>
+
+ <box autostretch="never" valign="top">
+ <box orient="vertical" flex="1">
+ <text class="text-link" onclick="load_relative_url('query.cgi')" value="new search"/>
+ <text class="text-link" onclick="load_relative_url('report.cgi')" value="reports"/>
+ <text class="text-link" onclick="load_relative_url('enter_bug.cgi')" value="new [% terms.bug %]"/>
+ <separator class="thin"/>
+
+[% IF user.id %]
+ <text class="text-link" onclick="load_relative_url('userprefs.cgi')" value="edit prefs"/>
+ [%- IF user.in_group('tweakparams') %]
+ <text class="text-link" onclick="load_relative_url('editparams.cgi')" value="edit params"/>
+ <text class="text-link" onclick="load_relative_url('editsettings.cgi')" value="edit default preferences"/>
+ [%- END %]
+ [%- IF user.in_group('editusers') || user.can_bless %]
+ <text class="text-link" onclick="load_relative_url('editusers.cgi')" value="edit users"/>
+ [%- END %]
+ [%- IF Param('useclassification') && user.in_group('editclassifications') %]
+ <text class="text-link" onclick="load_relative_url('editclassifications.cgi')" value="edit classifications"/>
+ [%- END %]
+ [%- IF user.in_group('editcomponents') %]
+ <text class="text-link" onclick="load_relative_url('editcomponents.cgi')" value="edit components"/>
+ <text class="text-link" onclick="load_relative_url('editflagtypes.cgi')" value="edit flags"/>
+ <text class="text-link" onclick="load_relative_url('editvalues.cgi')" value="edit field values"/>
+ [%- END %]
+ [%- IF user.in_group('creategroups') %]
+ <text class="text-link" onclick="load_relative_url('editgroups.cgi')" value="edit groups"/>
+ [%- END %]
+ [%- IF user.in_group('editkeywords') %]
+ <text class="text-link" onclick="load_relative_url('editkeywords.cgi')" value="edit keywords"/>
+ [%- END %]
+ [%- IF user.in_group('bz_canusewhines') %]
+ <text class="text-link" onclick="load_relative_url('editwhines.cgi')" value="edit whining"/>
+ [%- END %]
+ [%- IF user.in_group('editcomponents') %]
+ <text class="text-link" onclick="load_relative_url('sanitycheck.cgi')" value="sanity check"/>
+ [%- END %]
+ [%- IF user.authorizer.can_logout %]
+ <text class="text-link" onclick="load_relative_url('index.cgi?logout=1')"
+ value="log out [% user.login FILTER html %]"/>
+ [%- END %]
+ <separator class="thin"/>
+ [%- IF user.showmybugslink %]
+ [% filtered_username = user.login FILTER url_quote %]
+ <text class="text-link" onclick="load_relative_url('[% Param('mybugstemplate').replace('%userid%', filtered_username) FILTER js FILTER html %]')" value="my [% terms.bugs %]"/>
+ [%- END %]
+
+ [%- FOREACH q = user.queries %]
+ <text class="text-link" onclick="load_relative_url('buglist.cgi?cmdtype=runnamed&amp;namedcmd=[% q.name FILTER url_quote %]')" value="[% q.name FILTER html %]"/>
+ [% END %]
+
+[% ELSE %]
+ <text class="text-link" onclick="load_relative_url('createaccount.cgi')" value="new user"/>
+ <text class="text-link" onclick="load_relative_url('index.cgi?GoAheadAndLogIn=1')" value="log in"/>
+[% END %]
+
+ </box>
+ </box>
+
+ <spring flex="1"/>
+ <box orient="horizontal">
+ <spring flex="1"/>
+ <html align="right">
+ <html:a class="text-link" href="[% urlbase FILTER xml %]sidebar.cgi">reload</html:a>
+ </html>
+ </box>
+</window>
diff --git a/template/en/default/welcome-admin.html.tmpl b/template/en/default/welcome-admin.html.tmpl
new file mode 100644
index 000000000..289a6bd7d
--- /dev/null
+++ b/template/en/default/welcome-admin.html.tmpl
@@ -0,0 +1,86 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+
+[%# INTERFACE:
+ # none
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% title = BLOCK %]Welcome to [% terms.Bugzilla %][% END %]
+
+[% PROCESS global/header.html.tmpl
+ title = title
+ header_addl_info = "version $constants.BUGZILLA_VERSION"
+ style_urls = [ 'skins/standard/index.css' ]
+%]
+
+<div id="welcome-admin">
+ <p>Welcome, [% user.identity FILTER html %].</p>
+
+ <p>You are seeing this page because some of the core parameters have not been set up yet.
+ The goal of this page is to inform you about the last steps required to set up
+ your installation correctly.</p>
+
+ <p>As an administrator, you have access to all administrative pages, accessible from
+ the <a href="admin.cgi">Administration</a> link visible at the bottom of this page.
+ This link will always be visible, on all pages. From there, you must visit at least
+ the <a href="editparams.cgi">Parameters</a> page, from where you can set all important
+ parameters for this installation; among others:</p>
+
+ <ul>
+ <li><a href="editparams.cgi?section=core#urlbase_desc">urlbase</a>, which is the URL
+ pointing to this installation and which will be used in emails (which is also the
+ reason you see this page: as long as this parameter is not set, you will see this
+ page again and again).</li>
+
+ <li><a href="editparams.cgi?section=core#cookiepath_desc">cookiepath</a> is important
+ for your browser to manage your cookies correctly.</li>
+
+ <li><a href="editparams.cgi?section=general#maintainer_desc">maintainer</a>,
+ the person responsible for this installation if something is
+ running wrongly.</li>
+ </ul>
+
+ <p>Also important are the following parameters:</p>
+
+ <ul>
+ <li><a href="editparams.cgi?section=auth#requirelogin_desc">requirelogin</a>, if turned
+ on, will protect your installation from users having no account on this installation.
+ In other words, users who are not explicitly authenticated with a valid account
+ cannot see any data. This is what you want if you want to keep your data private.</li>
+
+ <li><a href="editparams.cgi?section=auth#createemailregexp_desc">createemailregexp</a>
+ defines which users are allowed to create an account on this installation. If set
+ to ".*" (the default), everybody is free to create his own account. If set to
+ "@mycompany.com$", only users having an account @mycompany.com will be allowed to
+ create an account. If left blank, users will not be able to create accounts themselves;
+ only an administrator will be able to create one for them. If you want a private
+ installation, you must absolutely set this parameter to something different from
+ the default.</li>
+
+ <li><a href="editparams.cgi?section=mta#mail_delivery_method_desc">mail_delivery_method</a>
+ defines the method used to send emails, such as sendmail or SMTP. You have to set
+ it correctly to send emails.</li>
+ </ul>
+
+ <p>
+ After having set up all this, we recommend looking at [% terms.Bugzilla %]'s other
+ parameters as well at some time so that you understand what they do and whether you
+ want to modify their settings for your installation.
+ </p>
+</div>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/whine/mail.html.tmpl b/template/en/default/whine/mail.html.tmpl
new file mode 100644
index 000000000..ae4f00cfc
--- /dev/null
+++ b/template/en/default/whine/mail.html.tmpl
@@ -0,0 +1,95 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Erik Stambaugh <erik@dasbistro.com>
+ #%]
+
+[%# INTERFACE:
+ # subject: subject line of message
+ # body: message body, shown before the query tables
+ # queries: array of hashes containing:
+ # bugs: array containing hashes of fieldnames->values for each bug
+ # title: the title given in the whine scheduling mechanism
+ # author: user object for the person who scheduled this whine
+ # recipient: user object for the intended recipient of the message
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+[% PROCESS 'global/field-descs.none.tmpl' %]
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+ <head>
+ <title>
+ [[% terms.Bugzilla %]] [% subject FILTER html %]
+ </title>
+ </head>
+ <body bgcolor="#FFFFFF">
+
+ <p align="left">
+ [% body FILTER html %]
+ </p>
+
+ <p align="left">
+ [% IF author.login == recipient.login %]
+ <a href="[%+ urlbase FILTER html %]editwhines.cgi">Click
+ here to edit your whine schedule</a>
+ [% ELSE %]
+ This search was scheduled by [% author.login FILTER html %].
+ [% END %]
+ </p>
+
+[% IF queries.size %]
+ [% FOREACH query=queries %]
+
+ <h2>[%+ query.title FILTER html %]</h2>
+
+ <table width="100%">
+ <tr>
+ <th align="left">ID</th>
+ <th align="left">Sev</th>
+ <th align="left">Pri</th>
+ <th align="left">Plt</th>
+ <th align="left">Assignee</th>
+ <th align="left">Status</th>
+ <th align="left">Resolution</th>
+ <th align="left">Summary</th>
+ </tr>
+
+ [% FOREACH bug=query.bugs %]
+ <tr>
+ <td align="left"><a href="[%+ urlbase FILTER html %]show_bug.cgi?id=
+ [%- bug.bug_id %]">[% bug.bug_id %]</a></td>
+ <td align="left">[% display_value("bug_severity", bug.bug_severity) FILTER html %]</td>
+ <td align="left">[% display_value("priority", bug.priority) FILTER html %]</td>
+ <td align="left">[% display_value("rep_platform", bug.rep_platform) FILTER html %]</td>
+ <td align="left">[% bug.assigned_to FILTER html %]</td>
+ <td align="left">[% display_value("bug_status", bug.bug_status) FILTER html %]</td>
+ <td align="left">[% display_value("resolution", bug.resolution) FILTER html %]</td>
+ <td align="left">[% bug.short_desc FILTER html %]</td>
+ </tr>
+ [% END %]
+ </table>
+ [% END %]
+[% ELSE %]
+
+ <h3>No [% terms.bugs %] were found that matched the search criteria.</h3>
+[% END %]
+ </body>
+</html>
+
+
diff --git a/template/en/default/whine/mail.txt.tmpl b/template/en/default/whine/mail.txt.tmpl
new file mode 100644
index 000000000..13216d895
--- /dev/null
+++ b/template/en/default/whine/mail.txt.tmpl
@@ -0,0 +1,68 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Erik Stambaugh <erik@dasbistro.com>
+ #%]
+
+[%# INTERFACE:
+ # subject: subject line of message
+ # body: message body, shown before the query tables
+ # queries: array of hashes containing:
+ # bugs: array containing hashes of fieldnames->values for each bug
+ # title: the title given in the whine scheduling mechanism
+ # author: user object for the person who scheduled this whine
+ # recipient: user object for the intended recipient of the message
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+[% PROCESS 'global/field-descs.none.tmpl' %]
+
+[% body %]
+
+[% IF author.login == recipient.login %]
+ To edit your whine schedule, visit the following URL:
+ [%+ urlbase %]editwhines.cgi
+[% ELSE %]
+ This search was scheduled by [% author.login %].
+[% END %]
+
+[% IF queries.size %]
+ [% FOREACH query=queries %]
+
+[%+ query.title +%]
+[%+ "-" FILTER repeat(query.title.length) %]
+
+ [% FOREACH bug=query.bugs %]
+ [% terms.Bug +%] [%+ bug.bug_id %]:
+ [%+ urlbase %]show_bug.cgi?id=[% bug.bug_id +%]
+ Priority: [%+ display_value("priority", bug.priority) -%]
+ Severity: [%+ display_value("bug_severity", bug.bug_severity) -%]
+ Platform: [%+ display_value("rep_platform", bug.rep_platform) %]
+ Assignee: [%+ bug.assigned_to %]
+ Status: [%+ display_value("bug_status", bug.bug_status) %]
+ [%- IF bug.resolution -%] Resolution: [% display_value("resolution", bug.resolution) -%]
+ [%- END %]
+ Summary: [% bug.short_desc %]
+
+ [% END %]
+
+ [% END %]
+[% ELSE %]
+
+ No [% terms.bugs %] were found that matched the search criteria.
+[% END %]
+
diff --git a/template/en/default/whine/multipart-mime.txt.tmpl b/template/en/default/whine/multipart-mime.txt.tmpl
new file mode 100644
index 000000000..7fdfdb026
--- /dev/null
+++ b/template/en/default/whine/multipart-mime.txt.tmpl
@@ -0,0 +1,53 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Erik Stambaugh <erik@dasbistro.com>
+ #%]
+
+[%# INTERFACE:
+ # subject: subject line of message
+ # alternatives: array of hashes containing:
+ # type: MIME type
+ # content: verbatim content
+ # boundary: a string that has been generated to be a unique boundary
+ # recipient: user object for the intended recipient of the message
+ # from: Bugzilla system email address
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+From: [% from %]
+To: [% recipient.email %]
+Subject: [[% terms.Bugzilla %]] [% subject %]
+MIME-Version: 1.0
+Content-Type: multipart/alternative; boundary="[% boundary %]"
+X-Bugzilla-Type: whine
+
+
+This is a MIME multipart message. It is possible that your mail program
+doesn't quite handle these properly. Some or all of the information in this
+message may be unreadable.
+
+
+[% FOREACH part=alternatives %]
+
+--[% boundary %]
+Content-type: [% part.type +%]
+
+[%+ part.content %]
+[%+ END %]
+--[% boundary %]--
diff --git a/template/en/default/whine/schedule.html.tmpl b/template/en/default/whine/schedule.html.tmpl
new file mode 100644
index 000000000..245a3e4a9
--- /dev/null
+++ b/template/en/default/whine/schedule.html.tmpl
@@ -0,0 +1,448 @@
+[%# -*- mode: html -*- %]
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Erik Stambaugh <erik@dasbistro.com>
+ #%]
+
+[%# INTERFACE:
+ # events: hash, keyed by event_id number. Values are anonymous hashes of:
+ # schedule: array of hashes containing schedule info:
+ # day: value in day column
+ # time: value selected in time column
+ # mailto_type: 0=user 1=group
+ # mailto: recipient's id (profile or group)
+ # queries: as with schedule, an anonymous array containing hashes of:
+ # name: the named query's name
+ # title: title to be displayed on the results
+ # sort: integer that sets execution order on named queries
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% title = "Set up whining" %]
+[% PROCESS global/header.html.tmpl
+ title = title
+ style_urls = ['skins/standard/admin.css']
+ doc_section = "whining.html"
+%]
+
+<p>
+ "Whining" is when [% terms.Bugzilla %] executes a saved query at a regular interval
+ and sends the resulting list of [% terms.bugs %] via email.
+</p>
+
+<p>
+ To set up a new whine event, click "Add a new event." Enter a subject line
+ for the message that will be sent, along with a block of text that will
+ accompany the [% terms.bug %] list in the body of the message.
+</p>
+
+<p>
+ Schedules are added to an event by clicking on "Add a new schedule." A schedule
+ consists of a day, a time of day or interval of times
+ (e.g., every 15 minutes), and a target email address that may or may not be
+ alterable, depending on your privileges. Events may have more than one schedule
+ in order to run at multiple times or for different users.
+</p>
+
+<p>
+ Searches come from saved searches, which are created by executing a <a
+ href="query.cgi">search</a>, then telling [% terms.Bugzilla %] to remember
+ the search under a particular name. Add a query by clicking "Add a new
+ query", and select the desired saved search name under "Search" and add a
+ title for the [% terms.bug %] table. The optional number entered under
+ "Sort" will determine the execution order (lowest to highest) if multiple
+ queries are listed. If you check "One message per [% terms.bug %]," each [%
+ terms.bug %] that matches the search will be sent in its own email message.
+</p>
+
+<p>
+ All times are server local time ([% local_timezone FILTER html %]).
+</p>
+
+<form method="post" action="editwhines.cgi">
+[%# This hidden submit button must be here to set default behavior when
+ the user presses return on a form input field #%]
+<input type="submit" value="Update / Commit" name="commit"
+ style="display: none;" id="commit">
+<input type="hidden" name="update" value="1">
+<input type="hidden" name="token" value="[% token FILTER html %]">
+
+[% FOREACH event = events %]
+
+<table cellspacing="2" cellpadding="2" style="border: 1px solid;">
+ <tr>
+ <th align="left">
+ Event:
+ </th>
+ <td align="right" colspan="2">
+ <input type="submit" value="Remove Event"
+ name="remove_event_[% event.key %]"
+ id="remove_event_[% event.key %]">
+ </td>
+ </tr>
+
+ <tr>
+ <td valign="top" align="right">
+ Email subject line:
+ </td>
+ <td colspan="2">
+ <input type="text" name="event_[% event.key %]_subject"
+ size="60" maxlength="128" value="
+ [%- event.value.subject FILTER html %]">
+ </td>
+ </tr>
+
+ <tr>
+ <td valign="top" align="right">
+ Descriptive text sent within whine message:
+ </td>
+ <td colspan="2">
+ [% INCLUDE global/textarea.html.tmpl
+ name = "event_${event.key}_body"
+ minrows = 3
+ maxrows = 10
+ defaultrows = 5
+ cols = 80
+ defaultcontent = event.value.body
+ %]
+ </td>
+ </tr>
+
+ <tr>
+ <td valign="top" align="right">
+ Send a message even if there are no [% terms.bugs %] in the search result:
+ </td>
+ <td colspan="2">
+ <input type="checkbox" name="event_[% event.key %]_mailifnobugs"
+ [%- IF event.value.mailifnobugs == 1 %] checked [% END %]>
+ </td>
+ </tr>
+
+ [% IF event.value.schedule.size == 0 %]
+
+ <tr>
+ <td valign="top" align="right">
+ Schedule:
+ </td>
+ <td class="unset" colspan="2">
+ Not scheduled to run<br>
+ <input type="submit" value="Add a new schedule"
+ name="add_schedule_[% event.key %]"
+ id="add_schedule_[% event.key %]">
+ </td>
+ </tr>
+
+ [% ELSE %]
+
+ <tr>
+ <td valign="top" align="right">
+ Schedule:
+ </td>
+ <td class="set" colspan="2">
+
+ <table>
+ <tr>
+ <th>
+ Interval
+ </th>
+ <th>
+ [% IF mail_others %]
+ Mail to
+ [% END %]
+ </th>
+ <th>
+ </th>
+ </tr>
+ [% FOREACH schedule = event.value.schedule %]
+ <tr>
+ <td align="left">
+
+ [%# these hidden fields allow us to compare old values instead
+ of reading the database to tell if a field has changed %]
+
+ <input type="hidden" value="[% schedule.day FILTER html %]"
+ name="orig_day_[% schedule.id %]">
+ <input type="hidden" value="[% schedule.time FILTER html %]"
+ name="orig_time_[% schedule.id %]">
+ [% PROCESS day_field val=schedule.day %]
+ [% PROCESS time_field val=schedule.time %]
+ </td>
+ <td align="left">
+ [% IF mail_others %]
+ <input type="hidden" name="orig_mailto_type_[% schedule.id %]"
+ value="[% schedule.mailto_type FILTER html %]">
+ <select name="mailto_type_[% schedule.id %]">
+ <option value="0" [% IF schedule.mailto_type == 0 %]
+ selected
+ [% END %]>User</option>
+ <option value="1" [% IF schedule.mailto_type == 1 %]
+ selected
+ [% END %]>Group</option>
+ </select>
+ <input type="hidden" name="orig_mailto_[% schedule.id %]"
+ value="[% schedule.mailto FILTER html %]">
+ <input type="text" name="mailto_[% schedule.id %]"
+ value="[% schedule.mailto FILTER html %]" size="30">
+ [% END %]
+ </td>
+ <td align="left">
+ <input type="submit" value="Remove"
+ name="remove_schedule_[% schedule.id %]"
+ id="remove_schedule_[% schedule.id %]">
+ </td>
+ </tr>
+ [% END %]
+
+ <tr>
+ <td colspan="3">
+ <input type="submit" value="Add a new schedule"
+ name="add_schedule_[% event.key %]"
+ id="add_schedule_[% event.key %]">
+ </td>
+ </tr>
+ </table>
+
+ </td>
+ </tr>
+
+ [% END %]
+
+ [% IF event.value.queries.size == 0 %]
+
+ <tr>
+ <td valign="top" align="right">
+ Searches:
+ </td>
+ <td align="left">
+ No searches <br>
+ <input type="submit" value="Add a new query"
+ name="add_query_[% event.key %]"
+ id="add_query_[% event.key %]">
+ </td>
+ <td align="right" valign="bottom">
+ <input type="submit" value="Update / Commit" name="commit" id="update">
+ </td>
+ </tr>
+
+ [% ELSE %]
+
+ <tr>
+ <td valign="top" align="right">
+ Searches:
+ </td>
+ <td align="left" colspan="2">
+
+ <table>
+ <tr>
+ <th>Sort</th>
+ <th>Search</th>
+ <th>Title</th>
+ <th></th>
+ <th></th>
+ </tr>
+
+ [% FOREACH query = event.value.queries %]
+
+ <tr>
+ <td align="left">
+ <input type="text" name="query_sort_[% query.id %]"
+ size="3" value="[% query.sort %]">
+ <input type="hidden" value="[% query.sort %]"
+ name="orig_query_sort_[% query.id %]">
+ </td>
+ <td align="left">
+ <input type="hidden" value="[% query.name FILTER html %]"
+ name="orig_query_name_[% query.id %]">
+ [% PROCESS query_field thisquery=query.name %]
+ </td>
+ <td align="left">
+ <input type="hidden" value="[% query.title FILTER html %]"
+ name="orig_query_title_[% query.id %]">
+ <input type="text" name="query_title_[% query.id %]"
+ size="50" value="[% query.title FILTER html %]"
+ maxlength="64">
+ </td>
+ <td align="left">
+ <input type="hidden" value="[% query.onemailperbug FILTER html %]"
+ name="orig_query_onemailperbug_[% query.id %]">
+ <input type="checkbox" [% IF query.onemailperbug == 1 %] checked [% END %]
+ id="query_onemailperbug_[% query.id %]"
+ name="query_onemailperbug_[% query.id %]">
+ <label for="query_onemailperbug_[% query.id %]">One message per [% terms.bug %]</label>
+ </td>
+ <td align="right">
+ <input type="submit" value="Remove"
+ name="remove_query_[% query.id %]"
+ id="remove_query_[% query.id %]">
+ </td>
+ </tr>
+
+ [% END %]
+
+ <tr>
+ <td colspan="3">
+ <input type="submit" value="Add a new query"
+ name="add_query_[% event.key %]"
+ id="add_query_[% event.key %]">
+ </td>
+ <td align="right" colspan="2">
+ <input type="submit" value="Update / Commit" name="commit" id="update">
+ </td>
+ </tr>
+ </table>
+
+ </td>
+ </tr>
+
+ [% END %]
+
+</table>
+
+[% END %]
+
+<p align="left">
+ <input type="submit" value="Add a new event" name="add_event" id="add_event">
+</p>
+
+</form>
+
+[% PROCESS global/footer.html.tmpl %]
+
+[% BLOCK query_field +%]
+
+ [% IF available_queries.size > 0 %]
+
+ <select name="query_name_[% query.id %]">
+ [% FOREACH q = available_queries %]
+ <option [% "selected" IF q == thisquery %] value="[% q FILTER html %]">
+ [% q FILTER html %]
+ </option>
+ [% END %]
+ </select>
+
+ [% ELSE %]
+ Please visit the <a href="query.cgi">Search</a> page and save a query
+ [% END %]
+
+[%+ END %]
+
+[% BLOCK day_field +%]
+ <select name="day_[% schedule.id %]">
+ [%
+ options = [
+ ['All', 'Each day', ],
+ ['MF', 'Monday through Friday', ],
+ ['Sun', 'Sunday', ],
+ ['Mon', 'Monday', ],
+ ['Tue', 'Tuesday', ],
+ ['Wed', 'Wednesday', ],
+ ['Thu', 'Thursday', ],
+ ['Fri', 'Friday', ],
+ ['Sat', 'Saturday', ],
+ ['1', 'On the 1st of the month', ],
+ ['2', 'On the 2nd of the month', ],
+ ['3', 'On the 3rd of the month', ],
+ ['4', 'On the 4th of the month', ],
+ ['5', 'On the 5th of the month', ],
+ ['6', 'On the 6th of the month', ],
+ ['7', 'On the 7th of the month', ],
+ ['8', 'On the 8th of the month', ],
+ ['9', 'On the 9th of the month', ],
+ ['10', 'On the 10th of the month', ],
+ ['11', 'On the 11th of the month', ],
+ ['12', 'On the 12th of the month', ],
+ ['13', 'On the 13th of the month', ],
+ ['14', 'On the 14th of the month', ],
+ ['15', 'On the 15th of the month', ],
+ ['16', 'On the 16th of the month', ],
+ ['17', 'On the 17th of the month', ],
+ ['18', 'On the 18th of the month', ],
+ ['19', 'On the 19th of the month', ],
+ ['20', 'On the 20th of the month', ],
+ ['21', 'On the 21st of the month', ],
+ ['22', 'On the 22nd of the month', ],
+ ['23', 'On the 23rd of the month', ],
+ ['24', 'On the 24th of the month', ],
+ ['25', 'On the 25th of the month', ],
+ ['26', 'On the 26th of the month', ],
+ ['27', 'On the 27th of the month', ],
+ ['28', 'On the 28th of the month', ],
+ ['29', 'On the 29th of the month', ],
+ ['30', 'On the 30th of the month', ],
+ ['31', 'On the 31st of the month', ],
+ ['last', 'Last day of the month', ],
+ ]
+ %]
+
+ [% FOREACH option = options %]
+ <option value="[% option.0 %]"
+ [%- IF val == option.0 +%] selected[% END %]>
+ [%- option.1 -%]
+ </option>
+ [% END %]
+
+ </select>
+[%+ END %]
+
+[% BLOCK time_field +%]
+<select name="time_[% schedule.id %]">
+
+ [%
+ options = [
+ [ '0', 'at midnight', ],
+ [ '1', 'at 01:00', ],
+ [ '2', 'at 02:00', ],
+ [ '3', 'at 03:00', ],
+ [ '4', 'at 04:00', ],
+ [ '5', 'at 05:00', ],
+ [ '6', 'at 06:00', ],
+ [ '7', 'at 07:00', ],
+ [ '8', 'at 08:00', ],
+ [ '9', 'at 09:00', ],
+ [ '10', 'at 10:00', ],
+ [ '11', 'at 11:00', ],
+ [ '12', 'at 12:00', ],
+ [ '13', 'at 13:00', ],
+ [ '14', 'at 14:00', ],
+ [ '15', 'at 15:00', ],
+ [ '16', 'at 16:00', ],
+ [ '17', 'at 17:00', ],
+ [ '18', 'at 18:00', ],
+ [ '19', 'at 19:00', ],
+ [ '20', 'at 20:00', ],
+ [ '21', 'at 21:00', ],
+ [ '22', 'at 22:00', ],
+ [ '23', 'at 23:00', ],
+ [ '60min', 'every hour', ],
+ [ '30min', 'every 30 minutes', ],
+ [ '15min', 'every 15 minutes', ],
+ ]
+ %]
+
+ [% FOREACH option = options %]
+ <option value="[% option.0 %]"
+ [%- IF val == option.0 +%] selected[% END %]>
+ [%- option.1 -%]
+ </option>
+ [% END %]
+
+</select>
+
+[%+ END %]
+
diff --git a/testagent.cgi b/testagent.cgi
new file mode 100755
index 000000000..4ecaa15fd
--- /dev/null
+++ b/testagent.cgi
@@ -0,0 +1,24 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# Contributor(s): Joel Peshkin <bugreport@peshkin.net>
+
+# This script is used by servertest.pl to confirm that cgi scripts
+# are being run instead of shown. This script does not rely on database access
+# or correct params.
+
+use strict;
+print "content-type:text/plain\n\n";
+print "OK " . ($::ENV{MOD_PERL} || "mod_cgi") . "\n";
+exit;
+
diff --git a/testserver.pl b/testserver.pl
new file mode 100755
index 000000000..3142685bc
--- /dev/null
+++ b/testserver.pl
@@ -0,0 +1,289 @@
+#!/usr/bin/perl -w
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# Contributor(s): Joel Peshkin <bugreport@peshkin.net>
+# Byron Jones <byron@glob.com.au>
+
+# testserver.pl is invoked with the baseurl of the Bugzilla installation
+# as its only argument. It attempts to troubleshoot as many installation
+# issues as possible.
+
+use strict;
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Constants;
+
+use Socket;
+
+my $datadir = bz_locations()->{'datadir'};
+
+eval "require LWP; require LWP::UserAgent;";
+my $lwp = $@ ? 0 : 1;
+
+if ((@ARGV != 1) || ($ARGV[0] !~ /^https?:/))
+{
+ print "Usage: $0 <URL to this Bugzilla installation>\n";
+ print "e.g.: $0 http://www.mycompany.com/bugzilla\n";
+ 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 $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);
+ }
+}
+
+# Determine the numeric GID of $webservergroup
+my $webgroupnum = 0;
+my $webservergroup = Bugzilla->localconfig->{webservergroup};
+if ($webservergroup =~ /^(\d+)$/) {
+ $webgroupnum = $1;
+}
+else {
+ eval { $webgroupnum = (getgrnam $webservergroup) || 0; };
+}
+
+# Check $webservergroup against the server's GID
+if ($sgid > 0) {
+ if ($webservergroup eq "") {
+ print
+"WARNING \$webservergroup is set to an empty string.
+That is a very insecure practice. Please refer to the
+Bugzilla documentation.\n";
+ }
+ elsif ($webgroupnum == $sgid || Bugzilla->localconfig->{use_suexec}) {
+ print "TEST-OK Webserver is running under group id in \$webservergroup.\n";
+ }
+ else {
+ print
+"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.\n";
+ }
+}
+elsif (!ON_WINDOWS) {
+ print
+"TEST-WARNING Failed to find the GID for the 'httpd' process, unable
+to validate webservergroup.\n";
+}
+
+
+# Try to fetch a static file (padlock.png)
+$ARGV[0] =~ s/\/$//;
+my $url = $ARGV[0] . "/images/padlock.png";
+if (fetch($url)) {
+ print "TEST-OK Got padlock picture.\n";
+} else {
+ print
+"TEST-FAILED Fetch of images/padlock.png failed
+Your web server could not fetch $url.
+Check your web server configuration and try again.\n";
+ exit(1);
+}
+
+# Try to execute a cgi script
+my $response = fetch($ARGV[0] . "/testagent.cgi");
+if ($response =~ /^OK (.*)$/) {
+ print "TEST-OK Webserver is executing CGIs via $1.\n";
+} elsif ($response =~ /^#!/) {
+ print
+"TEST-FAILED Webserver is fetching rather than executing CGI files.
+Check the AddHandler statement in your httpd.conf file.\n";
+ exit(1);
+} else {
+ print "TEST-FAILED Webserver is not executing CGI files.\n";
+}
+
+# Make sure that the web server is honoring .htaccess files
+my $localconfig = bz_locations()->{'localconfig'};
+$localconfig =~ s~^\./~~;
+$url = $ARGV[0] . "/$localconfig";
+$response = fetch($url);
+if ($response) {
+ print
+"TEST-FAILED Webserver is permitting fetch of $url.
+This is a serious security problem.
+Check your web server configuration.\n";
+ exit(1);
+} else {
+ print "TEST-OK Webserver is preventing fetch of $url.\n";
+}
+
+# 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) {
+ print "TEST-WARNING Failed to run gdlib-config; can't compare " .
+ "GD versions.\n";
+ }
+ else {
+ my $gd = $GD::VERSION;
+
+ my $verstring = "GD version $gd, libgd version $gdlib";
+
+ $gdlib =~ s/^([^\.]+)\..*/$1/;
+ $gd =~ s/^([^\.]+)\..*/$1/;
+
+ if ($gdlib == $gd) {
+ print "TEST-OK $verstring; Major versions match.\n";
+ } else {
+ print "TEST-FAILED $verstring; Major versions do not match.\n";
+ }
+ }
+ }
+
+ # 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);
+
+ if ($image->can('png')) {
+ create_file("$datadir/testgd-local.png", $image->png);
+ check_image("$datadir/testgd-local.png", 'GD');
+ } else {
+ print "TEST-FAILED GD doesn't support PNG generation.\n";
+ }
+ };
+ if ($@ ne '') {
+ print "TEST-FAILED GD returned: $@\n";
+ }
+
+ # Test Chart
+ eval 'use Chart::Lines';
+ if ($@) {
+ print "TEST-FAILED Chart::Lines is not installed.\n";
+ } 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 '') {
+ print "TEST-FAILED Chart returned: $@\n";
+ }
+ }
+
+ eval 'use Template::Plugin::GD::Image';
+ if ($@) {
+ print "TEST-FAILED Template::Plugin::GD is not installed.\n";
+ }
+ else {
+ print "TEST-OK Template::Plugin::GD is installed.\n";
+ }
+}
+
+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);
+ }
+ }
+ 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/) {
+ print "TEST-OK $library library generated a good PNG image.\n";
+ unlink $local_file;
+ } else {
+ print "TEST-WARNING $library library did not generate a good PNG.\n";
+ }
+}
+
+sub create_file {
+ 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;
+}
diff --git a/token.cgi b/token.cgi
new file mode 100755
index 000000000..560930385
--- /dev/null
+++ b/token.cgi
@@ -0,0 +1,410 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Myk Melez <myk@mozilla.org>
+# Frédéric Buclin <LpSolit@gmail.com>
+
+############################################################################
+# Script Initialization
+############################################################################
+
+# Make it harder for us to do dangerous things in Perl.
+use strict;
+
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Util;
+use Bugzilla::Error;
+use Bugzilla::Token;
+use Bugzilla::User;
+
+use Date::Format;
+use Date::Parse;
+
+my $dbh = Bugzilla->dbh;
+local our $cgi = Bugzilla->cgi;
+local our $template = Bugzilla->template;
+local our $vars = {};
+
+my $action = $cgi->param('a');
+my $token = $cgi->param('t');
+
+Bugzilla->login(LOGIN_OPTIONAL);
+
+################################################################################
+# Data Validation / Security Authorization
+################################################################################
+
+# Throw an error if the form does not contain an "action" field specifying
+# what the user wants to do.
+$action || ThrowUserError('unknown_action');
+
+# If a token was submitted, make sure it is a valid token that exists in the
+# database and is the correct type for the action being taken.
+if ($token) {
+ Bugzilla::Token::CleanTokenTable();
+
+ # It's safe to detaint the token as it's used in a placeholder.
+ trick_taint($token);
+
+ # Make sure the token exists in the database.
+ my ($tokentype) = $dbh->selectrow_array('SELECT tokentype FROM tokens
+ WHERE token = ?', undef, $token);
+ $tokentype || ThrowUserError("token_does_not_exist");
+
+ # Make sure the token is the correct type for the action being taken.
+ if ( grep($action eq $_ , qw(cfmpw cxlpw chgpw)) && $tokentype ne 'password' ) {
+ Bugzilla::Token::Cancel($token, "wrong_token_for_changing_passwd");
+ ThrowUserError("wrong_token_for_changing_passwd");
+ }
+ if ( ($action eq 'cxlem')
+ && (($tokentype ne 'emailold') && ($tokentype ne 'emailnew')) ) {
+ Bugzilla::Token::Cancel($token, "wrong_token_for_cancelling_email_change");
+ ThrowUserError("wrong_token_for_cancelling_email_change");
+ }
+ if ( grep($action eq $_ , qw(cfmem chgem))
+ && ($tokentype ne 'emailnew') ) {
+ Bugzilla::Token::Cancel($token, "wrong_token_for_confirming_email_change");
+ ThrowUserError("wrong_token_for_confirming_email_change");
+ }
+ if (($action =~ /^(request|confirm|cancel)_new_account$/)
+ && ($tokentype ne 'account'))
+ {
+ Bugzilla::Token::Cancel($token, 'wrong_token_for_creating_account');
+ ThrowUserError('wrong_token_for_creating_account');
+ }
+}
+
+
+# If the user is requesting a password change, make sure they submitted
+# their login name and it exists in the database, and that the DB module is in
+# the list of allowed verification methods.
+my $user_account;
+if ( $action eq 'reqpw' ) {
+ my $login_name = $cgi->param('loginname')
+ || ThrowUserError("login_needed_for_password_change");
+
+ # check verification methods
+ unless (Bugzilla->user->authorizer->can_change_password) {
+ ThrowUserError("password_change_requests_not_allowed");
+ }
+
+ validate_email_syntax($login_name)
+ || ThrowUserError('illegal_email_address', {addr => $login_name});
+
+ $user_account = Bugzilla::User->check($login_name);
+
+ # Make sure the user account is active.
+ if ($user_account->is_disabled) {
+ ThrowUserError('account_disabled',
+ {disabled_reason => get_text('account_disabled', {account => $login_name})});
+ }
+}
+
+# If the user is changing their password, make sure they submitted a new
+# password and that the new password is valid.
+my $password;
+if ( $action eq 'chgpw' ) {
+ $password = $cgi->param('password');
+ defined $password
+ && defined $cgi->param('matchpassword')
+ || ThrowUserError("require_new_password");
+
+ validate_password($password, $cgi->param('matchpassword'));
+ # Make sure that these never show up in the UI under any circumstances.
+ $cgi->delete('password', 'matchpassword');
+}
+
+################################################################################
+# Main Body Execution
+################################################################################
+
+# All calls to this script should contain an "action" variable whose value
+# determines what the user wants to do. The code below checks the value of
+# that variable and runs the appropriate code.
+
+if ($action eq 'reqpw') {
+ requestChangePassword($user_account);
+} elsif ($action eq 'cfmpw') {
+ confirmChangePassword($token);
+} elsif ($action eq 'cxlpw') {
+ cancelChangePassword($token);
+} elsif ($action eq 'chgpw') {
+ changePassword($token, $password);
+} elsif ($action eq 'cfmem') {
+ confirmChangeEmail($token);
+} elsif ($action eq 'cxlem') {
+ cancelChangeEmail($token);
+} elsif ($action eq 'chgem') {
+ changeEmail($token);
+} elsif ($action eq 'request_new_account') {
+ request_create_account($token);
+} elsif ($action eq 'confirm_new_account') {
+ confirm_create_account($token);
+} elsif ($action eq 'cancel_new_account') {
+ cancel_create_account($token);
+} else {
+ ThrowUserError('unknown_action', {action => $action});
+}
+
+exit;
+
+################################################################################
+# Functions
+################################################################################
+
+sub requestChangePassword {
+ my ($user) = @_;
+ Bugzilla::Token::IssuePasswordToken($user);
+
+ $vars->{'message'} = "password_change_request";
+
+ print $cgi->header();
+ $template->process("global/message.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+}
+
+sub confirmChangePassword {
+ my $token = shift;
+ $vars->{'token'} = $token;
+
+ 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'});
+
+ print $cgi->header();
+ $template->process("global/message.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+}
+
+sub changePassword {
+ my ($token, $password) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ # Create a crypted version of the new password
+ my $cryptedpassword = bz_crypt($password);
+
+ # Get the user's ID from the tokens table.
+ my ($userid) = $dbh->selectrow_array('SELECT userid FROM tokens
+ WHERE token = ?', undef, $token);
+
+ # Update the user's password in the profiles table and delete the token
+ # from the tokens table.
+ $dbh->bz_start_transaction();
+ $dbh->do(q{UPDATE profiles
+ SET cryptpassword = ?
+ WHERE userid = ?},
+ undef, ($cryptedpassword, $userid) );
+ $dbh->do('DELETE FROM tokens WHERE token = ?', undef, $token);
+ $dbh->bz_commit_transaction();
+
+ Bugzilla->logout_user_by_id($userid);
+
+ $vars->{'message'} = "password_changed";
+
+ print $cgi->header();
+ $template->process("global/message.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+}
+
+sub confirmChangeEmail {
+ my $token = shift;
+ $vars->{'token'} = $token;
+
+ print $cgi->header();
+ $template->process("account/email/confirm.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+}
+
+sub changeEmail {
+ my $token = shift;
+ my $dbh = Bugzilla->dbh;
+
+ # Get the user's ID from the tokens table.
+ my ($userid, $eventdata) = $dbh->selectrow_array(
+ q{SELECT userid, eventdata FROM tokens
+ WHERE token = ?}, undef, $token);
+ my ($old_email, $new_email) = split(/:/,$eventdata);
+
+ # Check the user entered the correct old email address
+ if(lc($cgi->param('email')) ne lc($old_email)) {
+ ThrowUserError("email_confirmation_failed");
+ }
+ # 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 and delete the token
+ # from the tokens table.
+ $dbh->bz_start_transaction();
+ $dbh->do(q{UPDATE profiles
+ SET login_name = ?
+ WHERE userid = ?},
+ undef, ($new_email, $userid));
+ $dbh->do('DELETE FROM tokens WHERE token = ?', undef, $token);
+ $dbh->do(q{DELETE FROM tokens WHERE userid = ?
+ AND tokentype = 'emailnew'}, undef, $userid);
+
+ # The email address has been changed, so we need to rederive the groups
+ my $user = new Bugzilla::User($userid);
+ $user->derive_regexp_groups;
+
+ $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 $token = shift;
+ my $dbh = Bugzilla->dbh;
+
+ $dbh->bz_start_transaction();
+
+ # Get the user's ID from the tokens table.
+ my ($userid, $tokentype, $eventdata) = $dbh->selectrow_array(
+ q{SELECT userid, tokentype, eventdata FROM tokens
+ WHERE token = ?}, undef, $token);
+ my ($old_email, $new_email) = split(/:/,$eventdata);
+
+ if($tokentype eq "emailold") {
+ $vars->{'message'} = "emailold_change_canceled";
+
+ my $actualemail = $dbh->selectrow_array(
+ q{SELECT login_name FROM profiles
+ WHERE userid = ?}, undef, $userid);
+
+ # check to see if it has been altered
+ if($actualemail ne $old_email) {
+ # XXX - This is NOT safe - if A has change to B, another profile
+ # could have grabbed A's username in the meantime.
+ # The DB constraint will catch this, though
+ $dbh->do(q{UPDATE profiles
+ SET login_name = ?
+ WHERE userid = ?},
+ undef, ($old_email, $userid));
+
+ # email has changed, so rederive groups
+
+ my $user = new Bugzilla::User($userid);
+ $user->derive_regexp_groups;
+
+ $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);
+
+ $dbh->do(q{DELETE FROM tokens WHERE userid = ?
+ AND tokentype = 'emailold' OR tokentype = 'emailnew'},
+ undef, $userid);
+
+ $dbh->bz_commit_transaction();
+
+ # Return HTTP response headers.
+ print $cgi->header();
+
+ $template->process("global/message.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+}
+
+sub request_create_account {
+ my $token = shift;
+
+ my (undef, $date, $login_name) = Bugzilla::Token::GetTokenData($token);
+ $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());
+}
+
+sub confirm_create_account {
+ my $token = shift;
+
+ my (undef, undef, $login_name) = Bugzilla::Token::GetTokenData($token);
+
+ 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');
+
+ my $otheruser = Bugzilla::User->create({
+ login_name => $login_name,
+ realname => $cgi->param('realname'),
+ cryptpassword => $password});
+
+ # Now delete this token.
+ delete_token($token);
+
+ # Let the user know that his user account has been successfully created.
+ $vars->{'message'} = 'account_created';
+ $vars->{'otheruser'} = $otheruser;
+
+ # Log in the new user using credentials he just gave.
+ $cgi->param('Bugzilla_login', $otheruser->login);
+ $cgi->param('Bugzilla_password', $password);
+ Bugzilla->login(LOGIN_OPTIONAL);
+
+ print $cgi->header();
+
+ $template->process('index.html.tmpl', $vars)
+ || ThrowTemplateError($template->error());
+}
+
+sub cancel_create_account {
+ my $token = shift;
+
+ my (undef, undef, $login_name) = Bugzilla::Token::GetTokenData($token);
+
+ $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());
+}
diff --git a/userprefs.cgi b/userprefs.cgi
new file mode 100755
index 000000000..8be6bcdfc
--- /dev/null
+++ b/userprefs.cgi
@@ -0,0 +1,564 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Dan Mosedale <dmose@mozilla.org>
+# Alan Raetz <al_raetz@yahoo.com>
+# David Miller <justdave@syndicomm.com>
+# Christopher Aillon <christopher@aillon.com>
+# Gervase Markham <gerv@gerv.net>
+# Vlad Dascalu <jocuri@softhome.net>
+# Shane H. W. Travis <travis@sedsystems.ca>
+
+use strict;
+
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::BugMail;
+use Bugzilla::Constants;
+use Bugzilla::Search;
+use Bugzilla::Util;
+use Bugzilla::Error;
+use Bugzilla::User;
+use Bugzilla::Token;
+
+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.
+###############################################################################
+sub DoAccount {
+ my $dbh = Bugzilla->dbh;
+ my $user = Bugzilla->user;
+
+ ($vars->{'realname'}) = $dbh->selectrow_array(
+ "SELECT realname FROM profiles WHERE userid = ?", undef, $user->id);
+
+ if(Bugzilla->params->{'allowemailchange'}
+ && Bugzilla->user->authorizer->can_change_email) {
+ # First delete old tokens.
+ Bugzilla::Token::CleanTokenTable();
+
+ my @token = $dbh->selectrow_array(
+ "SELECT tokentype, issuedate + " .
+ $dbh->sql_interval(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;
+ }
+ }
+ }
+}
+
+sub SaveAccount {
+ my $cgi = Bugzilla->cgi;
+ my $dbh = Bugzilla->dbh;
+ my $user = Bugzilla->user;
+
+ my $oldpassword = $cgi->param('old_password');
+ my $pwd1 = $cgi->param('new_password1');
+ my $pwd2 = $cgi->param('new_password2');
+
+ my $old_login_name = $cgi->param('old_login');
+ my $new_login_name = trim($cgi->param('new_login_name'));
+
+ 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");
+ }
+
+ if ($pwd1 ne "" || $pwd2 ne "") {
+ $pwd1 || ThrowUserError("new_password_missing");
+ validate_password($pwd1, $pwd2);
+
+ if ($oldpassword ne $pwd1) {
+ my $cryptedpassword = bz_crypt($pwd1);
+ $dbh->do(q{UPDATE profiles
+ SET cryptpassword = ?
+ WHERE userid = ?},
+ undef, ($cryptedpassword, $user->id));
+
+ # 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 ($old_login_name 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.
+ validate_email_syntax($new_login_name)
+ || ThrowUserError('illegal_email_address', {addr => $new_login_name});
+ is_available_username($new_login_name)
+ || ThrowUserError("account_exists", {email => $new_login_name});
+
+ Bugzilla::Token::IssueEmailChangeToken($user, $old_login_name,
+ $new_login_name);
+
+ $vars->{'email_changes_saved'} = 1;
+ }
+ }
+
+ my $realname = trim($cgi->param('realname'));
+ trick_taint($realname); # Only used in a placeholder
+ $dbh->do("UPDATE profiles SET realname = ? WHERE userid = ?",
+ undef, ($realname, $user->id));
+}
+
+
+sub DoSettings {
+ my $user = Bugzilla->user;
+
+ my $settings = $user->settings;
+ $vars->{'settings'} = $settings;
+
+ my @setting_list = 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->{'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);
+ }
+ }
+ $vars->{'settings'} = $user->settings(1);
+}
+
+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;
+}
+
+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);
+ }
+ }
+ }
+
+ # 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);
+
+ # 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;
+
+ foreach my $username (@new_watch_names) {
+ my $watched_userid = login_to_id(trim($username), THROW_ERROR);
+ $new_watch_ids{$watched_userid} = 1;
+ }
+
+ # 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);
+ }
+
+ 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);
+ }
+ }
+
+ $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
+ 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});
+ }
+ }
+
+ # If the user has product specific privileges, inform him 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;
+}
+
+# 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}];
+}
+
+sub SaveSavedSearches {
+ 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;
+
+ 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
+ WHERE namedquery_id = ?
+ 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
+ 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;
+ }
+ }
+
+ # 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 ($group_id) {
+ # Don't allow the user to share queries with groups he's 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;
+
+ # 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;
+}
+
+
+###############################################################################
+# Live code (not subroutine definitions) starts here
+###############################################################################
+
+my $cgi = Bugzilla->cgi;
+
+# Delete credentials before logging in in case we are in a sudo session.
+$cgi->delete('Bugzilla_login', 'Bugzilla_password') if ($cgi->cookie('sudo'));
+$cgi->delete('GoAheadAndLogIn');
+
+# First try to get credentials from cookies.
+Bugzilla->login(LOGIN_OPTIONAL);
+
+if (!Bugzilla->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'));
+}
+Bugzilla->login(LOGIN_REQUIRED);
+
+my $save_changes = $cgi->param('dosave');
+$vars->{'changes_saved'} = $save_changes;
+
+my $current_tab_name = $cgi->param('tab') || "settings";
+
+# The SWITCH below makes sure that this is valid
+trick_taint($current_tab_name);
+
+$vars->{'current_tab_name'} = $current_tab_name;
+
+my $token = $cgi->param('token');
+check_token_data($token, 'edit_user_prefs') if $save_changes;
+
+# Do any saving, and then display the current tab.
+SWITCH: for ($current_tab_name) {
+ /^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;
+ };
+ # 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;
+
+ 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');
+}
+
+# Generate and return the UI (HTML page) from the appropriate template.
+print $cgi->header();
+$template->process("account/prefs/prefs.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
diff --git a/votes.cgi b/votes.cgi
new file mode 100755
index 000000000..ef9227af0
--- /dev/null
+++ b/votes.cgi
@@ -0,0 +1,50 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by the Initial Developer are Copyright (C) 2010 the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+# This script remains as a backwards-compatibility URL for before
+# the time that Voting was an extension.
+
+use strict;
+use lib qw(. lib);
+use Bugzilla;
+use Bugzilla::Error;
+
+my $is_enabled = grep { $_->NAME eq 'Voting' } @{ Bugzilla->extensions };
+$is_enabled || ThrowCodeError('extension_disabled', { name => 'Voting' });
+
+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');
+}
+elsif ($action eq "show_user" or $action eq 'vote') {
+ $cgi->delete('action') unless $action eq 'vote';
+ $cgi->param('id', 'voting/user.html');
+}
+else {
+ ThrowUserError('unknown_action', {action => $action});
+}
+
+print $cgi->redirect('page.cgi?' . $cgi->query_string);
+exit;
diff --git a/whine.pl b/whine.pl
new file mode 100755
index 000000000..1f22b65fc
--- /dev/null
+++ b/whine.pl
@@ -0,0 +1,684 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Erik Stambaugh <erik@dasbistro.com>
+
+################################################################################
+# Script Initialization
+################################################################################
+
+use strict;
+
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Search;
+use Bugzilla::User;
+use Bugzilla::Mailer;
+use Bugzilla::Util;
+use Bugzilla::Group;
+
+# create some handles that we'll need
+my $template = Bugzilla->template;
+my $dbh = Bugzilla->dbh;
+my $sth;
+
+# @seen_schedules is a list of all of the schedules that have already been
+# touched by reset_timer. If reset_timer sees a schedule more than once, it
+# sets it to NULL so it won't come up again until the next execution of
+# whine.pl
+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 = ?");
+
+# 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)
+);
+
+# 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()"
+);
+
+
+################################################################################
+# Main Body Execution
+################################################################################
+
+# This script needs to check through the database for schedules that have
+# run_next set to NULL, which means that schedule is new or has been altered.
+# It then sets it to run immediately if the schedule entry has it running at
+# an interval like every hour, otherwise to the appropriate day and time.
+
+# After that, it looks over each user to see if they have schedules that need
+# running, then runs those and generates the email messages.
+
+# Send whines from the address in the 'mailfrom' Parameter so that all
+# Bugzilla-originated mail appears to come from a single address.
+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;
+# 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;
+}
+
+# run_day can contain either a calendar day (1, 2, 3...), a day of the week
+# (Mon, Tue, Wed...), a range of days (All, MF), or 'last' for the last day of
+# the month.
+#
+# run_time can contain either an hour (0, 1, 2...) or an interval
+# (60min, 30min, 15min).
+#
+# 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" );
+$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 = CURRENT_DATE + " .
+ $dbh->sql_interval('?', '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);
+ $sth = $dbh->prepare("UPDATE whine_schedules " .
+ "SET run_next = (CURRENT_DATE + " .
+ $dbh->sql_interval('?', 'DAY') . ") + " .
+ $dbh->sql_interval('?', 'HOUR') .
+ " 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;
+
+ $sth = $dbh->prepare("UPDATE whine_schedules " .
+ "SET run_next = (CURRENT_DATE + " .
+ $dbh->sql_interval('?', 'DAY') . ") + " .
+ $dbh->sql_interval('?', 'HOUR') .
+ " WHERE id = ?");
+ $sth->execute($target_date, $target_time, $schedule_id);
+ }
+}
+$sched_h->finish();
+
+# get_next_event
+#
+# This function will:
+# 1. Lock whine_schedules
+# 2. Grab the most overdue pending schedules on the same event that must run
+# 3. Update those schedules' run_next value
+# 4. Unlock the table
+# 5. Return an event hashref
+#
+# The event hashref consists of:
+# 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
+# body - the text inserted above the bug lists
+# 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]);
+ }
+ }
+ }
+ }
+
+ }
+
+ reset_timer($sid);
+ }
+
+ $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
+#
+# $event:
+# eventid (the database ID for this event)
+# author (user object for who created the event)
+# mailto (array of user objects for mail targets)
+# subject (subject line for message)
+# body (text blurb at top of message)
+# 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;
+ }
+
+ $args->{'queries'} = $queries;
+
+ mail($args);
+ }
+ $dbh = Bugzilla->switch_to_main_db();
+}
+
+################################################################################
+# Functions
+################################################################################
+
+# The mail and run_queries functions use an anonymous hash ($args) for their
+# arguments, which are then passed to the templates.
+#
+# When run_queries is run, $args contains the following fields:
+# - body Message body defined in event
+# - from Bugzilla system email address
+# - queries array of hashes containing:
+# - bugs: array of hashes mapping fieldnames to values for this bug
+# - title: text title given to this query in the whine event
+# - schedule_id integer id of the schedule being run
+# - subject Subject line for the message
+# - recipient user object for the recipient
+# - author user object of the person who created the whine event
+#
+# In addition, mail adds two more fields to $args:
+# - alternatives array of hashes defining mime multipart types and contents
+# - boundary a MIME boundary generated using the process id and time
+#
+sub mail {
+ my $args = shift;
+ my $addressee = $args->{recipient};
+ # Don't send mail to someone whose bugmail notification is disabled.
+ return if $addressee->email_disabled;
+
+ my $template = Bugzilla->template_inner($addressee->settings->{'lang'}->{'value'});
+ my $msg = ''; # it's a temporary variable to hold the template output
+ $args->{'alternatives'} ||= [];
+
+ # put together the different multipart mime segments
+
+ $template->process("whine/mail.txt.tmpl", $args, \$msg)
+ or die($template->error());
+ push @{$args->{'alternatives'}},
+ {
+ 'content' => $msg,
+ 'type' => 'text/plain',
+ };
+ $msg = '';
+
+ $template->process("whine/mail.html.tmpl", $args, \$msg)
+ or die($template->error());
+ push @{$args->{'alternatives'}},
+ {
+ 'content' => $msg,
+ 'type' => 'text/html',
+ };
+ $msg = '';
+
+ # now produce a ready-to-mail mime-encoded message
+
+ $args->{'boundary'} = "----------" . $$ . "--" . time() . "-----";
+
+ $template->process("whine/multipart-mime.txt.tmpl", $args, \$msg)
+ or die($template->error());
+
+ MessageToMTA($msg);
+
+ delete $args->{'boundary'};
+ delete $args->{'alternatives'};
+
+}
+
+# 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],
+ '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 = qw(
+ bug_id
+ bug_severity
+ priority
+ rep_platform
+ assigned_to
+ bug_status
+ resolution
+ short_desc
+ );
+ # 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);
+ my $search = new Bugzilla::Search(
+ 'fields' => \@searchfields,
+ 'params' => $searchparams,
+ 'user' => $args->{'recipient'}, # the search runs as the recipient
+ );
+ my $sqlquery = $search->getSQL();
+ $sth = $dbh->prepare($sqlquery);
+ $sth->execute;
+
+ while (my @row = $sth->fetchrow_array) {
+ 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'},
+ '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;
+}
+
+# get_query gets the namedquery. It's similar to LookupNamedQuery (in
+# buglist.cgi), but doesn't care if a query name really exists or not, since
+# 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] : '';
+}
+
+# check_today gets a run day from the schedule and sees if it matches today
+# a run day value can contain any of:
+# - a three-letter day of the week
+# - a number for a day of the month
+# - 'last' for the last day of the month
+# - 'All' for every day
+# - '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;
+}
+
+# reset_timer sets the next time a whine is supposed to run, assuming it just
+# ran moments ago. Its only parameter is a schedule ID.
+#
+# 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;
+ }
+ 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);
+
+ $sth = $dbh->prepare("UPDATE whine_schedules " .
+ "SET run_next = (CURRENT_DATE + " .
+ $dbh->sql_interval('?', 'DAY') . ") + " .
+ $dbh->sql_interval('?', 'HOUR') .
+ " 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 NOW() + ' .
+ $dbh->sql_interval('?', '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);
+}
+
+# get_next_date determines the difference in days between now and the next
+# time a schedule should run, excluding today
+#
+# 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 $add_days = 0;
+
+ if ($day eq 'All') {
+ $add_days = 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;
+ }
+ }
+ 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
+ # 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;
+
+ 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;
+ }
+ }
+ 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;
+}
diff --git a/whineatnews.pl b/whineatnews.pl
new file mode 100755
index 000000000..19b0e44fc
--- /dev/null
+++ b/whineatnews.pl
@@ -0,0 +1,98 @@
+#!/usr/bin/perl -w
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Joseph Heenan <joseph@heenan.me.uk>
+# Frédéric Buclin <LpSolit@gmail.com>
+
+
+# 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
+# Bugzillas.)
+
+use strict;
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Mailer;
+use Bugzilla::Util;
+use Bugzilla::User;
+
+# Whining is disabled if whinedays is zero
+exit unless Bugzilla->params->{'whinedays'} >= 1;
+
+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";
+
+my %bugs;
+my %desc;
+
+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;
+}
+
+
+foreach my $email (sort (keys %bugs)) {
+ my $user = new Bugzilla::User({name => $email});
+ next if $user->email_disabled;
+
+ 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 $msg;
+ my $template = Bugzilla->template_inner($user->settings->{'lang'}->{'value'});
+ $template->process("email/whine.txt.tmpl", $vars, \$msg)
+ or die($template->error());
+
+ MessageToMTA($msg);
+
+ print "$email " . join(" ", @{$bugs{$email}}) . "\n";
+}
diff --git a/xml.cgi b/xml.cgi
new file mode 100755
index 000000000..ce6a7c39b
--- /dev/null
+++ b/xml.cgi
@@ -0,0 +1,41 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Dawn Endico <endico@mozilla.org>
+# Terry Weissman <terry@mozilla.org>
+# Gervase Markham <gerv@gerv.net>
+
+use strict;
+
+use lib qw(. lib);
+use Bugzilla;
+
+my $cgi = Bugzilla->cgi;
+
+# Convert comma/space separated elements into separate params
+my @ids = ();
+
+if (defined $cgi->param('id')) {
+ @ids = split (/[, ]+/, $cgi->param('id'));
+}
+
+my $ids = join('', map { $_ = "&id=" . $_ } @ids);
+
+print $cgi->redirect("show_bug.cgi?ctype=xml$ids");
diff --git a/xmlrpc.cgi b/xmlrpc.cgi
new file mode 100755
index 000000000..8b1e69f28
--- /dev/null
+++ b/xmlrpc.cgi
@@ -0,0 +1,48 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s): Marc Schumann <wurblzap@gmail.com>
+
+use strict;
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::WebService::Constants;
+BEGIN {
+ if (!Bugzilla->feature('xmlrpc')) {
+ ThrowCodeError('feature_disabled', { feature => 'xmlrpc' });
+ }
+}
+use Bugzilla::WebService::Server::XMLRPC;
+
+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;
+$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
+# for setting SOAPAction, which isn't used by XML-RPC.
+$server->on_action(sub { $server->handle_login(WS_DISPATCH, @_) })
+ ->handle();
diff --git a/xt/README b/xt/README
new file mode 100644
index 000000000..22f9f171b
--- /dev/null
+++ b/xt/README
@@ -0,0 +1,18 @@
+The tests in this directory require a working database, as opposed
+to the tests in t/, which simply test the code without a working
+installation.
+
+Some of the tests may modify your current working installation, even
+if only temporarily. To run the tests that modify your database,
+set the environment variable BZ_WRITE_TESTS to 1.
+
+Some tests also take additional, optional arguments. You can pass arguments
+to tests like:
+
+ prove xt/search.t :: --long --operators=equals,notequals
+
+Note the "::"--that is necessary to note that the arguments are going to
+the test, not to "prove".
+
+See the perldoc of the individual tests to see what options they support,
+or do "perl xt/search.t --help".
diff --git a/xt/lib/Bugzilla/Test/Search.pm b/xt/lib/Bugzilla/Test/Search.pm
new file mode 100644
index 000000000..38004134c
--- /dev/null
+++ b/xt/lib/Bugzilla/Test/Search.pm
@@ -0,0 +1,942 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by the Initial Developer are Copyright (C) 2010 the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+# This module tests Bugzilla/Search.pm. It uses various constants
+# that are in Bugzilla::Test::Search::Constants, in xt/lib/.
+#
+# It does this by:
+# 1) Creating a bunch of field values. Each field value is
+# randomly named and fully unique.
+# 2) Creating a bunch of bugs that use those unique field
+# values. Each bug has different characteristics--see
+# the comment above the NUM_BUGS constant for a description
+# of each bug.
+# 3) Running searches using the combination of every search operator against
+# every field. The tests that we run are described by the TESTS constant.
+# Some of the operator/field combinations are known to be broken--
+# these are listed in the KNOWN_BROKEN constant.
+# 4) For each search, we make sure that certain bugs are contained in
+# the search, and certain other bugs are not contained in the search.
+# The code for the operator/field tests is mostly in
+# Bugzilla::Test::Search::FieldTest.
+# 5) After testing each operator/field combination's functionality, we
+# do additional tests to make sure that there are no SQL injections
+# possible via any operator/field combination. The code for the
+# SQL Injection tests is in Bugzilla::Test::Search::InjectionTest.
+#
+# Generally, the only way that you should modify the behavior of this
+# script is by modifying the constants.
+
+package Bugzilla::Test::Search;
+
+use strict;
+use warnings;
+use Bugzilla::Attachment;
+use Bugzilla::Bug ();
+use Bugzilla::Constants;
+use Bugzilla::Field;
+use Bugzilla::Field::Choice;
+use Bugzilla::FlagType;
+use Bugzilla::Group;
+use Bugzilla::Install ();
+use Bugzilla::Test::Search::Constants;
+use Bugzilla::Test::Search::OperatorTest;
+use Bugzilla::User ();
+use Bugzilla::Util qw(generate_random_password);
+
+use Carp;
+use DateTime;
+use Scalar::Util qw(blessed);
+
+###############
+# Constructor #
+###############
+
+sub new {
+ my ($class, $options) = @_;
+ return bless { options => $options }, $class;
+}
+
+#############
+# Accessors #
+#############
+
+sub options { return $_[0]->{options} }
+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;
+ my $operator_field_tests = ($top_combinations + $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;
+
+ return $operator_field_tests + $sql_injection_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;
+
+}
+
+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;
+ }
+ return @{ $self->{all_operators} };
+}
+
+sub all_fields {
+ my $self = shift;
+ if (not $self->{all_fields}) {
+ $self->_create_custom_fields();
+ my @fields = Bugzilla->get_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;
+ }
+ 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;
+}
+
+sub 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];
+}
+
+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);
+}
+
+sub 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};
+}
+
+sub bug_create_value {
+ my ($self, $number, $field) = @_;
+ $field = $field->name if blessed($field);
+ if ($number == 6 and $field ne 'alias') {
+ $number = 1;
+ }
+ my $value = $self->_bug_create_values->{$number}->{$field};
+ return $value if defined $value;
+ return $self->_extra_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};
+}
+
+# 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};
+}
+# 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};
+}
+
+# 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};
+}
+
+##############################
+# General Helper Subroutines #
+##############################
+
+sub random {
+ $_[0] ||= FIELD_SIZE;
+ generate_random_password(@_);
+}
+
+# We need to use a custom timestamp for each create() and update(),
+# because the database returns the same value for LOCALTIMESTAMP(0)
+# 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',
+ );
+}
+
+sub create_keyword {
+ 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;
+}
+
+sub create_group {
+ 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 });
+}
+
+#########################
+# Custom Field Creation #
+#########################
+
+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,
+ });
+ }
+}
+
+########################
+# Field Value Creation #
+########################
+
+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;
+ }
+
+ 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;
+ }
+ }
+
+ $product->update();
+
+ return \%values;
+}
+
+# Flags
+sub _create_flags {
+ my ($number, $setter, $requestee) = @_;
+
+ my $flagtypes = _create_flagtypes($number);
+
+ 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
+ (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;
+}
+
+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;
+ }
+ 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;
+}
+
+################
+# Bug Creation #
+################
+
+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.
+ $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)};
+
+ 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))
+ {
+ $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;
+ }
+ 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) VALUES (?,?)',
+ undef, $bug->id, $see_also);
+
+ 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);
+ }
+
+ 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_add = $update_params{cc};
+ $cc_add = [$cc_add] if !ref $cc_add;
+ $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);
+ }
+
+ # Values for changedfrom.
+ $extra_values->{creation_ts} = $bug->creation_ts;
+ $extra_values->{delta_ts} = $bug->creation_ts;
+
+ return new Bugzilla::Bug($bug->id);
+}
+
+###################################
+# Test::Builder Memory Efficiency #
+###################################
+
+# Test::Builder stores information for each test run, but Test::Harness
+# and TAP::Harness don't actually need this information. When we run 60
+# million tests, the history eats up all our memory. (After about
+# 1 million tests, memory usage is around 1 GB.)
+#
+# The only part of the history that Test::More actually *uses* is the "ok"
+# 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;
+}
+
+sub 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;
+}
+
+##########
+# Caches #
+##########
+
+# When doing AND and OR tests, we essentially test the same field/operator
+# combinations over and over. So, if we're going to be running those tests,
+# we cache the translated_value of the FieldTests globally so that we don't
+# 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};
+}
+
+#############
+# Main Test #
+#############
+
+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->{'usebugaliases'} = 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 $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();
+}
+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);
+ }
+ }
+}
+
+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);
+ }
+ }
+}
+
+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);
+}
+
+1;
diff --git a/xt/lib/Bugzilla/Test/Search/AndTest.pm b/xt/lib/Bugzilla/Test/Search/AndTest.pm
new file mode 100644
index 000000000..d7b21af48
--- /dev/null
+++ b/xt/lib/Bugzilla/Test/Search/AndTest.pm
@@ -0,0 +1,69 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by the Initial Developer are Copyright (C) 2010 the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+# This test combines two field/operator combinations using AND in
+# a single boolean chart.
+package Bugzilla::Test::Search::AndTest;
+use base qw(Bugzilla::Test::Search::OrTest);
+
+use Bugzilla::Test::Search::Constants;
+use Bugzilla::Test::Search::FakeCGI;
+use List::MoreUtils qw(all);
+
+use constant type => 'AND';
+
+#############
+# Accessors #
+#############
+
+# 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;
+}
+
+########################
+# SKIP & TODO Messages #
+########################
+
+sub _join_skip { () }
+sub _join_broken_constant { {} }
+
+##############################
+# Bugzilla::Search arguments #
+##############################
+
+sub search_params {
+ my ($self) = @_;
+ my @all_params = map { $_->search_params } $self->field_tests;
+ my $params = new Bugzilla::Test::Search::FakeCGI;
+ my $chart = 0;
+ foreach my $item (@all_params) {
+ $params->param("field0-$chart-0", $item->param('field0-0-0'));
+ $params->param("type0-$chart-0", $item->param('type0-0-0'));
+ $params->param("value0-$chart-0", $item->param('value0-0-0'));
+ $chart++;
+ }
+ return $params;
+}
+
+1; \ No newline at end of file
diff --git a/xt/lib/Bugzilla/Test/Search/Constants.pm b/xt/lib/Bugzilla/Test/Search/Constants.pm
new file mode 100644
index 000000000..01a27a931
--- /dev/null
+++ b/xt/lib/Bugzilla/Test/Search/Constants.pm
@@ -0,0 +1,1040 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by the Initial Developer are Copyright (C) 2010 the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+
+# These are constants used by Bugzilla::Test::Search.
+# See the comment at the top of that package for a general overview
+# of how the search test works, and how the constants are used.
+# More detailed information on each constant is available in the comments
+# in this file.
+package Bugzilla::Test::Search::Constants;
+use base qw(Exporter);
+use Bugzilla::Constants;
+
+our @EXPORT = qw(
+ ATTACHMENT_FIELDS
+ COLUMN_TRANSLATION
+ COMMENT_FIELDS
+ CUSTOM_FIELDS
+ FIELD_SIZE
+ FIELD_SUBSTR_SIZE
+ FLAG_FIELDS
+ INJECTION_BROKEN_FIELD
+ INJECTION_BROKEN_OPERATOR
+ INJECTION_TESTS
+ KNOWN_BROKEN
+ NUM_BUGS
+ NUM_SEARCH_TESTS
+ OR_BROKEN
+ OR_SKIP
+ SKIP_FIELDS
+ 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
+# multiple values for several fields where other fields only have
+# one value.
+#
+# Bug 2 and 3 have a dependency relationship with Bug 1,
+# but show up in "not equals" tests. We do use bug 2 in multiple-value
+# tests.
+#
+# Bug 4 should never show up in any equals test, and has no relationship
+# with any other bug. However, it does have all its fields set.
+#
+# Bug 5 only has values set for mandatory fields, to expose problems
+# that happen with "not equals" tests failing to catch bugs that don't
+# have a value set at all.
+#
+# Bug 6 is a clone of Bug 1, but is in a group that the searcher isn't
+# in.
+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;
+
+# This is how many random characters we generate for most fields' names.
+# (Some fields can't be this long, though, so they have custom lengths
+# in Bugzilla::Test::Search).
+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',
+};
+
+# This translates fielddefs names into Search column names.
+use constant COLUMN_TRANSLATION => {
+ 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',
+ work_time => 'work_time',
+ commenter => 'author',
+ 'longdescs.isprivate' => 'is_private',
+};
+
+# Same as above, for Bugzilla::Attachment.
+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',
+};
+
+# These are fields that we don't test. Test::More will mark these
+# "TODO & SKIP", and not run tests for them at all.
+#
+# attachments.isurl can't easily be supported by us, but it's basically
+# identical to isprivate and isobsolete for searching, so that's not a big
+# loss.
+#
+# We don't support days_elapsed or owner_idle_time yet.
+use constant SKIP_FIELDS => qw(
+ attachments.isurl
+ owner_idle_time
+ days_elapsed
+);
+
+# During OR tests, we skip these fields. They basically just don't work
+# right in OR tests, and it's too much work to document the exact tests
+# that they cause to fail.
+use constant OR_SKIP => qw(
+ percentage_complete
+ flagtypes.name
+);
+
+# All the fields that represent users.
+use constant USER_FIELDS => qw(
+ assigned_to
+ reporter
+ qa_contact
+ commenter
+ attachments.submitter
+ setters.login_name
+ requestees.login_name cc
+);
+
+# 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 => 7,
+ 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
+# the SUBSTR_SIZE specified above to determine how large of a substring
+# 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
+);
+
+################
+# Known Broken #
+################
+
+# See the KNOWN_BROKEN constant for a general description of these
+# "_BROKEN" constants.
+
+# Search.pm currently enforces "this must be a 0 or 1" in situations
+# where it should not, with two of the attachment booleans.
+use constant ATTACHMENT_BOOLEANS_SEARCH_BROKEN => (
+ 'attachments.ispatch' => { search => 1 },
+ 'attachments.isobsolete' => { search => 1 },
+);
+
+# Sometimes the search for attachment booleans works, but then contains
+# the wrong results, because it does not contain bugs that fully lack
+# attachments.
+use constant ATTACHMENT_BOOLEANS_CONTAINS_BROKEN => (
+ 'attachments.isobsolete' => { contains => [5] },
+ 'attachments.ispatch' => { contains => [5] },
+ 'attachments.isprivate' => { contains => [5] },
+);
+
+# Certain fields fail all the "negative" search tests:
+#
+# Blocked and Dependson "notequals" only finds bugs that have
+# values for the field, but where the dependency list doesn't contain
+# the bug you listed. It doesn't find bugs that fully lack values for
+# the fields, as it should.
+#
+# cc "not" matches if any CC'ed user matches, and it fails to match
+# if there are no CCs on the bug.
+#
+# bug_group notequals doesn't find bugs that fully lack groups,
+# and matches if there is one group that isn't equal.
+#
+# bug_file_loc can be NULL, so it gets missed by the normal
+# notequals search.
+#
+# longdescs "notequals" matches if *any* of the values
+# are not equal to the string provided.
+#
+# attachments.* notequals doesn't find bugs that lack attachments.
+#
+# deadline notequals does not find bugs that lack deadlines
+#
+# setters notequal doesn't find bugs that fully lack flags.
+# (maybe this is OK?)
+#
+# requestees.login_name doesn't find bugs that fully lack requestees.
+use constant NEGATIVE_BROKEN => (
+ ATTACHMENT_BOOLEANS_CONTAINS_BROKEN,
+ 'attach_data.thedata' => { contains => [5] },
+ 'attachments.description' => { contains => [5] },
+ 'attachments.filename' => { contains => [5] },
+ 'attachments.mimetype' => { contains => [5] },
+ 'attachments.submitter' => { contains => [5] },
+ blocked => { contains => [3,4,5] },
+ bug_file_loc => { contains => [5] },
+ bug_group => { contains => [1,5] },
+ cc => { contains => [1,5] },
+ deadline => { contains => [5] },
+ dependson => { contains => [2,4,5] },
+ longdesc => { contains => [1] },
+ 'longdescs.isprivate' => { contains => [1] },
+ percentage_complete => { contains => [1] },
+ 'requestees.login_name' => { contains => [3,4,5] },
+ 'setters.login_name' => { contains => [5] },
+ work_time => { contains => [1] },
+ # Custom fields are busted because they can be NULL.
+ FIELD_TYPE_FREETEXT, { contains => [5] },
+ FIELD_TYPE_BUG_ID, { contains => [5] },
+ FIELD_TYPE_DATETIME, { contains => [5] },
+ FIELD_TYPE_TEXTAREA, { contains => [5] },
+);
+
+# Shared between greaterthan and greaterthaneq.
+#
+# As with other fields, longdescs greaterthan matches if any comment
+# matches (which might be OK).
+#
+# Same for keywords, bug_group, and cc. Logically, all of these might
+# be OK, but it makes the operation not the logical reverse of
+# 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 => (
+ bug_group => { contains => [1] },
+ cc => { contains => [1] },
+ keywords => { contains => [1] },
+ longdesc => { contains => [1] },
+ FIELD_TYPE_MULTI_SELECT, { contains => [1] },
+);
+
+# allwords and allwordssubstr have these broken tests in common.
+#
+# allwordssubstr work_time only matches against a single comment,
+# instead of matching against all comments on a bug. Same is true
+# for the other longdesc fields, cc, keywords, and bug_group.
+#
+# percentage_complete just drops in 0=0 for the term.
+use constant ALLWORDS_BROKEN => (
+ ATTACHMENT_BOOLEANS_SEARCH_BROKEN,
+ bug_group => { contains => [1] },
+ cc => { contains => [1] },
+ longdesc => { contains => [1] },
+ work_time => { contains => [1] },
+ percentage_complete => { contains => [2,3,4,5] },
+);
+
+# nowords and nowordssubstr have these broken tests in common.
+#
+# flagtypes.name doesn't match bugs without flags.
+# cc, longdescs.isprivate, and bug_group actually work properly in
+# terms of excluding bug 1 (since we exclude all values in the search,
+# on our test), but still fail at including bug 5.
+# The longdesc* and work_time fields, coincidentally, work completely
+# correctly, possibly because there's only one comment on bug 5.
+use constant NOWORDS_BROKEN => (
+ NEGATIVE_BROKEN,
+ 'flagtypes.name' => { contains => [5] },
+ bug_group => { contains => [5] },
+ cc => { contains => [5] },
+ longdesc => {},
+ work_time => {},
+ 'longdescs.isprivate' => {},
+);
+
+# 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 => [2,3,4,5] },
+ '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] },
+ work_time => { contains => [1] },
+ FIELD_TYPE_MULTI_SELECT, { contains => [1] },
+);
+
+
+# Any test listed in KNOWN_BROKEN gets marked TODO by Test::More
+# (using some complex code in Bugzilla::Test::Seach::FieldTest).
+# This means that if you run the test under "prove -v", these tests will
+# still show up as "not ok", but the test suite results won't show them
+# as a failure.
+#
+# This constant contains operators as keys, which point to hashes. The hashes
+# have field names as keys. Each field name points to a hash describing
+# how that field/operator combination is broken. The "contains"
+# array specifies that that particular "contains" test is expected
+# to fail. If "search" is set to 1, then we expect the creation of the
+# Bugzilla::Search object to fail.
+#
+# To allow handling custom fields, you can also use the field type as a key
+# instead of the field name. Specifying explicit field names always overrides
+# specifying a field type.
+#
+# Sometimes the operators have multiple tests, and one of them works
+# while the other fails. In this case, we have a special override for
+# "operator-value", which uniquely identifies tests.
+use constant KNOWN_BROKEN => {
+ notequals => { NEGATIVE_BROKEN },
+ # percentage_complete substring matches every bug, regardless of
+ # its percentage_complete value.
+ substring => {
+ percentage_complete => { contains => [2,3,4,5] },
+ },
+ casesubstring => {
+ percentage_complete => { contains => [2,3,4,5] },
+ },
+ notsubstring => { NEGATIVE_BROKEN },
+
+ # Attachment noolean fields don't work with regexes, right now,
+ # because they throw an error that regexes are not valid booleans.
+ 'regexp-^1-' => { ATTACHMENT_BOOLEANS_SEARCH_BROKEN },
+
+ # percentage_complete notregexp fails to match bugs that
+ # fully lack hours worked.
+ notregexp => {
+ NEGATIVE_BROKEN,
+ percentage_complete => { contains => [5] },
+ },
+ 'notregexp-^1-' => { ATTACHMENT_BOOLEANS_SEARCH_BROKEN },
+
+ # percentage_complete doesn't match bugs with 0 hours worked or remaining.
+ #
+ # longdescs.isprivate matches if any comment matches, instead of if all
+ # comments match. Same for longdescs and work_time. (Commenter is probably
+ # also broken in this way, but all our comments come from the same user.)
+ # Also, the attachments ones don't find bugs that have no attachments
+ # at all (which might be OK?).
+ #
+ # attachments.isprivate lessthan doesn't find bugs without attachments.
+ lessthan => {
+ ATTACHMENT_BOOLEANS_SEARCH_BROKEN,
+ 'attachments.isprivate' => { contains => [5] },
+ 'longdescs.isprivate' => { contains => [1] },
+ percentage_complete => { contains => [5] },
+ work_time => { contains => [1,2,3,4] },
+ },
+ # The lessthaneq tests are broken for the same reasons, but they work
+ # slightly differently so they have a different set of broken tests.
+ lessthaneq => {
+ ATTACHMENT_BOOLEANS_CONTAINS_BROKEN,
+ 'longdescs.isprivate' => { contains => [1] },
+ work_time => { contains => [2,3,4] },
+ },
+
+ greaterthan => { GREATERTHAN_BROKEN },
+
+ # percentage_complete is broken -- it won't match equal values.
+ greaterthaneq => {
+ GREATERTHAN_BROKEN,
+ percentage_complete => { contains => [2] },
+ },
+
+ # percentage_complete just throws 0=0 into the search term, returning
+ # all bugs.
+ anyexact => {
+ ATTACHMENT_BOOLEANS_SEARCH_BROKEN,
+ percentage_complete => { contains => [3,4,5] },
+ },
+ # bug_group anywordssubstr returns all our bugs. Not sure why.
+ anywordssubstr => {
+ ATTACHMENT_BOOLEANS_SEARCH_BROKEN,
+ percentage_complete => { contains => [3,4,5] },
+ bug_group => { contains => [3,4,5] },
+ },
+
+ 'allwordssubstr-<1>' => {
+ ALLWORDS_BROKEN,
+ keywords => { contains => [1] }
+ },
+ 'allwordssubstr-<1>,<2>' => {
+ ATTACHMENT_BOOLEANS_SEARCH_BROKEN,
+ percentage_complete => { contains => [1,2,3,4,5] },
+ },
+ # flagtypes.name does not work here, probably because they all try to
+ # match against a single flag.
+ # Same for attach_data.thedata.
+ 'allwords-<1>' => {
+ ALLWORDS_BROKEN,
+ 'attach_data.thedata' => { contains => [1] },
+ 'flagtypes.name' => { contains => [1] },
+ },
+ 'allwords-<1> <2>' => {
+ ATTACHMENT_BOOLEANS_SEARCH_BROKEN,
+ percentage_complete => { contains => [1,2,3,4,5] },
+ },
+
+ nowordssubstr => { NOWORDS_BROKEN },
+ # attach_data.thedata doesn't match properly with any of the plain
+ # "words" searches. Also, bug 5 doesn't match because it lacks
+ # attachments.
+ nowords => {
+ NOWORDS_BROKEN,
+ 'attach_data.thedata' => { contains => [1,5] },
+ },
+
+ # anywords searches don't work on decimal values.
+ # bug_group anywords returns all bugs.
+ # attach_data doesn't work (perhaps because it's the entire
+ # data, or some problem with the regex?).
+ anywords => {
+ ATTACHMENT_BOOLEANS_SEARCH_BROKEN,
+ 'attach_data.thedata' => { contains => [1] },
+ bug_group => { contains => [2,3,4,5] },
+ percentage_complete => { contains => [2,3,4,5] },
+ work_time => { contains => [1] },
+ },
+ 'anywords-<1> <2>' => {
+ bug_group => { contains => [3,4,5] },
+ percentage_complete => { contains => [3,4,5] },
+ 'attach_data.thedata' => { contains => [1,2] },
+ work_time => { contains => [1,2] },
+ },
+
+ # 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] },
+ creation_ts => { contains => [1,5] },
+ # attachments.* finds values where the date matches exactly.
+ 'attachments.description' => { contains => [2] },
+ 'attachments.filename' => { contains => [2] },
+ 'attachments.isobsolete' => { contains => [2] },
+ 'attachments.ispatch' => { contains => [2] },
+ 'attachments.isprivate' => { contains => [2] },
+ 'attachments.mimetype' => { contains => [2] },
+ },
+ 'changedafter' => {
+ 'attach_data.thedata' => { contains => [2,3,4] },
+ classification => { contains => [2,3,4] },
+ commenter => { contains => [2,3,4] },
+ creation_ts => { contains => [2,3,4] },
+ delta_ts => { contains => [2,3,4] },
+ percentage_complete => { contains => [1,5] },
+ '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] },
+ dependson => { contains => [2,4,5] },
+ FIELD_TYPE_BUG_ID, { contains => [5] },
+ },
+ # changeto doesn't find work_time changes (probably due to decimal/string
+ # stuff). Same for remaining_time and estimated_time.
+ #
+ # 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] },
+ },
+};
+
+#############
+# Overrides #
+#############
+
+# These overrides are used in the TESTS constant, below.
+
+# 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.isprivate' => { value => '^1' },
+ cclist_accessible => { value => '^1' },
+ reporter_accessible => { value => '^1' },
+ everconfirmed => { value => '^1' },
+ '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.0' },
+ 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] },
+);
+
+# 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_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] },
+ 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] },
+ 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] },
+);
+
+# 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] },
+);
+
+# 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] },
+);
+
+# For anyexact and anywordssubstr
+use constant ANY_OVERRIDE => (
+ '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 => [] },
+);
+
+#########
+# Tests #
+#########
+
+# The basic format of this is a hashref, where the keys are operators,
+# and each operator has an arrayref of tests that it runs. The tests
+# are hashrefs, with the following possible keys:
+#
+# contains: This is a list of bug numbers that the search is expected
+# to contain. (This is bug numbers, like 1,2,3, not the bug
+# ids. For a description of each bug number, see NUM_BUGS.)
+# Any bug not listed in "contains" must *not* show up in the
+# search result.
+# value: The value that you're searching for. There are certain special
+# codes that will be replaced with bug values when the tests are
+# run. In these examples below, "#" indicates a bug number:
+#
+# <#> - The field value for this bug.
+#
+# For any operator that has the string "word" in it, this is
+# *all* the values for the current field from the numbered bug,
+# joined by a space.
+#
+# If the operator has the string "substr" in it, then we
+# take a substring of the value (for single-value searches)
+# or we take a substring of each value and join them (for
+# multi-value "word" searches). The length of the substring
+# is determined by the SUBSTR_SIZE constants above.)
+#
+# For other operators, this just becomes the first value from
+# the field for the numbered bug.
+#
+# So, if we were running the "equals" test and checking the
+# cc field, <1> would become the login name of the first cc on
+# Bug 1. If we did an "anywords" search test, it would become
+# a space-separated string of the login names of all the ccs
+# 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.
+#
+# escape: If true, we will call quotemeta() on the value immediately
+# before passing it to Search.pm.
+#
+# transform: A function to call on any field value before inserting
+# it for a <#> replacement. The transformation function
+# gets all of the bug's values for the field as its arguments.
+# if_equal: This allows you to override "contains" for the case where
+# the transformed value (from calling the "transform" function)
+# is equal to the original value.
+#
+# 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>' },
+ ],
+ casesubstring => [
+ { contains => [1], value => '<1>' },
+ { contains => [], value => '<1>', transform => sub { lc($_[0]) },
+ extra_name => 'lc', if_equal => { contains => [1] } },
+ ],
+ notsubstring => [
+ { contains => [2,3,4,5], value => '<1>' },
+ ],
+ regexp => [
+ { contains => [1], value => '<1>', escape => 1 },
+ { contains => [1], value => '^1-', override => REGEX_OVERRIDE },
+ ],
+ notregexp => [
+ { contains => [2,3,4,5], value => '<1>', escape => 1 },
+ { 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-' },
+ 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,5] },
+ cclist_accessible => { value => 1, contains => [2,3,4,5] },
+ reporter_accessible => { value => 1, contains => [2,3,4,5] },
+ 'longdescs.isprivate' => { value => 1, contains => [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' },
+ 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>' },
+ FIELD_TYPE_DATETIME, { value => '2037-03-02' },
+ LESSTHAN_OVERRIDE,
+ }
+ },
+ ],
+ lessthaneq => [
+ { contains => [1], value => '<1>',
+ override => {
+ 'attachments.ispatch' => { value => 0, contains => [2,3,4,5] },
+ 'attachments.isobsolete' => { value => 0, contains => [2,3,4,5] },
+ 'attachments.isprivate' => { value => 0, contains => [2,3,4,5] },
+ cclist_accessible => { value => 0, contains => [2,3,4,5] },
+ reporter_accessible => { value => 0, contains => [2,3,4,5] },
+ 'longdescs.isprivate' => { value => 0, contains => [2,3,4,5] },
+ everconfirmed => { value => 0, contains => [2,3,4,5] },
+ blocked => { contains => [1,2] },
+ dependson => { contains => [1,3] },
+ creation_ts => { contains => [1,5] },
+ delta_ts => { contains => [1,5] },
+ remaining_time => { contains => [1,5] },
+ longdesc => { contains => [1,5] },
+ work_time => { value => 1, 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.isprivate' => { value => 0, contains => [1] },
+ everconfirmed => { value => 0, contains => [1] },
+ 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.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 } },
+ ],
+ allwordssubstr => [
+ { contains => [1], value => '<1>',
+ override => { MULTI_BOOLEAN_OVERRIDE } },
+ { contains => [], value => '<1>,<2>',
+ override => {
+ dependson => { value => '<1-id> <3-id>', contains => [] },
+ }
+ },
+ ],
+ nowordssubstr => [
+ { contains => [2,3,4,5], value => '<1>',
+ override => {
+ # longdescs.isprivate translates to "1 0", so no bugs should
+ # show up.
+ 'longdescs.isprivate' => { contains => [] },
+ # 1.0 0.0 exludes bug 5.
+ # XXX However, it also shouldn't match 2, 3, or 4, because
+ # they contain at least one comment with 0.0 work_time.
+ work_time => { contains => [2,3,4] },
+ }
+ },
+ ],
+ anywords => [
+ { contains => [1], value => '<1>',
+ override => {
+ MULTI_BOOLEAN_OVERRIDE,
+ work_time => { value => '1.0', contains => [1] },
+ }
+ },
+ { contains => [1,2], value => '<1> <2>',
+ override => {
+ MULTI_BOOLEAN_OVERRIDE,
+ dependson => { value => '<1> <3>', contains => [1,3] },
+ work_time => { value => '1.0 2.0' },
+ },
+ },
+ ],
+ allwords => [
+ { contains => [1], value => '<1>',
+ override => { MULTI_BOOLEAN_OVERRIDE } },
+ { contains => [], value => '<1> <2>',
+ override => {
+ dependson => { contains => [], value => '<2-id> <3-id>' }
+ }
+ },
+ ],
+ nowords => [
+ { contains => [2,3,4,5], value => '<1>',
+ override => {
+ # longdescs.isprivate translates to "1 0", so no bugs should
+ # show up.
+ 'longdescs.isprivate' => { contains => [] },
+ # 1.0 0.0 exludes bug 5.
+ # XXX However, it also shouldn't match 2, 3, or 4, because
+ # they contain at least one comment with 0.0 work_time.
+ work_time => { contains => [2,3,4] },
+ }
+ },
+ ],
+
+ changedbefore => [
+ { contains => [1], value => '<2-delta>',
+ override => {
+ CHANGED_OVERRIDE,
+ creation_ts => { contains => [1,5] },
+ blocked => { contains => [1,2] },
+ dependson => { contains => [1,3] },
+ longdesc => { contains => [1,2,5] },
+ }
+ },
+ ],
+ changedafter => [
+ { contains => [2,3,4], value => '<1-delta>',
+ override => {
+ CHANGED_OVERRIDE,
+ creation_ts => { contains => [2,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] },
+ },
+ },
+ ],
+};
+
+# Fields that do not behave as we expect, for InjectionTest.
+# search => 1 means the Bugzilla::Search creation fails.
+# sql_error is a regex that specifies a SQL error that's OK for us to throw.
+# operator_ok overrides the "brokenness" of certain operators, so that they
+# are always OK for that field/operator combination.
+use constant INJECTION_BROKEN_FIELD => {
+ 'attachments.isobsolete' => { search => 1 },
+ 'attachments.ispatch' => { search => 1 },
+ owner_idle_time => {
+ sql_error => qr/bugs\.owner_idle_time.+where clause/,
+ operator_ok => [qw(changedfrom changedto greaterthan greaterthaneq
+ lessthan lessthaneq)]
+ },
+ keywords => {
+ search => 1,
+ operator_ok => [qw(allwordssubstr anywordssubstr casesubstring
+ changedfrom changedto greaterthan greaterthaneq
+ lessthan lessthaneq notregexp notsubstring
+ nowordssubstr regexp substring anywords
+ notequals nowords)]
+ },
+};
+
+# 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 => ['percentage_complete'] },
+ changedbefore => { search => 1, field_ok => ['percentage_complete'] },
+ changedby => { search => 1, field_ok => ['percentage_complete'] },
+};
+
+# 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' }
+);
+
+# This overrides KNOWN_BROKEN for OR configurations.
+# It indicates that these combinations are broken in some way that they
+# aren't broken when alone, because they don't return what they logically
+# should when put into an OR.
+use constant OR_BROKEN => {
+ # Multi-value fields search on individual values, so "equals" OR "notequals"
+ # returns nothing, when it should instead logically return everything.
+ 'blocked-equals' => {
+ 'blocked-notequals' => { contains => [1,2,3,4,5] },
+ },
+ 'dependson-equals' => {
+ 'dependson-notequals' => { contains => [1,2,3,4,5] },
+ },
+ 'bug_group-equals' => {
+ 'bug_group-notequals' => { contains => [1,2,3,4,5] },
+ },
+ 'cc-equals' => {
+ 'cc-notequals' => { contains => [1,2,3,4,5] },
+ },
+ 'commenter-equals' => {
+ 'commenter-notequals' => { contains => [1,2,3,4,5] },
+ 'longdesc-notequals' => { contains => [2,3,4,5] },
+ 'longdescs.isprivate-notequals' => { contains => [2,3,4,5] },
+ 'work_time-notequals' => { contains => [2,3,4,5] },
+ },
+ 'commenter-notequals' => {
+ 'commenter-equals' => { contains => [1,2,3,4,5] },
+ 'longdesc-equals' => { contains => [1] },
+ 'longdescs.isprivate-equals' => { contains => [1] },
+ 'work_time-equals' => { contains => [1] },
+ },
+};
+
+1;
diff --git a/xt/lib/Bugzilla/Test/Search/FakeCGI.pm b/xt/lib/Bugzilla/Test/Search/FakeCGI.pm
new file mode 100644
index 000000000..e20a57daf
--- /dev/null
+++ b/xt/lib/Bugzilla/Test/Search/FakeCGI.pm
@@ -0,0 +1,61 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by the Initial Developer are Copyright (C) 2010 the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+# Calling CGI::param over and over turned out to be one of the slowest
+# parts of search.t. So we create a simpler thing here that just supports
+# "param" in a fast way.
+package Bugzilla::Test::Search::FakeCGI;
+
+sub new {
+ my ($class) = @_;
+ return bless {}, $class;
+}
+
+sub param {
+ my ($self, $name, @values) = @_;
+ if (!defined $name) {
+ return keys %$self;
+ }
+
+ if (@values) {
+ if (ref $values[0] eq 'ARRAY') {
+ $self->{$name} = $values[0];
+ }
+ else {
+ $self->{$name} = \@values;
+ }
+ }
+
+ return () if !exists $self->{$name};
+
+ my $item = $self->{$name};
+ return wantarray ? @{ $item || [] } : $item->[0];
+}
+
+sub delete {
+ my ($self, $name) = @_;
+ delete $self->{$name};
+}
+
+# We don't need to do this, because we don't use old params in search.t.
+sub convert_old_params {}
+
+1; \ No newline at end of file
diff --git a/xt/lib/Bugzilla/Test/Search/FieldTest.pm b/xt/lib/Bugzilla/Test/Search/FieldTest.pm
new file mode 100644
index 000000000..940b48296
--- /dev/null
+++ b/xt/lib/Bugzilla/Test/Search/FieldTest.pm
@@ -0,0 +1,582 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by the Initial Developer are Copyright (C) 2010 the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+# This module represents the tests that get run on a single
+# operator/field combination for Bugzilla::Test::Search.
+# This is where all the actual testing happens.
+package Bugzilla::Test::Search::FieldTest;
+
+use strict;
+use warnings;
+use Bugzilla::Test::Search::FakeCGI;
+use Bugzilla::Search;
+use Bugzilla::Test::Search::Constants;
+
+use Data::Dumper;
+use Scalar::Util qw(blessed);
+use Test::More;
+use Test::Exception;
+
+###############
+# Constructor #
+###############
+
+sub new {
+ my ($class, $operator_test, $field, $test) = @_;
+ return bless { operator_test => $operator_test,
+ field_object => $field,
+ raw_test => $test }, $class;
+}
+
+#############
+# Accessors #
+#############
+
+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) = @_;
+ return $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(@_);
+}
+
+# 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;
+}
+
+# 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};
+}
+
+# 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};
+}
+# The values for this field for the numbered bug.
+sub bug_values {
+ 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} };
+# 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;
+ }
+ return $self->{translated_value};
+}
+# Used in failure diagnostic messages.
+sub debug_value {
+ my ($self) = @_;
+ return "Value: '" . $self->translated_value . "'";
+}
+
+# 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 (defined $value) {
+ $self->{transformed_value_was_equal}->{$number} = $value;
+ }
+ 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)) {
+ $contains = $self->test->{if_equal}->{contains};
+ }
+ return grep($_ == $number, @$contains) ? 1 : 0;
+}
+
+###################################################
+# Accessors: Ways of doing SKIP and TODO on tests #
+###################################################
+
+# The tests we know are broken for this operator/field combination.
+sub _known_broken {
+ my $self = shift;
+ my $field = $self->field;
+ my $type = $self->field_object->type;
+ my $operator = $self->operator;
+ my $value = $self->main_value;
+
+ my $value_name = "$operator-$value";
+ my $value_broken = KNOWN_BROKEN->{$value_name}->{$field};
+ $value_broken ||= KNOWN_BROKEN->{$value_name}->{$type};
+ return $value_broken if $value_broken;
+ my $operator_broken = KNOWN_BROKEN->{$operator}->{$field};
+ $operator_broken ||= KNOWN_BROKEN->{$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 $contains_broken = $self->_known_broken->{contains} || [];
+ if (grep($_ == $number, @$contains_broken)) {
+ return "$field $operator contains $number is known to be broken";
+ }
+ return undef;
+}
+
+# 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;
+}
+
+# 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;
+}
+
+# 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;
+}
+
+# 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};
+ }
+ }
+ return $or_broken;
+}
+
+#########################################
+# Accessors: Bugzilla::Search Arguments #
+#########################################
+
+# The CGI object that will get passed to Bugzilla::Search as its arguments.
+sub search_params {
+ my $self = shift;
+ return $self->{search_params} if $self->{search_params};
+
+ my $field = $self->field;
+ my $operator = $self->operator;
+ my $value = $self->translated_value;
+
+ my $cgi = new Bugzilla::Test::Search::FakeCGI;
+ $cgi->param("field0-0-0", $field);
+ $cgi->param('type0-0-0', $operator);
+ $cgi->param('value0-0-0', $value);
+
+ $self->{search_params} = $cgi;
+ 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;
+}
+
+
+################
+# Field Values #
+################
+
+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 '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');
+ }
+ # 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 $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);
+ }
+
+ 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};
+}
+
+#####################
+# Value Translation #
+#####################
+
+# This function translates the "value" specified in TESTS into an actual
+# search value to pass to Search.pm. This means that we get the value
+# from the current bug (or, in the case of changedfrom, from %original_values)
+# 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;
+}
+
+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;
+
+ 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);
+ # Make percentage_complete substrings strings match integers uniquely,
+ # by searching for the full decimal number.
+ if ($field eq 'percentage_complete' and length($string) < $substr_size) {
+ $string .= ".000";
+ }
+ return $string;
+ }
+ return substr($value, $substr_size);
+}
+
+#####################
+# Main Test Methods #
+#####################
+
+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();
+ }
+ }
+}
+
+sub do_tests {
+ my ($self) = @_;
+ my $name = $self->name;
+
+ my $search_broken = $self->search_known_broken;
+
+ my $search;
+ TODO: {
+ local $TODO = $search_broken if $search_broken;
+ $search = $self->_test_search_object_creation();
+ }
+
+ my ($results, $sql);
+ SKIP: {
+ skip "Can't run SQL without Search object", 2 if !$search;
+ lives_ok { $sql = $search->getSQL() } "$name: get SQL";
+
+ # This prevents warnings from DBD::mysql if we pass undef $sql,
+ # which happens if "new Bugzilla::Search" fails.
+ $sql ||= '';
+ $results = $self->_test_sql($sql);
+ }
+
+ $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;
+}
+
+sub _test_sql {
+ my ($self, $sql) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $name = $self->name;
+ my $results;
+ lives_ok { $results = $dbh->selectall_arrayref($sql) } "$name: Run SQL Query"
+ or diag($sql);
+ return $results;
+}
+
+sub _test_content {
+ 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);
+ }
+ }
+}
+
+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 Dumper($results) . $self->debug_value . "\n\nSQL: $sql";
+ }
+ else {
+ ok(!$result_ids{$bug_id},
+ "$name: does not contain bug $number ($bug_id)")
+ or diag Dumper($results) . $self->debug_value . "\n\nSQL: $sql";
+ }
+ }
+}
+
+1; \ No newline at end of file
diff --git a/xt/lib/Bugzilla/Test/Search/InjectionTest.pm b/xt/lib/Bugzilla/Test/Search/InjectionTest.pm
new file mode 100644
index 000000000..211026232
--- /dev/null
+++ b/xt/lib/Bugzilla/Test/Search/InjectionTest.pm
@@ -0,0 +1,77 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by the Initial Developer are Copyright (C) 2010 the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+# This module represents the SQL Injection tests that get run on a single
+# operator/field combination for Bugzilla::Test::Search.
+package Bugzilla::Test::Search::InjectionTest;
+use base qw(Bugzilla::Test::Search::FieldTest);
+
+use strict;
+use warnings;
+use Bugzilla::Test::Search::Constants;
+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} || [] }
+ : ();
+
+ return {} if grep { $_ eq $self->field } @field_ok;
+
+ my $field_broken = INJECTION_BROKEN_FIELD->{$self->field};
+ # We don't want to auto-vivify $field_broken and thus make it true.
+ my @operator_ok = $field_broken ? @{ $field_broken->{operator_ok} || [] }
+ : ();
+ return {} if grep { $_ eq $self->operator } @operator_ok;
+
+ return $operator_broken || $field_broken || {};
+}
+
+sub sql_error_ok { return $_[0]->_known_broken->{sql_error} }
+
+# Injection tests don't have to skip any fields.
+sub field_not_yet_implemented { 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_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; \ No newline at end of file
diff --git a/xt/lib/Bugzilla/Test/Search/OperatorTest.pm b/xt/lib/Bugzilla/Test/Search/OperatorTest.pm
new file mode 100644
index 000000000..6291fbac1
--- /dev/null
+++ b/xt/lib/Bugzilla/Test/Search/OperatorTest.pm
@@ -0,0 +1,110 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by the Initial Developer are Copyright (C) 2010 the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+# This module represents the tests that get run on a single operator
+# from the TESTS constant in Bugzilla::Search::Test::Constants.
+package Bugzilla::Test::Search::OperatorTest;
+
+use strict;
+use warnings;
+use Bugzilla::Test::Search::Constants;
+use Bugzilla::Test::Search::FieldTest;
+use Bugzilla::Test::Search::InjectionTest;
+use Bugzilla::Test::Search::OrTest;
+use Bugzilla::Test::Search::AndTest;
+
+###############
+# Constructor #
+###############
+
+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;
+}
+
+#############
+# Accessors #
+#############
+
+# 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 } } }
+# 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();
+
+ 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();
+ }
+ }
+}
+
+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();
+}
+
+1; \ No newline at end of file
diff --git a/xt/lib/Bugzilla/Test/Search/OrTest.pm b/xt/lib/Bugzilla/Test/Search/OrTest.pm
new file mode 100644
index 000000000..101e19fd5
--- /dev/null
+++ b/xt/lib/Bugzilla/Test/Search/OrTest.pm
@@ -0,0 +1,186 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by the Initial Developer are Copyright (C) 2010 the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+# This test combines two field/operator combinations using OR in
+# a single boolean chart.
+package Bugzilla::Test::Search::OrTest;
+use base qw(Bugzilla::Test::Search::FieldTest);
+
+use Bugzilla::Test::Search::Constants;
+use Bugzilla::Test::Search::FakeCGI;
+use List::MoreUtils qw(any uniq);
+
+use constant type => 'OR';
+
+###############
+# Constructor #
+###############
+
+sub new {
+ my $class = shift;
+ my $self = { field_tests => [@_] };
+ return bless $self, $class;
+}
+
+#############
+# Accessors #
+#############
+
+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);
+}
+
+# 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;
+}
+
+# Needed only for failure messages
+sub debug_value {
+ my ($self) = @_;
+ my @values = map { $_->field . ' ' . $_->debug_value } $self->field_tests;
+ return join(' ' . $self->type . ' ', @values);
+}
+
+########################
+# SKIP & TODO Messages #
+########################
+
+sub _join_skip { OR_SKIP }
+sub _join_broken_constant { OR_BROKEN }
+
+sub field_not_yet_implemented {
+ my ($self) = @_;
+ foreach my $test ($self->field_tests) {
+ if (grep { $_ eq $test->field } $self->_join_skip) {
+ return $test->field . " is not yet supported in OR tests";
+ }
+ }
+ return $self->_join_messages('field_not_yet_implemented');
+}
+sub 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');
+}
+
+sub _join_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) = @_;
+ my @results;
+ foreach my $test ($self->field_tests) {
+ if ($test->bug_is_contained($number)
+ and !$test->contains_known_broken($number))
+ {
+ return 1;
+ }
+ elsif (!$test->bug_is_contained($number)
+ and $test->contains_known_broken($number)) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+sub contains_known_broken {
+ my ($self, $number) = @_;
+
+ my $join_broken = $self->_join_known_broken;
+ if (my $contains = $join_broken->{contains}) {
+ my $contains_is_broken = grep { $_ == $number } @$contains;
+ if ($contains_is_broken) {
+ my $name = $self->name;
+ return "$name contains $number is broken";
+ }
+ return undef;
+ }
+
+ return $self->_join_contains_known_broken($number);
+}
+
+sub _join_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;
+ return join(' AND ', @messages);
+ }
+ return undef;
+}
+
+sub _join_known_broken {
+ my ($self) = @_;
+ my $or_broken = $self->_join_broken_constant;
+ foreach my $test ($self->field_tests) {
+ @or_broken_for = map { $_->join_broken($or_broken) } $self->field_tests;
+ @or_broken_for = grep { defined $_ } @or_broken_for;
+ last if !@or_broken_for;
+ $or_broken = $or_broken_for[0];
+ }
+ return $or_broken;
+}
+
+##############################
+# Bugzilla::Search arguments #
+##############################
+
+sub search_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 = new Bugzilla::Test::Search::FakeCGI;
+ my $chart = 0;
+ foreach my $item (@all_params) {
+ $params->param("field0-0-$chart", $item->param('field0-0-0'));
+ $params->param("type0-0-$chart", $item->param('type0-0-0'));
+ $params->param("value0-0-$chart", $item->param('value0-0-0'));
+ $chart++;
+ }
+ return $params;
+}
+
+1; \ No newline at end of file
diff --git a/xt/search.t b/xt/search.t
new file mode 100644
index 000000000..bd77f5b20
--- /dev/null
+++ b/xt/search.t
@@ -0,0 +1,96 @@
+#!/usr/bin/perl -w
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by the Initial Developer are Copyright (C) 2010 the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+# For a description of this test, see Bugzilla::Test::Search
+# in xt/lib/.
+
+use strict;
+use warnings;
+use lib qw(. xt/lib lib);
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Test::Search;
+use Getopt::Long;
+use Pod::Usage;
+
+use Test::More;
+
+my %switches;
+GetOptions(\%switches, 'operators=s', 'top-operators=s', 'long',
+ 'add-custom-fields', 'help|h') || die $@;
+
+pod2usage(verbose => 1) if $switches{'help'};
+
+plan skip_all => "BZ_WRITE_TESTS environment variable not set"
+ if !$ENV{BZ_WRITE_TESTS};
+
+Bugzilla->usage_mode(USAGE_MODE_TEST);
+
+my $test = new Bugzilla::Test::Search(\%switches);
+plan tests => $test->num_tests;
+$test->run();
+
+__END__
+
+=head1 NAME
+
+search.t - Test L<Bugzilla::Search>
+
+=head1 DESCRIPTION
+
+This test tests L<Bugzilla::Search>.
+
+Note that users may be prevented from writing new bugs, products, components,
+etc. to your database while this test is running.
+
+=head1 OPTIONS
+
+=over
+
+=item --long
+
+Run AND and OR tests in addition to normal tests. Specifying
+--long without also specifying L</--top-operators> is likely to
+run your system out of memory.
+
+=item --add-custom-fields
+
+This adds every type of custom field to the database, so that they can
+all be tested. Note that this B<CANNOT BE REVERSED>, so do not use this
+switch on a production installation.
+
+=item --operators=a,b,c
+
+Limit the test to testing only the listed operators.
+
+=item --top-operators=a,b,c
+
+Limit the top-level tested operators to the following list. This
+means that for normal tests, only the listed operators will be tested.
+However, for OR and AND tests, all other operators will be tested
+along with the operators you listed.
+
+=item --help
+
+Display this help.
+
+=back